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 00000000..4fa5adb6 Binary files /dev/null and b/resources/icons/light_darken.png differ diff --git a/resources/icons/light_darken.svg b/resources/icons/light_darken.svg new file mode 100644 index 00000000..1002ca85 --- /dev/null +++ b/resources/icons/light_darken.svg @@ -0,0 +1,52 @@ + + + + + + + + + + diff --git a/resources/icons/light_lighten.png b/resources/icons/light_lighten.png new file mode 100644 index 00000000..c8679b42 Binary files /dev/null and b/resources/icons/light_lighten.png differ diff --git a/resources/icons/light_reset.png b/resources/icons/light_reset.png new file mode 100644 index 00000000..225765d0 Binary files /dev/null and b/resources/icons/light_reset.png differ diff --git a/resources/icons/light_reset.svg b/resources/icons/light_reset.svg new file mode 100644 index 00000000..6aea0030 --- /dev/null +++ b/resources/icons/light_reset.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + 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