diff --git a/xlib/image/__init__.py b/xlib/image/__init__.py index 7230786..ff00996 100644 --- a/xlib/image/__init__.py +++ b/xlib/image/__init__.py @@ -1 +1,2 @@ from .ImageProcessor import ImageProcessor +from .misc import get_NHWC_shape \ No newline at end of file diff --git a/xlib/qt/__init__.py b/xlib/qt/__init__.py index e857383..b172fc9 100644 --- a/xlib/qt/__init__.py +++ b/xlib/qt/__init__.py @@ -4,7 +4,8 @@ from .core.widget import (BlockSignals, disable, enable, hide, hide_and_disable, show, show_and_enable) from .gui.from_file import (QIcon_from_file, QPixmap_from_file, QXImage_from_file, QXPixmap_from_file) -from .gui.from_np import QPixmap_from_np +from .gui.from_np import (QImage_ARGB32_from_buffer, QImage_BGR888_from_buffer, + QPixmap_from_np) from .gui.QXImageSequence import QXImageSequence from .gui.QXPixmap import QXPixmap from .widgets.QXCheckBox import QXCheckBox @@ -22,6 +23,7 @@ from .widgets.QXLabel import QXLabel from .widgets.QXLineEdit import QXLineEdit from .widgets.QXMainApplication import QXMainApplication from .widgets.QXMenuBar import QXMenuBar +from .widgets.QXOpenGLWidget import QXOpenGLWidget from .widgets.QXProgressBar import QXProgressBar from .widgets.QXPushButton import QXPushButton from .widgets.QXRadioButton import QXRadioButton diff --git a/xlib/qt/gui/from_np.py b/xlib/qt/gui/from_np.py index c036118..db1492c 100644 --- a/xlib/qt/gui/from_np.py +++ b/xlib/qt/gui/from_np.py @@ -1,25 +1,72 @@ +from typing import Union + import numpy as np from PyQt6.QtGui import * -from ...image import ImageProcessor +from ...image import ImageProcessor, get_NHWC_shape + +_C_to_Format = { + 1: QImage.Format.Format_Grayscale8, + 3: QImage.Format.Format_BGR888, + 4: QImage.Format.Format_ARGB32 + } def QPixmap_from_np(image : np.ndarray): - ip = ImageProcessor(image).to_uint8() - N,H,W,C = ip.get_dims() + """ + constructs QPixmap from image np.ndarray + """ + if image.dtype != np.uint8: + raise ValueError('image.dtype must be np.uint8') - if N > 1: - raise ValueError(f'N dim must be == 1') - - if C == 1: - format = QImage.Format.Format_Grayscale8 - elif C == 3: - format = QImage.Format.Format_BGR888 - elif C == 4: - format = QImage.Format.Format_ARGB32 - else: + N,H,W,C = get_NHWC_shape(image) + if N != 1: + raise ValueError('image N must == 1') + + format = _C_to_Format.get(C, None) + if format is None: raise ValueError(f'Unsupported channels {C}') - image = ip.get_image('HWC') q_image = QImage(image.data, W, H, W*C, format) q_pixmap = QPixmap.fromImage(q_image) return q_pixmap + +def QImage_from_np(image : np.ndarray): + """ + constructs QImage from image np.ndarray + + given image must live the whole life cycle of QImage. + """ + if image.dtype != np.uint8: + raise ValueError('image.dtype must be np.uint8') + + N,H,W,C = get_NHWC_shape(image) + if N != 1: + raise ValueError('image N must == 1') + format = _C_to_Format.get(C, None) + if format is None: + raise ValueError(f'Unsupported channels {C}') + return QImage(image.data, W, H, W*C, format) + +def QImage_BGR888_from_buffer(buffer : Union[bytes, bytearray], width : int, height : int ): + + """ + constructs QImage of BGR888 format from byte buffer with given width,height + + given buffer must live the whole life cycle of QImage. + """ + if len(buffer) != width*height*3: + raise ValueError(f'Size of buffer must be width*height*3 == {width*height*3}') + + return QImage(buffer, width, height, width*3, QImage.Format.Format_BGR888) + +def QImage_ARGB32_from_buffer(buffer : Union[bytes, bytearray], width : int, height : int ): + + """ + constructs QImage of ARGB32 format from byte buffer with given width,height + + given buffer must live the whole life cycle of QImage. + """ + if len(buffer) != width*height*4: + raise ValueError(f'Size of buffer must be width*height*4 == {width*height*4}') + + return QImage(buffer, width, height, width*3, QImage.Format.Format_ARGB32) diff --git a/xlib/qt/widgets/QXOpenGLWidget.py b/xlib/qt/widgets/QXOpenGLWidget.py new file mode 100644 index 0000000..cbf45d8 --- /dev/null +++ b/xlib/qt/widgets/QXOpenGLWidget.py @@ -0,0 +1,51 @@ +from PyQt6.QtGui import * +from PyQt6.QtOpenGL import * +from PyQt6.QtOpenGLWidgets import * +from PyQt6.QtWidgets import * + +from ._part_QXWidget import _part_QXWidget + +_size_policy_from_str = { + 'fixed' : QSizePolicy.Policy.Fixed, + 'minimum' : QSizePolicy.Policy.Minimum, + 'maximum' : QSizePolicy.Policy.Maximum, + 'preferred' : QSizePolicy.Policy.Preferred, + 'minimumexpanding' : QSizePolicy.Policy.MinimumExpanding, + 'expanding' : QSizePolicy.Policy.Expanding, + 'ignored' : QSizePolicy.Policy.Ignored, +} + +class QXOpenGLWidget(QOpenGLWidget, _part_QXWidget): + def __init__(self, + font=None, tooltip_text=None, + size_policy=None, + minimum_size=None, minimum_width=None, minimum_height=None, + maximum_size=None, maximum_width=None, maximum_height=None, + fixed_size=None, fixed_width=None, fixed_height=None, + hided=False, enabled=True + ): + + super().__init__() + self._default_pal = QPalette( self.palette() ) + + if size_policy is not None: + x1, x2 = size_policy + if isinstance(x1, str): + x1 = _size_policy_from_str[x1.lower()] + if isinstance(x2, str): + x2 = _size_policy_from_str[x2.lower()] + size_policy = (x1, x2) + + _part_QXWidget.__init__(self, font=font, tooltip_text=tooltip_text, + size_policy=size_policy, + minimum_size=minimum_size, minimum_width=minimum_width, minimum_height=minimum_height, + maximum_size=maximum_size, maximum_width=maximum_width, maximum_height=maximum_height, + fixed_size=fixed_size, fixed_width=fixed_width, fixed_height=fixed_height, + hided=hided, enabled=enabled ) + def focusInEvent(self, ev : QFocusEvent): + super().focusInEvent(ev) + _part_QXWidget.focusInEvent(self, ev) + + def resizeEvent(self, ev : QResizeEvent): + super().resizeEvent(ev) + _part_QXWidget.resizeEvent(self, ev)