Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 29 additions & 32 deletions gradia/backend/tool_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import re
import json
from gradia.backend.settings import Settings
from gradia.utils.colors import make_rgba, tuple_to_rgba

class ToolOption:
def __init__(
Expand All @@ -36,9 +37,14 @@ def __init__(
) -> None:
self.mode = mode
self._size = size
self._primary_color_str = self._rgba_to_str(primary_color or Gdk.RGBA(0,0,0,1))
self._fill_color_str = self._rgba_to_str(fill_color or Gdk.RGBA(1,1,1,1))
self._border_color_str = self._rgba_to_str(border_color or Gdk.RGBA(0,0,0,0))

default_primary = make_rgba(0.0, 0.0, 0.0, 1.0)
default_fill = make_rgba(1.0, 1.0, 1.0, 1.0)
default_border = make_rgba(0.0, 0.0, 0.0, 0.0)

self._primary_color_str = self._rgba_to_str(primary_color or default_primary)
self._fill_color_str = self._rgba_to_str(fill_color or default_fill)
self._border_color_str = self._rgba_to_str(border_color or default_border)
self._font = font or "Adwaita Sans"
self._on_change_callback = on_change_callback
self._is_temporary = is_temporary
Expand All @@ -51,7 +57,7 @@ def _str_to_rgba(self, s: str) -> Gdk.RGBA:
if not m:
return Gdk.RGBA(0,0,0,1)
r, g, b, a = map(float, m.groups())
return Gdk.RGBA(r, g, b, a)
return make_rgba(r, g, b, a)

def _notify_change(self):
if self._on_change_callback and not self._is_temporary:
Expand Down Expand Up @@ -123,11 +129,6 @@ def serialize(self) -> str:

@classmethod
def deserialize(cls, json_str: str, on_change_callback: Optional[Callable[['ToolOption'], None]] = None) -> "ToolOption":
def tuple_to_rgba(t):
if not t or len(t) != 4:
return Gdk.RGBA(0, 0, 0, 1)
return Gdk.RGBA(t[0], t[1], t[2], t[3])

data = json.loads(json_str)
mode = DrawingMode[data.get("mode", "PEN")]
return cls(
Expand Down Expand Up @@ -211,22 +212,22 @@ def export_config(self) -> dict:
class ToolConfig:

TEXT_COLORS = [
(Gdk.RGBA(0.65,0.11,0.18, 1), _("Red")),
(Gdk.RGBA(0.15,0.64,0.41, 1), _("Green")),
(Gdk.RGBA(0.1,0.37,0.71, 1), _("Blue")),
(Gdk.RGBA(0.9,0.65,0.04, 1), _("Yellow")),
(Gdk.RGBA(0,0,0, 1), _("Black")),
(Gdk.RGBA(1.0, 1.0, 1.0, 1), _("White")),
(make_rgba(0.65, 0.11, 0.18, 1.0), _("Red")),
(make_rgba(0.15, 0.64, 0.41, 1.0), _("Green")),
(make_rgba(0.10, 0.37, 0.71, 1.0), _("Blue")),
(make_rgba(0.90, 0.65, 0.04, 1.0), _("Yellow")),
(make_rgba(0.00, 0.00, 0.00, 1.0), _("Black")),
(make_rgba(1.00, 1.00, 1.00, 1.0), _("White")),
]

TEXT_BACKGROUND_COLORS = [
(Gdk.RGBA(0.96,0.38,0.32, 1), _("Red")),
(Gdk.RGBA(0.56,0.94,0.64, 1), _("Green")),
(Gdk.RGBA(0.6,0.76,0.95, 1), _("Blue")),
(Gdk.RGBA(0.98,0.94,0.42, 1), _("Yellow")),
(Gdk.RGBA(1.0, 1.0, 1.0, 1), _("White")),
(Gdk.RGBA(0,0,0, 1), _("Black")),
(Gdk.RGBA(0, 0, 0, 0), _("Transparent"))
(make_rgba(0.96, 0.38, 0.32, 1.0), _("Red")),
(make_rgba(0.56, 0.94, 0.64, 1.0), _("Green")),
(make_rgba(0.60, 0.76, 0.95, 1.0), _("Blue")),
(make_rgba(0.98, 0.94, 0.42, 1.0), _("Yellow")),
(make_rgba(1.00, 1.00, 1.00, 1.0), _("White")),
(make_rgba(0.00, 0.00, 0.00, 1.0), _("Black")),
(make_rgba(0.00, 0.00, 0.00, 0.0), _("Transparent"))
]

def __init__(
Expand Down Expand Up @@ -256,10 +257,6 @@ def __init__(

@staticmethod
def get_all_tools_positions():
black = Gdk.RGBA(red=0, green=0, blue=0, alpha=1)
white = Gdk.RGBA(red=1, green=1, blue=1, alpha=1)
transparent = Gdk.RGBA(red=0, green=0, blue=0, alpha=0)

return [
ToolConfig(
mode=DrawingMode.SELECT,
Expand Down Expand Up @@ -331,12 +328,12 @@ def get_all_tools_positions():
has_scale=True,
has_primary_color=True,
primary_color_list=[
(Gdk.RGBA(0.88, 0.11, 0.14, 0.4), _("Red")),
(Gdk.RGBA(0.18, 0.76, 0.49, 0.4), _("Green")),
(Gdk.RGBA(0.21, 0.52, 0.89, 0.4), _("Blue")),
(Gdk.RGBA(0.96, 0.83, 0.18, 0.4), _("Yellow")),
(Gdk.RGBA(0.51, 0.24, 0.61, 0.4), _("Purple")),
(Gdk.RGBA(1.0, 1.0, 1.0, 0.4), _("White")),
(make_rgba(0.88, 0.11, 0.14, 0.4), _("Red")),
(make_rgba(0.18, 0.76, 0.49, 0.4), _("Green")),
(make_rgba(0.21, 0.52, 0.89, 0.4), _("Blue")),
(make_rgba(0.96, 0.83, 0.18, 0.4), _("Yellow")),
(make_rgba(0.51, 0.24, 0.61, 0.4), _("Purple")),
(make_rgba(1.0, 1.0, 1.0, 0.4), _("White")),
]
),
ToolConfig(
Expand Down
71 changes: 49 additions & 22 deletions gradia/overlay/drawing_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from enum import Enum
import math
from gradia.backend.logger import Logger
from gradia.utils.colors import has_visible_color
from gradia.utils.colors import rgba_to_tuple
import time
import unicodedata

Expand Down Expand Up @@ -162,7 +162,7 @@ def draw(self, cr: cairo.Context, image_to_widget_coords: Callable[[int, int], t
coords = [image_to_widget_coords(x, y) for x, y in self.stroke]
line_width = self.options.size * scale
self._build_path(cr, coords)
cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.primary_color))
cr.set_line_width(line_width)
cr.stroke()

Expand Down Expand Up @@ -240,7 +240,7 @@ def draw(self, cr: cairo.Context, image_to_widget_coords: Callable[[int, int], t

cr.set_line_width(width * scale)
cr.set_line_cap(cairo.LINE_CAP_ROUND)
cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.primary_color))

cr.move_to(start_x, start_y)
cr.line_to(end_x, end_y)
Expand Down Expand Up @@ -412,23 +412,33 @@ def draw(self, cr: cairo.Context, image_to_widget_coords: Callable[[int, int], t
text_x_widget = x_widget - text_width_widget / 2
text_y_widget = y_widget - text_height_widget

if self.options.fill_color and any(c > 0 for c in self.options.fill_color):
cr.set_source_rgba(*self.options.fill_color)
if self.options.fill_color and (
self.options.fill_color.red > 0 or
self.options.fill_color.green > 0 or
self.options.fill_color.blue > 0 or
self.options.fill_color.alpha > 0
):
cr.set_source_rgba(*rgba_to_tuple(self.options.fill_color))
self.draw_per_line_background(cr, layout, text_x_widget, text_y_widget, scale)

cr.move_to(text_x_widget, text_y_widget)
if self.contains_emoji():
cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.primary_color))
PangoCairo.show_layout(cr, layout)
else:
PangoCairo.layout_path(cr, layout)
if self.options.border_color and any(c > 0 for c in self.options.border_color):
cr.set_source_rgba(*self.options.border_color)
if self.options.border_color and (
self.options.border_color.red > 0 or
self.options.border_color.green > 0 or
self.options.border_color.blue > 0 or
self.options.border_color.alpha > 0
):
cr.set_source_rgba(*rgba_to_tuple(self.options.border_color))
base_line_width = 2.0
adjusted_line_width = base_line_width * scale * (self.font_size / 14.0)
cr.set_line_width(adjusted_line_width)
cr.stroke_preserve()
cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.primary_color))
cr.fill()

def get_bounds(self) -> QuadBounds:
Expand All @@ -453,7 +463,12 @@ def get_bounds(self) -> QuadBounds:
x_img, y_img = self.position

outline_padding = 0
if self.options.border_color and any(c > 0 for c in self.options.border_color):
if self.options.border_color and (
self.options.border_color.red > 0 or
self.options.border_color.green > 0 or
self.options.border_color.blue > 0 or
self.options.border_color.alpha > 0
):
outline_padding = int(2.0 * (self.font_size / 14.0)) + 1

left_img = x_img - text_width_img // 2 - self.PADDING_X_IMG - outline_padding
Expand Down Expand Up @@ -485,7 +500,7 @@ def draw(self, cr: cairo.Context, image_to_widget_coords: Callable[[int, int], t
cr.set_line_width(line_width)
cr.move_to(start_x, start_y)
cr.line_to(end_x, end_y)
cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.primary_color))
cr.stroke()

def get_bounds(self) -> QuadBounds:
Expand Down Expand Up @@ -540,10 +555,10 @@ def draw(self, cr: cairo.Context, image_to_widget_coords: Callable[[int, int], t

if w > 0 and h > 0:
if self.options.fill_color:
cr.set_source_rgba(*self.options.fill_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.fill_color))
cr.rectangle(x, y, w, h)
cr.fill()
cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.primary_color))
cr.set_line_width(self.options.size * scale)
cr.rectangle(x, y, w, h)
cr.stroke()
Expand Down Expand Up @@ -599,9 +614,9 @@ def draw(self, cr: cairo.Context, image_to_widget_coords: Callable[[int, int], t
cr.restore()

if self.options.fill_color:
cr.set_source_rgba(*self.options.fill_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.fill_color))
cr.fill_preserve()
cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.primary_color))
cr.set_line_width(self.options.size * scale)
cr.stroke()

Expand All @@ -621,7 +636,7 @@ def draw(self, cr: cairo.Context, image_to_widget_coords: Callable[[int, int], t
return
coords = [image_to_widget_coords(x, y) for x, y in self.stroke]
cr.set_operator(cairo.Operator.MULTIPLY)
cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.primary_color))
cr.set_line_width(self.options.size * scale * 2)
cr.set_line_cap(cairo.LineCap.BUTT)
cr.move_to(*coords[0])
Expand Down Expand Up @@ -724,12 +739,12 @@ def draw(self, cr: cairo.Context, image_to_widget_coords: Callable[[int, int], t
x_widget, y_widget = image_to_widget_coords(*self.position)
r_widget = self.options.size * 2 * scale

cr.set_source_rgba(*self.options.fill_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.fill_color))
cr.arc(x_widget, y_widget, r_widget, 0, 2 * math.pi)
cr.fill_preserve()

if self.options.border_color.alpha != 0 and self.options.fill_color.alpha != 0:
cr.set_source_rgba(*self.options.border_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.border_color))
cr.set_line_width(2.0 * scale)
cr.stroke()
else:
Expand All @@ -746,12 +761,17 @@ def draw(self, cr: cairo.Context, image_to_widget_coords: Callable[[int, int], t
cr.move_to(tx, ty)
cr.text_path(text)

if self.options.border_color and any(c > 0 for c in self.options.border_color):
cr.set_source_rgba(*self.options.border_color)
if self.options.border_color and (
self.options.border_color.red > 0 or
self.options.border_color.green > 0 or
self.options.border_color.blue > 0 or
self.options.border_color.alpha > 0
):
cr.set_source_rgba(*rgba_to_tuple(self.options.border_color))
cr.set_line_width(4 * scale)
cr.stroke_preserve()

cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(*rgba_to_tuple(self.options.primary_color))
cr.fill()

def contains_point(self, px_img: int, py_img: int) -> bool:
Expand All @@ -762,7 +782,14 @@ def contains_point(self, px_img: int, py_img: int) -> bool:

def get_bounds(self) -> QuadBounds:
x_img, y_img = self.position
outline_padding = 2 if self.options.border_color and any(c > 0 for c in self.options.border_color) else 0
outline_padding = 0
if self.options.border_color and (
self.options.border_color.red > 0 or
self.options.border_color.green > 0 or
self.options.border_color.blue > 0 or
self.options.border_color.alpha > 0
):
outline_padding = 2
total_radius = self.options.size * 2 + outline_padding + 1
return QuadBounds.from_rect(x_img - total_radius, y_img - total_radius, x_img + total_radius, y_img + total_radius)

Expand Down
11 changes: 8 additions & 3 deletions gradia/overlay/drawing_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ def _draw_selection_box(self, cr: cairo.Context, scale: float):
points = bounds.get_points()
widget_points = [self._image_to_widget_coords(int(p[0]), int(p[1])) for p in points]
accent = Adw.StyleManager.get_default().get_accent_color_rgba()
cr.set_source_rgba(*accent)
cr.set_source_rgba(accent.red, accent.green, accent.blue, accent.alpha)
cr.set_line_width(2)
cr.move_to(*widget_points[0])
for point in widget_points[1:]:
Expand All @@ -384,7 +384,7 @@ def _draw_selection_box(self, cr: cairo.Context, scale: float):
cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
cr.rectangle(handle_x, handle_y, HANDLE_SIZE, HANDLE_SIZE)
cr.fill()
cr.set_source_rgba(*accent)
cr.set_source_rgba(accent.red, accent.green, accent.blue, accent.alpha)
cr.rectangle(handle_x, handle_y, HANDLE_SIZE, HANDLE_SIZE)
cr.stroke()

Expand Down Expand Up @@ -769,7 +769,12 @@ def _on_draw(self, area, cr: cairo.Context, width: int, height: int):
action.draw(cr, self._image_to_widget_coords, scale)

if self.is_drawing and self.options.mode != DrawingMode.TEXT and self.options.mode != DrawingMode.NUMBER:
cr.set_source_rgba(*self.options.primary_color)
cr.set_source_rgba(
self.options.primary_color.red,
self.options.primary_color.green,
self.options.primary_color.blue,
self.options.primary_color.alpha,
)
if self.options.mode == DrawingMode.PEN and len(self.current_stroke) > 1:
StrokeAction(self.current_stroke, self.options.copy()).draw(cr, self._image_to_widget_coords, scale)
elif self.options.mode == DrawingMode.HIGHLIGHTER and len(self.current_stroke) > 1:
Expand Down
20 changes: 10 additions & 10 deletions gradia/ui/widget/quick_color_picker.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@
# SPDX-License-Identifier: GPL-3.0-or-later

from gi.repository import Gtk, Adw, Gdk, GObject, Gio
from gradia.utils.colors import is_light_color_rgba, rgba_to_hex
from gradia.utils.colors import is_light_color_rgba, rgba_to_hex, make_rgba


class ColorPickerMixin:
def _get_base_colors(self, alpha=1.0, secondary=False):
base_colors = [
(Gdk.RGBA(0.88, 0.11, 0.14, alpha), _("Red")),
(Gdk.RGBA(0.18, 0.76, 0.49, alpha), _("Green")),
(Gdk.RGBA(0.21, 0.52, 0.89, alpha), _("Blue")),
(Gdk.RGBA(0.96, 0.83, 0.18, alpha), _("Yellow")),
(Gdk.RGBA(0.0, 0.0, 0.0, alpha), _("Black")),
(Gdk.RGBA(1.0, 1.0, 1.0, alpha), _("White")),
]
(make_rgba(0.88, 0.11, 0.14, alpha), _("Red")),
(make_rgba(0.18, 0.76, 0.49, alpha), _("Green")),
(make_rgba(0.21, 0.52, 0.89, alpha), _("Blue")),
(make_rgba(0.96, 0.83, 0.18, alpha), _("Yellow")),
(make_rgba(0.0, 0.0, 0.0, alpha), _("Black")),
(make_rgba(1.0, 1.0, 1.0, alpha), _("White")),
]

if secondary:
base_colors.append((Gdk.RGBA(0, 0, 0, 0), _("Transparent")))

base_colors.append((make_rgba(0.0, 0.0, 0.0, 0.0), _("Transparent")))

return base_colors

Expand Down
30 changes: 30 additions & 0 deletions gradia/utils/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@
HexColor = str
RGBTuple = tuple[int, int, int]

def make_rgba(r: float, g: float, b: float, a: float) -> Gdk.RGBA:
"""Utility to construct a Gdk.RGBA with the given components.

Gdk.RGBA does not take RGBA values as constructor arguments; you must
instantiate it and then assign the channels explicitly.
"""
rgba = Gdk.RGBA()
rgba.red = r
rgba.green = g
rgba.blue = b
rgba.alpha = a
return rgba

def hex_to_rgba(hex_color: HexColor, alpha: float | None = None) -> Gdk.RGBA:
"""
Converts hexadecimal color code to `Gdk.RGBA` object.
Expand Down Expand Up @@ -58,6 +71,23 @@ def hex_to_rgb(hex_color: HexColor) -> RGBTuple:
r, g, b = (int(hex_color[i:i+2], 16) for i in (0, 2, 4))
return (r, g, b)

def rgba_to_tuple(color: Gdk.RGBA) -> tuple[float, float, float, float]:
return (color.red, color.green, color.blue, color.alpha)

def tuple_to_rgba(color: tuple[float, float, float, float] | None) -> Gdk.RGBA:
rgba = Gdk.RGBA()
if not color or len(color) != 4:
rgba.red = 0.0
rgba.green = 0.0
rgba.blue = 0.0
rgba.alpha = 1.0
return rgba
rgba.red = float(color[0])
rgba.green = float(color[1])
rgba.blue = float(color[2])
rgba.alpha = float(color[3])
return rgba

def has_visible_color(color):
return any(c > 0 for c in color[:3]) or (len(color) > 3 and color[3] > 0)

Expand Down