From e2d758f5409edec9f55cc6170078906e95306ce5 Mon Sep 17 00:00:00 2001 From: Stefan Breunig Date: Sun, 12 Jun 2022 19:01:41 +0200 Subject: [PATCH] add brightness controls (#888) Closes #860 --- labelImg.py | 47 ++++++++++- libs/canvas.py | 16 +++- libs/lightWidget.py | 33 ++++++++ resources.qrc | 3 + resources/icons/light_brighten.svg | 112 +++++++++++++++++++++++++++ resources/icons/light_darken.png | Bin 0 -> 487 bytes resources/icons/light_darken.svg | 52 +++++++++++++ resources/icons/light_lighten.png | Bin 0 -> 477 bytes resources/icons/light_reset.png | Bin 0 -> 587 bytes resources/icons/light_reset.svg | 84 ++++++++++++++++++++ resources/strings/strings.properties | 7 ++ 11 files changed, 350 insertions(+), 4 deletions(-) create mode 100644 libs/lightWidget.py create mode 100644 resources/icons/light_brighten.svg create mode 100644 resources/icons/light_darken.png create mode 100644 resources/icons/light_darken.svg create mode 100644 resources/icons/light_lighten.png create mode 100644 resources/icons/light_reset.png create mode 100644 resources/icons/light_reset.svg diff --git a/labelImg.py b/labelImg.py index 2cac92a5..b11f9c15 100755 --- a/labelImg.py +++ b/labelImg.py @@ -34,6 +34,7 @@ from libs.shape import Shape, DEFAULT_LINE_COLOR, DEFAULT_FILL_COLOR from libs.stringBundle import StringBundle from libs.canvas import Canvas from libs.zoomWidget import ZoomWidget +from libs.lightWidget import LightWidget from libs.labelDialog import LabelDialog from libs.colorDialog import ColorDialog from libs.labelFile import LabelFile, LabelFileError, LabelFileFormat @@ -180,10 +181,12 @@ class MainWindow(QMainWindow, WindowMixin): self.file_dock.setWidget(file_list_container) self.zoom_widget = ZoomWidget() + self.light_widget = LightWidget(get_str('lightWidgetTitle')) self.color_dialog = ColorDialog(parent=self) self.canvas = Canvas(parent=self) self.canvas.zoomRequest.connect(self.zoom_request) + self.canvas.lightRequest.connect(self.light_request) self.canvas.set_drawing_shape_to_square(settings.get(SETTING_DRAW_SQUARE, False)) scroll = QScrollArea() @@ -326,6 +329,26 @@ class MainWindow(QMainWindow, WindowMixin): self.MANUAL_ZOOM: lambda: 1, } + light = QWidgetAction(self) + light.setDefaultWidget(self.light_widget) + self.light_widget.setWhatsThis( + u"Brighten or darken current image. Also accessible with" + " %s and %s from the canvas." % (format_shortcut("Ctrl+Shift+[-+]"), + format_shortcut("Ctrl+Shift+Wheel"))) + self.light_widget.setEnabled(False) + + light_brighten = action(get_str('lightbrighten'), partial(self.add_light, 10), + 'Ctrl+Shift++', 'light_lighten', get_str('lightbrightenDetail'), enabled=False) + light_darken = action(get_str('lightdarken'), partial(self.add_light, -10), + 'Ctrl+Shift+-', 'light_darken', get_str('lightdarkenDetail'), enabled=False) + light_org = action(get_str('lightreset'), partial(self.set_light, 50), + 'Ctrl+Shift+=', 'light_reset', get_str('lightresetDetail'), checkable=True, enabled=False) + light_org.setChecked(True) + + # Group light controls into a list for easier toggling. + light_actions = (self.light_widget, light_brighten, + light_darken, light_org) + edit = action(get_str('editLabel'), self.edit_label, 'Ctrl+E', 'edit', get_str('editLabelDetail'), enabled=False) @@ -364,6 +387,8 @@ class MainWindow(QMainWindow, WindowMixin): zoom=zoom, zoomIn=zoom_in, zoomOut=zoom_out, zoomOrg=zoom_org, fitWindow=fit_window, fitWidth=fit_width, zoomActions=zoom_actions, + lightBrighten=light_brighten, lightDarken=light_darken, lightOrg=light_org, + lightActions=light_actions, fileMenuActions=( open, open_dir, save, save_as, close, reset_all, quit), beginner=(), advanced=(), @@ -411,7 +436,8 @@ class MainWindow(QMainWindow, WindowMixin): labels, advanced_mode, None, hide_all, show_all, None, zoom_in, zoom_out, zoom_org, None, - fit_window, fit_width)) + fit_window, fit_width, None, + light_brighten, light_darken, light_org)) self.menus.file.aboutToShow.connect(self.update_file_menu) @@ -424,7 +450,8 @@ class MainWindow(QMainWindow, WindowMixin): self.tools = self.toolbar('Tools') self.actions.beginner = ( open, open_dir, change_save_dir, open_next_image, open_prev_image, verify, save, save_format, None, create, copy, delete, None, - zoom_in, zoom, zoom_out, fit_window, fit_width) + zoom_in, zoom, zoom_out, fit_window, fit_width, None, + light_brighten, light, light_darken, light_org) self.actions.advanced = ( open, open_dir, change_save_dir, open_next_image, open_prev_image, save, save_format, None, @@ -500,6 +527,7 @@ class MainWindow(QMainWindow, WindowMixin): # Callbacks: self.zoom_widget.valueChanged.connect(self.paint_canvas) + self.light_widget.valueChanged.connect(self.paint_canvas) self.populate_mode_actions() @@ -601,6 +629,8 @@ class MainWindow(QMainWindow, WindowMixin): """Enable/Disable widgets which depend on an opened image.""" for z in self.actions.zoomActions: z.setEnabled(value) + for z in self.actions.lightActions: + z.setEnabled(value) for action in self.actions.onLoadActive: action.setEnabled(value) @@ -1032,6 +1062,9 @@ class MainWindow(QMainWindow, WindowMixin): h_bar.setValue(new_h_bar_value) v_bar.setValue(new_v_bar_value) + def light_request(self, delta): + self.add_light(5*delta // (8 * 15)) + def set_fit_window(self, value=True): if value: self.actions.fitWidth.setChecked(False) @@ -1044,6 +1077,15 @@ class MainWindow(QMainWindow, WindowMixin): self.zoom_mode = self.FIT_WIDTH if value else self.MANUAL_ZOOM self.adjust_scale() + def set_light(self, value): + self.actions.lightOrg.setChecked(int(value) == 50) + # Arithmetic on scaling factor often results in float + # Convert to int to avoid type errors + self.light_widget.setValue(int(value)) + + def add_light(self, increment=10): + self.set_light(self.light_widget.value() + increment) + def toggle_polygons(self, value): for item, shape in self.items_to_shapes.items(): item.setCheckState(Qt.Checked if value else Qt.Unchecked) @@ -1172,6 +1214,7 @@ class MainWindow(QMainWindow, WindowMixin): def paint_canvas(self): assert not self.image.isNull(), "cannot paint null image" self.canvas.scale = 0.01 * self.zoom_widget.value() + self.canvas.overlay_color = self.light_widget.color() self.canvas.label_font_size = int(0.02 * max(self.image.width(), self.image.height())) self.canvas.adjustSize() self.canvas.update() diff --git a/libs/canvas.py b/libs/canvas.py index 98deba62..4a8d9586 100644 --- a/libs/canvas.py +++ b/libs/canvas.py @@ -23,6 +23,7 @@ CURSOR_GRAB = Qt.OpenHandCursor class Canvas(QWidget): zoomRequest = pyqtSignal(int) + lightRequest = pyqtSignal(int) scrollRequest = pyqtSignal(int, int) newShape = pyqtSignal() selectionChanged = pyqtSignal(bool) @@ -47,6 +48,7 @@ class Canvas(QWidget): self.prev_point = QPointF() self.offsets = QPointF(), QPointF() self.scale = 1.0 + self.overlay_color = None self.label_font_size = 8 self.pixmap = QPixmap() self.visible = {} @@ -503,7 +505,15 @@ class Canvas(QWidget): p.scale(self.scale, self.scale) p.translate(self.offset_to_center()) - p.drawPixmap(0, 0, self.pixmap) + temp = self.pixmap + if self.overlay_color: + temp = QPixmap(self.pixmap) + painter = QPainter(temp) + painter.setCompositionMode(painter.CompositionMode_Overlay) + painter.fillRect(temp.rect(), self.overlay_color) + painter.end() + + p.drawPixmap(0, 0, temp) Shape.scale = self.scale Shape.label_font_size = self.label_font_size for shape in self.shapes: @@ -607,7 +617,9 @@ class Canvas(QWidget): v_delta = delta.y() mods = ev.modifiers() - if Qt.ControlModifier == int(mods) and v_delta: + if int(Qt.ControlModifier) | int(Qt.ShiftModifier) == int(mods) and v_delta: + self.lightRequest.emit(v_delta) + elif Qt.ControlModifier == int(mods) and v_delta: self.zoomRequest.emit(v_delta) else: v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical) diff --git a/libs/lightWidget.py b/libs/lightWidget.py new file mode 100644 index 00000000..f177ed7d --- /dev/null +++ b/libs/lightWidget.py @@ -0,0 +1,33 @@ +try: + from PyQt5.QtGui import * + from PyQt5.QtCore import * + from PyQt5.QtWidgets import * +except ImportError: + from PyQt4.QtGui import * + from PyQt4.QtCore import * + + +class LightWidget(QSpinBox): + + def __init__(self, title, value=50): + super(LightWidget, self).__init__() + self.setButtonSymbols(QAbstractSpinBox.NoButtons) + self.setRange(0, 100) + self.setSuffix(' %') + self.setValue(value) + self.setToolTip(title) + self.setStatusTip(self.toolTip()) + self.setAlignment(Qt.AlignCenter) + + def minimumSizeHint(self): + height = super(LightWidget, self).minimumSizeHint().height() + fm = QFontMetrics(self.font()) + width = fm.width(str(self.maximum())) + return QSize(width, height) + + def color(self): + if self.value() == 50: + return None + + strength = int(self.value()/100 * 255 + 0.5) + return QColor(strength, strength, strength) diff --git a/resources.qrc b/resources.qrc index fd8b6c07..7479b9e3 100644 --- a/resources.qrc +++ b/resources.qrc @@ -27,6 +27,9 @@ resources/icons/zoom.png resources/icons/zoom-in.png resources/icons/zoom-out.png +resources/icons/light_reset.png +resources/icons/light_lighten.png +resources/icons/light_darken.png resources/icons/cancel.png resources/icons/next.png resources/icons/prev.png diff --git a/resources/icons/light_brighten.svg b/resources/icons/light_brighten.svg new file mode 100644 index 00000000..f257e184 --- /dev/null +++ b/resources/icons/light_brighten.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/light_darken.png b/resources/icons/light_darken.png new file mode 100644 index 0000000000000000000000000000000000000000..4fa5adb6ba0673de9796ed7831f5a1a0638a514b GIT binary patch literal 487 zcmV%poQrKn-n%;$bS&hq_a(9Wn-l_f{j>M z*=XlKAQA{x3MsTOu@Es9A-^xhoMBwoyA|hwfqiFZp6@$5!#i8jTpbukfBpF!r`W?` zv&p3f?>g}ULuf@0#xM|#Ilu~*@S{Moh4dV&_>Ee<9bqCB;7a5896^5JZEDC9oJ5en zc%2$DDrA2J`R4jQCDC#Os9`%bpelTx7%~Pei2%p=km;LA34o88_+@IqmrVQ&rIdj7 zOx%V_Ng*x?L zBY5v_D`~-8j(H6Wc@yTblO^AxUZLLPnSXL~KfnWQ2}f%a)94d!voapz71rxx^2{$W z*eJ7y*tlhKvv!Q(0;^ZT8=Mt1{)07i7cr0Z2=B1#`h^nT!;8XCLBni|s2xwyg|9+k d%NDm>{R7w%c + + + + + + + + + diff --git a/resources/icons/light_lighten.png b/resources/icons/light_lighten.png new file mode 100644 index 0000000000000000000000000000000000000000..c8679b42f5f42e4af181bccaad6f3f48ddbd1004 GIT binary patch literal 477 zcmV<30V4j1P)k)S9nnu;3@X7-9WsHcZEhRGW75^;yb`mBBwS;H?Tj#;{kiOMinoyiSL-M*rp=) zH<3g}M)^s0@DX?TixVscdm5vIAM!F(*H0{sD^8wyC|<75!^kb{;8|e5#Y=fPZVhh8 z9-iP2-s555KW}3O^O3+4g+Oaag5{dUT;QLn;r8*#;1YcuMPL|~@GBzz8!O|AKPaxt z_)-x+#q+{9*;G8kw@MK57h0|JB(62+;;uZ?EU-`ex0A^`z9(Tomg2FKYafRZ`$oap z9VWgeKIBS#!0`ZIR&ZZejp31bfpz&@Peq0+TsGkLQTGH<{P#0C^{?Vg;&{FX;Ye1M T?OG7R#9LC zi>fsARurg=yudh)&5^4IXR)J9U<8vmGY9fXoX0??;)`hFbRu^jw-fP8v1g)NN7rM+ ziCnynC0!b-V{MT@4{qRx@`c_G>*k`}j9-&Q6R(8vypOTIMDJ(~N1}@?z6+bN*#9v> z1CNEfGL`FTBm{O!^;BlFBonW=H0Hwb8NsHVKd(1xA4!|h + + + + + + + + + + + + + + + + diff --git a/resources/strings/strings.properties b/resources/strings/strings.properties index 64601175..8c5d14ce 100644 --- a/resources/strings/strings.properties +++ b/resources/strings/strings.properties @@ -52,6 +52,13 @@ fitWin=Fit Window fitWinDetail=Zoom follows window size fitWidth=Fit Width fitWidthDetail=Zoom follows window width +lightbrighten=Brighten +lightbrightenDetail=Increase Image Brightness +lightdarken=Darken +lightdarkenDetail=Decrease Image Brightness +lightreset=Original Brightness +lightresetDetail=Restore original brightness +lightWidgetTitle=Image Brightness editLabel=Edit Label editLabelDetail=Modify the label of the selected Box shapeLineColor=Shape Line Color