Beispiel #1
0
 def B_get_display_profile(handle=None, device_id=None):
     """
     bLUe version of ImageCms get_display_profile.
     @param handle: screen handle (Windows)
     @type handle: int
     @param device_id: name of display
     @type device_id: str
     @return: monitor profile or None
     @rtype: ImageCmsProfile or None
     """
     profile_path = DEFAULT_MONITOR_PROFILE_PATH
     if sys.platform == "win32":
         profile_path = core.get_display_profile_win32(handle, 1)
     elif HAS_GI:
         try:
             from PySide2.QtWidgets import QApplication, QMainWindow
             GIO_CANCELLABLE = Gio.Cancellable.new()
             client = Colord.Client.new()
             client.connect_sync(GIO_CANCELLABLE)
             device = client.find_device_sync('xrandr-' + device_id,
                                              GIO_CANCELLABLE)
             device.connect_sync(GIO_CANCELLABLE)
             default_profile = device.get_default_profile()
             default_profile.connect_sync(GIO_CANCELLABLE)
             profile_path = default_profile.get_filename()
         except (NameError, ImportError, GLib.GError) as e:
             from bLUeTop.QtGui1 import window
             dlgWarn('Cannot detect monitor profile',
                     info=str(e),
                     parent=window)
     try:
         Cms_profile = getOpenProfile(profile_path)
     except PyCMSError:
         Cms_profile = get_default_monitor_profile()
     return Cms_profile
Beispiel #2
0
 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)
Beispiel #3
0
 def __enter__(self):
     """
     entering "with" block: launch exiftool.
     According to the documentation stdin, stdout and stderr are open in binary mode.
     """
     try:
         # hide sub-window to prevent flashing console
         # when the program is frozen by PyInstaller with
         # console set to False.
         startupinfo = subprocess.STARTUPINFO()
         startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW  # prevent subprocess from opening a window.
         startupinfo.wShowWindow = subprocess.SW_HIDE  # This is needed when the application is frozen with PyInstaller
         # -@ FILE : read command line args from FILE
         # -stay_open True: keep reading -@ argFILE even after EOF
         self.process = subprocess.Popen(
             [self.executable, "-stay_open", "True", "-@", "-"],
             stdin=subprocess.PIPE,
             stdout=subprocess.PIPE,
             stderr=subprocess.STDOUT,  # subprocess.DEVNULL
             startupinfo=startupinfo)
     except OSError:
         dlgWarn(
             "cannot execute exiftool :\nset EXIFTOOL_PATH in settings.py")
         # exit program
         exit()
     return self
Beispiel #4
0
 def mouseMoveEvent(self, event):
     """
     Mouse move event handler
     @param event:
     @type event:
     """
     modifiers = QApplication.keyboardModifiers()
     # mouse coordinates, relative to parent widget
     pos = self.mapToParent(event.pos())
     img = self.tool.layer.parentImage
     r = self.tool.resizingCoeff
     self.tool.targetQuad_old = self.tool.getTargetQuad(
     )  # TODO added 15/05/18 validate
     self.posRelImg = (pos - QPoint(img.xOffset, img.yOffset)) / r
     if modifiers == Qt.ControlModifier | Qt.AltModifier:
         if self.tool.isModified():
             dlgWarn("A transformation is in progress", "Reset first")
             return
         # update the new starting  position
         self.posRelImg_ori = self.posRelImg  # (pos - QPoint(img.xOffset, img.yOffset)) / r
         self.posRelImg_frozen = self.posRelImg
         self.tool.moveRotatingTool()
         self.tool.parent().repaint()
         return
     curimg = self.tool.layer.getCurrentImage()
     w, h = curimg.width(), curimg.height()
     s = w / self.tool.img.width()
     form = self.tool.getForm()
     if form.options['Free']:
         pass
     elif form.options['Rotation']:
         center = self.tool.getTargetQuad().boundingRect().center()
         v = QPointF(self.posRelImg.x() - center.x(),
                     self.posRelImg.y() - center.y())
         v0 = QPointF(self.posRelImg_frozen.x() - center.x(),
                      self.posRelImg_frozen.y() - center.y())
         theta = (np.arctan2(v.y(), v.x()) -
                  np.arctan2(v0.y(), v0.x())) * 180.0 / np.pi
         T = QTransform()  # self.tool.geoTrans_ori)
         T.translate(center.x(), center.y()).rotate(theta).translate(
             -center.x(), -center.y())
         q = T.map(self.tool.getFrozenQuad())
         for i, role in enumerate(
             ['topLeft', 'topRight', 'bottomRight', 'bottomLeft']):
             self.tool.btnDict[role].posRelImg = q.at(i)
     elif form.options['Translation']:
         # translation vector (coordinates are relative to the full size image)
         p = QPointF(self.posRelImg) - QPointF(self.posRelImg_frozen)
         T = QTransform()
         T.translate(p.x(), p.y())
         q = T.map(self.tool.getFrozenQuad())
         for i, role in enumerate(
             ['topLeft', 'topRight', 'bottomRight', 'bottomLeft']):
             self.tool.btnDict[role].posRelImg = q.at(i)
     self.tool.moveRotatingTool()
     self.tool.modified = True
     self.tool.layer.applyToStack()
     self.parent().repaint()
Beispiel #5
0
 def f():
     layer = self.layer
     if vImage.isAllMasked(layer.mask):
         dlgWarn('Nothing to clone: unmask some pixels')
         return
     if layer.xAltOffset == 0.0 and layer.yAltOffset == 0.0:
         dlgWarn('Nothing to clone: Ctr+Alt+Drag the image ')
         return
     layer.applyCloning(seamless=True)
     layer.parentImage.onImageChanged()
Beispiel #6
0
 def testUpperVisibility():
     pos = self.img.getStackIndex(layer)
     upperVisible = False
     for i in range(len(self.img.layersStack) - pos - 1):
         if self.img.layersStack[pos + 1 + i].visible:
             upperVisible = True
             break
     if upperVisible:
         dlgWarn("Upper visible layers slow down mask edition")
         return True
     return False
Beispiel #7
0
 def groupSelection():
     layers = [rStack[i] for i in sorted(rows)]
     if any(l.group for l in layers):
         dlgWarn("Some layers are already grouped. Ungroup first")
         return
     mask = layers[0].mask
     for l in layers:
         l.group = layers
         l.mask = mask
         l.maskIsEnabled = True
         l.maskIsSelected = False
Beispiel #8
0
def get_default_working_profile():
    """
    try to find a default image profile
    @return: profile
    @rtype: ImageCmsProfile
    """
    try:
        profile = getOpenProfile(SRGB_PROFILE_PATH)
    except PyCMSError:
        dlgWarn(
            'No valid sRGB color profile found.\nSet SYSTEM_PROFILE_DIR and SRGB_PROFILE_NAME in your config.json',
            info='Invalid profile %s' % SRGB_PROFILE_PATH)
        sys.exit()
    return profile
def rawRead(filename):
    """
    Loads a raw image file into a RawPy instance.
    The image file is closed after reading.
    @param filename:
    @tyep filename: str
    @return:
    @rtype: RawPy instance
    """
    rawpyInst = rawpy.RawPy()
    with open(filename, "rb") as bufio:
        rawpyInst.open_buffer(bufio)
    try:
        rawpyInst.unpack()
    except LibRawFatalError as e:
        dlgWarn('LibRaw Fatal Error', 'Only flat raw images are supported')
        raise
    return rawpyInst
Beispiel #10
0
 def execute(self, *args, ascii=True):
     """
     Main ExifTool method. It executes
     the exiftool commands defined by *args and returns
     exif output. If ascii is True, output is decoded as str,
     and is a bytes object otherwise.
     @param args:
     @type args: tuple of str
     @param ascii: flag for the type of returned data
     @type ascii: boolean
     @return: command output
     @rtype: str or bytes according to the ascii flag.
     """
     args = args + ("-execute\n", )
     # convert command to bytes and write it to process stdin
     stdin = self.process.stdin
     try:
         stdin.write(bytearray(str.join("\n", args), 'ascii'))
     except UnicodeEncodeError as e:
         dlgWarn(str.join("\n", args), str(e))
     # flush and sync stdin : both are mandatory on Windows
     stdin.flush()
     if platform == 'win32':
         os.fsync(stdin.fileno())
     # get exiftool response : data, if any, followed by sentinel
     output = bytearray()
     fdout = self.process.stdout.fileno()
     # encode sentinel to bytes
     sb = self.sentinel.encode('ascii')
     # read stdout up to sentinel
     # NOTE: os.read is blocking; termination is granted by the sentinel
     if platform == 'win32':
         eol = 2  # CRLF
     else:
         eol = 1  # CR
     while not output[:-eol].endswith(sb):
         output.extend(os.read(fdout, 4096))
     # cut off sentinel and CRLF
     output = output[:-len(self.sentinel) - eol]
     if ascii:
         output = str(output, encoding='ascii')
     else:
         output = bytes(output)
     return output
Beispiel #11
0
 def infoDone():
     try:
         token = self.info.text().split(' ')
         if self.colorInfoFormat == 0:  # RGB
             r, g, b = [int(x) for x in token if x.isdigit()]
             pt = QPointF(*swatchImg.GetPoint(*rgb2hsB(r, g, b)[:2])) + offset
         elif self.colorInfoFormat == 1:  # CMYK
             c, m, y, k = [int(x) for x in token if x.isdigit()]
             pt = QPointF(*swatchImg.GetPoint(*rgb2hsB(*cmyk2rgb(c, m, y, k))[:2])) + offset
         elif self.colorInfoFormat == 2:  # HSV
             h, s, _ = [int(x) for x in token if x.isdigit()]
             if not 0 <= s <= 100:
                 raise ValueError
             pt = QPointF(*swatchImg.GetPoint(h, s / 100.0)) + offset
         else:
             raise ValueError
         self.workingMarker.setPos(pt.x(), pt.y())
     except ValueError:
         dlgWarn("Invalid color")
    def saveLUT(self):
        """

        """
        mainForm = self.mainForm
        lut = self.scene().lut
        lastDir = str(mainForm.settings.value('paths/dlg3DLUTdir', '.'))
        dlg = QFileDialog(mainForm, "Save Color LUT", lastDir)
        dlg.setNameFilter('*.cube')
        dlg.setDefaultSuffix('cube')
        try:
            if dlg.exec_():
                filenames = dlg.selectedFiles()
                newDir = dlg.directory().absolutePath()
                mainForm.settings.setValue('paths/dlg3DLUTdir', newDir)
                lut.writeToTextFile(filenames[0])
                dlgInfo('3D LUT written')
        except IOError as e:
            dlgWarn(str(e))
Beispiel #13
0
 def transparencyCheck(buf):
     if np.any(buf[:, :, 3] < 255):
         dlgWarn('Transparency will be lost. Use PNG format instead')
Beispiel #14
0
def mouseEvent(widget, event, qp=qp, window=window):  # TODO split into 3 handlers
    """
    Mouse event handler.
    The handler implements mouse actions on a vImage in a QLabel.
    It handles image positioning, zooming, and
    tool actions. It must be called by the mousePressed,
    mouseMoved and mouseReleased methods of the QLabel. This can be done by
    subclassing (not directly compatible with Qt Designer) or by
    dynamically assigning mouseEvent to the former three methods
    (cf. the function set_event_handler below).
    NOTE 1. Mouse hover generates mouse move events
    NOTE 2. Due to wheeelEvent, xOffset and yOffset are float numbers
    @param widget:
    @type widget: QLabel object with img attribute of type mImage
    @param event: mouse event
    @type event: QMouseEvent
    @param window:
    @type window: QMainWidget
    """
    global pressed, clicked
    if type(event) == QContextMenuEvent:
        return
    # get image and active layer
    img = widget.img
    layer = img.getActiveLayer()
    r = img.resize_coeff(widget)
    # x, y coordinates (relative to widget)
    x, y = event.x(), event.y()
    modifiers = event.modifiers() # app.keyboardModifiers()
    eventType = event.type()
    ###################
    # mouse press event
    ###################
    if eventType == QEvent.MouseButtonPress:
        # Mouse hover generates mouse move events,
        # so, we set pressed to select only non hovering events
        pressed = True
        if event.button() == Qt.LeftButton:
            # no move yet
            clicked = True
        State['ix'], State['iy'] = x, y
        State['ix_begin'], State['iy_begin'] = x, y
        State['x_imagePrecPos'], State['y_imagePrecPos'] = (x - img.xOffset) // r, (y - img.yOffset) // r
        # add current mask to history
        if window.btnValues['drawFG'] or window.btnValues['drawBG']:
            if layer.maskIsEnabled:
                layer.historyListMask.addItem(layer.mask.copy())
        # dragBtn or arrow
        elif modifiers == Qt.ControlModifier | Qt.AltModifier:
            if layer.isCloningLayer():
                layer.updateCloningMask()
                layer.updateSourcePixmap()
        return
    ##################
    # mouse move event
    ##################
    elif eventType == QEvent.MouseMove:
        # hover event
        if not pressed:
            x_img, y_img = (x - img.xOffset) / r, (y - img.yOffset) / r
            # read input and current colors from active layer (coordinates are relative to the full-sized image)
            clr = img.getActivePixel(x_img, y_img, qcolor=True)
            clrC = img.getActivePixel(x_img, y_img, fromInputImg=False, qcolor=True)
            window.infoView.setText(clr, clrC)
            return
        clicked = False
        if img.isMouseSelectable:
            # don't draw on a non visible layer
            if window.btnValues['rectangle'] or window.btnValues['drawFG'] or window.btnValues['drawBG']:
                if not layer.visible:
                    dlgWarn('Select a visible layer for drawing or painting')
                    pressed = False
                    return
                elif not window.btnValues['rectangle'] and not layer.maskIsEnabled:
                    dlgWarn('Enable the mask before painting')
                    pressed = False
                    return
            # marquee tool
            if window.btnValues['rectangle']:
                # rectangle coordinates are relative to full image
                x_img = (min(State['ix_begin'], x) - img.xOffset) // r
                y_img = (min(State['iy_begin'], y) - img.yOffset) // r
                w = abs(State['ix_begin'] - x) // r
                h = abs(State['iy_begin'] - y) // r
                layer.rect = QRect(x_img, y_img, w, h)
            # drawing tools
            elif window.btnValues['drawFG'] or window.btnValues['drawBG']:
                if layer.maskIsEnabled:
                    toolOpacity = window.verticalSlider2.value() / 100
                    if modifiers == Qt.NoModifier:
                        if layer.isSegmentLayer():
                            color = vImage.defaultColor_UnMasked_SM if \
                                window.btnValues['drawFG'] else vImage.defaultColor_Masked_SM
                        else:
                            color = vImage.defaultColor_UnMasked if \
                                window.btnValues['drawFG'] else vImage.defaultColor_Masked
                    else:
                        color = vImage.defaultColor_UnMasked_Invalid
                    qp.begin(layer.mask)
                    # get pen width (relative to image)
                    w_pen = window.verticalSlider1.value() // r
                    # mode source : result is source (=pen) pixel color and opacity
                    qp.setCompositionMode(qp.CompositionMode_Source)
                    tmp_x = (x - img.xOffset) // r
                    tmp_y = (y - img.yOffset) // r
                    qp.setPen(QPen(color, w_pen))
                    qp.setOpacity(toolOpacity)
                    # paint the brush tips spaced by 0.25 * w_pen
                    # use 1-norm for performance
                    a_x, a_y = tmp_x - State['x_imagePrecPos'], tmp_y - State['y_imagePrecPos']
                    d = abs(a_x) + abs(a_y)
                    x, y = State['x_imagePrecPos'], State['y_imagePrecPos']
                    radius = w_pen / 2
                    if d == 0:
                        qp.drawEllipse(QPointF(x, y), radius, radius)  # center, radius : QPointF mandatory, else bounding rect topleft and size
                    else:
                        step = w_pen * 0.25 / d
                        for i in range(int(1 / step) + 1):
                            qp.drawEllipse(QPointF(x, y), radius, radius)  # center, radius : QPointF mandatory, else bounding rect topleft and size
                            x, y = x + a_x * step, y + a_y * step
                    qp.end()
                    State['x_imagePrecPos'], State['y_imagePrecPos'] = tmp_x, tmp_y
                    ############################
                    # update upper stack
                    # should be layer.applyToStack() if any upper layer visible : too slow
                    layer.updatePixmap()  # maskOnly=True not used: removed
                    #############################
                    img.prLayer.applyNone()
                    window.label.repaint()
            # dragBtn or arrow
            else:
                # drag image
                if modifiers == Qt.NoModifier:
                    img.xOffset += x - State['ix']
                    img.yOffset += y - State['iy']
                    if window.btnValues['Crop_Button']:
                        window.cropTool.drawCropTool(img)
                # drag active layer only
                elif modifiers == Qt.ControlModifier:
                    layer.xOffset += (x - State['ix'])
                    layer.yOffset += (y - State['iy'])
                    layer.updatePixmap()
                    img.prLayer.applyNone()
                # drag cloning virtual layer
                elif modifiers == Qt.ControlModifier | Qt.AltModifier:
                    if layer.isCloningLayer():
                        layer.xAltOffset += (x - State['ix'])
                        layer.yAltOffset += (y - State['iy'])
                        layer.vlChanged = True
                        if layer.maskIsSelected or not layer.maskIsEnabled:
                            layer.setMaskEnabled(color=False)
                        layer.applyCloning(seamless=False, showTranslated=True, moving=True)
        # not mouse selectable widget : probably before window alone !
        else:
            if modifiers == Qt.NoModifier:
                img.xOffset += (x - State['ix'])
                img.yOffset += (y - State['iy'])
            elif modifiers == Qt.ControlModifier:
                layer.xOffset += (x - State['ix'])
                layer.yOffset += (y - State['iy'])
                layer.updatePixmap()
        # update current coordinates
        State['ix'], State['iy'] = x, y
        if layer.isGeomLayer():
            layer.tool.moveRotatingTool()
    #####################
    # mouse release event
    # mouse click event
    ####################
    elif eventType == QEvent.MouseButtonRelease:
        pressed = False
        if event.button() == Qt.LeftButton:
            if layer.maskIsEnabled \
                    and layer.getUpperVisibleStackIndex() != -1\
                    and (window.btnValues['drawFG'] or window.btnValues['drawBG']):
                layer.applyToStack()
            if img.isMouseSelectable:
                # click event
                if clicked:
                    x_img, y_img = (x - img.xOffset) / r, (y - img.yOffset) / r
                    # read input and current colors from active layer (coordinates are relative to the full-sized image)
                    clr = img.getActivePixel(x_img, y_img, qcolor=True)
                    clrC = img.getActivePixel(x_img, y_img, fromInputImg=False, qcolor=True)
                    red, green, blue = clr.red(), clr.green(), clr.blue()
                    # read color from presentation layer
                    redP, greenP, blueP = img.getPrPixel(x_img, y_img)
                    # color chooser : when visible the colorPicked signal is not emitted
                    if getattr(window, 'colorChooser', None) and window.colorChooser.isVisible():
                        if (modifiers & Qt.ControlModifier) and (modifiers & Qt.ShiftModifier):
                            window.colorChooser.setCurrentColor(clr)
                        elif modifiers & Qt.ControlModifier:
                            window.colorChooser.setCurrentColor(clrC)
                        else:
                            window.colorChooser.setCurrentColor(QColor(redP, greenP, blueP))
                    else:
                        # emit colorPicked signal
                        layer.colorPicked.sig.emit(x_img, y_img, modifiers)
                        # select grid node for 3DLUT form
                        if layer.is3DLUTLayer():
                            layer.getGraphicsForm().selectGridNode(red, green, blue)
                        # rectangle selection
                        if window.btnValues['rectangle'] and (modifiers == Qt.ControlModifier):
                                layer.rect = None
                                layer.selectionChanged.sig.emit()
                        # for raw layer, set multipliers to get selected pixel as White Point : NOT USED YET
                        if layer.isRawLayer() and window.btnValues['colorPicker']:
                            # get demosaic buffer and sample raw pixels
                            bufRaw = layer.parentImage.demosaic
                            nb = QRect(x_img-2, y_img-2, 4, 4)
                            r = QImage.rect(layer.parentImage).intersected(nb)
                            if not r.isEmpty():
                                color = np.sum(bufRaw[r.top():r.bottom()+1, r.left():r.right()+1], axis=(0, 1))/(r.width()*r.height())
                            else:
                                color = bufRaw[y_img, x_img, :]
                            color = [color[i] - layer.parentImage.rawImage.black_level_per_channel[i] for i in range(3)]
                            form = layer.getGraphicsForm()
                            if form.sampleMultipliers:
                                row, col = 3*y_img//layer.height(), 3*x_img//layer.width()
                                if form.samples:
                                    form.setRawMultipliers(*form.samples[3*row + col], sampling=False)
                            else:
                                form.setRawMultipliers(1/color[0], 1/color[1], 1/color[2], sampling=True)
                else:  # not clicked
                    if window.btnValues['rectangle']:
                        layer.selectionChanged.sig.emit()
                    # cloning layer
                    elif layer.isCloningLayer():
                        if layer.vlChanged:
                            # the virtual layer was moved : clone
                            layer.applyCloning(seamless=True, showTranslated=True, moving=True)
                            layer.vlChanged = False
    # updates
    widget.repaint()
    # sync split views
    linked = True
    if widget.objectName() == 'label_2':
        splittedWin.syncSplittedView(window.label_3, window.label_2, linked)
        window.label_3.repaint()
    elif widget.objectName() == 'label_3':
        splittedWin.syncSplittedView(window.label_2, window.label_3, linked)
        window.label_2.repaint()
Beispiel #15
0
 def mouseMoveEvent(self, event):
     """
    Mouse event handlers.
    The handlers implement mouse actions on an imImage displayed in a QLabel.
    It handles image positioning, zooming, and
    tool actions.
    NOTE 1. Mouse hover generates mouse move events
    NOTE 2. Due to wheeelEvent, xOffset and yOffset are float numbers
    @param event: mouse event
    @type event: QMouseEvent
    """
     window = self.window
     State = self.State
     qp = self.qp
     eventType = event.type()
     # no context menu
     if eventType == QContextMenuEvent:
         return
     # get image and active layer
     img = self.img
     layer = img.getActiveLayer()
     r = img.resize_coeff(self)
     ############################################################
     # get mouse x, y coordinates (relative to widget).
     # The mouse coordinates relative to the (full size) image are
     # (x - img.xOffset) / r, (y - img.yOffset) / r
     #############################################################
     x, y = event.x(), event.y()
     modifiers = event.modifiers()
     # hover event
     if not self.pressed:
         x_img, y_img = (x - img.xOffset) / r, (y - img.yOffset) / r
         # read input and current colors from active layer (coordinates are relative to the full-sized image)
         clr = img.getActivePixel(x_img, y_img, qcolor=True)
         clrC = img.getActivePixel(x_img,
                                   y_img,
                                   fromInputImg=False,
                                   qcolor=True)
         window.infoView.setText(clr, clrC)
         if layer.isCloningLayer():
             if layer.cloningState == 'continue':
                 mx, my = x_img - layer.xAltOffset, y_img - layer.yAltOffset
             elif layer.cloningState == 'start':
                 mx, my = layer.sourceX, layer.sourceY
             else:
                 mx, my = x_img, y_img
             if layer.sourceFromFile:
                 pxmp = layer.getGraphicsForm().sourcePixmap
                 mx, my = mx * pxmp.width() / layer.width(
                 ), my * pxmp.height() / layer.height()
             layer.marker = QPointF(mx, my)
             window.label.repaint()
             if layer.sourceFromFile:
                 layer.getGraphicsForm().widgetImg.repaint()
         return
     self.clicked = False
     if img.isMouseSelectable:
         # don't draw on a non visible layer
         if window.btnValues['rectangle'] or window.btnValues[
                 'drawFG'] or window.btnValues['drawBG']:
             if not layer.visible:
                 dlgWarn('Select a visible layer for drawing or painting')
                 self.pressed = False
                 return
             elif not window.btnValues[
                     'rectangle'] and not layer.maskIsEnabled:
                 dlgWarn('Enable the mask before painting')
                 self.pressed = False
                 return
         # marquee tool
         if window.btnValues['rectangle']:
             # rectangle coordinates are relative to full image
             x_img = (min(State['ix_begin'], x) - img.xOffset) // r
             y_img = (min(State['iy_begin'], y) - img.yOffset) // r
             w = abs(State['ix_begin'] - x) // r
             h = abs(State['iy_begin'] - y) // r
             layer.rect = QRect(x_img, y_img, w, h)
         # drawing
         elif layer.isDrawLayer() and (window.btnValues['brushButton']
                                       or window.btnValues['eraserButton']):
             self.__strokePaint(layer, x, y, r)
         # mask
         elif window.btnValues['drawFG'] or window.btnValues['drawBG']:
             if layer.maskIsEnabled:
                 if layer.isCloningLayer:
                     layer.vlChanged = True  # TODO added 16/12/19
                     # layer.setMaskEnabled(color=True)  # set mask to color mask
                 toolOpacity = window.verticalSlider2.value() / 100
                 if modifiers == Qt.NoModifier:
                     if layer.isSegmentLayer():
                         color = vImage.defaultColor_UnMasked_SM if \
                             window.btnValues['drawFG'] else vImage.defaultColor_Masked_SM
                     else:
                         color = vImage.defaultColor_UnMasked if \
                             window.btnValues['drawFG'] else vImage.defaultColor_Masked
                 else:
                     color = vImage.defaultColor_UnMasked_Invalid
                 qp.begin(layer.mask)
                 # get pen width (relative to image)
                 w_pen = window.verticalSlider1.value() // r
                 # mode source : result is source (=pen) pixel color and opacity
                 qp.setCompositionMode(qp.CompositionMode_Source)
                 tmp_x = (x - img.xOffset) // r
                 tmp_y = (y - img.yOffset) // r
                 qp.setPen(QPen(color, w_pen))
                 qp.setOpacity(toolOpacity)
                 # paint the brush tips spaced by 0.25 * w_pen
                 # use 1-norm for performance
                 a_x, a_y = tmp_x - State['x_imagePrecPos'], tmp_y - State[
                     'y_imagePrecPos']
                 d = abs(a_x) + abs(a_y)
                 x, y = State['x_imagePrecPos'], State['y_imagePrecPos']
                 radius = w_pen / 2
                 if d == 0:
                     qp.drawEllipse(
                         QPointF(x, y), radius, radius
                     )  # center, radius : QPointF mandatory, else bounding rect topleft and size
                 else:
                     step = w_pen * 0.25 / d
                     for i in range(int(1 / step) + 1):
                         qp.drawEllipse(
                             QPointF(x, y), radius, radius
                         )  # center, radius : QPointF mandatory, else bounding rect topleft and size
                         x, y = x + a_x * step, y + a_y * step
                 qp.end()
                 if layer.isCloningLayer():
                     if not layer.sourceFromFile:
                         layer.marker = QPointF(tmp_x - layer.xAltOffset,
                                                tmp_y - layer.yAltOffset)
                     else:
                         pxmp = layer.getGraphicsForm().sourcePixmap
                         layer.marker = QPointF(
                             (tmp_x - layer.xAltOffset) * pxmp.width() /
                             layer.width(), (tmp_y - layer.yAltOffset) *
                             pxmp.height() / layer.height())
                         layer.getGraphicsForm().widgetImg.repaint()
                 State['x_imagePrecPos'], State[
                     'y_imagePrecPos'] = tmp_x, tmp_y
                 ############################
                 # update upper stack
                 # should be layer.applyToStack() if any upper layer visible : too slow
                 # layer.applyToStack()
                 layer.updatePixmap()
                 img.prLayer.update()  # =applyNone()
                 #############################
                 window.label.repaint()
         # dragBtn or arrow
         else:
             # drag image
             if modifiers == Qt.NoModifier:
                 img.xOffset += x - State['ix']
                 img.yOffset += y - State['iy']
                 if window.btnValues['Crop_Button']:
                     window.cropTool.drawCropTool(img)
             # drag active layer only
             elif modifiers == Qt.ControlModifier:
                 layer.xOffset += (x - State['ix'])
                 layer.yOffset += (y - State['iy'])
                 layer.updatePixmap()
                 img.prLayer.update()  # =applyNone()
             # drag cloning virtual layer
             elif modifiers == Qt.ControlModifier | Qt.AltModifier:
                 if layer.isCloningLayer():
                     layer.xAltOffset += (x - State['ix'])
                     layer.yAltOffset += (y - State['iy'])
                     layer.vlChanged = True
                     if layer.maskIsSelected or not layer.maskIsEnabled:
                         layer.setMaskEnabled(
                             color=False)  # set to opacity mask
                     layer.applyCloning(seamless=False,
                                        showTranslated=True,
                                        moving=True)
     # not mouse selectable widget : probably before window alone !
     else:
         if modifiers == Qt.NoModifier:
             img.xOffset += (x - State['ix'])
             img.yOffset += (y - State['iy'])
         elif modifiers == Qt.ControlModifier:
             layer.xOffset += (x - State['ix'])
             layer.yOffset += (y - State['iy'])
             layer.updatePixmap()
     # update current coordinates
     State['ix'], State['iy'] = x, y
     if layer.isGeomLayer():
         layer.tool.moveRotatingTool()
     # updates
     self.repaint()
     # sync split views
     linked = True
     if self.objectName() == 'label_2':
         self.splitWin.syncSplitView(window.label_3, window.label_2, linked)
         window.label_3.repaint()
     elif self.objectName() == 'label_3':
         self.splitWin.syncSplitView(window.label_2, window.label_3, linked)
         window.label_2.repaint()
Beispiel #16
0
 def mousePressEvent(self, event):
     """
     Mouse event handlers.
     The handlers implement mouse actions on an imImage displayed in a QLabel.
     It handles image positioning, zooming, and
     tool actions.
     NOTE. Due to wheeelEvent, xOffset and yOffset are float numbers
     @param event: mouse event
     @type event: QMouseEvent
     """
     State = self.State
     window = self.window
     eventType = event.type()
     # no context menu
     if eventType == QContextMenuEvent:
         return
     # get image and active layer
     img = self.img
     layer = img.getActiveLayer()
     r = img.resize_coeff(self)
     ############################################################
     # get mouse x, y coordinates (relative to widget).
     # The mouse coordinates relative to the (full size) image are
     # (x - img.xOffset) / r, (y - img.yOffset) / r
     #############################################################
     x, y = event.x(), event.y()
     modifiers = event.modifiers()
     # Mouse hover generates mouse move events,
     # so, we set pressed to select only non hovering events
     self.pressed = True
     if event.button() == Qt.LeftButton:
         # no move yet
         self.clicked = True
     State['ix'], State['iy'] = x, y
     State['ix_begin'], State['iy_begin'] = x, y
     State['x_imagePrecPos'], State['y_imagePrecPos'] = (
         x - img.xOffset) // r, (y - img.yOffset) // r
     if layer.isDrawLayer():
         layer.history.addItem(layer.sourceImg.copy())
         if window.btnValues['brushButton'] or window.btnValues['bucket']:
             # starting a new stroke : save initial image for atomic stroke painting  and init intermediate layer
             layer.strokeDest = layer.sourceImg.copy()
             layer.stroke.fill(QColor(0, 0, 0, 0))
             if window.btnValues['brushButton']:
                 self.syncBrush(r)
     # add current mask to history
     if window.btnValues['drawFG'] or window.btnValues['drawBG']:
         if layer.maskIsEnabled:
             layer.historyListMask.addItem(layer.mask.copy())
     # dragBtn or arrow
     if layer.isCloningLayer():
         if not (window.btnValues['drawFG'] or window.btnValues['drawBG']):
             if modifiers == Qt.ControlModifier | Qt.AltModifier:  # prevent unwanted clicks
                 if layer.cloningState == 'continue':
                     dlgWarn(
                         'Layer already cloned',
                         'To start a new cloning operation, add another cloning layer'
                     )
                     return
                 # set source starting point (coordinates are relative to full size image)
                 layer.sourceX, layer.sourceY = (x - img.xOffset) / r, (
                     y - img.yOffset) / r
                 layer.cloningState = 'start'
         elif layer.cloningState == 'start':
             # set the virtual layer translation (relative to full size image)
             layer.xAltOffset, layer.yAltOffset = (x - img.xOffset) / r - layer.sourceX,\
                                                  (y - img.yOffset) / r - layer.sourceY
             layer.cloningState = 'continue'
             layer.updateCloningMask()
             layer.updateSourcePixmap()
Beispiel #17
0
    def contextMenuEvent(self, event):
        """
        context menu handler
        @param event
        @type event: QContextMenuEvent
        """
        selection = self.selectedIndexes()
        if not selection:
            return
        # get fresh context menu
        self.cMenu = self.initContextMenu()
        # get current selection
        rows = set([mi.row() for mi in selection])
        rStack = self.img.layersStack[::-1]
        layers = [rStack[r] for r in rows]
        group = []  # TODO added 5/11/18 validate
        if layers:
            group = layers[0].group
        for l in layers:
            # different groups
            if l.group and group:
                if l.group is not group:
                    dlgWarn("Select a single group")
                    return
        # get current position
        index = self.indexAt(event.pos())
        layerStackIndex = len(self.img.layersStack) - 1 - index.row()
        layer = self.img.layersStack[layerStackIndex]
        lowerVisible = self.img.layersStack[layer.getLowerVisibleStackIndex()]
        lower = self.img.layersStack[layerStackIndex -
                                     1]  # case index == 0 doesn't matter
        # toggle actions
        self.cMenu.actionMerge.setEnabled(not (
            hasattr(layer, 'inputImg') or hasattr(lowerVisible, 'inputImg')))
        self.actionDup.setEnabled(not layer.isAdjustLayer())
        self.cMenu.actionColorMaskEnable.setChecked(layer.maskIsSelected
                                                    and layer.maskIsEnabled)
        self.cMenu.actionOpacityMaskEnable.setChecked(
            (not layer.maskIsSelected) and layer.maskIsEnabled)
        self.cMenu.actionClippingMaskEnable.setChecked(
            layer.isClipping and (layer.maskIsSelected or layer.maskIsEnabled))
        self.cMenu.actionMaskDisable.setChecked(not (
            layer.isClipping or layer.maskIsSelected or layer.maskIsEnabled))
        self.cMenu.actionMaskUndo.setEnabled(layer.historyListMask.canUndo())
        self.cMenu.actionMaskRedo.setEnabled(layer.historyListMask.canRedo())
        self.cMenu.actionUnselect.setEnabled(layer.rect is None)
        self.cMenu.subMenuEnable.setEnabled(len(rows) == 1)
        self.cMenu.actionMaskPaste.setEnabled(
            not QApplication.clipboard().image().isNull())
        self.cMenu.actionImagePaste.setEnabled(
            not QApplication.clipboard().image().isNull())

        # Event handlers

        def f():
            self.opacitySlider.show()

        def unselectAll():
            layer.rect = None

        def RepositionLayer():
            layer.xOffset, layer.yOffset = 0, 0
            layer.Zoom_coeff = 1.0
            layer.AltZoom_coeff = 1.0
            layer.xAltOffset, layer.yAltOffset = 0, 0
            layer.updatePixmap()
            self.img.onImageChanged()

        def loadImage():
            return  # TODO 26/06/18 action to remove from menu? replaced by new image layer
            filename = openDlg(window)
            img = QImage(filename)
            layer.thumb = None
            layer.setImage(img)

        def merge():
            layer.merge_with_layer_immediately_below()

        def testUpperVisibility():
            pos = self.img.getStackIndex(layer)
            upperVisible = False
            for i in range(len(self.img.layersStack) - pos - 1):
                if self.img.layersStack[pos + 1 + i].visible:
                    upperVisible = True
                    break
            if upperVisible:
                dlgWarn("Upper visible layers slow down mask edition")
                return True
            return False

        def colorMaskEnable():
            testUpperVisibility()
            layer.maskIsEnabled = True
            layer.maskIsSelected = True
            self.maskLabel.setEnabled(layer.maskIsSelected)
            self.maskSlider.setEnabled(layer.maskIsSelected)
            self.maskValue.setEnabled(layer.maskIsSelected)
            layer.applyToStack()
            self.img.onImageChanged()

        def opacityMaskEnable():
            testUpperVisibility()
            layer.maskIsEnabled = True
            layer.maskIsSelected = False
            self.maskLabel.setEnabled(layer.maskIsSelected)
            self.maskSlider.setEnabled(layer.maskIsSelected)
            self.maskValue.setEnabled(layer.maskIsSelected)
            layer.applyToStack()
            self.img.onImageChanged()

        def clippingMaskEnable():
            layer.maskIsEnabled = True
            layer.maskIsSelected = False
            self.maskLabel.setEnabled(layer.maskIsSelected)
            self.maskSlider.setEnabled(layer.maskIsSelected)
            self.maskValue.setEnabled(layer.maskIsSelected)
            layer.isClipping = True
            layer.applyToStack()
            self.img.onImageChanged()

        def maskDisable():
            layer.maskIsEnabled = False
            layer.maskIsSelected = False
            self.maskLabel.setEnabled(layer.maskIsSelected)
            self.maskSlider.setEnabled(layer.maskIsSelected)
            self.maskValue.setEnabled(layer.maskIsSelected)
            layer.isClipping = False  # TODO added 28/11/18
            layer.applyToStack()
            self.img.onImageChanged()

        def undoMask():
            mask = layer.historyListMask.undo(saveitem=layer.mask.copy())
            if mask is not None:
                layer.mask = mask
            layer.applyToStack()
            self.img.onImageChanged()

        def redoMask():
            mask = layer.historyListMask.redo()
            if mask is not None:
                layer.mask = mask
            layer.applyToStack()
            self.img.onImageChanged()

        def maskInvert():
            layer.invertMask()
            # update mask stack
            layer.applyToStack()
            #for l in self.img.layersStack:
            #l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def maskReset_UM():
            layer.resetMask(maskAll=False)
            # update mask stack
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def maskReset_M():
            layer.resetMask(maskAll=True)
            # update mask stack
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def maskCopy():
            QApplication.clipboard().setImage(layer.mask)

        def imageCopy():
            QApplication.clipboard().setImage(layer.getCurrentMaskedImage())

        def maskPaste():
            """
            Pastes clipboard to mask and updates the stack. The clipboard image
            is scaled if its size does not match the size of the mask
            """
            cb = QApplication.clipboard()
            if not cb.image().isNull():
                img = cb.image()
                if img.size() == layer.mask.size():
                    layer.mask = img
                else:
                    layer.mask = img.scaled(layer.mask.size())
            layer.applyToStack()
            self.img.onImageChanged()

        def imagePaste():
            """
            Pastes clipboard to mask and updates the stack. The clipboard image
            is scaled if its size does not match the size of the mask
            """
            cb = QApplication.clipboard()
            if not cb.image().isNull():
                srcImg = cb.image()
                if srcImg.size() == layer.size():
                    layer.setImage(srcImg)
                else:
                    layer.setImage(srcImg.scaled(layer.size()))
            layer.applyToStack()
            self.img.onImageChanged()

        def maskDilate():
            """
            Increase the masked part of the image
            """
            buf = QImageBuffer(layer.mask)
            buf[:, :, 2] = vImage.maskDilate(buf[:, :, 2])
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.applyNone()
            self.img.onImageChanged()

        def maskErode():
            """
            Reduce the masked part of the image
            """
            buf = QImageBuffer(layer.mask)
            buf[:, :, 2] = vImage.maskErode(buf[:, :, 2])
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.applyNone()
            self.img.onImageChanged()

        def maskSmooth():
            """
            Smooth the mask boundary
            """
            buf = QImageBuffer(layer.mask)
            buf[:, :, 2] = vImage.maskSmooth(buf[:, :, 2])
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.prLayer.applyNone()
            self.img.onImageChanged()

        self.cMenu.actionRepositionLayer.triggered.connect(RepositionLayer)
        self.cMenu.actionUnselect.triggered.connect(unselectAll)
        self.cMenu.actionLoadImage.triggered.connect(loadImage)
        self.cMenu.actionMerge.triggered.connect(merge)
        self.cMenu.actionColorMaskEnable.triggered.connect(colorMaskEnable)
        self.cMenu.actionOpacityMaskEnable.triggered.connect(opacityMaskEnable)
        self.cMenu.actionClippingMaskEnable.triggered.connect(
            clippingMaskEnable)
        self.cMenu.actionMaskDisable.triggered.connect(maskDisable)
        self.cMenu.actionMaskUndo.triggered.connect(undoMask)
        self.cMenu.actionMaskRedo.triggered.connect(redoMask)
        self.cMenu.actionMaskInvert.triggered.connect(maskInvert)
        self.cMenu.actionMaskReset_UM.triggered.connect(maskReset_UM)
        self.cMenu.actionMaskReset_M.triggered.connect(maskReset_M)
        self.cMenu.actionMaskCopy.triggered.connect(maskCopy)
        self.cMenu.actionMaskPaste.triggered.connect(maskPaste)
        self.cMenu.actionImageCopy.triggered.connect(imageCopy)
        self.cMenu.actionImagePaste.triggered.connect(imagePaste)
        self.cMenu.actionMaskDilate.triggered.connect(maskDilate)
        self.cMenu.actionMaskErode.triggered.connect(maskErode)
        self.cMenu.actionMaskSmooth.triggered.connect(maskSmooth)
        # self.cMenu.actionReset.triggered.connect(layerReset)
        self.cMenu.exec_(event.globalPos() - QPoint(400, 0))
        # update table
        for row in rows:
            self.updateRow(row)
Beispiel #18
0
from bLUeTop.settings import COLOR_MANAGE_OPT, SRGB_PROFILE_PATH, ADOBE_RGB_PROFILE_PATH, DEFAULT_MONITOR_PROFILE_PATH

if COLOR_MANAGE_OPT:
    if sys.platform == 'win32':
        import win32gui
    else:
        # python-gi flag
        HAS_GI = False
        try:
            from gi.repository import GLib, Gio, Colord
            HAS_GI = True
        except ImportError:
            pass
        if not HAS_GI:
            dlgWarn(
                "Automatic detection of monitor profile needs gi installed.\n trying to use %s instead"
                % DEFAULT_MONITOR_PROFILE_PATH)
            try:
                getOpenProfile(DEFAULT_MONITOR_PROFILE_PATH)
            except PyCMSError:
                dlgWarn("Invalid profile %s" % DEFAULT_MONITOR_PROFILE_PATH,
                        info="Color management is disabled")


def get_default_working_profile():
    """
    try to find a default image profile
    @return: profile
    @rtype: ImageCmsProfile
    """
    try:
Beispiel #19
0
    def contextMenuEvent(self, event):
        """
        context menu handler
        @param event
        @type event: QContextMenuEvent
        """
        selection = self.selectedIndexes()
        if not selection:
            return
        # get fresh context menu
        self.cMenu = self.initContextMenu()
        # get current selection
        rows = set([mi.row() for mi in selection])
        rStack = self.img.layersStack[::-1]
        layers = [rStack[r] for r in rows]
        group = []  # TODO added 5/11/18 validate
        if layers:
            group = layers[0].group
        for l in layers:
            # different groups
            if l.group and group:
                if l.group is not group:
                    dlgWarn("Select a single group")
                    return
        # get current position
        index = self.indexAt(event.pos())
        layerStackIndex = len(self.img.layersStack) - 1 - index.row()
        layer = self.img.layersStack[layerStackIndex]
        lowerVisible = self.img.layersStack[layer.getLowerVisibleStackIndex()]
        lower = self.img.layersStack[layerStackIndex -
                                     1]  # case index == 0 doesn't matter
        # toggle actions
        self.cMenu.actionGroupSelection.setEnabled(not (len(rows) < 2 or any(
            l.group for l in layers)))
        self.cMenu.actionAdd2Group.setEnabled(not (group or layer.group))
        self.cMenu.actionUnGroup.setEnabled(bool(layer.group))
        self.cMenu.actionMerge.setEnabled(not (
            hasattr(layer, 'inputImg') or hasattr(lowerVisible, 'inputImg')))
        self.actionDup.setEnabled(not layer.isAdjustLayer())
        self.cMenu.actionColorMaskEnable.setChecked(layer.maskIsSelected
                                                    and layer.maskIsEnabled)
        self.cMenu.actionOpacityMaskEnable.setChecked(
            (not layer.maskIsSelected) and layer.maskIsEnabled)
        self.cMenu.actionClippingMaskEnable.setChecked(
            layer.isClipping and (layer.maskIsSelected or layer.maskIsEnabled))
        self.cMenu.actionMaskDisable.setChecked(not (
            layer.isClipping or layer.maskIsSelected or layer.maskIsEnabled))
        self.cMenu.actionUnselect.setEnabled(layer.rect is None)
        self.cMenu.subMenuEnable.setEnabled(len(rows) == 1)
        self.cMenu.actionMaskPaste.setEnabled(
            not QApplication.clipboard().image().isNull())
        self.cMenu.actionImagePaste.setEnabled(
            not QApplication.clipboard().image().isNull())

        # Event handlers
        def f():
            self.opacitySlider.show()

        def unselectAll():
            layer.rect = None

        def RepositionLayer():
            layer.xOffset, layer.yOffset = 0, 0
            layer.Zoom_coeff = 1.0
            layer.AltZoom_coeff = 1.0
            layer.xAltOffset, layer.yAltOffset = 0, 0
            layer.updatePixmap()
            self.img.onImageChanged()

        def loadImage():
            return  # TODO 26/06/18 action to remove from menu? replaced by new image layer
            filename = openDlg(window)
            img = QImage(filename)
            layer.thumb = None
            layer.setImage(img)

        def add2Group():
            layer.group = group
            layer.mask = group[0].mask
            layer.maskIsEnabled = True
            layer.maskIsSelected = True

        def groupSelection():
            layers = [rStack[i] for i in sorted(rows)]
            if any(l.group for l in layers):
                dlgWarn("Some layers are already grouped. Ungroup first")
                return
            mask = layers[0].mask
            for l in layers:
                l.group = layers
                l.mask = mask
                l.maskIsEnabled = True
                l.maskIsSelected = False

        def unGroup():
            group = layer.group.copy()
            for l in group:
                l.unlinkMask()

        def merge():
            layer.merge_with_layer_immediately_below()

        def testUpperVisibility():
            pos = self.img.getStackIndex(layer)
            upperVisible = False
            for i in range(len(self.img.layersStack) - pos - 1):
                if self.img.layersStack[pos + 1 + i].visible:
                    upperVisible = True
                    break
            if upperVisible:
                dlgWarn("Upper visible layers slow down mask edition")
                return True
            return False

        def colorMaskEnable():
            testUpperVisibility()
            layer.maskIsEnabled = True
            layer.maskIsSelected = True
            layer.applyToStack()
            self.img.onImageChanged()

        def opacityMaskEnable():
            testUpperVisibility()
            layer.maskIsEnabled = True
            layer.maskIsSelected = False
            layer.applyToStack()
            self.img.onImageChanged()

        def clippingMaskEnable():
            layer.maskIsEnabled = True
            layer.maskIsSelected = False
            layer.isClipping = True
            layer.applyToStack()
            self.img.onImageChanged()

        def maskDisable():
            layer.maskIsEnabled = False
            layer.maskIsSelected = False
            layer.isClipping = False  # TODO added 28/11/18
            layer.applyToStack()
            self.img.onImageChanged()

        def maskInvert():
            layer.invertMask()
            # update mask stack
            layer.applyToStack()
            #for l in self.img.layersStack:
            #l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def maskReset():
            layer.resetMask()
            # update mask stack
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def maskCopy():
            QApplication.clipboard().setImage(layer.mask)

        def imageCopy():
            QApplication.clipboard().setImage(layer.getCurrentMaskedImage())

        def maskPaste():
            """
            Pastes clipboard to mask and updates the stack. The clipboard image
            is scaled if its size does not match the size of the mask
            """
            cb = QApplication.clipboard()
            if not cb.image().isNull():
                img = cb.image()
                if img.size() == layer.mask.size():
                    layer.mask = img
                else:
                    layer.mask = img.scaled(layer.mask.size())
            layer.applyToStack()
            self.img.onImageChanged()

        def imagePaste():
            """
            Pastes clipboard to mask and updates the stack. The clipboard image
            is scaled if its size does not match the size of the mask
            """
            cb = QApplication.clipboard()
            if not cb.image().isNull():
                srcImg = cb.image()
                if srcImg.size() == layer.size():
                    layer.setImage(srcImg)
                else:
                    layer.setImage(srcImg.scaled(layer.size()))
            layer.applyToStack()
            self.img.onImageChanged()

        def maskDilate():
            kernel = np.ones((5, 5), np.uint8)
            buf = QImageBuffer(layer.mask)
            # CAUTION erode decreases values (min filter), so it extends the masked part of the image
            buf[:, :, 2] = cv2.erode(buf[:, :, 2], kernel, iterations=1)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def maskErode():
            kernel = np.ones((5, 5), np.uint8)
            buf = QImageBuffer(layer.mask)
            # CAUTION dilate increases values (max filter), so it reduces the masked part of the image
            buf[:, :, 2] = cv2.dilate(buf[:, :, 2], kernel, iterations=1)
            for l in self.img.layersStack:
                l.updatePixmap(maskOnly=True)
            self.img.onImageChanged()

        def layerReset():
            view = layer.getGraphicsForm()
            if hasattr(view, 'reset'):
                view.reset()

        self.cMenu.actionRepositionLayer.triggered.connect(RepositionLayer)
        self.cMenu.actionUnselect.triggered.connect(unselectAll)
        self.cMenu.actionLoadImage.triggered.connect(loadImage)
        self.cMenu.actionAdd2Group.triggered.connect(add2Group)
        self.cMenu.actionGroupSelection.triggered.connect(groupSelection)
        self.cMenu.actionUnGroup.triggered.connect(unGroup)
        self.cMenu.actionMerge.triggered.connect(merge)
        self.cMenu.actionColorMaskEnable.triggered.connect(colorMaskEnable)
        self.cMenu.actionOpacityMaskEnable.triggered.connect(opacityMaskEnable)
        self.cMenu.actionClippingMaskEnable.triggered.connect(
            clippingMaskEnable)
        self.cMenu.actionMaskDisable.triggered.connect(maskDisable)
        self.cMenu.actionMaskInvert.triggered.connect(maskInvert)
        self.cMenu.actionMaskReset.triggered.connect(maskReset)
        self.cMenu.actionMaskCopy.triggered.connect(maskCopy)
        self.cMenu.actionMaskPaste.triggered.connect(maskPaste)
        self.cMenu.actionImageCopy.triggered.connect(imageCopy)
        self.cMenu.actionImagePaste.triggered.connect(imagePaste)
        self.cMenu.actionMaskDilate.triggered.connect(maskDilate)
        self.cMenu.actionMaskErode.triggered.connect(maskErode)
        self.cMenu.actionReset.triggered.connect(layerReset)
        self.cMenu.exec_(event.globalPos())
        # update table
        for row in rows:
            self.updateRow(row)