You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
4.5 KiB

# coding:utf-8
from math import floor
import numpy as np
from colorthief import ColorThief
from PIL import Image
from PySide6.QtGui import QImage, QPixmap
from scipy.ndimage.filters import gaussian_filter
from .exception_handler import exceptionHandler
def gaussianBlur(imagePath, blurRadius=18, brightFactor=1, blurPicSize= None):
if not imagePath.startswith(':'):
image = Image.open(imagePath)
else:
image = Image.fromqpixmap(QPixmap(imagePath))
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, _ = image.shape
return QPixmap.fromImage(QImage(image.data, w, h, 3*w, QImage.Format_RGB888))
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)