Ejemplo n.º 1
0
class PipetteToolbar(AbstractToolbar):
    __slots__ = (
        'color_view',
        'outputs',
        'position',
        'pos_fmt',
        'rgb_dec',
        'rgb_hex',
        'rgb_label',
        'src_dec',
        'src_dec_fmt',
        'src_hex',
        'src_hex_fmt',
        'src_label',
    )

    data_types = {
        vs.INTEGER: {
            1: ctypes.c_uint8,
            2: ctypes.c_uint16,
            # 4: ctypes.c_char * 4,
        },
        vs.FLOAT: {
            # 2: ctypes.c_char * 2,
            4: ctypes.c_float,
        }
    }

    def __init__(self, main: AbstractMainWindow) -> None:
        super().__init__(main, 'Pipette')

        self.setup_ui()
        self.main.graphics_view.mouseMoved.connect(self.mouse_moved)

        self.pos_fmt = '{},{}'
        self.src_hex_fmt = '{:2X}'
        self.src_max_val: Union[int, float] = 2**8 - 1
        self.src_dec_fmt = '{:3d}'
        self.src_norm_fmt = '{:0.5f}'
        self.outputs: Dict[Output, vs.VideoNode] = {}

        set_qobject_names(self)

    def setup_ui(self) -> None:
        layout = Qt.QHBoxLayout(self)
        layout.setObjectName('PipetteToolbar.setup_ui.layout')
        layout.setContentsMargins(0, 0, 0, 0)

        self.color_view = ColorView(self)
        self.color_view.setFixedSize(self.height() // 2, self.height() // 2)
        layout.addWidget(self.color_view)

        font = Qt.QFont('Consolas', 9)
        font.setStyleHint(Qt.QFont.Monospace)

        self.position = Qt.QLabel(self)
        self.position.setFont(font)
        self.position.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.position)

        self.rgb_label = Qt.QLabel(self)
        self.rgb_label.setText('Rendered (RGB):')
        layout.addWidget(self.rgb_label)

        self.rgb_hex = Qt.QLabel(self)
        self.rgb_hex.setFont(font)
        self.rgb_hex.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.rgb_hex)

        self.rgb_dec = Qt.QLabel(self)
        self.rgb_dec.setFont(font)
        self.rgb_dec.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.rgb_dec)

        self.rgb_norm = Qt.QLabel(self)
        self.rgb_norm.setFont(font)
        self.rgb_norm.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.rgb_norm)

        self.src_label = Qt.QLabel(self)
        layout.addWidget(self.src_label)

        self.src_hex = Qt.QLabel(self)
        self.src_hex.setFont(font)
        self.src_hex.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.src_hex)

        self.src_dec = Qt.QLabel(self)
        self.src_dec.setFont(font)
        self.src_dec.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.src_dec)

        self.src_norm = Qt.QLabel(self)
        self.src_norm.setFont(font)
        self.src_norm.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.src_norm)

        layout.addStretch()

    def mouse_moved(self, event: Qt.QMouseEvent) -> None:
        if not event.buttons() & Qt.Qt.LeftButton:
            self.update_labels(event.pos())

    def update_labels(self, local_pos: Qt.QPoint) -> None:
        from math import floor, trunc
        from struct import unpack

        point_f = self.main.graphics_view.mapToScene(local_pos)
        point = Qt.QPoint(floor(point_f.x()), floor(point_f.y()))
        if not self.main.current_output.graphics_scene_item.contains(point_f):
            return
        color = self.main.current_output.graphics_scene_item.image() \
                    .pixelColor(point)
        self.color_view.color = color

        self.position.setText(self.pos_fmt.format(point.x(), point.y()))

        self.rgb_hex.setText('{:2X},{:2X},{:2X}'.format(
            color.red(), color.green(), color.blue()))
        self.rgb_dec.setText('{:3d},{:3d},{:3d}'.format(
            color.red(), color.green(), color.blue()))
        self.rgb_norm.setText('{:0.5f},{:0.5f},{:0.5f}'.format(
            color.red() / 255,
            color.green() / 255,
            color.blue() / 255))

        if not self.src_label.isVisible():
            return

        src_vals: List[Union[int, float]] = []
        vs_frame = self.outputs[self.main.current_output].get_frame(
            int(self.main.current_frame))
        fmt = vs_frame.format
        idx = point.y() * vs_frame.width + point.x()

        for i in range(vs_frame.format.num_planes):
            if fmt.sample_type == vs.FLOAT and fmt.bytes_per_sample == 2:
                ptr = ctypes.cast(
                    vs_frame.get_read_ptr(i),
                    ctypes.POINTER(ctypes.c_char *
                                   (2 * vs_frame.width * vs_frame.height)))
                val = unpack('e',
                             ptr.contents[(idx * 2):(idx * 2 +
                                                     2)])[0]  # type: ignore
                src_vals.append(val)
            else:
                ptr = ctypes.cast(
                    vs_frame.get_read_ptr(i),
                    ctypes.POINTER(
                        self.data_types[fmt.sample_type][fmt.bytes_per_sample]
                        * (  # type:ignore
                            vs_frame.width * vs_frame.height)))
                src_vals.append(ptr.contents[idx])  # type: ignore

        self.src_dec.setText(self.src_dec_fmt.format(*src_vals))
        if fmt.sample_type == vs.INTEGER:
            self.src_hex.setText(self.src_hex_fmt.format(*src_vals))
            self.src_norm.setText(
                self.src_norm_fmt.format(
                    *[src_val / self.src_max_val for src_val in src_vals]))
        elif fmt.sample_type == vs.FLOAT:
            self.src_norm.setText(
                self.src_norm_fmt.format(
                    self.clip(src_vals[0], 0.0, 1.0),
                    self.clip(src_vals[1], -0.5, 0.5) + 0.5,
                    self.clip(src_vals[2], -0.5, 0.5) + 0.5,
                ))

    def on_current_output_changed(self, index: int, prev_index: int) -> None:
        from math import ceil, log

        def hide() -> None:
            self.src_label.setVisible(False)
            self.src_dec.setVisible(False)
            self.src_hex.setVisible(False)
            self.src_norm.setVisible(False)

        def show_and_reset() -> None:
            self.src_label.setVisible(True)
            self.src_dec.setVisible(True)
            self.src_hex.setVisible(True)
            self.src_norm.setVisible(True)
            self.src_label.setText('')
            self.src_hex.setText('')
            self.src_dec.setText('')
            self.src_norm.setText('')

        super().on_current_output_changed(index, prev_index)

        hide()
        fmt = self.main.current_output.format
        if fmt.color_family == vs.RGB:
            show_and_reset()
            self.src_label.setText('Raw (RGB):')
        elif fmt.color_family == vs.YUV:
            show_and_reset()
            self.src_label.setText('Raw (YUV):')
        elif fmt.color_family == vs.GRAY:
            show_and_reset()
            self.src_label.setText('Raw (Gray):')
        elif fmt.color_family == vs.YCOCG:
            show_and_reset()
            self.src_label.setText('Raw (YCoCg):')
        elif fmt.id == vs.COMPATBGR32.value:
            show_and_reset()
            self.src_label.setText('Raw (RGB)')
        elif fmt.id == vs.COMPATYUY2.value:
            show_and_reset()
            self.src_label.setText('Raw (YUV)')

        self.pos_fmt = '{{:{}d}},{{:{}d}}'.format(
            ceil(log(self.main.current_output.width, 10)),
            ceil(log(self.main.current_output.height, 10)))

        if self.main.current_output not in self.outputs:
            self.outputs[self.main.current_output] = self.prepare_vs_output(
                self.main.current_output.source_vs_output)
        src_fmt = self.outputs[self.main.current_output].format

        if src_fmt.sample_type == vs.INTEGER:
            self.src_max_val = 2**src_fmt.bits_per_sample - 1
        elif src_fmt.sample_type == vs.FLOAT:
            self.src_hex.setVisible(False)
            self.src_max_val = 1.0

        self.src_hex_fmt = ','.join(('{{:{w}X}}',) * src_fmt.num_planes) \
                           .format(w=ceil(log(self.src_max_val, 16)))
        if src_fmt.sample_type == vs.INTEGER:
            self.src_dec_fmt = ','.join(('{{:{w}d}}',) * src_fmt.num_planes) \
                               .format(w=ceil(log(self.src_max_val, 10)))
        elif src_fmt.sample_type == vs.FLOAT:
            self.src_dec_fmt = ','.join(('{: 0.5f}', ) * src_fmt.num_planes)
        self.src_norm_fmt = ','.join(('{:0.5f}', ) * src_fmt.num_planes)

        self.update_labels(
            self.main.graphics_view.mapFromGlobal(self.main.cursor().pos()))

    def on_toggle(self, enabled: bool) -> None:
        super().on_toggle(enabled)
        self.main.graphics_view.setMouseTracking(enabled)
        if enabled:
            self.main.graphics_view.setDragMode(Qt.QGraphicsView.NoDrag)
        else:
            self.main.graphics_view.setDragMode(
                Qt.QGraphicsView.ScrollHandDrag)

    @staticmethod
    def prepare_vs_output(vs_output: vs.VideoNode) -> vs.VideoNode:
        def non_subsampled_format(fmt: vs.Format) -> vs.Format:
            if fmt.id == vs.COMPATBGR32.value:
                return vs.RGB24  # type: ignore
            elif fmt.id == vs.COMPATYUY2.value:
                return vs.YUV444P8  # type: ignore
            else:
                return vs.core.register_format(
                    color_family=fmt.color_family,
                    sample_type=fmt.sample_type,
                    bits_per_sample=fmt.bits_per_sample,
                    subsampling_w=0,
                    subsampling_h=0)

        return vs.core.resize.Bicubic(vs_output,
                                      format=non_subsampled_format(
                                          vs_output.format))

    @staticmethod
    def clip(value: Number, lower_bound: Number,
             upper_bound: Number) -> Number:
        return max(lower_bound, min(value, upper_bound))
Ejemplo n.º 2
0
class PipetteToolbar(AbstractToolbar):
    __slots__ = (
        'color_view',
        'outputs',
        'position',
        'pos_fmt',
        'tracking',
        'rgb_dec',
        'rgb_hex',
        'rgb_label',
        'src_dec',
        'src_dec_fmt',
        'src_hex',
        'src_hex_fmt',
        'src_label',
    )

    data_types = {
        vs.INTEGER: {
            1: ctypes.c_uint8,
            2: ctypes.c_uint16,
            # 4: ctypes.c_char * 4,
        },
        vs.FLOAT: {
            # 2: ctypes.c_char * 2,
            4: ctypes.c_float,
        }
    }

    def __init__(self, main: AbstractMainWindow) -> None:
        super().__init__(main, 'Pipette')

        self.setup_ui()
        self.main.graphics_view.mouseMoved.connect(self.mouse_moved)
        self.main.graphics_view.mousePressed.connect(self.mouse_pressed)
        self.main.graphics_view.mouseReleased.connect(self.mouse_released)

        self.pos_fmt = '{},{}'
        self.src_hex_fmt = '{:2X}'
        self.src_max_val: Union[int, float] = 2**8 - 1
        self.src_dec_fmt = '{:3d}'
        self.src_norm_fmt = '{:0.5f}'
        self.outputs = WeakKeyDictionary[Output, vs.VideoNode]()
        self.tracking = False

        set_qobject_names(self)

    def setup_ui(self) -> None:
        layout = Qt.QHBoxLayout(self)
        layout.setObjectName('PipetteToolbar.setup_ui.layout')
        layout.setContentsMargins(0, 0, 0, 0)

        self.color_view = ColorView(self)
        self.color_view.setFixedSize(self.height() // 2, self.height() // 2)
        layout.addWidget(self.color_view)

        font = Qt.QFont('Consolas', 9)
        font.setStyleHint(Qt.QFont.Monospace)

        self.position = Qt.QLabel(self)
        self.position.setFont(font)
        self.position.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.position)

        self.rgb_label = Qt.QLabel(self)
        self.rgb_label.setText('Rendered (RGB):')
        layout.addWidget(self.rgb_label)

        self.rgb_hex = Qt.QLabel(self)
        self.rgb_hex.setFont(font)
        self.rgb_hex.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.rgb_hex)

        self.rgb_dec = Qt.QLabel(self)
        self.rgb_dec.setFont(font)
        self.rgb_dec.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.rgb_dec)

        self.rgb_norm = Qt.QLabel(self)
        self.rgb_norm.setFont(font)
        self.rgb_norm.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.rgb_norm)

        self.src_label = Qt.QLabel(self)
        layout.addWidget(self.src_label)

        self.src_hex = Qt.QLabel(self)
        self.src_hex.setFont(font)
        self.src_hex.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.src_hex)

        self.src_dec = Qt.QLabel(self)
        self.src_dec.setFont(font)
        self.src_dec.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.src_dec)

        self.src_norm = Qt.QLabel(self)
        self.src_norm.setFont(font)
        self.src_norm.setTextInteractionFlags(Qt.Qt.TextSelectableByMouse)
        layout.addWidget(self.src_norm)

        layout.addStretch()

    def on_script_unloaded(self) -> None:
        self.outputs.clear()

    def mouse_moved(self, event: Qt.QMouseEvent) -> None:
        if self.tracking and not event.buttons():
            self.update_labels(event.pos())

    def mouse_pressed(self, event: Qt.QMouseEvent) -> None:
        if event.buttons() == Qt.Qt.RightButton:
            self.tracking = False

    def mouse_released(self, event: Qt.QMouseEvent) -> None:
        if event.buttons() == Qt.Qt.RightButton:
            self.tracking = True
        self.update_labels(event.pos())

    def update_labels(self, local_pos: Qt.QPoint) -> None:
        from math import floor, trunc
        from struct import unpack

        pos_f = self.main.graphics_view.mapToScene(local_pos)
        pos = Qt.QPoint(floor(pos_f.x()), floor(pos_f.y()))
        if not self.main.current_output.graphics_scene_item.contains(pos_f):
            return
        color = self.main.current_output.graphics_scene_item.image() \
                    .pixelColor(pos)
        self.color_view.color = color

        self.position.setText(self.pos_fmt.format(pos.x(), pos.y()))

        self.rgb_hex.setText('{:2X},{:2X},{:2X}'.format(
            color.red(), color.green(), color.blue()))
        self.rgb_dec.setText('{:3d},{:3d},{:3d}'.format(
            color.red(), color.green(), color.blue()))
        self.rgb_norm.setText('{:0.5f},{:0.5f},{:0.5f}'.format(
            color.red() / 255,
            color.green() / 255,
            color.blue() / 255))

        if not self.src_label.isVisible():
            return

        def extract_value(vs_frame: vs.VideoFrame, plane: int,
                          pos: Qt.QPoint) -> Union[int, float]:
            fmt = vs_frame.format
            stride = vs_frame.get_stride(plane)
            if fmt.sample_type == vs.FLOAT and fmt.bytes_per_sample == 2:
                ptr = ctypes.cast(
                    vs_frame.get_read_ptr(plane),
                    ctypes.POINTER(ctypes.c_char * (stride * vs_frame.height)))
                offset = pos.y() * stride + pos.x() * 2
                val = unpack('e', ptr.contents[offset:(offset +
                                                       2)])[0]  # type: ignore
                return cast(float, val)
            else:
                ptr = ctypes.cast(
                    vs_frame.get_read_ptr(plane),
                    ctypes.POINTER(
                        self.data_types[fmt.sample_type][fmt.bytes_per_sample]
                        * (  # type:ignore
                            stride * vs_frame.height)))
                logical_stride = stride // fmt.bytes_per_sample
                idx = pos.y() * logical_stride + pos.x()
                return ptr.contents[idx]  # type: ignore

        vs_frame = self.outputs[self.main.current_output].get_frame(
            int(self.main.current_frame))
        fmt = vs_frame.format

        src_vals = [
            extract_value(vs_frame, i, pos) for i in range(fmt.num_planes)
        ]
        if self.main.current_output.has_alpha:
            vs_alpha = self.main.current_output.source_vs_alpha.get_frame(
                int(self.main.current_frame))
            src_vals.append(extract_value(vs_alpha, 0, pos))

        self.src_dec.setText(self.src_dec_fmt.format(*src_vals))
        if fmt.sample_type == vs.INTEGER:
            self.src_hex.setText(self.src_hex_fmt.format(*src_vals))
            self.src_norm.setText(
                self.src_norm_fmt.format(
                    *[src_val / self.src_max_val for src_val in src_vals]))
        elif fmt.sample_type == vs.FLOAT:
            self.src_norm.setText(
                self.src_norm_fmt.format(*[
                    self.clip(val, 0.0, 1.0) if i in (
                        0, 3) else self.clip(val, -0.5, 0.5) + 0.5
                    for i, val in enumerate(src_vals)
                ]))

    def on_current_output_changed(self, index: int, prev_index: int) -> None:
        from math import ceil, log

        super().on_current_output_changed(index, prev_index)

        fmt = self.main.current_output.format
        src_label_text = ''
        if fmt.color_family == vs.RGB:
            src_label_text = 'Raw (RGB{}):'
        elif fmt.color_family == vs.YUV:
            src_label_text = 'Raw (YUV{}):'
        elif fmt.color_family == vs.GRAY:
            src_label_text = 'Raw (Gray{}):'
        elif fmt.color_family == vs.YCOCG:
            src_label_text = 'Raw (YCoCg{}):'
        elif fmt.id == vs.COMPATBGR32.value:
            src_label_text = 'Raw (RGB{}):'
        elif fmt.id == vs.COMPATYUY2.value:
            src_label_text = 'Raw (YUV{}):'

        has_alpha = self.main.current_output.has_alpha
        if not has_alpha:
            self.src_label.setText(src_label_text.format(''))
        else:
            self.src_label.setText(src_label_text.format(' + Alpha'))

        self.pos_fmt = '{{:{}d}},{{:{}d}}'.format(
            ceil(log(self.main.current_output.width, 10)),
            ceil(log(self.main.current_output.height, 10)))

        if self.main.current_output not in self.outputs:
            self.outputs[self.main.current_output] = self.prepare_vs_output(
                self.main.current_output.source_vs_output)
        src_fmt = self.outputs[self.main.current_output].format

        if src_fmt.sample_type == vs.INTEGER:
            self.src_max_val = 2**src_fmt.bits_per_sample - 1
        elif src_fmt.sample_type == vs.FLOAT:
            self.src_hex.setVisible(False)
            self.src_max_val = 1.0

        src_num_planes = src_fmt.num_planes + int(has_alpha)
        self.src_hex_fmt = ','.join(('{{:{w}X}}',) * src_num_planes) \
                           .format(w=ceil(log(self.src_max_val, 16)))
        if src_fmt.sample_type == vs.INTEGER:
            self.src_dec_fmt = ','.join(('{{:{w}d}}',) * src_num_planes) \
                               .format(w=ceil(log(self.src_max_val, 10)))
        elif src_fmt.sample_type == vs.FLOAT:
            self.src_dec_fmt = ','.join(('{: 0.5f}', ) * src_num_planes)
        self.src_norm_fmt = ','.join(('{:0.5f}', ) * src_num_planes)

        self.update_labels(
            self.main.graphics_view.mapFromGlobal(self.main.cursor().pos()))

    def on_toggle(self, new_state: bool) -> None:
        super().on_toggle(new_state)
        self.main.graphics_view.setMouseTracking(new_state)
        if new_state is True:
            self.main.graphics_view.setDragMode(Qt.QGraphicsView.NoDrag)
        else:
            self.main.graphics_view.setDragMode(
                Qt.QGraphicsView.ScrollHandDrag)
        self.tracking = new_state

    @staticmethod
    def prepare_vs_output(vs_output: vs.VideoNode) -> vs.VideoNode:
        def non_subsampled_format(fmt: vs.Format) -> vs.Format:
            if fmt.id == vs.COMPATBGR32.value:
                return vs.RGB24  # type: ignore
            elif fmt.id == vs.COMPATYUY2.value:
                return vs.YUV444P8  # type: ignore
            else:
                return vs.core.register_format(
                    color_family=fmt.color_family,
                    sample_type=fmt.sample_type,
                    bits_per_sample=fmt.bits_per_sample,
                    subsampling_w=0,
                    subsampling_h=0)

        return vs.core.resize.Bicubic(vs_output,
                                      format=non_subsampled_format(
                                          vs_output.format))

    @staticmethod
    def clip(value: Number, lower_bound: Number,
             upper_bound: Number) -> Number:
        return max(lower_bound, min(value, upper_bound))