Ejemplo n.º 1
0
class patchForm(baseForm):
    """
    Seamless cloning form.
    """
    # positioning window size
    pwSize = 200

    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)
        # positioning window
        self.widgetImg = BWidgetImg(parent=self)
        self.widgetImg.setWindowTitle("Pointing")
        # source
        self.sourceImage = None  # keep for eventual rotation
        self.sourcePixmap = None
        self.sourcePixmapThumb = None
        # flag indicating where source pixmap comes from
        self.layer.sourceFromFile = False
        # opencv flags
        cv2Flag_dict = {
            'Normal Clone': cv2.NORMAL_CLONE,
            'Mixed Clone': cv2.MIXED_CLONE,
            'Monochrome Transfer': cv2.MONOCHROME_TRANSFER
        }
        cv2Flags = list(cv2Flag_dict.keys())

        self.layer.autoclone = True

        self.listWidget1 = optionsWidget(options=cv2Flags,
                                         exclusive=True,
                                         changed=self.dataChanged)
        # init flags
        for i in range(self.listWidget1.count()):
            item = self.listWidget1.item(i)
            item.setData(Qt.UserRole, cv2Flag_dict[item.text()])
        self.options = self.listWidget1.options
        self.listWidget1.checkOption(self.listWidget1.intNames[0])

        optionList2, optionListNames2 = ['opencv', 'blue'
                                         ], ['OpenCV Cloning', 'bLUe Cloning']
        self.listWidget2 = optionsWidget(options=optionList2,
                                         optionNames=optionListNames2,
                                         exclusive=True,
                                         changed=self.dataChanged)
        self.listWidget2.checkOption(self.listWidget2.intNames[1])

        self.options = UDict((self.options, self.listWidget2.options))

        pushButton1 = QPushButton('Load Image From File')

        # Load Image button clicked slot
        def f():
            from bLUeTop.QtGui1 import window
            lastDir = str(window.settings.value('paths/dlgdir', '.'))
            filter = "Images ( *" + " *".join(IMAGE_FILE_EXTENSIONS) + ")"
            dlg = QFileDialog(window, "select", lastDir, filter)
            if dlg.exec_():
                filenames = dlg.selectedFiles()
                newDir = dlg.directory().absolutePath()
                window.settings.setValue('paths/dlgdir', newDir)
                self.sourceImage = QImage(filenames[0])
                self.updateSource()
                """
                # scale img while keeping its aspect ratio
                # into a QPixmap having the same size than self.layer
                sourcePixmap = QPixmap.fromImage(self.sourceImage).scaled(self.layer.size(), Qt.KeepAspectRatio)
                self.sourceSize = sourcePixmap.size()
                self.sourcePixmap = QPixmap(self.layer.size())
                self.sourcePixmap.fill(Qt.black)
                qp = QPainter(self.sourcePixmap)
                qp.drawPixmap(QPointF(), sourcePixmap)  # (QRect(0, 0, sourcePixmap.width(), sourcePixmap.height()), sourcePixmap)
                qp.end()
                self.sourcePixmapThumb = self.sourcePixmap.scaled(self.pwSize, self.pwSize, aspectMode=Qt.KeepAspectRatio)
                self.widgetImg.setPixmap(self.sourcePixmapThumb)
                self.widgetImg.setFixedSize(self.sourcePixmapThumb.size())
                self.layer.sourceFromFile = True
                """
            self.widgetImg.show()

        pushButton1.clicked.connect(f)
        pushButton2 = QPushButton('Reset')

        # reset button clicked slot
        def g():
            layer = self.layer
            # mask all pixels
            layer.resetMask(maskAll=True)
            layer.setMaskEnabled(color=False)
            # reset cloning layer
            layer.xAltOffset, layer.yAltOffset = 0.0, 0.0
            layer.AltZoom_coeff = 1.0
            layer.cloningState = ''
            layer.applyCloning(seamless=False, showTranslated=True)
            layer.parentImage.onImageChanged()

        pushButton2.clicked.connect(g)

        pushButton3 = QPushButton('Rotate Image')

        def h():
            self.sourceImage = self.sourceImage.transformed(
                QTransform().rotate(90))
            self.updateSource()

        pushButton3.clicked.connect(h)

        # layout
        layout = QVBoxLayout()
        layout.setContentsMargins(20, 0, 20, 25)  # left, top, right, bottom
        self.setLayout(layout)
        layout.addWidget(self.listWidget2)
        layout.addWidget(self.listWidget1)
        hl = QHBoxLayout()
        hl.addWidget(pushButton1)
        hl.addWidget(pushButton3)
        hl.addWidget(pushButton2)
        layout.addLayout(hl)

        self.setDefaults()

        self.setWhatsThis("""
                            <b>Cloning/healing brush</b><br>
                            Seamless replacement of a region of the image by another region of the same image 
                            or by another image (e.g. to erase an object):<br>
                               &nbsp; 1) <b> Make sure that the cloning layer is the topmost visible layer</b><br>
                               &nbsp; 2) With the <i>Pointer Tool</i> selected, <b>Ctrl+Alt+Click</b>
                                          on the layer or the pointing window to mark the source starting point;<br> 
                               &nbsp; 3) Select the <i>Unmask/FG Tool</i> and paint the destination region to copy and clone pixels. 
                                         Use <i>the Mask/BG Tool</i> to adjust the mask if needed. <br>
                            Use <b>Ctrl+Alt+Mouse Wheel</b> to zoom in or out the cloned region.<br>
                            Eventually use <b>Mask Erode</b> from the layer context menu to smooth the contour of the mask.<br>
                            """)  # end of setWhatsthis

    def setDefaults(self):
        self.enableOptions()
        self.listWidget1.checkOption(self.listWidget1.intNames[0])
        self.layer.cloningMethod = self.listWidget1.checkedItems[0].data(
            Qt.UserRole)
        self.layer.setMaskEnabled()  # TODO added 18/12/19 validate
        self.layer.resetMask(
            maskAll=True)  # , alpha=128)  # TODO added 18/12/19 validate
        # self.widgetImg.setPixmap(QPixmap.fromImage(self.layer.inputImg().scaled(200, 200, aspectMode=Qt.KeepAspectRatio)))
        # init positioning window
        img = self.layer.inputImg(
            drawTranslated=False
        )  # TODO added drawTranslated 16/12/19 validate
        if img.rPixmap is None:
            img.rPixmap = QPixmap.fromImage(img)
        self.sourcePixmap = img.rPixmap
        self.sourcePixmapThumb = self.sourcePixmap.scaled(
            200, 200, aspectMode=Qt.KeepAspectRatio)
        self.widgetImg.setPixmap(self.sourcePixmapThumb)
        self.widgetImg.setFixedSize(self.sourcePixmapThumb.size())
        # show positioning window
        self.widgetImg.hide()
        try:
            self.dataChanged.disconnect()
        except RuntimeError:
            pass
        self.dataChanged.connect(self.updateLayer)

    def updateSource(self):
        """
        sets the pointing window, using self.sourceImage
        """
        # scale img while keeping its aspect ratio
        # into a QPixmap having the same size than self.layer
        sourcePixmap = QPixmap.fromImage(self.sourceImage).scaled(
            self.layer.size(), Qt.KeepAspectRatio)
        self.sourceSize = sourcePixmap.size()
        self.sourcePixmap = QPixmap(self.layer.size())
        self.sourcePixmap.fill(Qt.black)
        qp = QPainter(self.sourcePixmap)
        qp.drawPixmap(
            QPointF(), sourcePixmap
        )  # (QRect(0, 0, sourcePixmap.width(), sourcePixmap.height()), sourcePixmap)
        qp.end()
        self.sourcePixmapThumb = self.sourcePixmap.scaled(
            self.pwSize, self.pwSize, aspectMode=Qt.KeepAspectRatio)
        self.widgetImg.setPixmap(self.sourcePixmapThumb)
        self.widgetImg.setFixedSize(self.sourcePixmapThumb.size())
        self.layer.sourceFromFile = True

    def enableOptions(self):
        if self.options['blue']:
            # make sure disabled options are unchecked
            self.listWidget1.checkOption('Normal Clone')
        for item in [self.listWidget1.item(i)
                     for i in (1, 2)]:  # mixed clone, monochrome transfer
            if self.options['opencv']:
                item.setFlags(item.flags() | Qt.ItemIsEnabled)
            else:
                item.setFlags(item.flags() & ~Qt.ItemIsEnabled)

    def updateLayer(self):
        """
        data changed slot
        """
        self.enableOptions()
        layer = self.layer
        layer.cloningMethod = self.listWidget1.checkedItems[0].data(
            Qt.UserRole)
        layer.applyToStack()
        layer.parentImage.onImageChanged()
Ejemplo n.º 2
0
class Picture(object):
    def __init__(self, path):
        super(Picture, self).__init__()

        self.path = path
        self.name = self.path.split("/")[-1]
        self.extension = os.path.splitext(self.path)[1]
        self.scale = 1.0
        self.image = QImage(self.path)
        self.thumbnail = self.image.scaled(QSize(110, 110),
                                           aspectMode=Qt.KeepAspectRatio,
                                           mode=Qt.SmoothTransformation)
        self.resolution = "Resolution: " + str(self.image.width()) + "x" + str(
            self.image.height()) + "px"

    def deletePicture(self):
        os.remove(self.path)

    def verticalFlip(self):
        self.image = self.image.mirrored(vertically=True, horizontally=False)
        self.thumbnail = self.image.scaled(QSize(110, 110),
                                           aspectMode=Qt.KeepAspectRatio,
                                           mode=Qt.SmoothTransformation)
        self.image.save(self.path)

    def horizontalFlip(self):
        self.image = self.image.mirrored(horizontally=True, vertically=False)
        self.thumbnail = self.image.scaled(QSize(110, 110),
                                           aspectMode=Qt.KeepAspectRatio,
                                           mode=Qt.SmoothTransformation)
        self.image.save(self.path)

    def zoomIn(self):
        self.scale = self.scale * 0.9

    def zoomOut(self):
        self.scale = self.scale * 1.1

    def rotateCW(self):
        transform = QTransform()
        transform.translate(self.image.width() / 2, self.image.height() / 2)
        transform.rotate(90)

        self.image = self.image.transformed(transform)
        self.image.save(self.path)
        self.thumbnail = self.image.scaled(QSize(110, 110),
                                           aspectMode=Qt.KeepAspectRatio,
                                           mode=Qt.SmoothTransformation)

    def rotateCCW(self):
        transform = QTransform()
        transform.translate(self.image.width() / 2, self.image.height() / 2)
        transform.rotate(-90)

        self.image = self.image.transformed(transform)
        self.image.save(self.path)

        self.thumbnail = self.image.scaled(QSize(110, 110),
                                           aspectMode=Qt.KeepAspectRatio,
                                           mode=Qt.SmoothTransformation)

    def saveImage(self):
        self.image.save(self.path)
Ejemplo n.º 3
0
class QLayer(vImage):
    """
    Base class for image layers
    """
    @classmethod
    def fromImage(cls, mImg, parentImage=None):
        """
        Returns a QLayer object initialized with mImg.
        @param mImg:
        @type mImg: QImage
        @param parentImage:
        @type parentImage: mImage
        @return:
        @rtype: Qlayer
        """
        layer = QLayer(QImg=mImg, parentImage=parentImage)
        layer.parentImage = parentImage
        return layer

    def __init__(self, *args, **kwargs):
        ############################################################
        # Signals
        # Making QLayer inherit from QObject leads to
        # a bugged behavior of hasattr and getattr.
        # So, we don't add signals as first level class attributes.
        # Instead, we use instances of ad hoc signal containers.
        ############################################################
        self.visibilityChanged = baseSignal_bool()
        self.colorPicked = baseSignal_Int2()
        self.selectionChanged = baseSignal_No()
        self.maskSettingsChanged = baseSignal_No()
        ###########################################################
        # when a geometric transformation is applied to the whole image
        # each layer must be replaced with a transformed layer, recorded in tLayer
        # and tLayer.parentLayer keeps a reference to the original layer.
        ###########################################################
        self.tLayer = self
        self.parentLayer = self
        self.modified = False
        self.name = 'noname'
        self.visible = True
        self.isClipping = False
        self.role = kwargs.pop('role', '')
        self.tool = None
        # back link to parent image
        parentImage = kwargs.pop('parentImage', None)
        self.parentImage = weakProxy(parentImage)
        super().__init__(*args, **kwargs)  # don't move backwards
        # mask init, must be done after after calling super().__init__
        if type(self) not in [QPresentationLayer]:
            self.mask = QImage(self.width(), self.height(),
                               QImage.Format_ARGB32)
            # default : unmask all
            self.mask.fill(self.defaultColor_UnMasked)
        # layer opacity, range 0.0...1.0
        self.opacity = 1.0
        self.compositionMode = QPainter.CompositionMode_SourceOver
        # The next two attributes are used by adjustment layers only.
        self.execute = lambda l=None, pool=None: l.updatePixmap(
        ) if l is not None else None
        self.options = {}
        # actionName is used by methods graphics***.writeToStream()
        self.actionName = 'actionNull'
        # view is the dock widget containing
        # the graphics form associated with the layer
        self.view = None
        # undo/redo mask history
        self.historyListMask = historyList(size=5)
        # consecutive layers can be grouped.
        # A group is a list of QLayer objects
        self.group = []
        # layer offsets
        self.xOffset, self.yOffset = 0, 0
        self.Zoom_coeff = 1.0
        # clone dup layer shift and zoom  relative to current layer
        self.xAltOffset, self.yAltOffset = 0, 0
        self.AltZoom_coeff = 1.0
        self.updatePixmap()

    def getGraphicsForm(self):
        """
        Return the graphics form associated with the layer
        @return:
        @rtype: QWidget
        """
        if self.view is not None:
            return self.view.widget()
        return None

    def isActiveLayer(self):
        if self.parentImage.getActiveLayer() is self:
            return True
        return False

    def getMmcSpline(self):
        """
        Returns the spline used for multimode contrast
        correction if it is initialized, and None otherwise.
        @return:
        @rtype: activeSpline
        """
        # get layer graphic form
        grf = self.getGraphicsForm()
        # manual curve form
        if grf.contrastForm is not None:
            return grf.contrastForm.scene().cubicItem
        return None

    def addTool(self, tool):
        """
        Adds tool to layer
        @param tool:
        @type tool: rotatingTool
        """
        self.tool = tool
        tool.modified = False
        tool.layer = self
        try:
            tool.layer.visibilityChanged.sig.disconnect(
            )  # TODO signals removed 30/09/18 validate
        except RuntimeError:
            pass
        tool.layer.visibilityChanged.sig.connect(
            tool.setVisible)  # TODO signals removed 30/09/18 validate
        tool.img = self.parentImage
        w, h = tool.img.width(), tool.img.height()
        for role, pos in zip(
            ['topLeft', 'topRight', 'bottomRight', 'bottomLeft'],
            [QPoint(0, 0),
             QPoint(w, 0),
             QPoint(w, h),
             QPoint(0, h)]):
            tool.btnDict[role].posRelImg = pos
            tool.btnDict[role].posRelImg_ori = pos
            tool.btnDict[role].posRelImg_frozen = pos
        tool.moveRotatingTool()

    def setVisible(self, value):
        """
        Sets self.visible to value and emit visibilityChanged.sig
        @param value:
        @type value: bool
        """
        self.visible = value
        self.visibilityChanged.sig.emit(
            value)  # TODO signals removed 30/09/18 validate

    def bTransformed(self, transformation, parentImage):
        """
        Apply transformation to a copy of layer. Returns the transformed copy.
        @param transformation:
        @type transformation: QTransform
        @param parentImage:
        @type parentImage: vImage
        @return: transformed layer
        @rtype: QLayer
        """
        # init a new layer from transformed image :
        # all static attributes (caches...) are reset to default, but thumb
        tLayer = QLayer.fromImage(self.transformed(transformation),
                                  parentImage=parentImage)
        # copy  dynamic attributes from old layer
        for a in self.__dict__.keys():
            if a not in tLayer.__dict__.keys():
                tLayer.__dict__[a] = self.__dict__[a]
        tLayer.name = self.name
        tLayer.actionName = self.actionName
        tLayer.view = self.view
        tLayer.visible = self.visible  # TODO added 25/09/18 validate
        # cut link from old layer to graphic form
        # self.view = None                        # TODO 04/12/17 validate
        tLayer.execute = self.execute
        tLayer.mask = self.mask.transformed(transformation)
        tLayer.maskIsEnabled, tLayer.maskIsSelected = self.maskIsEnabled, self.maskIsSelected
        return tLayer

    def initThumb(self):
        """
        Override vImage.initThumb, to set the parentImage attribute
        """
        super().initThumb()
        self.thumb.parentImage = self.parentImage

    def initHald(self):
        """
        Build a hald image (as a QImage) from identity 3D LUT.
        """
        if not self.cachesEnabled:
            return
        s = int(LUT3DIdentity.size**(3.0 / 2.0)) + 1
        buf0 = LUT3DIdentity.toHaldArray(s, s).haldBuffer
        # self.hald = QLayer(QImg=QImage(QSize(190,190), QImage.Format_ARGB32))
        self.hald = QImage(QSize(s, s), QImage.Format_ARGB32)
        buf1 = QImageBuffer(self.hald)
        buf1[:, :, :3] = buf0
        buf1[:, :, 3] = 255
        self.hald.parentImage = self.parentImage

    def getHald(self):
        if not self.cachesEnabled:
            s = int(LUT3DIdentity.size**(3.0 / 2.0)) + 1
            buf0 = LUT3DIdentity.toHaldArray(s, s).haldBuffer
            # self.hald = QLayer(QImg=QImage(QSize(190,190), QImage.Format_ARGB32))
            hald = QImage(QSize(s, s), QImage.Format_ARGB32)
            buf1 = QImageBuffer(hald)
            buf1[:, :, :3] = buf0
            buf1[:, :, 3] = 255
            hald.parentImage = self.parentImage
            return hald
        if self.hald is None:
            self.initHald()
        return self.hald

    def getCurrentImage(self):
        """
        Returns current (full, preview or hald) image, according to
        the value of the flags useThumb and useHald. The thumbnail and hald
        are computed if they are not initialized.
        Otherwise, they are not updated unless self.thumb is
        None or purgeThumb is True.
        Overrides vImage method
        @return: current image
        @rtype: QLayer
        """
        if self.parentImage.useHald:
            return self.getHald()
        if self.parentImage.useThumb:
            return self.getThumb()
        else:
            return self

    def inputImg(self, redo=True):
        """
        return maskedImageContainer/maskedThumbContainer.
        If redo is True(default), containers are updated.
        layer.applyToStack() always calls inputImg() with redo=True.
        So, to keep the containers up to date we only have to follow
        each layer modification with a call to layer.applyToStack().
        @param redo:
        @type redo: boolean
        @return:
        @rtype: bImage
        """
        lower = self.parentImage.layersStack[self.getLowerVisibleStackIndex()]
        container = lower.maskedThumbContainer if self.parentImage.useThumb else lower.maskedImageContainer
        if redo or container is None:
            container = lower.getCurrentMaskedImage()
            container.rPixmap = None  # invalidate and don't update
        else:
            if container.rPixmap is None:
                container.rPixmap = QPixmap.fromImage(
                    container)  # will probably be reused : update
        return container

    def full2CurrentXY(self, x, y):
        """
        Maps x,y coordinates of pixel in the full image to
        coordinates in current image.
        @param x:
        @type x: int or float
        @param y:
        @type y: int or float
        @return:
        @rtype: 2uple of int
        """
        if self.parentImage.useThumb:
            currentImg = self.getThumb()
            x = (x * currentImg.width()) / self.width()
            y = (y * currentImg.height()) / self.height()
        return int(x), int(y)

    def getCurrentMaskedImage(self):
        """
        Blend the layer stack up to self (included),
        taking into account the masks. The method uses the
        non color managed rPixmap to build the masked image.
        For convenience, mainly to be able to use its color space buffers,
        the built image is of type bImage. It is drawn on a container image,
        instantiated only once.
        @return: masked image
        @rtype: bImage
        """
        # init containers if needed. They are instantiated only
        # once and updated by drawing.
        if self.parentImage.useHald:
            return self.getHald()
        if self.maskedThumbContainer is None:
            self.maskedThumbContainer = bImage.fromImage(
                self.getThumb(), parentImage=self.parentImage)
        if self.maskedImageContainer is None:
            self.maskedImageContainer = bImage.fromImage(
                self, parentImage=self.parentImage)
        if self.parentImage.useThumb:
            img = self.maskedThumbContainer
        else:
            img = self.maskedImageContainer
        # draw lower stack
        qp = QPainter(img)
        top = self.parentImage.getStackIndex(self)
        bottom = 0
        for i, layer in enumerate(self.parentImage.layersStack[bottom:top +
                                                               1]):
            if layer.visible:
                if i == 0:
                    qp.setCompositionMode(QPainter.CompositionMode_Source)
                else:
                    qp.setOpacity(layer.opacity)
                    qp.setCompositionMode(layer.compositionMode)
                if layer.rPixmap is None:
                    layer.rPixmap = QPixmap.fromImage(layer.getCurrentImage(
                    ))  # TODO modified 9/12/18 validate
                qp.drawPixmap(QRect(0, 0, img.width(), img.height()),
                              layer.rPixmap)
                # clipping
                if layer.isClipping and layer.maskIsEnabled:
                    # draw mask as opacity mask
                    # mode DestinationIn (set dest opacity to source opacity)
                    qp.setCompositionMode(
                        QPainter.CompositionMode_DestinationIn)
                    omask = vImage.color2OpacityMask(layer.mask)
                    qp.drawImage(QRect(0, 0, img.width(), img.height()), omask)
        qp.end()
        return img

    def applyToStack(self):
        """
        Apply new layer parameters and propagate changes to upper layers.
        """

        # recursive function
        def applyToStack_(layer, pool=None):
            # apply transformation
            if layer.visible:
                start = time()
                layer.execute(l=layer)
                layer.cacheInvalidate()
                print("%s %.2f" % (layer.name, time() - start))
            stack = layer.parentImage.layersStack
            lg = len(stack)
            ind = layer.getStackIndex() + 1
            # update histograms displayed
            # on the layer form, if any
            if ind < lg:
                grForm = stack[ind].getGraphicsForm()
                if grForm is not None:
                    grForm.updateHists()
            # get next upper visible layer
            while ind < lg:
                if stack[ind].visible:
                    break
                ind += 1
            if ind < lg:
                layer1 = stack[ind]
                applyToStack_(layer1, pool=pool)

        try:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            QApplication.processEvents()
            applyToStack_(self, pool=None)
            # update the presentation layer
            self.parentImage.prLayer.execute(l=None, pool=None)
        finally:
            self.parentImage.setModified(True)
            QApplication.restoreOverrideCursor()
            QApplication.processEvents()

    """
    def applyToStackIter(self):
        #iterative version of applyToStack
        stack = self.parentImage.layersStack
        ind = self.getStackIndex() + 1
        try:
            QApplication.setOverrideCursor(Qt.WaitCursor)
            QApplication.processEvents()
            self.execute()
            for layer in stack[ind:]:
                if layer.visible:
                    layer.cacheInvalidate()
                    # for hald friendly layer compute output hald, otherwise compute output image
                    layer.execute()
        finally:
            QApplication.restoreOverrideCursor()
            QApplication.processEvents()
    """

    def isAdjustLayer(self):
        return self.view is not None  # hasattr(self, 'view')

    def isSegmentLayer(self):
        return 'SEGMENT' in self.role

    def isCloningLayer(self):
        return 'CLONING' in self.role

    def isGeomLayer(self):
        return 'GEOM' in self.role

    def is3DLUTLayer(self):
        return '3DLUT' in self.role

    def isRawLayer(self):
        return 'RAW' in self.role

    def updatePixmap(self, maskOnly=False):
        """
        Synchronize rPixmap with the layer image and mask.
        if maskIsEnabled is False, the mask is not used.
        If maskIsEnabled is True, then
            - if maskIsSelected is True, the mask is drawn over
              the layer as a color mask.
            - if maskIsSelected is False, the mask is drawn as an
              opacity mask, setting the image opacity to that of the mask
              (mode DestinationIn).
        @param maskOnly: not used : for consistency with overriding method signature
        @type maskOnly: boolean
        """
        rImg = self.getCurrentImage()
        # apply layer transformation. Missing pixels are set to QColor(0,0,0,0)
        if self.xOffset != 0 or self.yOffset != 0:
            x, y = self.full2CurrentXY(self.xOffset, self.yOffset)
            rImg = rImg.copy(
                QRect(-x, -y,
                      rImg.width() * self.Zoom_coeff,
                      rImg.height() * self.Zoom_coeff))
        if self.maskIsEnabled:
            rImg = vImage.visualizeMask(rImg,
                                        self.mask,
                                        color=self.maskIsSelected,
                                        clipping=True)  # self.isClipping)
        self.rPixmap = QPixmap.fromImage(rImg)
        self.setModified(True)

    def getStackIndex(self):
        """
        Returns layer index in the stack, len(stack) - 1 if
        the layer is not in the stack.
        @return:
        @rtype: int
        """
        i = -1  # TODO added 5/11/18 validate
        for i, l in enumerate(self.parentImage.layersStack):
            if l is self:
                break
        return i

    def setMaskEnabled(self, color=False):
        self.maskIsEnabled = True
        self.maskIsSelected = color
        self.maskSettingsChanged.sig.emit()

    def getTopVisibleStackIndex(self):
        """
        Returns the index of the top visible layer
        @return:
        @rtype:
        """
        stack = self.parentImage.layersStack
        lg = len(stack)
        for i in range(lg - 1, -1, -1):
            if stack[i].visible:
                return i
        return -1

    def getLowerVisibleStackIndex(self):
        """
        Returns the index of the next lower visible layer,
        -1 if it does not exists
        @return:
        @rtype: int
        """
        ind = self.getStackIndex()
        stack = self.parentImage.layersStack
        for i in range(ind - 1, -1, -1):
            if stack[i].visible:
                return i
        return -1

    def getUpperVisibleStackIndex(self):
        """
        Returns the index of the next upper visible layer,
        -1 if it does not exists
        @return:
        @rtype: int
        """
        ind = self.getStackIndex()
        stack = self.parentImage.layersStack
        lg = len(stack)
        for i in range(ind + 1, lg, 1):
            if stack[i].visible:
                return i
        return -1

    def getLowerClippingStackIndex(self):
        """
         Returns the index of the next lower clipping layer,
        -1 if it does not exists

        @return:
        @rtype: int
        """
        ind = self.getStackIndex()
        for i in range(ind + 1, len(self.parentImage.layersStack), 1):
            if self.parentImage.layersStack[i].isClipping:
                return i
        return -1

    def linkMask2Lower(self):
        """
        share mask with next lower layer
        @return:
        @rtype:
        """
        ind = self.getStackIndex()
        if ind == 0:
            return
        lower = self.parentImage.layersStack[ind - 1]
        # don't link two groups
        if self.group and lower.group:
            return
        if not self.group and not lower.group:
            self.group = [self, lower]
            lower.group = self.group
        elif not lower.group:
            if not any(o is lower for o in self.group):
                self.group.append(lower)
            lower.group = self.group
        elif not self.group:
            if not any(item is self for item in lower.group):
                lower.group.append(self)
            self.group = lower.group
        self.mask = lower.mask

    def unlinkMask(self):
        self.mask = self.mask.copy()
        # remove self from group
        for i, item in enumerate(self.group):
            if item is self:
                self.group.pop(i)
                # don't keep  group with length 1
                if len(self.group) == 1:
                    self.group.pop(0)
                break
        self.group = []

    def merge_with_layer_immediately_below(self):
        """
        Merges a layer with the next lower visible layer. Does nothing
        if mode is preview or the target layer is an adjustment layer.
        """
        if not hasattr(self, 'inputImg'):
            return
        ind = self.getLowerVisibleStackIndex()
        if ind < 0:
            # no visible layer found
            return
        target = self.parentImage.layersStack[ind]
        if hasattr(target, 'inputImg') or self.parentImage.useThumb:
            info = "Uncheck Preview first" if self.parentImage.useThumb else "Target layer must be background or image"
            dlgWarn("Cannot Merge layers", info=info)
            return
        # update stack
        self.parentImage.layersStack[0].applyToStack()
        # merge
        # target.setImage(self)
        qp = QPainter(target)
        qp.setCompositionMode(self.compositionMode)
        qp.setOpacity(self.opacity)
        qp.drawImage(QRect(0, 0, self.width(), self.height()), self)
        target.updatePixmap()
        self.parentImage.layerView.clear(delete=False)
        currentIndex = self.getStackIndex()
        self.parentImage.activeLayerIndex = ind
        self.parentImage.layersStack.pop(currentIndex)
        self.parentImage.layerView.setLayers(self.parentImage)

    def reset(self):
        """
        reset layer to inputImg
        """
        self.setImage(self.inputImg())

    def setOpacity(self, value):
        """
        set layer opacity to value/100.0
        @param value:
        @type value: int in range 0..100
        """
        self.opacity = value / 100.0

    def setColorMaskOpacity(self, value):
        """
        Set mask alpha channel to value
        @param value:
        @type value: int in range 0..255
        """
        self.colorMaskOpacity = value
        buf = QImageBuffer(self.mask)
        buf[:, :, 3] = np.uint8(value)

    def readFromStream(self, dataStream):
        grForm = self.getGraphicsForm()
        if grForm is not None:
            grForm.readFromStream(dataStream)
        return dataStream