Exemple #1
0
    def draw_selection_rect(self, painter):
        cr, sr = self.target, self.selection_state.rect
        painter.setPen(self.SELECT_PEN)
        painter.setRenderHint(QPainter.Antialiasing, False)
        if self.selection_state.current_mode == 'selected':
            # Shade out areas outside the selection rect
            for r in (
                QRectF(cr.topLeft(), QPointF(sr.left(), cr.bottom())),  # left
                QRectF(QPointF(sr.left(), cr.top()), sr.topRight()),  # top
                QRectF(QPointF(sr.right(), cr.top()), cr.bottomRight()),  # right
                QRectF(sr.bottomLeft(), QPointF(sr.right(), cr.bottom())),  # bottom
            ):
                painter.fillRect(r, self.SHADE_COLOR)

            dr = self.get_drag_rect()
            if self.selection_state.in_selection and dr is not None:
                # Draw the resize rectangle
                painter.save()
                painter.setCompositionMode(QPainter.RasterOp_SourceAndNotDestination)
                painter.setClipRect(sr.adjusted(1, 1, -1, -1))
                painter.drawRect(dr)
                painter.restore()

        # Draw the selection rectangle
        painter.setCompositionMode(QPainter.RasterOp_SourceAndNotDestination)
        painter.drawRect(sr)
Exemple #2
0
 def draw_pixmap(self, painter):
     p = self.current_scaled_pixmap
     width, height = p.width(), p.height()
     pwidth, pheight = self.last_canvas_size
     x = int(abs(pwidth - width)/2.)
     y = int(abs(pheight - height)/2.)
     self.target = QRectF(x, y, width, height)
     painter.drawPixmap(self.target, p, QRectF(p.rect()))
Exemple #3
0
 def __call__(self, canvas):
     if canvas.has_selection and canvas.selection_state.rect is not None:
         pimg = self.after_image
         img = self.after_image = QImage(canvas.current_image)
         rect = QRectF(*get_selection_rect(img, canvas.selection_state.rect, canvas.target))
         p = QPainter(img)
         p.setRenderHint(p.SmoothPixmapTransform, True)
         p.drawImage(rect, pimg, QRectF(pimg.rect()))
         p.end()
     return self.after_image
Exemple #4
0
 def _paint_title(self, p: QPainter):
     p.drawLine(Block.padding, 35 + Block.padding, self.width() - Block.padding, 35 + Block.padding)
     p.setPen(self._fg_color)
     f = p.font()
     f.setPointSize(10)
     f.setBold(True)
     p.setFont(f)
     p.drawText(QRectF(4 + Block.padding, Block.padding + 2, self.width() - 12, 25),
                str(self.settings["Name"].value()))
     f.setBold(False)
     f.setPointSize(8)
     p.setPen(QColor(self._fg_color.red(), self._fg_color.green(), self._fg_color.blue(), 100))
     p.setFont(f)
     p.drawText(QRectF(4 + Block.padding, 18 + Block.padding, self.width() - 12, 15), str(self.__type_name))
Exemple #5
0
 def paint(self, painter, option, index):
     QStyledItemDelegate.paint(self, painter, option, self.dummy_index)
     painter.save()
     painter.setClipRect(QRectF(option.rect))
     painter.translate(option.rect.topLeft())
     self.to_doc(index, option).drawContents(painter)
     painter.restore()
Exemple #6
0
    def _paint_content(self, p: QPainter):
        t = self.settings["Value"].type()
        p.setPen(self._fg_color)
        f = self.font()
        f.setPointSize(9)
        p.setFont(f)
        p.drawText(QRectF(Block.padding + 5, self.height() / 2 + 10, self.width() - Block.padding * 2 - 10,
                          30), Qt.AlignCenter, str(t))

        f.setPointSize(12)
        p.setFont(f)
        s = 'None'
        if self.settings["Value"].data() is not None:
            s = str(self.settings["Value"].data())
        p.drawText(QRectF(Block.padding + 5, self.height() / 2 - 5, self.width() - Block.padding * 2 - 10,
                          30), Qt.AlignCenter, s)
Exemple #7
0
 def draw_pixmap(self, painter):
     p = self.current_scaled_pixmap
     width, height = p.width(), p.height()
     pwidth, pheight = self.last_canvas_size
     x = int(abs(pwidth - width)/2.)
     y = int(abs(pheight - height)/2.)
     self.target = QRectF(x, y, width, height)
     painter.drawPixmap(self.target, p, QRectF(p.rect()))
Exemple #8
0
 def _paint_title(self, p: QPainter):
     f = p.font()
     f.setBold(True)
     f.setPointSize(8)
     p.setPen(QColor(self._fg_color.red(), self._fg_color.green(), self._fg_color.blue(), 180))
     p.setFont(f)
     title = str(self.name()) + ' : ' + self.type_name()
     p.drawText(QRectF(6 + Block.padding, 25 + Block.padding, self.width() - 12, 15), title)
Exemple #9
0
 def paint(self, painter, option, index):
     painter.save()
     painter.setClipRect(QRectF(option.rect))
     if hasattr(QStyle, 'CE_ItemViewItem'):
         QApplication.style().drawControl(QStyle.CE_ItemViewItem, option,
                                          painter)
     elif option.state & QStyle.State_Selected:
         painter.fillRect(option.rect, option.palette.highlight())
     painter.translate(option.rect.topLeft())
     self.to_doc(index).drawContents(painter)
     painter.restore()
Exemple #10
0
 def get_drag_rect(self):
     sr = self.selection_state.rect
     dc = self.selection_state.drag_corner
     if None in (sr, dc):
         return
     dx, dy = self.dc_size
     if None in dc:
         # An edge
         if dc[0] is None:
             top = sr.top() if dc[1] == 'top' else sr.bottom() - dy
             ans = QRectF(sr.left() + dx, top, sr.width() - 2 * dx, dy)
         else:
             left = sr.left() if dc[0] == 'left' else sr.right() - dx
             ans = QRectF(left, sr.top() + dy, dx, sr.height() - 2 * dy)
     else:
         # A corner
         left = sr.left() if dc[0] == 'left' else sr.right() - dx
         top = sr.top() if dc[1] == 'top' else sr.bottom() - dy
         ans = QRectF(left, top, dx, dy)
     return ans
Exemple #11
0
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.ClickFocus)
        self.selection_state = SelectionState()
        self.undo_stack = u = QUndoStack()
        u.setUndoLimit(10)
        u.canUndoChanged.connect(self.emit_undo_redo_state)
        u.canRedoChanged.connect(self.emit_undo_redo_state)

        self.original_image_data = None
        self.is_valid = False
        self.original_image_format = None
        self.current_image = None
        self.current_scaled_pixmap = None
        self.last_canvas_size = None
        self.target = QRectF(0, 0, 0, 0)

        self.undo_action = a = self.undo_stack.createUndoAction(self, _('Undo') + ' ')
        a.setIcon(QIcon(I('edit-undo.png')))
        self.redo_action = a = self.undo_stack.createRedoAction(self, _('Redo') + ' ')
        a.setIcon(QIcon(I('edit-redo.png')))
Exemple #12
0
def full(p, xmax, ymax):
    p.drawRect(0, 0, xmax, ymax)
    p.drawPolyline(QPoint(0, 0), QPoint(xmax, 0), QPoint(xmax, ymax),
                   QPoint(0, ymax), QPoint(0, 0))
    pp = QPainterPath()
    pp.addRect(0, 0, xmax, ymax)
    p.drawPath(pp)
    p.save()
    for i in xrange(3):
        col = [0, 0, 0, 200]
        col[i] = 255
        p.setOpacity(0.3)
        p.fillRect(0, 0, xmax / 10, xmax / 10, QBrush(QColor(*col)))
        p.setOpacity(1)
        p.drawRect(0, 0, xmax / 10, xmax / 10)
        p.translate(xmax / 10, xmax / 10)
        p.scale(1, 1.5)
    p.restore()

    # p.scale(2, 2)
    # p.rotate(45)
    p.drawPixmap(0, 0, xmax / 4, xmax / 4, QPixmap(I('library.png')))
    p.drawRect(0, 0, xmax / 4, xmax / 4)

    f = p.font()
    f.setPointSize(20)
    # f.setLetterSpacing(f.PercentageSpacing, 200)
    f.setUnderline(True)
    # f.setOverline(True)
    # f.setStrikeOut(True)
    f.setFamily('Calibri')
    p.setFont(f)
    # p.setPen(QColor(0, 0, 255))
    # p.scale(2, 2)
    # p.rotate(45)
    p.drawText(QPoint(xmax / 3.9, 30), 'Some—text not By’s ū --- Д AV ff ff')

    b = QBrush(Qt.HorPattern)
    b.setColor(QColor(Qt.blue))
    pix = QPixmap(I('console.png'))
    w = xmax / 4
    p.fillRect(0, ymax / 3, w, w, b)
    p.fillRect(xmax / 3, ymax / 3, w, w, QBrush(pix))
    x, y = 2 * xmax / 3, ymax / 3
    p.drawTiledPixmap(QRectF(x, y, w, w), pix, QPointF(10, 10))

    x, y = 1, ymax / 1.9
    g = QLinearGradient(QPointF(x, y), QPointF(x + w, y + w))
    g.setColorAt(0, QColor('#00f'))
    g.setColorAt(1, QColor('#fff'))
    p.fillRect(x, y, w, w, QBrush(g))
Exemple #13
0
 def setImageCoordinates(self, nx, ny, x0, y0, l0, m0, dl, dm):
     """Sets up image coordinates. Pixel x0,y0 is centered at location l0,m0 in the plot, pixel size is dl,dm, image size is (nx,ny)"""
     dprint(2, "image coordinates are", nx, ny, x0, y0, l0, m0, dl, dm)
     self._nx, self._ny = nx, ny
     self._l0, self._m0 = l0, m0
     self._dl, self._dm = dl, dm
     self._x0, self._y0 = x0, y0
     self._lminmax = (l0 - dl * (x0 + 0.5), l0 + (nx - x0 - 0.5) * dl)
     if dl < 0:
         self._lminmax = (self._lminmax[1], self._lminmax[0])
     self._mminmax = (m0 - dm * (y0 + 0.5), m0 + (ny - y0 - 0.5) * dm)
     self._bounding_rect = QRectF(self._lminmax[0], self._mminmax[0],
                                  nx * abs(dl), ny * abs(dm))
     self._bounding_rect_pix = QRect(0, 0, nx, ny)
     dprint(2, "image extents are", self._lminmax, self._mminmax)
Exemple #14
0
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.ClickFocus)
        self.selection_state = SelectionState()
        self.undo_stack = u = QUndoStack()
        u.setUndoLimit(10)
        u.canUndoChanged.connect(self.emit_undo_redo_state)
        u.canRedoChanged.connect(self.emit_undo_redo_state)

        self.original_image_data = None
        self.is_valid = False
        self.original_image_format = None
        self.current_image = None
        self.current_scaled_pixmap = None
        self.last_canvas_size = None
        self.target = QRectF(0, 0, 0, 0)

        self.undo_action = a = self.undo_stack.createUndoAction(self, _('Undo') + ' ')
        a.setIcon(QIcon(I('edit-undo.png')))
        self.redo_action = a = self.undo_stack.createRedoAction(self, _('Redo') + ' ')
        a.setIcon(QIcon(I('edit-redo.png')))
Exemple #15
0
 def mouseMoveEvent(self, ev):
     changed = False
     if self.selection_state.in_selection:
         changed = True
     self.selection_state.in_selection = False
     self.selection_state.drag_corner = None
     pos = ev.pos()
     cursor = Qt.ArrowCursor
     try:
         if not self.target.contains(pos):
             return
         if ev.buttons() & Qt.LeftButton:
             if self.selection_state.last_press_point is not None and self.selection_state.current_mode is not None:
                 if self.selection_state.current_mode == 'select':
                     self.selection_state.rect = QRectF(self.selection_state.last_press_point, pos).normalized()
                     changed = True
                 elif self.selection_state.last_drag_pos is not None:
                     self.selection_state.in_selection = True
                     self.selection_state.drag_corner = self.selection_state.dragging
                     dp = pos - self.selection_state.last_drag_pos
                     self.selection_state.last_drag_pos = pos
                     self.move_selection(dp)
                     cursor = self.get_cursor()
                     changed = True
         else:
             if self.selection_state.rect is None or not self.selection_state.rect.contains(pos):
                 return
             if self.selection_state.current_mode == 'selected':
                 if self.selection_state.rect is not None and self.selection_state.rect.contains(pos):
                     self.selection_state.drag_corner = self.get_drag_corner(pos)
                     self.selection_state.in_selection = True
                     cursor = self.get_cursor()
                     changed = True
     finally:
         if changed:
             self.update()
         self.setCursor(cursor)
    def boundingRect(self):
        if self.isValid():
            return items.PolygonItem.boundingRect(self)

        return QRectF()
Exemple #17
0
class Canvas(QWidget):

    BACKGROUND = QColor(60, 60, 60)
    SHADE_COLOR = QColor(0, 0, 0, 180)
    SELECT_PEN = QPen(QColor(Qt.white))

    selection_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    image_changed = pyqtSignal(object)

    @property
    def has_selection(self):
        return self.selection_state.current_mode == 'selected'

    @property
    def is_modified(self):
        return self.current_image is not self.original_image

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.ClickFocus)
        self.selection_state = SelectionState()
        self.undo_stack = u = QUndoStack()
        u.setUndoLimit(10)
        u.canUndoChanged.connect(self.emit_undo_redo_state)
        u.canRedoChanged.connect(self.emit_undo_redo_state)

        self.original_image_data = None
        self.is_valid = False
        self.original_image_format = None
        self.current_image = None
        self.current_scaled_pixmap = None
        self.last_canvas_size = None
        self.target = QRectF(0, 0, 0, 0)

        self.undo_action = a = self.undo_stack.createUndoAction(self, _('Undo') + ' ')
        a.setIcon(QIcon(I('edit-undo.png')))
        self.redo_action = a = self.undo_stack.createRedoAction(self, _('Redo') + ' ')
        a.setIcon(QIcon(I('edit-redo.png')))

    def load_image(self, data):
        self.is_valid = False
        try:
            fmt = identify_data(data)[-1].encode('ascii')
        except Exception:
            fmt = b''
        self.original_image_format = fmt.decode('ascii').lower()
        self.selection_state.reset()
        self.original_image_data = data
        self.current_image = i = self.original_image = (
            QImage.fromData(data, format=fmt) if fmt else QImage.fromData(data))
        self.is_valid = not i.isNull()
        self.update()
        self.image_changed.emit(self.current_image)

    def set_image(self, qimage):
        self.selection_state.reset()
        self.current_scaled_pixmap = None
        self.current_image = qimage
        self.is_valid = not qimage.isNull()
        self.update()
        self.image_changed.emit(self.current_image)

    def get_image_data(self, quality=90):
        if not self.is_modified:
            return self.original_image_data
        return pixmap_to_data(self.current_image, format=self.original_image_format or 'JPEG', quality=90)

    def copy(self):
        if not self.is_valid:
            return
        clipboard = QApplication.clipboard()
        if not self.has_selection or self.selection_state.rect is None:
            clipboard.setImage(self.current_image)
        else:
            trim = Trim(self)
            clipboard.setImage(trim.after_image)
            trim.before_image = trim.after_image = None

    def paste(self):
        clipboard = QApplication.clipboard()
        md = clipboard.mimeData()
        if md.hasImage():
            img = QImage(md.imageData())
            if not img.isNull():
                self.undo_stack.push(Replace(img, _('Paste image'), self))
        else:
            error_dialog(self, _('No image'), _(
                'No image available in the clipboard'), show=True)

    def break_cycles(self):
        self.undo_stack.clear()
        self.original_image_data = self.current_image = self.current_scaled_pixmap = None

    def emit_undo_redo_state(self):
        self.undo_redo_state_changed.emit(self.undo_action.isEnabled(), self.redo_action.isEnabled())

    @imageop
    def trim_image(self):
        if self.selection_state.rect is None:
            error_dialog(self, _('No selection'), _(
                'No active selection, first select a region in the image, by dragging with your mouse'), show=True)
            return False
        self.undo_stack.push(Trim(self))
        return True

    @imageop
    def autotrim_image(self):
        self.undo_stack.push(AutoTrim(self))
        return True

    @imageop
    def rotate_image(self):
        self.undo_stack.push(Rotate(self))
        return True

    @imageop
    def resize_image(self, width, height):
        self.undo_stack.push(Scale(width, height, self))
        return True

    @imageop
    def sharpen_image(self, sigma=3.0):
        self.undo_stack.push(Sharpen(sigma, self))
        return True

    @imageop
    def blur_image(self, sigma=3.0):
        self.undo_stack.push(Blur(sigma, self))
        return True

    @imageop
    def despeckle_image(self):
        self.undo_stack.push(Despeckle(self))
        return True

    # The selection rectangle {{{
    @property
    def dc_size(self):
        sr = self.selection_state.rect
        dx = min(75, sr.width() / 4)
        dy = min(75, sr.height() / 4)
        return dx, dy

    def get_drag_corner(self, pos):
        dx, dy = self.dc_size
        sr = self.selection_state.rect
        x, y = pos.x(), pos.y()
        hedge = 'left' if x < sr.x() + dx else 'right' if x > sr.right() - dx else None
        vedge = 'top' if y < sr.y() + dy else 'bottom' if y > sr.bottom() - dy else None
        return (hedge, vedge) if hedge or vedge else None

    def get_drag_rect(self):
        sr = self.selection_state.rect
        dc = self.selection_state.drag_corner
        if None in (sr, dc):
            return
        dx, dy = self.dc_size
        if None in dc:
            # An edge
            if dc[0] is None:
                top = sr.top() if dc[1] == 'top' else sr.bottom() - dy
                ans = QRectF(sr.left() + dx, top, sr.width() - 2 * dx, dy)
            else:
                left = sr.left() if dc[0] == 'left' else sr.right() - dx
                ans = QRectF(left, sr.top() + dy, dx, sr.height() - 2 * dy)
        else:
            # A corner
            left = sr.left() if dc[0] == 'left' else sr.right() - dx
            top = sr.top() if dc[1] == 'top' else sr.bottom() - dy
            ans = QRectF(left, top, dx, dy)
        return ans

    def get_cursor(self):
        dc = self.selection_state.drag_corner
        if dc is None:
            ans = Qt.OpenHandCursor if self.selection_state.last_drag_pos is None else Qt.ClosedHandCursor
        elif None in dc:
            ans = Qt.SizeVerCursor if dc[0] is None else Qt.SizeHorCursor
        else:
            ans = Qt.SizeBDiagCursor if dc in {('left', 'bottom'), ('right', 'top')} else Qt.SizeFDiagCursor
        return ans

    def move_edge(self, edge, dp):
        sr = self.selection_state.rect
        horiz = edge in {'left', 'right'}
        func = getattr(sr, 'set' + capitalize(edge))
        delta = getattr(dp, 'x' if horiz else 'y')()
        buf = 50
        if horiz:
            minv = self.target.left() if edge == 'left' else sr.left() + buf
            maxv = sr.right() - buf if edge == 'left' else self.target.right()
        else:
            minv = self.target.top() if edge == 'top' else sr.top() + buf
            maxv = sr.bottom() - buf if edge == 'top' else self.target.bottom()
        func(max(minv, min(maxv, delta + getattr(sr, edge)())))

    def move_selection_rect(self, x, y):
        sr = self.selection_state.rect
        half_width = sr.width() / 2.0
        half_height = sr.height() / 2.0
        c = sr.center()
        nx = c.x() + x
        ny = c.y() + y
        minx = self.target.left() + half_width
        maxx = self.target.right() - half_width
        miny, maxy = self.target.top() + half_height, self.target.bottom() - half_height
        nx = max(minx, min(maxx, nx))
        ny = max(miny, min(maxy, ny))
        sr.moveCenter(QPointF(nx, ny))

    def move_selection(self, dp):
        dm = self.selection_state.dragging
        if dm is None:
            self.move_selection_rect(dp.x(), dp.y())
        else:
            for edge in dm:
                if edge is not None:
                    self.move_edge(edge, dp)

    def mousePressEvent(self, ev):
        if ev.button() == Qt.LeftButton and self.target.contains(ev.pos()):
            pos = ev.pos()
            self.selection_state.last_press_point = pos
            if self.selection_state.current_mode is None:
                self.selection_state.current_mode = 'select'

            elif self.selection_state.current_mode == 'selected':
                if self.selection_state.rect is not None and self.selection_state.rect.contains(pos):
                    self.selection_state.drag_corner = self.selection_state.dragging = self.get_drag_corner(pos)
                    self.selection_state.last_drag_pos = pos
                    self.setCursor(self.get_cursor())
                else:
                    self.selection_state.current_mode = 'select'
                    self.selection_state.rect = None
                    self.selection_state_changed.emit(False)

    def mouseMoveEvent(self, ev):
        changed = False
        if self.selection_state.in_selection:
            changed = True
        self.selection_state.in_selection = False
        self.selection_state.drag_corner = None
        pos = ev.pos()
        cursor = Qt.ArrowCursor
        try:
            if not self.target.contains(pos):
                return
            if ev.buttons() & Qt.LeftButton:
                if self.selection_state.last_press_point is not None and self.selection_state.current_mode is not None:
                    if self.selection_state.current_mode == 'select':
                        self.selection_state.rect = QRectF(self.selection_state.last_press_point, pos).normalized()
                        changed = True
                    elif self.selection_state.last_drag_pos is not None:
                        self.selection_state.in_selection = True
                        self.selection_state.drag_corner = self.selection_state.dragging
                        dp = pos - self.selection_state.last_drag_pos
                        self.selection_state.last_drag_pos = pos
                        self.move_selection(dp)
                        cursor = self.get_cursor()
                        changed = True
            else:
                if self.selection_state.rect is None or not self.selection_state.rect.contains(pos):
                    return
                if self.selection_state.current_mode == 'selected':
                    if self.selection_state.rect is not None and self.selection_state.rect.contains(pos):
                        self.selection_state.drag_corner = self.get_drag_corner(pos)
                        self.selection_state.in_selection = True
                        cursor = self.get_cursor()
                        changed = True
        finally:
            if changed:
                self.update()
            self.setCursor(cursor)

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.LeftButton:
            self.selection_state.dragging = self.selection_state.last_drag_pos = None
            if self.selection_state.current_mode == 'select':
                r = self.selection_state.rect
                if r is None or max(r.width(), r.height()) < 3:
                    self.selection_state.reset()
                else:
                    self.selection_state.current_mode = 'selected'
                self.selection_state_changed.emit(self.has_selection)
            elif self.selection_state.current_mode == 'selected' and self.selection_state.rect is not None and self.selection_state.rect.contains(ev.pos()):
                self.setCursor(self.get_cursor())
            self.update()

    def keyPressEvent(self, ev):
        k = ev.key()
        if k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down) and self.selection_state.rect is not None and self.has_selection:
            ev.accept()
            delta = 10 if ev.modifiers() & Qt.ShiftModifier else 1
            x = y = 0
            if k in (Qt.Key_Left, Qt.Key_Right):
                x = delta * (-1 if k == Qt.Key_Left else 1)
            else:
                y = delta * (-1 if k == Qt.Key_Up else 1)
            self.move_selection_rect(x, y)
            self.update()
        else:
            return QWidget.keyPressEvent(self, ev)
    # }}}

    # Painting {{{
    @painter
    def draw_background(self, painter):
        painter.fillRect(self.rect(), self.BACKGROUND)

    @painter
    def draw_image_error(self, painter):
        font = painter.font()
        font.setPointSize(3 * font.pointSize())
        font.setBold(True)
        painter.setFont(font)
        painter.setPen(QColor(Qt.black))
        painter.drawText(self.rect(), Qt.AlignCenter, _('Not a valid image'))

    def load_pixmap(self):
        canvas_size = self.rect().width(), self.rect().height()
        if self.last_canvas_size != canvas_size:
            if self.last_canvas_size is not None and self.selection_state.rect is not None:
                self.selection_state.reset()
                # TODO: Migrate the selection rect
            self.last_canvas_size = canvas_size
            self.current_scaled_pixmap = None
        if self.current_scaled_pixmap is None:
            pwidth, pheight = self.last_canvas_size
            i = self.current_image
            width, height = i.width(), i.height()
            scaled, width, height = fit_image(width, height, pwidth, pheight)
            if scaled:
                i = self.current_image.scaled(width, height, transformMode=Qt.SmoothTransformation)
            self.current_scaled_pixmap = QPixmap.fromImage(i)

    @painter
    def draw_pixmap(self, painter):
        p = self.current_scaled_pixmap
        width, height = p.width(), p.height()
        pwidth, pheight = self.last_canvas_size
        x = int(abs(pwidth - width)/2.)
        y = int(abs(pheight - height)/2.)
        self.target = QRectF(x, y, width, height)
        painter.drawPixmap(self.target, p, QRectF(p.rect()))

    @painter
    def draw_selection_rect(self, painter):
        cr, sr = self.target, self.selection_state.rect
        painter.setPen(self.SELECT_PEN)
        painter.setRenderHint(QPainter.Antialiasing, False)
        if self.selection_state.current_mode == 'selected':
            # Shade out areas outside the selection rect
            for r in (
                QRectF(cr.topLeft(), QPointF(sr.left(), cr.bottom())),  # left
                QRectF(QPointF(sr.left(), cr.top()), sr.topRight()),  # top
                QRectF(QPointF(sr.right(), cr.top()), cr.bottomRight()),  # right
                QRectF(sr.bottomLeft(), QPointF(sr.right(), cr.bottom())),  # bottom
            ):
                painter.fillRect(r, self.SHADE_COLOR)

            dr = self.get_drag_rect()
            if self.selection_state.in_selection and dr is not None:
                # Draw the resize rectangle
                painter.save()
                painter.setCompositionMode(QPainter.RasterOp_SourceAndNotDestination)
                painter.setClipRect(sr.adjusted(1, 1, -1, -1))
                painter.drawRect(dr)
                painter.restore()

        # Draw the selection rectangle
        painter.setCompositionMode(QPainter.RasterOp_SourceAndNotDestination)
        painter.drawRect(sr)

    def paintEvent(self, event):
        QWidget.paintEvent(self, event)
        p = QPainter(self)
        p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        try:
            self.draw_background(p)
            if self.original_image_data is None:
                return
            if not self.is_valid:
                return self.draw_image_error(p)
            self.load_pixmap()
            self.draw_pixmap(p)
            if self.selection_state.rect is not None:
                self.draw_selection_rect(p)
        finally:
            p.end()
Exemple #18
0
    def draw(self, painter, xmap, ymap, rect):
        """Implements QwtPlotItem.draw(), to render the image on the given painter."""
        xp1, xp2, xdp, xs1, xs2, xds = xinfo = xmap.p1(), xmap.p2(
        ), xmap.pDist(), xmap.s1(), xmap.s2(), xmap.sDist()
        yp1, yp2, ydp, ys1, ys2, yds = yinfo = ymap.p1(), ymap.p2(
        ), ymap.pDist(), ymap.s1(), ymap.s2(), ymap.sDist()
        dprint(5, "draw:", rect, xinfo, yinfo)
        self._current_rect = QRectF(QPointF(xs2, ys1), QSizeF(xds, yds))
        self._current_rect_pix = QRect(
            QPoint(*self.lmToPix(xs1, ys1)),
            QPoint(*self.lmToPix(xs2, ys2))).intersected(
                self._bounding_rect_pix)
        dprint(5, "draw:", self._current_rect_pix)
        # put together tuple describing current mapping
        mapping = xinfo, yinfo
        # if mapping has changed w.r.t. cache (i.e. zoom has changed), discard all cached QImages
        if mapping != self._cache_mapping:
            dprint(2, "does not match cached mapping, cache is:",
                   self._cache_mapping)
            dprint(2, "and we have:", mapping)
            self.clearDisplayCache()
            self._cache_mapping = mapping
        t0 = time.time()
        # check cached QImage for current image key.
        qimg = self._cache_qimage.get(self._image_key)
        if qimg:
            dprint(5, "QImage found in cache, reusing")
        # else regenerate image
        else:
            # check for cached intensity-mapped data
            if self._cache_imap is not None:
                dprint(5, "intensity-mapped data found in cache, reusing")
            else:
                if self._cache_interp is not None:
                    dprint(5, "interpolated data found in cache, reusing")
                else:
                    image = self._image.transpose(
                    ) if self._data_fortran_order else self._image
                    spline_order = 2
                    xsamp = abs(xmap.sDist() / xmap.pDist()) / abs(self._dl)
                    ysamp = abs(ymap.sDist() / ymap.pDist()) / abs(self._dm)
                    if max(xsamp, ysamp) < .33 or min(xsamp, ysamp) > 2:
                        spline_order = 1
                    dprint(2,
                           "regenerating drawing cache, sampling factors are",
                           xsamp, ysamp, "spline order is", spline_order)
                    self._cache_imap = None
                    if self._prefilter is None and spline_order > 1:
                        self._prefilter = interpolation.spline_filter(
                            image, order=spline_order)
                        dprint(2, "spline prefiltering took",
                               time.time() - t0, "secs")
                        t0 = time.time()
                    # make arrays of plot coordinates
                    # xp[0],yp[0] corresponds to pixel 0,0, where 0,0 is the upper-left corner of the plot
                    # the maps are in a funny order (w.r.t. meaning of p1/p2/s1/s2), so the indices here are determined empirically
                    # We also adjust by half-pixel, to get the world coordinate of the pixel _center_
                    xp = xmap.s1() - (xmap.sDist() / xmap.pDist()) * (
                        0.5 + numpy.arange(int(xmap.pDist())))
                    yp = ymap.s2() - (ymap.sDist() / ymap.pDist()) * (
                        0.5 + numpy.arange(int(ymap.pDist())))
                    # now convert plot coordinates into fractional image pixel coordinates
                    xi = self._x0 + (xp - self._l0) / self._dl
                    yi = self._y0 + (yp - self._m0) / self._dm
                    # interpolate image data
                    ###        # old code for nearest-neighbour interpolation
                    ###        # superceded by interpolation below (we simply round pixel coordinates to go to NN when oversampling)
                    ###        xi = xi.round().astype(int)
                    ###        oob_x = (xi<0)|(xi>=self._nx)
                    ###        xi[oob_x] = 0
                    ###        yi = yi.round().astype(int)
                    ###        oob_y = (yi<0)|(yi>=self._ny)
                    ###        yi[oob_y] = 0
                    ###        idx = (xi[:,numpy.newaxis]*self._ny + yi[numpy.newaxis,:]).ravel()
                    ###        interp_image = self._image.ravel()[idx].reshape((len(xi),len(yi)))
                    ###        interp_image[oob_x,:] = 0
                    ###        interp_image[:,oob_y] = 0
                    ###        self._qimage_cache = self.colormap.colorize(interp_image,self._img_range)
                    ###        self._qimage_cache_attrs = (rect,xinfo,yinfo)

                    # if either axis is oversampled by a factor of 3 or more, switch to nearest-neighbour interpolation by rounding pixel values
                    if xsamp < .33:
                        xi = xi.round()
                    if ysamp < .33:
                        yi = yi.round()
                    # make [2,nx,ny] array of interpolation coordinates
                    xy = numpy.zeros((2, len(xi), len(yi)))
                    xy[0, :, :] = xi[:, numpy.newaxis]
                    xy[1, :, :] = yi[numpy.newaxis, :]
                    # interpolate. Use NAN for out of range pixels...
                    # for fortran order, tranpose axes for extra speed (flip XY around then)
                    if self._data_fortran_order:
                        xy = xy[-1::-1, ...]
                    if spline_order > 1:
                        interp_image = interpolation.map_coordinates(
                            self._prefilter,
                            xy,
                            order=spline_order,
                            cval=numpy.nan,
                            prefilter=False)
                    else:
                        interp_image = interpolation.map_coordinates(
                            image, xy, order=spline_order, cval=numpy.nan)
                    # ...and put a mask on them (Colormap.colorize() will make these transparent).
                    mask = ~numpy.isfinite(interp_image)
                    self._cache_interp = numpy.ma.masked_array(
                        interp_image, mask)
                    dprint(2, "interpolation took", time.time() - t0, "secs")
                    t0 = time.time()
                # ok, we have interpolated data in _cache_interp
                self._cache_imap = self.imap.remap(self._cache_interp)
                dprint(2, "intensity mapping took", time.time() - t0, "secs")
                t0 = time.time()
            # ok, we have intensity-mapped data in _cache_imap
            qimg = self.colormap.colorize(self._cache_imap)
            dprint(2, "colorizing took", time.time() - t0, "secs")
            t0 = time.time()
            # cache the qimage
            self._cache_qimage[self._image_key] = qimg.copy()
        # now draw the image
        t0 = time.time()
        painter.drawImage(xp1, yp2, qimg)
        dprint(2, "drawing took", time.time() - t0, "secs")
Exemple #19
0
class Canvas(QWidget):

    BACKGROUND = QColor(60, 60, 60)
    SHADE_COLOR = QColor(0, 0, 0, 180)
    SELECT_PEN = QPen(QColor(Qt.white))

    selection_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    image_changed = pyqtSignal(object)

    @property
    def has_selection(self):
        return self.selection_state.current_mode == 'selected'

    @property
    def is_modified(self):
        return self.current_image is not self.original_image

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.ClickFocus)
        self.selection_state = SelectionState()
        self.undo_stack = u = QUndoStack()
        u.setUndoLimit(10)
        u.canUndoChanged.connect(self.emit_undo_redo_state)
        u.canRedoChanged.connect(self.emit_undo_redo_state)

        self.original_image_data = None
        self.is_valid = False
        self.original_image_format = None
        self.current_image = None
        self.current_scaled_pixmap = None
        self.last_canvas_size = None
        self.target = QRectF(0, 0, 0, 0)

        self.undo_action = a = self.undo_stack.createUndoAction(self, _('Undo') + ' ')
        a.setIcon(QIcon(I('edit-undo.png')))
        self.redo_action = a = self.undo_stack.createRedoAction(self, _('Redo') + ' ')
        a.setIcon(QIcon(I('edit-redo.png')))

    def load_image(self, data):
        self.is_valid = False
        try:
            fmt = identify_data(data)[-1].encode('ascii')
        except Exception:
            fmt = b''
        self.original_image_format = fmt.decode('ascii').lower()
        self.selection_state.reset()
        self.original_image_data = data
        self.current_image = i = self.original_image = (
            QImage.fromData(data, format=fmt) if fmt else QImage.fromData(data))
        self.is_valid = not i.isNull()
        self.update()
        self.image_changed.emit(self.current_image)

    def set_image(self, qimage):
        self.selection_state.reset()
        self.current_scaled_pixmap = None
        self.current_image = qimage
        self.is_valid = not qimage.isNull()
        self.update()
        self.image_changed.emit(self.current_image)

    def get_image_data(self, quality=90):
        if not self.is_modified:
            return self.original_image_data
        return pixmap_to_data(self.current_image, format=self.original_image_format or 'JPEG', quality=90)

    def copy(self):
        if not self.is_valid:
            return
        clipboard = QApplication.clipboard()
        if not self.has_selection or self.selection_state.rect is None:
            clipboard.setImage(self.current_image)
        else:
            trim = Trim(self)
            clipboard.setImage(trim.after_image)
            trim.before_image = trim.after_image = None

    def paste(self):
        clipboard = QApplication.clipboard()
        md = clipboard.mimeData()
        if md.hasImage():
            img = QImage(md.imageData())
            if not img.isNull():
                self.undo_stack.push(Replace(img, _('Paste image'), self))
        else:
            error_dialog(self, _('No image'), _(
                'No image available in the clipboard'), show=True)

    def break_cycles(self):
        self.undo_stack.clear()
        self.original_image_data = self.current_image = self.current_scaled_pixmap = None

    def emit_undo_redo_state(self):
        self.undo_redo_state_changed.emit(self.undo_action.isEnabled(), self.redo_action.isEnabled())

    @imageop
    def trim_image(self):
        if self.selection_state.rect is None:
            error_dialog(self, _('No selection'), _(
                'No active selection, first select a region in the image, by dragging with your mouse'), show=True)
            return False
        self.undo_stack.push(Trim(self))
        return True

    @imageop
    def autotrim_image(self):
        self.undo_stack.push(AutoTrim(self))
        return True

    @imageop
    def rotate_image(self):
        self.undo_stack.push(Rotate(self))
        return True

    @imageop
    def resize_image(self, width, height):
        self.undo_stack.push(Scale(width, height, self))
        return True

    @imageop
    def sharpen_image(self, sigma=3.0):
        self.undo_stack.push(Sharpen(sigma, self))
        return True

    @imageop
    def blur_image(self, sigma=3.0):
        self.undo_stack.push(Blur(sigma, self))
        return True

    @imageop
    def despeckle_image(self):
        self.undo_stack.push(Despeckle(self))
        return True

    # The selection rectangle {{{
    @property
    def dc_size(self):
        sr = self.selection_state.rect
        dx = min(75, sr.width() / 4)
        dy = min(75, sr.height() / 4)
        return dx, dy

    def get_drag_corner(self, pos):
        dx, dy = self.dc_size
        sr = self.selection_state.rect
        x, y = pos.x(), pos.y()
        hedge = 'left' if x < sr.x() + dx else 'right' if x > sr.right() - dx else None
        vedge = 'top' if y < sr.y() + dy else 'bottom' if y > sr.bottom() - dy else None
        return (hedge, vedge) if hedge or vedge else None

    def get_drag_rect(self):
        sr = self.selection_state.rect
        dc = self.selection_state.drag_corner
        if None in (sr, dc):
            return
        dx, dy = self.dc_size
        if None in dc:
            # An edge
            if dc[0] is None:
                top = sr.top() if dc[1] == 'top' else sr.bottom() - dy
                ans = QRectF(sr.left() + dx, top, sr.width() - 2 * dx, dy)
            else:
                left = sr.left() if dc[0] == 'left' else sr.right() - dx
                ans = QRectF(left, sr.top() + dy, dx, sr.height() - 2 * dy)
        else:
            # A corner
            left = sr.left() if dc[0] == 'left' else sr.right() - dx
            top = sr.top() if dc[1] == 'top' else sr.bottom() - dy
            ans = QRectF(left, top, dx, dy)
        return ans

    def get_cursor(self):
        dc = self.selection_state.drag_corner
        if dc is None:
            ans = Qt.OpenHandCursor if self.selection_state.last_drag_pos is None else Qt.ClosedHandCursor
        elif None in dc:
            ans = Qt.SizeVerCursor if dc[0] is None else Qt.SizeHorCursor
        else:
            ans = Qt.SizeBDiagCursor if dc in {('left', 'bottom'), ('right', 'top')} else Qt.SizeFDiagCursor
        return ans

    def move_edge(self, edge, dp):
        sr = self.selection_state.rect
        horiz = edge in {'left', 'right'}
        func = getattr(sr, 'set' + capitalize(edge))
        delta = getattr(dp, 'x' if horiz else 'y')()
        buf = 50
        if horiz:
            minv = self.target.left() if edge == 'left' else sr.left() + buf
            maxv = sr.right() - buf if edge == 'left' else self.target.right()
        else:
            minv = self.target.top() if edge == 'top' else sr.top() + buf
            maxv = sr.bottom() - buf if edge == 'top' else self.target.bottom()
        func(max(minv, min(maxv, delta + getattr(sr, edge)())))

    def move_selection_rect(self, x, y):
        sr = self.selection_state.rect
        half_width = sr.width() / 2.0
        half_height = sr.height() / 2.0
        c = sr.center()
        nx = c.x() + x
        ny = c.y() + y
        minx = self.target.left() + half_width
        maxx = self.target.right() - half_width
        miny, maxy = self.target.top() + half_height, self.target.bottom() - half_height
        nx = max(minx, min(maxx, nx))
        ny = max(miny, min(maxy, ny))
        sr.moveCenter(QPointF(nx, ny))

    def move_selection(self, dp):
        dm = self.selection_state.dragging
        if dm is None:
            self.move_selection_rect(dp.x(), dp.y())
        else:
            for edge in dm:
                if edge is not None:
                    self.move_edge(edge, dp)

    def mousePressEvent(self, ev):
        if ev.button() == Qt.LeftButton and self.target.contains(ev.pos()):
            pos = ev.pos()
            self.selection_state.last_press_point = pos
            if self.selection_state.current_mode is None:
                self.selection_state.current_mode = 'select'

            elif self.selection_state.current_mode == 'selected':
                if self.selection_state.rect is not None and self.selection_state.rect.contains(pos):
                    self.selection_state.drag_corner = self.selection_state.dragging = self.get_drag_corner(pos)
                    self.selection_state.last_drag_pos = pos
                    self.setCursor(self.get_cursor())
                else:
                    self.selection_state.current_mode = 'select'
                    self.selection_state.rect = None
                    self.selection_state_changed.emit(False)

    def mouseMoveEvent(self, ev):
        changed = False
        if self.selection_state.in_selection:
            changed = True
        self.selection_state.in_selection = False
        self.selection_state.drag_corner = None
        pos = ev.pos()
        cursor = Qt.ArrowCursor
        try:
            if not self.target.contains(pos):
                return
            if ev.buttons() & Qt.LeftButton:
                if self.selection_state.last_press_point is not None and self.selection_state.current_mode is not None:
                    if self.selection_state.current_mode == 'select':
                        self.selection_state.rect = QRectF(self.selection_state.last_press_point, pos).normalized()
                        changed = True
                    elif self.selection_state.last_drag_pos is not None:
                        self.selection_state.in_selection = True
                        self.selection_state.drag_corner = self.selection_state.dragging
                        dp = pos - self.selection_state.last_drag_pos
                        self.selection_state.last_drag_pos = pos
                        self.move_selection(dp)
                        cursor = self.get_cursor()
                        changed = True
            else:
                if self.selection_state.rect is None or not self.selection_state.rect.contains(pos):
                    return
                if self.selection_state.current_mode == 'selected':
                    if self.selection_state.rect is not None and self.selection_state.rect.contains(pos):
                        self.selection_state.drag_corner = self.get_drag_corner(pos)
                        self.selection_state.in_selection = True
                        cursor = self.get_cursor()
                        changed = True
        finally:
            if changed:
                self.update()
            self.setCursor(cursor)

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.LeftButton:
            self.selection_state.dragging = self.selection_state.last_drag_pos = None
            if self.selection_state.current_mode == 'select':
                r = self.selection_state.rect
                if r is None or max(r.width(), r.height()) < 3:
                    self.selection_state.reset()
                else:
                    self.selection_state.current_mode = 'selected'
                self.selection_state_changed.emit(self.has_selection)
            elif self.selection_state.current_mode == 'selected' and self.selection_state.rect is not None and self.selection_state.rect.contains(ev.pos()):
                self.setCursor(self.get_cursor())
            self.update()

    def keyPressEvent(self, ev):
        k = ev.key()
        if k in (Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down) and self.selection_state.rect is not None and self.has_selection:
            ev.accept()
            delta = 10 if ev.modifiers() & Qt.ShiftModifier else 1
            x = y = 0
            if k in (Qt.Key_Left, Qt.Key_Right):
                x = delta * (-1 if k == Qt.Key_Left else 1)
            else:
                y = delta * (-1 if k == Qt.Key_Up else 1)
            self.move_selection_rect(x, y)
            self.update()
        else:
            return QWidget.keyPressEvent(self, ev)
    # }}}

    # Painting {{{
    @painter
    def draw_background(self, painter):
        painter.fillRect(self.rect(), self.BACKGROUND)

    @painter
    def draw_image_error(self, painter):
        font = painter.font()
        font.setPointSize(3 * font.pointSize())
        font.setBold(True)
        painter.setFont(font)
        painter.setPen(QColor(Qt.black))
        painter.drawText(self.rect(), Qt.AlignCenter, _('Not a valid image'))

    def load_pixmap(self):
        canvas_size = self.rect().width(), self.rect().height()
        if self.last_canvas_size != canvas_size:
            if self.last_canvas_size is not None and self.selection_state.rect is not None:
                self.selection_state.reset()
                # TODO: Migrate the selection rect
            self.last_canvas_size = canvas_size
            self.current_scaled_pixmap = None
        if self.current_scaled_pixmap is None:
            pwidth, pheight = self.last_canvas_size
            i = self.current_image
            width, height = i.width(), i.height()
            scaled, width, height = fit_image(width, height, pwidth, pheight)
            if scaled:
                i = self.current_image.scaled(width, height, transformMode=Qt.SmoothTransformation)
            self.current_scaled_pixmap = QPixmap.fromImage(i)

    @painter
    def draw_pixmap(self, painter):
        p = self.current_scaled_pixmap
        width, height = p.width(), p.height()
        pwidth, pheight = self.last_canvas_size
        x = int(abs(pwidth - width)/2.)
        y = int(abs(pheight - height)/2.)
        self.target = QRectF(x, y, width, height)
        painter.drawPixmap(self.target, p, QRectF(p.rect()))

    @painter
    def draw_selection_rect(self, painter):
        cr, sr = self.target, self.selection_state.rect
        painter.setPen(self.SELECT_PEN)
        painter.setRenderHint(QPainter.Antialiasing, False)
        if self.selection_state.current_mode == 'selected':
            # Shade out areas outside the selection rect
            for r in (
                QRectF(cr.topLeft(), QPointF(sr.left(), cr.bottom())),  # left
                QRectF(QPointF(sr.left(), cr.top()), sr.topRight()),  # top
                QRectF(QPointF(sr.right(), cr.top()), cr.bottomRight()),  # right
                QRectF(sr.bottomLeft(), QPointF(sr.right(), cr.bottom())),  # bottom
            ):
                painter.fillRect(r, self.SHADE_COLOR)

            dr = self.get_drag_rect()
            if self.selection_state.in_selection and dr is not None:
                # Draw the resize rectangle
                painter.save()
                painter.setCompositionMode(QPainter.RasterOp_SourceAndNotDestination)
                painter.setClipRect(sr.adjusted(1, 1, -1, -1))
                painter.drawRect(dr)
                painter.restore()

        # Draw the selection rectangle
        painter.setCompositionMode(QPainter.RasterOp_SourceAndNotDestination)
        painter.drawRect(sr)

    def paintEvent(self, event):
        QWidget.paintEvent(self, event)
        p = QPainter(self)
        p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
        try:
            self.draw_background(p)
            if self.original_image_data is None:
                return
            if not self.is_valid:
                return self.draw_image_error(p)
            self.load_pixmap()
            self.draw_pixmap(p)
            if self.selection_state.rect is not None:
                self.draw_selection_rect(p)
        finally:
            p.end()