2022-06-20 05:42:49 +12:00
|
|
|
from enum import Enum
|
|
|
|
from typing import Tuple, Optional, Union
|
|
|
|
|
|
|
|
from PyQt5.QtCore import Qt, QRectF
|
|
|
|
from PyQt5.QtGui import (
|
|
|
|
QPaintEvent,
|
|
|
|
QPainter,
|
|
|
|
QPixmap,
|
|
|
|
QTransform,
|
|
|
|
QBrush,
|
|
|
|
QPalette,
|
|
|
|
QPainterPath,
|
|
|
|
QLinearGradient,
|
|
|
|
QColor,
|
|
|
|
)
|
|
|
|
from PyQt5.QtWidgets import QWidget
|
|
|
|
|
|
|
|
from rare.shared.image_manager import ImageSize
|
|
|
|
|
|
|
|
OverlayPath = Tuple[QPainterPath, Union[QColor, QLinearGradient]]
|
|
|
|
|
|
|
|
|
|
|
|
class ImageWidget(QWidget):
|
|
|
|
class Border(Enum):
|
|
|
|
Rounded = 0
|
|
|
|
Squared = 1
|
|
|
|
|
|
|
|
_pixmap: Optional[QPixmap] = None
|
|
|
|
_opacity: float = 1.0
|
|
|
|
_transform: QTransform
|
|
|
|
_smooth_transform: bool = False
|
|
|
|
_rounded_overlay: Optional[OverlayPath] = None
|
|
|
|
_squared_overlay: Optional[OverlayPath] = None
|
|
|
|
_image_size: Optional[ImageSize.Preset] = None
|
|
|
|
|
|
|
|
def __init__(self, parent=None) -> None:
|
|
|
|
super(ImageWidget, self).__init__(parent=parent)
|
|
|
|
self.setObjectName(type(self).__name__)
|
|
|
|
self.setContentsMargins(0, 0, 0, 0)
|
|
|
|
self.paint_image = self.paint_image_empty
|
|
|
|
self.paint_overlay = self.paint_overlay_rounded
|
|
|
|
|
|
|
|
def setOpacity(self, value: float) -> None:
|
|
|
|
self._opacity = value
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
def setPixmap(self, pixmap: QPixmap) -> None:
|
|
|
|
if not pixmap.isNull():
|
|
|
|
self._pixmap = pixmap
|
|
|
|
self.paint_image = self.paint_image_cover
|
|
|
|
if not self._image_size:
|
|
|
|
self._transform = QTransform().scale(
|
|
|
|
1 / pixmap.devicePixelRatioF(),
|
|
|
|
1 / pixmap.devicePixelRatioF(),
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
self._transform = QTransform().scale(
|
|
|
|
1 / pixmap.devicePixelRatioF() / self._image_size.divisor,
|
|
|
|
1 / pixmap.devicePixelRatioF() / self._image_size.divisor,
|
|
|
|
)
|
2023-01-30 11:04:08 +13:00
|
|
|
else:
|
|
|
|
self.paint_image = self.paint_image_empty
|
|
|
|
self.update()
|
2022-06-20 05:42:49 +12:00
|
|
|
|
|
|
|
def setFixedSize(self, a0: ImageSize.Preset) -> None:
|
|
|
|
self._squared_overlay = None
|
|
|
|
self._rounded_overlay = None
|
|
|
|
self._image_size = a0
|
|
|
|
self._smooth_transform = a0.smooth
|
|
|
|
super(ImageWidget, self).setFixedSize(a0.size)
|
|
|
|
|
|
|
|
def setBorder(self, border: Border):
|
|
|
|
if border == ImageWidget.Border.Rounded:
|
|
|
|
self.paint_overlay = self.paint_overlay_rounded
|
|
|
|
else:
|
|
|
|
self.paint_overlay = self.paint_overlay_squared
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
def _generate_squared_overlay(self) -> OverlayPath:
|
|
|
|
if self._image_size is not None and self._squared_overlay is not None:
|
|
|
|
return self._squared_overlay
|
|
|
|
path = QPainterPath()
|
|
|
|
path.addRect(0, 0, self.width(), self.height())
|
|
|
|
border = 2
|
|
|
|
inner_path = QPainterPath()
|
|
|
|
inner_path.addRect(
|
|
|
|
QRectF(
|
|
|
|
border,
|
|
|
|
border,
|
|
|
|
self.width() - border * 2,
|
|
|
|
self.height() - border * 2,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
gradient = QLinearGradient(0, 0, 0, self.height())
|
|
|
|
gradient.setColorAt(0.0, Qt.black)
|
|
|
|
gradient.setColorAt(1.0, Qt.transparent)
|
|
|
|
self._squared_overlay = path.subtracted(inner_path), gradient
|
|
|
|
return self._squared_overlay
|
|
|
|
|
|
|
|
def _generate_rounded_overlay(self) -> OverlayPath:
|
|
|
|
if self._image_size is not None and self._rounded_overlay is not None:
|
|
|
|
return self._rounded_overlay
|
|
|
|
# lk: the '-1' and '+1' are adjustments required for anti-aliasing
|
|
|
|
# lk: otherwise vertical lines would appear at the edges
|
|
|
|
path = QPainterPath()
|
|
|
|
path.addRect(-1, -1, self.width() + 2, self.height() + 2)
|
|
|
|
rounded_path = QPainterPath()
|
|
|
|
rounded_path.addRoundedRect(
|
|
|
|
QRectF(0, 0, self.width(), self.height()),
|
|
|
|
self.height() * 0.045,
|
|
|
|
self.height() * 0.045,
|
|
|
|
)
|
|
|
|
self._rounded_overlay = path.subtracted(rounded_path), QColor(0, 0, 0, 0)
|
|
|
|
return self._rounded_overlay
|
|
|
|
|
|
|
|
def paint_image_empty(self, painter: QPainter, a0: QPaintEvent) -> None:
|
|
|
|
# when pixmap object is not available yet, show a gray rectangle
|
|
|
|
painter.setOpacity(0.5 * self._opacity)
|
|
|
|
painter.fillRect(a0.rect(), Qt.darkGray)
|
|
|
|
|
|
|
|
def paint_image_cover(self, painter: QPainter, a0: QPaintEvent) -> None:
|
|
|
|
painter.setOpacity(self._opacity)
|
|
|
|
brush = QBrush(self._pixmap)
|
|
|
|
# downscale the image during painting to fit the pixelratio
|
|
|
|
brush.setTransform(self._transform)
|
|
|
|
painter.fillRect(a0.rect(), brush)
|
|
|
|
|
|
|
|
def paint_overlay_rounded(self, painter: QPainter, a0: QPaintEvent) -> None:
|
|
|
|
painter.setRenderHint(QPainter.Antialiasing, True)
|
|
|
|
painter.setOpacity(1.0)
|
|
|
|
painter.setCompositionMode(QPainter.CompositionMode_Source)
|
|
|
|
overlay, _ = self._generate_rounded_overlay()
|
|
|
|
painter.fillPath(overlay, self.palette().color(QPalette.Background))
|
|
|
|
|
|
|
|
def paint_overlay_squared(self, painter: QPainter, a0: QPaintEvent) -> None:
|
|
|
|
painter.setRenderHint(QPainter.Antialiasing, False)
|
|
|
|
painter.setOpacity(self._opacity)
|
|
|
|
painter.fillPath(*self._generate_squared_overlay())
|
|
|
|
|
|
|
|
def paintEvent(self, a0: QPaintEvent) -> None:
|
|
|
|
painter = QPainter(self)
|
2023-03-12 23:19:46 +13:00
|
|
|
if not painter.paintEngine().isActive():
|
|
|
|
return
|
2022-06-20 05:42:49 +12:00
|
|
|
# helps with better image quality
|
|
|
|
painter.setRenderHint(QPainter.SmoothPixmapTransform, self._smooth_transform)
|
|
|
|
self.paint_image(painter, a0)
|
|
|
|
self.paint_overlay(painter, a0)
|
|
|
|
painter.end()
|