# coding:utf-8 from math import floor from io import BytesIO from typing import Union import numpy as np from colorthief import ColorThief from PIL import Image from PySide6.QtGui import QImage, QPixmap from PySide6.QtCore import QIODevice, QBuffer from scipy.ndimage.filters import gaussian_filter from .exception_handler import exceptionHandler def gaussianBlur(image, blurRadius=18, brightFactor=1, blurPicSize= None): if isinstance(image, str) and not image.startswith(':'): image = Image.open(image) else: image = fromqpixmap(QPixmap(image)) if blurPicSize: # adjust image size to reduce computation w, h = image.size ratio = min(blurPicSize[0] / w, blurPicSize[1] / h) w_, h_ = w * ratio, h * ratio if w_ < w: image = image.resize((int(w_), int(h_)), Image.ANTIALIAS) image = np.array(image) # handle gray image if len(image.shape) == 2: image = np.stack([image, image, image], axis=-1) # blur each channel for i in range(3): image[:, :, i] = gaussian_filter( image[:, :, i], blurRadius) * brightFactor # convert ndarray to QPixmap h, w, c = image.shape if c == 3: format = QImage.Format_RGB888 else: format = QImage.Format_RGBA8888 return QPixmap.fromImage(QImage(image.data, w, h, c*w, format)) # https://github.com/python-pillow/Pillow/blob/main/src/PIL/ImageQt.py def fromqpixmap(im: Union[QImage, QPixmap]): """ :param im: QImage or PIL ImageQt object """ buffer = QBuffer() buffer.open(QIODevice.OpenModeFlag.ReadWrite) # preserve alpha channel with png # otherwise ppm is more friendly with Image.open if im.hasAlphaChannel(): im.save(buffer, "png") else: im.save(buffer, "ppm") b = BytesIO() b.write(buffer.data()) buffer.close() b.seek(0) return Image.open(b) class DominantColor: """ Dominant color class """ @classmethod @exceptionHandler((24, 24, 24)) def getDominantColor(cls, imagePath): """ extract dominant color from image Parameters ---------- imagePath: str image path Returns ------- r, g, b: int gray value of each color channel """ if imagePath.startswith(':'): return (24, 24, 24) colorThief = ColorThief(imagePath) # scale image to speed up the computation speed if max(colorThief.image.size) > 400: colorThief.image = colorThief.image.resize((400, 400)) palette = colorThief.get_palette(quality=9) # adjust the brightness of palette palette = cls.__adjustPaletteValue(palette) for rgb in palette[:]: h, s, v = cls.rgb2hsv(rgb) if h < 0.02: palette.remove(rgb) if len(palette) <= 2: break palette = palette[:5] palette.sort(key=lambda rgb: cls.colorfulness(*rgb), reverse=True) return palette[0] @classmethod def __adjustPaletteValue(cls, palette): """ adjust the brightness of palette """ newPalette = [] for rgb in palette: h, s, v = cls.rgb2hsv(rgb) if v > 0.9: factor = 0.8 elif 0.8 < v <= 0.9: factor = 0.9 elif 0.7 < v <= 0.8: factor = 0.95 else: factor = 1 v *= factor newPalette.append(cls.hsv2rgb(h, s, v)) return newPalette @staticmethod def rgb2hsv(rgb): """ convert rgb to hsv """ r, g, b = [i / 255 for i in rgb] mx = max(r, g, b) mn = min(r, g, b) df = mx - mn if mx == mn: h = 0 elif mx == r: h = (60 * ((g - b) / df) + 360) % 360 elif mx == g: h = (60 * ((b - r) / df) + 120) % 360 elif mx == b: h = (60 * ((r - g) / df) + 240) % 360 s = 0 if mx == 0 else df / mx v = mx return (h, s, v) @staticmethod def hsv2rgb(h, s, v): """ convert hsv to rgb """ h60 = h / 60.0 h60f = floor(h60) hi = int(h60f) % 6 f = h60 - h60f p = v * (1 - s) q = v * (1 - f * s) t = v * (1 - (1 - f) * s) r, g, b = 0, 0, 0 if hi == 0: r, g, b = v, t, p elif hi == 1: r, g, b = q, v, p elif hi == 2: r, g, b = p, v, t elif hi == 3: r, g, b = p, q, v elif hi == 4: r, g, b = t, p, v elif hi == 5: r, g, b = v, p, q r, g, b = int(r * 255), int(g * 255), int(b * 255) return (r, g, b) @staticmethod def colorfulness(r: int, g: int, b: int): rg = np.absolute(r - g) yb = np.absolute(0.5 * (r + g) - b) # Compute the mean and standard deviation of both `rg` and `yb`. rg_mean, rg_std = (np.mean(rg), np.std(rg)) yb_mean, yb_std = (np.mean(yb), np.std(yb)) # Combine the mean and standard deviations. std_root = np.sqrt((rg_std ** 2) + (yb_std ** 2)) mean_root = np.sqrt((rg_mean ** 2) + (yb_mean ** 2)) return std_root + (0.3 * mean_root)