def __init__(self, *args, **kwargs): ############################################################ # Signals # Making QLayer inherit from QObject leads to # a bugged behavior of hasattr and getattr. # So, we don't add signals as first level class attributes. # Instead, we use instances of ad hoc signal containers. ############################################################ self.visibilityChanged = baseSignal_bool() self.colorPicked = baseSignal_Int2() self.selectionChanged = baseSignal_No() self.maskSettingsChanged = baseSignal_No() ########################################################### # when a geometric transformation is applied to the whole image # each layer must be replaced with a transformed layer, recorded in tLayer # and tLayer.parentLayer keeps a reference to the original layer. ########################################################### self.tLayer = self self.parentLayer = self self.modified = False self.name = 'noname' self.visible = True self.isClipping = False self.role = kwargs.pop('role', '') self.tool = None # back link to parent image parentImage = kwargs.pop('parentImage', None) self.parentImage = weakProxy(parentImage) super().__init__(*args, **kwargs) # don't move backwards # mask init, must be done after after calling super().__init__ if type(self) not in [QPresentationLayer]: self.mask = QImage(self.width(), self.height(), QImage.Format_ARGB32) # default : unmask all self.mask.fill(self.defaultColor_UnMasked) # layer opacity, range 0.0...1.0 self.opacity = 1.0 self.compositionMode = QPainter.CompositionMode_SourceOver # The next two attributes are used by adjustment layers only. self.execute = lambda l=None, pool=None: l.updatePixmap( ) if l is not None else None self.options = {} # actionName is used by methods graphics***.writeToStream() self.actionName = 'actionNull' # view is the dock widget containing # the graphics form associated with the layer self.view = None # undo/redo mask history self.historyListMask = historyList(size=5) # consecutive layers can be grouped. # A group is a list of QLayer objects self.group = [] # layer offsets self.xOffset, self.yOffset = 0, 0 self.Zoom_coeff = 1.0 # clone dup layer shift and zoom relative to current layer self.xAltOffset, self.yAltOffset = 0, 0 self.AltZoom_coeff = 1.0 self.updatePixmap()
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, mainForm=None): super().__init__(parent=parent) self.layer = layer # additional inactive curve to draw (QPolyLineF or list of QPointF) self.baseCurve = None self.setMinimumSize(axeSize + 60, axeSize + 140) self.setAttribute(Qt.WA_DeleteOnClose) graphicsScene = QGraphicsScene() self.setScene(graphicsScene) # back links to image graphicsScene.targetImage = weakProxy(targetImage) graphicsScene.layer = weakProxy(layer) graphicsScene.bgColor = QColor(200, 200, 200) self.mainForm = mainForm graphicsScene.axeSize = axeSize # add axes and grid graphicsScene.defaultAxes = self.drawPlotGrid(axeSize) graphicsScene.addItem(graphicsScene.defaultAxes) # connect layer colorPicked signal self.scene().layer.colorPicked.sig.connect(self.colorPickedSlot) # default WhatsThis for interactive curves self.setWhatsThis(""" The background histogram is the <i>input</i> histogram; it is refreshed only when the curve is reset.<br> <b>Drag control points</b> with the mouse.<br> <b>Add a control point</b> by clicking on the curve.<br> <b>Remove a control point</b> by clicking it.<br> <b>Zoom</b> with the mouse wheel.<br> <b>Set black, white and neutral points</b> in the image by clicking the corresponding pixels while pressing one of the following key combination (RGB and Lab curves only):<br> <b>Black Point</b> : Ctrl+Shift<br> <b>White Point</b> : Ctrl<br> <b>Grey Neutral Point (Lab only)</b></br> : Shift<br> <b>Caution</b> : Selecting a black, white or neutral point in an image is enabled only when the Color Chooser is closed. """) # end setWhatsThis
def __init__(self, layer=None, targetImage=None, parent=None): super().__init__(parent=parent) # QGraphicsView __init__ is mandatory : don't rely on MRO ! self.setAlignment(Qt.AlignTop) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setFocusPolicy(Qt.ClickFocus) # back links to image self.layer = layer self.targetImage = targetImage self.setScene(QGraphicsScene()) # convenience attributes self.graphicsScene = weakProxy(self.scene()) self.graphicsScene.options = None self.graphicsScene.layer = self.layer self.graphicsScene.targetImage = self.targetImage self.dataChanged.connect(self.updateLayer) # layer color picked signal if layer is not None: layer.colorPicked.sig.connect(self.colorPickedSlot)
def setLayers(self, mImg, delete=False): """ Displays the layer stack of mImg @param mImg: image @type mImg: mImage """ # close open adjustment windows #self.closeAdjustForms() self.clear(delete=delete) mImg.layerView = self # back link to image self.img = weakProxy(mImg) model = layerModel() model.setColumnCount(3) l = len(mImg.layersStack) # dataChanged event handler : enables edition of layer name def f(index1, index2): # index1 and index2 should be equal # only layer name should be editable # dropEvent emit dataChanged when setting item values. f must # return immediately from these calls, as they are possibly made with unconsistent data : # dragged rows are already removed from layersStack # and not yet removed from model. if l != self.model().rowCount(): return # only name is editable if index1.column() != 1: return row = index1.row() stackIndex = l - row - 1 mImg.layersStack[stackIndex].name = index1.data() model.dataChanged.connect(f) for r, lay in enumerate(reversed(mImg.layersStack)): items = [] # col 0 : visibility icon if lay.visible: item_visible = QStandardItem( QIcon(":/images/resources/eye-icon.png"), "") else: item_visible = QStandardItem( QIcon(":/images/resources/eye-icon-strike.png"), "") items.append(item_visible) # col 1 : image icon (for non-adjustment layeronly) and name if len(lay.name) <= 30: name = lay.name else: name = lay.name[:28] + '...' if hasattr(lay, 'inputImg'): item_name = QStandardItem(name) else: # icon with very small dim causes QPainter error # QPixmap.fromImage bug ? smallImg = lay.resize(50 * 50) w, h = smallImg.width(), smallImg.height() if w < h / 5 or h < w / 5: item_name = QStandardItem(name) else: item_name = QStandardItem( QIcon(QPixmap.fromImage(smallImg)), name) # set tool tip to full name item_name.setToolTip(lay.name) items.append(item_name) item_mask = QStandardItem('m') items.append(item_mask) model.appendRow(items) self.setModel(model) self.horizontalHeader().hide() self.verticalHeader().hide() header = self.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.ResizeToContents) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # select active layer self.selectRow(len(mImg.layersStack) - 1 - mImg.activeLayerIndex) layerview = mImg.getActiveLayer().view # TODO added 25/11/18 if layerview is not None: layerview.show() if TABBING: layerview.raise_() self.updateForm() for item in self.img.layersStack: if hasattr(item, 'sourceIndex'): combo = item.getGraphicsForm().sourceCombo currentText = combo.currentText() combo.clear() for i, x in enumerate(self.img.layersStack): item.view.widget().sourceCombo.addItem(x.name, i) combo.setCurrentIndex(combo.findText(currentText))
def __init__(self, cModel, targetImage=None, axeSize=500, LUTSize=LUTSIZE, layer=None, parent=None, mainForm=None): """ @param cModel: color space used by colorPicker, slider2D and colorPicker @type cModel: cmConverter object @param axeSize: size of the color wheel @type axeSize: int @param targetImage: @type targetImage: imImage @param LUTSize: @type LUTSize: int @param layer: layer of targetImage linked to graphics form @type layer : QLayer @param parent: @type parent: """ super().__init__(parent=parent) self.mainForm = mainForm # used by saveLUT() # context help tag self.helpId = "LUT3DForm" self.cModel = cModel border = 20 self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(axeSize + 90, axeSize + 200) self.setAttribute(Qt.WA_DeleteOnClose) self.setBackgroundBrush(QBrush(Qt.black, Qt.SolidPattern)) self.currentHue, self.currentSat, self.currentPb = 0, 0, self.defaultColorWheelBr self.currentR, self.currentG, self.currentB = 0, 0, 0 self.size = axeSize # back links to image self.targetImage = weakProxy(targetImage) self.layer = weakProxy(layer) # currently selected grid node self.selected = None self.graphicsScene = QGraphicsScene() self.graphicsScene.options = None self.setScene(self.graphicsScene) # back to image layer self.graphicsScene.layer = weakProxy(layer) # init LUT freshLUT3D = LUT3D(None, size=LUTSize, alpha=True) self.graphicsScene.lut = freshLUT3D # init 2D slider QImg = hueSatPattern(axeSize, axeSize, cModel, bright=self.defaultColorWheelBr, border=border) self.graphicsScene.slider2D = colorChooser(cModel, QImg, target=self.targetImage, size=axeSize, border=border) self.graphicsScene.selectMarker = activeMarker.fromCross(parent=self.graphicsScene.slider2D) self.graphicsScene.selectMarker.setPos(axeSize / 2, axeSize / 2) # color wheel event handler def f1(p, r, g, b): h, s, br = self.cModel.rgb2cm(r, g, b) self.currentHue, self.currentSat, self.currentPb = h, s, br self.currentR, self.currentG, self.currentB = r, g, b self.bSliderUpdate() self.displayStatus() self.graphicsScene.slider2D.onMouseRelease = f1 self.graphicsScene.addItem(self.graphicsScene.slider2D) # Brightness slider self.bSliderHeight = 20 self.bSliderWidth = self.graphicsScene.slider2D.QImg.width() px = brightnessPattern(self.bSliderWidth, self.bSliderHeight, cModel, self.currentHue, self.currentSat).rPixmap self.graphicsScene.bSlider = QGraphicsPixmapItem(px, parent=self.graphicsScene.slider2D) self.graphicsScene.bSlider.setPos(QPointF(-border, self.graphicsScene.slider2D.QImg.height() - border)) bSliderCursor = activeMarker.fromTriangle(parent=self.graphicsScene.bSlider) bSliderCursor.setMoveRange(QRectF(0.0, bSliderCursor.size, self.graphicsScene.bSlider.pixmap().width(), 0.0)) bSliderCursor.setPos(self.graphicsScene.bSlider.pixmap().width() * self.defaultColorWheelBr, bSliderCursor.size) # cursor event handlers def f2(e, p, q): self.currentPb = p / float(self.bSliderWidth) QToolTip.showText(e.screenPos(), (str(int(self.currentPb * 100.0))), self) bSliderCursor.onMouseMove = f2 def f3(e, p, q): self.currentPb = p / float(self.bSliderWidth) self.graphicsScene.slider2D.QImg.setPb(self.currentPb) self.graphicsScene.slider2D.setPixmap(self.graphicsScene.slider2D.QImg.rPixmap) self.displayStatus() bSliderCursor.onMouseRelease = f3 # status bar offset = 60 self.graphicsScene.statusBar = QGraphicsTextItem() self.graphicsScene.statusBar.setPos(-20, axeSize + offset) self.graphicsScene.statusBar.setDefaultTextColor(QColor(255, 255, 255)) self.graphicsScene.statusBar.setPlainText('') self.graphicsScene.addItem(self.graphicsScene.statusBar) self.displayStatus() # self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) # grid self.grid = activeGrid(self.graphicsScene.lut.size, self.cModel, parent=self.graphicsScene.slider2D) self.graphicsScene.grid = self.grid # buttons pushButton1 = QbLUePushButton("Reset Grid") pushButton1.clicked.connect(self.onReset) pushButton2 = QbLUePushButton("Save LUT") pushButton2.clicked.connect(self.saveLUT) pushButton3 = QbLUePushButton("Smooth Grid") pushButton3.clicked.connect(self.onSmoothGrid) pushButton4 = QbLUePushButton('Set Mask') pushButton4.clicked.connect(self.setMask) # pushButton4 needs enabling/disabling self.pushButton4 = pushButton4 # options options1, optionNames1 = ['use image', 'use selection'], ['Use Image', 'Use Selection'] self.listWidget1 = optionsWidget(options=options1, optionNames=optionNames1, exclusive=True) """ def onSelect1(item): self.graphicsScene.options['use selection'] = item is self.listWidget1.items['use selection'] self.listWidget1.onSelect = onSelect1 """ self.listWidget1.setFocusPolicy(Qt.NoFocus) # set initial selection to 'use image' self.listWidget1.checkOption(self.listWidget1.intNames[0]) options2, optionNames2 = ['add node', 'remove node'], ['Add Node', 'Remove Node'] self.listWidget2 = optionsWidget(options=options2, optionNames=optionNames2, exclusive=True) """ def onSelect2(item): self.graphicsScene.options['add node'] = item is self.listWidget2.items['add node'] self.listWidget2.onSelect = onSelect2 """ # set initial selection to add node' self.listWidget2.checkOption(self.listWidget2.intNames[0]) options3 = ['select neighbors', 'reset removed nodes', 'show histogram', 'keep alpha'] optionNames3 = ['Select Neighbors', 'Reset Removed', 'Show Histogram', 'Keep Alpha'] self.listWidget3 = optionsWidget(options=options3, optionNames=optionNames3, exclusive=False) self.listWidget3.checkOption(self.listWidget3.intNames[0]) self.listWidget3.checkOption(self.listWidget3.intNames[1]) def onSelect3(item): option = item.internalName if option == 'show histogram': self.graphicsScene.slider2D.showTargetHist = self.graphicsScene.options[option] # .isChecked() self.graphicsScene.slider2D.updatePixmap() return if option == 'keep alpha': self.enableButtons() self.layer.applyToStack() self.listWidget3.onSelect = onSelect3 # set initial selection to 'select naighbors' item = self.listWidget3.items[options3[0]] item.setCheckState(Qt.Checked) self.graphicsScene.options = UDict((self.listWidget1.options, self.listWidget2.options, self.listWidget3.options)) # layouts hlButtons = QHBoxLayout() hlButtons.addWidget(pushButton1) hlButtons.addWidget(pushButton3) hlButtons.addWidget(pushButton2) hl = QHBoxLayout() vl1 = QVBoxLayout() vl1.addWidget(self.listWidget1) vl1.addWidget(pushButton4) hl.addLayout(vl1) hl.addWidget(self.listWidget2) hl.addWidget(self.listWidget3) vl = QVBoxLayout() for l in [hlButtons, hl]: vl.addLayout(l) # Init QWidget container for adding buttons and options to graphicsScene container = QWidget() container.setObjectName("container") container.setLayout(vl) ss = """QWidget#container{background: black}\ QListWidget{background-color: black; selection-background-color: black; border: none; font-size: 7pt}\ QListWidget::item{color: white;}\ QListWidget::item::selected{background: black; border: none}""" container.setStyleSheet(ss) for wdg in [self.listWidget1, self.listWidget2, self.listWidget3]: wdg.setMinimumWidth(wdg.sizeHintForColumn(0)) wdg.setMinimumHeight(wdg.sizeHintForRow(0)*len(wdg.items)) container.setGeometry(-offset//2, axeSize + offset - 20, axeSize + offset, 20) self.graphicsScene.addWidget(container) #container.setStyleSheet("QPushButton {color: white;}\ # QPushButton:pressed, QPushButton:hover {background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #0d5ca6, stop: 1 #2198c0);}") self.setWhatsThis( """ <b>2.5D LUT Editor</b><br> HSpB layers are slower than HSV, but usually they give better results.<br> <b>Select nodes</b> with mouse clicks on the image. Selected nodes are shown as small black circles on the color wheel.<br> <b>Modify the color</b> of a node by dragging it on the wheel. Several nodes can be moved simultaneously by grouping them.<br> <b>Group nodes</b> :<br> 1 - select them with the mouse : while pressing the mouse left button, drag a rubber band around the nodes to select;<br> 2 - next, right click any one of the selected nodes and choose group from the context menu<br> <b>unselect nodes</b> :<br> 1 - check the option Remove Node;<br> 2 - ungroup;<br> 3 - on the image, click the pixels to unselect.<br> <b>Caution</b> : Selecting nodes with the mouse is enabled only when the Color Chooser is closed.<br> Click the <b> Smooth Grid</b> button to smooth color transitions between neighbor nodes.<br> Check the <br>Keep Alpha</b> option to forward the alpha channel without modifications.<br> This option must be unchecked to build a mask from the 3D LUT.<br> """ ) # end of setWhatsThis
def targetImage(self, aTargetImage): self.__targetImage = weakProxy(aTargetImage)
def layer(self, aLayer): self.__layer = weakProxy(aLayer)
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, mainForm=None): super().__init__(parent=parent) self.defaultFilterStart = 0 self.defaultFilterEnd = 99 self.filterStart = self.defaultFilterStart self.filterEnd = self.defaultFilterEnd self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(axeSize, axeSize) self.setAttribute(Qt.WA_DeleteOnClose) # link back to image layer self.targetImage = weakProxy(targetImage) self.img = weakProxy(targetImage) self.layer = weakProxy(layer) """ # using weak ref for back links if type(layer) in weakref.ProxyTypes: self.layer = layer else: self.layer = weakref.proxy(layer) """ self.mainForm = mainForm self.kernelCategory = filterIndex.UNSHARP self.kernel = getKernel(self.kernelCategory) # options optionList, optionNames = ['Gradual Top', 'Gradual Bottom'], ['Top To Bottom', 'Bottom To Top'] filters = [blendFilterIndex.GRADUALTB, blendFilterIndex.GRADUALBT] #, blendFilterIndex.GRADUALNONE] filterDict = dict(zip(optionList, filters)) self.listWidget1 = optionsWidget(options=optionList, optionNames=optionNames, exclusive=True, changed=self.dataChanged) # set initial selection to gradual top self.listWidget1.checkOption(optionList[0]) rs = QRangeSlider() rs.setMaximumSize(16000, 10) rs.tail.setStyleSheet( 'background: white; /*qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #222, stop:1 #888); margin 3px;*/') rs.handle.setStyleSheet('background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 black, stop:1 white);') rs.head.setStyleSheet( 'background: black; /*qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #999, stop:1 #222);*/') self.sliderFilterRange = rs frLabel = QLabel('Range') # filter range done event handler def frUpdate(start, end): if self.sliderFilterRange.isSliderDown() or (start == self.filterStart and end == self.filterEnd): return self.sliderFilterRange.startValueChanged.disconnect() self.sliderFilterRange.endValueChanged.disconnect() self.sliderFilterRange.rangeDone.disconnect() self.filterStart, self.filterEnd = self.sliderFilterRange.getRange() self.dataChanged.emit() self.sliderFilterRange.startValueChanged.connect(frUpdate) # send new value as parameter self.sliderFilterRange.endValueChanged.connect(frUpdate) # send new value as parameter self.sliderFilterRange.rangeDone.connect(frUpdate) self.sliderFilterRange.startValueChanged.connect(frUpdate) # send new value as parameter self.sliderFilterRange.endValueChanged.connect(frUpdate) # send new value as parameter self.sliderFilterRange.rangeDone.connect(frUpdate) # data changed event handler def updateLayer(): #enableSliders() for key in self.listWidget1.options: if self.listWidget1.options[key]: self.kernelCategory = filterDict[key] break self.layer.applyToStack() self.layer.parentImage.onImageChanged() self.dataChanged.connect(updateLayer) # TODO 3/12/18 move to setDefaults # layout l = QVBoxLayout() l.setAlignment(Qt.AlignBottom) l.addWidget(self.listWidget1) hl8 = QHBoxLayout() hl8.addWidget(frLabel) hl8.addWidget(self.sliderFilterRange) l.addLayout(hl8) l.setContentsMargins(20, 0, 20, 25) # left, top, right, bottom self.setLayout(l) self.setWhatsThis( """<b>Gradual neutral filter.</b><br> It mimics the classical gradual gray filter often used by photographers to darken the sky.<br> To control the regions of maximum and minimum intensities use the Range slider. """ ) # end setWhatsThis self.setDefaults()
def __init__(self, targetImage=None, size=200, layer=None, parent=None, mainForm=None): super().__init__(parent=parent) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.setMinimumSize(size, 100) self.setAttribute(Qt.WA_DeleteOnClose) self.targetImage = weakProxy(targetImage) self.img = weakProxy(targetImage) # link back to image layer # using weak ref for back links self.layer = layer if (layer is None or type(layer) in weakref.ProxyTypes) else weakref.proxy(layer) self.Label_Hist = QLabel() self.Label_Hist.setScaledContents(True) self.Label_Hist.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) #self.setStyleSheet("QListWidget{background-color: rgb(200,200,200); selection-background-color: rgb(200,200,200); border: 0px; font-size: 9px}") self.setStyleSheet("QListWidget{border: 0px; font-size: 12px}") # options. We want the items displayed horizontally, so we use 2 lists options1, optionNames1 = ['Original Image'], ['Source'] self.listWidget1 = optionsWidget(options=options1, optionNames=optionNames1, exclusive=False) self.listWidget1.item(0).setSizeHint(QSize(100, 10)) self.listWidget1.setMaximumSize( self.listWidget1.sizeHintForColumn(0) + 5, self.listWidget1.sizeHintForRow(0) * len(options1)) options2 = ['Color Chans'] self.listWidget2 = optionsWidget(options=options2, exclusive=False) self.listWidget2.item(0).setSizeHint(QSize(100, 10)) self.listWidget2.setMaximumSize( self.listWidget2.sizeHintForColumn(0) + 5, self.listWidget2.sizeHintForRow(0) * len(options2)) # default: show color hists self.listWidget2.item(0).setCheckState(Qt.Checked) self.options = {option: True for option in options1 + options2} def onSelect1(item): self.options[options1[0]] = item.checkState() is Qt.Checked try: self.targetImage.onImageChanged() self.Label_Hist.update() except AttributeError: return def onSelect2(item): self.options[options2[0]] = item.checkState() is Qt.Checked try: self.targetImage.onImageChanged() self.Label_Hist.update() except AttributeError: return self.listWidget1.onSelect = onSelect1 self.listWidget2.onSelect = onSelect2 # layout h = QHBoxLayout() h.addWidget(self.listWidget1) h.addWidget(self.listWidget2) l = QVBoxLayout() l.setAlignment(Qt.AlignTop) l.addWidget(self.Label_Hist) l.addLayout(h) l.setContentsMargins(0, 0, 0, 2) # left, top, right, bottom self.setLayout(l)
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, mainForm=None): super(transForm, self).__init__(parent=parent) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(axeSize, axeSize) self.setAttribute(Qt.WA_DeleteOnClose) # back links to image self.targetImage = weakProxy(targetImage) self.img = weakProxy(targetImage) self.layer = weakProxy(layer) self.mainForm = mainForm # options optionList1, optionNames1 = ['Free', 'Rotation', 'Translation'], [ 'Free Transformation', 'Rotation', 'Translation' ] self.listWidget1 = optionsWidget(options=optionList1, optionNames=optionNames1, exclusive=True) optionList2, optionNames2 = ['Transparent' ], ['Set Transparent Pixels To Black'] self.listWidget2 = optionsWidget(options=optionList2, optionNames=optionNames2, exclusive=False) self.options = UDict( (self.listWidget1.options, self.listWidget2.options)) # set initial selection to Perspective self.listWidget1.checkOption(optionList1[0]) # option changed handler def g(item): l = self.layer l.tool.setBaseTransform() #self.layer.tool.setVisible(True) l.applyToStack() l.parentImage.onImageChanged() self.listWidget1.onSelect = g self.listWidget2.onSelect = g pushButton1 = QPushButton(' Reset Transformation ') pushButton1.adjustSize() def f(): self.layer.tool.resetTrans() pushButton1.clicked.connect(f) # layout l = QVBoxLayout() l.setAlignment(Qt.AlignTop) l.addWidget(self.listWidget1) l.addWidget(self.listWidget2) hl = QHBoxLayout() hl.setAlignment(Qt.AlignHCenter) hl.addWidget(pushButton1) l.addLayout(hl) self.setLayout(l) self.adjustSize() self.setWhatsThis(""" <b>Geometric transformation :</b><br> Choose a transformation type and drag either corner of the image using the small square red buttons.<br> Ctrl+Alt+Drag to change the <b>initial positions</b> of buttons. """)
def __init__(self, layer=None): super(segmentForm, self).__init__() self.setWindowTitle('grabcut') self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(200, 200) self.setAttribute(Qt.WA_DeleteOnClose) # link back to image layer self.layer = weakProxy(layer) pushButton = QPushButton('apply') # button slot def f(): self.layer.noSegment = False self.layer.applyToStack() window.label.img.onImageChanged() # do manual segmentation only layer.noSegment = True pushButton.clicked.connect(f) pushButton1 = QPushButton('Reset') pushButton1.clicked.connect(lambda : self.reset()) self.spBox = QSpinBox() self.spBox.setRange(1,10) # spBox Slot def f2(iterCount): self.spBox.valueChanged.disconnect() self.dataChanged.emit() self.spBox.valueChanged.connect(f2) self.spBox.valueChanged.connect(f2) spBoxLabel = QLabel() spBoxLabel.setText('Iterations') self.spBox1 = QSpinBox() self.spBox1.setRange(0, 20) spBox1Label = QLabel() spBox1Label.setText('Contour Margin') # spBox1 slot def f1(margin): self.spBox1.valueChanged.disconnect() self.dataChanged.emit() self.spBox1.valueChanged.connect(f1) self.spBox1.valueChanged.connect(f1) # options optionList1, optionNames1 = ['Clipping Layer'], ['Clipping Layer'] self.listWidget1 = optionsWidget(options=optionList1, optionNames=optionNames1, exclusive=False) self.options = self.listWidget1.options # option changed slot def g(item): self.layer.isClipping = self.options['Clipping Layer'] self.layer.applyToStack() self.layer.parentImage.onImageChanged() self.listWidget1.onSelect = g # attributes initialized in setDefaults, declared here # for the sake of correctness self.start = None self.nbIter = None self.contourMargin = None # layout hLay = QHBoxLayout() hLay.addWidget(spBoxLabel) hLay.addWidget(self.spBox) hLay.addStretch(1) hLay1 = QHBoxLayout() hLay1.addWidget(spBox1Label) hLay1.addWidget(self.spBox1) hLay1.addStretch(1) h2 = QHBoxLayout() h2.addWidget(self.listWidget1) vLay = QVBoxLayout() vLay.setAlignment(Qt.AlignTop) vLay.setContentsMargins(20, 8, 20, 25) # left, top, right, bottom vLay.addLayout(hLay) vLay.addLayout(hLay1) vLay.addLayout(h2) h3 = QHBoxLayout() h3.addWidget(pushButton) h3.addWidget(pushButton1) vLay.addLayout(h3) self.setLayout(vLay) self.setDefaults() self.setWhatsThis( """ <b>Object extraction</b><br> Select the object to extract with the rectangle Marquee Tool. Next, click the Apply button.<br> Correct (roughly) if needed the foreground (FG) and the background (BG) regions using the FG and BG tools (Ctrl to undo) and click again the Apply button.<br> To get a smoother contour increase the value of the Contour Margin and click the Apply Button.<br> By default the mask is displayed as a color mask. To view it as an opacity mask, right click on the Segmentation layer row in the right pane and check Enable Mask As > Opacity Mask in the context menu. Use the same context menu to copy/paste the object to a new image layer or the mask to another layer.<br> """ ) # end setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, mainForm=None): super().__init__(parent=parent) defaultRadius = 10 defaultTone = 100.0 defaultAmount = 50.0 self.radius = defaultRadius self.tone = defaultTone self.amount = defaultAmount self.targetImage = targetImage self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(axeSize, axeSize) self.setAttribute(Qt.WA_DeleteOnClose) self.img = targetImage # link back to image layer self.layer = weakProxy(layer) """ # using weak ref for back links if type(layer) in weakref.ProxyTypes: self.layer = layer else: self.layer = weakref.proxy(layer) """ self.mainForm = mainForm self.kernelCategory = filterIndex.UNSHARP # options self.optionList = [ 'Unsharp Mask', 'Sharpen', 'Gaussian Blur', 'Surface Blur' ] filters = [ filterIndex.UNSHARP, filterIndex.SHARPEN, filterIndex.BLUR1, filterIndex.SURFACEBLUR ] self.filterDict = dict(zip(self.optionList, filters)) self.listWidget1 = optionsWidget(options=self.optionList, exclusive=True, changed=self.dataChanged) # set initial selection to unsharp mask item = self.listWidget1.checkOption(self.optionList[0]) # sliders self.sliderRadius = QbLUeSlider(Qt.Horizontal) #self.sliderRadius.setTickPosition(QSlider.TicksBelow) self.sliderRadius.setRange(1, 50) self.sliderRadius.setSingleStep(1) self.radiusLabel = QLabel() self.radiusLabel.setMaximumSize(150, 30) self.radiusLabel.setText("Radius") self.radiusValue = QLabel() font = self.radiusValue.font() metrics = QFontMetrics(font) w = metrics.width("1000 ") h = metrics.height() self.radiusValue.setMinimumSize(w, h) self.radiusValue.setMaximumSize(w, h) self.sliderAmount = QbLUeSlider(Qt.Horizontal) #self.sliderAmount.setTickPosition(QSlider.TicksBelow) self.sliderAmount.setRange(0, 100) self.sliderAmount.setSingleStep(1) self.amountLabel = QLabel() self.amountLabel.setMaximumSize(150, 30) self.amountLabel.setText("Amount") self.amountValue = QLabel() font = self.radiusValue.font() metrics = QFontMetrics(font) w = metrics.width("1000 ") h = metrics.height() self.amountValue.setMinimumSize(w, h) self.amountValue.setMaximumSize(w, h) self.toneValue = QLabel() self.toneLabel = QLabel() self.toneLabel.setMaximumSize(150, 30) self.toneLabel.setText("Sigma") self.sliderTone = QbLUeSlider(Qt.Horizontal) #self.sliderTone.setTickPosition(QSlider.TicksBelow) self.sliderTone.setRange(0, 100) self.sliderTone.setSingleStep(1) font = self.radiusValue.font() metrics = QFontMetrics(font) w = metrics.width("1000 ") h = metrics.height() self.toneValue.setMinimumSize(w, h) self.toneValue.setMaximumSize(w, h) # layout l = QVBoxLayout() l.setAlignment(Qt.AlignBottom) l.addWidget(self.listWidget1) hl = QHBoxLayout() hl.addWidget(self.radiusLabel) hl.addWidget(self.radiusValue) hl.addWidget(self.sliderRadius) l.addLayout(hl) hl = QHBoxLayout() hl.addWidget(self.amountLabel) hl.addWidget(self.amountValue) hl.addWidget(self.sliderAmount) l.addLayout(hl) hl = QHBoxLayout() hl.addWidget(self.toneLabel) hl.addWidget(self.toneValue) hl.addWidget(self.sliderTone) l.addLayout(hl) l.setContentsMargins(20, 0, 20, 25) # left, top, right, bottom self.setLayout(l) # value changed event handler def sliderUpdate(): self.radiusValue.setText(str('%d ' % self.sliderRadius.value())) self.amountValue.setText(str('%d ' % self.sliderAmount.value())) self.toneValue.setText(str('%d ' % self.sliderTone.value())) # value done event handler def formUpdate(): sR, sA, sT = self.sliderRadius.isEnabled( ), self.sliderAmount.isEnabled(), self.sliderTone.isEnabled() self.sliderRadius.setEnabled(False) self.sliderAmount.setEnabled(False) self.sliderTone.setEnabled(False) self.tone = self.sliderTone.value() self.radius = self.sliderRadius.value() self.amount = self.sliderAmount.value() sliderUpdate() self.dataChanged.emit() self.sliderRadius.setEnabled(sR) self.sliderAmount.setEnabled(sA) self.sliderTone.setEnabled(sT) self.sliderRadius.valueChanged.connect(sliderUpdate) self.sliderRadius.sliderReleased.connect(formUpdate) self.sliderAmount.valueChanged.connect(sliderUpdate) self.sliderAmount.sliderReleased.connect(formUpdate) self.sliderTone.valueChanged.connect(sliderUpdate) self.sliderTone.sliderReleased.connect(formUpdate) self.dataChanged.connect(self.updateLayer) # init self.sliderRadius.setValue(defaultRadius) self.sliderAmount.setValue(defaultAmount) self.sliderTone.setValue(defaultTone) self.enableSliders() sliderUpdate() self.setWhatsThis(""" <b>Unsharp Mask</b> and <b>Sharpen Mask</b> are used to sharpen an image.<br> <b>Gaussian Blur</b> and <b>Surface Blur</b> are used to blur an image.<br> In contrast to Gaussian Blur, Surface Blur preserves edges and reduces noise.<br> It is possible to <b>limit the effect of a filter to a rectangular region of the image</b> by drawing a selection rectangle on the layer with the marquee tool.<br> Ctrl-Click to <b>clear the selection</b><br> """) # end setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, mainForm=None): super().__init__(parent=parent) self.tempCorrection = 6500 self.tintCorrection = 1.0 self.setStyleSheet( 'QRangeSlider * {border: 0px; padding: 0px; margin: 0px}') self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(axeSize, axeSize) self.setAttribute(Qt.WA_DeleteOnClose) # link back to image layer self.layer = weakProxy(layer) """ # using weak ref for back links if type(layer) in weakref.ProxyTypes: self.layer = layer else: self.layer = weakref.proxy(layer) """ self.defaultTemp = sRGBWP # ref temperature D65 self.defaultTint = 0 # options optionList, optionNames = ['Photo Filter', 'Chromatic Adaptation' ], ['Photo Filter', 'Chromatic Adaptation'] self.listWidget1 = optionsWidget( options=optionList, optionNames=optionNames, exclusive=True, changed=lambda: self.dataChanged.emit()) self.listWidget1.checkOption(self.listWidget1.intNames[1]) self.options = self.listWidget1.options # temp slider self.sliderTemp = QbLUeSlider(Qt.Horizontal) self.sliderTemp.setStyleSheet( QbLUeSlider.bLueSliderDefaultIColorStylesheet) self.sliderTemp.setRange( 17, 100 ) # 250) # valid range for spline approximation is 1667..25000, cf. colorConv.temperature2xyWP self.sliderTemp.setSingleStep(1) tempLabel = QbLUeLabel() tempLabel.setMaximumSize(150, 30) tempLabel.setText("Color temperature") tempLabel.doubleClicked.connect(lambda: self.sliderTemp.setValue( self.temp2Slider(self.defaultTemp))) self.tempValue = QLabel() font = self.tempValue.font() metrics = QFontMetrics(font) w = metrics.width("0000") h = metrics.height() self.tempValue.setMinimumSize(w, h) self.tempValue.setMaximumSize(w, h) self.tempValue.setText( str("{:d}".format(self.sliderTemp2User(self.sliderTemp.value())))) # tint slider self.sliderTint = QbLUeSlider(Qt.Horizontal) self.sliderTint.setStyleSheet( QbLUeSlider.bLueSliderDefaultMGColorStylesheet) self.sliderTint.setRange( 0, 100 ) # 250) # valid range for spline approximation is 1667..25000, cf. colorConv.temperature2xyWP self.sliderTint.setSingleStep(1) tintLabel = QbLUeLabel() tintLabel.setMaximumSize(150, 30) tintLabel.setText("Tint") tintLabel.doubleClicked.connect(lambda: self.sliderTint.setValue( self.tint2Slider(self.defaultTint))) self.tintValue = QLabel() font = self.tintValue.font() metrics = QFontMetrics(font) w = metrics.width("0000") h = metrics.height() self.tintValue.setMinimumSize(w, h) self.tintValue.setMaximumSize(w, h) self.tintValue.setText( str("{:d}".format(self.sliderTint2User(self.sliderTint.value())))) # temp change event handler def tempUpdate(value): self.tempValue.setText( str("{:d}".format(self.sliderTemp2User(value)))) # move not yet terminated or values not modified if self.sliderTemp.isSliderDown() or self.slider2Temp( value) == self.tempCorrection: return self.sliderTemp.valueChanged.disconnect() self.sliderTemp.sliderReleased.disconnect() self.tempCorrection = self.slider2Temp(value) self.dataChanged.emit() self.sliderTemp.valueChanged.connect(tempUpdate) self.sliderTemp.sliderReleased.connect( lambda: tempUpdate(self.sliderTemp.value())) # tint change event handler def tintUpdate(value): self.tintValue.setText( str("{:d}".format(self.sliderTint2User(value)))) # move not yet terminated or values not modified if self.sliderTint.isSliderDown() or self.slider2Tint( value) == self.tintCorrection: return self.sliderTint.valueChanged.disconnect() self.sliderTint.sliderReleased.disconnect() self.tintCorrection = self.slider2Tint(value) self.dataChanged.emit() self.sliderTint.valueChanged.connect(tintUpdate) self.sliderTint.sliderReleased.connect( lambda: tintUpdate(self.sliderTint.value())) self.sliderTemp.valueChanged.connect(tempUpdate) self.sliderTemp.sliderReleased.connect( lambda: tempUpdate(self.sliderTemp.value())) self.sliderTint.valueChanged.connect(tintUpdate) self.sliderTint.sliderReleased.connect( lambda: tintUpdate(self.sliderTint.value())) # layout l = QVBoxLayout() l.setAlignment(Qt.AlignTop) l.addWidget(self.listWidget1) l.addWidget(tempLabel) hl = QHBoxLayout() hl.addWidget(self.tempValue) hl.addWidget(self.sliderTemp) l.addLayout(hl) l.addWidget(tintLabel) hl1 = QHBoxLayout() hl1.addWidget(self.tintValue) hl1.addWidget(self.sliderTint) l.addLayout(hl1) self.setLayout(l) self.adjustSize() self.dataChanged.connect( self.updateLayer) # TODO move to setDefaults 3/12/18 self.setStyleSheet("QListWidget, QLabel {font : 7pt;}") self.setDefaults() self.setWhatsThis("""<b>Color Temperature</b><br> <b>Photo Filter</b> uses the multiply blending mode to mimic a color filter in front of the camera lens.<br> <b>Chromatic Adaptation</b> uses multipliers in the linear sRGB color space to adjust <b>temperature</b> and <b>tint</b>. """) # end of setWhatsThis
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, mainForm=None): super().__init__(parent=parent) self.setStyleSheet( 'QRangeSlider * {border: 0px; padding: 0px; margin: 0px}') self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) self.setMinimumSize(axeSize, axeSize + 300) # +300 to prevent scroll bars in list Widgets self.setAttribute(Qt.WA_DeleteOnClose) # links back to image : using weak refs self.targetImage = weakProxy(targetImage) """ if type(targetImage) in weakref.ProxyTypes: self.targetImage = targetImage else: self.targetImage = weakref.proxy(targetImage) """ #self.targetImage = targetImage self.layer = weakProxy(layer) """ if type(layer) in weakref.ProxyTypes: self.layer = layer else: self.layer = weakref.proxy(layer) """ ####################################### # Libraw correspondences: # rgb_xyz_matrix is libraw cam_xyz # camera_whitebalance is libraw cam_mul # daylight_whitebalance is libraw pre_mul # dng correspondences: # ASSHOTNEUTRAL tag value is (X,Y,Z) = 1 / rawpyObj.camera_whitebalance ########################################## rawpyObj = layer.parentImage.rawImage # constants and as shot values self.XYZ2CameraMatrix = rawpyObj.rgb_xyz_matrix[:3, :] self.XYZ2CameraInverseMatrix = np.linalg.inv(self.XYZ2CameraMatrix) # initial post processing multipliers (as shot) m1, m2, m3, m4 = rawpyObj.camera_whitebalance self.asShotMultipliers = ( m1 / m2, 1.0, m3 / m2, m4 / m2 ) # normalization is mandatory : for nef files white balance is around 256 self.asShotTemp, self.asShotTint = multipliers2TemperatureAndTint( *1 / np.array(self.asShotMultipliers[:3]), self.XYZ2CameraMatrix) self.rawMultipliers = self.asShotMultipliers #rawpyObj.camera_whitebalance # = 1/(dng ASSHOTNEUTRAL tag value) self.sampleMultipliers = False self.samples = [] ######################################## # XYZ-->Camera conversion matrix: # Last row is zero for RGB cameras (cf. rawpy and libraw docs). # type ndarray, shape (4,3) ######################################### # attributes initialized in setDefaults, declared here for the sake of correctness self.tempCorrection, self.tintCorrection, self.expCorrection, self.highCorrection,\ self.contCorrection, self.satCorrection, self.brCorrection = [None] * 7 # contrast spline vie (initialized by setContrastSpline) self.contrastForm = None # tone spline view (initialized by setToneSpline) self.toneForm = None # dock containers for contrast and tome forms self.dockC, self.dockT = None, None # options optionList0, optionNames0 = ['Auto Brightness', 'Preserve Highlights' ], ['Auto Expose', 'Preserve Highlights'] optionList1, optionNames1 = ['Auto WB', 'Camera WB', 'User WB' ], ['Auto', 'Camera (As Shot)', 'User'] optionList2, optionNames2 = [ 'cpLookTable', 'cpToneCurve', 'manualCurve' ], [ 'Use Camera Profile Look Table', 'Show Tone Curves', 'Show Contrast Curve' ] self.listWidget1 = optionsWidget( options=optionList0, optionNames=optionNames0, exclusive=False, changed=lambda: self.dataChanged.emit(1)) #self.listWidget1.checkOption(self.listWidget1.intNames[0]) #self.listWidget1.checkOption(self.listWidget1.intNames[1]) self.listWidget2 = optionsWidget( options=optionList1, optionNames=optionNames1, exclusive=True, changed=lambda: self.dataChanged.emit(1)) #self.listWidget2.checkOption(self.listWidget2.intNames[1]) self.listWidget3 = optionsWidget( options=optionList2, optionNames=optionNames2, exclusive=False, changed=lambda: self.dataChanged.emit(2)) self.options = UDict( (self.listWidget1.options, self.listWidget2.options, self.listWidget3.options)) # display the 'as shot' temperature item = self.listWidget2.item(1) item.setText(item.text() + ' : %d' % self.asShotTemp) # temperature slider self.sliderTemp = QbLUeSlider(Qt.Horizontal) self.sliderTemp.setStyleSheet( QbLUeSlider.bLueSliderDefaultColorStylesheet) self.sliderTemp.setRange( 0, 100) # TODO 130 changed to 100 12/11/18 validate self.sliderTemp.setSingleStep(1) self.tempLabel = QLabel() self.tempLabel.setText("Temp") self.tempValue = QLabel() font = self.tempValue.font() metrics = QFontMetrics(font) w = metrics.width("10000") h = metrics.height() self.tempValue.setMinimumSize(w, h) self.tempValue.setMaximumSize(w, h) self.tempValue.setText( str("{:.0f}".format(self.slider2Temp(self.sliderTemp.value())))) self.sliderTemp.valueChanged.connect( self.tempUpdate) # send new value as parameter self.sliderTemp.sliderReleased.connect(lambda: self.tempUpdate( self.sliderTemp.value())) # signal has no parameter # tint slider self.sliderTint = QbLUeSlider(Qt.Horizontal) # self.sliderTint.setStyleSheet(self.sliderTint.styleSheet()+'QSlider::groove:horizontal {background: red;}') self.sliderTint.setStyleSheet( QbLUeSlider.bLueSliderDefaultIMGColorStylesheet) self.sliderTint.setRange(0, 150) self.sliderTint.setSingleStep(1) self.tintLabel = QLabel() self.tintLabel.setText("Tint") self.tintValue = QLabel() font = self.tempValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.tintValue.setMinimumSize(w, h) self.tintValue.setMaximumSize(w, h) self.tintValue.setText( str("{:.0f}".format(self.sliderTint2User( self.sliderTint.value())))) self.sliderTint.valueChanged.connect(self.tintUpdate) self.sliderTint.sliderReleased.connect(lambda: self.tintUpdate( self.sliderTint.value())) # signal has no parameter) ###################### # From libraw and dcraw sources: # Exposure and brightness are curve transformations. # Exposure curve is y = alpha*x, with cubic root ending; it is applied before demosaicing. # Brightness is (similar to) y = x**alpha and part of gamma transformation from linear sRGB to RGB. # Exposure and brightness both dilate the histogram towards highlights. # Exposure dilatation is uniform (homothety), brightness dilataion is # maximum for the midtones and the highlghts are preserved. # As a consequence, normal workflow begins with the adjustment of exposure, # to fill the entire range of the histogram and to adjust the highlights. Next, # one adjusts the brightness to put the midtones at the level we want them to be. # Cf. https://www.cambridgeincolour.com/forums/thread653.htm ##################### # profile combo self.dngDict = self.setCameraProfilesCombo() # cameraProfilesCombo index changed event handler def cameraProfileUpdate(value): self.dngDict = self.cameraProfilesCombo.itemData(value) if self.options['cpToneCurve']: toneCurve = dngProfileToneCurve( self.dngDict.get('ProfileToneCurve', [])) self.toneForm.baseCurve = [ QPointF(x * axeSize, -y * axeSize) for x, y in zip(toneCurve.dataX, toneCurve.dataY) ] self.toneForm.update() # recompute as shot temp and tint using new profile self.asShotTemp, self.asShotTint = multipliers2TemperatureAndTint( *1 / np.array(self.asShotMultipliers[:3]), self.XYZ2CameraMatrix, self.dngDict) # display updated as shot temp item = self.listWidget2.item(1) item.setText(item.text().split(":")[0] + ': %d' % self.asShotTemp) # invalidate cache self.layer.bufCache_HSV_CV32 = None self.dataChanged.emit(2) # 2 = no postprocessing self.cameraProfilesCombo.currentIndexChanged.connect( cameraProfileUpdate) # denoising combo self.denoiseCombo = QComboBox() items = OrderedDict([('Off', 0), ('Medium', 1), ('Full', 2)]) for key in items: self.denoiseCombo.addItem(key, items[key]) # denoiseCombo index changed event handler def denoiseUpdate(value): self.denoiseValue = self.denoiseCombo.itemData(value) self.dataChanged.emit(1) self.denoiseCombo.currentIndexChanged.connect(denoiseUpdate) # overexposed area restoration self.overexpCombo = QComboBox() items = OrderedDict([('Clip', 0), ('Ignore', 1), ('Blend', 2), ('Reconstruct', 3)]) for key in items: self.overexpCombo.addItem(key, items[key]) # overexpCombo index changed event handler def overexpUpdate(value): self.overexpValue = self.overexpCombo.itemData(value) self.dataChanged.emit(1) self.overexpCombo.currentIndexChanged.connect(overexpUpdate) # exp slider self.sliderExp = QbLUeSlider(Qt.Horizontal) self.sliderExp.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet) self.sliderExp.setRange(0, 100) self.sliderExp.setSingleStep(1) self.expLabel = QLabel() self.expLabel.setText("Exp.") self.expValue = QLabel() font = self.expValue.font() metrics = QFontMetrics(font) w = metrics.width("+1.0") h = metrics.height() self.expValue.setMinimumSize(w, h) self.expValue.setMaximumSize(w, h) self.expValue.setText( str("{:.1f}".format(self.slider2Exp(self.sliderExp.value())))) # exp done event handler def expUpdate(value): self.expValue.setText( str("{:+.1f}".format( self.sliderExp2User(self.sliderExp.value())))) # move not yet terminated or value not modified if self.sliderExp.isSliderDown() or self.slider2Exp( value) == self.expCorrection: return self.sliderExp.valueChanged.disconnect() self.sliderExp.sliderReleased.disconnect() # rawpy: expCorrection range is -2.0...3.0, boiling down to exp_shift range 2**(-2)=0.25...2**3=8.0 self.expCorrection = self.slider2Exp(self.sliderExp.value()) self.dataChanged.emit(1) self.sliderExp.valueChanged.connect( expUpdate) # send new value as parameter self.sliderExp.sliderReleased.connect(lambda: expUpdate( self.sliderExp.value())) # signal has no parameter self.sliderExp.valueChanged.connect( expUpdate) # send new value as parameter self.sliderExp.sliderReleased.connect(lambda: expUpdate( self.sliderExp.value())) # signal has no parameter # brightness slider brSlider = QbLUeSlider(Qt.Horizontal) brSlider.setRange(1, 101) self.sliderExp.setSingleStep(1) brSlider.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet) self.sliderBrightness = brSlider brLabel = QLabel() brLabel.setText("Bright.") self.brValue = QLabel() font = self.expValue.font() metrics = QFontMetrics(font) w = metrics.width("+99") h = metrics.height() self.brValue.setMinimumSize(w, h) self.brValue.setMaximumSize(w, h) self.brValue.setText( str("{:+d}".format( int(self.brSlider2User(self.sliderBrightness.value()))))) # brightness done event handler def brUpdate(value): self.brValue.setText( str("{:+d}".format( int(self.brSlider2User(self.sliderBrightness.value()))))) # move not yet terminated or value not modified if self.sliderBrightness.isSliderDown() or self.slider2Br( value) == self.brCorrection: return self.sliderBrightness.valueChanged.disconnect() self.sliderBrightness.sliderReleased.disconnect() self.brCorrection = self.slider2Br(self.sliderBrightness.value()) self.dataChanged.emit(1) self.sliderBrightness.sliderReleased.connect( lambda: brUpdate(self.sliderBrightness.value())) self.sliderBrightness.valueChanged.connect( brUpdate) # send new value as parameter self.sliderBrightness.valueChanged.connect( brUpdate) # send new value as parameter self.sliderBrightness.sliderReleased.connect( lambda: brUpdate(self.sliderBrightness.value())) # contrast slider self.sliderCont = QbLUeSlider(Qt.Horizontal) self.sliderCont.setStyleSheet( QbLUeSlider.bLueSliderDefaultBWStylesheet) self.sliderCont.setRange(0, 20) self.sliderCont.setSingleStep(1) self.contLabel = QLabel() self.contLabel.setText("Cont.") self.contValue = QLabel() font = self.contValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.contValue.setMinimumSize(w, h) self.contValue.setMaximumSize(w, h) self.contValue.setText( str("{:.0f}".format(self.slider2Cont(self.sliderCont.value())))) # cont done event handler def contUpdate(value): self.contValue.setText( str("{:.0f}".format(self.slider2Cont( self.sliderCont.value())))) # move not yet terminated or value not modified if self.sliderCont.isSliderDown() or self.slider2Cont( value) == self.tempCorrection: return self.sliderCont.valueChanged.disconnect() self.sliderCont.sliderReleased.disconnect() self.contCorrection = self.slider2Cont(self.sliderCont.value()) self.contValue.setText(str("{:+d}".format(self.contCorrection))) # force to recalculate the spline self.layer.autoSpline = True self.dataChanged.emit( 3) # no postprocessing and no camera profile stuff self.sliderCont.valueChanged.connect( contUpdate) # send new value as parameter self.sliderCont.sliderReleased.connect(lambda: contUpdate( self.sliderCont.value())) # signal has no parameter self.sliderCont.valueChanged.connect( contUpdate) # send new value as parameter self.sliderCont.sliderReleased.connect(lambda: contUpdate( self.sliderCont.value())) # signal has no parameter # saturation slider self.sliderSat = QbLUeSlider(Qt.Horizontal) self.sliderSat.setStyleSheet( QbLUeSlider.bLueSliderDefaultColorStylesheet) self.sliderSat.setRange(0, 100) self.sliderSat.setSingleStep(1) satLabel = QLabel() satLabel.setText("Sat.") self.satValue = QLabel() font = self.satValue.font() metrics = QFontMetrics(font) w = metrics.width("+10") h = metrics.height() self.satValue.setMinimumSize(w, h) self.satValue.setMaximumSize(w, h) self.satValue.setText( str("{:+d}".format(self.slider2Sat(self.sliderSat.value())))) """sat done event handler""" def satUpdate(value): self.satValue.setText( str("{:+d}".format(self.slider2Sat(self.sliderSat.value())))) # move not yet terminated or value not modified if self.sliderSat.isSliderDown() or self.slider2Sat( value) == self.satCorrection: return self.sliderSat.valueChanged.disconnect() self.sliderSat.sliderReleased.disconnect() self.satCorrection = self.slider2Sat(self.sliderSat.value()) self.dataChanged.emit( 3) # no post processing and no camera profile stuff self.sliderSat.valueChanged.connect( satUpdate) # send new value as parameter self.sliderSat.sliderReleased.connect(lambda: satUpdate( self.sliderSat.value())) # signal has no parameter self.sliderSat.valueChanged.connect( satUpdate) # send new value as parameter self.sliderSat.sliderReleased.connect(lambda: satUpdate( self.sliderSat.value())) # signal has no parameter # self.dataChanged.connect(self.updateLayer) # TODO 30/10/18 moved to base class self.setStyleSheet("QListWidget, QLabel {font : 7pt;}") # layout l = QVBoxLayout() l.addWidget(self.listWidget3) hl01 = QHBoxLayout() hl01.addWidget(QLabel('Camera Profile')) hl01.addWidget(self.cameraProfilesCombo) l.addLayout(hl01) hl0 = QHBoxLayout() hl0.addWidget(QLabel('Denoising')) hl0.addWidget(self.denoiseCombo) l.addLayout(hl0) hl00 = QHBoxLayout() hl00.addWidget(QLabel('Overexp. Restoration')) hl00.addWidget(self.overexpCombo) l.addLayout(hl00) hl1 = QHBoxLayout() hl1.addWidget(self.expLabel) hl1.addWidget(self.expValue) hl1.addWidget(self.sliderExp) l.addLayout(hl1) hl8 = QHBoxLayout() hl8.addWidget(brLabel) hl8.addWidget(self.brValue) hl8.addWidget(self.sliderBrightness) l.addLayout(hl8) l.addWidget(self.listWidget1) self.listWidget2.setStyleSheet( "QListWidget {border: 0px;} QListWidget::item {border: 0px; padding-left: 20px;}" ) vl1 = QVBoxLayout() vl1.addWidget(QLabel('White Balance')) vl1.addWidget(self.listWidget2) gb1 = QGroupBox() gb1.setStyleSheet( "QGroupBox {border: 1px solid gray; border-radius: 4px}") hl2 = QHBoxLayout() hl2.addWidget(self.tempLabel) hl2.addWidget(self.tempValue) hl2.addWidget(self.sliderTemp) hl3 = QHBoxLayout() hl3.addWidget(self.tintLabel) hl3.addWidget(self.tintValue) hl3.addWidget(self.sliderTint) vl1.addLayout(hl2) vl1.addLayout(hl3) gb1.setLayout(vl1) l.addWidget(gb1) hl4 = QHBoxLayout() hl4.addWidget(self.contLabel) hl4.addWidget(self.contValue) hl4.addWidget(self.sliderCont) hl7 = QHBoxLayout() hl7.addWidget(satLabel) hl7.addWidget(self.satValue) hl7.addWidget(self.sliderSat) # separator sep = QFrame() sep.setFrameShape(QFrame.HLine) sep.setFrameShadow(QFrame.Sunken) l.addWidget(sep) l.addLayout(hl4) l.addLayout(hl7) #l.addLayout(hl5) l.addStretch(1) self.setLayout(l) self.adjustSize() self.setDefaults() #self.lookTable = dngProfileLookTable(self.dngDict) self.setWhatsThis("""<b>Development of raw files</b><br> <b>Default settings</b> are a good starting point.<br> A <b>Tone Curve</b> is applied to the raw image prior to postprocessing.<br> Il can be edited by checking the option <b>Show Tone Curve</b>; this option works best with manual exposure.<br> <b>Contrast</b> correction is based on an automatic algorithm well suited to multi-mode histograms.<br> <b>Brightness, Contrast</b> and <b>Saturation</b> levels</b> are adjustable with the correponding sliders.<br> The <b>Contrast Curve</b> can be edited manually by checking the option <b>Show Contrast Curve</b>.<br> Uncheck <b>Auto Expose</b> to adjust the exposure manually.<br> The <b>OverExp. Rest.</b> slider controls the mode of restoration of overexposed areas. Valid values are 0 to 3 (0=clip;1=unclip;2=blend;3=rebuild); (with Auto Exposed checked the mode is clip).<br> <b> """) # end of setWhatsThis
def __init__(self, *args, **kwargs): ############################################################ # Signals # Making QLayer inherit from QObject leads to # a buggy behavior of hasattr and getattr. # So, we don't add signals as first level class attributes. # Instead, we use instances of ad hoc signal containers (cf. utils.py) self.visibilityChanged = baseSignal_bool() self.colorPicked = baseSignal_Int2() ########################################################### # when a geometric transformation is applied to the whole image # each layer must be replaced with a transformed layer, recorded in tLayer # and tLayer.parentLayer keeps a reference to the original layer. self.tLayer = self self.parentLayer = self self.modified = False self.name = 'noname' self.visible = True # add signal instance (layer visibility change,..) #self.signals = baseSignals() self.isClipping = False self.role = kwargs.pop('role', '') self.tool = None # back link to parent image parentImage = kwargs.pop('parentImage', None) self.parentImage = weakProxy(parentImage) """ if type(parentImage) in weakref.ProxyTypes: # TODO 21/11/18 added weakref for back link self.parentImage = parentImage else: self.parentImage = weakref.proxy(parentImage) """ #self.parentImage = kwargs.pop('parentImage', None) # layer opacity is used by QPainter operations. # Its value must be in the range 0.0...1.0 self.opacity = 1.0 self.compositionMode = QPainter.CompositionMode_SourceOver # The next two attributes are used by adjustment layers only. self.execute = lambda l=None, pool=None: l.updatePixmap( ) if l is not None else None self.options = {} # actionName is used by methods graphics***.writeToStream() self.actionName = 'actionNull' # view is the dock widget containing # the graphics form associated with the layer self.view = None # containers are initialized (only once) by # getCurrentMaskedImage. Their type is QLayer self.maskedImageContainer = None self.maskedThumbContainer = None # consecutive layers can be grouped. # A group is a list of QLayer objects self.group = [] # layer offsets self.xOffset, self.yOffset = 0, 0 self.Zoom_coeff = 1.0 # clone dup layer shift and zoom relative to current layer self.xAltOffset, self.yAltOffset = 0, 0 self.AltZoom_coeff = 1.0 super().__init__(*args, **kwargs) self.updatePixmap()
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, mainForm=None): super().__init__(parent=parent) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(axeSize, axeSize) self.setAttribute(Qt.WA_DeleteOnClose) # back links to image self.targetImage = weakProxy(targetImage) self.img = weakProxy(targetImage) self.layer = weakProxy(layer) self.mainForm = mainForm # options options_dict = {'Normal Clone':cv2.NORMAL_CLONE, 'Mixed Clone':cv2.MIXED_CLONE, 'Monochrome Transfer':cv2.MONOCHROME_TRANSFER} options = list(options_dict.keys()) self.layer.cloningMethod = options_dict['Normal Clone'] self.layer.maskIsEnabled = True self.layer.maskIsSelected = True # mask all pixels, use a semi transparent mask self.layer.resetMask(maskAll=True, alpha=128) self.layer.autoclone = False self.layer.cloningMethod = cv2.NORMAL_CLONE self.options={} for op in options: self.options[op] = False self.listWidget1 = optionsWidget(options=options, exclusive=True) sel = options[0] self.listWidget1.select(self.listWidget1.items[sel]) self.options[sel] = True # select event handler def onSelect1(item): for key in self.options: self.options[key] = item is self.listWidget1.items[key] if self.options[key]: self.layer.cloningMethod = options_dict[key] self.listWidget1.onSelect = onSelect1 # set initial selection to normal cloning item = self.listWidget1.items[options[0]] item.setCheckState(Qt.Checked) self.listWidget1.select(item) pushButton1 = QPushButton('Clone') # button clicked event handler 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() #layer.maskIsEnabled = False # done in onselect2 #layer.maskIsSelected = False pushButton1.clicked.connect(f) pushButton2 = QPushButton('Reset') def g(): layer = self.layer # mask all pixels layer.resetMask(maskAll=True) layer.maskIsSelected = True # reset clone layer layer.xAltOffset, layer.yAltOffset = 0.0, 0.0 layer.AltZoom_coeff = 1.0 layer.applyCloning(seamless=False) layer.parentImage.onImageChanged() pushButton2.clicked.connect(g) # layout layout = QVBoxLayout() layout.setContentsMargins(20, 0, 20, 25) # left, top, right, bottom self.setLayout(layout) layout.addWidget(self.listWidget1) hl = QHBoxLayout() hl.addWidget(pushButton1) hl.addWidget(pushButton2) layout.addLayout(hl) self.setWhatsThis( """ <b>Cloning</b> : Seamless replacement of a region of the image by another region from the same image (e.g. to erase an object):<br> 1) Select the Unmask/FG tool and paint the pixels to erase (use the Mask/BG tool to adjust if needed); <br> 2) Select the drag tool and while pressing <b>Ctrl-Alt</b>, drag or zoom the image shown in the painted region with the mouse; the mask can be modified with the mask/unmask tools<br> 3) Click the Clone button to start the cloning.<br> 4) Redo steps 2 and 3 until the result is satisfactory.<br> <b> While executing steps 1 to 4 above, make sure the that layer is the topmost visible layer</b> """ )
def setLayers(self, mImg, delete=False): """ Displays the layer stack of a mImage instance. @param mImg: image @type mImg: mImage @param delete: @type delete: """ # close open adjustment windows # self.closeAdjustForms() self.clear(delete=delete) mImg.layerView = self # back link to image self.img = weakProxy(mImg) model = layerModel() model.setColumnCount(3) l = len(mImg.layersStack) # dataChanged event handler : enables edition of layer name def f(index1, index2): # index1 and index2 should be equal # only layer name should be editable # dropEvent emit dataChanged when setting item values. f must # return immediately from these calls, as they are possibly made with unconsistent data : # dragged rows are already removed from layersStack # and not yet removed from model. if l != self.model().rowCount(): return # only name is editable if index1.column() != 1: return row = index1.row() stackIndex = l - row - 1 mImg.layersStack[stackIndex].name = index1.data() model.dataChanged.connect(f) for r, lay in enumerate(reversed(mImg.layersStack)): try: lay.maskSettingsChanged.sig.disconnect() except RuntimeError: pass lay.maskSettingsChanged.sig.connect(self.updateRows) items = [] # col 0 : visibility icon if lay.visible: item_visible = QStandardItem( QIcon(":/images/resources/eye-icon.png"), "") else: item_visible = QStandardItem( QIcon(":/images/resources/eye-icon-strike.png"), "") items.append(item_visible) # col 1 : image icon (for non-adjustment layer only) and name if len(lay.name) <= 30: name = lay.name else: name = lay.name[:28] + '...' if hasattr(lay, 'inputImg'): item_name = QStandardItem(name) else: # icon with very small dim causes QPainter error # QPixmap.fromImage bug ? smallImg = lay.resized(50, 50) w, h = smallImg.width(), smallImg.height() if w < h / 5 or h < w / 5: item_name = QStandardItem(name) else: item_name = QStandardItem( QIcon(QPixmap.fromImage(smallImg)), name) # set tool tip to full name item_name.setToolTip(lay.name) items.append(item_name) item_mask = QStandardItem('m') items.append(item_mask) model.appendRow(items) self.setModel(model) self.horizontalHeader().hide() self.verticalHeader().hide() header = self.horizontalHeader() header.setSectionResizeMode(0, QHeaderView.ResizeToContents) header.setSectionResizeMode(1, QHeaderView.ResizeToContents) header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # lay out the graphic forms into right pane forms = [ item.view for item in mImg.layersStack if getattr(item, 'view', None) is not None ] for dock in forms: if TABBING: dockedForms = [item for item in forms if not item.isFloating()] dock.show() # needed to get working tabbing if dock not in dockedForms and dock.tabbed: if dockedForms: window.tabifyDockWidget(dockedForms[-1], dock) else: window.addDockWidget(Qt.RightDockWidgetArea, dock) dock.setFloating(False) # select active layer self.selectRow(len(mImg.layersStack) - 1 - mImg.activeLayerIndex) activeLayer = mImg.getActiveLayer() layerview = activeLayer.view if layerview is not None: layerview.show() if TABBING: layerview.raise_() # lay out subcontrols of activeLayer form = layerview.widget() for dk in form.subControls: dk.setVisible(form.options[dk.widget().optionName]) # clean up: we (re)dock all sucontrols dk.setFloating(False) # emit topLevelChanged signal self.opacitySlider.setSliderPosition(int(activeLayer.opacity * 100)) self.maskSlider.setSliderPosition( int(activeLayer.colorMaskOpacity * 100.0 / 255.0)) ind = self.blendingModeCombo.findData(activeLayer.compositionMode) self.blendingModeCombo.setCurrentIndex(ind) self.previewOptionBox.setChecked(activeLayer.parentImage.useThumb) #activeLayer.maskColor self.updateForm() """ # TODO removed 25/01/20 useless validate
def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None, mainForm=None): super().__init__(parent=parent) self.setStyleSheet( 'QRangeSlider * {border: 0px; padding: 0px; margin: 0px}') self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.setMinimumSize(axeSize, axeSize + 100) self.setAttribute(Qt.WA_DeleteOnClose) # link back to image layer self.layer = weakProxy(layer) """ # using weak ref for back links if type(layer) in weakref.ProxyTypes: self.layer = layer else: self.layer = weakref.proxy(layer) """ # contrast spline viewer self.contrastForm = None # options optionList1, optionNames1 = ['Multi-Mode', 'CLAHE'], ['Multi-Mode', 'CLAHE'] self.listWidget1 = optionsWidget( options=optionList1, optionNames=optionNames1, exclusive=True, changed=lambda: self.dataChanged.emit()) self.listWidget1.checkOption(self.listWidget1.intNames[0]) self.listWidget1.setStyleSheet( "QListWidget {border: 0px;} QListWidget::item {border: 0px; padding-left: 0px;}" ) optionList2, optionNames2 = ['High', 'manualCurve'], [ 'Preserve Highlights', 'Show Contrast Curve' ] def optionList2Change(item): if item.internalName == 'High': # force to recalculate the spline self.layer.autoSpline = True self.dataChanged.emit() self.listWidget2 = optionsWidget(options=optionList2, optionNames=optionNames2, exclusive=False, changed=optionList2Change) self.listWidget2.checkOption(self.listWidget2.intNames[0]) self.listWidget2.setStyleSheet( "QListWidget {border: 0px;} QListWidget::item {border: 0px; padding-left: 0px;}" ) self.options = UDict( (self.listWidget1.options, self.listWidget2.options)) # contrast slider self.sliderContrast = QbLUeSlider(Qt.Horizontal) self.sliderContrast.setStyleSheet( QbLUeSlider.bLueSliderDefaultIBWStylesheet) self.sliderContrast.setRange(0, 10) self.sliderContrast.setSingleStep(1) contrastLabel = QbLUeLabel() contrastLabel.setMaximumSize(150, 30) contrastLabel.setText("Contrast Level") contrastLabel.doubleClicked.connect( lambda: self.sliderContrast.setValue( self.contrast2Slider(self.contrastDefault))) self.contrastValue = QLabel() font = self.contrastValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.contrastValue.setMinimumSize(w, h) self.contrastValue.setMaximumSize(w, h) self.contrastValue.setText( str("{:d}".format(self.sliderContrast.value()))) # contrast changed event handler. def contrastUpdate(value): self.contrastValue.setText( str("{:d}".format(self.sliderContrast.value()))) # move not yet terminated or value not modified if self.sliderContrast.isSliderDown() or self.slider2Contrast( value) == self.contrastCorrection: return self.sliderContrast.valueChanged.disconnect() self.sliderContrast.sliderReleased.disconnect() self.contrastCorrection = self.slider2Contrast( self.sliderContrast.value()) # force to recalculate the spline self.layer.autoSpline = True self.dataChanged.emit() self.sliderContrast.valueChanged.connect(contrastUpdate) self.sliderContrast.sliderReleased.connect( lambda: contrastUpdate(self.sliderContrast.value())) self.sliderContrast.valueChanged.connect(contrastUpdate) self.sliderContrast.sliderReleased.connect( lambda: contrastUpdate(self.sliderContrast.value())) # saturation slider self.sliderSaturation = QbLUeSlider(Qt.Horizontal) self.sliderSaturation.setStyleSheet( QbLUeSlider.bLueSliderDefaultColorStylesheet) self.sliderSaturation.setRange(0, 100) self.sliderSaturation.setSingleStep(1) saturationLabel = QbLUeLabel() saturationLabel.setMaximumSize(150, 30) saturationLabel.setText("Saturation") saturationLabel.doubleClicked.connect( lambda: self.sliderSaturation.setValue( self.saturation2Slider(self.saturationDefault))) self.saturationValue = QLabel() font = self.saturationValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.saturationValue.setMinimumSize(w, h) self.saturationValue.setMaximumSize(w, h) self.saturationValue.setText( str("{:+d}".format(self.sliderContrast.value()))) # saturation changed event handler def saturationUpdate(value): self.saturationValue.setText( str("{:+d}".format( int( self.slidersaturation2User( self.sliderSaturation.value()))))) # move not yet terminated or value not modified if self.sliderSaturation.isSliderDown() or self.slider2Saturation( value) == self.satCorrection: return self.sliderSaturation.valueChanged.disconnect() self.sliderSaturation.sliderReleased.disconnect() self.satCorrection = self.slider2Saturation( self.sliderSaturation.value()) self.dataChanged.emit() self.sliderSaturation.valueChanged.connect(saturationUpdate) self.sliderSaturation.sliderReleased.connect( lambda: saturationUpdate(self.sliderSaturation.value())) self.sliderSaturation.valueChanged.connect(saturationUpdate) self.sliderSaturation.sliderReleased.connect( lambda: saturationUpdate(self.sliderSaturation.value())) # brightness slider self.sliderBrightness = QbLUeSlider(Qt.Horizontal) self.sliderBrightness.setStyleSheet( QbLUeSlider.bLueSliderDefaultBWStylesheet) self.sliderBrightness.setRange(0, 100) self.sliderBrightness.setSingleStep(1) brightnessLabel = QbLUeLabel() brightnessLabel.setMaximumSize(150, 30) brightnessLabel.setText("Brightness") brightnessLabel.doubleClicked.connect( lambda: self.sliderBrightness.setValue( self.brightness2Slider(self.brightnessDefault))) self.brightnessValue = QLabel() font = self.brightnessValue.font() metrics = QFontMetrics(font) w = metrics.width("100") h = metrics.height() self.brightnessValue.setMinimumSize(w, h) self.brightnessValue.setMaximumSize(w, h) self.brightnessValue.setText( str("{:+d}".format(self.sliderContrast.value()))) # brightness changed event handler def brightnessUpdate(value): self.brightnessValue.setText( str("{:+d}".format( int( self.sliderBrightness2User( self.sliderBrightness.value()))))) # move not yet terminated or value not modified if self.sliderBrightness.isSliderDown() or self.slider2Brightness( value) == self.brightnessCorrection: return self.sliderBrightness.valueChanged.disconnect() self.sliderBrightness.sliderReleased.disconnect() self.brightnessCorrection = self.slider2Brightness( self.sliderBrightness.value()) self.dataChanged.emit() self.sliderBrightness.valueChanged.connect(brightnessUpdate) self.sliderBrightness.sliderReleased.connect( lambda: brightnessUpdate(self.sliderBrightness.value())) self.sliderBrightness.valueChanged.connect(brightnessUpdate) self.sliderBrightness.sliderReleased.connect( lambda: brightnessUpdate(self.sliderBrightness.value())) # attributes initialized in setDefaults, declared here # for the sake of correctness self.contrastCorrection = None # range self.satCorrection = None # range -0.5..0.5 self.brightnessCorrection = None # range -0.5..0.5 # layout l = QVBoxLayout() l.setAlignment(Qt.AlignTop) gb1 = QGroupBox() gb1.setStyleSheet( "QGroupBox {border: 1px solid gray; border-radius: 4px}") l1 = QVBoxLayout() ct = QLabel() ct.setText('Contrast') l1.addWidget(ct) l1.addWidget(self.listWidget1) gb1.setLayout(l1) l.addWidget(gb1) l.addWidget(self.listWidget2) l.addWidget(contrastLabel) hl = QHBoxLayout() hl.addWidget(self.contrastValue) hl.addWidget(self.sliderContrast) l.addLayout(hl) l.addWidget(brightnessLabel) hl3 = QHBoxLayout() hl3.addWidget(self.brightnessValue) hl3.addWidget(self.sliderBrightness) l.addLayout(hl3) l.addWidget(saturationLabel) hl2 = QHBoxLayout() hl2.addWidget(self.saturationValue) hl2.addWidget(self.sliderSaturation) l.addLayout(hl2) self.setLayout(l) self.adjustSize() self.setStyleSheet("QListWidget, QLabel {font : 7pt;}") self.setDefaults() self.setWhatsThis("""<b>Contrast Brightness Saturation</b><br> <b>Contrast</b> is enhanced using one of these two methods:<br> - <b>CLAHE</b> : increases the local contrast.<br> - <b>Multi-Mode</b> : increases the local contrast and the contrast between regions of the image.<br> For both methods the contrast slider controls the level of the correction.<br> With Multi-Mode enabled, use the option <b>Show Contrast Curve</b> to edit the correction curve and check <b>Preserve Highlights</b> for softer highlights.<br> Sliders are <b>reset</b> to their default value by double clicking the name of the slider.<br> """) # end setWhatsThis