piquadrat
2nd April 2009, 13:38
Hi,
I'm in the process of writing my first Qt application (I've done Swing and SWT applications before). As Python is my favored language, I use PyQt4. My application needs a simple image editor, only cropping and scaling. Scaling is done with a slider (0-100%), which transforms the image and also adjusts the sceneRect to be exactly as big as the image (to get rid of unnecessary scrollbars). That all works fine.
My problem comes with cropping. The user should be able to draw a rectangular cropping area on the image. He should not be able to extend the cropping area outside of the image. And he should be allowed to move the cropping area around after he has drawn it.
Part 1 (restrict to image while extending the cropper) works more or less, if not 100% accurate, with a overridden mouseMoveEvent method on the image.
Part 2 (restrict movement of the cropper to the scene) doesn't work. I read in this thread (http://www.qtcentre.org/forum/f-qt-programming-2/t-moving-of-qgraphicsitem-6841.html) that overriding itemChange is the right way to go, but I don't really understand the value I get in that method. The docs say it's the new position, but according to my tests, it's the delta between the original position of the rect (before the first move operation) and the new position, which irritates the heck out of me. This probably has a perfectly reasonable explanation that involves local, parent, and scene coordinates, but my head is hurting enough with just one coordinate system :)
How could I implement this movement restriction? Here's the code I have so far:
from PyQt4 import QtGui, QtCore
from Ui_editor import Ui_Dialog
import resources_rc
class EditorPixmapItem(QtGui.QGraphicsPixmapItem):
def __init__(self, pixmap, editor):
super(EditorPixmapItem, self).__init__(pixmap)
self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
self.editor = editor
self.crop_rect = None
def mousePressEvent(self, event):
if self.crop_rect:
self.editor.scene.removeItem(self.crop_rect)
self.crop_rect = CropRectItem(self)
self.start_pos = event.pos()
def mouseMoveEvent(self, event):
p = event.pos()
x1, y1, x2, y2 = self.start_pos.x(), self.start_pos.y(), p.x(), p.y()
dims = self.scene().sceneRect()
min_x, max_x, min_y, max_y = dims.x(), dims.width(), dims.y(), dims.height()
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
x1 = max(x1, min_x)
x2 = min(x2, max_x)
y1 = max(y1, min_y)
y2 = min(y2, max_y)
self.crop_rect.setRect(x1, y1, x2 - x1, y2 - y1)
def mouseReleaseEvent(self, event):
self.crop_rect.final_pos = event.pos()
class CropRectItem(QtGui.QGraphicsRectItem):
def __init__(self, parent):
super(CropRectItem, self).__init__(0,0, 0,0)
self.setParentItem(parent)
self.setBrush(QtGui.QColor(150, 150, 150, 150))
self.setFlag(self.ItemIsMovable)
def itemChange(self, change, value):
if change == self.ItemPositionChange:
new_pos = value.toPointF()
print '%f, %f' % (new_pos.x(), new_pos.y())
return QtGui.QGraphicsRectItem.itemChange(self, change, value)
class ImageEditor(QtGui.QDialog):
def __init__(self, parent, image):
super(ImageEditor, self).__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.image = image
self.pixmap_item = EditorPixmapItem(image.pixmap, self)
self.scene = QtGui.QGraphicsScene()
self.scene.setSceneRect(0, 0, self.pixmap_item.pixmap().width(), self.pixmap_item.pixmap().height())
self.scene.addItem(self.pixmap_item)
self.ui.cover_view.setScene(self.scene)
self.ui.cover_view.centerOn(self.pixmap_item)
self.ui.cover_view.setHorizontalScrollBarPolicy(Qt Core.Qt.ScrollBarAsNeeded)
self.ui.cover_view.setVerticalScrollBarPolicy(QtCo re.Qt.ScrollBarAsNeeded)
self.scale_percent = 100
bg_brush = QtGui.QBrush(QtGui.QPixmap(':/img/checkerboard.png'))
self.ui.cover_view.setBackgroundBrush(bg_brush)
QtCore.QObject.connect(self.ui.scale_slider, QtCore.SIGNAL('valueChanged(int)'), self.scale)
def scale(self, value):
self.pixmap_item.resetTransform()
self.pixmap_item.scale(value/100.0, value/100.0)
self.ui.cover_view.setSceneRect(0, 0, int(self.original_width*(value/100.0)), int(self.original_height*(value/100.0)))
self.ui.cover_view.centerOn(self.pixmap_item)
self.scale_percent = value
I'm in the process of writing my first Qt application (I've done Swing and SWT applications before). As Python is my favored language, I use PyQt4. My application needs a simple image editor, only cropping and scaling. Scaling is done with a slider (0-100%), which transforms the image and also adjusts the sceneRect to be exactly as big as the image (to get rid of unnecessary scrollbars). That all works fine.
My problem comes with cropping. The user should be able to draw a rectangular cropping area on the image. He should not be able to extend the cropping area outside of the image. And he should be allowed to move the cropping area around after he has drawn it.
Part 1 (restrict to image while extending the cropper) works more or less, if not 100% accurate, with a overridden mouseMoveEvent method on the image.
Part 2 (restrict movement of the cropper to the scene) doesn't work. I read in this thread (http://www.qtcentre.org/forum/f-qt-programming-2/t-moving-of-qgraphicsitem-6841.html) that overriding itemChange is the right way to go, but I don't really understand the value I get in that method. The docs say it's the new position, but according to my tests, it's the delta between the original position of the rect (before the first move operation) and the new position, which irritates the heck out of me. This probably has a perfectly reasonable explanation that involves local, parent, and scene coordinates, but my head is hurting enough with just one coordinate system :)
How could I implement this movement restriction? Here's the code I have so far:
from PyQt4 import QtGui, QtCore
from Ui_editor import Ui_Dialog
import resources_rc
class EditorPixmapItem(QtGui.QGraphicsPixmapItem):
def __init__(self, pixmap, editor):
super(EditorPixmapItem, self).__init__(pixmap)
self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
self.editor = editor
self.crop_rect = None
def mousePressEvent(self, event):
if self.crop_rect:
self.editor.scene.removeItem(self.crop_rect)
self.crop_rect = CropRectItem(self)
self.start_pos = event.pos()
def mouseMoveEvent(self, event):
p = event.pos()
x1, y1, x2, y2 = self.start_pos.x(), self.start_pos.y(), p.x(), p.y()
dims = self.scene().sceneRect()
min_x, max_x, min_y, max_y = dims.x(), dims.width(), dims.y(), dims.height()
if x1 > x2:
x1, x2 = x2, x1
if y1 > y2:
y1, y2 = y2, y1
x1 = max(x1, min_x)
x2 = min(x2, max_x)
y1 = max(y1, min_y)
y2 = min(y2, max_y)
self.crop_rect.setRect(x1, y1, x2 - x1, y2 - y1)
def mouseReleaseEvent(self, event):
self.crop_rect.final_pos = event.pos()
class CropRectItem(QtGui.QGraphicsRectItem):
def __init__(self, parent):
super(CropRectItem, self).__init__(0,0, 0,0)
self.setParentItem(parent)
self.setBrush(QtGui.QColor(150, 150, 150, 150))
self.setFlag(self.ItemIsMovable)
def itemChange(self, change, value):
if change == self.ItemPositionChange:
new_pos = value.toPointF()
print '%f, %f' % (new_pos.x(), new_pos.y())
return QtGui.QGraphicsRectItem.itemChange(self, change, value)
class ImageEditor(QtGui.QDialog):
def __init__(self, parent, image):
super(ImageEditor, self).__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.image = image
self.pixmap_item = EditorPixmapItem(image.pixmap, self)
self.scene = QtGui.QGraphicsScene()
self.scene.setSceneRect(0, 0, self.pixmap_item.pixmap().width(), self.pixmap_item.pixmap().height())
self.scene.addItem(self.pixmap_item)
self.ui.cover_view.setScene(self.scene)
self.ui.cover_view.centerOn(self.pixmap_item)
self.ui.cover_view.setHorizontalScrollBarPolicy(Qt Core.Qt.ScrollBarAsNeeded)
self.ui.cover_view.setVerticalScrollBarPolicy(QtCo re.Qt.ScrollBarAsNeeded)
self.scale_percent = 100
bg_brush = QtGui.QBrush(QtGui.QPixmap(':/img/checkerboard.png'))
self.ui.cover_view.setBackgroundBrush(bg_brush)
QtCore.QObject.connect(self.ui.scale_slider, QtCore.SIGNAL('valueChanged(int)'), self.scale)
def scale(self, value):
self.pixmap_item.resetTransform()
self.pixmap_item.scale(value/100.0, value/100.0)
self.ui.cover_view.setSceneRect(0, 0, int(self.original_width*(value/100.0)), int(self.original_height*(value/100.0)))
self.ui.cover_view.centerOn(self.pixmap_item)
self.scale_percent = value