diff --git a/tagstudio/src/qt/helpers/gradient.py b/tagstudio/src/qt/helpers/gradient.py new file mode 100644 index 00000000..b346d71a --- /dev/null +++ b/tagstudio/src/qt/helpers/gradient.py @@ -0,0 +1,50 @@ +# Copyright (C) 2024 Travis Abendshien (CyanVoxel). +# Licensed under the GPL-3.0 License. +# Created for TagStudio: https://github.com/CyanVoxel/TagStudio + +from PIL import Image, ImageEnhance, ImageChops + + +def four_corner_gradient_background(image: Image.Image, adj_size, mask, hl): + if image.size != (adj_size, adj_size): + # Old 1 color method. + # bg_col = image.copy().resize((1, 1)).getpixel((0,0)) + # bg = Image.new(mode='RGB',size=(adj_size,adj_size),color=bg_col) + # bg.thumbnail((1, 1)) + # bg = bg.resize((adj_size,adj_size), resample=Image.Resampling.NEAREST) + + # Small gradient background. Looks decent, and is only a one-liner. + # bg = image.copy().resize((2, 2), resample=Image.Resampling.BILINEAR).resize((adj_size,adj_size),resample=Image.Resampling.BILINEAR) + + # Four-Corner Gradient Background. + # Not exactly a one-liner, but it's (subjectively) really cool. + tl = image.getpixel((0, 0)) + tr = image.getpixel(((image.size[0] - 1), 0)) + bl = image.getpixel((0, (image.size[1] - 1))) + br = image.getpixel(((image.size[0] - 1), (image.size[1] - 1))) + bg = Image.new(mode="RGB", size=(2, 2)) + bg.paste(tl, (0, 0, 2, 2)) + bg.paste(tr, (1, 0, 2, 2)) + bg.paste(bl, (0, 1, 2, 2)) + bg.paste(br, (1, 1, 2, 2)) + bg = bg.resize((adj_size, adj_size), resample=Image.Resampling.BICUBIC) + + bg.paste( + image, + box=( + (adj_size - image.size[0]) // 2, + (adj_size - image.size[1]) // 2, + ), + ) + + bg.putalpha(mask) + final = bg + + else: + image.putalpha(mask) + final = image + + hl_soft = hl.copy() + hl_soft.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(0.5)) + final.paste(ImageChops.soft_light(final, hl_soft), mask=hl_soft.getchannel(3)) + return final diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index 5f3de6f8..a34e10eb 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -1174,7 +1174,7 @@ class QtDriver(QObject): self.thumb_job_queue.put( ( item_thumb.renderer.render, - (sys.float_info.max, "", base_size, ratio, True), + (sys.float_info.max, "", base_size, ratio, True, True), ) ) # # Restore Selected Borders @@ -1272,7 +1272,7 @@ class QtDriver(QObject): self.thumb_job_queue.put( ( item_thumb.renderer.render, - (time.time(), filepath, base_size, ratio, False), + (time.time(), filepath, base_size, ratio, False, True), ) ) else: diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index bd6cf3b5..0ae8d73f 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -90,9 +90,11 @@ class PreviewPanel(QWidget): self.preview_img.addAction(self.open_file_action) self.preview_img.addAction(self.open_explorer_action) - self.tr = ThumbRenderer() - self.tr.updated.connect(lambda ts, i, s: (self.preview_img.setIcon(i))) - self.tr.updated_ratio.connect( + self.thumb_renderer = ThumbRenderer() + self.thumb_renderer.updated.connect( + lambda ts, i, s: (self.preview_img.setIcon(i)) + ) + self.thumb_renderer.updated_ratio.connect( lambda ratio: ( self.set_image_ratio(ratio), self.update_image_size( @@ -380,7 +382,7 @@ class PreviewPanel(QWidget): # if self.preview_img.iconSize().toTuple()[0] < self.preview_img.size().toTuple()[0] + 10: # if type(self.item) == Entry: # filepath = os.path.normpath(f'{self.lib.library_dir}/{self.item.path}/{self.item.filename}') - # self.tr.render_big(time.time(), filepath, self.preview_img.size().toTuple(), self.devicePixelRatio()) + # self.thumb_renderer.render(time.time(), filepath, self.preview_img.size().toTuple(), self.devicePixelRatio(),update_on_ratio_change=True) # logging.info(f' Img Aspect Ratio: {self.image_ratio}') # logging.info(f' Max Button Size: {size}') @@ -443,7 +445,14 @@ class PreviewPanel(QWidget): self.preview_img.setCursor(Qt.CursorShape.ArrowCursor) ratio: float = self.devicePixelRatio() - self.tr.render_big(time.time(), "", (512, 512), ratio, True) + self.thumb_renderer.render( + time.time(), + "", + (512, 512), + ratio, + True, + update_on_ratio_change=True, + ) try: self.preview_img.clicked.disconnect() except RuntimeError: @@ -466,8 +475,14 @@ class PreviewPanel(QWidget): ) self.file_label.setFilePath(filepath) window_title = filepath - ratio = self.devicePixelRatio() - self.tr.render_big(time.time(), filepath, (512, 512), ratio) + ratio: float = self.devicePixelRatio() + self.thumb_renderer.render( + time.time(), + filepath, + (512, 512), + ratio, + update_on_ratio_change=True, + ) self.file_label.setText("\u200b".join(filepath)) self.file_label.setCursor(Qt.CursorShape.PointingHandCursor) @@ -575,8 +590,15 @@ class PreviewPanel(QWidget): ) self.preview_img.setCursor(Qt.CursorShape.ArrowCursor) - ratio = self.devicePixelRatio() - self.tr.render_big(time.time(), "", (512, 512), ratio, True) + ratio: float = self.devicePixelRatio() + self.thumb_renderer.render( + time.time(), + "", + (512, 512), + ratio, + True, + update_on_ratio_change=True, + ) try: self.preview_img.clicked.disconnect() except RuntimeError: @@ -658,7 +680,7 @@ class PreviewPanel(QWidget): # filepath = os.path.normpath(f'{self.lib.library_dir}/{item.path}/{item.filename}') # window_title = filepath # ratio: float = self.devicePixelRatio() - # self.tr.render_big(time.time(), filepath, (512, 512), ratio) + # self.thumb_renderer.render(time.time(), filepath, (512, 512), ratio,update_on_ratio_change=True) # self.file_label.setText("\u200b".join(filepath)) # # TODO: Deal with this later. diff --git a/tagstudio/src/qt/widgets/thumb_renderer.py b/tagstudio/src/qt/widgets/thumb_renderer.py index f3d5be64..0c42017f 100644 --- a/tagstudio/src/qt/widgets/thumb_renderer.py +++ b/tagstudio/src/qt/widgets/thumb_renderer.py @@ -3,7 +3,6 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import ctypes import logging import math import os @@ -12,25 +11,24 @@ from pathlib import Path import cv2 from PIL import ( Image, - ImageChops, UnidentifiedImageError, ImageQt, ImageDraw, ImageFont, - ImageEnhance, ImageOps, ImageFile, ) from PIL.Image import DecompressionBombError from PySide6.QtCore import QObject, Signal, QSize from PySide6.QtGui import QPixmap +from src.qt.helpers.gradient import four_corner_gradient_background from src.core.constants import PLAINTEXT_TYPES, VIDEO_TYPES, IMAGE_TYPES ImageFile.LOAD_TRUNCATED_IMAGES = True -ERROR = f"[ERROR]" -WARNING = f"[WARNING]" -INFO = f"[INFO]" +ERROR = "[ERROR]" +WARNING = "[WARNING]" +INFO = "[INFO]" logging.basicConfig(format="%(message)s", level=logging.INFO) @@ -44,50 +42,40 @@ class ThumbRenderer(QObject): # updatedSize = Signal(QSize) thumb_mask_512: Image.Image = Image.open( - os.path.normpath( - f"{Path(__file__).parents[3]}/resources/qt/images/thumb_mask_512.png" - ) + Path(f"{Path(__file__).parents[3]}/resources/qt/images/thumb_mask_512.png") ) thumb_mask_512.load() thumb_mask_hl_512: Image.Image = Image.open( - os.path.normpath( - f"{Path(__file__).parents[3]}/resources/qt/images/thumb_mask_hl_512.png" - ) + Path(f"{Path(__file__).parents[3]}/resources/qt/images/thumb_mask_hl_512.png") ) thumb_mask_hl_512.load() thumb_loading_512: Image.Image = Image.open( - os.path.normpath( - f"{Path(__file__).parents[3]}/resources/qt/images/thumb_loading_512.png" - ) + Path(f"{Path(__file__).parents[3]}/resources/qt/images/thumb_loading_512.png") ) thumb_loading_512.load() thumb_broken_512: Image.Image = Image.open( - os.path.normpath( - f"{Path(__file__).parents[3]}/resources/qt/images/thumb_broken_512.png" - ) + Path(f"{Path(__file__).parents[3]}/resources/qt/images/thumb_broken_512.png") ) thumb_broken_512.load() thumb_file_default_512: Image.Image = Image.open( - os.path.normpath( + Path( f"{Path(__file__).parents[3]}/resources/qt/images/thumb_file_default_512.png" ) ) thumb_file_default_512.load() - # thumb_debug: Image.Image = Image.open(os.path.normpath( + # thumb_debug: Image.Image = Image.open(Path( # f'{Path(__file__).parents[2]}/resources/qt/images/temp.jpg')) # thumb_debug.load() # TODO: Make dynamic font sized given different pixel ratios font_pixel_ratio: float = 1 ext_font = ImageFont.truetype( - os.path.normpath( - f"{Path(__file__).parents[3]}/resources/qt/fonts/Oxanium-Bold.ttf" - ), + Path(f"{Path(__file__).parents[3]}/resources/qt/fonts/Oxanium-Bold.ttf"), math.floor(12 * font_pixel_ratio), ) @@ -96,42 +84,37 @@ class ThumbRenderer(QObject): timestamp: float, filepath, base_size: tuple[int, int], - pixelRatio: float, - isLoading=False, + pixel_ratio: float, + is_loading=False, + gradient=False, + update_on_ratio_change=False, ): - """Renders an entry/element thumbnail for the GUI.""" - adj_size: int = 1 - image = None - pixmap = None + """Internal renderer. Renders an entry/element thumbnail for the GUI.""" + image: Image.Image = None + pixmap: QPixmap = None + final: Image.Image = None extension: str = None - # adj_font_size = math.floor(12 * pixelRatio) - if ThumbRenderer.font_pixel_ratio != pixelRatio: - ThumbRenderer.font_pixel_ratio = pixelRatio + resampling_method = Image.Resampling.BILINEAR + if ThumbRenderer.font_pixel_ratio != pixel_ratio: + ThumbRenderer.font_pixel_ratio = pixel_ratio ThumbRenderer.ext_font = ImageFont.truetype( - os.path.normpath( + Path( f"{Path(__file__).parents[3]}/resources/qt/fonts/Oxanium-Bold.ttf" ), math.floor(12 * ThumbRenderer.font_pixel_ratio), ) - if isLoading or filepath: - adj_size = math.ceil(base_size[0] * pixelRatio) - - if isLoading: - li: Image.Image = ThumbRenderer.thumb_loading_512.resize( - (adj_size, adj_size), resample=Image.Resampling.BILINEAR + adj_size = math.ceil(max(base_size[0], base_size[1]) * pixel_ratio) + if is_loading: + final = ThumbRenderer.thumb_loading_512.resize( + (adj_size, adj_size), resample=resampling_method ) - qim = ImageQt.ImageQt(li) + qim = ImageQt.ImageQt(final) pixmap = QPixmap.fromImage(qim) - pixmap.setDevicePixelRatio(pixelRatio) + pixmap.setDevicePixelRatio(pixel_ratio) + if update_on_ratio_change: + self.updated_ratio.emit(1) elif filepath: - mask: Image.Image = ThumbRenderer.thumb_mask_512.resize( - (adj_size, adj_size), resample=Image.Resampling.BILINEAR - ).getchannel(3) - hl: Image.Image = ThumbRenderer.thumb_mask_hl_512.resize( - (adj_size, adj_size), resample=Image.Resampling.BILINEAR - ) - extension = os.path.splitext(filepath)[1][1:].lower() try: @@ -151,7 +134,7 @@ class ThumbRenderer(QObject): image = ImageOps.exif_transpose(image) except DecompressionBombError as e: logging.info( - f"[ThumbRenderer][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})" + f"[ThumbRenderer]{WARNING} Couldn't Render thumbnail for {filepath} (because of {e})" ) # Videos ======================================================= @@ -173,22 +156,16 @@ class ThumbRenderer(QObject): # Plain Text =================================================== elif extension in PLAINTEXT_TYPES: - try: - text: str = extension - with open(filepath, "r", encoding="utf-8") as text_file: - text = text_file.read(256) - bg = Image.new("RGB", (256, 256), color="#1e1e1e") - draw = ImageDraw.Draw(bg) - draw.text((16, 16), text, file=(255, 255, 255)) - image = bg - except: - logging.info( - f"[ThumbRenderer][ERROR]: Coulnd't render thumbnail for {filepath}" - ) + with open(filepath, "r", encoding="utf-8") as text_file: + text = text_file.read(256) + bg = Image.new("RGB", (256, 256), color="#1e1e1e") + draw = ImageDraw.Draw(bg) + draw.text((16, 16), text, file=(255, 255, 255)) + image = bg # No Rendered Thumbnail ======================================== else: image = ThumbRenderer.thumb_file_default_512.resize( - (adj_size, adj_size), resample=Image.Resampling.BILINEAR + (adj_size, adj_size), resample=resampling_method ) if not image: @@ -204,307 +181,65 @@ class ThumbRenderer(QObject): new_y = adj_size new_x = math.ceil(adj_size * (orig_x / orig_y)) - # img_ratio = new_x / new_y - image = image.resize((new_x, new_y), resample=Image.Resampling.BILINEAR) - - if image.size != (adj_size, adj_size): - # Old 1 color method. - # bg_col = image.copy().resize((1, 1)).getpixel((0,0)) - # bg = Image.new(mode='RGB',size=(adj_size,adj_size),color=bg_col) - # bg.thumbnail((1, 1)) - # bg = bg.resize((adj_size,adj_size), resample=Image.Resampling.NEAREST) - - # Small gradient background. Looks decent, and is only a one-liner. - # bg = image.copy().resize((2, 2), resample=Image.Resampling.BILINEAR).resize((adj_size,adj_size),resample=Image.Resampling.BILINEAR) - - # Four-Corner Gradient Background. - # Not exactly a one-liner, but it's (subjectively) really cool. - tl = image.getpixel((0, 0)) - tr = image.getpixel(((image.size[0] - 1), 0)) - bl = image.getpixel((0, (image.size[1] - 1))) - br = image.getpixel(((image.size[0] - 1), (image.size[1] - 1))) - bg = Image.new(mode="RGB", size=(2, 2)) - bg.paste(tl, (0, 0, 2, 2)) - bg.paste(tr, (1, 0, 2, 2)) - bg.paste(bl, (0, 1, 2, 2)) - bg.paste(br, (1, 1, 2, 2)) - bg = bg.resize( - (adj_size, adj_size), resample=Image.Resampling.BICUBIC - ) - - bg.paste( - image, - box=( - (adj_size - image.size[0]) // 2, - (adj_size - image.size[1]) // 2, - ), - ) - - bg.putalpha(mask) - final = bg - - else: - image.putalpha(mask) - final = image - - hl_soft = hl.copy() - hl_soft.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(0.5)) - final.paste( - ImageChops.soft_light(final, hl_soft), mask=hl_soft.getchannel(3) - ) - - # hl_add = hl.copy() - # hl_add.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(.25)) - # final.paste(hl_add, mask=hl_add.getchannel(3)) - - except (UnidentifiedImageError, FileNotFoundError, cv2.error): - broken_thumb = True - final = ThumbRenderer.thumb_broken_512.resize( - (adj_size, adj_size), resample=Image.Resampling.BILINEAR - ) - - qim = ImageQt.ImageQt(final) - if image: - image.close() - pixmap = QPixmap.fromImage(qim) - pixmap.setDevicePixelRatio(pixelRatio) - - if pixmap: - self.updated.emit(timestamp, pixmap, QSize(*base_size), extension) - - else: - self.updated.emit(timestamp, QPixmap(), QSize(*base_size), extension) - - def render_big( - self, - timestamp: float, - filepath, - base_size: tuple[int, int], - pixelRatio: float, - isLoading: bool = False, - ): - """Renders a large, non-square entry/element thumbnail for the GUI.""" - adj_size: int = 1 - image: Image.Image = None - pixmap: QPixmap = None - final: Image.Image = None - extension: str = None - # adj_font_size = math.floor(12 * pixelRatio) - if ThumbRenderer.font_pixel_ratio != pixelRatio: - ThumbRenderer.font_pixel_ratio = pixelRatio - ThumbRenderer.ext_font = ImageFont.truetype( - os.path.normpath( - f"{Path(__file__).parents[3]}/resources/qt/fonts/Oxanium-Bold.ttf" - ), - math.floor(12 * ThumbRenderer.font_pixel_ratio), - ) - - if isLoading or filepath: - adj_size = math.ceil(max(base_size[0], base_size[1]) * pixelRatio) - - if isLoading: - adj_size = math.ceil((512 * pixelRatio)) - final = ThumbRenderer.thumb_loading_512.resize( - (adj_size, adj_size), resample=Image.Resampling.BILINEAR - ) - qim = ImageQt.ImageQt(final) - pixmap = QPixmap.fromImage(qim) - pixmap.setDevicePixelRatio(pixelRatio) - self.updated_ratio.emit(1) - - elif filepath: - # mask: Image.Image = ThumbRenderer.thumb_mask_512.resize( - # (adj_size, adj_size), resample=Image.Resampling.BILINEAR).getchannel(3) - # hl: Image.Image = ThumbRenderer.thumb_mask_hl_512.resize( - # (adj_size, adj_size), resample=Image.Resampling.BILINEAR) - - extension = os.path.splitext(filepath)[1][1:].lower() - - try: - # Images ======================================================= - if extension in IMAGE_TYPES: - try: - image = Image.open(filepath) - # image = self.thumb_debug - if image.mode == "RGBA": - # logging.info(image.getchannel(3).tobytes()) - new_bg = Image.new("RGB", image.size, color="#1e1e1e") - new_bg.paste(image, mask=image.getchannel(3)) - image = new_bg - if image.mode != "RGB": - image = image.convert(mode="RGB") - - image = ImageOps.exif_transpose(image) - except DecompressionBombError as e: - logging.info( - f"[ThumbRenderer][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})" - ) - - # Videos ======================================================= - elif extension in VIDEO_TYPES: - video = cv2.VideoCapture(filepath) - video.set( - cv2.CAP_PROP_POS_FRAMES, - (video.get(cv2.CAP_PROP_FRAME_COUNT) // 2), - ) - success, frame = video.read() - if not success: - # Depending on the video format, compression, and frame - # count, seeking halfway does not work and the thumb - # must be pulled from the earliest available frame. - video.set(cv2.CAP_PROP_POS_FRAMES, 0) - success, frame = video.read() - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - image = Image.fromarray(frame) - # Plain Text =================================================== - elif extension in PLAINTEXT_TYPES: - try: - text: str = extension - with open(filepath, "r", encoding="utf-8") as text_file: - text = text_file.read(256) - bg = Image.new("RGB", (256, 256), color="#1e1e1e") - draw = ImageDraw.Draw(bg) - draw.text((16, 16), text, file=(255, 255, 255)) - image = bg - except: - logging.info( - f"[ThumbRenderer][ERROR]: Coulnd't render thumbnail for {filepath}" - ) - # No Rendered Thumbnail ======================================== - else: - image = ThumbRenderer.thumb_file_default_512.resize( + if update_on_ratio_change: + self.updated_ratio.emit(new_x / new_y) + image = image.resize((new_x, new_y), resample=resampling_method) + if gradient: + mask: Image.Image = ThumbRenderer.thumb_mask_512.resize( + (adj_size, adj_size), resample=Image.Resampling.BILINEAR + ).getchannel(3) + hl: Image.Image = ThumbRenderer.thumb_mask_hl_512.resize( (adj_size, adj_size), resample=Image.Resampling.BILINEAR ) - - if not image: - raise UnidentifiedImageError - - orig_x, orig_y = image.size - if orig_x < adj_size and orig_y < adj_size: - new_x, new_y = (adj_size, adj_size) - if orig_x > orig_y: - new_x = adj_size - new_y = math.ceil(adj_size * (orig_y / orig_x)) - elif orig_y > orig_x: - new_y = adj_size - new_x = math.ceil(adj_size * (orig_x / orig_y)) + final = four_corner_gradient_background(image, adj_size, mask, hl) else: - new_x, new_y = (adj_size, adj_size) - if orig_x > orig_y: - new_x = adj_size - new_y = math.ceil(adj_size * (orig_y / orig_x)) - elif orig_y > orig_x: - new_y = adj_size - new_x = math.ceil(adj_size * (orig_x / orig_y)) - - self.updated_ratio.emit(new_x / new_y) - image = image.resize((new_x, new_y), resample=Image.Resampling.BILINEAR) - - # image = image.resize( - # (new_x, new_y), resample=Image.Resampling.BILINEAR) - - # if image.size != (adj_size, adj_size): - # # Old 1 color method. - # # bg_col = image.copy().resize((1, 1)).getpixel((0,0)) - # # bg = Image.new(mode='RGB',size=(adj_size,adj_size),color=bg_col) - # # bg.thumbnail((1, 1)) - # # bg = bg.resize((adj_size,adj_size), resample=Image.Resampling.NEAREST) - - # # Small gradient background. Looks decent, and is only a one-liner. - # # bg = image.copy().resize((2, 2), resample=Image.Resampling.BILINEAR).resize((adj_size,adj_size),resample=Image.Resampling.BILINEAR) - - # # Four-Corner Gradient Background. - # # Not exactly a one-liner, but it's (subjectively) really cool. - # tl = image.getpixel((0, 0)) - # tr = image.getpixel(((image.size[0]-1), 0)) - # bl = image.getpixel((0, (image.size[1]-1))) - # br = image.getpixel(((image.size[0]-1), (image.size[1]-1))) - # bg = Image.new(mode='RGB', size=(2, 2)) - # bg.paste(tl, (0, 0, 2, 2)) - # bg.paste(tr, (1, 0, 2, 2)) - # bg.paste(bl, (0, 1, 2, 2)) - # bg.paste(br, (1, 1, 2, 2)) - # bg = bg.resize((adj_size, adj_size), - # resample=Image.Resampling.BICUBIC) - - # bg.paste(image, box=( - # (adj_size-image.size[0])//2, (adj_size-image.size[1])//2)) - - # bg.putalpha(mask) - # final = bg - - # else: - # image.putalpha(mask) - # final = image - - # hl_soft = hl.copy() - # hl_soft.putalpha(ImageEnhance.Brightness( - # hl.getchannel(3)).enhance(.5)) - # final.paste(ImageChops.soft_light(final, hl_soft), - # mask=hl_soft.getchannel(3)) - - # hl_add = hl.copy() - # hl_add.putalpha(ImageEnhance.Brightness(hl.getchannel(3)).enhance(.25)) - # final.paste(hl_add, mask=hl_add.getchannel(3)) - scalar = 4 - rec: Image.Image = Image.new( - "RGB", - tuple([d * scalar for d in image.size]), # type: ignore - "black", - ) - draw = ImageDraw.Draw(rec) - draw.rounded_rectangle( - (0, 0) + rec.size, - (base_size[0] // 32) * scalar * pixelRatio, - fill="red", - ) - rec = rec.resize( - tuple([d // scalar for d in rec.size]), - resample=Image.Resampling.BILINEAR, - ) - # final = image - final = Image.new("RGBA", image.size, (0, 0, 0, 0)) - # logging.info(rec.size) - # logging.info(image.size) - final.paste(image, mask=rec.getchannel(0)) - - except (UnidentifiedImageError, FileNotFoundError, cv2.error): - broken_thumb = True - self.updated_ratio.emit(1) + scalar = 4 + rec: Image.Image = Image.new( + "RGB", + tuple([d * scalar for d in image.size]), # type: ignore + "black", + ) + draw = ImageDraw.Draw(rec) + draw.rounded_rectangle( + (0, 0) + rec.size, + (base_size[0] // 32) * scalar * pixel_ratio, + fill="red", + ) + rec = rec.resize( + tuple([d // scalar for d in rec.size]), + resample=Image.Resampling.BILINEAR, + ) + final = Image.new("RGBA", image.size, (0, 0, 0, 0)) + final.paste(image, mask=rec.getchannel(0)) + except ( + UnidentifiedImageError, + FileNotFoundError, + cv2.error, + DecompressionBombError, + UnicodeDecodeError, + ) as e: + if e is not UnicodeDecodeError: + logging.info( + f"[ThumbRenderer]{ERROR}: Couldn't render thumbnail for {filepath} ({e})" + ) + if update_on_ratio_change: + self.updated_ratio.emit(1) final = ThumbRenderer.thumb_broken_512.resize( - (adj_size, adj_size), resample=Image.Resampling.BILINEAR + (adj_size, adj_size), resample=resampling_method ) - # if extension in VIDEO_TYPES + ['gif', 'apng'] or broken_thumb: - # idk = ImageDraw.Draw(final) - # # idk.textlength(file_type) - # ext_offset_x = idk.textlength( - # text=extension.upper(), font=ThumbRenderer.ext_font) / 2 - # ext_offset_x = math.floor(ext_offset_x * (1/pixelRatio)) - # x_margin = math.floor( - # (adj_size-((base_size[0]//6)+ext_offset_x) * pixelRatio)) - # y_margin = math.floor( - # (adj_size-((base_size[0]//8)) * pixelRatio)) - # stroke_width = round(2 * pixelRatio) - # fill = 'white' if not broken_thumb else '#E32B41' - # idk.text((x_margin, y_margin), extension.upper( - # ), fill=fill, font=ThumbRenderer.ext_font, stroke_width=stroke_width, stroke_fill=(0, 0, 0)) - qim = ImageQt.ImageQt(final) if image: image.close() pixmap = QPixmap.fromImage(qim) - pixmap.setDevicePixelRatio(pixelRatio) - + pixmap.setDevicePixelRatio(pixel_ratio) if pixmap: - # logging.info(final.size) - # self.updated.emit(pixmap, QSize(*final.size)) self.updated.emit( timestamp, pixmap, QSize( - math.ceil(adj_size * 1 / pixelRatio), - math.ceil(final.size[1] * 1 / pixelRatio), + math.ceil(adj_size / pixel_ratio), + math.ceil(final.size[1] / pixel_ratio), ), extension, )