mirror of
https://github.com/pythonguis/pythonguis-examples.git
synced 2025-01-13 16:42:55 +08:00
paint: Implement polygon copy using mask.
This commit is contained in:
parent
c4e1edfa80
commit
0b722367e4
10
README.md
10
README.md
@ -1,15 +1,17 @@
|
|||||||
# Minute Apps
|
# 15 Minute Apps
|
||||||
|
|
||||||
A collection of minute (small) desktop applications written in Python
|
A collection of 15 small (minute) desktop applications written in Python
|
||||||
using the PyQt framework. These apps are intended as examples from
|
using the PyQt framework. These apps are intended as examples from
|
||||||
which you can poke, hack and prod your way to writing your own tools.
|
which you can poke, hack and prod your way to writing your own tools.
|
||||||
|
|
||||||
If you find these apps interesting, or want to learn more about
|
If you find these apps interesting, or want to learn more about
|
||||||
PyQt in general, [take a look at my ebook & online course
|
PyQt in general, [take a look at my ebook & online course
|
||||||
"Create Simple GUI Applications"](https://martinfitzpatrick.name/create-simple-gui-applications)
|
"Create Simple GUI Applications"](https://martinfitzpatrick.name/create-simple-gui-applications)
|
||||||
which covers everything you need to know to get you programming with PyQt.
|
which covers everything you need to know to start building your own applications with PyQt.
|
||||||
|
|
||||||
|
More articles, tutorials and projects using PyQt can be
|
||||||
|
found [on my site](http://martinfitzpatrick.name/tag/pyqt).
|
||||||
|
|
||||||
Write-ups of these apps, including design explanations and walkthroughs, can be found [on my site]().
|
|
||||||
|
|
||||||
All code is **licensed under an MIT license**. This allows you to re-use the code freely,
|
All code is **licensed under an MIT license**. This allows you to re-use the code freely,
|
||||||
remixed in both commercial and non-commercial projects. The only requirement is that you must
|
remixed in both commercial and non-commercial projects. The only requirement is that you must
|
||||||
|
163
paint/paint.py
163
paint/paint.py
@ -34,6 +34,8 @@ MODES = [
|
|||||||
'ellipse', 'roundrect'
|
'ellipse', 'roundrect'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
CANVAS_DIMENSIONS = 600, 400
|
||||||
|
|
||||||
STAMP_DIR = './stamps'
|
STAMP_DIR = './stamps'
|
||||||
STAMPS = [os.path.join(STAMP_DIR, f) for f in os.listdir(STAMP_DIR)]
|
STAMPS = [os.path.join(STAMP_DIR, f) for f in os.listdir(STAMP_DIR)]
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ class Canvas(QLabel):
|
|||||||
secondary_color_updated = pyqtSignal(str)
|
secondary_color_updated = pyqtSignal(str)
|
||||||
|
|
||||||
active_color = None
|
active_color = None
|
||||||
|
preview_pen = None
|
||||||
|
|
||||||
timer_event = None
|
timer_event = None
|
||||||
|
|
||||||
@ -59,7 +62,7 @@ class Canvas(QLabel):
|
|||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
# Create the pixmap for display.
|
# Create the pixmap for display.
|
||||||
self.setPixmap(QPixmap(600,400))
|
self.setPixmap(QPixmap(*CANVAS_DIMENSIONS))
|
||||||
self.eraser_color = self.secondary_color or QColor(Qt.white)
|
self.eraser_color = self.secondary_color or QColor(Qt.white)
|
||||||
self.eraser_color.setAlpha(100)
|
self.eraser_color.setAlpha(100)
|
||||||
self.pixmap().fill(self.background_color)
|
self.pixmap().fill(self.background_color)
|
||||||
@ -136,10 +139,86 @@ class Canvas(QLabel):
|
|||||||
|
|
||||||
# Mode-specific events.
|
# Mode-specific events.
|
||||||
|
|
||||||
|
# Select polygon events
|
||||||
|
|
||||||
|
def selectpoly_mousePressEvent(self, e):
|
||||||
|
if not self.locked or e.button == Qt.RightButton:
|
||||||
|
self.active_shape_fn = 'drawPolygon'
|
||||||
|
self.preview_pen = SELECTION_PEN
|
||||||
|
self.generic_poly_mousePressEvent(e)
|
||||||
|
|
||||||
|
def selectpoly_timerEvent(self, final=False):
|
||||||
|
self.generic_poly_timerEvent(final)
|
||||||
|
|
||||||
|
def selectpoly_mouseMoveEvent(self, e):
|
||||||
|
if not self.locked:
|
||||||
|
self.generic_poly_mouseMoveEvent(e)
|
||||||
|
|
||||||
|
def selectpoly_mouseDoubleClickEvent(self, e):
|
||||||
|
self.current_pos = e.pos()
|
||||||
|
self.locked = True
|
||||||
|
|
||||||
|
def selectpoly_copy(self):
|
||||||
|
"""
|
||||||
|
Copy a polygon region from the current image, returning it.
|
||||||
|
|
||||||
|
Create a mask for the selected area, and use it to blank
|
||||||
|
out non-selected regions. Then get the bounding rect of the
|
||||||
|
selection and crop to produce the smallest possible image.
|
||||||
|
|
||||||
|
:return: QPixmap of the copied region.
|
||||||
|
"""
|
||||||
|
self.timer_cleanup()
|
||||||
|
|
||||||
|
pixmap = self.pixmap().copy()
|
||||||
|
bitmap = QBitmap(*CANVAS_DIMENSIONS)
|
||||||
|
bitmap.clear() # Starts with random data visible.
|
||||||
|
|
||||||
|
p = QPainter(bitmap)
|
||||||
|
# Construct a mask where the user selected area will be kept, the rest removed from the image is transparent.
|
||||||
|
userpoly = QPolygon(self.history_pos + [self.current_pos])
|
||||||
|
p.setPen(QPen(Qt.color1))
|
||||||
|
p.setBrush(QBrush(Qt.color1)) # Solid color, Qt.color1 == bit on.
|
||||||
|
p.drawPolygon(userpoly)
|
||||||
|
p.end()
|
||||||
|
|
||||||
|
# Set our created mask on the image.
|
||||||
|
pixmap.setMask(bitmap)
|
||||||
|
|
||||||
|
# Calculate the bounding rect and return a copy of that region.
|
||||||
|
return pixmap.copy(userpoly.boundingRect())
|
||||||
|
|
||||||
|
# Select rectangle events
|
||||||
|
|
||||||
|
def selectrect_mousePressEvent(self, e):
|
||||||
|
self.active_shape_fn = 'drawRect'
|
||||||
|
self.preview_pen = SELECTION_PEN
|
||||||
|
self.generic_shape_mousePressEvent(e)
|
||||||
|
|
||||||
|
def selectrect_timerEvent(self, final=False):
|
||||||
|
self.generic_shape_timerEvent(final)
|
||||||
|
|
||||||
|
def selectrect_mouseMoveEvent(self, e):
|
||||||
|
if not self.locked:
|
||||||
|
self.current_pos = e.pos()
|
||||||
|
|
||||||
|
def selectrect_mouseReleaseEvent(self, e):
|
||||||
|
self.current_pos = e.pos()
|
||||||
|
self.locked = True
|
||||||
|
|
||||||
|
def selectrect_copy(self):
|
||||||
|
"""
|
||||||
|
Copy a rectangle region of the current image, returning it.
|
||||||
|
|
||||||
|
:return: QPixmap of the copied region.
|
||||||
|
"""
|
||||||
|
self.timer_cleanup()
|
||||||
|
return self.pixmap().copy(QRect(self.origin_pos, self.current_pos))
|
||||||
|
|
||||||
# Eraser events
|
# Eraser events
|
||||||
|
|
||||||
def eraser_mousePressEvent(self, e):
|
def eraser_mousePressEvent(self, e):
|
||||||
return self.generic_mousePressEvent(e)
|
self.generic_mousePressEvent(e)
|
||||||
|
|
||||||
def eraser_mouseMoveEvent(self, e):
|
def eraser_mouseMoveEvent(self, e):
|
||||||
if self.last_pos:
|
if self.last_pos:
|
||||||
@ -151,7 +230,7 @@ class Canvas(QLabel):
|
|||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def eraser_mouseReleaseEvent(self, e):
|
def eraser_mouseReleaseEvent(self, e):
|
||||||
return self.generic_mouseReleaseEvent(e)
|
self.generic_mouseReleaseEvent(e)
|
||||||
|
|
||||||
# Stamp (pie) events
|
# Stamp (pie) events
|
||||||
|
|
||||||
@ -164,43 +243,41 @@ class Canvas(QLabel):
|
|||||||
# Pen events
|
# Pen events
|
||||||
|
|
||||||
def pen_mousePressEvent(self, e):
|
def pen_mousePressEvent(self, e):
|
||||||
return self.generic_mousePressEvent(e)
|
self.generic_mousePressEvent(e)
|
||||||
|
|
||||||
def pen_mouseMoveEvent(self, e):
|
def pen_mouseMoveEvent(self, e):
|
||||||
if self.last_pos:
|
if self.last_pos:
|
||||||
p = QPainter(self.pixmap())
|
p = QPainter(self.pixmap())
|
||||||
p.setPen(QPen(self.active_color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
|
p.setPen(QPen(self.active_color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
|
||||||
|
|
||||||
p.drawLine(self.last_pos, e.pos())
|
p.drawLine(self.last_pos, e.pos())
|
||||||
|
|
||||||
self.last_pos = e.pos()
|
self.last_pos = e.pos()
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def pen_mouseReleaseEvent(self, e):
|
def pen_mouseReleaseEvent(self, e):
|
||||||
return self.generic_mouseReleaseEvent(e)
|
self.generic_mouseReleaseEvent(e)
|
||||||
|
|
||||||
# Brush events
|
# Brush events
|
||||||
|
|
||||||
def brush_mousePressEvent(self, e):
|
def brush_mousePressEvent(self, e):
|
||||||
return self.generic_mousePressEvent(e)
|
self.generic_mousePressEvent(e)
|
||||||
|
|
||||||
def brush_mouseMoveEvent(self, e):
|
def brush_mouseMoveEvent(self, e):
|
||||||
if self.last_pos:
|
if self.last_pos:
|
||||||
p = QPainter(self.pixmap())
|
p = QPainter(self.pixmap())
|
||||||
p.setPen(QPen(self.active_color, 10, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
|
p.setPen(QPen(self.active_color, 10, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
|
||||||
|
|
||||||
p.drawLine(self.last_pos, e.pos())
|
p.drawLine(self.last_pos, e.pos())
|
||||||
|
|
||||||
self.last_pos = e.pos()
|
self.last_pos = e.pos()
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def brush_mouseReleaseEvent(self, e):
|
def brush_mouseReleaseEvent(self, e):
|
||||||
return self.generic_mouseReleaseEvent(e)
|
self.generic_mouseReleaseEvent(e)
|
||||||
|
|
||||||
# Spray events
|
# Spray events
|
||||||
|
|
||||||
def spray_mousePressEvent(self, e):
|
def spray_mousePressEvent(self, e):
|
||||||
return self.generic_mousePressEvent(e)
|
self.generic_mousePressEvent(e)
|
||||||
|
|
||||||
def spray_mouseMoveEvent(self, e):
|
def spray_mouseMoveEvent(self, e):
|
||||||
if self.last_pos:
|
if self.last_pos:
|
||||||
@ -289,48 +366,7 @@ class Canvas(QLabel):
|
|||||||
self.set_secondary_color(hex)
|
self.set_secondary_color(hex)
|
||||||
self.secondary_color_updated.emit(hex) # Update UI.
|
self.secondary_color_updated.emit(hex) # Update UI.
|
||||||
|
|
||||||
# Select rectangle events
|
# Generic shape events: Rectangle, Ellipse, Rounded-rect
|
||||||
|
|
||||||
def selectrect_mousePressEvent(self, e):
|
|
||||||
if self.current_pos:
|
|
||||||
# Clear up indicator.
|
|
||||||
self.timer_cleanup()
|
|
||||||
self.reset_mode()
|
|
||||||
|
|
||||||
self.origin_pos = e.pos()
|
|
||||||
self.current_pos = e.pos()
|
|
||||||
self.timer_event = self.selectrect_timerEvent
|
|
||||||
|
|
||||||
def selectrect_timerEvent(self, final=False):
|
|
||||||
p = QPainter(self.pixmap())
|
|
||||||
p.setCompositionMode(QPainter.RasterOp_SourceXorDestination)
|
|
||||||
pen = QPen(QColor(0xff, 0xff, 0xff), 1, Qt.DashLine)
|
|
||||||
pen.setDashOffset(self.dash_offset)
|
|
||||||
p.setPen(pen)
|
|
||||||
if self.last_pos:
|
|
||||||
p.drawRect(QRect(self.origin_pos, self.last_pos))
|
|
||||||
|
|
||||||
self.dash_offset -= 1
|
|
||||||
if not final:
|
|
||||||
pen.setDashOffset(self.dash_offset)
|
|
||||||
p.setPen(pen)
|
|
||||||
p.drawRect(QRect(self.origin_pos, self.current_pos))
|
|
||||||
|
|
||||||
self.update()
|
|
||||||
self.last_pos = self.current_pos
|
|
||||||
|
|
||||||
def selectrect_mouseMoveEvent(self, e):
|
|
||||||
if self.locked == False:
|
|
||||||
self.current_pos = e.pos()
|
|
||||||
|
|
||||||
def selectrect_mouseReleaseEvent(self, e):
|
|
||||||
self.current_pos = e.pos()
|
|
||||||
self.locked = True
|
|
||||||
|
|
||||||
def selectrect_copy(self):
|
|
||||||
return self.pixmap().copy(QRect(self.origin_pos, self.current_pos))
|
|
||||||
|
|
||||||
# Generic shape events: Rectangle & Ellipse
|
|
||||||
|
|
||||||
def generic_shape_mousePressEvent(self, e):
|
def generic_shape_mousePressEvent(self, e):
|
||||||
self.origin_pos = e.pos()
|
self.origin_pos = e.pos()
|
||||||
@ -340,12 +376,16 @@ class Canvas(QLabel):
|
|||||||
def generic_shape_timerEvent(self, final=False):
|
def generic_shape_timerEvent(self, final=False):
|
||||||
p = QPainter(self.pixmap())
|
p = QPainter(self.pixmap())
|
||||||
p.setCompositionMode(QPainter.RasterOp_SourceXorDestination)
|
p.setCompositionMode(QPainter.RasterOp_SourceXorDestination)
|
||||||
pen = PREVIEW_PEN
|
pen = self.preview_pen
|
||||||
|
pen.setDashOffset(self.dash_offset)
|
||||||
p.setPen(pen)
|
p.setPen(pen)
|
||||||
if self.last_pos:
|
if self.last_pos:
|
||||||
getattr(p, self.active_shape_fn)(QRect(self.origin_pos, self.last_pos), *self.active_shape_args)
|
getattr(p, self.active_shape_fn)(QRect(self.origin_pos, self.last_pos), *self.active_shape_args)
|
||||||
|
|
||||||
if not final:
|
if not final:
|
||||||
|
self.dash_offset -= 1
|
||||||
|
pen.setDashOffset(self.dash_offset)
|
||||||
|
p.setPen(pen)
|
||||||
getattr(p, self.active_shape_fn)(QRect(self.origin_pos, self.current_pos), *self.active_shape_args)
|
getattr(p, self.active_shape_fn)(QRect(self.origin_pos, self.current_pos), *self.active_shape_args)
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
@ -369,18 +409,18 @@ class Canvas(QLabel):
|
|||||||
|
|
||||||
self.reset_mode()
|
self.reset_mode()
|
||||||
|
|
||||||
|
|
||||||
# Line events
|
# Line events
|
||||||
|
|
||||||
def line_mousePressEvent(self, e):
|
def line_mousePressEvent(self, e):
|
||||||
self.origin_pos = e.pos()
|
self.origin_pos = e.pos()
|
||||||
self.current_pos = e.pos()
|
self.current_pos = e.pos()
|
||||||
|
self.preview_pen = PREVIEW_PEN
|
||||||
self.timer_event = self.line_timerEvent
|
self.timer_event = self.line_timerEvent
|
||||||
|
|
||||||
def line_timerEvent(self, final=False):
|
def line_timerEvent(self, final=False):
|
||||||
p = QPainter(self.pixmap())
|
p = QPainter(self.pixmap())
|
||||||
p.setCompositionMode(QPainter.RasterOp_SourceXorDestination)
|
p.setCompositionMode(QPainter.RasterOp_SourceXorDestination)
|
||||||
pen = PREVIEW_PEN
|
pen = self.preview_pen
|
||||||
p.setPen(pen)
|
p.setPen(pen)
|
||||||
if self.last_pos:
|
if self.last_pos:
|
||||||
p.drawLine(self.origin_pos, self.last_pos)
|
p.drawLine(self.origin_pos, self.last_pos)
|
||||||
@ -417,7 +457,7 @@ class Canvas(QLabel):
|
|||||||
self.current_pos = e.pos()
|
self.current_pos = e.pos()
|
||||||
self.timer_event = self.generic_poly_timerEvent
|
self.timer_event = self.generic_poly_timerEvent
|
||||||
|
|
||||||
if e.button() == Qt.RightButton and self.history_pos:
|
elif e.button() == Qt.RightButton and self.history_pos:
|
||||||
# Clean up, we're not drawing
|
# Clean up, we're not drawing
|
||||||
self.timer_cleanup()
|
self.timer_cleanup()
|
||||||
self.reset_mode()
|
self.reset_mode()
|
||||||
@ -425,12 +465,16 @@ class Canvas(QLabel):
|
|||||||
def generic_poly_timerEvent(self, final=False):
|
def generic_poly_timerEvent(self, final=False):
|
||||||
p = QPainter(self.pixmap())
|
p = QPainter(self.pixmap())
|
||||||
p.setCompositionMode(QPainter.RasterOp_SourceXorDestination)
|
p.setCompositionMode(QPainter.RasterOp_SourceXorDestination)
|
||||||
pen = PREVIEW_PEN
|
pen = self.preview_pen
|
||||||
|
pen.setDashOffset(self.dash_offset)
|
||||||
p.setPen(pen)
|
p.setPen(pen)
|
||||||
if self.last_pos:
|
if self.last_pos:
|
||||||
getattr(p, self.active_shape_fn)(*self.history_pos + [self.last_pos])
|
getattr(p, self.active_shape_fn)(*self.history_pos + [self.last_pos])
|
||||||
|
|
||||||
if not final:
|
if not final:
|
||||||
|
self.dash_offset -= 1
|
||||||
|
pen.setDashOffset(self.dash_offset)
|
||||||
|
p.setPen(pen)
|
||||||
getattr(p, self.active_shape_fn)(*self.history_pos + [self.current_pos])
|
getattr(p, self.active_shape_fn)(*self.history_pos + [self.current_pos])
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
@ -456,6 +500,7 @@ class Canvas(QLabel):
|
|||||||
|
|
||||||
def polyline_mousePressEvent(self, e):
|
def polyline_mousePressEvent(self, e):
|
||||||
self.active_shape_fn = 'drawPolyline'
|
self.active_shape_fn = 'drawPolyline'
|
||||||
|
self.preview_pen = PREVIEW_PEN
|
||||||
self.generic_poly_mousePressEvent(e)
|
self.generic_poly_mousePressEvent(e)
|
||||||
|
|
||||||
def polyline_timerEvent(self, final=False):
|
def polyline_timerEvent(self, final=False):
|
||||||
@ -472,6 +517,7 @@ class Canvas(QLabel):
|
|||||||
def rect_mousePressEvent(self, e):
|
def rect_mousePressEvent(self, e):
|
||||||
self.active_shape_fn = 'drawRect'
|
self.active_shape_fn = 'drawRect'
|
||||||
self.active_shape_args = ()
|
self.active_shape_args = ()
|
||||||
|
self.preview_pen = PREVIEW_PEN
|
||||||
self.generic_shape_mousePressEvent(e)
|
self.generic_shape_mousePressEvent(e)
|
||||||
|
|
||||||
def rect_timerEvent(self, final=False):
|
def rect_timerEvent(self, final=False):
|
||||||
@ -487,6 +533,7 @@ class Canvas(QLabel):
|
|||||||
|
|
||||||
def polygon_mousePressEvent(self, e):
|
def polygon_mousePressEvent(self, e):
|
||||||
self.active_shape_fn = 'drawPolygon'
|
self.active_shape_fn = 'drawPolygon'
|
||||||
|
self.preview_pen = PREVIEW_PEN
|
||||||
self.generic_poly_mousePressEvent(e)
|
self.generic_poly_mousePressEvent(e)
|
||||||
|
|
||||||
def polygon_timerEvent(self, final=False):
|
def polygon_timerEvent(self, final=False):
|
||||||
@ -503,6 +550,7 @@ class Canvas(QLabel):
|
|||||||
def ellipse_mousePressEvent(self, e):
|
def ellipse_mousePressEvent(self, e):
|
||||||
self.active_shape_fn = 'drawEllipse'
|
self.active_shape_fn = 'drawEllipse'
|
||||||
self.active_shape_args = ()
|
self.active_shape_args = ()
|
||||||
|
self.preview_pen = PREVIEW_PEN
|
||||||
self.generic_shape_mousePressEvent(e)
|
self.generic_shape_mousePressEvent(e)
|
||||||
|
|
||||||
def ellipse_timerEvent(self, final=False):
|
def ellipse_timerEvent(self, final=False):
|
||||||
@ -519,6 +567,7 @@ class Canvas(QLabel):
|
|||||||
def roundrect_mousePressEvent(self, e):
|
def roundrect_mousePressEvent(self, e):
|
||||||
self.active_shape_fn = 'drawRoundedRect'
|
self.active_shape_fn = 'drawRoundedRect'
|
||||||
self.active_shape_args = (25, 25)
|
self.active_shape_args = (25, 25)
|
||||||
|
self.preview_pen = PREVIEW_PEN
|
||||||
self.generic_shape_mousePressEvent(e)
|
self.generic_shape_mousePressEvent(e)
|
||||||
|
|
||||||
def roundrect_timerEvent(self, final=False):
|
def roundrect_timerEvent(self, final=False):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user