Skip to content

Commit

Permalink
UI fixes and other stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
evnlme committed Apr 4, 2024
1 parent 7c234de commit 7504f13
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 40 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
__pycache__
__pycache__
*.zip
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,28 @@ Generate half tones from light and dark tones depending on the angle of incidenc

By default, it will compute 5 tones at angles 15, 30, 45, 60, and 75 degrees.

![Default](./selection.png)

## Usage

1. Select the light and dark tones. Clicking on the color brings up the color dialog. "FG" and "BG" copy the foreground and background colors respectively. "Half" computes the tone halfway between the light tone and black.
2. "Count" is the number of half tones between the light and dark tones.
3. "Cosine" and "Exponent" control which tones are chosen. A linear set of tones can be achieved by toggling off "Cosine" and setting "Exponent" to 1.
4. The button "Create" generates a set of tones below the button. Multiple sets of tones can be generated. A set of tones can be cleared by clicking on the delete button. Clicking on a tone updates the foreground color to that tone.

## Install

Please follow Krita's official docs for [installing a custom plugin](https://docs.krita.org/en/user_manual/python_scripting/install_custom_python_plugin.html).

## Example

![Example](./example.png)
![Example](./example.png)

## References

* https://scripting.krita.org/lessons/plugins-create
* https://github.com/kaichi1342/PaletteGenerator
* https://api.kde.org/krita/html/index.html
* https://doc.qt.io/qt-6/widget-classes.html
* https://www.riverbankcomputing.com/static/Docs/PyQt6/index.html
* https://invent.kde.org/graphics/krita/-/blob/master/plugins/python/plugin_importer/plugin_importer.py
110 changes: 72 additions & 38 deletions half_tone_selector/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
"""
References:
https://scripting.krita.org/lessons/plugins-create
https://github.com/kaichi1342/PaletteGenerator
https://api.kde.org/krita/html/index.html
https://doc.qt.io/qt-6/widget-classes.html
https://www.riverbankcomputing.com/static/Docs/PyQt6/index.html
"""

import math
from typing import Callable, Optional, List, Tuple
from krita import (
Expand All @@ -19,7 +9,10 @@
View,
)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
from PyQt5.QtGui import (
QColor,
QPalette,
)
from PyQt5.QtWidgets import (
QWidget,
QLayout,
Expand All @@ -32,6 +25,8 @@
QSpinBox,
QDoubleSpinBox,
QCheckBox,
QApplication,
QScrollArea,
)

def getActiveView() -> View:
Expand All @@ -57,6 +52,9 @@ def setFGColor(color: QColor) -> None:
if view:
view.setForeGroundColor(ManagedColor.fromQColor(color))

def getWindowColor() -> QColor:
return QApplication.palette().color(QPalette.Window)

def addLayout(
qlayout: Callable[[], QLayout],
qwidget: Callable[[], QWidget]=None,
Expand All @@ -69,26 +67,37 @@ def addLayout(
layout.addWidget(child)
return widget, layout

def createSelectToneWidget(name, options, defaultTone, updateTone):
def createSelectToneWidget(
name: str,
options: list,
defaultTone: QColor,
updateTone: Callable[[QColor], None],
style: dict,
) -> QWidget:
def setLabelStyle(widget):
widget.setFixedWidth(80)
widget.setStyleSheet('''
QLabel {
widget.setStyleSheet(f'''
QLabel {{
border: 0px solid transparent;
border-radius: 0px;
padding: 5px 10px;
background-color: #404040;
}
background-color: {style['label'].name()};
}}
''')

def setColor(widget, color):
updateTone(color)
fontColor = scaleColor(color, 1, 96 if color.value() < 128 else -96)
widget.setStyleSheet(f'''
QPushButton {{
border: 0px solid transparent;
border-radius: 0px;
padding: 5px 10px;
background-color: {color.name()};
color: {fontColor.name()};
}}
QPushButton::hover {{
border: 1px solid {style['window'].name()};
}}
''')

Expand All @@ -102,16 +111,16 @@ def handleColorDialog(widget):
setColor(widget, color)

def setOptionStyle(widget):
widget.setStyleSheet('''
QPushButton {
widget.setStyleSheet(f'''
QPushButton {{
border: 0px solid transparent;
border-radius: 0px;
padding: 5px 10px;
background-color: #606060;
}
QPushButton::hover {
background-color: #505050;
}
background-color: {style['button'].name()};
}}
QPushButton::hover {{
background-color: {style['window'].name()};
}}
''')

def handleOptionClick(widget, getColor):
Expand Down Expand Up @@ -144,15 +153,19 @@ def createOptionButton(widget, opt):

return widget

def createColorBarWidget(colors: List[QColor]) -> QWidget:
def createColorBarWidget(colors: List[QColor], style: dict) -> QWidget:
def createColorPatch(color):
patch = QPushButton()
patch.setMinimumSize(16, 16)
patch.setStyleSheet(f'''
QPushButton {{
border: 0px solid transparent;
border-radius: 0px;
background-color: {color.name()};
}}
QPushButton::hover {{
border: 0.5px solid {style['window'].name()};
}}
''')
patch.clicked.connect(lambda: setFGColor(color))
return patch
Expand All @@ -165,18 +178,17 @@ def createColorPatch(color):
patch = createColorPatch(color)
layout.addWidget(patch)

deleteButton = QPushButton('Delete')
deleteButton = QPushButton()
deleteButton.setIcon(Krita.instance().icon('deletelayer'))
deleteButton.clicked.connect(lambda: widget.deleteLater())
layout.addWidget(deleteButton)

return widget

def halfLight(state: dict) -> QColor:
r, g, b, a = state['light'].getRgb()
r2 = round(r / 2)
g2 = round(g / 2)
b2 = round(b / 2)
return QColor(r2, g2, b2, a)
rgba = state['light'].getRgb()
halfRgb = [round(x / 2) for x in rgba[:3]]
return QColor(*halfRgb, rgba[3])

def computeIntervals(getN, getExp, getCos) -> List[float]:
n = getN()
Expand All @@ -194,11 +206,27 @@ def interpolateColors(c1: QColor, c2: QColor, t: float) -> QColor:
rgba = [round(x1*t + x2*(1-t)) for x1, x2 in zip(rgba1, rgba2)]
return QColor(*rgba)

def clamp(xmin: float, xmax: float, x: float) -> float:
return max(min(x, xmax), xmin)

def scaleColor(color: QColor, scale: float, b: int = 0) -> QColor:
rgb = color.getRgb()[:3]
scaledRgb = [round(clamp(0, 255, x*scale + b)) for x in rgb]
return QColor(*scaledRgb)

def createHalfToneSelectorWidget() -> QWidget:
state = {
'light': QColor(128, 128, 128),
'dark': QColor(64, 64, 64),
}
windowColor = getWindowColor()
isDark = windowColor.value() < 128
style = {
'window': windowColor,
'background': scaleColor(windowColor, 1, 8 if isDark else -8),
'label': scaleColor(windowColor, 1, 16 if isDark else -16),
'button': scaleColor(windowColor, 1, 32 if isDark else -32),
}

# Select light tone
# Choice [FG | BG | Custom]
Expand All @@ -210,6 +238,7 @@ def createHalfToneSelectorWidget() -> QWidget:
],
state['light'],
lambda tone: state.update(light=tone),
style,
)

# Select dark tone
Expand All @@ -223,6 +252,7 @@ def createHalfToneSelectorWidget() -> QWidget:
],
state['dark'],
lambda tone: state.update(dark=tone),
style,
)

# Select half tone count - Positive integer
Expand Down Expand Up @@ -262,7 +292,7 @@ def create():
getExp=doubleSpinBox.value,
getCos=cosineCheckBox.isChecked)
colors = [interpolateColors(state['light'], state['dark'], i) for i in intervals]
layout.addWidget(createColorBarWidget(colors))
layout.addWidget(createColorBarWidget(colors, style))

createButton = QPushButton('Create half tones')
createButton.clicked.connect(create)
Expand All @@ -276,18 +306,22 @@ def create():
])
layout.setSpacing(5)
layout.setAlignment(Qt.AlignTop)
widget.setStyleSheet(f'''
QWidget {{
background-color: {style['background'].name()};
}}
''')

wrapperWidget, _ = addLayout(
qlayout=QVBoxLayout,
childWidgets=[widget])

return wrapperWidget
scrollArea = QScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setWidget(widget)
return scrollArea

class HalfToneSelector(DockWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('Half Tone Selector')
widget = createHalfToneSelectorWidget()
self.setWindowTitle('Half Tone Selector')
self.setWidget(widget)

# notifies when views are added or removed
Expand All @@ -303,4 +337,4 @@ def addHalfToneSelector():
HalfToneSelector)
instance.addDockWidgetFactory(dock_widget_factory)

addHalfToneSelector()
addHalfToneSelector()
Binary file added selection.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions zip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from pathlib import Path
from zipfile import ZipFile

script_path = Path(__file__).resolve().parent

name = 'half_tone_selector'
local_paths = [
f'{name}.desktop',
f'{name}/',
f'{name}/__init__.py',
f'{name}/Manual.html',
]

with ZipFile(script_path / f'{name}.zip', 'w') as f:
for path in local_paths:
f.write(filename=script_path / path, arcname=path)

0 comments on commit 7504f13

Please sign in to comment.