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
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 __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
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()
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()
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 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 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
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
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))
def transparencyCheck(buf): if np.any(buf[:, :, 3] < 255): dlgWarn('Transparency will be lost. Use PNG format instead')
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()
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()
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()
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)
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:
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)