Esempio n. 1
0
class BasicSvgPage(page.AbstractPage):
    """A page that can display a SVG document."""
    paperColor = QColor(Qt.white)
    
    def __init__(self, load_file=None):
        self._svg_r = QSvgRenderer()
        if load_file:
            self.load(load_file)
    
    def load(self, load_file):
        """Load filename or QByteArray."""
        success = self._svg_r.load(load_file)
        if success:
            self.pageWidth = self._svg_r.defaultSize().width()
            self.pageHeight = self._svg_r.defaultSize().height()
        return success
    
    def paint(self, painter, rect, callback=None):
        painter.fillRect(rect, self.paperColor)
        page = QRect(0, 0, self.width, self.height)
        painter.translate(page.center())
        painter.rotate(self.computedRotation * 90)
        if self.computedRotation & 1:
            page.setSize(page.size().transposed())
        painter.translate(-page.center())
        self._svg_r.render(painter, QRectF(page))
    def intrinsicSize(self, doc, posInDocument, format):
        renderer = QSvgRenderer(format.property(Window.SvgData))
        size = renderer.defaultSize()

        if size.height() > 25:
            size *= 25.0 / size.height()

        return QSizeF(size)
Esempio n. 3
0
    def intrinsicSize(self, doc, posInDocument, format):
        renderer = QSvgRenderer(format.property(Window.SvgData))
        size = renderer.defaultSize()

        if size.height() > 25:
            size *= 25.0 / size.height()

        return QSizeF(size)
Esempio n. 4
0
def svg2png( input_svg_file, newWidth = None, verify = True ):
    """
    Returns an :py:class:`Image <PIL.Image.Image>` object of the PNG_ file produced when the CloudConvert_ server uploaded an input `SVG(Z) <svg_>`_ file. The PNG_ file has the same aspect ratio as the input file. Uses :py:class:`QSvgRenderer <PyQt5.QtSvg.QSvgRenderer>` to convert an `SVG(Z) <svg_>`_ into a PNG_.

    :param str input_svg_file: the input SVG or SVGZ file. Filename must end in ``.svg`` or ``.svgz``.
    :param int newWidth: optional argument. If specified, the pixel width of the output image.
    :param bool verify: optional argument, whether to verify SSL connections. Default is ``True``.
    
    :returns: the :py:class:`Image <PIL.Image.Image>` object of the PNG_ file from the input SVG or SVGZ file.
    
    .. _PNG: https://en.wikipedia.org/wiki/Portable_Network_Graphics
    .. _svg: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics

    
    .. seealso::
    
        * :py:meth:`png2png <nprstuff.core.convert_image.png2png>`.
        * :py:meth:`pdf2png <nprstuff.core.convert_image.pdf2png>`.
    """
    
    try:
        from PyQt5.QtSvg import QSvgRenderer
    except:
        logging.error( "ERROR, MUST INSTALL PyQt5.QtSvg MODULE. IT DOES NOT EXIST ON PYPI RIGHT NOW. YOU CAN RUN THE COMMAND, 'sudo apt install python3-pyqt5.qtsvg', TO INSTALL MODULE." )
        sys.exit( 0 )
    
    assert(any(map(lambda suffix: os.path.basename( input_svg_file ).endswith( '.%s' % suffix ),
                   ( 'svg', 'svgz' ) ) ) )
    assert( os.path.isfile( input_svg_file ) )
    if os.path.basename( input_svg_file ).endswith( '.svgz' ):
        r = QSvgRenderer( QByteArray( gzip.open( input_svg_file, 'rb' ).read( ) ) )
        files = { 'file' : gzip.open( input_svg_file, 'rb' ).read( ) }
    else:
        r = QSvgRenderer( input_svg_file )
        files = { 'file' : open( input_svg_file, 'rb' ).read( ) }
    width = r.defaultSize().width()
    height = r.defaultSize().height()
    return _return_image_cc(
        width, height, input_svg_file, 'svg', files,
        newWidth = newWidth, verify = verify )
Esempio n. 5
0
def svg_to_png(svg_path, png_path):
    """
    inspired by https://stackoverflow.com/questions/8551690/how-to-render-a-scaled-svg-to-a-qimage
    """
    renderer = QSvgRenderer(svg_path)
    print(renderer)
    print(renderer.viewBoxF())
    print(renderer.defaultSize())
    image = QImage(width, height, QImage.Format_ARGB32)
    painter = QPainter(image)
    renderer.render(painter)
    image.save(png_path)
    painter.end()
Esempio n. 6
0
class BasicSvgPage(page.AbstractPage):
    """A page that can display a SVG document."""
    def __init__(self, load_file=None):
        self._svg_r = QSvgRenderer()
        if load_file:
            self.load(load_file)

    def load(self, load_file):
        """Load filename or QByteArray."""
        success = self._svg_r.load(load_file)
        if success:
            self.pageWidth = self._svg_r.defaultSize().width()
            self.pageHeight = self._svg_r.defaultSize().height()
        return success

    def paint(self, painter, rect, callback=None):
        painter.fillRect(rect, self.paperColor or QColor(Qt.white))
        page = QRect(0, 0, self.width, self.height)
        painter.translate(page.center())
        painter.rotate(self.computedRotation * 90)
        if self.computedRotation & 1:
            page.setSize(page.size().transposed())
        painter.translate(-page.center())
        self._svg_r.render(painter, QRectF(page))
Esempio n. 7
0
class GraphViewer(QWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self._y = 0
        self._width = 1
        self._height = 1

        self.dot = Digraph(format='svg', strict=True)
        self._declared_count = 1
        self._declared = dict()
        self._renderer = QSvgRenderer(self.dot.pipe(), self)

        self.scrollbar = QScrollBar(self.parent())
        self.scrollbar.setRange(0, 0)
        self.parent().wheelEvent = self.wheelEvent

    def wheelEvent(self, event):
        if event.x() > self.getScrollWidth():
            return
        if event.y() > self._height:
            return
        self.scrollbar.wheelEvent(event)

    def add(self, data):
        # is variable
        if data in self._declared.keys():
            return self._declared[data]
        if data.is_variable:
            name = data.name
            self._declared[data] = name
            self.dot.node(name)
            if data.toward is not None:
                toward = self.add(data.toward)
                self.dot.edge(toward, name)
            return name
        # is constant
        if data.is_constant:
            name = data.symbol
            self._declared[data] = name
            self.dot.node(name)
            return name
        # is operator
        if data.is_operator:
            name = '[%d] %s' % (self._declared_count, data.name)
            self._declared_count += 1
            self._declared[data] = name
            self.dot.node(name)
            args = [data.sub, data.obj, data.step]
            if data.args is not None:
                args += data.args
            args = [arg for arg in args if arg is not None]
            for arg in args:
                arg = self.add(arg)
                self.dot.edge(arg, name)
            return name

    def paintEvent(self, event):
        self._width = self.width()
        self._height = self.height()
        self.scrollbar.setGeometry(self.getScrollWidth(), 0, 20, self._height)
        self.resize(self._renderer.defaultSize())
        painter = QPainter(self)
        painter.restore()
        drawRect = QRectF(self.rect())

        if self.scrollbar.maximum() == 0:
            draw_y = 0
        else:
            draw_y = drawRect.height() - self._height
            draw_y *= self.scrollbar.value() / self.scrollbar.maximum()

        drawRect.setY(-draw_y)
        drawRect.setHeight(drawRect.y() + drawRect.height())
        self._renderer.render(painter, drawRect)

    def flush(self):
        self._renderer = QSvgRenderer(self.dot.pipe())
        max_h = self._renderer.defaultSize().height() / self._height
        if max_h <= 1:
            max_h = 0
        max_h = int(self.delta() * max_h)
        self.scrollbar.setMaximum(max_h)

    def clear(self):
        self._declared_count = 1
        self._declared = dict()
        self.dot.clear()

    def getScrollWidth(self):
        return self._width - 20

    def delta(self):
        return 3.14
Esempio n. 8
0
class VectorGraphics2D(Rect2D):  # Image2D # Rect2D

    def __init__(self, figure, *args, x=None, y=None, width=None, height=None, opacity=1., stroke=0.65, **kwargs):
        self.isSet = False

        # TODO can only force size of a graph but not of an svg --> modify code accordingly, if graph and modify need apply set_size_inches to the figure... --> TODO rapidly
        tmp_size = QRectF()
        if x is not None and str(x).isnumeric():
            tmp_size.setX(x)
        if y is not None and str(x).isnumeric():
            tmp_size.setY(y)
        if width is not None and str(width).isnumeric():
            tmp_size.setWidth(width)
        if height is not None and str(height).isnumeric():
            tmp_size.setHeight(height)
        super(VectorGraphics2D, self).__init__(tmp_size)

        # self.annotation = [] # should contain the objects for annotating imaging --> shapes and texts
        self.letter = None  # when objects are swapped need change the letter
        self.figure = None
        self.annotation = []  # should contain the objects for annotating imaging --> shapes and texts
        # self.img = None
        # self.qimage = None
        self.renderer = None
        self.filename = None
        # crops
        self.__crop_left = None
        self.__crop_right = None
        self.__crop_top = None
        self.__crop_bottom = None

        # first argument should be fig
        # if args:
        #     if len(args) == 1:
        #         self.filename = args[0]
        # else:
        #     self.filename = None

        # if x is None and y is None and width is not None and height is not None:
        #     super(Graph2D, self).__init__(0, 0, width, height)
        #     self.isSet = True
        # elif x is None and y is None and width is None and height is None:
        #     # print('in 0')
        #     self.img = Img(self.filename)
        #     self.qimage = self.img.getQimage()
        #     width = self.img.get_width()
        #     height = self.img.get_height()
        #     super(Graph2D, self).__init__(0, 0, width, height)
        #     self.isSet = True
        # elif x is not None and y is not None and width is not None and height is not None:
        #     self.img = None
        #     super(Graph2D, self).__init__(x, y, width, height)
        #     self.isSet = True
        # elif data is None:
        #     if self.filename is not None:
        #         self.img = Img(self.filename)
        #         self.qimage = self.img.getQimage()
        #         if x is None:
        #             x = 0
        #         if y is None:
        #             y = 0
        #         super(Graph2D, self).__init__(x, y, self.img.get_width(), self.img.get_height())
        #         self.isSet = True
        # elif data is not None:
        #     self.img = Img(data,
        #                    dimensions=dimensions)  # need width and height so cannot really be only a numpy stuff --> cause no width or height by default --> or need tags such as image type for dimensions
        #     self.qimage = self.img.getQimage()
        #     # need Image dimensions id data is not of type IMG --> could check that
        #     if x is None:
        #         x = 0
        #     if y is None:
        #         y = 0
        #     super(Graph2D, self).__init__(x, y, self.img.get_width(), self.img.get_height())
        # TODO should I also allow aspect ratio or keep aspect ratio or fixed width or height or auto --> think about it and simply try

        # TODO should I allow to create a fig from some code using eval (i.e. a script that can be serialized, given the dangers of the eval function I dunno)
        # if self.figure is not None and isinstance(self.figure, plt.figure):
        #     self.isSet = True
        # else:
        #     self.isSet = False
        self.setFigure(figure)
        # super(Graph2D, self).__init__(0, 0, self.img.get_width(), self.img.get_height())

        # store also the image for that

        self.stroke = stroke  # DO I REALLY NEED STROKE
        self.opacity = opacity

    def getFigure(self):
        return self.figure

    def setFigure(self, figure):
        # TODO load the raw image there so that it can be drawn easily
        # self.figure = figure
        if figure is not None and isinstance(figure, plt.Figure):

            # self.img = self.toImg()
            # print(self.img.get_width())
            # self.qimage = self.img.getQimage()
            # make a renderer out of it and display it ...

            # print("size inches before rendering", figure.get_size_inches())
            self.figure = figure
            buffer = self._toBuffer(bufferType='svg')
            self.renderer = QSvgRenderer(buffer.read())
            buffer.close()
            # self.setSize(QSizeF(self.renderer.defaultSize()))
            # upon init we do set the width --> should this be done here or at other position ??? think about it
            if self.width() == 0:
                size = self.renderer.defaultSize()
                # print('default size', size, 'vs', self.renderer.viewBox())
                self.setWidth(size.width())
                self.setHeight(size.height())
            self.isSet = True
        elif figure is not None and isinstance(figure, str):  # path to an svg file
            # just try load it
            # do I ever need the buffer for this too cause if yes then I would need to get it --> think how ???

            # if buffer is needed --> this is how I should do it
            # in_file = open(figure)  # opening for [r]eading as [b]inary
            # data = in_file.read()  # if you only wanted to read 512 bytes, do .read(512)
            # in_file.close()
            # TODO add tries to see if that works and if opened properly
            # print(data)
            self.renderer = QSvgRenderer(figure)  # data --> need convert data to be able to read it

            # self.renderer.render(painter, self)# the stuff is a qrectf so that should work

            # self.renderer.setViewBox(viewbox)

            self.filename = figure
            if self.width() == 0:
                size = self.renderer.defaultSize()
                # print('default size', size, 'vs', self.renderer.viewBox())
                self.setWidth(size.width())
                self.setHeight(size.height())
                if size.width() <= 0:
                    logger.error('image "' + str(self.filename) + '" could not be loaded')
                    self.isSet = False
                    return
                # j'arrive pas à faire des crop avec les viewbox
                # viewbox = self.renderer.viewBoxF()
                #
                # # does not really work
                # neo = QRectF()
                # neo.setX(30)
                # neo.setY(30)
                # neo.setHeight(self.height()-30)
                # neo.setWidth(self.width()-30)
                # self.renderer.setViewBox(neo)

            self.figure = None
            self.isSet = True
        else:
            logger.error(
                'The provided figure is not a valid matplotlib figure nor a valid svg file! Nothing can be done with it... Sorry...')
            self.figure = None
            self.isSet = False

    def getAxes(self):
        if self.figure is not None:
            return self.figure.axes
        return None

    # @return the block incompressible width
    def getIncompressibleWidth(self):
        extra_space = 0  # can add some if boxes around to add text
        return extra_space

    # @return the block incompressible height
    def getIncompressibleHeight(self):
        extra_space = 0  # can add some if boxes around to add text
        return extra_space

    # if bg color is set then need add it --> see how to do that
    def setLettering(self, letter):
        if isinstance(letter, TAText2D):
            self.letter = letter
        elif isinstance(letter, str):
            if letter.strip() == '':
                self.letter = None
            else:
                self.letter = TAText2D(letter)

    def _toBuffer(self, bufferType='raster'):
        if self.figure is not None:
            buf = io.BytesIO()
            if bufferType == 'raster':
                self.figure.savefig(buf, format='png', bbox_inches='tight')
            else:
                self.figure.savefig(buf, format='svg', bbox_inches='tight')
            buf.seek(0)
            return buf
        return None

    # will only work for figures and is that even needed in fact --> think about it
    def _toBase64(self):
        buf = self._toBuffer()
        if buf is None:
            return None
        buf.seek(0)
        figdata_png = base64.b64encode(buf.getvalue())
        buf.close()
        return figdata_png

    # will only work for figures and is that even needed in fact --> think about it
    def toSVG(self):
        buf = self._toBuffer()
        if buf is None:
            return None
        buf.seek(0)
        text = buf.read()
        buf.close()
        return text

    # will only work for figures and is that even needed in fact --> think about it
    def _toImg(self):
        # print(self.toBase64()) # this is ok
        if self.figure is not None:
            buf = self._toBuffer()
            if buf is None:
                return None
            buf.seek(0)
            im = Image.open(buf)

            # im.show()
            pix = np.array(im)

            # print(pix.shape)
            img = Img(pix, dimensions='hwc')
            # print(img.shape, pix.shape)

            buf.close()
            # should I get image width and height there ???
            # im.show()
            return img
        return None

    # NEED CONVERSION FROM AND TO PX AND CM AND INCHES... --> TODO
    # TODO do a class to convert stuff and do it smart enough to handle any type of data and return the same type e.g. tuples as tuples, numbers as numbers and lists as lists
    # https://stackoverflow.com/questions/14708695/specify-figure-size-in-centimeter-in-matplotlib
    def cm2inch(self, *tupl):
        inch = 2.54
        if isinstance(tupl[0], tuple):
            return tuple(i / inch for i in tupl[0])
        else:
            # print(tupl) # why does it contain a qrectf ????
            return tuple(i / inch for i in tupl)

    def draw(self, painter, draw=True):
        if draw:
            painter.save()
        painter.setOpacity(self.opacity)
        if draw and self.renderer is not None:
            # if self.img is not None:
            #     qsource = QRectF(0,0,self.img.get_width(), self.img.get_height())
            #     painter.drawImage(self, self.qimage , qsource) # , flags=QtCore.Qt.AutoColor
            # else:
            #     painter.drawRect(self)

            # print('size of drawing', self)

            # print('view box',self.renderer.viewBoxF())

            # viewbox = self.renderer.viewBoxF()

            # --> vraiment presque ça
            # does not really work
            neo = QRectF(self)
            # neo.setX(self.x()-10)
            # neo.setY(self.y()-10)
            # neo.setHeight(self.height()+30)
            # neo.setWidth(self.width()+30)

            # nb this will create a shear if one dim is not cropped as the other --> really not great in fact maybe deactivate for now ???? or compute AR and adapt it
            # TODO just warn that it's buggy and should not be used for SVG files only, ok for other stuff though

            # can I preserve AR ???

            if self.__crop_left is not None:
                neo.setX(neo.x() - self.__crop_left)
                neo.setWidth(neo.width() + self.__crop_left)
            if self.__crop_top is not None:
                neo.setY(neo.y() - self.__crop_top)
                neo.setHeight(neo.height() + self.__crop_top)
            if self.__crop_bottom is not None:
                neo.setHeight(neo.height() + self.__crop_bottom)
            if self.__crop_right is not None:
                neo.setWidth(neo.width() + self.__crop_right)

            # maintenant ça a l'air bon...

            # --> ça c'est ok --> c'est le clip rect qui merde du coup

            # print('view box neo', neo)
            # self.renderer.setViewBox(neo)
            # le clipping marche mais faut le combiner avec autre chose
            # painter.setClipRect(self.x()+10, self.y()+10, self.width()-30,self.height()-30)#, Qt::ClipOperation operation = Qt::ReplaceClip

            # TODO KEEP UNFORTUNATELY  unfortunately cropping does not work when saved as svg but works when saved as raster... see https://bugreports.qt.io/browse/QTBUG-28636
            # maybe do masque d'ecretage in illustrator or inkscape https://linuxgraphic.org/forums/viewtopic.php?f=6&t=6437
            # TODO KEEP IT PROBABLY ALSO CREATES A ZOOM THAT WILL MESS WITH THE FONTS AND LINE SIZE...
            painter.setClipRect(self)  # , Qt::ClipOperation operation = Qt::ReplaceClip , operation=Qt.ReplaceClip

            self.renderer.render(painter, neo)  # the stuff is a qrectf so that should work

            painter.restore()
            # self.renderer.setViewBox(viewbox)

        # then need to draw the letter
        if self.letter is not None:
            self.letter.set_P1(self.get_P1())
            self.letter.draw(painter)

        if self.annotation is not None and self.annotation:
            for annot in self.annotation:
                annot.draw(draw=draw)

    # def fill(self, painter, draw=True):
    # if self.fill_color is None:
    #     return
    # if draw:
    #     painter.save()
    # painter.setOpacity(self.opacity)
    # if draw:
    #     if self.img is not None:
    #         qsource = QRectF(0, 0, self.img.get_width(), self.img.get_height())
    #         painter.drawImage(self, self.qimage , qsource)
    #     else:
    #         painter.drawRect(self)
    #     painter.restore()
    # return self.draw(painter=painter, draw=draw)

    # def drawAndFill(self, painter):
    # painter.save()
    # if self.img is not None:
    #     qsource = QRectF(0, 0, self.img.get_width(), self.img.get_height())
    #     painter.drawImage(self, self.qimage , qsource)
    # else:
    #     painter.drawRect(self)
    # painter.restore()
    # return self.draw(painter=painter)

    def __add__(self, other):
        from epyseg.figure.row import Row  # KEEP Really required to avoid circular imports
        return Row(self, other)

    # create a Fig with divide
    def __truediv__(self, other):
        from deprecated_demos.ezfig_tests.col import col  # KEEP Really required to avoid circular imports
        return col(self, other)

    # Force the montage width to equal 'width_in_px'

    # ne marche qu'avec les graphes en fait et faudrait demander si on peut changer aspect ratio... avant
    def setToWidth(self, width_in_px):
        pure_image_width = self.width()
        ratio = width_in_px / pure_image_width
        self.setWidth(width_in_px)
        self.setHeight(self.height() * ratio)

        if self.figure is not None:
            # need recompute the graph and update it with the new size --> convert in pixels
            cm_width = 0.02646 * width_in_px
            cm_height = 0.02646 * self.height()
            # print('cm', cm_width, cm_height)
            # print('cm', cm_width, cm_height)
            # print(self.figure)
            # print('abs')
            # print('inches', self.cm2inch(cm_width,cm_height)) # bug is here
            # print('ibs')
            self.figure.set_size_inches(self.cm2inch(cm_width, cm_height))  # need do a px to inch or to cm --> TODO
            # print(self.figure.get_size_inches())
            # print('size before', self) # --> size ok after not ok --> bug in conversion somewhere
            self.setFigure(self.figure)
            # print('changing size')
            # print('inches after', self.figure.get_size_inches())
            # print('size after', self)

    def setToHeight(self, height_in_px):
        pure_image_height = self.height()
        self.setHeight(height_in_px)
        ratio = height_in_px / pure_image_height
        self.setWidth(self.width() * ratio)

        if self.figure is not None:
            # the lines below cause a bug I guess it's because it's an image object and not a qrect object anymore and so width has pb --> calls a function
            cm_width = 0.02646 * self.width()
            cm_height = 0.02646 * height_in_px
            # print('cm', cm_width, cm_height)
            # print(self.figure)
            # print('oubs')
            # print(self.cm2inch(cm_width, cm_height))
            # print('ebs')
            self.figure.set_size_inches(self.cm2inch(cm_width, cm_height))  # need do a px to inch or to cm --> TODO
            # print(self.figure.get_size_inches())
            self.setFigure(self.figure)

    # No clue how to do that --> ignore...
    def crop(self, left=None, right=None, top=None, bottom=None, all=None):
        logger.warning(
            'Crop of svg files is very buggy and should not be used, especially for scientific publications as it may distort the image...')
        # print(self.boundingRect())
        if left is not None:
            self.__crop_left = left
            self.setWidth(self.width() - self.__crop_left)
            self.setX(self.x() + self.__crop_left)
        if right is not None:
            self.__crop_right = right
            self.setWidth(self.width() - self.__crop_right)
        if top is not None:
            self.__crop_top = top
            self.setHeight(self.height() - self.__crop_top)
            self.setY(self.y() + self.__crop_top)
        if bottom is not None:
            self.__crop_bottom = bottom
            self.setHeight(self.height() - self.__crop_bottom)
        if all is not None:
            self.__crop_left = all
            self.__crop_right = all
            self.__crop_top = all
            self.__crop_bottom = all
            self.setX(self.x() + self.__crop_left)
            self.setY(self.y() + self.__crop_top)
            self.setWidth(self.width() - self.__crop_left)
            self.setWidth(self.width() - self.__crop_right)
            self.setHeight(self.height() - self.__crop_top)
            self.setHeight(self.height() - self.__crop_bottom)
Esempio n. 9
0
class NodeItem(QGraphicsSvgItem):
    """
        Extends PyQt5's QGraphicsSvgItem to create the basic structure of shapes with given unit operation type
    """
    def __init__(self, unitOperationType=None, parent=None):
        QGraphicsSvgItem.__init__(self, parent)
        self.m_type = str(unitOperationType)
        self.m_renderer = QSvgRenderer(
            fileImporter(f'{unitOperationType}.svg'))
        self.setSharedRenderer(self.m_renderer)
        # set initial size of item
        self.width = self.m_renderer.defaultSize().width()
        self.height = self.m_renderer.defaultSize().height()
        # set graphical settings for this item
        self.setFlags(QGraphicsSvgItem.ItemIsMovable
                      | QGraphicsSvgItem.ItemIsSelectable
                      | QGraphicsSvgItem.ItemSendsGeometryChanges)
        self.setAcceptHoverEvents(True)
        self.setZValue(2)
        # items connected to this item
        self.lineGripItems = []
        self.sizeGripItems = []
        self.label = None
        self._rotation = 0
        self.flipState = [False, False]

    @property
    def flipH(self):
        return self.flipState[0]

    @property
    def flipV(self):
        return self.flipState[1]

    def updateTransformation(self):
        # update transformation on flipstate or rotation change
        transform = QTransform()
        h = -1 if self.flipH else 1
        w = -1 if self.flipV else 1
        transform.rotate(90 * self.rotation)
        transform.scale(h, w)
        self.setTransform(transform)
        self.setTransform(transform)
        for i in self.lineGripItems:
            i.setTransform(transform)
            i.updatePosition()

    @flipH.setter
    def flipH(self, state):
        self.flipState[0] = state
        self.updateTransformation()

    @flipV.setter
    def flipV(self, state):
        self.flipState[1] = state
        self.updateTransformation()

    @property
    def rotation(self):
        return self._rotation

    @rotation.setter
    def rotation(self, rotation):
        self._rotation = rotation % 4
        self.updateTransformation()

    def boundingRect(self):
        """Overrides QGraphicsSvgItem's boundingRect() virtual public function and
        returns a valid bounding
        """
        return QRectF(-self.width / 2, -self.height / 2, self.width,
                      self.height)

    def paint(self, painter, option, widget):
        """
            Paints the contents of an item in local coordinates.
            :param painter: QPainter instance
            :param option: QStyleOptionGraphicsItem instance
            :param widget: QWidget instance
        """
        # check if render is set
        if not self.m_renderer:
            QGraphicsSvgItem.paint(self, painter, option, widget)
        else:
            self.m_renderer.render(
                painter, self.boundingRect())  # render svg using painter

    def resize(self, index, movement):
        """Move grip item with changing rect of node item
        """
        self.prepareGeometryChange()
        if index in [0, 1]:
            self.width -= movement.x()
            self.height -= movement.y()
        else:
            self.width += movement.x()
            self.height += movement.y()
        transform = QTransform()
        transform.translate(movement.x() / 2, movement.y() / 2)
        self.setTransform(transform, True)
        self.updateSizeGripItem([index])

    def addGripItem(self):
        """adds grip items
        """
        if self.scene():
            # add grip for resizing
            for i, (direction) in enumerate((
                    Qt.Vertical,
                    Qt.Horizontal,
                    Qt.Vertical,
                    Qt.Horizontal,
            )):
                item = SizeGripItem(i, direction, parent=self)
                self.sizeGripItems.append(item)
            # add grip items for connecting lines
            for i in range(len(self.grips)):
                grip = self.grips[i]
                item = LineGripItem(i, grip, parent=self)
                self.lineGripItems.append(item)

    def updateLineGripItem(self):
        """
        updates line grip items
        """
        for item in self.lineGripItems:
            item.updatePosition()

    def updateSizeGripItem(self, index_no_updates=None):
        """
        updates size grip items
        """
        index_no_updates = index_no_updates or []
        for i, item in enumerate(self.sizeGripItems):
            if i not in index_no_updates:
                item.updatePosition()

    def itemChange(self, change, value):
        """Overloads and extends QGraphicsSvgItem to also update grip items
        """
        # check if item selected is changed
        if change == QGraphicsItem.ItemSelectedHasChanged:
            # show grips if selected
            if value is True:
                self.showGripItem()
            else:
                self.hideGripItem()
            return
        # check if transform changed
        if change == QGraphicsItem.ItemTransformHasChanged:
            self.updateLineGripItem()
            return
        # check if position is changed
        if change == QGraphicsItem.ItemPositionHasChanged:
            # update grips
            self.updateLineGripItem()
            self.updateSizeGripItem()
            return
        # check if item is add on scene
        if change == QGraphicsItem.ItemSceneHasChanged and self.scene():
            # add grips and update them
            self.addGripItem()
            self.updateLineGripItem()
            self.updateSizeGripItem()
            return
        return super(NodeItem, self).itemChange(change, value)

    def hoverEnterEvent(self, event):
        """defines shape highlighting on Mouse Over
        """
        self.showGripItem()
        super(NodeItem, self).hoverEnterEvent(event)

    def hoverLeaveEvent(self, event):
        """defines shape highlighting on Mouse Leave
        """
        self.hideGripItem()
        super(NodeItem, self).hoverLeaveEvent(event)

    def showGripItem(self):
        """shows grip items of svg item
        """
        for item in self.lineGripItems:
            item.show()
        for item in self.sizeGripItems:
            item.show()

    def hideGripItem(self):
        """hide grip items of svg item
        """
        for item in self.lineGripItems:
            item.hide()
        for item in self.sizeGripItems:
            item.hide()

    def contextMenuEvent(self, event):
        """Pop up menu
        :return:
        """
        # create a menu and add action
        contextMenu = QMenu()
        contextMenu.addAction("Add Label",
                              lambda: setattr(self, "label", ItemLabel(self)))
        contextMenu.addAction(
            "Rotate right(E)",
            lambda: setattr(self, "rotation", self.rotation + 1))
        contextMenu.addAction(
            "Rotate left(Q)",
            lambda: setattr(self, "rotation", self.rotation - 1))
        contextMenu.addAction("Flip Horizontally",
                              lambda: setattr(self, "flipH", not self.flipH))
        contextMenu.addAction("Flip Vertically",
                              lambda: setattr(self, "flipV", not self.flipV))
        action = contextMenu.exec_(event.screenPos())

    def __getstate__(self):
        return {
            "_classname_": self.__class__.__name__,
            "width": self.width,
            "height": self.height,
            "pos": (self.pos().x(), self.pos().y()),
            "lineGripItems":
            [(hex(id(i)), i.m_index) for i in self.lineGripItems],
            "label": self.label,
            "rotation": self.rotation,
            "flipstate": self.flipState
        }

    def __setstate__(self, dict):
        self.prepareGeometryChange()
        self.width = dict['width']
        self.height = dict['height']
        self.updateSizeGripItem()