Skip to content

Commit

Permalink
Alpha blending and gamma correction for previews and font view window.
Browse files Browse the repository at this point in the history
  • Loading branch information
psb1558 committed May 14, 2023
1 parent b15bf9b commit fed7c62
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 79 deletions.
123 changes: 60 additions & 63 deletions src/ygt/freetypeFont.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@
import cv2
import copy
from tempfile import SpooledTemporaryFile
from PyQt6.QtGui import QColor, QPen, QImage
from PyQt6.QtGui import QColor, QPen, QImage, QRegion, QBitmap, QPixmap
from PyQt6.QtCore import QRect, QLine
import uharfbuzz as uhb

def inverse_gamma(image, gamma):
# invGamma = 1.0 / gamma
table = numpy.array([((i / 255.0) ** gamma) * 255
for i in numpy.arange(0, 256)]).astype("uint8")
return cv2.LUT(image, table)

def adjust_gamma(image, gamma):
invGamma = 1.0 / gamma
table = numpy.array([((i / 255.0) ** invGamma) * 255
Expand Down Expand Up @@ -233,15 +227,13 @@ def mk_bg_array(self, ar, bg_color):
blue = bg_color.blue()
for row in ZZ:
for col in row:
rgb_counter = 0
for elem in col:
if rgb_counter == 0:
elem = red
elif rgb_counter == 1:
elem = green
elif rgb_counter == 2:
elem = blue
rgb_counter = 0
for en, elem in enumerate(col):
if en == 0:
col[en] = red
elif en == 1:
col[en] = green
elif en == 2:
col[en] = blue
return ZZ


Expand Down Expand Up @@ -274,24 +266,51 @@ def _draw_char_lcd(
"""
gdata = self._get_bitmap_metrics()
# ZZZZ = self.mk_array(gdata, RENDER_LCD_1)

# Here we try (1) getting bitmap into linear space with a standard gamma;
# (2) alpha blending of fg and bg color and (2) applying inverse gamma.
# Note that I don't really know what I'm doing here, welcome correction.
#
#s = ZZZZ.shape
#if not 0 in list(s):
# ZZZ = adjust_gamma(ZZZZ, 2.2)
# ZZ = cv2.addWeighted(self.mk_bg_array(ZZZ, bg_color), 0.5, ZZZ, 0.5, 0)
# Z = inverse_gamma(ZZ, 1.8)
#else:
# Z = ZZZZ
# (2) alpha blending of fg and bg color and (3) applying inverse gamma.
# Note that I don't really know what I'm doing here, but just trying to
# follow FreeType instructions. Corrections welcome.

# 1. Get the bitmap into a numpy array.
# 2. Get a mask.
# 3. Apply gamma.
# 4. blend fg and bg colors.
# 5. Apply gamma.
# 6. Do some metrics and positioning stuff.
# 7. Apply mask to bitmap.
# 8. Draw image.

# Get the Freetype bitmap into a numpy array.
Z = self.mk_array(gdata, RENDER_LCD_1)
if not 0 in list(Z.shape):
Z = adjust_gamma(Z, 2.2)
Z = cv2.addWeighted(self.mk_bg_array(Z, bg_color), 0.5, Z, 0.5, 0)
Z = inverse_gamma(Z, 1.8)
height, width, channel = Z.shape
bytesPerLine = channel * width
# Make a QImage from the numpy array, then make a mask that will turn its
# (currently) black pixels transparent.
img = QImage(Z.data, width, height, bytesPerLine, QImage.Format.Format_RGB888)
qmask = QBitmap.fromImage(img.createMaskFromColor(QColor("black").rgb()))
# Different alpha blending for dark and light themes.
if dark_theme:
alpha = 0.95
else:
alpha = 0.88
# If we have a bitmap (we may not for space, etc.), do: (1) invert for
# black-on-white text; (2) apply gamma to get bitmap into linear space;
# (3) apply alpha blending; (4) apply inverse gamma.
have_outline = True
if Z is not None:
if not (0 in list(Z.shape)):
if not dark_theme:
Z = (255-Z)
# Z = cv2.bitwise_not(Z)

Z = adjust_gamma(Z, 2.2)
Z = cv2.addWeighted(Z, alpha, self.mk_bg_array(Z, bg_color), 1.0 - alpha, 0)
Z = adjust_gamma(Z, 1.0 / 1.8)
else:
have_outline = False

# Get starting position and metrics.
ypos = y - gdata["bitmap_top"]
starting_ypos = ypos
is_mark = spacing_mark and gdata["advance"] == 0
Expand All @@ -301,38 +320,16 @@ def _draw_char_lcd(
gdata["advance"] = self.advance = round(gdata["width"] / 3) + 4
else:
starting_xpos = xpos = x + gdata["bitmap_left"]
if dark_theme:
qp = QPen(QColor("white"))
white_color = QColor("black")
else:
qp = QPen(QColor("black"))
white_color = QColor("white")
if not dark_theme:
Z = cv2.bitwise_not(Z)
height, width, channel = Z.shape
bytesPerLine = 3 * width
img = QImage(Z.data, width, height, bytesPerLine, QImage.Format.Format_RGB888)
painter.drawImage(starting_xpos, starting_ypos, img)
#qp.setWidth(1)
#for row in Z:
# xpos = starting_xpos
# for col in row:
# rgb = []
# for elem in col:
# rgb.append(elem)
# # The cached function doesn't help at all (timer produces about the same result).
# # Look for other possibilities for optimization.
# #qc = self._get_lcd_color(tuple(rgb), dark_theme)
# if dark_theme:
# qc = QColor(rgb[0], rgb[1], rgb[2])
# else:
# qc = QColor(255 - rgb[0], 255 - rgb[1], 255 - rgb[2])
# if qc != white_color:
# qp.setColor(qc)
# painter.setPen(qp)
# painter.drawPoint(xpos, ypos)
# xpos += 1
# ypos += 1

# Get QImage from Z; convert to QPixmap; apply mask; draw the character.
if have_outline:
img = QImage(Z.data, width, height, bytesPerLine, QImage.Format.Format_RGB888)
pm = QPixmap(img)
pm.setMask(qmask)
painter.drawPixmap(starting_xpos, starting_ypos, pm)

# Draw a red line under target glyph (the one in the current resolution); get
# a QRect for this glyph (will be a target for clicks).
ending_xpos = starting_xpos + round(gdata["advance"])
ending_ypos = starting_ypos + gdata["rows"]
if is_target:
Expand Down
51 changes: 35 additions & 16 deletions src/ygt/ygPreview.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
from typing import Callable, List, Optional
from .freetypeFont import freetypeFont, RENDER_GRAYSCALE, RENDER_LCD_1, RENDER_LCD_2
import copy
from numpy import nditer
from .freetypeFont import (
freetypeFont,
RENDER_GRAYSCALE,
RENDER_LCD_1,
RENDER_LCD_2,
adjust_gamma,
)
from PyQt6.QtWidgets import (
QWidget,
QLabel,
Expand All @@ -12,6 +20,7 @@
)
from PyQt6.QtGui import QPainter, QBrush, QColor, QPalette, QPixmap
from PyQt6.QtCore import Qt, QRect, pyqtSignal, pyqtSlot, QLine
import cv2


PREVIEW_WIDTH = 450
Expand Down Expand Up @@ -445,24 +454,34 @@ def make_pixmap_lcd1(self) -> None:
if len(self.Z) == 0:
painter.end()
return
xposition = self.horizontal_margin
yposition = self.vertical_margin + (self.top_char_margin * self.pixel_size)

# Get a copy of the pre-corrected bitmap to use as a mask. We'll skip drawing any
# pixels with color 0,0,0.
mask = copy.deepcopy(self.Z)
if dark_theme:
skippable = QColor("black")
alpha = 0.95
else:
skippable = QColor("white")
alpha = 0.88
if not 0 in list(self.Z.shape):
self.Z = adjust_gamma(self.Z, 2.2)
self.Z = cv2.addWeighted(
self.Z, alpha, self.face.mk_bg_array(self.Z, self.background_color), 1.0 - alpha, 0
)
self.Z = adjust_gamma(self.Z, 1 / 1.8)
xposition = self.horizontal_margin
yposition = self.vertical_margin + (self.top_char_margin * self.pixel_size)

for row in self.Z:
for col in row:
rgb = []
for elem in col:
rgb.append(elem)
if dark_theme:
qc = QColor(rgb[0], rgb[1], rgb[2])
else:
qc = QColor(255 - rgb[0], 255 - rgb[1], 255 - rgb[2])
if qc != skippable:
for i, row in enumerate(self.Z):
for ii, col in enumerate(row):
# If any of the (rgb) bytes for this pixel are non-zero in the mask,
# draw the pixel. Otherwise skip.
if mask[i][ii].any():
rgb = []
for elem in col:
rgb.append(elem)
if dark_theme:
qc = QColor(rgb[0], rgb[1], rgb[2])
else:
qc = QColor(255 - rgb[0], 255 - rgb[1], 255 - rgb[2])
qr = QRect(xposition, yposition, self.pixel_size, self.pixel_size)
painter.fillRect(qr, qc)
xposition += self.pixel_size
Expand Down

0 comments on commit fed7c62

Please sign in to comment.