Exemple #1
0
    def __init__(self, parent, text, lastDir):
        """

        @param parent:
        @type parent: QObject
        @param text:
        @type text: str
        @param lastDir:
        @type lastDir:str
        """
        # QDialog __init__
        super().__init__()
        self.setWindowTitle(text)
        # File Dialog
        self.dlg = QFileDialog(caption=text, directory=lastDir)
        # sliders
        self.sliderComp = QbLUeSlider(Qt.Horizontal)
        self.sliderComp.setTickPosition(QSlider.TicksBelow)
        self.sliderComp.setRange(0, 9)
        self.sliderComp.setSingleStep(1)
        self.sliderComp.setValue(5)
        self.sliderQual = QbLUeSlider(Qt.Horizontal)
        self.sliderQual.setTickPosition(QSlider.TicksBelow)
        self.sliderQual.setRange(0, 100)
        self.sliderQual.setSingleStep(10)
        self.sliderQual.setValue(90)
        self.dlg.setVisible(True)
        l = QVBoxLayout()
        h = QHBoxLayout()
        l.addWidget(self.dlg)
        h.addWidget(QLabel("Quality"))
        h.addWidget(self.sliderQual)
        h.addWidget(QLabel("Compression"))
        h.addWidget(self.sliderComp)
        l.addLayout(h)
        self.setLayout(l)

        # file dialog close event handler
        def f():
            self.close()

        self.dlg.finished.connect(f)
Exemple #2
0
    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)
        # options
        self.options = None
        # exposure slider
        self.sliderExp = QbLUeSlider(Qt.Horizontal)
        self.sliderExp.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.sliderExp.setRange(-20, 20)
        self.sliderExp.setSingleStep(1)

        expLabel = QbLUeLabel()
        expLabel.setMaximumSize(150, 30)
        expLabel.setText("Exposure Correction")
        expLabel.doubleClicked.connect(
            lambda: self.sliderExp.setValue(self.defaultExpCorrection))

        self.expValue = QbLUeLabel()
        font = self.expValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("1000 ")
        h = metrics.height()
        self.expValue.setMinimumSize(w, h)
        self.expValue.setMaximumSize(w, h)

        # exp change/released slot
        def f():
            self.expValue.setText(
                str("{:+.1f}".format(self.sliderExp.value() *
                                     self.defaultStep)))
            if self.sliderExp.isSliderDown() or (self.expCorrection
                                                 == self.sliderExp.value() *
                                                 self.defaultStep):
                return
            try:
                self.sliderExp.valueChanged.disconnect()
                self.sliderExp.sliderReleased.disconnect()
            except RuntimeError:
                pass
            self.expCorrection = self.sliderExp.value() * self.defaultStep
            self.dataChanged.emit()
            self.sliderExp.valueChanged.connect(f)
            self.sliderExp.sliderReleased.connect(f)

        self.sliderExp.valueChanged.connect(f)
        self.sliderExp.sliderReleased.connect(f)

        # layout
        l = QVBoxLayout()
        l.setAlignment(Qt.AlignTop)
        l.addWidget(expLabel)
        hl = QHBoxLayout()
        hl.addWidget(self.expValue)
        hl.addWidget(self.sliderExp)
        l.addLayout(hl)
        l.setContentsMargins(20, 0, 20, 25)  # left, top, right, bottom
        self.setLayout(l)
        self.adjustSize()
        self.setWhatsThis("""<b>Exposure Correction</b>
                        Multiplicative correction in the linear sRGB color space.<br>
                        Unit is the diaphragm stop.<br>
                        """)  # end setWhatsThis

        self.setDefaults()
Exemple #3
0
class ExpForm(baseForm):
    defaultExpCorrection = 0.0
    defaultStep = 0.1

    @classmethod
    def getNewWindow(cls,
                     targetImage=None,
                     axeSize=500,
                     layer=None,
                     parent=None):
        wdgt = ExpForm(targetImage=targetImage,
                       axeSize=axeSize,
                       layer=layer,
                       parent=parent)
        wdgt.setWindowTitle(layer.name)
        return wdgt

    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)
        # options
        self.options = None
        # exposure slider
        self.sliderExp = QbLUeSlider(Qt.Horizontal)
        self.sliderExp.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.sliderExp.setRange(-20, 20)
        self.sliderExp.setSingleStep(1)

        expLabel = QbLUeLabel()
        expLabel.setMaximumSize(150, 30)
        expLabel.setText("Exposure Correction")
        expLabel.doubleClicked.connect(
            lambda: self.sliderExp.setValue(self.defaultExpCorrection))

        self.expValue = QbLUeLabel()
        font = self.expValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("1000 ")
        h = metrics.height()
        self.expValue.setMinimumSize(w, h)
        self.expValue.setMaximumSize(w, h)

        # exp change/released slot
        def f():
            self.expValue.setText(
                str("{:+.1f}".format(self.sliderExp.value() *
                                     self.defaultStep)))
            if self.sliderExp.isSliderDown() or (self.expCorrection
                                                 == self.sliderExp.value() *
                                                 self.defaultStep):
                return
            try:
                self.sliderExp.valueChanged.disconnect()
                self.sliderExp.sliderReleased.disconnect()
            except RuntimeError:
                pass
            self.expCorrection = self.sliderExp.value() * self.defaultStep
            self.dataChanged.emit()
            self.sliderExp.valueChanged.connect(f)
            self.sliderExp.sliderReleased.connect(f)

        self.sliderExp.valueChanged.connect(f)
        self.sliderExp.sliderReleased.connect(f)

        # layout
        l = QVBoxLayout()
        l.setAlignment(Qt.AlignTop)
        l.addWidget(expLabel)
        hl = QHBoxLayout()
        hl.addWidget(self.expValue)
        hl.addWidget(self.sliderExp)
        l.addLayout(hl)
        l.setContentsMargins(20, 0, 20, 25)  # left, top, right, bottom
        self.setLayout(l)
        self.adjustSize()
        self.setWhatsThis("""<b>Exposure Correction</b>
                        Multiplicative correction in the linear sRGB color space.<br>
                        Unit is the diaphragm stop.<br>
                        """)  # end setWhatsThis

        self.setDefaults()

    def updateLayer(self):
        self.layer.applyToStack()
        self.layer.parentImage.onImageChanged()

    def setDefaults(self):
        try:
            self.dataChanged.disconnect()
        except RuntimeError:
            pass
        self.sliderExp.setValue(self.defaultExpCorrection)
        self.expValue.setText(str("{:+.1f}".format(self.defaultExpCorrection)))
        self.expCorrection = self.defaultExpCorrection * self.defaultStep
        self.dataChanged.connect(self.updateLayer)

    def writeToStream(self, outStream):
        layer = self.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        outStream.writeQString(self.listWidget1.selectedItems()[0].text())
        outStream.writeInt32(self.sliderExp.value())
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        temp = inStream.readInt32()
        for r in range(self.listWidget1.count()):
            currentItem = self.listWidget1.item(r)
            if currentItem.text() == sel:
                self.listWidget.select(currentItem)
        self.sliderExp.setValue(temp)
        self.update()
        return inStream
Exemple #4
0
    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(targetImage=targetImage,
                         axeSize=axeSize,
                         layer=layer,
                         parent=parent)
        graphicsScene = self.scene()
        # connect layer selectionChanged signal
        self.layer.selectionChanged.sig.connect(self.updateLayer)
        # Init curves
        dSplineItem = activeBSpline(axeSize,
                                    period=axeSize,
                                    yZero=-3 * axeSize // 4)
        graphicsScene.addItem(dSplineItem)
        dSplineItem.setVisible(True)
        dSplineItem.initFixedPoints()
        self.dSplineItemB = dSplineItem
        graphicsScene.dSplineItemB = dSplineItem

        dSplineItem = activeBSpline(axeSize,
                                    period=axeSize,
                                    yZero=-axeSize // 4)
        graphicsScene.addItem(dSplineItem)
        dSplineItem.setVisible(True)
        dSplineItem.initFixedPoints()
        self.dSplineItemH = dSplineItem
        graphicsScene.dSplineItemH = dSplineItem

        # init 3D LUT
        self.LUT = DeltaLUT3D((34, 32, 32))

        self.marker = activeMarker.fromTriangle(parent=self.dSplineItemB)
        self.marker.setPos(0, -axeSize // 2)
        self.scene().addItem(self.marker)

        def showPos(e, x, y):
            self.markerLabel.setText("%d" % (x * 360 // axeSize))

        self.marker.onMouseMove = showPos

        self.markerLabel = QLabel()
        font = self.markerLabel.font()
        metrics = QFontMetrics(font)
        w = metrics.width("0000")
        h = metrics.height()
        self.markerLabel.setMinimumSize(w, h)
        self.markerLabel.setMaximumSize(w, h)

        self.sliderSat = QbLUeSlider(Qt.Horizontal)
        self.sliderSat.setMinimumWidth(200)

        def satUpdate(value):
            self.satValue.setText(str("{:d}".format(value)))
            # move not yet terminated or values not modified
            if self.sliderSat.isSliderDown() or value == self.satThr:
                return
            try:
                self.sliderSat.valueChanged.disconnect()
                self.sliderSat.sliderReleased.disconnect()
            except RuntimeError:
                pass
            self.satThr = value
            self.dataChanged.emit()
            self.sliderSat.valueChanged.connect(satUpdate)
            self.sliderSat.sliderReleased.connect(
                lambda: satUpdate(self.sliderSat.value()))

        self.sliderSat.valueChanged.connect(satUpdate)
        self.sliderSat.sliderReleased.connect(
            lambda: satUpdate(self.sliderSat.value()))

        self.satValue = QLabel()
        font = self.markerLabel.font()
        metrics = QFontMetrics(font)
        w = metrics.width("0000")
        h = metrics.height()
        self.satValue.setMinimumSize(w, h)
        self.satValue.setMaximumSize(w, h)

        # layout
        gl = QGridLayout()
        gl.addWidget(QLabel('Hue '), 0, 0)
        gl.addWidget(self.markerLabel, 0, 1)
        gl.addWidget(QLabel('Sat Thr '), 1, 0)
        gl.addWidget(self.satValue, 1, 1)
        gl.addWidget(self.sliderSat, 1, 2, 4, 1)
        self.addCommandLayout(gl)

        self.setDefaults()

        self.setWhatsThis("""<b>3D LUT Shift HSV</b><br>
            x-axes represent hue values from 0 to 360.
            The upper curve shows brightness multiplicative shifts (initially 1) and
            the lower curve hue additive shifts (initially 0). 
            All pixel colors are changed by the specific shifts corresponding to their hue.
            Each curve is controlled by bump triangles.<br>
            To <b>add a bump triangle</b> to the curve click anywhere on the curve.
            To <b>remove the triangle</b> click on any vertex.<br>
            Drag the triangle vertices to move the bump along the x-axis and to change 
            its height and orientation. Use the <b> Sat Thr</b> slider 
            to preserve low saturated colors.<br>
            To <b>set the Hue Value Marker</b> Ctrl+click on the image.<br>
            To limit the shift corrections to a region of the image select the desired area
            with the rectangular marquee tool.<br>
            <b>Zoom</b> the curves with the mouse wheel.<br>
            """)
    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
Exemple #6
0
class filterForm(baseForm):

    defaultRadius = 10
    defaultTone = 100.0
    defaultAmount = 50.0

    @classmethod
    def getNewWindow(cls,
                     targetImage=None,
                     axeSize=500,
                     layer=None,
                     parent=None):
        wdgt = filterForm(targetImage=targetImage,
                          axeSize=axeSize,
                          layer=layer,
                          parent=parent)
        wdgt.setWindowTitle(layer.name)
        return wdgt

    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)
        # connect layer selectionChanged signal
        self.layer.selectionChanged.sig.connect(self.updateLayer)
        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))  # filters is not a dict: don't use UDict here

        self.listWidget1 = optionsWidget(options=self.optionList,
                                         exclusive=True,
                                         changed=self.dataChanged)
        # set initial selection to unsharp mask
        self.listWidget1.checkOption(self.optionList[0])

        # sliders
        self.sliderRadius = QbLUeSlider(Qt.Horizontal)
        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.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.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)

        # value change/done slot
        def formUpdate():
            self.radiusValue.setText(str('%d ' % self.sliderRadius.value()))
            self.amountValue.setText(str('%d ' % self.sliderAmount.value()))
            self.toneValue.setText(str('%d ' % self.sliderTone.value()))
            if self.sliderRadius.isSliderDown(
            ) or self.sliderAmount.isSliderDown(
            ) or self.sliderTone.isSliderDown():
                return
            try:
                for slider in [
                        self.sliderRadius, self.sliderAmount, self.sliderTone
                ]:
                    slider.valueChanged.disconnect()
                    slider.sliderReleased.disconnect()
            except RuntimeError:
                pass
            self.tone = self.sliderTone.value()
            self.radius = self.sliderRadius.value()
            self.amount = self.sliderAmount.value()
            self.dataChanged.emit()
            for slider in [
                    self.sliderRadius, self.sliderAmount, self.sliderTone
            ]:
                slider.valueChanged.connect(formUpdate)
                slider.sliderReleased.connect(formUpdate)

        for slider in [self.sliderRadius, self.sliderAmount, self.sliderTone]:
            slider.valueChanged.connect(formUpdate)
            slider.sliderReleased.connect(formUpdate)

        # layout
        l = QVBoxLayout()
        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)

        self.setDefaults()
        self.setWhatsThis("""
                       <b>Unsharp Mask</b> and <b>Sharpen Mask</b> are used to sharpen an image.
                       Unsharp Mask usually gives best results.<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,
                       but it may be slow.<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 (rectangle) tool.<br>
                       Ctrl Click <b>clears the selection</b><br>
                       
                    """)  # end setWhatsThis

    def setDefaults(self):
        self.enableSliders()
        try:
            self.dataChanged.disconnect()
        except RuntimeError:
            pass
        self.sliderRadius.setValue(self.defaultRadius)
        self.sliderAmount.setValue(self.defaultAmount)
        self.sliderTone.setValue(self.defaultTone)
        self.dataChanged.connect(self.updateLayer)

    def updateLayer(self):
        """
        dataChanged Slot
        """
        self.enableSliders()
        for key in self.listWidget1.options:
            if self.listWidget1.options[key]:
                self.kernelCategory = self.filterDict[key]
                break
        self.layer.applyToStack()
        self.layer.parentImage.onImageChanged()

    def enableSliders(self):
        opt = self.listWidget1.options
        useRadius = opt[self.optionList[0]] or opt[self.optionList[2]] or opt[
            self.optionList[3]]
        useAmount = opt[self.optionList[0]] or opt[self.optionList[2]]
        useTone = opt[self.optionList[3]]
        self.sliderRadius.setEnabled(useRadius)
        self.sliderAmount.setEnabled(useAmount)
        self.sliderTone.setEnabled(useTone)
        self.radiusValue.setEnabled(self.sliderRadius.isEnabled())
        self.amountValue.setEnabled(self.sliderAmount.isEnabled())
        self.toneValue.setEnabled(self.sliderTone.isEnabled())
        self.radiusLabel.setEnabled(self.sliderRadius.isEnabled())
        self.amountLabel.setEnabled(self.sliderAmount.isEnabled())
        self.toneLabel.setEnabled(self.sliderTone.isEnabled())

    def writeToStream(self, outStream):
        layer = self.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        outStream.writeQString(self.listWidget1.selectedItems()[0].text())
        outStream.writeFloat32(self.sliderRadius.value())
        outStream.writeFloat32(self.sliderAmount.value())
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        radius = inStream.readFloat32()
        amount = inStream.readFloat32()
        for r in range(self.listWidget1.count()):
            currentItem = self.listWidget1.item(r)
            if currentItem.text() == sel:
                self.listWidget.select(currentItem)
        self.sliderRadius.setValue(radius)
        self.sliderAmount.setValue(amount)
        self.repaint()
        return inStream
class noiseForm(baseForm):
    dataChanged = QtCore.Signal(bool)

    @classmethod
    def getNewWindow(cls, axeSize=500, layer=None, parent=None):
        wdgt = noiseForm(axeSize=axeSize, layer=layer, parent=parent)
        wdgt.setWindowTitle(layer.name)
        return wdgt

    def __init__(self, axeSize=500, layer=None, parent=None):
        super(noiseForm, self).__init__(parent=parent)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.setMinimumSize(axeSize, axeSize)
        self.setAttribute(Qt.WA_DeleteOnClose)
        # link back to image layer
        # using weak ref for back links
        if type(layer) in weakref.ProxyTypes:
            self.layer = layer
        else:
            self.layer = weakref.proxy(layer)
        # attribute initialized in setDefaults
        # defined here for the sake of correctness
        self.noiseCorrection = 0

        # options
        optionList = ['Wavelets', 'Bilateral', 'NLMeans']
        self.listWidget1 = optionsWidget(
            options=optionList,
            exclusive=True,
            changed=lambda: self.dataChanged.emit(True))
        self.listWidget1.checkOption(self.listWidget1.intNames[0])
        self.options = self.listWidget1.options

        # threshold slider
        self.sliderThr = QbLUeSlider(Qt.Horizontal)
        self.sliderThr.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.sliderThr.setTickPosition(QSlider.TicksBelow)
        self.sliderThr.setRange(0, 10)
        self.sliderThr.setSingleStep(1)

        self.sliderThr.valueChanged.connect(self.thrUpdate)
        self.sliderThr.sliderReleased.connect(lambda: self.thrUpdate(
            self.sliderThr.value()))  # signal has no parameter)

        self.thrLabel = QLabel()
        self.thrLabel.setMaximumSize(150, 30)
        self.thrLabel.setText("level")

        self.thrValue = QLabel()
        font = self.thrValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("0000")
        h = metrics.height()
        self.thrValue.setMinimumSize(w, h)
        self.thrValue.setMaximumSize(w, h)
        self.thrValue.setText(
            str("{:.0f}".format(self.slider2Thr(self.sliderThr.value()))))
        # self.dataChanged.connect(self.updateLayer)
        # self.setStyleSheet("QListWidget, QLabel {font : 7pt;}")
        # layout
        l = QVBoxLayout()
        #l.setAlignment(Qt.AlignBottom)
        l.addWidget(self.listWidget1)
        hl1 = QHBoxLayout()
        hl1.addWidget(self.thrLabel)
        hl1.addWidget(self.thrValue)
        hl1.addWidget(self.sliderThr)
        l.addLayout(hl1)
        l.setContentsMargins(20, 0, 20, 25)  # left, top, right, bottom
        #l.setContentsMargins(10, 10, 10, 10)  # left, top, right, bottom
        self.setLayout(l)
        self.adjustSize()
        self.setDefaults()
        self.setWhatsThis("""<b>Noise Reduction</b><br>
   <b>Bilateral Filtering</b> is the fastest method.<br>
   <b>NLMeans</b> (Non Local Means) and <b>Wavelets</b> are slower,
   but they usually give better results.<br>
   It is possible to <b>limit the application of all methods 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 of setWhatsThis

    def setDefaults(self):
        self.listWidget1.unCheckAll()
        self.listWidget1.checkOption(self.listWidget1.intNames[0])
        self.noiseCorrection = 0
        # prevent multiple updates
        try:
            self.dataChanged.disconnect()
        except RuntimeError:
            pass
        self.sliderThr.setValue(round(self.thr2Slider(self.noiseCorrection)))
        self.dataChanged.connect(self.updateLayer)
        self.dataChanged.emit(True)

    def updateLayer(self, invalidate):
        """
        data changed event handler
        """
        #if invalidate:
        #self.layer.postProcessCache = None  # TODO 2/11/18 unused validate
        #self.enableSliders()
        self.layer.applyToStack()
        self.layer.parentImage.onImageChanged()

    def slider2Thr(self, v):
        return v

    def thr2Slider(self, t):
        return t

    def thrUpdate(self, value):
        self.thrValue.setText(
            str("{:.0f}".format(self.slider2Thr(self.sliderThr.value()))))
        # move not yet terminated or value not modified
        if self.sliderThr.isSliderDown() or self.slider2Thr(
                value) == self.noiseCorrection:
            return
        self.sliderThr.valueChanged.disconnect()
        self.sliderThr.sliderReleased.disconnect()
        self.noiseCorrection = self.slider2Thr(self.sliderThr.value())
        self.thrValue.setText(str("{:+d}".format(self.noiseCorrection)))
        self.dataChanged.emit(False)
        self.sliderThr.valueChanged.connect(
            self.thrUpdate)  # send new value as parameter
        self.sliderThr.sliderReleased.connect(lambda: self.thrUpdate(
            self.sliderThr.value()))  # signal has no parameter

    def writeToStream(self, outStream):
        layer = self.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        outStream.writeQString(self.listWidget1.selectedItems()[0].text())
        outStream.writeInt32(self.sliderTemp.value() * 100)
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        temp = inStream.readInt32()
        for r in range(self.listWidget1.count()):
            currentItem = self.listWidget1.item(r)
            if currentItem.text() == sel:
                self.listWidget.select(currentItem)
        self.sliderTemp.setValue(temp // 100)
        self.update()
        return inStream
Exemple #8
0
class QLayerView(QTableView):
    """
    Display the stack of image layers.
    """
    def __init__(self, parent):
        super(QLayerView, self).__init__(parent)
        self.img = None
        # graphic form to show : it
        # should correspond to the currently selected layer
        self.currentWin = None
        # mouse click event
        self.clicked.connect(self.viewClicked)

        # set behavior and styles
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        delegate = itemDelegate(parent=self)
        self.setItemDelegate(delegate)
        ic1 = QImage(":/images/resources/eye-icon.png")
        ic2 = QImage(":/images/resources/eye-icon-strike.png")
        delegate.px1 = QPixmap.fromImage(ic1)
        delegate.px2 = QPixmap.fromImage(ic2)
        ic1.invertPixels()
        ic2.invertPixels()
        delegate.inv_px1 = QPixmap.fromImage(ic1)
        delegate.inv_px2 = QPixmap.fromImage(ic2)
        self.setIconSize(QSize(20, 15))
        self.verticalHeader().setMinimumSectionSize(-1)
        self.verticalHeader().setDefaultSectionSize(
            self.verticalHeader().minimumSectionSize())
        self.horizontalHeader().setMinimumSectionSize(40)
        self.horizontalHeader().setDefaultSectionSize(40)

        # drag and drop
        self.setDragDropMode(QAbstractItemView.DragDrop)
        self.setDefaultDropAction(Qt.MoveAction)
        self.setDragDropOverwriteMode(False)
        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.setDropIndicatorShown(True)

        ################################
        # layer property GUI :
        # preview, blending mode, opacity, mask color
        ################################
        # Preview option
        # We should use a QListWidget or a custom optionsWidget
        # (cf. utils.py) :  adding it to QVBoxLayout with mode
        # Qt.AlignBottom does not work.
        self.previewOptionBox = QCheckBox('Preview')
        self.previewOptionBox.setMaximumSize(100, 30)

        # View/Preview changed event handler
        def m(state):  # state : Qt.Checked Qt.UnChecked
            if self.img is None:
                return
            self.img.useThumb = (state == Qt.Checked)
            window.updateStatus()
            self.img.cacheInvalidate()
            for layer in self.img.layersStack:
                layer.autoclone = True  # auto update cloning layers
                layer.knitted = False
            try:
                QApplication.setOverrideCursor(
                    Qt.WaitCursor
                )  # TODO 18/04/18 waitcursor is called by applytostack?
                QApplication.processEvents()
                # update the whole stack
                self.img.layersStack[0].applyToStack()
                self.img.onImageChanged()  # TODO added 30/11/18 validate

            finally:
                for layer in self.img.layersStack:
                    layer.autoclone = False  # reset flags
                    layer.knitted = False
                QApplication.restoreOverrideCursor()
                QApplication.processEvents()
            # window.label.repaint()  # TODO removed 30/11/18 replaced by onImageChange above

        self.previewOptionBox.stateChanged.connect(m)
        self.previewOptionBox.setChecked(True)  # m is not triggered

        # title
        titleLabel = QLabel('Layer')
        titleLabel.setMaximumSize(100, 30)

        # opacity slider
        self.opacitySlider = QbLUeSlider(Qt.Horizontal)
        self.opacitySlider.setStyleSheet(
            QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.opacitySlider.setTickPosition(QSlider.TicksBelow)
        self.opacitySlider.setRange(0, 100)
        self.opacitySlider.setSingleStep(1)
        self.opacitySlider.setSliderPosition(100)

        self.opacityValue = QLabel()
        font = self.opacityValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("100 ")
        h = metrics.height()
        self.opacityValue.setMinimumSize(w, h)
        self.opacityValue.setMaximumSize(w, h)
        self.opacityValue.setText('100 ')

        # opacity value changed event handler
        def f1():
            self.opacityValue.setText(str('%d ' % self.opacitySlider.value()))

        # opacity slider released event handler
        def f2():
            try:
                layer = self.img.getActiveLayer()
                layer.setOpacity(self.opacitySlider.value())
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.opacitySlider.valueChanged.connect(f1)
        self.opacitySlider.sliderReleased.connect(f2)

        # mask color slider
        maskSlider = QbLUeSlider(Qt.Horizontal)
        maskSlider.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        maskSlider.setTickPosition(QSlider.TicksBelow)
        maskSlider.setRange(0, 100)
        maskSlider.setSingleStep(1)
        maskSlider.setSliderPosition(100)
        self.maskSlider = maskSlider

        self.maskValue = QLabel()
        font = self.maskValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("100 ")
        h = metrics.height()
        self.maskValue.setMinimumSize(w, h)
        self.maskValue.setMaximumSize(w, h)
        self.maskValue.setText('100 ')

        # mask value changed event handler
        def g1():
            self.maskValue.setText(str('%d ' % self.maskSlider.value()))

        # mask slider released event handler
        def g2():
            try:
                layer = self.img.getActiveLayer()
                layer.setColorMaskOpacity(self.maskSlider.value())
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.maskSlider.valueChanged.connect(g1)
        self.maskSlider.sliderReleased.connect(g2)

        # blending mode combo box
        compLabel = QLabel()
        compLabel.setText("Blend")

        self.compositionModeDict = OrderedDict([
            ('Normal', QPainter.CompositionMode_SourceOver),
            ('Plus', QPainter.CompositionMode_Plus),
            ('Multiply', QPainter.CompositionMode_Multiply),
            ('Screen', QPainter.CompositionMode_Screen),
            ('Overlay', QPainter.CompositionMode_Overlay),
            ('Darken', QPainter.CompositionMode_Darken),
            ('Lighten', QPainter.CompositionMode_Lighten),
            ('Color Dodge', QPainter.CompositionMode_ColorDodge),
            ('Color Burn', QPainter.CompositionMode_ColorBurn),
            ('Hard Light', QPainter.CompositionMode_HardLight),
            ('Soft Light', QPainter.CompositionMode_SoftLight),
            ('Difference', QPainter.CompositionMode_Difference),
            ('Exclusion', QPainter.CompositionMode_Exclusion)
        ])
        self.blendingModeCombo = QComboBox()
        for key in self.compositionModeDict:
            self.blendingModeCombo.addItem(key, self.compositionModeDict[key])

        # combo box item chosen event handler
        def g(ind):
            s = self.blendingModeCombo.currentText()
            try:
                layer = self.img.getActiveLayer()
                layer.compositionMode = self.compositionModeDict[str(s)]
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.blendingModeCombo.currentIndexChanged.connect(g)
        # self.blendingModeCombo.activated.connect(g)  # TODO activated changed to currentIndexChanged 08/10/18 validate

        #layout
        l = QVBoxLayout()
        l.setAlignment(Qt.AlignTop)
        hl0 = QHBoxLayout()
        hl0.addWidget(titleLabel)
        hl0.addStretch(1)
        hl0.addWidget(self.previewOptionBox)
        l.addLayout(hl0)
        hl = QHBoxLayout()
        hl.addWidget(QLabel('Opacity'))
        hl.addWidget(self.opacityValue)
        hl.addWidget(self.opacitySlider)
        l.addLayout(hl)
        hl1 = QHBoxLayout()
        hl1.addWidget(QLabel('Mask Color'))
        hl1.addWidget(self.maskValue)
        hl1.addWidget(self.maskSlider)
        l.addLayout(hl1)
        l.setContentsMargins(0, 0, 10, 0)  # left, top, right, bottom
        hl2 = QHBoxLayout()
        hl2.addWidget(compLabel)
        hl2.addWidget(self.blendingModeCombo)
        l.addLayout(hl2)
        for layout in [hl, hl1, hl2]:
            layout.setContentsMargins(5, 0, 0, 0)
        # this layout must be added to the propertyWidget object loaded from blue.ui :
        # we postpone it after loading of the main form, in blue.py.
        self.propertyLayout = l

        # shortcut actions
        self.actionDup = QAction('Duplicate layer', None)
        self.actionDup.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_J))
        self.addAction(self.actionDup)

        def dup():
            row = self.selectedIndexes()[0].row()
            # Stack index
            index = len(self.img.layersStack) - row - 1
            layer = self.img.layersStack[index]
            if layer.isAdjustLayer():
                return
            # add new layer to stack and set it to active
            self.img.dupLayer(index=index)
            # update layer view
            self.setLayers(self.img)

        self.actionDup.triggered.connect(dup)
        self.setWhatsThis("""<b>Layer Stack</b>
To <b>toggle the layer visibility</b> click on the Eye icon.<br>
To <b>add a mask</b> use the context menu to enable it and paint pixels with the Mask/Unmask tools in the left pane.<br>
For <b>color mask<b/b>: <br>
    &nbsp; green pixels are masked,<br>
    &nbsp; red pixels are unmasked.<br>
Note that upper visible layers slow down mask edition.<br>
""")  # end of setWhatsThis

    """
    def setEnabled(self, value):  # TODO removed 30/11/18
        super(QLayerView, self).setEnabled(value)  
        if not self.isEnabled():
            self.setStatusTip('Close adjustment form %s to enable Layers' % self.currentWin.windowTitle())
        else:
            self.setStatusTip('')
    """

    def closeAdjustForms(self, delete=False):
        """
        Close all layer forms. If delete is True (default False),
        the forms and their dock containers are deleted.
        @param delete:
        @type delete: boolean
        """
        if self.img is None:
            return
        stack = self.img.layersStack
        for layer in stack:
            if hasattr(layer, "view"):
                if layer.view is not None:
                    dock = layer.view
                    if delete:
                        form = dock.widget()
                        # remove back link
                        form.layer = None
                        # QtGui1.window.removeDockWidget(dock)
                        form.setAttribute(Qt.WA_DeleteOnClose)
                        form.close()
                        dock.setAttribute(Qt.WA_DeleteOnClose)
                        dock.close()
                        layer.view = None
                    elif not TABBING:  # tabbed forms should not be closed
                        dock.close()
        if delete:
            self.currentWin = None
            gc.collect()

    def clear(self, delete=True):
        """
        Reset LayerView and clear the back
        links to image.
        """
        self.closeAdjustForms(delete=delete)
        self.img = None
        self.currentWin = None
        model = layerModel()
        model.setColumnCount(3)
        self.setModel(None)

    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 updateForm(self):
        activeLayer = self.img.getActiveLayer()
        if hasattr(activeLayer, 'view'):
            self.currentWin = activeLayer.view
        if self.currentWin is not None:
            self.currentWin.show()
            self.currentWin.activateWindow()

    def updateRow(self, row):
        minInd, maxInd = self.model().index(row, 0), self.model().index(row, 3)
        self.model().dataChanged.emit(minInd, maxInd)

    def dropEvent(self, event):
        """
        drop event handler : moving layer
        @param event:
        @type event: Qevent
        """
        if event.source() is not self:
            return
        # get selected rows and layers
        rows = set([mi.row() for mi in self.selectedIndexes()])
        rStack = self.img.layersStack[::-1]
        layers = [rStack[i] for i in rows]
        linked = any(l.group for l in layers)
        if linked and len(rows) > 1:
            return
        # get target row and layer
        targetRow = self.indexAt(event.pos()).row()
        targetLayer = rStack[targetRow]
        if linked:
            if layers[0].group is not targetLayer.group:
                return
        if bool(targetLayer.group) != linked:
            return
        # remove target from selection
        if targetRow in rows:
            rows.discard(targetRow)
        rows = sorted(rows)
        if not rows:
            return
        # if target is below last row insert at the last position
        if targetRow == -1:
            targetRow = self.model().rowCount()
        # mapping of src (row) indices to target indices
        rowMapping = dict()
        for idx, row in enumerate(rows):
            if row < targetRow:
                rowMapping[row] = targetRow + idx
            else:
                rowMapping[row + len(rows)] = targetRow + idx
        # update layerStack using rowMapping
        # insert None items
        for _ in range(len(rows)):
            rStack.insert(targetRow, None)
        # copy moved items to their final place
        for srcRow, tgtRow in sorted(
                rowMapping.items()):  # python 3 iteritems->items
            rStack[tgtRow] = rStack[srcRow]
        # remove moved items from their initial place
        for row in reversed(sorted(
                rowMapping.keys())):  # python 3 iterkeys -> keys
            rStack.pop(row)
        self.img.layersStack = rStack[::-1]
        # update model
        # insert empty rows
        for _ in range(len(rows)):
            result = self.model().insertRow(targetRow, QModelIndex())
        # copy moved rows to their final place
        colCount = self.model().columnCount()
        for srcRow, tgtRow in sorted(
                rowMapping.items()):  # python 3 iteritems->items
            for col in range(0, colCount):
                # CAUTION : setItem calls the data changed event handler (cf. setLayers above)
                self.model().setItem(tgtRow, col,
                                     self.model().takeItem(srcRow, col))
        # remove moved rows from their initial place and keep track of moved items
        movedDict = rowMapping.copy()
        for row in reversed(sorted(
                rowMapping.keys())):  # python 3 iterkeys -> keys
            self.model().removeRow(row)
            for s, t in rowMapping.items():
                if t > row:
                    movedDict[s] -= 1
        ######################################### sanity check
        for r in range(self.model().rowCount()):
            id = self.model().index(r, 1)
            if id.data() != rStack[r].name:
                raise ValueError('Drop Error')
        ########################################
        # reselect moved rows
        sel = sorted(movedDict.values())
        selectionModel = QtCore.QItemSelectionModel(self.model())
        self.setSelectionModel(selectionModel)
        index1 = self.model().index(sel[0], 1)
        index2 = self.model().index(sel[-1], 1)
        itemSelection = QtCore.QItemSelection(index1, index2)
        self.selectionModel().select(
            itemSelection, QtCore.QItemSelectionModel.Rows
            | QtCore.QItemSelectionModel.Select)
        # multiple selection : display no window
        if len(sel) > 1:
            self.currentWin.hide()
            self.currentWin = None
        elif len(sel) == 1:
            self.img.setActiveLayer(len(self.img.layersStack) - sel[0] - 1)
        # update stack
        self.img.layersStack[0].applyToStack()
        self.img.onImageChanged()

    def select(self, row, col):
        """
        select item in view
        @param row:
        @type row:
        @param col:
        @type col:
        @return:
        @rtype:
        """
        model = self.model()
        self.viewClicked(model.index(row, col))

    def viewClicked(self, clickedIndex):
        """
        Mouse clicked event handler.
        @param clickedIndex: 
        @type clickedIndex: QModelIndex
        """
        row = clickedIndex.row()
        rows = set([mi.row() for mi in self.selectedIndexes()])
        #multiple selection : go to top of selection
        m = min(rows)
        if row != m:
            clickedIndex = self.model().index(m, clickedIndex.column())
        layer = self.img.layersStack[-1 - row]
        self.actionDup.setEnabled(not layer.isAdjustLayer())
        # toggle layer visibility
        if clickedIndex.column() == 0:
            # background layer is always visible
            if row == len(self.img.layersStack) - 1:
                return
            #layer.visible = not(layer.visible)
            layer.setVisible(not (layer.visible))
            if self.currentWin is not None:
                self.currentWin.setVisible(layer.visible)
                if not layer.visible:
                    self.currentWin = None
            if layer.tool is not None:
                layer.tool.setVisible(layer.visible)
            # update stack
            if layer.visible:
                layer.applyToStack()
            else:
                i = layer.getUpperVisibleStackIndex()
                if i >= 0:
                    layer.parentImage.layersStack[i].applyToStack()
                else:
                    # top layer : update only the presentation layer
                    layer.parentImage.prLayer.execute(l=None, pool=None)
            self.img.onImageChanged()
        # update displayed window and active layer
        activeStackIndex = len(self.img.layersStack) - 1 - row
        activeLayer = self.img.setActiveLayer(activeStackIndex)
        if self.currentWin is not None:
            if not self.currentWin.isFloating():
                #self.currentWin.hide()
                self.currentWin = None
        if hasattr(self.img.layersStack[activeStackIndex], "view"):
            self.currentWin = self.img.layersStack[activeStackIndex].view
        if self.currentWin is not None and activeLayer.visible:
            self.currentWin.show()
            self.currentWin.raise_()
            # make self.currentWin the active window
            self.currentWin.activateWindow()
        # update opacity and composition mode for current layer
        opacity = int(layer.opacity * 100)
        self.opacityValue.setText(str('%d ' % opacity))
        self.opacitySlider.setSliderPosition(opacity)
        compositionMode = layer.compositionMode
        ind = self.blendingModeCombo.findData(compositionMode)
        self.blendingModeCombo.setCurrentIndex(ind)
        # draw the right rectangle
        window.label.repaint()

    def initContextMenu(self):
        """
        return the context menu
        @return:
        @rtype: QMenu
        """
        menu = QMenu()
        menu.actionReset = QAction('Reset To Default', None)
        menu.actionLoadImage = QAction('Load New Image', None)
        menu.actionGroupSelection = QAction('Group Selection', None)
        menu.actionAdd2Group = QAction('Add to Group', None)
        # Active layer is not in a group or right clicked layer is in a group

        menu.actionUnGroup = QAction('Ungroup', None)

        # multiple selections
        menu.actionMerge = QAction('Merge Lower', None)
        # merge only adjustment layer with image layer

        # don't dup adjustment layers
        menu.actionUnselect = QAction('Unselect All', None)

        menu.actionRepositionLayer = QAction('Reposition Layer(s)', None)
        menu.actionColorMaskEnable = QAction('Color Mask', None)
        menu.actionOpacityMaskEnable = QAction('Opacity Mask', None)
        menu.actionClippingMaskEnable = QAction('Clipping Mask', None)
        menu.actionMaskDisable = QAction('Disable Mask', None)
        menu.actionMaskInvert = QAction('Invert Mask', None)
        menu.actionMaskReset = QAction('Clear Mask', None)
        menu.actionMaskCopy = QAction('Copy Mask to Clipboard', None)
        menu.actionImageCopy = QAction('Copy Image to Clipboard', None)
        menu.actionMaskPaste = QAction('Paste Mask', None)
        menu.actionImagePaste = QAction('Paste Image', None)
        menu.actionMaskDilate = QAction('Dilate Mask', None)
        menu.actionMaskErode = QAction('Erode Mask', None)
        menu.actionColorMaskEnable.setCheckable(True)
        menu.actionOpacityMaskEnable.setCheckable(True)
        menu.actionClippingMaskEnable.setCheckable(True)
        menu.actionMaskDisable.setCheckable(True)
        ####################
        # Build menu
        ###################
        # group/ungroup
        menu.addAction(menu.actionAdd2Group)
        menu.addAction(menu.actionGroupSelection)
        menu.addAction(menu.actionUnGroup)
        menu.addSeparator()
        menu.addAction(menu.actionUnselect)
        menu.addSeparator()
        menu.addAction(menu.actionRepositionLayer)
        menu.addSeparator()
        # layer
        menu.addAction(menu.actionImageCopy)
        menu.addAction(menu.actionImagePaste)
        menu.addSeparator()
        # mask
        menu.subMenuEnable = menu.addMenu('Mask...')
        menu.subMenuEnable.addAction(menu.actionColorMaskEnable)
        menu.subMenuEnable.addAction(menu.actionOpacityMaskEnable)
        menu.subMenuEnable.addAction(menu.actionClippingMaskEnable)
        menu.subMenuEnable.addAction(menu.actionMaskDisable)
        menu.addAction(menu.actionMaskInvert)
        menu.addAction(menu.actionMaskReset)
        menu.addAction(menu.actionMaskCopy)
        menu.addAction(menu.actionMaskPaste)
        menu.addAction(menu.actionMaskDilate)
        menu.addAction(menu.actionMaskErode)
        menu.addSeparator()
        # miscellaneous
        menu.addAction(menu.actionLoadImage)
        # to link actionDup with a shortcut,
        # it must be set in __init__
        menu.addAction(self.actionDup)
        menu.addAction(menu.actionMerge)
        menu.addAction(menu.actionReset)
        return menu

    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)
Exemple #9
0
class savingDialog(QDialog):
    """
    File dialog with quality and compression sliders.
    We use a standard QFileDialog as a child widget and we
    forward its methods to the top level.
    """
    def __init__(self, parent, text, lastDir):
        """

        @param parent:
        @type parent: QObject
        @param text:
        @type text: str
        @param lastDir:
        @type lastDir:str
        """
        # QDialog __init__
        super().__init__()
        self.setWindowTitle(text)
        # File Dialog
        self.dlg = QFileDialog(caption=text, directory=lastDir)
        # sliders
        self.sliderComp = QbLUeSlider(Qt.Horizontal)
        self.sliderComp.setTickPosition(QSlider.TicksBelow)
        self.sliderComp.setRange(0, 9)
        self.sliderComp.setSingleStep(1)
        self.sliderComp.setValue(5)
        self.sliderQual = QbLUeSlider(Qt.Horizontal)
        self.sliderQual.setTickPosition(QSlider.TicksBelow)
        self.sliderQual.setRange(0, 100)
        self.sliderQual.setSingleStep(10)
        self.sliderQual.setValue(90)
        self.dlg.setVisible(True)
        l = QVBoxLayout()
        h = QHBoxLayout()
        l.addWidget(self.dlg)
        h.addWidget(QLabel("Quality"))
        h.addWidget(self.sliderQual)
        h.addWidget(QLabel("Compression"))
        h.addWidget(self.sliderComp)
        l.addLayout(h)
        self.setLayout(l)

        # file dialog close event handler
        def f():
            self.close()

        self.dlg.finished.connect(f)

    def exec_(self):
        # QDialog exec_
        super().exec_()
        # forward file dialog result
        return self.dlg.result()

    def selectFile(self, fileName):
        self.dlg.selectFile(fileName)

    def selectedFiles(self):
        return self.dlg.selectedFiles()

    def directory(self):
        return self.dlg.directory()
Exemple #10
0
    def __init__(self,
                 targetImage=None,
                 axeSize=500,
                 layer=None,
                 parent=None,
                 mainForm=None):
        super().__init__(parent=parent)
        #self.targetImage = targetImage
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.setMinimumSize(axeSize, axeSize)
        self.setAttribute(Qt.WA_DeleteOnClose)
        # link back to image layer
        # using weak ref for back links
        if type(layer) in weakref.ProxyTypes:
            self.layer = layer
        else:
            self.layer = weakref.proxy(layer)

        # options
        self.options = None

        # exposure slider
        self.sliderExp = QbLUeSlider(Qt.Horizontal)
        self.sliderExp.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.sliderExp.setTickPosition(QSlider.TicksBelow)
        self.sliderExp.setRange(-20, 20)
        self.sliderExp.setSingleStep(1)

        expLabel = QbLUeLabel()
        expLabel.setMaximumSize(150, 30)
        expLabel.setText("Exposure Correction")
        expLabel.doubleClicked.connect(
            lambda: self.sliderExp.setValue(self.defaultExpCorrection))

        self.expValue = QbLUeLabel()
        font = self.expValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("1000 ")
        h = metrics.height()
        self.expValue.setMinimumSize(w, h)
        self.expValue.setMaximumSize(w, h)

        # exp done event handler
        def f():
            self.sliderExp.setEnabled(False)
            self.expValue.setText(
                str("{:+.1f}".format(self.sliderExp.value() *
                                     self.DefaultStep)))
            self.onUpdateExposure(self.layer,
                                  self.sliderExp.value() * self.DefaultStep)
            self.sliderExp.setEnabled(True)

        # exp value changed slot
        def g():
            self.expValue.setText(
                str("{:+.1f}".format(self.sliderExp.value() *
                                     self.DefaultStep)))

        self.sliderExp.valueChanged.connect(g)
        self.sliderExp.sliderReleased.connect(f)

        self.sliderExp.setValue(self.defaultExpCorrection / self.DefaultStep)
        self.expValue.setText(str("{:+.1f}".format(self.defaultExpCorrection)))

        #layout
        l = QVBoxLayout()
        l.setAlignment(Qt.AlignTop)
        l.addWidget(expLabel)
        hl = QHBoxLayout()
        hl.addWidget(self.expValue)
        hl.addWidget(self.sliderExp)
        l.addLayout(hl)
        l.setContentsMargins(20, 0, 20, 25)  # left, top, right, bottom
        #l.addStretch(1)
        self.setLayout(l)
        self.adjustSize()
        self.setWhatsThis("""<b>Exposure Correction</b>
Multiplicative correction in the linear sRGB color space.<br>
""")  # end setWhatsThis
Exemple #11
0
class ExpForm(baseForm):
    defaultExpCorrection = 0.0
    DefaultStep = 0.1

    @classmethod
    def getNewWindow(cls,
                     targetImage=None,
                     axeSize=500,
                     layer=None,
                     parent=None,
                     mainForm=None):
        wdgt = ExpForm(axeSize=axeSize,
                       layer=layer,
                       parent=parent,
                       mainForm=mainForm)
        wdgt.setWindowTitle(layer.name)
        return wdgt

    def __init__(self,
                 targetImage=None,
                 axeSize=500,
                 layer=None,
                 parent=None,
                 mainForm=None):
        super().__init__(parent=parent)
        #self.targetImage = targetImage
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.setMinimumSize(axeSize, axeSize)
        self.setAttribute(Qt.WA_DeleteOnClose)
        # link back to image layer
        # using weak ref for back links
        if type(layer) in weakref.ProxyTypes:
            self.layer = layer
        else:
            self.layer = weakref.proxy(layer)

        # options
        self.options = None

        # exposure slider
        self.sliderExp = QbLUeSlider(Qt.Horizontal)
        self.sliderExp.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.sliderExp.setTickPosition(QSlider.TicksBelow)
        self.sliderExp.setRange(-20, 20)
        self.sliderExp.setSingleStep(1)

        expLabel = QbLUeLabel()
        expLabel.setMaximumSize(150, 30)
        expLabel.setText("Exposure Correction")
        expLabel.doubleClicked.connect(
            lambda: self.sliderExp.setValue(self.defaultExpCorrection))

        self.expValue = QbLUeLabel()
        font = self.expValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("1000 ")
        h = metrics.height()
        self.expValue.setMinimumSize(w, h)
        self.expValue.setMaximumSize(w, h)

        # exp done event handler
        def f():
            self.sliderExp.setEnabled(False)
            self.expValue.setText(
                str("{:+.1f}".format(self.sliderExp.value() *
                                     self.DefaultStep)))
            self.onUpdateExposure(self.layer,
                                  self.sliderExp.value() * self.DefaultStep)
            self.sliderExp.setEnabled(True)

        # exp value changed slot
        def g():
            self.expValue.setText(
                str("{:+.1f}".format(self.sliderExp.value() *
                                     self.DefaultStep)))

        self.sliderExp.valueChanged.connect(g)
        self.sliderExp.sliderReleased.connect(f)

        self.sliderExp.setValue(self.defaultExpCorrection / self.DefaultStep)
        self.expValue.setText(str("{:+.1f}".format(self.defaultExpCorrection)))

        #layout
        l = QVBoxLayout()
        l.setAlignment(Qt.AlignTop)
        l.addWidget(expLabel)
        hl = QHBoxLayout()
        hl.addWidget(self.expValue)
        hl.addWidget(self.sliderExp)
        l.addLayout(hl)
        l.setContentsMargins(20, 0, 20, 25)  # left, top, right, bottom
        #l.addStretch(1)
        self.setLayout(l)
        self.adjustSize()
        self.setWhatsThis("""<b>Exposure Correction</b>
Multiplicative correction in the linear sRGB color space.<br>
""")  # end setWhatsThis

    def writeToStream(self, outStream):
        layer = self.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        outStream.writeQString(self.listWidget1.selectedItems()[0].text())
        outStream.writeInt32(self.sliderExp.value())
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        temp = inStream.readInt32()
        for r in range(self.listWidget1.count()):
            currentItem = self.listWidget1.item(r)
            if currentItem.text() == sel:
                self.listWidget.select(currentItem)
        self.sliderExp.setValue(temp)
        self.update()
        return inStream
Exemple #12
0
    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)
        self.tempCorrection = 6500
        self.tintCorrection = 1.0
        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("Filter 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("00000")
        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)

        self.tintLabel = QbLUeLabel()
        self.tintLabel.setMaximumSize(150, 30)
        self.tintLabel.setText("Tint")
        self.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 slot
        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
            try:
                self.sliderTemp.valueChanged.disconnect()
                self.sliderTemp.sliderReleased.disconnect()
            except RuntimeError:
                pass
            self.tempCorrection = self.slider2Temp(value)
            self.dataChanged.emit()
            self.sliderTemp.valueChanged.connect(tempUpdate)
            self.sliderTemp.sliderReleased.connect(
                lambda: tempUpdate(self.sliderTemp.value()))

        # tint change slot
        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
            try:
                self.sliderTint.valueChanged.disconnect()
                self.sliderTint.sliderReleased.disconnect()
            except RuntimeError:
                pass
            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(self.tintLabel)
        hl1 = QHBoxLayout()
        hl1.addWidget(self.tintValue)
        hl1.addWidget(self.sliderTint)
        l.addLayout(hl1)
        self.setLayout(l)
        self.adjustSize()
        self.setDefaults()
        self.setWhatsThis("""<b>Color Temperature</b><br>
                        <b>Photo Filter</b> uses the multiply blending mode to mimic a warming or cooling filter
                        put in front of the camera lens. The luminosity of the resulting image is corrected.<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
Exemple #13
0
class temperatureForm(baseForm):
    @classmethod
    def getNewWindow(cls,
                     targetImage=None,
                     axeSize=500,
                     layer=None,
                     parent=None):
        wdgt = temperatureForm(axeSize=axeSize, layer=layer, parent=parent)
        wdgt.setWindowTitle(layer.name)
        return wdgt

    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)
        self.tempCorrection = 6500
        self.tintCorrection = 1.0
        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("Filter 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("00000")
        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)

        self.tintLabel = QbLUeLabel()
        self.tintLabel.setMaximumSize(150, 30)
        self.tintLabel.setText("Tint")
        self.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 slot
        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
            try:
                self.sliderTemp.valueChanged.disconnect()
                self.sliderTemp.sliderReleased.disconnect()
            except RuntimeError:
                pass
            self.tempCorrection = self.slider2Temp(value)
            self.dataChanged.emit()
            self.sliderTemp.valueChanged.connect(tempUpdate)
            self.sliderTemp.sliderReleased.connect(
                lambda: tempUpdate(self.sliderTemp.value()))

        # tint change slot
        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
            try:
                self.sliderTint.valueChanged.disconnect()
                self.sliderTint.sliderReleased.disconnect()
            except RuntimeError:
                pass
            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(self.tintLabel)
        hl1 = QHBoxLayout()
        hl1.addWidget(self.tintValue)
        hl1.addWidget(self.sliderTint)
        l.addLayout(hl1)
        self.setLayout(l)
        self.adjustSize()
        self.setDefaults()
        self.setWhatsThis("""<b>Color Temperature</b><br>
                        <b>Photo Filter</b> uses the multiply blending mode to mimic a warming or cooling filter
                        put in front of the camera lens. The luminosity of the resulting image is corrected.<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 enableSliders(self):
        self.sliderTemp.setEnabled(True)
        for item in [self.sliderTint, self.tintLabel, self.tintValue]:
            item.setEnabled(self.options['Chromatic Adaptation'])

    def setDefaults(self):
        # prevent multiple updates
        try:
            self.dataChanged.disconnect()
        except RuntimeError:
            pass
        self.listWidget1.unCheckAll()
        self.listWidget1.checkOption(self.listWidget1.intNames[0])
        self.enableSliders()
        self.sliderTemp.setValue(round(self.temp2Slider(self.tempCorrection)))
        self.sliderTint.setValue(round(self.tint2Slider(self.defaultTint)))
        self.dataChanged.connect(self.updateLayer)

    def updateLayer(self):
        """
        data changed slot
        """
        self.enableSliders()
        self.layer.applyToStack()
        self.layer.parentImage.onImageChanged()

    @staticmethod
    def slider2Temp(v):
        return v * 100

    @staticmethod
    def temp2Slider(v):
        return int(v / 100)

    @staticmethod
    def sliderTemp2User(v):
        return v * 100

    @staticmethod
    def slider2Tint(v):
        return (v - 50) / 50

    @staticmethod
    def tint2Slider(v):
        return int((1.0 + v) * 50.0)

    @staticmethod
    def sliderTint2User(v):
        return int((v - 50) / 5.0)

    def writeToStream(self, outStream):
        layer = self.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        outStream.writeQString(self.listWidget1.selectedItems()[0].text())
        outStream.writeInt32(self.sliderTemp.value() * 100)
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        temp = inStream.readInt32()
        for r in range(self.listWidget1.count()):
            currentItem = self.listWidget1.item(r)
            if currentItem.text() == sel:
                self.listWidget.select(currentItem)
        self.sliderTemp.setValue(temp // 100)
        self.update()
        return inStream
Exemple #14
0
    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
Exemple #15
0
class filterForm(baseForm):
    # dataChanged = QtCore.Signal()
    @classmethod
    def getNewWindow(cls,
                     targetImage=None,
                     axeSize=500,
                     layer=None,
                     parent=None,
                     mainForm=None):
        wdgt = filterForm(targetImage=targetImage,
                          axeSize=axeSize,
                          layer=layer,
                          parent=parent,
                          mainForm=mainForm)
        wdgt.setWindowTitle(layer.name)
        return wdgt

    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 updateLayer(self):
        """
        dataChanged Slot
        """
        self.enableSliders()
        for key in self.listWidget1.options:
            if self.listWidget1.options[key]:
                self.kernelCategory = self.filterDict[key]
                break
        self.layer.applyToStack()
        self.layer.parentImage.onImageChanged()

    def enableSliders(self):
        opt = self.listWidget1.options
        useRadius = opt[self.optionList[0]] or opt[self.optionList[2]] or opt[
            self.optionList[3]]
        useAmount = opt[self.optionList[0]] or opt[self.optionList[2]]
        useTone = opt[self.optionList[3]]
        self.sliderRadius.setEnabled(useRadius)
        self.sliderAmount.setEnabled(useAmount)
        self.sliderTone.setEnabled(useTone)
        self.radiusValue.setEnabled(self.sliderRadius.isEnabled())
        self.amountValue.setEnabled(self.sliderAmount.isEnabled())
        self.toneValue.setEnabled(self.sliderTone.isEnabled())
        self.radiusLabel.setEnabled(self.sliderRadius.isEnabled())
        self.amountLabel.setEnabled(self.sliderAmount.isEnabled())
        self.toneLabel.setEnabled(self.sliderTone.isEnabled())

    def writeToStream(self, outStream):
        layer = self.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        outStream.writeQString(self.listWidget1.selectedItems()[0].text())
        outStream.writeFloat32(self.sliderRadius.value())
        outStream.writeFloat32(self.sliderAmount.value())
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        radius = inStream.readFloat32()
        amount = inStream.readFloat32()
        for r in range(self.listWidget1.count()):
            currentItem = self.listWidget1.item(r)
            if currentItem.text() == sel:
                self.listWidget.select(currentItem)
        self.sliderRadius.setValue(radius)
        self.sliderAmount.setValue(amount)
        self.repaint()
        return inStream
Exemple #16
0
class CoBrSatForm(baseForm):
    """
    Contrast, Brightness, Saturation adjustment form
    """
    layerTitle = "Cont/Bright/Sat"
    contrastDefault = 0.0
    brightnessDefault = 0.0
    saturationDefault = 0.0

    @classmethod
    def getNewWindow(cls,
                     targetImage=None,
                     axeSize=500,
                     layer=None,
                     parent=None):
        wdgt = CoBrSatForm(targetImage=targetImage,
                           axeSize=axeSize,
                           layer=layer,
                           parent=parent)
        wdgt.setWindowTitle(layer.name)
        return wdgt

    @classmethod
    def slider2Contrast(cls, v):
        return v / 10

    @classmethod
    def contrast2Slider(cls, v):
        return v * 10

    @classmethod
    def slider2Saturation(cls, v):
        return v / 100 - 0.5

    @classmethod
    def saturation2Slider(cls, v):
        return v * 100 + 50

    @classmethod
    def slidersaturation2User(cls, v):
        return v - 50

    @classmethod
    def slider2Brightness(cls, v):
        return v / 100 - 0.5

    @classmethod
    def brightness2Slider(cls, v):
        return v * 100 + 50

    @classmethod
    def sliderBrightness2User(cls, v):
        return v - 50

    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)
        self.setMinimumSize(axeSize, axeSize + 100)
        # 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')
        l.setAlignment(Qt.AlignTop)
        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>
            <b>Brightness</b> and <b>Saturation</b> corrections are non linear to limit clipping.<br>
            Sliders are <b>reset</b> to their default value by double clicking the name of the slider.<br>
            """)  # end setWhatsThis

    def setContrastSpline(self, a, b, d, T):
        """
        Updates and displays the contrast spline viewer.
        The form is created only once.
        (Cf also rawForm.setCoBrSat.setContrastSpline).
        @param a: x_coordinates
        @type a:
        @param b: y-coordinates
        @type b:
        @param d: tangent slopes
        @type d:
        @param T: spline
        @type T: ndarray dtype=float
        """
        axeSize = 200
        if self.contrastForm is None:
            form = graphicsSplineForm.getNewWindow(targetImage=None,
                                                   axeSize=axeSize,
                                                   layer=self.layer,
                                                   parent=None)
            form.setAttribute(Qt.WA_DeleteOnClose, on=False)
            form.setWindowTitle('Contrast Curve')
            form.setMaximumSize(300, 400)
            self.contrastForm = form
            window = self.parent().parent()
            dock = stateAwareQDockWidget(self.parent())
            dock.setWidget(form)
            dock.setWindowFlags(form.windowFlags())
            dock.setWindowTitle(form.windowTitle())
            dock.setStyleSheet(
                "QGraphicsView{margin: 10px; border-style: solid; border-width: 1px; border-radius: 1px;}"
            )
            window.addDockWidget(Qt.LeftDockWidgetArea, dock)
            self.dock = dock

            # curve changed slot
            def f():
                self.layer.applyToStack()
                self.layer.parentImage.onImageChanged()

            form.scene().quadricB.curveChanged.sig.connect(f)
        else:
            form = self.contrastForm
        # update the curve
        form.scene().setSceneRect(-25, -axeSize - 25, axeSize + 50,
                                  axeSize + 50)
        form.scene().quadricB.setCurve(a * axeSize, b * axeSize, d,
                                       T * axeSize)
        self.dock.showNormal()

    def updateHists(self):
        """
        Update the histogram displayed under
        the contrast spline.
        """
        if self.contrastForm is not None:
            self.contrastForm.updateHists()

    def enableSliders(self):
        self.sliderContrast.setEnabled(True)
        self.sliderSaturation.setEnabled(True)
        self.sliderBrightness.setEnabled(True)

    def setDefaults(self):
        try:
            self.dataChanged.disconnect()
        except RuntimeError:
            pass
        self.listWidget1.unCheckAll()
        self.listWidget1.checkOption(self.listWidget1.intNames[0])
        self.listWidget2.unCheckAll()
        self.listWidget2.checkOption(self.listWidget2.intNames[0])
        self.enableSliders()
        self.contrastCorrection = self.contrastDefault
        self.sliderContrast.setValue(
            round(self.contrast2Slider(self.contrastCorrection)))
        self.satCorrection = self.saturationDefault
        self.sliderSaturation.setValue(
            round(self.saturation2Slider(self.satCorrection)))
        self.brightnessCorrection = self.brightnessDefault
        self.sliderBrightness.setValue(
            round(self.brightness2Slider(self.brightnessCorrection)))
        self.dataChanged.connect(self.updateLayer)

    def updateLayer(self):
        """
        data changed slot.
        """
        self.enableSliders()
        self.layer.applyToStack()
        self.layer.parentImage.onImageChanged()
        # enable/disable options relative to multi-mode
        for intname in ['High', 'manualCurve']:
            item = self.listWidget2.items[intname]
            if self.options['Multi-Mode']:
                item.setFlags(item.flags() | Qt.ItemIsEnabled)
            else:
                item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
        # show/hide contrast curve
        cf = getattr(self, 'dock', None)
        if cf is None:
            return
        if self.options['manualCurve'] and self.options['Multi-Mode']:
            cf.showNormal()
        else:
            cf.hide()

    def writeToStream(self, outStream):
        layer = self.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        for item in self.listWidget1.selectedItems():
            outStream.writeQString(item.text())
        outStream.writeInt32(self.sliderContrast.value())
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        temp = inStream.readInt32()
        for r in range(self.listWidget1.count()):
            currentItem = self.listWidget1.item(r)
            if currentItem.text() == sel:
                self.listWidget.select(currentItem)
        self.sliderContrast.setValue(temp)
        self.update()
        return inStream
Exemple #17
0
    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)
        self.setMinimumSize(axeSize, axeSize + 100)
        # 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')
        l.setAlignment(Qt.AlignTop)
        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>
            <b>Brightness</b> and <b>Saturation</b> corrections are non linear to limit clipping.<br>
            Sliders are <b>reset</b> to their default value by double clicking the name of the slider.<br>
            """)  # end setWhatsThis
Exemple #18
0
    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)

        #######################################
        # 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.listWidget2 = optionsWidget(
            options=optionList1,
            optionNames=optionNames1,
            exclusive=True,
            changed=lambda: self.dataChanged.emit(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)  # signal send new value as parameter
        self.sliderTemp.sliderReleased.connect(lambda: self.tempUpdate(
            self.sliderTemp.value()))  # signal pass no parameter

        # tint slider
        self.sliderTint = QbLUeSlider(Qt.Horizontal)
        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 pass 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
            try:
                self.sliderExp.valueChanged.disconnect()
                self.sliderExp.sliderReleased.disconnect()
            except RuntimeError:
                pass
            # 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 pass no parameter

        self.sliderExp.valueChanged.connect(
            expUpdate)  # send new value as parameter
        self.sliderExp.sliderReleased.connect(lambda: expUpdate(
            self.sliderExp.value()))  # signal pass 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
            try:
                self.sliderBrightness.valueChanged.disconnect()
                self.sliderBrightness.sliderReleased.disconnect()
            except RuntimeError:
                pass
            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
            try:
                self.sliderCont.valueChanged.disconnect()
                self.sliderCont.sliderReleased.disconnect()
            except RuntimeError:
                pass
            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
            try:
                self.sliderSat.valueChanged.disconnect()
                self.sliderSat.sliderReleased.disconnect()
            except RuntimeError:
                pass
            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.addStretch(1)
        self.setLayout(l)
        self.adjustSize()
        self.setDefaults()
        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> 
                    The cuvre 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>
                    """)  # end of setWhatsThis
Exemple #19
0
    def __init__(self, parent):
        super(QLayerView, self).__init__(parent)
        self.img = None
        # graphic form to show : it
        # should correspond to the currently selected layer
        self.currentWin = None
        # mouse click event
        self.clicked.connect(self.viewClicked)

        # set behavior and styles
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        delegate = itemDelegate(parent=self)
        self.setItemDelegate(delegate)
        ic1 = QImage(":/images/resources/eye-icon.png")
        ic2 = QImage(":/images/resources/eye-icon-strike.png")
        delegate.px1 = QPixmap.fromImage(ic1)
        delegate.px2 = QPixmap.fromImage(ic2)
        ic1.invertPixels()
        ic2.invertPixels()
        delegate.inv_px1 = QPixmap.fromImage(ic1)
        delegate.inv_px2 = QPixmap.fromImage(ic2)
        self.setIconSize(QSize(20, 15))
        self.verticalHeader().setMinimumSectionSize(-1)
        self.verticalHeader().setDefaultSectionSize(
            self.verticalHeader().minimumSectionSize())
        self.horizontalHeader().setMinimumSectionSize(40)
        self.horizontalHeader().setDefaultSectionSize(40)

        # drag and drop
        self.setDragDropMode(QAbstractItemView.DragDrop)
        self.setDefaultDropAction(Qt.MoveAction)
        self.setDragDropOverwriteMode(False)
        self.setDragEnabled(True)
        self.setAcceptDrops(True)
        self.setDropIndicatorShown(True)

        ################################
        # layer property GUI :
        # preview, blending mode, opacity, mask color
        ################################
        # Preview option
        # We should use a QListWidget or a custom optionsWidget
        # (cf. utils.py) :  adding it to QVBoxLayout with mode
        # Qt.AlignBottom does not work.
        self.previewOptionBox = QCheckBox('Preview')
        self.previewOptionBox.setMaximumSize(100, 30)

        # View/Preview changed event handler
        def m(state):  # state : Qt.Checked Qt.UnChecked
            if self.img is None:
                return
            self.img.useThumb = (state == Qt.Checked)
            window.updateStatus()
            self.img.cacheInvalidate()
            for layer in self.img.layersStack:
                layer.autoclone = True  # auto update cloning layers
                layer.knitted = False
            try:
                QApplication.setOverrideCursor(
                    Qt.WaitCursor
                )  # TODO 18/04/18 waitcursor is called by applytostack?
                QApplication.processEvents()
                # update the whole stack
                self.img.layersStack[0].applyToStack()
                self.img.onImageChanged()  # TODO added 30/11/18 validate

            finally:
                for layer in self.img.layersStack:
                    layer.autoclone = False  # reset flags
                    layer.knitted = False
                QApplication.restoreOverrideCursor()
                QApplication.processEvents()
            # window.label.repaint()  # TODO removed 30/11/18 replaced by onImageChange above

        self.previewOptionBox.stateChanged.connect(m)
        self.previewOptionBox.setChecked(True)  # m is not triggered

        # title
        titleLabel = QLabel('Layer')
        titleLabel.setMaximumSize(100, 30)

        # opacity slider
        self.opacitySlider = QbLUeSlider(Qt.Horizontal)
        self.opacitySlider.setStyleSheet(
            QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.opacitySlider.setTickPosition(QSlider.TicksBelow)
        self.opacitySlider.setRange(0, 100)
        self.opacitySlider.setSingleStep(1)
        self.opacitySlider.setSliderPosition(100)

        self.opacityValue = QLabel()
        font = self.opacityValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("100 ")
        h = metrics.height()
        self.opacityValue.setMinimumSize(w, h)
        self.opacityValue.setMaximumSize(w, h)
        self.opacityValue.setText('100 ')

        # opacity value changed event handler
        def f1():
            self.opacityValue.setText(str('%d ' % self.opacitySlider.value()))

        # opacity slider released event handler
        def f2():
            try:
                layer = self.img.getActiveLayer()
                layer.setOpacity(self.opacitySlider.value())
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.opacitySlider.valueChanged.connect(f1)
        self.opacitySlider.sliderReleased.connect(f2)

        # mask color slider
        maskSlider = QbLUeSlider(Qt.Horizontal)
        maskSlider.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        maskSlider.setTickPosition(QSlider.TicksBelow)
        maskSlider.setRange(0, 100)
        maskSlider.setSingleStep(1)
        maskSlider.setSliderPosition(100)
        self.maskSlider = maskSlider

        self.maskValue = QLabel()
        font = self.maskValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("100 ")
        h = metrics.height()
        self.maskValue.setMinimumSize(w, h)
        self.maskValue.setMaximumSize(w, h)
        self.maskValue.setText('100 ')

        # mask value changed event handler
        def g1():
            self.maskValue.setText(str('%d ' % self.maskSlider.value()))

        # mask slider released event handler
        def g2():
            try:
                layer = self.img.getActiveLayer()
                layer.setColorMaskOpacity(self.maskSlider.value())
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.maskSlider.valueChanged.connect(g1)
        self.maskSlider.sliderReleased.connect(g2)

        # blending mode combo box
        compLabel = QLabel()
        compLabel.setText("Blend")

        self.compositionModeDict = OrderedDict([
            ('Normal', QPainter.CompositionMode_SourceOver),
            ('Plus', QPainter.CompositionMode_Plus),
            ('Multiply', QPainter.CompositionMode_Multiply),
            ('Screen', QPainter.CompositionMode_Screen),
            ('Overlay', QPainter.CompositionMode_Overlay),
            ('Darken', QPainter.CompositionMode_Darken),
            ('Lighten', QPainter.CompositionMode_Lighten),
            ('Color Dodge', QPainter.CompositionMode_ColorDodge),
            ('Color Burn', QPainter.CompositionMode_ColorBurn),
            ('Hard Light', QPainter.CompositionMode_HardLight),
            ('Soft Light', QPainter.CompositionMode_SoftLight),
            ('Difference', QPainter.CompositionMode_Difference),
            ('Exclusion', QPainter.CompositionMode_Exclusion)
        ])
        self.blendingModeCombo = QComboBox()
        for key in self.compositionModeDict:
            self.blendingModeCombo.addItem(key, self.compositionModeDict[key])

        # combo box item chosen event handler
        def g(ind):
            s = self.blendingModeCombo.currentText()
            try:
                layer = self.img.getActiveLayer()
                layer.compositionMode = self.compositionModeDict[str(s)]
                layer.applyToStack()
                self.img.onImageChanged()
            except AttributeError:
                return

        self.blendingModeCombo.currentIndexChanged.connect(g)
        # self.blendingModeCombo.activated.connect(g)  # TODO activated changed to currentIndexChanged 08/10/18 validate

        #layout
        l = QVBoxLayout()
        l.setAlignment(Qt.AlignTop)
        hl0 = QHBoxLayout()
        hl0.addWidget(titleLabel)
        hl0.addStretch(1)
        hl0.addWidget(self.previewOptionBox)
        l.addLayout(hl0)
        hl = QHBoxLayout()
        hl.addWidget(QLabel('Opacity'))
        hl.addWidget(self.opacityValue)
        hl.addWidget(self.opacitySlider)
        l.addLayout(hl)
        hl1 = QHBoxLayout()
        hl1.addWidget(QLabel('Mask Color'))
        hl1.addWidget(self.maskValue)
        hl1.addWidget(self.maskSlider)
        l.addLayout(hl1)
        l.setContentsMargins(0, 0, 10, 0)  # left, top, right, bottom
        hl2 = QHBoxLayout()
        hl2.addWidget(compLabel)
        hl2.addWidget(self.blendingModeCombo)
        l.addLayout(hl2)
        for layout in [hl, hl1, hl2]:
            layout.setContentsMargins(5, 0, 0, 0)
        # this layout must be added to the propertyWidget object loaded from blue.ui :
        # we postpone it after loading of the main form, in blue.py.
        self.propertyLayout = l

        # shortcut actions
        self.actionDup = QAction('Duplicate layer', None)
        self.actionDup.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_J))
        self.addAction(self.actionDup)

        def dup():
            row = self.selectedIndexes()[0].row()
            # Stack index
            index = len(self.img.layersStack) - row - 1
            layer = self.img.layersStack[index]
            if layer.isAdjustLayer():
                return
            # add new layer to stack and set it to active
            self.img.dupLayer(index=index)
            # update layer view
            self.setLayers(self.img)

        self.actionDup.triggered.connect(dup)
        self.setWhatsThis("""<b>Layer Stack</b>
To <b>toggle the layer visibility</b> click on the Eye icon.<br>
To <b>add a mask</b> use the context menu to enable it and paint pixels with the Mask/Unmask tools in the left pane.<br>
For <b>color mask<b/b>: <br>
    &nbsp; green pixels are masked,<br>
    &nbsp; red pixels are unmasked.<br>
Note that upper visible layers slow down mask edition.<br>
""")  # end of setWhatsThis
Exemple #20
0
class rawForm(baseForm):
    """
    Postprocessing of raw files.
    """
    dataChanged = QtCore.Signal(int)

    @classmethod
    def getNewWindow(cls,
                     targetImage=None,
                     axeSize=500,
                     layer=None,
                     parent=None):
        wdgt = rawForm(axeSize=axeSize,
                       targetImage=targetImage,
                       layer=layer,
                       parent=parent)
        wdgt.setWindowTitle(layer.name)
        return wdgt

    @staticmethod
    def slider2Temp(v):
        return 2000 + v * v

    @staticmethod
    def temp2Slider(T):
        return np.sqrt(T - 2000)

    @staticmethod
    def slider2Tint(v):
        return 0.1 + 0.0125 * v  # 0.2 + 0.0125 * v  # wanted range : 0.2...2.5
        # coeff = (self.tempCorrection / 4000 - 1) * 1.2 # experimental formula
        # eturn coeff + 0.01*v

    @staticmethod
    def tint2Slider(t):
        return (t - 0.1) / 0.0125
        # coeff = (self.tempCorrection / 4000 - 1) * 1.2 # experimental formula
        # return (t-coeff)/0.01
        # displayed value

    @staticmethod
    def sliderTint2User(v):
        return v - 75  # ((slider2Tint(v) - 1)*100)

    @staticmethod
    def slider2Exp(v):
        return 2**((v - 50) / 15.0)

    @staticmethod
    def exp2Slider(e):
        return round(15 * np.log2(e) + 50)

    @staticmethod
    def sliderExp2User(v):
        return (v - 50) / 15

    @staticmethod
    def slider2Cont(v):
        return v

    @staticmethod
    def cont2Slider(e):
        return e

    @staticmethod
    def slider2Br(v):
        return (np.power(3, v / 50) - 1) / 2

    @staticmethod
    def br2Slider(v):
        return 50 * log(2 * v + 1, 3)  # int(round(50.0 * e))

    @staticmethod
    def brSlider2User(v):
        return v - 50

    @staticmethod
    def slider2Sat(v):
        return v - 50

    @staticmethod
    def sat2Slider(e):
        return e + 50

    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)

        #######################################
        # 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.listWidget2 = optionsWidget(
            options=optionList1,
            optionNames=optionNames1,
            exclusive=True,
            changed=lambda: self.dataChanged.emit(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)  # signal send new value as parameter
        self.sliderTemp.sliderReleased.connect(lambda: self.tempUpdate(
            self.sliderTemp.value()))  # signal pass no parameter

        # tint slider
        self.sliderTint = QbLUeSlider(Qt.Horizontal)
        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 pass 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
            try:
                self.sliderExp.valueChanged.disconnect()
                self.sliderExp.sliderReleased.disconnect()
            except RuntimeError:
                pass
            # 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 pass no parameter

        self.sliderExp.valueChanged.connect(
            expUpdate)  # send new value as parameter
        self.sliderExp.sliderReleased.connect(lambda: expUpdate(
            self.sliderExp.value()))  # signal pass 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
            try:
                self.sliderBrightness.valueChanged.disconnect()
                self.sliderBrightness.sliderReleased.disconnect()
            except RuntimeError:
                pass
            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
            try:
                self.sliderCont.valueChanged.disconnect()
                self.sliderCont.sliderReleased.disconnect()
            except RuntimeError:
                pass
            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
            try:
                self.sliderSat.valueChanged.disconnect()
                self.sliderSat.sliderReleased.disconnect()
            except RuntimeError:
                pass
            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.addStretch(1)
        self.setLayout(l)
        self.adjustSize()
        self.setDefaults()
        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> 
                    The cuvre 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>
                    """)  # end of setWhatsThis

    def close(self):
        """
        Overrides QWidget.close to
        close toneForm and contrastForm
        @return:
        @rtype: boolean
        """
        delete = self.testAttribute(Qt.WA_DeleteOnClose)
        for attr in ['toneForm', 'contrastForm']:
            form = getattr(self, attr, None)
            if delete and form is not None:
                dock = form.parent()
                dock.setAttribute(Qt.WA_DeleteOnClose)
                form.setAttribute(Qt.WA_DeleteOnClose)
                dock.close()
                form.close()
        return super().close()

    def showToneSpline(self):
        """
        On first call, init and show the Tone Curve form.
        Otherwise, show the form.
        Return True if called for the first time, False otherwise.
        @return:
        @rtype: boolean
        """
        axeSize = 200
        if self.toneForm is None:
            form = graphicsToneForm.getNewWindow(targetImage=self.targetImage,
                                                 axeSize=axeSize,
                                                 layer=self.layer,
                                                 parent=self,
                                                 curveType='cubic')
            form.setWindowFlags(Qt.WindowStaysOnTopHint)
            form.setAttribute(Qt.WA_DeleteOnClose, on=False)
            form.setWindowTitle('Cam Tone Curve')
            form.setButtonText('Reset Curve')
            # get base curve from profile
            toneCurve = dngProfileToneCurve(
                self.dngDict.get('ProfileToneCurve', []))
            form.baseCurve = [
                QPointF(x * axeSize, -y * axeSize)
                for x, y in zip(toneCurve.dataX, toneCurve.dataY)
            ]

            def f():
                layer = self.layer
                layer.bufCache_HSV_CV32 = None
                layer.applyToStack()
                layer.parentImage.onImageChanged()

            form.scene().quadricB.curveChanged.sig.connect(f)
            self.toneForm = form
            dockT = stateAwareQDockWidget(self.parent())
            dockT.setWindowFlags(form.windowFlags())
            dockT.setWindowTitle(form.windowTitle())
            dockT.setStyleSheet(
                "QGraphicsView{margin: 10px; border-style: solid; border-width: 1px; border-radius: 1px;}"
            )
            window = self.parent().parent()
            window.addDockWidget(Qt.LeftDockWidgetArea, dockT)
            self.dockT = dockT
            dockT.setWidget(form)
            showFirst = True
            form.setWhatsThis("""<b>Camera Profile Tone Curve</b><br>
                            The profile curve, if any, is applied as a starting point for user adjustments,
                            after raw post-processing.
                            Its input and output are in <b>linear</b> gamma.
                            The curve is shown in red and cannot be changed.<br>
                            A user curve, shown in black, is editable and is applied right after the
                            former.<br>         
                            """)  # end of setWhatsThis
        else:
            form = self.toneForm
            showFirst = False
        form.scene().setSceneRect(-25, -axeSize - 25, axeSize + 50,
                                  axeSize + 50)
        self.dockT.showNormal()
        return showFirst

    def setContrastSpline(self, a, b, d, T):
        """
        Updates and displays the contrast spline Form.
        The form is created if needed.
        (Cf. also CoBrStaForm setContrastSpline).
        @param a: x_coordinates
        @type a:
        @param b: y-coordinates
        @type b:
        @param d: tangent slopes
        @type d:
        @param T: spline
        @type T: ndarray dtype=float
        """
        axeSize = 200
        if self.contrastForm is None:
            form = graphicsSplineForm.getNewWindow(targetImage=None,
                                                   axeSize=axeSize,
                                                   layer=self.layer,
                                                   parent=None)
            form.setWindowFlags(Qt.WindowStaysOnTopHint)
            form.setAttribute(Qt.WA_DeleteOnClose, on=False)
            form.setWindowTitle('Contrast Curve')

            def f():
                layer = self.layer
                layer.applyToStack()
                layer.parentImage.onImageChanged()

            form.scene().quadricB.curveChanged.sig.connect(f)
            self.contrastForm = form
            dockC = stateAwareQDockWidget(self.parent())
            dockC.setWindowFlags(form.windowFlags())
            dockC.setWindowTitle(form.windowTitle())
            dockC.setStyleSheet(
                "QGraphicsView{margin: 10px; border-style: solid; border-width: 1px; border-radius: 1px;}"
            )
            window = self.parent().parent()
            window.addDockWidget(Qt.LeftDockWidgetArea, dockC)
            self.dockC = dockC
            dockC.setWidget(form)
        else:
            form = self.contrastForm
        # update the curve
        form.scene().setSceneRect(-25, -axeSize - 25, axeSize + 50,
                                  axeSize + 50)  # TODO added 15/07/18
        form.scene().quadricB.setCurve(a * axeSize, b * axeSize, d,
                                       T * axeSize)
        self.dockC.showNormal()

    # temp changed  event handler
    def tempUpdate(self, value):
        self.tempValue.setText(
            str("{:.0f}".format(self.slider2Temp(self.sliderTemp.value()))))
        # move not yet terminated or value not modified
        if self.sliderTemp.isSliderDown() or self.slider2Temp(
                value) == self.tempCorrection:
            return
        try:
            self.sliderTemp.valueChanged.disconnect()
            self.sliderTemp.sliderReleased.disconnect()
        except RuntimeError:
            pass
        self.tempCorrection = self.slider2Temp(self.sliderTemp.value())
        # get multipliers (temperatureAndTint2Multipliers returns the camera neutral)
        multipliers = [
            1 / m for m in temperatureAndTint2Multipliers(
                self.tempCorrection, 1.0, self.XYZ2CameraMatrix, self.dngDict)
        ]
        multipliers[1] *= self.tintCorrection
        self.rawMultipliers = multipliers
        m = multipliers[1]
        self.rawMultipliers = [self.rawMultipliers[i] / m for i in range(4)]
        self.dataChanged.emit(1)
        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 change event handler
    def tintUpdate(self, value):
        self.tintValue.setText(
            str("{:.0f}".format(self.sliderTint2User(
                self.sliderTint.value()))))
        # move not yet terminated or value not modified
        if self.sliderTint.isSliderDown() or self.slider2Tint(
                value) == self.tintCorrection:
            return
        try:
            self.sliderTint.valueChanged.disconnect()
            self.sliderTint.sliderReleased.disconnect()
        except RuntimeError:
            pass
        self.tintCorrection = self.slider2Tint(self.sliderTint.value())
        # get multipliers (temperatureAndTint2Multipliers returns the camera neutral)
        multipliers = [
            1 / m for m in temperatureAndTint2Multipliers(
                self.tempCorrection, 1.0, self.XYZ2CameraMatrix, self.dngDict)
        ]
        multipliers[1] *= self.tintCorrection
        self.rawMultipliers = multipliers
        m = multipliers[1]
        self.rawMultipliers = [self.rawMultipliers[i] / m for i in range(4)]
        self.dataChanged.emit(1)
        self.sliderTint.valueChanged.connect(self.tintUpdate)
        self.sliderTint.sliderReleased.connect(lambda: self.tintUpdate(
            self.sliderTint.value()))  # signal has no parameter)

    def setRawMultipliers(self, m0, m1, m2, sampling=True):
        mi = min(m0, m1, m2)
        m0, m1, m2 = m0 / mi, m1 / mi, m2 / mi
        self.rawMultipliers = [m0, m1, m2, m1]
        # convert multipliers to White Point RGB coordinates, modulo tint green correction (mult[1] = tint*WP_G)
        # invMultipliers = [self.daylight[i] / self.rawMultipliers[i] for i in range(3)]
        invMultipliers = [1 / self.rawMultipliers[i]
                          for i in range(3)]  # TODO modified 11/11/18 validate
        try:
            self.sliderTemp.valueChanged.disconnect()
            self.sliderTint.valueChanged.disconnect()
        except RuntimeError:
            pass
        # get temp and tint
        temp, tint = multipliers2TemperatureAndTint(*invMultipliers,
                                                    self.XYZ2CameraMatrix)
        self.tintCorrection = tint
        self.sliderTemp.setValue(self.temp2Slider(temp))
        self.sliderTint.setValue(self.tint2Slider(tint))
        self.tempValue.setText(
            str("{:.0f}".format(self.slider2Temp(self.sliderTemp.value()))))
        self.tintValue.setText(
            str("{:.0f}".format(self.sliderTint2User(
                self.sliderTint.value()))))
        self.sliderTemp.valueChanged.connect(self.tempUpdate)
        self.sliderTint.valueChanged.connect(self.tintUpdate)
        self.sampleMultipliers = sampling
        self.dataChanged.emit(1)

    def updateLayer(self, level):
        """
        data changed event handler.
        @param level: 3: redo contrast and saturation, 2: previous + camera profile stuff, 1: all
        @type level: int
        """
        if level == 1:
            # force all
            self.layer.bufCache_HSV_CV32 = None
            self.layer.postProcessCache = None
        elif level == 2:
            # force camera profile stuff
            self.layer.bufCache_HSV_CV32 = None
        elif level == 3:
            # keep the 2 cache buffers
            pass
        # contrast curve
        cf = getattr(self, 'dockC', None)
        if cf is not None:
            if self.options['manualCurve']:
                cf.showNormal()
            else:
                cf.hide()
        # tone curve
        ct = getattr(self, 'dockT', None)
        if ct is not None:
            if self.options['cpToneCurve']:
                ct.showNormal()
            else:
                ct.hide()
        self.enableSliders()
        self.layer.applyToStack()
        self.layer.parentImage.onImageChanged()

    def enableSliders(self):
        useUserWB = self.listWidget2.options["User WB"]
        useUserExp = not self.listWidget1.options["Auto Brightness"]
        self.sliderTemp.setEnabled(useUserWB)
        self.sliderTint.setEnabled(useUserWB)
        self.sliderExp.setEnabled(useUserExp)
        # self.sliderHigh.setEnabled(useUserExp)
        self.tempValue.setEnabled(self.sliderTemp.isEnabled())
        self.tintValue.setEnabled(self.sliderTint.isEnabled())
        self.expValue.setEnabled(self.sliderExp.isEnabled())
        # self.highValue.setEnabled(self.sliderHigh.isEnabled())
        self.tempLabel.setEnabled(self.sliderTemp.isEnabled())
        self.tintLabel.setEnabled(self.sliderTint.isEnabled())
        self.expLabel.setEnabled(self.sliderExp.isEnabled())
        # self.highLabel.setEnabled(self.sliderHigh.isEnabled())

    def setDefaults(self):
        self.dngDict = self.cameraProfilesCombo.itemData(0)
        self.listWidget1.unCheckAll()
        self.listWidget2.unCheckAll()
        self.listWidget1.checkOption(self.listWidget1.intNames[0])
        self.listWidget1.checkOption(self.listWidget1.intNames[1])
        self.listWidget2.checkOption(self.listWidget2.intNames[1])
        self.enableSliders()
        self.denoiseValue = 0  # denoising off
        self.overexpValue = 0  # clip
        self.tempCorrection = self.asShotTemp
        self.tintCorrection = 1.0
        self.expCorrection = 1.0
        # self.highCorrection = 3.0  # restoration of overexposed highlights. 0: clip 1:unclip, 2: blend, 3...: rebuild
        self.contCorrection = 0.0  # TODO change 5.0 to 0.0 6/11/2018 validate
        # self.noiseCorrection = 0
        self.satCorrection = 0.0
        self.brCorrection = 1.0
        # prevent multiple updates
        try:
            self.dataChanged.disconnect()
        except RuntimeError:
            pass
        self.sliderTemp.setValue(round(self.temp2Slider(self.tempCorrection)))
        self.sliderTint.setValue(round(self.tint2Slider(self.tintCorrection)))
        self.sliderExp.setValue(self.exp2Slider(self.expCorrection))
        # self.sliderHigh.setValue(self.highCorrection)
        self.sliderCont.setValue(self.cont2Slider(self.contCorrection))
        self.sliderBrightness.setValue(self.br2Slider(self.brCorrection))
        self.sliderSat.setValue(self.sat2Slider(self.satCorrection))
        self.dataChanged.connect(self.updateLayer)

    def setCameraProfilesCombo(self):
        """
        # for each item, text is the filename and data is the corresponding dict
        @return: the currently selected item data
        @rtype: dict
        """
        self.cameraProfilesCombo = QComboBox()
        files = [self.targetImage.filename]
        files.extend(getDngProfileList(self.targetImage.cameraModel()))
        # load profiles
        items = OrderedDict([
            (basename(f)[:-4] if i > 0 else 'Embedded Profile',
             getDngProfileDict(f)) for i, f in enumerate(files)
        ])
        # add 'None' and all found profiles for the current camera model: 'None' will be the default selection

        # filter items to eliminate empty entries and
        # add non empty dicts to cameraProfileCombo
        for key in items:
            # filter items[key]
            d = {k: items[key][k] for k in items[key] if items[key][k] != ''}
            if d:
                self.cameraProfilesCombo.addItem(key, d)
        self.cameraProfilesCombo.addItem('None', {})
        self.cameraProfilesCombo.setSizeAdjustPolicy(
            QComboBox.SizeAdjustPolicy.AdjustToContents)
        self.cameraProfilesCombo.setMaximumWidth(150)
        self.cameraProfilesCombo.setStyleSheet(
            "QComboBox QAbstractItemView { min-width: 250px;}")
        # return the currently selected item
        return self.cameraProfilesCombo.itemData(0)

    def writeToStream(self, outStream):
        layer = self.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        outStream.writeQString(self.listWidget1.selectedItems()[0].text())
        outStream.writeInt32(self.sliderExp.value())
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        temp = inStream.readInt32()
        for r in range(self.listWidget1.count()):
            currentItem = self.listWidget1.item(r)
            if currentItem.text() == sel:
                self.listWidget.select(currentItem)
        self.sliderExp.setValue(temp)
        self.update()
        return inStream
    def __init__(self, axeSize=500, layer=None, parent=None):
        super(noiseForm, self).__init__(parent=parent)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.setMinimumSize(axeSize, axeSize)
        self.setAttribute(Qt.WA_DeleteOnClose)
        # link back to image layer
        # using weak ref for back links
        if type(layer) in weakref.ProxyTypes:
            self.layer = layer
        else:
            self.layer = weakref.proxy(layer)
        # attribute initialized in setDefaults
        # defined here for the sake of correctness
        self.noiseCorrection = 0

        # options
        optionList = ['Wavelets', 'Bilateral', 'NLMeans']
        self.listWidget1 = optionsWidget(
            options=optionList,
            exclusive=True,
            changed=lambda: self.dataChanged.emit(True))
        self.listWidget1.checkOption(self.listWidget1.intNames[0])
        self.options = self.listWidget1.options

        # threshold slider
        self.sliderThr = QbLUeSlider(Qt.Horizontal)
        self.sliderThr.setStyleSheet(QbLUeSlider.bLueSliderDefaultBWStylesheet)
        self.sliderThr.setTickPosition(QSlider.TicksBelow)
        self.sliderThr.setRange(0, 10)
        self.sliderThr.setSingleStep(1)

        self.sliderThr.valueChanged.connect(self.thrUpdate)
        self.sliderThr.sliderReleased.connect(lambda: self.thrUpdate(
            self.sliderThr.value()))  # signal has no parameter)

        self.thrLabel = QLabel()
        self.thrLabel.setMaximumSize(150, 30)
        self.thrLabel.setText("level")

        self.thrValue = QLabel()
        font = self.thrValue.font()
        metrics = QFontMetrics(font)
        w = metrics.width("0000")
        h = metrics.height()
        self.thrValue.setMinimumSize(w, h)
        self.thrValue.setMaximumSize(w, h)
        self.thrValue.setText(
            str("{:.0f}".format(self.slider2Thr(self.sliderThr.value()))))
        # self.dataChanged.connect(self.updateLayer)
        # self.setStyleSheet("QListWidget, QLabel {font : 7pt;}")
        # layout
        l = QVBoxLayout()
        #l.setAlignment(Qt.AlignBottom)
        l.addWidget(self.listWidget1)
        hl1 = QHBoxLayout()
        hl1.addWidget(self.thrLabel)
        hl1.addWidget(self.thrValue)
        hl1.addWidget(self.sliderThr)
        l.addLayout(hl1)
        l.setContentsMargins(20, 0, 20, 25)  # left, top, right, bottom
        #l.setContentsMargins(10, 10, 10, 10)  # left, top, right, bottom
        self.setLayout(l)
        self.adjustSize()
        self.setDefaults()
        self.setWhatsThis("""<b>Noise Reduction</b><br>
   <b>Bilateral Filtering</b> is the fastest method.<br>
   <b>NLMeans</b> (Non Local Means) and <b>Wavelets</b> are slower,
   but they usually give better results.<br>
   It is possible to <b>limit the application of all methods 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 of setWhatsThis
Exemple #22
0
class HVLUT2DForm(graphicsCurveForm):
    """
    Form for interactive HV 2D LUT
    """
    @classmethod
    def getNewWindow(cls,
                     targetImage=None,
                     axeSize=500,
                     layer=None,
                     parent=None):
        newWindow = HVLUT2DForm(targetImage=targetImage,
                                axeSize=axeSize,
                                layer=layer,
                                parent=parent)
        newWindow.setWindowTitle(layer.name)
        return newWindow

    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(targetImage=targetImage,
                         axeSize=axeSize,
                         layer=layer,
                         parent=parent)
        graphicsScene = self.scene()
        # connect layer selectionChanged signal
        self.layer.selectionChanged.sig.connect(self.updateLayer)
        # Init curves
        dSplineItem = activeBSpline(axeSize,
                                    period=axeSize,
                                    yZero=-3 * axeSize // 4)
        graphicsScene.addItem(dSplineItem)
        dSplineItem.setVisible(True)
        dSplineItem.initFixedPoints()
        self.dSplineItemB = dSplineItem
        graphicsScene.dSplineItemB = dSplineItem

        dSplineItem = activeBSpline(axeSize,
                                    period=axeSize,
                                    yZero=-axeSize // 4)
        graphicsScene.addItem(dSplineItem)
        dSplineItem.setVisible(True)
        dSplineItem.initFixedPoints()
        self.dSplineItemH = dSplineItem
        graphicsScene.dSplineItemH = dSplineItem

        # init 3D LUT
        self.LUT = DeltaLUT3D((34, 32, 32))

        self.marker = activeMarker.fromTriangle(parent=self.dSplineItemB)
        self.marker.setPos(0, -axeSize // 2)
        self.scene().addItem(self.marker)

        def showPos(e, x, y):
            self.markerLabel.setText("%d" % (x * 360 // axeSize))

        self.marker.onMouseMove = showPos

        self.markerLabel = QLabel()
        font = self.markerLabel.font()
        metrics = QFontMetrics(font)
        w = metrics.width("0000")
        h = metrics.height()
        self.markerLabel.setMinimumSize(w, h)
        self.markerLabel.setMaximumSize(w, h)

        self.sliderSat = QbLUeSlider(Qt.Horizontal)
        self.sliderSat.setMinimumWidth(200)

        def satUpdate(value):
            self.satValue.setText(str("{:d}".format(value)))
            # move not yet terminated or values not modified
            if self.sliderSat.isSliderDown() or value == self.satThr:
                return
            try:
                self.sliderSat.valueChanged.disconnect()
                self.sliderSat.sliderReleased.disconnect()
            except RuntimeError:
                pass
            self.satThr = value
            self.dataChanged.emit()
            self.sliderSat.valueChanged.connect(satUpdate)
            self.sliderSat.sliderReleased.connect(
                lambda: satUpdate(self.sliderSat.value()))

        self.sliderSat.valueChanged.connect(satUpdate)
        self.sliderSat.sliderReleased.connect(
            lambda: satUpdate(self.sliderSat.value()))

        self.satValue = QLabel()
        font = self.markerLabel.font()
        metrics = QFontMetrics(font)
        w = metrics.width("0000")
        h = metrics.height()
        self.satValue.setMinimumSize(w, h)
        self.satValue.setMaximumSize(w, h)

        # layout
        gl = QGridLayout()
        gl.addWidget(QLabel('Hue '), 0, 0)
        gl.addWidget(self.markerLabel, 0, 1)
        gl.addWidget(QLabel('Sat Thr '), 1, 0)
        gl.addWidget(self.satValue, 1, 1)
        gl.addWidget(self.sliderSat, 1, 2, 4, 1)
        self.addCommandLayout(gl)

        self.setDefaults()

        self.setWhatsThis("""<b>3D LUT Shift HSV</b><br>
            x-axes represent hue values from 0 to 360.
            The upper curve shows brightness multiplicative shifts (initially 1) and
            the lower curve hue additive shifts (initially 0). 
            All pixel colors are changed by the specific shifts corresponding to their hue.
            Each curve is controlled by bump triangles.<br>
            To <b>add a bump triangle</b> to the curve click anywhere on the curve.
            To <b>remove the triangle</b> click on any vertex.<br>
            Drag the triangle vertices to move the bump along the x-axis and to change 
            its height and orientation. Use the <b> Sat Thr</b> slider 
            to preserve low saturated colors.<br>
            To <b>set the Hue Value Marker</b> Ctrl+click on the image.<br>
            To limit the shift corrections to a region of the image select the desired area
            with the rectangular marquee tool.<br>
            <b>Zoom</b> the curves with the mouse wheel.<br>
            """)

    def setDefaults(self):
        try:
            self.dataChanged.disconnect()
            self.dSplineItemB.curveChanged.sig.disconnect()
            self.dSplineItemH.curveChanged.sig.disconnect()
        except RuntimeError:
            pass
        self.satThr = 10
        self.sliderSat.setValue(self.satThr)
        self.dataChanged.connect(self.updateLayer)
        self.dSplineItemB.curveChanged.sig.connect(self.updateLayer)
        self.dSplineItemH.curveChanged.sig.connect(self.updateLayer)

    def updateLUT(self):
        """
        Updates the displacement LUT
        """
        data = self.LUT.data
        axeSize = self.axeSize
        hdivs = self.LUT.divs[0]
        # sat threshold
        sThr = int(self.LUT.divs[1] * self.satThr / 100)
        hstep = 360 / hdivs
        activeSpline = self.dSplineItemB
        sp = activeSpline.spline[activeSpline.periodViewing:]
        d = activeSpline.yZero
        # reinit the LUT
        data[...] = 0, 1, 1
        for i in range(hdivs):
            pt = sp[int(i * hstep * axeSize / 360)]
            data[i, sThr:, :, 2] = 1.0 - (pt.y() - d) / 100

        activeSpline = self.dSplineItemH
        sp = activeSpline.spline[activeSpline.periodViewing:]
        d = activeSpline.yZero
        for i in range(hdivs):
            pt = sp[int(i * hstep * axeSize / 360)]
            data[i, sThr:, :, 0] = -(pt.y() - d) / 5

    def updateLayer(self):
        self.updateLUT()
        l = self.scene().layer
        l.applyToStack()
        l.parentImage.onImageChanged()

    def colorPickedSlot(self, x, y, modifiers):
        """
        sets black/white points
        (x,y) coordinates are relative to the full size image.
        @param x:
        @type x:
        @param y:
        @type y:
        @param modifiers:
        @type modifiers:
        """
        color = self.scene().targetImage.getActivePixel(x, y, qcolor=True)
        h = color.hsvHue()
        if modifiers & QtCore.Qt.ControlModifier:
            self.marker.setPos(h * 300 / 360, -self.axeSize // 2)
            self.markerLabel.setText("%d" % h)
            self.update()

    def writeToStream(self, outStream):
        """

        @param outStream:
        @type outStream: QDataStream
        @return:
        @rtype: QDataStream
        """
        graphicsScene = self.scene()
        layer = graphicsScene.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        if layer.actionName in [
                'actionBrightness_Contrast', 'actionCurves_HSpB',
                'actionCurves_Lab'
        ]:
            outStream.writeQString(self.listWidget1.selectedItems()[0].text())
            graphicsScene.cubicRGB.writeToStream(outStream)
            graphicsScene.cubicR.writeToStream(outStream)
            graphicsScene.cubicG.writeToStream(outStream)
            graphicsScene.cubicB.writeToStream(outStream)
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        graphicsScene = self.scene()
        graphicsScene.cubicRGB.readFromStream(inStream)
        graphicsScene.cubicR.readFromStream(inStream)
        graphicsScene.cubicG.readFromStream(inStream)
        graphicsScene.cubicB.readFromStream(inStream)
        return inStream
Exemple #23
0
    def __init__(self, targetImage=None, axeSize=500, layer=None, parent=None):
        super().__init__(layer=layer, targetImage=targetImage, parent=parent)
        # connect layer selectionChanged signal
        self.layer.selectionChanged.sig.connect(self.updateLayer)
        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))  # filters is not a dict: don't use UDict here

        self.listWidget1 = optionsWidget(options=self.optionList,
                                         exclusive=True,
                                         changed=self.dataChanged)
        # set initial selection to unsharp mask
        self.listWidget1.checkOption(self.optionList[0])

        # sliders
        self.sliderRadius = QbLUeSlider(Qt.Horizontal)
        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.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.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)

        # value change/done slot
        def formUpdate():
            self.radiusValue.setText(str('%d ' % self.sliderRadius.value()))
            self.amountValue.setText(str('%d ' % self.sliderAmount.value()))
            self.toneValue.setText(str('%d ' % self.sliderTone.value()))
            if self.sliderRadius.isSliderDown(
            ) or self.sliderAmount.isSliderDown(
            ) or self.sliderTone.isSliderDown():
                return
            try:
                for slider in [
                        self.sliderRadius, self.sliderAmount, self.sliderTone
                ]:
                    slider.valueChanged.disconnect()
                    slider.sliderReleased.disconnect()
            except RuntimeError:
                pass
            self.tone = self.sliderTone.value()
            self.radius = self.sliderRadius.value()
            self.amount = self.sliderAmount.value()
            self.dataChanged.emit()
            for slider in [
                    self.sliderRadius, self.sliderAmount, self.sliderTone
            ]:
                slider.valueChanged.connect(formUpdate)
                slider.sliderReleased.connect(formUpdate)

        for slider in [self.sliderRadius, self.sliderAmount, self.sliderTone]:
            slider.valueChanged.connect(formUpdate)
            slider.sliderReleased.connect(formUpdate)

        # layout
        l = QVBoxLayout()
        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)

        self.setDefaults()
        self.setWhatsThis("""
                       <b>Unsharp Mask</b> and <b>Sharpen Mask</b> are used to sharpen an image.
                       Unsharp Mask usually gives best results.<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,
                       but it may be slow.<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 (rectangle) tool.<br>
                       Ctrl Click <b>clears the selection</b><br>
                       
                    """)  # end setWhatsThis
class temperatureForm(baseForm):

    # dataChanged = QtCore.Signal()

    @classmethod
    def getNewWindow(cls,
                     targetImage=None,
                     axeSize=500,
                     layer=None,
                     parent=None,
                     mainForm=None):
        wdgt = temperatureForm(axeSize=axeSize,
                               layer=layer,
                               parent=parent,
                               mainForm=mainForm)
        wdgt.setWindowTitle(layer.name)
        return wdgt

    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 enableSliders(self):
        self.sliderTemp.setEnabled(True)
        self.sliderTint.setEnabled(self.options['Chromatic Adaptation'])

    def setDefaults(self):
        # prevent multiple updates
        try:
            self.dataChanged.disconnect()
        except RuntimeError:
            pass
        self.listWidget1.unCheckAll()
        self.listWidget1.checkOption(self.listWidget1.intNames[0])
        self.enableSliders()
        self.sliderTemp.setValue(round(self.temp2Slider(self.tempCorrection)))
        self.sliderTint.setValue(round(self.tint2Slider(self.defaultTint)))
        self.dataChanged.connect(self.updateLayer)
        # self.dataChanged.emit() # removed 30/10/18

    def updateLayer(self):
        """
        data changed event handler.
        """
        self.enableSliders()
        self.layer.applyToStack()
        self.layer.parentImage.onImageChanged()

    def slider2Temp(self, v):
        return v * 100

    def temp2Slider(self, v):
        return int(v / 100)

    def sliderTemp2User(selfself, v):
        return v * 100

    def slider2Tint(self, v):
        return (v - 50) / 50

    def tint2Slider(self, v):
        return int((1.0 + v) * 50.0)

    def sliderTint2User(selfself, v):
        return int((v - 50) / 5.0)

    def writeToStream(self, outStream):
        layer = self.layer
        outStream.writeQString(layer.actionName)
        outStream.writeQString(layer.name)
        outStream.writeQString(self.listWidget1.selectedItems()[0].text())
        outStream.writeInt32(self.sliderTemp.value() * 100)
        return outStream

    def readFromStream(self, inStream):
        actionName = inStream.readQString()
        name = inStream.readQString()
        sel = inStream.readQString()
        temp = inStream.readInt32()
        for r in range(self.listWidget1.count()):
            currentItem = self.listWidget1.item(r)
            if currentItem.text() == sel:
                self.listWidget.select(currentItem)
        self.sliderTemp.setValue(temp // 100)
        self.update()
        return inStream