Commit 854fac3d authored by Alex Op's avatar Alex Op
Browse files

Add gradient support to font formatting

- Introduced gradient properties in FontFormat: enabled state, start color, end color, angle, and size.
- Implemented gradient handling functions in fontformat_commands.py.
- Updated funcmaps.py to include new gradient handlers.
- Enhanced FontFormatPanel in text_panel.py with UI controls for gradient settings (enable checkbox, color pickers, angle slider, size slider).
- Modified TextBlkItem in textitem.py to support gradient rendering in the paint method.
- Ensured gradient properties are preserved during font format updates.

This commit enhances the text formatting capabilities by allowing users to apply gradients to text elements, improving visual customization options.
parent 1733a0e8
Loading
Loading
Loading
Loading
+45 −1
Original line number Diff line number Diff line
@@ -158,3 +158,47 @@ def ffmt_change_line_spacing_type(param_name: str, values: float, act_ffmt: Font
    restore_cursor = not is_global
    for blkitem, value in zip(blkitems, values):
        blkitem.setLineSpacingType(value, restore_cursor=restore_cursor)

@font_formating(push_undostack=True)
def handle_gradient_enabled(param_name: str, values: List[bool], act_ffmt: FontFormat, is_global: bool, blkitems: List[TextBlkItem], **kwargs):
    set_kwargs = global_default_set_kwargs if is_global else local_default_set_kwargs
    for blkitem, value in zip(blkitems, values):
        blkitem.fontformat.gradient_enabled = value
        blkitem.update()

@font_formating(push_undostack=True)
def handle_gradient_start_color(param_name: str, values: List[List], act_ffmt: FontFormat, is_global: bool, blkitems: List[TextBlkItem], **kwargs):
    set_kwargs = global_default_set_kwargs if is_global else local_default_set_kwargs
    for blkitem, value in zip(blkitems, values):
        blkitem.fontformat.gradient_start_color = value
        blkitem.update()

@font_formating(push_undostack=True)
def handle_gradient_end_color(param_name: str, values: List[List], act_ffmt: FontFormat, is_global: bool, blkitems: List[TextBlkItem], **kwargs):
    set_kwargs = global_default_set_kwargs if is_global else local_default_set_kwargs
    for blkitem, value in zip(blkitems, values):
        blkitem.fontformat.gradient_end_color = value
        blkitem.update()

@font_formating(push_undostack=True)
def handle_gradient_angle(param_name: str, values: List[float], act_ffmt: FontFormat, is_global: bool, blkitems: List[TextBlkItem], **kwargs):
    set_kwargs = global_default_set_kwargs if is_global else local_default_set_kwargs
    for blkitem, value in zip(blkitems, values):
        blkitem.fontformat.gradient_angle = value
        blkitem.update()

@font_formating(push_undostack=True)
def handle_gradient_size(param_name: str, values: List[float], act_ffmt: FontFormat, is_global: bool, blkitems: List[TextBlkItem], **kwargs):
    set_kwargs = global_default_set_kwargs if is_global else local_default_set_kwargs
    for blkitem, value in zip(blkitems, values):
        blkitem.fontformat.gradient_size = value
        blkitem.update()

# Add gradient handlers to the handle_ffmt_change dictionary
handle_ffmt_change = {
    'gradient_enabled': handle_gradient_enabled,
    'gradient_start_color': handle_gradient_start_color,
    'gradient_end_color': handle_gradient_end_color,
    'gradient_angle': handle_gradient_angle,
    'gradient_size': handle_gradient_size
}
 No newline at end of file
+16 −0
Original line number Diff line number Diff line
@@ -2,11 +2,27 @@ from utils.io_utils import build_funcmap
from utils.fontformat import FontFormat
from utils.config import pcfg
from utils.textblock_mask import canny_flood, connected_canny_flood, existing_mask
from ui.fontformat_commands import (
    handle_gradient_enabled,
    handle_gradient_start_color,
    handle_gradient_end_color,
    handle_gradient_angle,
    handle_gradient_size
)

# Build base function map
handle_ffmt_change = build_funcmap('ui.fontformat_commands', 
                                     list(FontFormat.params().keys()), 
                                     'ffmt_change_', verbose=False)

# Add gradient handlers
handle_ffmt_change.update({
    'gradient_enabled': handle_gradient_enabled,
    'gradient_start_color': handle_gradient_start_color,
    'gradient_end_color': handle_gradient_end_color,
    'gradient_angle': handle_gradient_angle,
    'gradient_size': handle_gradient_size
})

def get_maskseg_method():
    return [canny_flood, connected_canny_flood, existing_mask][pcfg.drawpanel.rectool_method]
 No newline at end of file
+110 −1
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@ import copy
import sys
from typing import List

from qtpy.QtWidgets import QLineEdit, QSizePolicy, QHBoxLayout, QVBoxLayout, QFrame, QFontComboBox, QApplication, QPushButton, QLabel
from qtpy.QtWidgets import QLineEdit, QSizePolicy, QHBoxLayout, QVBoxLayout, QFrame, QFontComboBox, QApplication, QPushButton, QLabel, QGroupBox, QCheckBox, QSlider
from qtpy.QtCore import Signal, Qt
from qtpy.QtGui import QFocusEvent, QMouseEvent, QTextCursor, QKeyEvent

@@ -420,6 +420,60 @@ class FontFormatPanel(Widget):
        self.sourceBtn = TextCheckerLabel(self.tr("Source"))
        self.transBtn = TextCheckerLabel(self.tr("Translation"))

        # Add gradient controls
        self.gradientGroup = QGroupBox(self.tr("Gradient"), parent=self)
        gradientLayout = QVBoxLayout(self.gradientGroup)
        gradientLayout.setContentsMargins(10, 10, 10, 10)
        gradientLayout.setSpacing(5)
        
        # Enable gradient checkbox
        self.gradientEnableBox = QCheckBox(self.tr("Enable Gradient"), parent=self.gradientGroup)
        self.gradientEnableBox.stateChanged.connect(lambda state: self.on_param_changed('gradient_enabled', state == Qt.CheckState.Checked.value))
        gradientLayout.addWidget(self.gradientEnableBox)

        # Start color picker
        startColorLayout = QHBoxLayout()
        startColorLayout.addWidget(QLabel(self.tr("Start Color:"), parent=self.gradientGroup))
        self.gradientStartColorPicker = ColorPickerLabel(parent=self.gradientGroup)
        self.gradientStartColorPicker.colorChanged.connect(self.onGradientStartColorChanged)
        self.gradientStartColorPicker.changingColor.connect(self.changingColor)
        startColorLayout.addWidget(self.gradientStartColorPicker)
        gradientLayout.addLayout(startColorLayout)

        # End color picker
        endColorLayout = QHBoxLayout()
        endColorLayout.addWidget(QLabel(self.tr("End Color:"), parent=self.gradientGroup))
        self.gradientEndColorPicker = ColorPickerLabel(parent=self.gradientGroup)
        self.gradientEndColorPicker.colorChanged.connect(self.onGradientEndColorChanged)
        self.gradientEndColorPicker.changingColor.connect(self.changingColor)
        endColorLayout.addWidget(self.gradientEndColorPicker)
        gradientLayout.addLayout(endColorLayout)

        # Angle slider
        angleLayout = QHBoxLayout()
        angleLayout.addWidget(QLabel(self.tr("Angle:"), parent=self.gradientGroup))
        self.gradientAngleSlider = QSlider(Qt.Orientation.Horizontal, parent=self.gradientGroup)
        self.gradientAngleSlider.setRange(0, 359)
        self.gradientAngleSlider.valueChanged.connect(self.onGradientAngleChanged)
        angleLayout.addWidget(self.gradientAngleSlider)
        self.gradientAngleLabel = QLabel("", parent=self.gradientGroup)
        self.gradientAngleLabel.setMinimumWidth(30)
        angleLayout.addWidget(self.gradientAngleLabel)
        gradientLayout.addLayout(angleLayout)

        # Add gradient size control
        sizeLayout = QHBoxLayout()
        sizeLayout.addWidget(QLabel(self.tr("Size:"), parent=self.gradientGroup))
        self.gradientSizeSlider = QSlider(Qt.Orientation.Horizontal, parent=self.gradientGroup)
        self.gradientSizeSlider.setRange(50, 200)  # 0.5x to 2.0x
        self.gradientSizeSlider.setValue(100)  # Default 1.0x
        self.gradientSizeSlider.valueChanged.connect(self.onGradientSizeChanged)
        sizeLayout.addWidget(self.gradientSizeSlider)
        self.gradientSizeLabel = QLabel("1.0x", parent=self.gradientGroup)
        self.gradientSizeLabel.setMinimumWidth(40)
        sizeLayout.addWidget(self.gradientSizeLabel)
        gradientLayout.addLayout(sizeLayout)

        FONTFORMAT_SPACING = 6

        vl0 = QVBoxLayout()
@@ -466,6 +520,7 @@ class FontFormatPanel(Widget):
        self.vlayout.addLayout(hl2)
        self.vlayout.addLayout(hl3)
        self.vlayout.addLayout(hl4)
        self.vlayout.addWidget(self.gradientGroup)
        self.vlayout.setContentsMargins(7, 0, 7, 0)
        self.vlayout.setSpacing(0)

@@ -549,6 +604,16 @@ class FontFormatPanel(Widget):
        self.formatBtnGroup.underlineBtn.setChecked(font_format.underline)
        self.formatBtnGroup.italicBtn.setChecked(font_format.italic)
        self.alignBtnGroup.setAlignment(font_format.alignment)
        
        # Initialize gradient controls
        self.gradientEnableBox.setChecked(font_format.gradient_enabled)
        self.gradientStartColorPicker.setPickerColor(font_format.gradient_start_color)
        self.gradientEndColorPicker.setPickerColor(font_format.gradient_end_color)
        self.gradientAngleSlider.setValue(int(font_format.gradient_angle))
        self.gradientAngleLabel.setText(f"{int(font_format.gradient_angle)}°")
        self.gradientSizeSlider.setValue(int(font_format.gradient_size * 100))
        self.gradientSizeLabel.setText(f"{font_format.gradient_size:.1f}x")
        
        self.familybox.blockSignals(False)
        # self.texteffect_panel.set_active_format(font_format)
        self.textadvancedfmt_panel.set_active_format(font_format)
@@ -588,6 +653,10 @@ class FontFormatPanel(Widget):
                if focus_p == self or focus_p.parentWidget() == self:
                    focus_on_fmtoptions = True
            if not focus_on_fmtoptions:
                # Store the current text block's format before switching to global
                if self.textblk_item is not None:
                    # Save all format properties including gradient state
                    self.textblk_item.fontformat = copy.deepcopy(C.active_format)
                self.textblk_item = None
                self.set_active_format(self.global_format, multi_select)
                if multi_select:
@@ -598,6 +667,13 @@ class FontFormatPanel(Widget):
        else:
            if not self.restoring_textblk:
                blk_fmt = textblk_item.get_fontformat()
                # Preserve gradient properties from the text block's format
                if hasattr(textblk_item.fontformat, 'gradient_enabled'):
                    blk_fmt.gradient_enabled = textblk_item.fontformat.gradient_enabled
                    blk_fmt.gradient_start_color = textblk_item.fontformat.gradient_start_color
                    blk_fmt.gradient_end_color = textblk_item.fontformat.gradient_end_color
                    blk_fmt.gradient_angle = textblk_item.fontformat.gradient_angle
                    blk_fmt.gradient_size = textblk_item.fontformat.gradient_size
                self.textblk_item = textblk_item
                multi_size = not textblk_item.isEditing() and textblk_item.isMultiFontSize()
                self.set_active_format(blk_fmt, multi_size)
@@ -608,3 +684,36 @@ class FontFormatPanel(Widget):
        self.effect_panel.fontfmt = copy.deepcopy(C.active_format)
        self.effect_panel.updatePanels()
        self.effect_panel.show()

    def onGradientAngleChanged(self, value):
        self.gradientAngleLabel.setText(f"{value}°")
        self.on_param_changed('gradient_angle', float(value))

    def onGradientStartColorChanged(self, is_valid=True):
        self.focusOnColorDialog = False
        if is_valid:
            rgb = self.gradientStartColorPicker.rgb()
            self.on_param_changed('gradient_start_color', rgb)

    def onGradientEndColorChanged(self, is_valid=True):
        self.focusOnColorDialog = False
        if is_valid:
            rgb = self.gradientEndColorPicker.rgb()
            self.on_param_changed('gradient_end_color', rgb)

    def onGradientSizeChanged(self, value):
        self.gradientSizeLabel.setText(f"{value/100:.1f}x")
        self.on_param_changed('gradient_size', value/100)

    def updateGradientControls(self, fontfmt: FontFormat):
        self.gradientEnableBox.setChecked(fontfmt.gradient_enabled)
        self.gradientStartColorPicker.setRGB(*fontfmt.gradient_start_color)
        self.gradientEndColorPicker.setRGB(*fontfmt.gradient_end_color)
        self.gradientAngleSlider.setValue(int(fontfmt.gradient_angle))
        self.gradientAngleLabel.setText(f"{int(fontfmt.gradient_angle)}°")
        self.gradientSizeSlider.setValue(int(fontfmt.gradient_size * 100))
        self.gradientSizeLabel.setText(f"{fontfmt.gradient_size:.1f}x")

    def updateFontFormatPanel(self, fontfmt: FontFormat):
        self.updateGradientControls(fontfmt)
        self.update_text_style_label()
+76 −4
Original line number Diff line number Diff line
@@ -4,7 +4,9 @@ from typing import List, Union, Tuple

from qtpy.QtWidgets import QGraphicsItem, QWidget, QGraphicsSceneHoverEvent, QGraphicsTextItem, QStyleOptionGraphicsItem, QStyle, QGraphicsSceneMouseEvent
from qtpy.QtCore import Qt, QRect, QRectF, QPointF, Signal, QSizeF
from qtpy.QtGui import QGradient, QKeyEvent, QFont, QTextCursor, QPixmap, QPainterPath, QTextDocument, QInputMethodEvent, QPainter, QPen, QColor, QTextCursor, QTextCharFormat, QTextDocument
from qtpy.QtGui import (QGradient, QKeyEvent, QFont, QTextCursor, QPixmap, QPainterPath, QTextDocument, 
                       QInputMethodEvent, QPainter, QPen, QColor, QTextCharFormat, QTextDocument, QLinearGradient, 
                       QBrush, QPalette, QAbstractTextDocumentLayout)

from utils.textblock import TextBlock, FontFormat, TextAlignment, LineSpacingType
from utils.imgproc_utils import xywh2xyxypoly, rotate_polygons
@@ -467,8 +469,61 @@ class TextBlkItem(QGraphicsTextItem):
            pen = QPen(TEXTRECT_SHOW_COLOR, 3 / self.get_scale(), Qt.PenStyle.SolidLine)
            painter.setPen(pen)
            painter.drawRect(self.unpadRect(br))
        painter.restore()

        if self.fontformat.gradient_enabled:
            # Paint text to a temporary pixmap first
            pixmap = QPixmap(br.size().toSize())
            pixmap.fill(Qt.GlobalColor.transparent)
            temp_painter = QPainter(pixmap)
            temp_painter.setRenderHints(QPainter.RenderHint.Antialiasing | QPainter.RenderHint.TextAntialiasing)
            
            # Draw text in white
            doc = self.document()
            temp_painter.translate(doc.documentMargin(), doc.documentMargin())
            cursor = QTextCursor(doc)
            cursor.select(QTextCursor.SelectionType.Document)
            fmt = cursor.charFormat()
            fmt.setForeground(Qt.GlobalColor.white)
            cursor.mergeCharFormat(fmt)
            doc.drawContents(temp_painter)
            temp_painter.end()

            # Create gradient
            gradient = QLinearGradient()
            angle = self.fontformat.gradient_angle
            rad = math.radians(angle)
            dx = math.cos(rad)
            dy = math.sin(rad)
            
            # Set gradient points with size adjustment
            rect = br
            center = rect.center()
            radius = max(rect.width(), rect.height()) * self.fontformat.gradient_size
            gradient.setStart(center.x() - dx * radius, center.y() - dy * radius)
            gradient.setFinalStop(center.x() + dx * radius, center.y() + dy * radius)
            
            # Set gradient colors
            start_color = QColor(*self.fontformat.gradient_start_color)
            end_color = QColor(*self.fontformat.gradient_end_color)
            gradient.setColorAt(0, start_color)
            gradient.setColorAt(1, end_color)

            # Paint the gradient using the text as a mask
            painter.save()
            painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
            painter.setBrush(QBrush(gradient))
            painter.setPen(Qt.PenStyle.NoPen)
            painter.drawPixmap(br.topLeft(), pixmap)
            painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
            painter.drawRect(br)
            painter.restore()

            fmt.setForeground(QColor(*self.fontformat.frgb))
            cursor.mergeCharFormat(fmt)

            print(f"Created gradient pixmap for {self.idx} with angle {self.fontformat.gradient_angle} and size {self.fontformat.gradient_size} at {self.pos()}")
        else:
            option.state = QStyle.State_None
            super().paint(painter, option, widget)

@@ -600,6 +655,12 @@ class TextBlkItem(QGraphicsTextItem):
        fontformat.bold = font.bold()
        fontformat.underline = font.underline()
        fontformat.italic = font.italic()
        # Preserve gradient settings
        fontformat.gradient_enabled = self.fontformat.gradient_enabled
        fontformat.gradient_start_color = self.fontformat.gradient_start_color
        fontformat.gradient_end_color = self.fontformat.gradient_end_color
        fontformat.gradient_angle = self.fontformat.gradient_angle
        fontformat.gradient_size = self.fontformat.gradient_size
        return fontformat

    def set_fontformat(self, ffmat: FontFormat, set_char_format=False, set_stroke_width=True, set_effect=True):
@@ -656,8 +717,19 @@ class TextBlkItem(QGraphicsTextItem):
        if ffmat.vertical:
            self.setLetterSpacing(ffmat.letter_spacing)
        self.setLineSpacing(ffmat.line_spacing)
        
        # Preserve gradient properties
        self.fontformat.gradient_enabled = ffmat.gradient_enabled
        self.fontformat.gradient_start_color = ffmat.gradient_start_color
        self.fontformat.gradient_end_color = ffmat.gradient_end_color
        self.fontformat.gradient_angle = ffmat.gradient_angle
        self.fontformat.gradient_size = ffmat.gradient_size
        
        self.fontformat.merge(ffmat)
        
        if self.fontformat.gradient_enabled:
            self.update()

    def updateBlkFormat(self):
        fmt = self.get_fontformat()
        self.blk.fontformat.merge(fmt)
+7 −0
Original line number Diff line number Diff line
@@ -76,6 +76,13 @@ class FontFormat(Config):
    shadow_strength: float = 1.
    shadow_color: List = field(default_factory=lambda: [0, 0, 0])
    shadow_offset: List = field(default_factory=lambda: [0., 0.])
    # Gradient properties
    gradient_enabled: bool = False
    gradient_start_color: List = field(default_factory=lambda: [0, 0, 0])
    gradient_end_color: List = field(default_factory=lambda: [255, 255, 255])
    gradient_angle: float = 0.
    gradient_size: float = 1.0
    gradient_applied: bool = False
    _style_name: str = ''
    line_spacing_type: int = LineSpacingType.Proportional