diff options
Diffstat (limited to 'src/avp/toolkit/frame.py')
| -rw-r--r-- | src/avp/toolkit/frame.py | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/src/avp/toolkit/frame.py b/src/avp/toolkit/frame.py new file mode 100644 index 0000000..94537a6 --- /dev/null +++ b/src/avp/toolkit/frame.py @@ -0,0 +1,117 @@ +""" +Common tools for drawing compatible frames in a Component's frameRender() +""" + +from PyQt6 import QtGui +from PIL import Image +from PIL.ImageQt import ImageQt +from PyQt6 import QtCore +import sys +import os +import math +import logging +from .. import core + + +log = logging.getLogger("AVP.Toolkit.Frame") + + +class FramePainter(QtGui.QPainter): + """ + A QPainter for a blank frame, which can be converted into a + Pillow image with finalize() + """ + + def __init__(self, width, height): + image = BlankFrame(width, height) + log.debug("Creating QImage from PIL image object") + self.image = ImageQt(image) + super().__init__(self.image) + + def setPen(self, penStyle): + if type(penStyle) is tuple: + super().setPen(PaintColor(*penStyle)) + else: + super().setPen(penStyle) + + def finalize(self): + log.verbose("Finalizing FramePainter") + buffer = QtCore.QBuffer() + buffer.open(QtCore.QBuffer.OpenModeFlag.ReadWrite) + self.image.save(buffer, "PNG") + import io + + frame = Image.open(io.BytesIO(buffer.data())) + buffer.close() + self.end() + return frame + imBytes = self.image.bits().asstring(self.image.byteCount()) + frame = Image.frombytes( + "RGBA", (self.image.width(), self.image.height()), imBytes + ) + self.end() + return frame + + +class PaintColor(QtGui.QColor): + """ + Subclass of QtGui.QColor with an added scale() method + Previously this class reversed the painter colour to solve + hardware issues related to endianness, + but Qt appears to deal with this itself nowadays + """ + + def __init__(self, r, g, b, a=255): + super().__init__(r, g, b, a) + + +def scale(scalePercent, width, height, returntype=None): + width = (float(width) / 100.0) * float(scalePercent) + height = (float(height) / 100.0) * float(scalePercent) + if returntype == str: + return (str(math.ceil(width)), str(math.ceil(height))) + elif returntype == int: + return (math.ceil(width), math.ceil(height)) + else: + return (width, height) + + +def defaultSize(framefunc): + """Makes width/height arguments optional""" + + def decorator(*args): + if len(args) < 2: + newArgs = list(args) + if len(args) == 0 or len(args) == 1: + height = int(core.Core.settings.value("outputHeight")) + newArgs.append(height) + if len(args) == 0: + width = int(core.Core.settings.value("outputWidth")) + newArgs.insert(0, width) + args = tuple(newArgs) + return framefunc(*args) + + return decorator + + +def FloodFrame(width, height, RgbaTuple): + return Image.new("RGBA", (width, height), RgbaTuple) + + +@defaultSize +def BlankFrame(width, height): + """The base frame used by each component to start drawing.""" + return FloodFrame(width, height, (0, 0, 0, 0)) + + +@defaultSize +def Checkerboard(width, height): + """ + A checkerboard to represent transparency to the user. + """ + # TODO: Would be cool to generate this image with numpy instead. + log.debug("Creating new %s*%s checkerboard" % (width, height)) + image = FloodFrame(1920, 1080, (0, 0, 0, 0)) + image.paste(Image.open(os.path.join(core.Core.wd, "gui", "background.png")), (0, 0)) + image = image.resize((width, height)) + return image |
