def FontSizeForWidth(self): text = self.label.text() textSize = len(text) f = self.label.font() metrics = QFontMetrics(f) width = metrics.width(text) myWidth = self.label.width() size = 0 if width < myWidth: direction = 1 else: direction = -1 while (True): f = QFont("Ariel", size + 1) mf = QFontMetrics(f) self.label.setFont(f) width = mf.width(text) if direction > 0: if width >= myWidth: break else: if width <= myWidth: break size = f.pointSize() + direction return size
def getTextWidth(self, text: str) -> int: font = self.font() # ps = font.pixelSize() # --> -1 font.setPixelSize(0) fm = QFontMetrics(font) width = fm.width(text) return width
class ApolloGroupHeading(QWidget): def __init__(self, parent, text, *args, **kwargs): super().__init__(parent, *args, **kwargs) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) self.text = text self.font_metrics = QFontMetrics(self.font()) self.text_width = self.font_metrics.width(self.text) self.text_height = self.font_metrics.height() def sizeHint(self): return QSize(self.text_width, self.text_height) def paintEvent(self, event): p = QPainter(self) left_edge = self.width() // 2 - self.text_width // 2 right_edge = self.width() // 2 + self.text_width // 2 p.drawText(left_edge, self.text_height, self.text) x0 = 0 x1 = left_edge - 4 x2 = right_edge + 4 x3 = self.width() - 1 y0 = self.height() // 2 + 2 y1 = self.height() p.drawLine(x0, y0, x1, y0) p.drawLine(x2, y0, x3, y0) p.drawLine(x0, y0, x0, y1) p.drawLine(x3, y0, x3, y1) p.end()
def text_changed(self, t): fm = QFontMetrics(self.font()) text_width = fm.width(t) text_width = text_width+25 self.setFixedWidth(text_width if text_width > self.base_width else self.base_width) self.parent_node_instance.update_shape()
def __init__(self, item, parent): super(CollapsibleArrowAndTitle, self).__init__(item, parent=parent) self.isCollapsed = False fontMetric = QFontMetrics(self.item().data(Qt.FontRole)) text = self.item().data(renderSetupRoles.NODE_TYPE_STR) self.setMinimumSize(self.WIDTH + fontMetric.width(text), self.HEIGHT) self.arrowPoints = BaseDelegate.EXPANDED_ARROW
def text_changed(self, new_text): if self.resizing: fm = QFontMetrics(self.font()) text_width = fm.width(new_text) new_width = text_width + 15 self.setFixedWidth( new_width if new_width > self.base_width else self.base_width) self.parent_node_instance.update_shape()
def update_char_nb(self): font_metrics = QFontMetrics(self.seq_font) px_wide_char = font_metrics.width("A") label_width = self.seq_record_items[0].seqLabel.width( ) # width of first seq label = all seq labels approx_char_nb = label_width // px_wide_char test_str = "A" * approx_char_nb while font_metrics.width( test_str) < label_width: # fontMetrics not precise at all... test_str += "A" while font_metrics.width( test_str) >= label_width: # fontMetrics not precise at all... test_str = test_str[:-1] self.char_nb = len(test_str)
def setFont(self, qFont): qFont.setStyleHint(QFont.Monospace) qFont.setFixedPitch(True) fontMetrics = QFontMetrics(qFont) self.setTabStopWidth(self.__tabStop * fontMetrics.width(' ')) self.__lineHeight = fontMetrics.height() * self.__lineHeightMultiplier super().setFont(qFont) self.__setLineHeight()
def _create_raw_widget(self, ) -> QWidget: widget = QPlainTextEdit(self) font = QFontDatabase.systemFont(QFontDatabase.FixedFont) font.setFixedPitch(True) metrics = QFontMetrics(font) widget.setFont(font) widget.setTabStopDistance(2 * metrics.width(' ')) as_signal_instance(widget.textChanged).connect(self._on_raw_changed, ) return widget
def __init__(self, text, position, attachment_point, *args, **kwargs): super(TextItem, self).__init__(*args, **kwargs) self.text = text self.angle = kwargs.pop('angle') self.position = position self.font = QFont('Arial', 5) metrics = QFontMetrics(self.font) self.w = metrics.width(self.text) self.h = metrics.height() self.attachment_point = attachment_point
def text_changed(self): t = self.toPlainText() fm = QFontMetrics(self.font()) text_width = fm.width(self.get_longest_line(t)) text_width = text_width+25 text_height = fm.height()*(t.count('\n')+1)+12 self.setFixedWidth(text_width if text_width > self.base_width else self.base_width) self.setFixedHeight(text_height if text_height > self.base_height else self.base_height) self.parent_node_instance.update_shape()
def add_item(self, item_str: str): """ 向导航栏中添加项目(代表一个选项卡),添加之前会查重 :param item_str:项目名称(显示出来的文字) :return:None """ if not item_str: return for key, value in self._m_item_maps.items(): if value[0] == item_str: return # 如果存在同名item,则返回 f = QFont() f.setPointSize(self._m_item_font_size) fm = QFontMetrics(f) text_width = fm.width(item_str) text_height = fm.height() item_count = len(self._m_item_maps) if item_count > 0: if self._m_orientation == Qt.Horizontal: top_left = QPointF(self._m_total_text_width, 0) self._m_total_text_width += text_width + self._m_space bottom_right = QPointF(self._m_total_text_width, self._m_total_text_height) else: top_left = QPointF(0, self._m_total_text_height) self._m_total_text_height += text_height + self._m_space bottom_right = QPointF(self._m_total_text_width, self._m_total_text_height) self._m_item_maps[item_count] = [item_str, QRectF(top_left, bottom_right)] else: if self._m_orientation == Qt.Horizontal: # 水平方向,水平各占1个space, 竖直占1个space self._m_total_text_width = text_width + self._m_space self._m_total_text_height = text_height + self._m_space else: # 竖直方向, 水平各占2个space, 竖直占一个space self._m_total_text_width = text_width + 2 * self._m_space self._m_total_text_height = text_height + self._m_space top_left = QPointF(0.0, 0.0) bottom_right = QPointF(self._m_total_text_width, self._m_total_text_height) self._m_item_maps[item_count] = [item_str, QRectF(top_left, bottom_right)] self.setMinimumSize(self._m_total_text_width, self._m_total_text_height) if self._m_fixed: if self._m_orientation == Qt.Horizontal: self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) # 固定高度 else: self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred) # 固定宽度 if len(self._m_item_maps): self._m_start_rect = QRectF(self._m_item_maps[0][1]) self.update()
def resize_to_content(self, lines): if len(lines) == 0: lines.append('') # resize properly fm = QFontMetrics(self.font()) text_width = fm.width(lines[0]+'__') text_width = text_width+20 # some buffer text_height = fm.height()*(len(lines))+12 # also some vertical buffer self.setFixedWidth(text_width if text_width > self.base_width else self.base_width) self.setFixedHeight(text_height if text_height > self.base_height else self.base_height) self.parent_node_instance.update_shape()
def set_window_title(self, title: str) -> None: """ 设置窗口的标题 :param title:标题 :return: """ f = QFont(QFont('Microsoft YaHei', 14)) fm = QFontMetrics(f) self._m_title_text_width = fm.width(title) self._m_title_label.setFont(f) self._m_title_label.setText(title) self._m_title_text = title self.setWindowTitle(title) self.update()
def draw_message_text(item: QGraphicsTextItem, font: QFont, text: str, x: int, y: int, max_width: int): item.setDefaultTextColor(QColor.fromRgba(0xFF440400)) item.setFont(font) item.setPos(x, y) # Calculate how many character to trim *without* making copies of the string. font_metrics = QFontMetrics(font) cur_width = font_metrics.width(text) cur_end = len(text) - 1 while cur_width > max_width: cur_width -= font_metrics.charWidth(text, cur_end) cur_end -= 1 item.setPlainText(text[:cur_end + 1])
def createDropTextPixmap(self): pixmap = QPixmap(481, 300) pixmap.fill(QColor("#333333")) painter = QPainter(pixmap) font = QFont("Arial") font.setPixelSize(28) font.setBold(True) fm = QFontMetrics(font) painter.setFont(font) painter.setPen(QPen(QColor("#888888"), 1)) text = "Drop the tileset image here" x = (pixmap.width()-fm.width(text))/2 y = (pixmap.height()+fm.height())/2 painter.drawText(x, y, text) del painter return pixmap
def _set_font(self): mono_font = QFontDatabase.systemFont(QFontDatabase.FixedFont) mono_font.setStyleStrategy(QFont.ForceIntegerMetrics) mono_font.setFixedPitch(True) mono_font.setPointSize(9) font_metrics = QFontMetrics(mono_font) self.font_width = font_metrics.width("M") self.setFont(mono_font) self.layout.setFont(mono_font) self.viewport().setFont(mono_font) self.line_height = self.fontMetrics().height() self.viewport().update()
def draw(self, painter): r = self.mPointer.rect() margin = r.height() / 10 shadow = self.mPointer.palette().color(QPalette.Dark) light = self.mPointer.palette().color(QPalette.Light) button = self.mPointer.palette().color(QPalette.Button) painter.setPen(Qt.NoPen) self.mGradient.setColorAt(0, shadow.darker(130)) self.mGradient.setColorAt(1, light.darker(130)) self.mGradient.setStart(0, r.height()) self.mGradient.setFinalStop(0, 0) painter.setBrush(self.mGradient) painter.drawRoundedRect(r, r.height() / 2, r.height() / 2) self.mGradient.setColorAt(0, shadow.darker(140)) self.mGradient.setColorAt(1, light.darker(160)) self.mGradient.setStart(0, 0) self.mGradient.setFinalStop(0, r.height()) painter.setBrush(self.mGradient) painter.drawRoundedRect(r.adjusted(margin, margin, -margin, -margin), r.height() / 2, r.height() / 2) self.mGradient.setColorAt(0, button.darker(130)) self.mGradient.setColorAt(1, button) painter.setBrush(self.mGradient) x = r.height() / 2.0 + self.mPosition * (r.width() - r.height()) painter.drawEllipse(QPointF(x, r.height() / 2), r.height() / 2 - margin, r.height() / 2 - margin) text = self.leftText if bool(self.mPosition) else self.rightText painter.setPen(QColor(255, 255, 255)) font = QFont('Decorative', 10, QFont.Bold) fm = QFontMetrics(font) painter.setFont(font) textWidth = fm.width(text) textHeight = painter.font().pointSize() xpoint = r.width() - x - textWidth + 10 if bool( self.mPosition) else r.width() - x - 12 painter.drawText(QPointF(xpoint, r.height() / 2 + textHeight / 2), text)
def __init__(self, track, rect, *args, **kwargs): super(TrackNameItem, self).__init__(None, None, rect, *args, **kwargs) self.track = track self.track_name = 'Track' if not track.name else track.name self.full_track_name = self.track_name if len(self.track_name) > SHORT_NAME_LENGTH: self.track_name = self.track_name[:SHORT_NAME_LENGTH] + '...' self.source_name_label.setText(self.track_name + '\n({})'.format(track.kind)) self.source_name_label.setY( (TRACK_HEIGHT - self.source_name_label.boundingRect().height()) / 2.0) self.setToolTip('{} items'.format(len(track))) self.track_widget = None self.name_toggle = False self.font = self.source_name_label.font() self.short_width = TRACK_NAME_WIDGET_WIDTH font_metrics = QFontMetrics(self.font) self.full_width = font_metrics.width(self.full_track_name) + 40
def _adjust_item_size(self) -> None: """ 调整Item大小 :return: """ if self._m_fixed: return item_count = len(self._m_item_maps) if self._m_orientation == Qt.Horizontal: add_width = 1.0 * (self.width() - self._m_total_text_width) / item_count add_height = 1.0 * (self.height() - self._m_total_text_height) else: add_width = 1.0 * (self.width() - self._m_total_text_width) add_height = 1.0 * (self.height() - self._m_total_text_height) / item_count dx = dy = 0.0 for key, value in self._m_item_maps.items(): # f = QFont() fm = QFontMetrics(self._m_item_font) text_width = fm.width(value[0]) text_height = fm.height() if self._m_orientation == Qt.Horizontal: topLeft = QPointF(dx, 0) dx += text_width + self._m_space + add_width dy = self._m_total_text_height + add_height else: topLeft = QPointF(0, dy) dx = self._m_total_text_width + add_width dy += text_height + self._m_space + add_height bottomRight = QPointF(dx, dy) text_rect = QRectF(topLeft, bottomRight) self._m_item_maps[key] = [value[0], QRectF(text_rect)] if key == self._m_current_index: self._m_start_rect = text_rect self._m_stop_rect = text_rect self.update()
def __init__(self, text): super(Button, self).__init__() self.text = text self.roundness = 10 self.painter = QPainter() self.font = QFont() self.font.setPointSize(14) self.font.setBold(True) metrics = QFontMetrics(self.font) self.setMinimumSize(metrics.width(text) + 40, 50) self.pen = QPen(QColor("#01d277")) self.pen.setWidth(5) self.hover_brush = QBrush(QColor(1, 210, 119, 100)) self.hover = False
def mouseMoveEvent(self, event: QMouseEvent) -> None: if self._panEnabled and self._panOn: offset = event.pos() - self._mousePressEventPos self.chart().scroll(-offset.x(), offset.y()) self._mousePressEventPos = event.pos() event.accept() elif self._chartIsSet and self._positionTrackerEnabled: metrics = QFontMetrics(self._coordX.font()) xVal = self.chart().mapToValue(event.pos()).x() yVal = self.chart().mapToValue(event.pos()).y() # if self.chart().axisX().type() == QtCharts.QAbstractAxis.AxisTypeDateTime: xText: str = 'X: {}'.format( computeAxisValue(self.chart().axisX(), xVal)) yText: str = 'Y: {}'.format( computeAxisValue(self.chart().axisY(), yVal)) xSize = metrics.width(xText, -1) ySize = metrics.width(yText, -1) totSize = xSize + ySize self._updateMouseTrackerPosition(xOffset=(totSize // 2)) self._coordX.setText(xText) self._coordY.setText(yText) super().mouseMoveEvent(event)
def paintEvent(self, event): painter = QPainter(self) painter.setPen(Qt.white) painter.setBrush(Qt.white) painter.drawRect(event.rect()) painter.setPen(Qt.lightGray) r = QRect(event.rect()).adjusted(TapeWidget.X_MARGIN, TapeWidget.Y_MARGIN, -TapeWidget.X_MARGIN, -TapeWidget.Y_MARGIN) # Draw tape painter.fillRect(r, QColor(Qt.gray).light(150)) painter.drawLine(r.topLeft(), r.topRight()) painter.drawLine(r.bottomLeft(), r.bottomRight()) fm = QFontMetrics(self.font()) center = QPoint(self.width() / 2, self.height() / 2) cw = fm.width(' ') + 15 # ch = fm.height() + 5 x = self._displace + center.x() + cw / 2 while x > 0: x -= cw painter.drawLine(x, r.y() + 5, x, r.bottom() - 5) x = self._displace + center.x() - cw / 2 while x < self.width(): x += cw painter.drawLine(x, r.y() + 5, x, r.bottom() - 5) # Draw head color = QColor('#00a8ff') color.setAlpha(100) painter.setBrush(color) painter.drawRect(center.x() - 15, 3, 29, 33)
def __init__(self, parent=None): super(Snippets, self).__init__(parent) # Create widgets self.setWindowModality(Qt.NonModal) self.title = QLabel(self.tr("Snippet Editor")) self.saveButton = QPushButton(self.tr("Save")) self.revertButton = QPushButton(self.tr("Revert")) self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey")) self.setWindowTitle(self.title.text()) self.newFolderButton = QPushButton("New Folder") self.deleteSnippetButton = QPushButton("Delete") self.newSnippetButton = QPushButton("New Snippet") self.edit = QPlainTextEdit() self.resetting = False self.columns = 3 self.keySequenceEdit = QKeySequenceEdit(self) self.currentHotkey = QKeySequence() self.currentHotkeyLabel = QLabel("") self.currentFileLabel = QLabel() self.currentFile = "" self.snippetDescription = QLineEdit() self.snippetEditsPending = False self.clearSelection() #Set Editbox Size font = getMonospaceFont(self) self.edit.setFont(font) font = QFontMetrics(font) self.edit.setTabStopWidth(4 * font.width(' ')); #TODO, replace with settings API #Files self.files = QFileSystemModel() self.files.setRootPath(snippetPath) self.files.setNameFilters(["*.py"]) #Tree self.tree = QTreeView() self.tree.setModel(self.files) self.tree.setSortingEnabled(True) self.tree.hideColumn(2) self.tree.sortByColumn(0, Qt.AscendingOrder) self.tree.setRootIndex(self.files.index(snippetPath)) for x in range(self.columns): #self.tree.resizeColumnToContents(x) self.tree.header().setSectionResizeMode(x, QHeaderView.ResizeToContents) treeLayout = QVBoxLayout() treeLayout.addWidget(self.tree) treeButtons = QHBoxLayout() treeButtons.addWidget(self.newFolderButton) treeButtons.addWidget(self.newSnippetButton) treeButtons.addWidget(self.deleteSnippetButton) treeLayout.addLayout(treeButtons) treeWidget = QWidget() treeWidget.setLayout(treeLayout) # Create layout and add widgets buttons = QHBoxLayout() buttons.addWidget(self.clearHotkeyButton) buttons.addWidget(self.keySequenceEdit) buttons.addWidget(self.currentHotkeyLabel) buttons.addWidget(self.revertButton) buttons.addWidget(self.saveButton) description = QHBoxLayout() description.addWidget(QLabel(self.tr("Description: "))) description.addWidget(self.snippetDescription) vlayoutWidget = QWidget() vlayout = QVBoxLayout() vlayout.addWidget(self.currentFileLabel) vlayout.addWidget(self.edit) vlayout.addLayout(description) vlayout.addLayout(buttons) vlayoutWidget.setLayout(vlayout) hsplitter = QSplitter() hsplitter.addWidget(treeWidget) hsplitter.addWidget(vlayoutWidget) hlayout = QHBoxLayout() hlayout.addWidget(hsplitter) self.showNormal() #Fixes bug that maximized windows are "stuck" self.settings = QSettings("Vector 35", "Snippet Editor") if self.settings.contains("ui/snippeteditor/geometry"): self.restoreGeometry(self.settings.value("ui/snippeteditor/geometry")) else: self.edit.setMinimumWidth(80 * font.averageCharWidth()) self.edit.setMinimumHeight(30 * font.lineSpacing()) # Set dialog layout self.setLayout(hlayout) # Add signals self.saveButton.clicked.connect(self.save) self.revertButton.clicked.connect(self.loadSnippet) self.clearHotkeyButton.clicked.connect(self.clearHotkey) self.tree.selectionModel().selectionChanged.connect(self.selectFile) self.newSnippetButton.clicked.connect(self.newFileDialog) self.deleteSnippetButton.clicked.connect(self.deleteSnippet) self.newFolderButton.clicked.connect(self.newFolder)
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 __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 email_text_changed(self, new_text): fm = QFontMetrics(self.font()) text_width = fm.width(self.text()) new_width = text_width+2*10 self.setFixedWidth(new_width if new_width > 150 else 150) self.parent_node_instance.update_shape()
def __init__(self, parent): super().__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 slot def m(state): # state : Qt.Checked Qt.UnChecked if self.img is None: return useThumb = (state == Qt.Checked) if useThumb == self.img.useThumb: return self.img.useThumb = useThumb window.updateStatus() self.img.cacheInvalidate() try: QApplication.setOverrideCursor( Qt.WaitCursor ) # TODO 18/04/18 waitcursor already called by applytostack QApplication.processEvents() # update the whole stack self.img.layersStack[0].applyToStack() self.img.onImageChanged() finally: QApplication.restoreOverrideCursor() QApplication.processEvents() 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 self.maskLabel = QLabel('Mask Color') 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 ') # masks are disbled by default self.maskLabel.setEnabled(False) self.maskSlider.setEnabled(False) self.maskValue.setEnabled(False) # 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() * 255.0 / 100.0) 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), # Type of previous modes is QPainter.CompositionMode (Shiboken enum-type). # Next additional modes are not implemented by QPainter: ('Luminosity', -1), ('color', -2) ]) self.blendingModeCombo = QComboBox() for key in self.compositionModeDict: self.blendingModeCombo.addItem(key, self.compositionModeDict[key]) # combo box item changed slot def g(ind): layer = self.img.getActiveLayer() s = self.blendingModeCombo.currentText() newMode = self.compositionModeDict[str(s)] if newMode == layer.compositionMode: return layer.compositionMode = newMode layer.applyToStack() self.img.onImageChanged() self.blendingModeCombo.currentIndexChanged.connect(g) # 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(self.maskLabel) 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 to in blue.py, after loading the main form. 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><br> 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> green pixels are masked,<br> red pixels are unmasked.<br> Other colors correspond to partially masked pixels.<br> Note that upper visible layers slow down mask edition.<br> """) # end of setWhatsThis
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 __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 __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 text = graphicsScene.addText('Hue (0-360)') text.setDefaultTextColor(Qt.white) text.setPos(-40, 10) text = graphicsScene.addText('delta H') text.setDefaultTextColor(Qt.white) text.setPos(-40, -self.axeSize // 4) text = graphicsScene.addText('delta B') text.setDefaultTextColor(Qt.white) text.setPos(-40, -(self.axeSize * 3) // 4) 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, 0) # -(axeSize * 3) // 4) self.marker.setMoveRange(QRect(0, 0, axeSize, 0)) 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> All pixel colors are changed by the specific hue and brightness shifts corresponding to their hue.<br> x-axis represents hue values from 0 to 360. The upper curve shows brightness multiplicative shifts (initially 1) and the lower curve hue additive shifts (initially 0). <br> 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> """)