Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. How can I highlight sections of an image between two right-clicks?
Forum Updated to NodeBB v4.3 + New Features

How can I highlight sections of an image between two right-clicks?

Scheduled Pinned Locked Moved Unsolved Qt for Python
qt for pythonpython
2 Posts 2 Posters 346 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • S Offline
    S Offline
    srcLegend
    wrote on last edited by srcLegend
    #1

    I've got the general visualizer so far, but I'm stuck at how to implement a multi-column spanning highlighter, if that makes sense (I'll attach an example of what I mean).

    My goal is to display multiple photographic images stacked vertically (Photo 1.jpg, Photo 2.jpg), with a transparent overlay applied over them (Overlay 1.png, Overlay 2.png).

    The resulting highlight should be like this (as a purple highlighter here, between the two green dots representing consecutive right-clicks), although not necessarily purple (I'd prefer to just increase that section's brightness, actually).

    from PyQt6.QtCore import QPointF, Qt, pyqtSignal
    from PyQt6.QtGui import QImage, QMouseEvent, QPixmap, QTransform, QWheelEvent
    from PyQt6.QtOpenGLWidgets import QOpenGLWidget
    from PyQt6.QtWidgets import QApplication, QGraphicsPixmapItem, QGraphicsScene, QGraphicsView, QLabel, QPushButton, QSlider, QVBoxLayout, QWidget
    
    
    class ImageOverlayApp(QWidget):
        def __init__(self, corebox_images: list[str], overlay_images: list[str]) -> None:
            super().__init__()
            self.setWindowTitle("Viewer")
    
            # Initial opacity
            self.alpha = 0.5
    
            # Prepare the scene
            self.scene = QGraphicsScene()
    
            # Lists to hold pixmap items and images
            self.pixmap_items_core: list[QGraphicsPixmapItem] = []
            self.pixmap_items_overlay: list[QGraphicsPixmapItem] = []
            self.overlay_images: list[QImage] = []
    
            # Transformation for rotation
            transform = QTransform()
            transform.rotate(-90)  # Rotate 90 degrees counterclockwise
    
            y_offset = 0  # Vertical position offset
            for core_img_path, overlay_img_path in zip(corebox_images, overlay_images, strict=True):
                # Load images as QImage
                core_photo = QImage(core_img_path)
                false_color = QImage(overlay_img_path)
    
                # Rotate images
                core_photo = core_photo.transformed(transform, Qt.TransformationMode.FastTransformation)
                false_color = false_color.transformed(transform, Qt.TransformationMode.FastTransformation)
    
                # Resize overlay image to match core photo
                false_color = false_color.scaled(core_photo.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation)
    
                # Create pixmap items
                pixmap_item_core = QGraphicsPixmapItem(QPixmap.fromImage(core_photo))
                pixmap_item_overlay = QGraphicsPixmapItem(QPixmap.fromImage(false_color))
                pixmap_item_overlay.setOpacity(self.alpha)
    
                # Position pixmap items vertically
                pixmap_item_core.setPos(0, y_offset)
                pixmap_item_overlay.setPos(0, y_offset)
    
                # Add items to scene
                self.scene.addItem(pixmap_item_core)
                self.scene.addItem(pixmap_item_overlay)
    
                # Store pixmap items and images
                self.pixmap_items_core.append(pixmap_item_core)
                self.pixmap_items_overlay.append(pixmap_item_overlay)
                self.overlay_images.append(false_color)
    
                # Update y_offset for the next image
                y_offset += core_photo.height()
    
            # Set up QGraphicsView with OpenGL for performance
            self.view = GraphicsView(self.scene)
            self.view.setViewport(QOpenGLWidget())
            self.view.setBackgroundBrush(Qt.GlobalColor.transparent)
            self.view.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
            self.view.setOptimizationFlags(QGraphicsView.OptimizationFlag.DontSavePainterState)
            self.view.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.SmartViewportUpdate)
    
            # Connect signals
            self.view.clicked.connect(self.on_image_clicked)
    
            # Slider for adjusting transparency
            self.slider = QSlider(Qt.Orientation.Horizontal)
            self.slider.setRange(0, 100)
            self.slider.setValue(int(self.alpha * 100))
            self.slider.valueChanged.connect(self.slider_changed)
    
            # Label for transparency slider
            self.slider_label = QLabel("Transparency")
    
            # Button to reset zoom
            self.reset_button = QPushButton("Reset Zoom")
            self.reset_button.clicked.connect(self.reset_zoom)
    
            # Layout
            layout = QVBoxLayout()
            layout.addWidget(self.view)
            layout.addWidget(self.slider_label)
            layout.addWidget(self.slider)
            layout.addWidget(self.reset_button)
            self.setLayout(layout)
    
        def reset_zoom(self) -> None:
            """Reset the zoom to the original state."""
            self.view.resetTransform()
            self.view.zoom = 0
    
        def slider_changed(self, value: float) -> None:
            """Adjust the opacity of the overlay images."""
            self.alpha = value / 100
            for pixmap_item_overlay in self.pixmap_items_overlay:
                pixmap_item_overlay.setOpacity(self.alpha)
    
        def on_image_clicked(self, scene_pos: QPointF) -> None:
            """Handle clicks on the image."""
            # Now check which image was clicked
            for index, pixmap_item_overlay in enumerate(self.pixmap_items_overlay):
                # Get the bounding rectangle of the item in scene coordinates
                item_rect = pixmap_item_overlay.mapRectToScene(pixmap_item_overlay.boundingRect())
                if item_rect.contains(scene_pos):
                    # Map the scene position to the item's local coordinates
                    item_pos = pixmap_item_overlay.mapFromScene(scene_pos)
                    x = item_pos.x()
                    y = item_pos.y()
    
                    # Optionally, get the pixel value from the original image
                    pixel_value = self.overlay_images[index].pixelColor(int(x), int(y))
                    print(f"Clicked on image {index} at coordinates: ({int(x)}, {int(y)})")
                    print(f"Pixel color: {pixel_value}")
                    # Process the coordinates as needed
                    break
    
    
    class GraphicsView(QGraphicsView):
        clicked = pyqtSignal(QPointF)
    
        def __init__(self, scene: QGraphicsScene) -> None:
            super().__init__(scene)
            self.zoom = 0
            self.setMouseTracking(True)
            self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
            self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse)
    
        def wheelEvent(self, event: QWheelEvent) -> None:
            """Handle mouse wheel events for panning and zooming."""
            if event.modifiers() & Qt.KeyboardModifier.ControlModifier:
                # Handle zooming
                zoom_in_factor = 1.25
                zoom_out_factor = 1.0 / zoom_in_factor
    
                # Save the scene pos
                old_pos = self.mapToScene(event.position().toPoint())
    
                # Zoom
                if event.angleDelta().y() > 0:
                    zoom_factor = zoom_in_factor
                    self.zoom += 1
                else:
                    zoom_factor = zoom_out_factor
                    self.zoom -= 1
    
                # Scale the viewer
                self.scale(zoom_factor, zoom_factor)
    
                # Get the new position
                new_pos = self.mapToScene(event.position().toPoint())
    
                # Move scene to old position to achieve zoom at cursor
                delta = new_pos - old_pos
                self.translate(delta.x(), delta.y())
            else:
                # Handle panning with the scroll wheel
                delta = event.pixelDelta() if not event.pixelDelta().isNull() else event.angleDelta() / 8
    
                # Invert delta to match the scroll direction
                self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta.x())
                self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta.y())
    
        def mousePressEvent(self, event: QMouseEvent) -> None:
            """Handle mouse press events."""
            if event.button() == Qt.MouseButton.LeftButton:
                scene_pos = self.mapToScene(event.position().toPoint())
                self.clicked.emit(scene_pos)
            super().mousePressEvent(event)
    
        def mouseReleaseEvent(self, event: QMouseEvent) -> None:
            """Disable dragging when mouse is released."""
            if event.button() == Qt.MouseButton.LeftButton:
                self.setDragMode(QGraphicsView.DragMode.NoDrag)
            super().mouseReleaseEvent(event)
    
    
    # Run the application
    if __name__ == "__main__":
        app = QApplication([])
        window = ImageOverlayApp(
            [
                r"Photo 1.jpg",
                r"Photo 2.jpg",
            ],
            [
                r"Overlay 1.png",
                r"Overlay 2.png",
            ],
        )
    
        window.show()
        app.exec()
    
    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      It seems you would need an item on top of your images that you would paint transparently and then use something like QPainterPath to create the shape for your selection. The QGraphicsPathItem might be enough for your needs.

      Hope it helps

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      1 Reply Last reply
      0

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved