from PIL import Image, ImageOps, ImageEnhance from PyQt6 import QtWidgets import os from ..libcomponent import BaseComponent from ..toolkit.frame import BlankFrame, addShadow from ..toolkit.visualizer import createSpectrumArray class Component(BaseComponent): name = "Image" version = "2.1.0" def widget(self, *args): super().widget(*args) # cache a modified image object in case we are rendering beyond frame 1 self.existingImage = None self.page.pushButton_image.clicked.connect(self.pickImage) self.page.comboBox_resizeMode.addItem("Scale") self.page.comboBox_resizeMode.addItem("Cover") self.page.comboBox_resizeMode.addItem("Stretch") self.page.comboBox_resizeMode.setCurrentIndex(0) self.trackWidgets( { "imagePath": self.page.lineEdit_image, "scale": self.page.spinBox_scale, "rotate": self.page.spinBox_rotate, "color": self.page.spinBox_color, "xPosition": self.page.spinBox_x, "yPosition": self.page.spinBox_y, "resizeMode": self.page.comboBox_resizeMode, "mirror": self.page.checkBox_mirror, "respondToAudio": self.page.checkBox_respondToAudio, "sensitivity": self.page.spinBox_sensitivity, "shadow": self.page.checkBox_shadow, }, presetNames={ "imagePath": "image", "xPosition": "x", "yPosition": "y", }, relativeWidgets=["xPosition", "yPosition", "scale"], ) def update(self): self.page.spinBox_sensitivity.setEnabled( self.page.checkBox_respondToAudio.isChecked() ) self.page.spinBox_scale.setEnabled( self.page.comboBox_resizeMode.currentIndex() == 0 ) def previewRender(self): return self.drawFrame(self.width, self.height, None) def properties(self): props = ["pcm" if self.respondToAudio else "static"] if not os.path.exists(self.imagePath): props.append("error") return props def error(self): if not self.imagePath: return "There is no image selected." if not os.path.exists(self.imagePath): return "The image selected does not exist!" def preFrameRender(self, **kwargs): super().preFrameRender(**kwargs) if not self.respondToAudio: return # Trigger creation of new base image self.existingImage = None self.spectrumArray = createSpectrumArray( self, self.completeAudioArray, self.sampleSize, 0.08, 0.8, self.sensitivity, self.progressBarUpdate, self.progressBarSetText, ) def frameRender(self, frameNo): return self.drawFrame( self.width, self.height, ( None if not self.respondToAudio else self.spectrumArray[frameNo * self.sampleSize] ), ) def drawFrame(self, width, height, dynamicScale): frame = BlankFrame(width, height) if self.imagePath and os.path.exists(self.imagePath): if dynamicScale is not None and self.existingImage: image = self.existingImage else: image = Image.open(self.imagePath) # Modify static image appearance if self.color != 100: image = ImageEnhance.Color(image).enhance(float(self.color / 100)) if self.mirror: image = image.transpose(Image.Transpose.FLIP_LEFT_RIGHT) if self.resizeMode == 1: # Cover image = ImageOps.fit( image, (width, height), Image.Resampling.LANCZOS ) elif self.resizeMode == 2: # Stretch image = image.resize((width, height), Image.Resampling.LANCZOS) elif self.scale != 100: # Scale newHeight = int((image.height / 100) * self.scale) newWidth = int((image.width / 100) * self.scale) image = image.resize( (newWidth, newHeight), Image.Resampling.LANCZOS ) self.existingImage = image # Respond to audio resolutionFactor = height / 1080 shadX = int(resolutionFactor * 1) shadY = int(resolutionFactor * -1) shadBlur = resolutionFactor * 3.50 scale = 0 if dynamicScale is not None: scale = dynamicScale[36 * 4] / 4 shadX += int((scale / 4) * resolutionFactor) shadY += int((scale / 2) * resolutionFactor) shadBlur += (scale / 8) * resolutionFactor image = ImageOps.contain( image, ( image.width + int(scale / 2), image.height + int(scale / 2), ), Image.Resampling.LANCZOS, ) # Paste image at correct position frame.paste( image, box=( self.xPosition - (0 if not self.respondToAudio else int(scale / 2)), self.yPosition - (0 if not self.respondToAudio else int(scale / 2)), ), ) if self.rotate != 0: frame = frame.rotate(self.rotate) if self.shadow: frame = addShadow(frame, shadBlur, shadX, shadY) return frame def postFrameRender(self): self.existingImage = None def pickImage(self): imgDir = self.settings.value("componentDir", os.path.expanduser("~")) filename, _ = QtWidgets.QFileDialog.getOpenFileName( self.page, "Choose Image", imgDir, "Image Files (%s)" % " ".join(self.core.imageFormats), ) if filename: self.settings.setValue("componentDir", os.path.dirname(filename)) self.mergeUndo = False self.page.lineEdit_image.setText(filename) self.mergeUndo = True def command(self, arg): def fail(): print("Not a supported image format") quit(1) if "=" in arg: key, arg = arg.split("=", 1) if key == "path" and os.path.exists(arg): if f"*{os.path.splitext(arg)[1]}" not in self.core.imageFormats: fail() try: Image.open(arg) self.page.lineEdit_image.setText(arg) self.page.comboBox_resizeMode.setCurrentIndex(2) return except OSError as e: fail() super().command(arg) def commandHelp(self): print("Load an image:\n path=/filepath/to/image.png")