Unverified Commit 2708612d authored by dmMaze's avatar dmMaze Committed by GitHub
Browse files

Merge pull request #734 from alexop1000/gradients

Simple two-stop text gradients
parents 7509529c 9a8be508
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -220,6 +220,8 @@ def main():
    if args.headless:
        app_args = sys.argv + ['-platform', 'offscreen']
    app = QApplication(app_args)
    app.setApplicationName('BalloonsTranslator')
    app.setApplicationVersion(VERSION)

    if not args.headless:
        ps = QGuiApplication.primaryScreen()
+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
+178 −3
Original line number Diff line number Diff line
@@ -2,15 +2,15 @@ from typing import Union, Tuple, Callable

import cv2
import numpy as np
from qtpy.QtCore import Signal, Qt, QPoint
from qtpy.QtGui import QColor, QShowEvent, QPixmap, QImage, QPainter, QFontMetricsF
from qtpy.QtCore import Signal, Qt, QPoint, QPointF
from qtpy.QtGui import QColor, QShowEvent, QPixmap, QImage, QPainter, QFontMetricsF, QLinearGradient, QPainterPath
from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QGridLayout, QScrollArea, QGroupBox, QPushButton, QLabel, QDialog

from utils import shared as C
from .misc import pixmap2ndarray, ndarray2pixmap
from utils.fontformat import FontFormat, pt2px
from .custom_widget import Widget, ColorPickerLabel, PaintQSlider

import math

def apply_shadow_effect(img: Union[QPixmap, QImage, np.ndarray], color: QColor, strength=1.0, radius=21) -> Tuple[
    QPixmap, np.ndarray, np.ndarray]:
@@ -226,3 +226,178 @@ class TextEffectPanelDeprecated(QDialog):
        self.shadow_color_picker.setPickerColor(self.fontfmt.shadow_color)
        self.shadow_radius_slider.setValue(int(self.fontfmt.shadow_radius * 100))
        self.shadow_strength_slider.setValue(int(self.fontfmt.shadow_strength * 100))


class GradientPreviewPanel(QDialog):
    apply = Signal()
    gradient_changed = Signal()
    
    def __init__(self, update_text_style_label: Callable, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)

        self.setModal(True)
        self.update_text_style_label = update_text_style_label
        self.fontfmt: FontFormat = None
        self.fontfmt = FontFormat()
        self.active_fontfmt = FontFormat()

        # Preview label setup
        self.preview_label = QLabel(self)
        self.preview_label.setContentsMargins(0, 0, 0, 0)
        font = self.preview_label.font()
        font.setPointSizeF(24)
        self.preview_label.setFont(font)
        self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.preview_text = self.tr("Gradient")
        fm = QFontMetricsF(font)
        br = fm.boundingRect(self.preview_text)
        br_w, br_h = br.width(), br.height()
        self.preview_pixmap = QPixmap(int(br_w + br_h * 2), int(br_h * 3))
        self.preview_origin = QPointF(int(br_h), int(1.75 * br_h))

        self.preview_label.setFixedHeight(int(br_h * 2))

        # Enable gradient checkbox
        self.gradient_enable = QGroupBox(self.tr("Enable Gradient"))
        self.gradient_enable.setCheckable(True)
        self.gradient_enable.toggled.connect(self.on_gradient_enabled_changed)

        # Color pickers
        start_color_layout = QHBoxLayout()
        start_color_layout.addWidget(QLabel(self.tr("Start Color:")))
        self.gradient_start_picker = ColorPickerLabel(self)
        self.gradient_start_picker.colorChanged.connect(self.on_gradient_start_color_changed)
        start_color_layout.addWidget(self.gradient_start_picker)

        end_color_layout = QHBoxLayout()
        end_color_layout.addWidget(QLabel(self.tr("End Color:")))
        self.gradient_end_picker = ColorPickerLabel(self)
        self.gradient_end_picker.colorChanged.connect(self.on_gradient_end_color_changed)
        end_color_layout.addWidget(self.gradient_end_picker)

        # Angle slider
        self.angle_slider = PaintQSlider(self.tr("Angle"))
        self.angle_slider.setRange(0, 359)
        self.angle_slider.valueChanged.connect(self.on_gradient_angle_changed)

        # Size slider
        self.size_slider = PaintQSlider(self.tr("Size"))
        self.size_slider.setRange(50, 200)
        self.size_slider.valueChanged.connect(self.on_gradient_size_changed)

        # Buttons
        self.apply_btn = QPushButton(self.tr('Apply'))
        self.apply_btn.clicked.connect(self.on_apply_clicked)
        self.cancel_btn = QPushButton(self.tr('Cancel'))
        self.cancel_btn.clicked.connect(self.on_cancel_clicked)

        # Layout
        gradient_layout = QVBoxLayout()
        gradient_layout.addLayout(start_color_layout)
        gradient_layout.addLayout(end_color_layout)
        gradient_layout.addWidget(self.angle_slider)
        gradient_layout.addWidget(self.size_slider)
        self.gradient_enable.setLayout(gradient_layout)

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.apply_btn)
        button_layout.addWidget(self.cancel_btn)

        main_layout = QVBoxLayout(self)
        main_layout.addWidget(self.preview_label)
        main_layout.addWidget(self.gradient_enable)
        main_layout.addLayout(button_layout)

        self.setMaximumHeight(C.TEXTEFFECT_MAXHEIGHT)
        self.updatePreviewPixmap()

    def on_gradient_enabled_changed(self, enabled):
        self.fontfmt.gradient_enabled = enabled
        self.updatePreviewPixmap()

    def on_gradient_start_color_changed(self, is_valid):
        if is_valid:
            color = self.gradient_start_picker.color
            self.fontfmt.gradient_start_color = list(color.getRgb()[:3])  # Convert to list and take only RGB
            self.updatePreviewPixmap()

    def on_gradient_end_color_changed(self, is_valid):
        if is_valid:
            color = self.gradient_end_picker.color
            self.fontfmt.gradient_end_color = list(color.getRgb()[:3])  # Convert to list and take only RGB
            self.updatePreviewPixmap()

    def on_gradient_angle_changed(self, value):
        self.fontfmt.gradient_angle = float(value)
        self.updatePreviewPixmap()

    def on_gradient_size_changed(self, value):
        self.fontfmt.gradient_size = value / 100
        self.updatePreviewPixmap()

    def updatePreviewPixmap(self):
        if not self.isVisible():
            return

        self.preview_pixmap.fill(Qt.GlobalColor.transparent)
        painter = QPainter(self.preview_pixmap)
        painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
        painter.setFont(self.preview_label.font())

        if self.fontfmt.gradient_enabled:
            # Create gradient
            gradient = QLinearGradient()
            angle = self.fontfmt.gradient_angle
            rad = math.radians(angle)
            dx = math.cos(rad)
            dy = math.sin(rad)
            
            rect = self.preview_pixmap.rect()
            center = rect.center()
            radius = max(rect.width(), rect.height()) * self.fontfmt.gradient_size
            gradient.setStart(center.x() - dx * radius, center.y() - dy * radius)
            gradient.setFinalStop(center.x() + dx * radius, center.y() + dy * radius)
            
            start_color = QColor(*self.fontfmt.gradient_start_color)
            end_color = QColor(*self.fontfmt.gradient_end_color)
            gradient.setColorAt(0, start_color)
            gradient.setColorAt(1, end_color)
            
            painter.setBrush(gradient)
            painter.setPen(Qt.PenStyle.NoPen)
            
            # Draw text with gradient
            path = QPainterPath()
            path.addText(self.preview_origin, self.preview_label.font(), self.preview_text)
            painter.drawPath(path)
        else:
            painter.drawText(self.preview_origin, self.preview_text)
        
        painter.end()
        self.preview_label.setPixmap(self.preview_pixmap)

    def on_apply_clicked(self):
        self.active_fontfmt.gradient_enabled = self.fontfmt.gradient_enabled
        self.active_fontfmt.gradient_start_color = self.fontfmt.gradient_start_color
        self.active_fontfmt.gradient_end_color = self.fontfmt.gradient_end_color
        self.active_fontfmt.gradient_angle = self.fontfmt.gradient_angle
        self.active_fontfmt.gradient_size = self.fontfmt.gradient_size
        
        self.gradient_changed.emit()
        self.update_text_style_label()
        self.apply.emit()
        self.hide()

    def on_cancel_clicked(self):
        self.hide()

    def showEvent(self, e: QShowEvent) -> None:
        self.updatePreviewPixmap()
        return super().showEvent(e)

    def updatePanels(self):
        self.gradient_enable.setChecked(self.fontfmt.gradient_enabled)
        self.gradient_start_picker.setPickerColor(self.fontfmt.gradient_start_color)
        self.gradient_end_picker.setPickerColor(self.fontfmt.gradient_end_color)
        self.angle_slider.setValue(int(self.fontfmt.gradient_angle))
        self.size_slider.setValue(int(self.fontfmt.gradient_size * 100))
+46 −9
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

@@ -11,7 +11,7 @@ from utils import config as C
from utils.fontformat import FontFormat, px2pt, LineSpacingType
from .custom_widget import Widget, ColorPickerLabel, ClickableLabel, CheckableLabel, TextCheckerLabel, AlignmentChecker, QFontChecker, SizeComboBox
from .textitem import TextBlkItem
from .text_graphical_effect import TextEffectPanelDeprecated
from .text_graphical_effect import TextEffectPanelDeprecated, GradientPreviewPanel
from .text_advanced_format import TextAdvancedFormatPanel
from .text_style_presets import TextStylePresetPanel
from . import funcmaps as FM
@@ -411,15 +411,22 @@ class FontFormatPanel(Widget):
        )
        self.textadvancedfmt_panel.param_changed.connect(self.on_param_changed)

        self.effectBtn = ClickableLabel(self.tr("Effect"), self)
        self.effectBtn.clicked.connect(self.on_effectbtn_clicked)
        self.effect_panel = TextEffectPanelDeprecated(update_text_style_label=self.update_text_style_label)
        self.effect_panel.hide()
        self.shadowBtn = ClickableLabel(self.tr("Shadow"), self)
        self.shadowBtn.clicked.connect(self.on_shadow_btn_clicked)
        self.gradientBtn = ClickableLabel(self.tr("Gradient"), self)
        self.gradientBtn.clicked.connect(self.on_gradient_btn_clicked)
        
        self.foldTextBtn = CheckableLabel(self.tr("Unfold"), self.tr("Fold"), False)
        self.sourceBtn = TextCheckerLabel(self.tr("Source"))
        self.transBtn = TextCheckerLabel(self.tr("Translation"))
        
        self.effect_panel = TextEffectPanelDeprecated(update_text_style_label=self.update_text_style_label)
        self.effect_panel.hide()
        
        self.gradient_panel = GradientPreviewPanel(update_text_style_label=self.update_text_style_label)
        self.gradient_panel.gradient_changed.connect(self.on_gradient_changed)
        self.gradient_panel.hide()

        FONTFORMAT_SPACING = 6

        vl0 = QVBoxLayout()
@@ -447,7 +454,8 @@ class FontFormatPanel(Widget):
        hl3.setAlignment(Qt.AlignmentFlag.AlignCenter)
        hl3.addLayout(stroke_hlayout)
        hl3.addLayout(lettersp_hlayout)
        hl3.addWidget(self.effectBtn)
        hl3.addWidget(self.shadowBtn)
        hl3.addWidget(self.gradientBtn)
        hl3.setContentsMargins(3, 0, 3, 0)
        hl3.setSpacing(13)
        hl4 = QHBoxLayout()
@@ -549,6 +557,7 @@ class FontFormatPanel(Widget):
        self.formatBtnGroup.underlineBtn.setChecked(font_format.underline)
        self.formatBtnGroup.italicBtn.setChecked(font_format.italic)
        self.alignBtnGroup.setAlignment(font_format.alignment)
        
        self.familybox.blockSignals(False)
        # self.texteffect_panel.set_active_format(font_format)
        self.textadvancedfmt_panel.set_active_format(font_format)
@@ -588,6 +597,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,13 +611,37 @@ 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)
                self.textstyle_panel.setTitle(f'TextBlock #{textblk_item.idx}')

    def on_effectbtn_clicked(self):
    def on_shadow_btn_clicked(self):
        self.effect_panel.active_fontfmt = C.active_format
        self.effect_panel.fontfmt = copy.deepcopy(C.active_format)
        self.effect_panel.updatePanels()
        self.effect_panel.show()

    def on_gradient_btn_clicked(self):
        self.gradient_panel.active_fontfmt = C.active_format
        self.gradient_panel.fontfmt = copy.deepcopy(C.active_format)
        self.gradient_panel.updatePanels()
        self.gradient_panel.show()

    def on_gradient_changed(self):
        """Handle gradient changes from the preview panel"""
        start_color = self.gradient_panel.gradient_start_picker.color.getRgb()[:3]
        end_color = self.gradient_panel.gradient_end_picker.color.getRgb()[:3]
        
        self.on_param_changed('gradient_enabled', self.gradient_panel.fontfmt.gradient_enabled)
        self.on_param_changed('gradient_start_color', start_color)
        self.on_param_changed('gradient_end_color', end_color)
        self.on_param_changed('gradient_angle', self.gradient_panel.fontfmt.gradient_angle)
        self.on_param_changed('gradient_size', self.gradient_panel.fontfmt.gradient_size)
Loading