Exemplo n.º 1
0
 def destinationsRect(self, destinations):
     """Return the rectangle containing all destinations."""
     layout = self.view.surface().pageLayout()
     rect = QRect()
     for dest in destinations:
         for pageNum, r in dest:
             rect = rect.united(layout[pageNum].linkRect(r.normalized()))
     # not larger than viewport
     rect.setSize(rect.size().boundedTo(self.view.viewport().size()))
     return rect
Exemplo n.º 2
0
 def destinationsRect(self, destinations):
     """Return the rectangle containing all destinations."""
     layout = self.view.surface().pageLayout()
     rect = QRect()
     for dest in destinations:
         for pageNum, r in dest:
             rect = rect.united(layout[pageNum].linkRect(r.normalized()))
     # not larger than viewport
     rect.setSize(rect.size().boundedTo(self.view.viewport().size()))
     return rect
Exemplo n.º 3
0
 def setGeometry(self, rect):
     QStackedLayout.setGeometry(self, rect)
     for i in range(self.count()):
         w = self.widget(i)
         hint = w.sizeHint()
         geom = QRect(rect)
         size = clipMinMax(rect.size(), w.minimumSize(), w.maximumSize())
         size = fixSizePolicy(size, hint, w.sizePolicy())
         geom.setSize(size)
         if geom != w.geometry():
             w.setGeometry(geom)
Exemplo n.º 4
0
 def paintEvent(self, ev):
     image = self.viewer.image()
     painter = QPainter(self)
     if self.size() == image.size():
         painter.drawImage(ev.rect(), image, ev.rect())
     else:
         s = image.size()
         s.scale(self.size(), Qt.KeepAspectRatio)
         r = QRect()
         r.setSize(s)
         r.moveCenter(self.rect().center())
         painter.drawPixmap(r, self.viewer.pixmap(s))
Exemplo n.º 5
0
 def paintEvent(self, ev):
     image = self.viewer.image()
     painter = QPainter(self)
     if self.size() == image.size():
         painter.drawImage(ev.rect(), image, ev.rect())
     else:
         s = image.size()
         s.scale(self.size(), Qt.KeepAspectRatio)
         r = QRect()
         r.setSize(s)
         r.moveCenter(self.rect().center())
         painter.drawPixmap(r, self.viewer.pixmap(s))
Exemplo n.º 6
0
class Page(object):
    """Represents a page from a Poppler.Document.
    
    It maintains its own size and can draw itself using the cache.
    It also can maintain a list of links and return links at certain
    points or rectangles.
    
    The visible attribute (setVisible and visible) defaults to True but
    can be set to False to hide the page from a Surface (this is done by
    the Layout).
    
    """
    def __init__(self, document, pageNumber):
        self._document = document
        self._pageNumber = pageNumber
        self._pageSize = document.page(pageNumber).pageSize()
        self._rotation = popplerqt4.Poppler.Page.Rotate0
        self._rect = QRect()
        self._scale = 1.0
        self._visible = True
        self._layout = lambda: None
        self._waiting = True # whether image still needs to be generated
        
    def document(self):
        """Returns the document."""
        return self._document
        
    def pageNumber(self):
        """Returns the page number."""
        return self._pageNumber
    
    def pageSize(self):
        """The page size in points (1/72 inch), taking rotation into account."""
        return self._pageSize
        
    def layout(self):
        """Returns the Layout if we are part of one."""
        return self._layout()
    
    def visible(self):
        """Returns True if this page is visible (will be displayed)."""
        return self._visible
        
    def setVisible(self, visible):
        """Sets whether  this page is visible (will be displayed)."""
        self._visible = visible
        
    def rect(self):
        """Returns our QRect(), with position and size."""
        return self._rect
    
    def size(self):
        """Returns our size."""
        return self._rect.size()
    
    def height(self):
        """Returns our height."""
        return self._rect.height()
        
    def width(self):
        """Returns our width."""
        return self._rect.width()
        
    def pos(self):
        """Returns our position."""
        return self._rect.topLeft()
    
    def setPos(self, point):
        """Sets our position (affects the Layout)."""
        self._rect.moveTopLeft(point)
    
    def setRotation(self, rotation):
        """Sets our Poppler.Page.Rotation."""
        old, self._rotation = self._rotation, rotation
        if (old ^ rotation) & 1:
            self._pageSize.transpose()
            self.computeSize()
    
    def rotation(self):
        """Returns our rotation."""
        return self._rotation
    
    def computeSize(self):
        """Recomputes our size."""
        xdpi, ydpi = self.layout().dpi() if self.layout() else (72.0, 72.0)
        x = round(self._pageSize.width() * xdpi / 72.0 * self._scale)
        y = round(self._pageSize.height() * ydpi / 72.0 * self._scale)
        self._rect.setSize(QSize(x, y))
        
    def setScale(self, scale):
        """Changes the display scale."""
        self._scale = scale
        self.computeSize()
        
    def scale(self):
        """Returns our display scale."""
        return self._scale
    
    def scaleForWidth(self, width):
        """Returns the scale we need to display ourselves at the given width."""
        if self.layout():
            return width * 72.0 / self.layout().dpi()[0] / self._pageSize.width()
        else:
            return float(width) / self._pageSize.width()
        
    def scaleForHeight(self, height):
        """Returns the scale we need to display ourselves at the given height."""
        if self.layout():
            return height * 72.0 / self.layout().dpi()[1] / self._pageSize.height()
        else:
            return float(height) / self._pageSize.height()
        
    def setWidth(self, width):
        """Change our scale to force our width to the given value."""
        self.setScale(self.scaleForWidth(width))

    def setHeight(self, height):
        """Change our scale to force our height to the given value."""
        self.setScale(self.scaleForHeight(height))
        
    def paint(self, painter, rect):
        update_rect = rect & self.rect()
        if not update_rect:
            return
        image_rect = QRect(update_rect.topLeft() - self.rect().topLeft(), update_rect.size())
        image = cache.image(self)
        self._waiting = not image
        if image:
            painter.drawImage(update_rect, image, image_rect)
        else:
            # schedule an image to be generated, if done our update() method is called
            cache.generate(self)
            # find suitable image to be scaled from other size
            image = cache.image(self, False)
            if image:
                hscale = float(image.width()) / self.width()
                vscale = float(image.height()) / self.height()
                image_rect = QRectF(image_rect.x() * hscale, image_rect.y() * vscale,
                                    image_rect.width() * hscale, image_rect.height() * vscale)
                painter.drawImage(QRectF(update_rect), image, image_rect)
            else:
                # draw blank paper, using the background color of the cache rendering (if set)
                # or from the document itself.
                color = (cache.options(self.document()).paperColor()
                         or cache.options().paperColor() or self.document().paperColor())
                painter.fillRect(update_rect, color)

    def update(self):
        """Called when an image is drawn."""
        # only redraw when we were waiting for a correctly sized image.
        if self._waiting and self.layout():
            self.layout().updatePage(self)
    
    def repaint(self):
        """Call this to force a repaint (e.g. when the rendering options are changed)."""
        self._waiting = True
        cache.generate(self)
    
    def image(self, rect, xdpi=72.0, ydpi=None, options=None):
        """Returns a QImage of the specified rectangle (relative to our layout).
        
        xdpi defaults to 72.0 and ydpi defaults to xdpi.
        options may be a render.RenderOptions instance that will set some document
        rendering options just before rendering the image.
        """
        rect = rect.normalized().intersected(self.rect())
        if not rect:
            return
        rect.translate(-self.pos())
        if ydpi is None:
            ydpi = xdpi
        hscale = (xdpi * self.pageSize().width()) / (72.0 * self.width())
        vscale = (ydpi * self.pageSize().height()) / (72.0 * self.height())
        x = rect.x() * hscale
        y = rect.y() * vscale
        w = rect.width() * hscale
        h = rect.height() * vscale
        with lock(self.document()):
            options and options.write(self.document())
            page = self.document().page(self._pageNumber)
            image = page.renderToImage(xdpi, ydpi, x, y, w, h, self._rotation)
        image.setDotsPerMeterX(int(xdpi * 39.37))
        image.setDotsPerMeterY(int(ydpi * 39.37))
        return image
    
    def linksAt(self, point):
        """Returns a list() of zero or more links touched by point (relative to surface).
        
        The list is sorted with the smallest rectangle first.
        
        """
        # Poppler.Link objects have their linkArea() ranging in width and height
        # from 0.0 to 1.0, so divide by resp. height and width of the Page.
        point = point - self.pos()
        x = float(point.x()) / self.width()
        y = float(point.y()) / self.height()
        # rotate
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                x, y = y, 1-x
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                x, y = 1-x, 1-y
            else: # 270
                x, y = 1-y, x
        return list(sorted(cache.links(self).at(x, y), key=lambda link: link.linkArea().width()))
        
    def linksIn(self, rect):
        """Returns an unordered set() of links enclosed in rectangle (relative to surface)."""
        rect = rect.normalized()
        rect.translate(-self.pos())
        left   = float(rect.left())   / self.width()
        top    = float(rect.top())    / self.height()
        right  = float(rect.right())  / self.width()
        bottom = float(rect.bottom()) / self.height()
        # rotate
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                left, top, right, bottom = top, 1-right, bottom, 1-left
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                left, top, right, bottom = 1-right, 1-bottom, 1-left, 1-top
            else: # 270
                left, top, right, bottom = 1-bottom, left, 1-top, right
        return cache.links(self).inside(left, top, right, bottom)

    def linkRect(self, linkarea):
        """Returns a QRect encompassing the linkArea (of a link) in coordinates of our rect()."""
        left, top, right, bottom = linkarea.normalized().getCoords()
        # rotate
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                left, top, right, bottom = 1-bottom, left, 1-top, right
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                left, top, right, bottom = 1-right, 1-bottom, 1-left, 1-top
            else: # 270
                left, top, right, bottom = top, 1-right, bottom, 1-left
        rect = QRect()
        rect.setCoords(left * self.width(), top * self.height(), right * self.width(), bottom * self.height())
        rect.translate(self.pos())
        return rect
        
    def text(self, rect):
        """Returns text inside rectangle (relative to surface)."""
        rect = rect.normalized()
        rect.translate(-self.pos())
        w, h = self.pageSize().width(), self.pageSize().height()
        left   = float(rect.left())   / self.width()  * w
        top    = float(rect.top())    / self.height() * h
        right  = float(rect.right())  / self.width()  * w
        bottom = float(rect.bottom()) / self.height() * h
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                left, top, right, bottom = top, w-right, bottom, w-left
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                left, top, right, bottom = w-right, h-bottom, w-left, h-top
            else: # 270
                left, top, right, bottom = h-bottom, left, h-top, right
        rect = QRectF()
        rect.setCoords(left, top, right, bottom)
        with lock(self.document()):
            page = self.document().page(self._pageNumber)
            return page.text(rect)
        
    def searchRect(self, rectF):
        """Returns a QRect encompassing the given rect (in points) to our position, size and rotation."""
        rect = rectF.normalized()
        left, top, right, bottom = rect.getCoords()
        w, h = self.pageSize().width(), self.pageSize().height()
        hscale = self.width()  / float(w)
        vscale = self.height() / float(h)
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                left, top, right, bottom = w-bottom, left, w-top, right
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                left, top, right, bottom = w-right, h-bottom, w-left, h-top
            else: # 270
                left, top, right, bottom = top, h-right, bottom, h-left
        rect = QRect()
        rect.setCoords(left * hscale, top * vscale, right * hscale, bottom * vscale)
        return rect
Exemplo n.º 7
0
class ImageAreaSelector (QtGui.QWidget):
    '''This widget provides means to visually crop a portion of
    an image by selecting it'''
     # pylint: disable=W0612
    NAME = 'ImageAreaSelector'
    DESCRIPTION = 'A widget used to select part of an image'
    AUTHOR = 'Gabriele "Whisky" Visconti'
    WEBSITE = ''
    # pylint: enable=W0612
    
    selection_changed = QtCore.pyqtSignal()
    
    def __init__(self, pixmap, parent=None):
        '''Constructor'''
        QtGui.QWidget.__init__(self, parent)
        self._pixmap = pixmap
        
        self._selection_rect = QRect() 
        self._image_origin = QPoint()
        self._resize_start = None
        self._drag_start = None
        self._handle_size = QSize(-10, -10)
        self._painter = QtGui.QPainter()
        self._hl_color1 = QtGui.QPalette().color(QtGui.QPalette.Highlight)
        self._hl_color2 = QtGui.QPalette().color(QtGui.QPalette.Highlight)
        self._hl_color2.setAlpha(150)
        self._zoom = 1.0
        
        self.adjust_minum_size()
        self.setBackgroundRole(QtGui.QPalette.Dark)
        self.setMouseTracking(True)
        self.setCursor(Qt.CrossCursor)
        
# -------------------- [BEGIN] QT_OVERRIDE

    def mousePressEvent (self, event):
        '''Overrides QWidget's mousePressEvent. Handles starting
        a new selection, starting a drag operation'''
        # pylint: disable=C0103
        mouse_pos = event.pos() / self._zoom
        sel_rect = self._selection_rect
        
        if not event.button() == Qt.LeftButton:
            return
        
        if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True):
            handle_rect = QRect(sel_rect.bottomRight(), self._handle_size)
            if handle_rect.contains(mouse_pos):
                self._resize_start = mouse_pos
            else:
                self._drag_start = mouse_pos
        else:
            self._resize_start = mouse_pos
            sel_rect.setTopLeft    (mouse_pos)
            self._selection_rect.setSize(QSize(0, 0))
        

    
    def mouseMoveEvent (self, event):
        '''Overrides QWidget's mouseMoveEvent. Handles resizing
        and dragging operations on selection'''
        # pylint: disable=C0103
        sel_rect = self._selection_rect
        if self._resize_start:
            resize_end = event.pos() / self._zoom
            sel_rect.setBottomRight(sel_rect.bottomRight() +
                                    (resize_end - self._resize_start))
            self._resize_start = resize_end
            self.make_selection_square()
            self.update()
        elif self._drag_start is not None:
            drag_end = event.pos() / self._zoom
            sel_rect.translate(drag_end - self._drag_start)
            self._drag_start = drag_end
            self.update()
            
        # cursor shape:
        mouse_pos = event.pos() / self._zoom
        if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True):
            handle_rect = QRect(sel_rect.bottomRight(), self._handle_size)
            if handle_rect.contains(mouse_pos):
                self.setCursor(Qt.SizeFDiagCursor)
            else:
                self.setCursor(Qt.OpenHandCursor)
                
        else:
            self.setCursor(Qt.CrossCursor)
        
            
    
    def mouseReleaseEvent (self, event):
        '''Overrides QWidget's mouseReleaseEvent. Handles ending a resizing or
        draggin operation on the selection'''
        # pylint: disable=C0103
        self._selection_rect = self._selection_rect.normalized()
        self._resize_start = None
        self._drag_start = None
        self.update()
        if not self._selection_rect.isNull():
            self.selection_changed.emit()
        
    
    def paintEvent(self, event):
        '''Overrides QWidtget's paintEvent.'''
        # pylint: disable=C0103
        QtGui.QWidget.paintEvent(self, event)
        self._painter.begin(self)
        pixmap_dest_rect = QRect(self._image_origin * self._zoom, 
                                 self._pixmap.size()*self._zoom)
        self._painter.drawPixmap(pixmap_dest_rect, self._pixmap)
        if not self._selection_rect.isNull():
            # preparing the darkened frame:
            sel_rect = self._selection_rect.normalized()
            frame = QtGui.QPixmap(event.rect().size())
            frame.fill(QtGui.QColor(0, 0, 0, 127))
            frame_painter = QtGui.QPainter(frame)
            # erase the selected area from the frame:
            frame_painter.setCompositionMode(
                                QtGui.QPainter.CompositionMode_DestinationIn)
            sel_rect_scaled = QRect(sel_rect.topLeft() * self._zoom,
                                    sel_rect.size() * self._zoom)
            frame_painter.fillRect(sel_rect_scaled, QtGui.QColor(0, 0, 0, 0))
            # draw selection border :
            frame_painter.setCompositionMode(
                                QtGui.QPainter.CompositionMode_SourceOver)
            frame_painter.setPen(self._hl_color1)
            frame_painter.drawRect(sel_rect_scaled)
            # draw the resize grip (if possible)
            if sel_rect_scaled.width() > 20 and sel_rect_scaled.height() > 20:
                handle_rect = QRect(sel_rect_scaled.bottomRight(), 
                                    self._handle_size)
                frame_painter.fillRect(handle_rect, self._hl_color2)
                frame_painter.drawRect(handle_rect)
            frame_painter.end()
            # painting the darkened frame:
            self._painter.drawPixmap(0, 0, frame)
            
        self._painter.end()
        
        
    def resizeEvent(self, event):
        '''Overrides QWidget's resizeEvent. Handles image centering.'''
        # pylint: disable=C0103
        self.adjust_image_origin()
        
# -------------------- [END] QT_OVERRIDE

    def adjust_image_origin(self):
        '''Recalculates the top left corner's image position, so the
        image is painted centered'''
        # pylint: disable=C0103
        new_size = self.size() / self._zoom
        pix_size = self._pixmap.size()
        
        dx = (new_size.width()  - pix_size.width() ) /2
        dy = (new_size.height() - pix_size.height()) /2
        
        new_image_origin = QPoint(dx, dy)
        self._selection_rect.translate(new_image_origin - self._image_origin)
        self._image_origin = new_image_origin
        log.info('image origin: %s' % new_image_origin)
        
    def select_unscaled(self):
        '''Selects, if possible, a 96 x 96 square centered around the original
        image. In this way the image won't be scaled but won't take up all the 
        96 x 96 area.'''
        # pylint: disable=C0103
        pix_size = self._pixmap.size()
        if pix_size.width() <= 96 and pix_size.height() <= 96:
            viewport_size = self.size()
            x =   (viewport_size.width () - 96) / 2
            y =   (viewport_size.height() - 96) / 2
            self._selection_rect.setTopLeft(QPoint(x, y))
            self._selection_rect.setSize(QSize(96, 96))
            self.update()
            self.selection_changed.emit()
            
            
    def select_all(self):
        '''Selects the whole image. Currently broken for images taller
        than wide.'''
        # TODO: make me work!
        self._selection_rect.setTopLeft(self._image_origin)
        self._selection_rect.setSize(self._pixmap.size())
        self.update()
        self.selection_changed.emit()
        
        
    def rotate_left(self):
        '''Rotates the image counterclockwise.'''
        self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(-90))
        self.adjust_minum_size()
        self.adjust_image_origin()
        self.update()
        self.selection_changed.emit()


    def rotate_right(self):
        '''Rotates the image clockwise'''
        self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(90))
        self.adjust_minum_size()
        self.adjust_image_origin()
        self.update()
        self.selection_changed.emit()
        
    
    def adjust_minum_size(self):
        '''Sets the new minimum size, calculated upon the image size
        and the _zoom factor.'''
        pixmap = self._pixmap
        if pixmap.width() < 96 or pixmap.height() < 96:
            min_size = QSize(96, 96)
        else:
            min_size = pixmap.size()
            
        self.setMinimumSize(min_size*self._zoom)


    def make_selection_square(self):
        '''Modify the selected area making it square'''
        wid = self._selection_rect.width ()
        self._selection_rect.setSize(QSize(wid, wid))
        
    
    def set_zoom(self, zoomlevel):
        '''Sets the specified zoomlevel'''
        self._zoom = zoomlevel
        self.adjust_minum_size()
        self.adjust_image_origin()
        self.update()
        self.selection_changed.emit()
        
        
    def fit_zoom(self):
        '''Chooses a zoomlevel that makes visible the entire image.
        Currently broken.'''
        widget_wid = self.size().width ()
        widget_hei = self.size().height()
        
        pixmap_wid = self._pixmap.width ()
        pixmap_hei = self._pixmap.height()
        
        self._zoom = (min(widget_wid, widget_hei) / 
                      min(pixmap_wid, pixmap_hei))
                      
        self.adjust_minum_size()
        self.adjust_image_origin()
        self.update()
        self.selection_changed.emit()
        
    def get_selected_pixmap(self):
        '''Returns the pixmap contained in the selection rect.
        Currently doesn't handle transparency correctly'''
        sel_rect_scaled = QRect(self._selection_rect.topLeft() * self._zoom,
                                self._selection_rect.size() * self._zoom)
        return QtGui.QPixmap.grabWidget(self, sel_rect_scaled)
Exemplo n.º 8
0
class Page(object):
    """Represents a page from a Poppler.Document.
    
    It maintains its own size and can draw itself using the cache.
    It also can maintain a list of links and return links at certain
    points or rectangles.
    
    The visible attribute (setVisible and visible) defaults to True but
    can be set to False to hide the page from a Surface (this is done by
    the Layout).
    
    """
    def __init__(self, document, pageNumber):
        self._document = document
        self._pageNumber = pageNumber
        self._pageSize = document.page(pageNumber).pageSize()
        self._rotation = popplerqt4.Poppler.Page.Rotate0
        self._rect = QRect()
        self._scale = 1.0
        self._visible = True
        self._layout = lambda: None
        self._waiting = True  # whether image still needs to be generated

    def document(self):
        """Returns the document."""
        return self._document

    def pageNumber(self):
        """Returns the page number."""
        return self._pageNumber

    def pageSize(self):
        """The page size in points (1/72 inch), taking rotation into account."""
        return self._pageSize

    def layout(self):
        """Returns the Layout if we are part of one."""
        return self._layout()

    def visible(self):
        """Returns True if this page is visible (will be displayed)."""
        return self._visible

    def setVisible(self, visible):
        """Sets whether  this page is visible (will be displayed)."""
        self._visible = visible

    def rect(self):
        """Returns our QRect(), with position and size."""
        return self._rect

    def size(self):
        """Returns our size."""
        return self._rect.size()

    def height(self):
        """Returns our height."""
        return self._rect.height()

    def width(self):
        """Returns our width."""
        return self._rect.width()

    def pos(self):
        """Returns our position."""
        return self._rect.topLeft()

    def setPos(self, point):
        """Sets our position (affects the Layout)."""
        self._rect.moveTopLeft(point)

    def setRotation(self, rotation):
        """Sets our Poppler.Page.Rotation."""
        old, self._rotation = self._rotation, rotation
        if (old ^ rotation) & 1:
            self._pageSize.transpose()
            self.computeSize()

    def rotation(self):
        """Returns our rotation."""
        return self._rotation

    def computeSize(self):
        """Recomputes our size."""
        xdpi, ydpi = self.layout().dpi() if self.layout() else (72.0, 72.0)
        x = round(self._pageSize.width() * xdpi / 72.0 * self._scale)
        y = round(self._pageSize.height() * ydpi / 72.0 * self._scale)
        self._rect.setSize(QSize(x, y))

    def setScale(self, scale):
        """Changes the display scale."""
        self._scale = scale
        self.computeSize()

    def scale(self):
        """Returns our display scale."""
        return self._scale

    def scaleForWidth(self, width):
        """Returns the scale we need to display ourselves at the given width."""
        if self.layout():
            return width * 72.0 / self.layout().dpi(
            )[0] / self._pageSize.width()
        else:
            return float(width) / self._pageSize.width()

    def scaleForHeight(self, height):
        """Returns the scale we need to display ourselves at the given height."""
        if self.layout():
            return height * 72.0 / self.layout().dpi(
            )[1] / self._pageSize.height()
        else:
            return float(height) / self._pageSize.height()

    def setWidth(self, width):
        """Change our scale to force our width to the given value."""
        self.setScale(self.scaleForWidth(width))

    def setHeight(self, height):
        """Change our scale to force our height to the given value."""
        self.setScale(self.scaleForHeight(height))

    def image(self):
        """Render the page as an image or our size. Return a QImage."""
        d = self._document
        w, h, r = self.width(), self.height(), self.rotation()
        page = d.page(self._pageNumber)
        pageSize = page.pageSize()
        if r & 1:
            pageSize.transpose()
        xres = 72.0 * w / pageSize.width()
        yres = 72.0 * h / pageSize.height()
        threshold = cache.options().oversampleThreshold() or cache.options(
            d).oversampleThreshold()
        multiplier = 2 if xres < threshold else 1
        with lock(d):
            cache.options().write(d)
            cache.options(d).write(d)
            image = page.renderToImage(xres * multiplier, yres * multiplier, 0,
                                       0, w * multiplier, h * multiplier, r)
        if multiplier == 2:
            image = image.scaledToWidth(w, Qt.SmoothTransformation)
        return image

    def paint(self, painter, rect):
        update_rect = rect & self.rect()
        if not update_rect:
            return
        image_rect = QRect(update_rect.topLeft() - self.rect().topLeft(),
                           update_rect.size())
        image = cache.image(self)
        self._waiting = not image
        if image:
            painter.drawImage(update_rect, image, image_rect)
        else:
            # schedule an image to be generated, if done our update() method is called
            cache.generate(self)
            # find suitable image to be scaled from other size
            image = cache.image(self, False)
            if image:
                hscale = float(image.width()) / self.width()
                vscale = float(image.height()) / self.height()
                image_rect = QRectF(image_rect.x() * hscale,
                                    image_rect.y() * vscale,
                                    image_rect.width() * hscale,
                                    image_rect.height() * vscale)
                painter.drawImage(QRectF(update_rect), image, image_rect)
            else:
                # draw blank paper, using the background color of the cache rendering (if set)
                # or from the document itself.
                color = (cache.options(self.document()).paperColor()
                         or cache.options().paperColor()
                         or self.document().paperColor())
                painter.fillRect(update_rect, color)

    def update(self):
        """Called when an image is drawn."""
        # only redraw when we were waiting for a correctly sized image.
        if self._waiting and self.layout():
            self.layout().updatePage(self)

    def repaint(self):
        """Call this to force a repaint (e.g. when the rendering options are changed)."""
        self._waiting = True
        cache.generate(self)

    def image(self, rect, xdpi=72.0, ydpi=None, options=None):
        """Returns a QImage of the specified rectangle (relative to our layout).
        
        xdpi defaults to 72.0 and ydpi defaults to xdpi.
        options may be a render.RenderOptions instance that will set some document
        rendering options just before rendering the image.
        """
        rect = rect.normalized().intersected(self.rect())
        if not rect:
            return
        rect.translate(-self.pos())
        if ydpi is None:
            ydpi = xdpi
        hscale = (xdpi * self.pageSize().width()) / (72.0 * self.width())
        vscale = (ydpi * self.pageSize().height()) / (72.0 * self.height())
        x = rect.x() * hscale
        y = rect.y() * vscale
        w = rect.width() * hscale
        h = rect.height() * vscale
        with lock(self.document()):
            options and options.write(self.document())
            page = self.document().page(self._pageNumber)
            image = page.renderToImage(xdpi, ydpi, x, y, w, h, self._rotation)
        image.setDotsPerMeterX(int(xdpi * 39.37))
        image.setDotsPerMeterY(int(ydpi * 39.37))
        return image

    def linksAt(self, point):
        """Returns a list() of zero or more links touched by point (relative to surface).
        
        The list is sorted with the smallest rectangle first.
        
        """
        # Poppler.Link objects have their linkArea() ranging in width and height
        # from 0.0 to 1.0, so divide by resp. height and width of the Page.
        point = point - self.pos()
        x = float(point.x()) / self.width()
        y = float(point.y()) / self.height()
        # rotate
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                x, y = y, 1 - x
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                x, y = 1 - x, 1 - y
            else:  # 270
                x, y = 1 - y, x
        return list(
            sorted(cache.links(self).at(x, y),
                   key=lambda link: link.linkArea().width()))

    def linksIn(self, rect):
        """Returns an unordered set() of links enclosed in rectangle (relative to surface)."""
        rect = rect.normalized()
        rect.translate(-self.pos())
        left = float(rect.left()) / self.width()
        top = float(rect.top()) / self.height()
        right = float(rect.right()) / self.width()
        bottom = float(rect.bottom()) / self.height()
        # rotate
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                left, top, right, bottom = top, 1 - right, bottom, 1 - left
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                left, top, right, bottom = 1 - right, 1 - bottom, 1 - left, 1 - top
            else:  # 270
                left, top, right, bottom = 1 - bottom, left, 1 - top, right
        return cache.links(self).inside(left, top, right, bottom)

    def linkRect(self, linkarea):
        """Returns a QRect encompassing the linkArea (of a link) in coordinates of our rect()."""
        left, top, right, bottom = linkarea.normalized().getCoords()
        # rotate
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                left, top, right, bottom = 1 - bottom, left, 1 - top, right
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                left, top, right, bottom = 1 - right, 1 - bottom, 1 - left, 1 - top
            else:  # 270
                left, top, right, bottom = top, 1 - right, bottom, 1 - left
        rect = QRect()
        rect.setCoords(left * self.width(), top * self.height(),
                       right * self.width(), bottom * self.height())
        rect.translate(self.pos())
        return rect

    def text(self, rect):
        """Returns text inside rectangle (relative to surface)."""
        rect = rect.normalized()
        rect.translate(-self.pos())
        w, h = self.pageSize().width(), self.pageSize().height()
        left = float(rect.left()) / self.width() * w
        top = float(rect.top()) / self.height() * h
        right = float(rect.right()) / self.width() * w
        bottom = float(rect.bottom()) / self.height() * h
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                left, top, right, bottom = top, w - right, bottom, w - left
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                left, top, right, bottom = w - right, h - bottom, w - left, h - top
            else:  # 270
                left, top, right, bottom = h - bottom, left, h - top, right
        rect = QRectF()
        rect.setCoords(left, top, right, bottom)
        with lock(self.document()):
            page = self.document().page(self._pageNumber)
            return page.text(rect)

    def searchRect(self, rectF):
        """Returns a QRect encompassing the given rect (in points) to our position, size and rotation."""
        rect = rectF.normalized()
        left, top, right, bottom = rect.getCoords()
        w, h = self.pageSize().width(), self.pageSize().height()
        hscale = self.width() / float(w)
        vscale = self.height() / float(h)
        if self._rotation:
            if self._rotation == popplerqt4.Poppler.Page.Rotate90:
                left, top, right, bottom = w - bottom, left, w - top, right
            elif self._rotation == popplerqt4.Poppler.Page.Rotate180:
                left, top, right, bottom = w - right, h - bottom, w - left, h - top
            else:  # 270
                left, top, right, bottom = top, h - right, bottom, h - left
        rect = QRect()
        rect.setCoords(left * hscale, top * vscale, right * hscale,
                       bottom * vscale)
        return rect
Exemplo n.º 9
0
class ImageAreaSelector(QtGui.QWidget):
    '''This widget provides means to visually crop a portion of
    an image by selecting it'''
    # pylint: disable=W0612
    NAME = 'ImageAreaSelector'
    DESCRIPTION = 'A widget used to select part of an image'
    AUTHOR = 'Gabriele "Whisky" Visconti'
    WEBSITE = ''
    # pylint: enable=W0612

    selection_changed = QtCore.pyqtSignal()

    def __init__(self, pixmap, parent=None):
        '''Constructor'''
        QtGui.QWidget.__init__(self, parent)
        self._pixmap = pixmap

        self._selection_rect = QRect()
        self._image_origin = QPoint()
        self._resize_start = None
        self._drag_start = None
        self._handle_size = QSize(-10, -10)
        self._painter = QtGui.QPainter()
        self._hl_color1 = QtGui.QPalette().color(QtGui.QPalette.Highlight)
        self._hl_color2 = QtGui.QPalette().color(QtGui.QPalette.Highlight)
        self._hl_color2.setAlpha(150)
        self._zoom = 1.0

        self.adjust_minum_size()
        self.setBackgroundRole(QtGui.QPalette.Dark)
        self.setMouseTracking(True)
        self.setCursor(Qt.CrossCursor)

# -------------------- [BEGIN] QT_OVERRIDE

    def mousePressEvent(self, event):
        '''Overrides QWidget's mousePressEvent. Handles starting
        a new selection, starting a drag operation'''
        # pylint: disable=C0103
        mouse_pos = event.pos() / self._zoom
        sel_rect = self._selection_rect

        if not event.button() == Qt.LeftButton:
            return

        if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True):
            handle_rect = QRect(sel_rect.bottomRight(), self._handle_size)
            if handle_rect.contains(mouse_pos):
                self._resize_start = mouse_pos
            else:
                self._drag_start = mouse_pos
        else:
            self._resize_start = mouse_pos
            sel_rect.setTopLeft(mouse_pos)
            self._selection_rect.setSize(QSize(0, 0))

    def mouseMoveEvent(self, event):
        '''Overrides QWidget's mouseMoveEvent. Handles resizing
        and dragging operations on selection'''
        # pylint: disable=C0103
        sel_rect = self._selection_rect
        if self._resize_start:
            resize_end = event.pos() / self._zoom
            sel_rect.setBottomRight(sel_rect.bottomRight() +
                                    (resize_end - self._resize_start))
            self._resize_start = resize_end
            self.make_selection_square()
            self.update()
        elif self._drag_start is not None:
            drag_end = event.pos() / self._zoom
            sel_rect.translate(drag_end - self._drag_start)
            self._drag_start = drag_end
            self.update()

        # cursor shape:
        mouse_pos = event.pos() / self._zoom
        if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True):
            handle_rect = QRect(sel_rect.bottomRight(), self._handle_size)
            if handle_rect.contains(mouse_pos):
                self.setCursor(Qt.SizeFDiagCursor)
            else:
                self.setCursor(Qt.OpenHandCursor)

        else:
            self.setCursor(Qt.CrossCursor)

    def mouseReleaseEvent(self, event):
        '''Overrides QWidget's mouseReleaseEvent. Handles ending a resizing or
        draggin operation on the selection'''
        # pylint: disable=C0103
        self._selection_rect = self._selection_rect.normalized()
        self._resize_start = None
        self._drag_start = None
        self.update()
        if not self._selection_rect.isNull():
            self.selection_changed.emit()

    def paintEvent(self, event):
        '''Overrides QWidtget's paintEvent.'''
        # pylint: disable=C0103
        QtGui.QWidget.paintEvent(self, event)
        self._painter.begin(self)
        pixmap_dest_rect = QRect(self._image_origin * self._zoom,
                                 self._pixmap.size() * self._zoom)
        self._painter.drawPixmap(pixmap_dest_rect, self._pixmap)
        if not self._selection_rect.isNull():
            # preparing the darkened frame:
            sel_rect = self._selection_rect.normalized()
            frame = QtGui.QPixmap(event.rect().size())
            frame.fill(QtGui.QColor(0, 0, 0, 127))
            frame_painter = QtGui.QPainter(frame)
            # erase the selected area from the frame:
            frame_painter.setCompositionMode(
                QtGui.QPainter.CompositionMode_DestinationIn)
            sel_rect_scaled = QRect(sel_rect.topLeft() * self._zoom,
                                    sel_rect.size() * self._zoom)
            frame_painter.fillRect(sel_rect_scaled, QtGui.QColor(0, 0, 0, 0))
            # draw selection border :
            frame_painter.setCompositionMode(
                QtGui.QPainter.CompositionMode_SourceOver)
            frame_painter.setPen(self._hl_color1)
            frame_painter.drawRect(sel_rect_scaled)
            # draw the resize grip (if possible)
            if sel_rect_scaled.width() > 20 and sel_rect_scaled.height() > 20:
                handle_rect = QRect(sel_rect_scaled.bottomRight(),
                                    self._handle_size)
                frame_painter.fillRect(handle_rect, self._hl_color2)
                frame_painter.drawRect(handle_rect)
            frame_painter.end()
            # painting the darkened frame:
            self._painter.drawPixmap(0, 0, frame)

        self._painter.end()

    def resizeEvent(self, event):
        '''Overrides QWidget's resizeEvent. Handles image centering.'''
        # pylint: disable=C0103
        self.adjust_image_origin()

# -------------------- [END] QT_OVERRIDE

    def adjust_image_origin(self):
        '''Recalculates the top left corner's image position, so the
        image is painted centered'''
        # pylint: disable=C0103
        new_size = self.size() / self._zoom
        pix_size = self._pixmap.size()

        dx = (new_size.width() - pix_size.width()) / 2
        dy = (new_size.height() - pix_size.height()) / 2

        new_image_origin = QPoint(dx, dy)
        self._selection_rect.translate(new_image_origin - self._image_origin)
        self._image_origin = new_image_origin
        log.info('image origin: %s' % new_image_origin)

    def select_unscaled(self):
        '''Selects, if possible, a 96 x 96 square centered around the original
        image. In this way the image won't be scaled but won't take up all the 
        96 x 96 area.'''
        # pylint: disable=C0103
        pix_size = self._pixmap.size()
        if pix_size.width() <= 96 and pix_size.height() <= 96:
            viewport_size = self.size()
            x = (viewport_size.width() - 96) / 2
            y = (viewport_size.height() - 96) / 2
            self._selection_rect.setTopLeft(QPoint(x, y))
            self._selection_rect.setSize(QSize(96, 96))
            self.update()
            self.selection_changed.emit()

    def select_all(self):
        '''Selects the whole image. Currently broken for images taller
        than wide.'''
        # TODO: make me work!
        self._selection_rect.setTopLeft(self._image_origin)
        self._selection_rect.setSize(self._pixmap.size())
        self.update()
        self.selection_changed.emit()

    def rotate_left(self):
        '''Rotates the image counterclockwise.'''
        self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(-90))
        self.adjust_minum_size()
        self.adjust_image_origin()
        self.update()
        self.selection_changed.emit()

    def rotate_right(self):
        '''Rotates the image clockwise'''
        self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(90))
        self.adjust_minum_size()
        self.adjust_image_origin()
        self.update()
        self.selection_changed.emit()

    def adjust_minum_size(self):
        '''Sets the new minimum size, calculated upon the image size
        and the _zoom factor.'''
        pixmap = self._pixmap
        if pixmap.width() < 96 or pixmap.height() < 96:
            min_size = QSize(96, 96)
        else:
            min_size = pixmap.size()

        self.setMinimumSize(min_size * self._zoom)

    def make_selection_square(self):
        '''Modify the selected area making it square'''
        wid = self._selection_rect.width()
        self._selection_rect.setSize(QSize(wid, wid))

    def set_zoom(self, zoomlevel):
        '''Sets the specified zoomlevel'''
        self._zoom = zoomlevel
        self.adjust_minum_size()
        self.adjust_image_origin()
        self.update()
        self.selection_changed.emit()

    def fit_zoom(self):
        '''Chooses a zoomlevel that makes visible the entire image.
        Currently broken.'''
        widget_wid = self.size().width()
        widget_hei = self.size().height()

        pixmap_wid = self._pixmap.width()
        pixmap_hei = self._pixmap.height()

        self._zoom = (min(widget_wid, widget_hei) /
                      min(pixmap_wid, pixmap_hei))

        self.adjust_minum_size()
        self.adjust_image_origin()
        self.update()
        self.selection_changed.emit()

    def get_selected_pixmap(self):
        '''Returns the pixmap contained in the selection rect.
        Currently doesn't handle transparency correctly'''
        sel_rect_scaled = QRect(self._selection_rect.topLeft() * self._zoom,
                                self._selection_rect.size() * self._zoom)
        return QtGui.QPixmap.grabWidget(self, sel_rect_scaled)