class VanillaWindow(QMainWindow): SHIFT = 100 MAX_BRUSH_SIZE = 500 TOOLBAR_HEIGHT = 791 TOOLBAR_WIDTH = 389 BUTTON_SIZE = 64 MENU_BAR_HEIGHT = 30 BUTTON_SHIFT = 20 MINIMUM_CANVAS_LEFT_SHIFT = 250 def __init__(self): super().__init__() self.to_draw_canvas = False self.to_draw_line = False self.to_draw_rectangle = False self.to_draw_triangle = False self.to_draw_ellipse = False self.to_draw_selection = False self.cursor_on_canvas = False self.shape_start = None self.selection_left = 0 self.selection_right = 0 self.selection_up = 0 self.selection_down = 0 self.selection_width = 0 self.selection_height = 0 self.mouse_pressed = False self.picture_name = None self.canvas = Canvas() self.button_images = {} self.buttons = {} self.layer_tables = [] self.init_ui() def init_ui(self): self.setWindowTitle('Vanilla') self.setWindowIcon(QIcon('images/icon.png')) self.showMaximized() self.setStyleSheet('QMainWindow{background: Gray;}' 'QMenuBar::item:selected{background: #202020;}' 'QMenu::item:disabled{color: #505050;}' 'QToolTip{background : black; font-size: 16px; color: white; ' 'border: black solid 1px}') self.setMouseTracking(True) self.create_menu_bar() self.color_picker = QPushButton('', self) color_picker_height = 100 self.color_picker.setGeometry(50, self.TOOLBAR_HEIGHT + self.MENU_BAR_HEIGHT - color_picker_height - 50, 100, color_picker_height) red = Canvas().current_color.r green = Canvas().current_color.g blue = Canvas().current_color.b self.color_picker.setStyleSheet(f'background: Color({red}, {green}, {blue})') self.color_picker.clicked.connect(self.pick_color) self.color_picker.show() self.size_slider = QSlider(Qt.Horizontal, self) slider_height = 20 self.size_slider.setGeometry(10, self.color_picker.y() - slider_height - 60, 145, slider_height) self.size_slider.setMinimum(1) self.size_slider.setMaximum(self.MAX_BRUSH_SIZE) self.size_slider.valueChanged[int].connect(self.size_changed) self.size_slider.show() validator = QIntValidator(1, self.MAX_BRUSH_SIZE) self.size_edit = QLineEdit('1', self) self.size_edit.setGeometry(self.size_slider.x() + self.size_slider.width() + 5, self.size_slider.y(), 35, 20) self.size_edit.setFont(QFont('Arial', 10)) self.size_edit.setAlignment(Qt.AlignCenter) self.size_edit.setValidator(validator) self.size_edit.textChanged[str].connect(self.size_edited) self.size_edit.show() self.size_label = QLabel('Brush Size:', self) self.size_label.setFont(QFont('Arial', 16)) self.size_label.setStyleSheet('color: lightGray;') self.size_label.setGeometry(self.size_slider.x(), self.size_slider.y() - self.size_slider.height() - 10, self.size_slider.width() + self.size_edit.width() + 5, 30) self.size_label.show() self.create_buttons() width = QDesktopWidget().width() height = QDesktopWidget().height() - 64 size = 20 self.layer_button_left = width - 220 self.add_layer_button = QPushButton('Add Layer', self) self.add_layer_button.setGeometry(self.layer_button_left + 24, self.TOOLBAR_HEIGHT - 15, 150, 30) self.add_layer_button.clicked.connect(self.add_layer) self.add_layer_button.setFont(QFont('Arial', 14)) self.add_layer_button.show() self.layers_label = QLabel('Layers', self) self.layers_label.setFont(QFont('Arial', 20)) self.layers_label.setStyleSheet('color: lightGray;') self.layers_label.setGeometry(self.layer_button_left, 25, 197, 60) self.layers_label.setAlignment(Qt.AlignCenter) self.layers_label.show() self.vertical_scrollbar = QScrollBar(self) self.vertical_scrollbar.setGeometry(width - size, 0, size, height - size) self.vertical_scrollbar.valueChanged[int].connect(self.vertical_scrollbar_value_changed) self.horizontal_scrollbar = QScrollBar(Qt.Horizontal, self) self.horizontal_scrollbar.setGeometry(0, height - size, width - size, size) self.horizontal_scrollbar.valueChanged[int].connect(self.horizontal_scrollbar_value_changed) self.show() def create_buttons(self): left = 25 brush_button = self.create_button(left, self.MENU_BAR_HEIGHT + 20, 'Brush', 'B', self.brush_button_clicked) self.buttons[Tools.BRUSH] = brush_button right = brush_button.x() + brush_button.width() + self.BUTTON_SHIFT eraser_button = self.create_button(right, brush_button.y(), 'Eraser', 'E', self.eraser_button_clicked) self.buttons[Tools.ERASER] = eraser_button fill_button = self.create_button(left, self.get_button_shift(brush_button), 'Fill', 'F', self.fill_button_pressed) self.buttons[Tools.FILL] = fill_button selection_button = self.create_button(right, fill_button.y(), 'Selection', 'S', self.selection_button_clicked) self.buttons[Tools.SELECTION] = selection_button line_button = self.create_button(left, self.get_button_shift(fill_button), 'Line', 'L', self.line_button_clicked) self.buttons[Tools.LINE] = line_button rectangle_button = self.create_button(right, line_button.y(), 'Rectangle', 'R', self.rectangle_button_clicked) self.buttons[Tools.SQUARE] = rectangle_button ellipse_button = self.create_button(left, self.get_button_shift(line_button), 'Ellipse', 'C', self.ellipse_button_clicked) self.buttons[Tools.CIRCLE] = ellipse_button triangle_button = self.create_button(right, ellipse_button.y(), 'Triangle', 'T', self.triangle_button_clicked) self.buttons[Tools.TRIANGLE] = triangle_button right_turn_button = self.create_button(left, self.get_button_shift(ellipse_button), 'Turn Right', 'Ctrl+R', self.turn_selection_right) left_turn_button = self.create_button(right, right_turn_button.y(), 'Turn Left', 'Ctrl+L', self.turn_selection_left) flip_horizontally_button = self.create_button(left, self.get_button_shift(right_turn_button), 'Flip Horizontally', 'Ctrl+H', self.flip_horizontally) flip_vertically_button = self.create_button(right, flip_horizontally_button.y(), 'Flip Vertically', 'Ctrl+F', self.flip_vertically) def create_menu_bar(self): self.menu_bar = self.menuBar() self.menu_bar.setStyleSheet('selection-background-color: #202020; background: #393939; color: lightGray;') file_menu = self.menu_bar.addMenu('&File') new_action = QAction('&New', self) new_action.setShortcut('Ctrl+N') new_action.triggered.connect(self.ask_size) file_menu.addAction(new_action) open_action = QAction('&Open', self) open_action.setShortcut('Ctrl+O') open_action.triggered.connect(self.open) file_menu.addAction(open_action) file_menu.addSeparator() self.save_action = QAction('&Save', self) self.save_action.setShortcut('Ctrl+S') self.save_action.triggered.connect(self.save) self.save_action.setDisabled(True) file_menu.addAction(self.save_action) self.save_as_action = QAction('Save as', self) self.save_as_action.setShortcut('Ctrl+Shift+S') self.save_as_action.triggered.connect(self.save_as) self.save_as_action.setDisabled(True) file_menu.addAction(self.save_as_action) image_menu = self.menu_bar.addMenu('&Image') self.greyscale_action = QAction('Greyscale', self) self.greyscale_action.setShortcut('Ctrl+G') self.greyscale_action.triggered.connect(self.change_greyscale) self.greyscale_action.setDisabled(True) image_menu.addAction(self.greyscale_action) self.brightness_action = QAction('Brightness', self) self.brightness_action.setShortcut('Ctrl+B') self.brightness_action.triggered.connect(self.brightness) self.brightness_action.setDisabled(True) image_menu.addAction(self.brightness_action) selection_menu = self.menu_bar.addMenu('&Selection') self.deselect_action = QAction('Deselect', self) self.deselect_action.setShortcut('Ctrl+D') self.deselect_action.triggered.connect(self.deselect) self.deselect_action.setDisabled(True) selection_menu.addAction(self.deselect_action) self.select_all_action = QAction('Select All', self) self.select_all_action.setShortcut('Ctrl+A') self.select_all_action.triggered.connect(self.select_all) self.select_all_action.setDisabled(True) selection_menu.addAction(self.select_all_action) selection_menu.addSeparator() self.delete_selection_action = QAction('Delete', self) self.delete_selection_action.setShortcut('Delete') self.delete_selection_action.triggered.connect(self.delete_in_selection) self.delete_selection_action.setDisabled(True) selection_menu.addAction(self.delete_selection_action) def create_button(self, x, y, image, shortcut='', action=lambda: None): button = QPushButton('', self) button.setGeometry(x, y, self.BUTTON_SIZE, self.BUTTON_SIZE) button.setStyleSheet('background: transparent;') button.setShortcut(shortcut) button.setToolTip(f'{image} ({shortcut})') button.clicked.connect(action) self.button_images[button] = QImage(f'images/{image}.png') button.show() return button def flip(self, flip_function): if not self.canvas.selection_is_on: return flip_function() self.to_draw_selection = False self.update_canvas() self.update() def flip_horizontally(self): self.flip(self.canvas.flip_horizontally) def flip_vertically(self): self.flip(self.canvas.flip_vertically) def get_button_shift(self, upper_button): return upper_button.y() + upper_button.height() + self.BUTTON_SHIFT def turn_selection_right(self): self.turn_selection(self.canvas.turn_selection_right) def turn_selection_left(self): self.turn_selection(self.canvas.turn_selection_left) def turn_selection(self, turn_function): if not self.canvas.selection_is_on: return turn_function() self.to_draw_selection = False self.update_canvas() self.update() def create_layer_button(self, x, y, layer_number): button = QPushButton('', self) button.setGeometry(x, y, 187, 100) button.setStyleSheet('background: transparent;') button.clicked.connect(lambda: self.change_layer(layer_number)) button.show() return button def change_layer(self, number): self.canvas.change_layer(number) self.update_color_button() self.update() def delete_layer(self, layer_table): self.canvas.delete_layer(layer_table.layer) self.layer_tables.remove(layer_table) self.update_canvas() self.update_layers_buttons() if len(self.canvas.layers) == 5: self.add_layer_button.setDisabled(False) self.update() def update_layers_buttons(self): for table in self.layer_tables: table.close() self.layer_tables = [] x = self.layer_button_left + 5 for i in range(len(self.canvas.layers)): layer = self.canvas.layers[i] btn = self.create_layer_button(x, 90 + i * 105, i) self.layer_tables.append(LayerTable(x, 90 + i * 105, layer, btn, self)) def add_layer(self): self.canvas.add_layer() if len(self.canvas.layers) == 6: self.add_layer_button.setDisabled(True) self.update_layers_buttons() self.update() def eraser_button_clicked(self): self.canvas.choose_eraser() self.update() def brush_button_clicked(self): self.canvas.choose_brush() self.update() def line_button_clicked(self): self.canvas.choose_line() self.update() def rectangle_button_clicked(self): self.canvas.choose_rectangle() self.update() def triangle_button_clicked(self): self.canvas.choose_triangle() self.update() def ellipse_button_clicked(self): self.canvas.choose_ellipse() self.update() def fill_button_pressed(self): self.canvas.choose_fill() self.update() def selection_button_clicked(self): self.canvas.choose_selection() self.update() def horizontal_scrollbar_value_changed(self, value): self.update() def vertical_scrollbar_value_changed(self, value): self.update() def change_greyscale(self): if self.canvas.active_layer.greyscale: self.canvas.turn_greyscale_off() else: self.canvas.turn_greyscale_on() self.update_canvas() self.update_color_button() self.update() def deselect(self): self.canvas.deselect() self.deselect_action.setDisabled(True) self.delete_selection_action.setDisabled(True) self.to_draw_selection = False self.update() def select_all(self): self.canvas.deselect() self.shape_start = 0, 0 self.cursor_position = self.canvas.width - 1, self.canvas.height - 1 self.update_selection_position() self.canvas.select(*self.shape_start, *self.cursor_position) self.to_draw_selection = True self.deselect_action.setDisabled(False) self.delete_selection_action.setDisabled(False) self.update() def change_brightness(self, brightness): self.canvas.change_brightness(brightness) self.update_canvas() self.update() def brightness(self): success, brightness = BrightnessDialog.get_brightness(self, self.canvas.active_layer.brightness) if success: self.change_brightness(brightness) def delete_in_selection(self): if not self.canvas.selection_is_on: return self.canvas.delete_selection() self.update_canvas() self.update() @property def canvas_left_side(self): return self.canvas_left_edge - self.horizontal_scrollbar.value() @property def canvas_upper_side(self): return self.canvas_upper_edge - self.vertical_scrollbar.value() def size_edited(self, size): if size == '': value = 1 else: value = int(size) if value == 0: self.size_edit.setText('1') else: self.size_slider.setValue(value) def size_changed(self, value): if value == 0: self.canvas.brush_size = 1 else: self.canvas.brush_size = value if self.to_draw_canvas: self.change_cursor() self.size_edit.setText(f'{self.canvas.brush_size}') def pick_color(self): color = QColorDialog.getColor() if color.isValid(): self.canvas.change_color(color.red(), color.green(), color.blue(), color.alpha()) self.update_color_button() def update_color_button(self): cur_color = self.canvas.current_color color = QColor(cur_color.r, cur_color.g, cur_color.b) self.color_picker.setStyleSheet(f'background: {color.name()}') def ask_size(self): success, width, height = SizeDialog.get_size(self) if success: self.create_canvas(width, height) def create_canvas(self, width, height): old_canvas = self.canvas self.canvas = Canvas(width, height) self.canvas.current_color_rgb = old_canvas.current_color_rgb self.canvas.brush_size = old_canvas.brush_size self.canvas.current_tool = old_canvas.current_tool max_size = self.height() - self.SHIFT * 2 proportion = width / height if proportion > 1: self.canvas_width = max_size self.canvas_height = max_size / proportion else: self.canvas_width = max_size * proportion self.canvas_height = max_size self.pixel_size = self.canvas_width / self.canvas.width self.canvas_left_edge = (self.width() - self.canvas_width) / 2 self.canvas_upper_edge = (self.height() - self.canvas_height) / 2 self.canvas_as_image = self.convert_to_image() self.to_draw_canvas = True self.enable_actions() self.update_layers_buttons() def enable_actions(self): self.save_action.setDisabled(False) self.save_as_action.setDisabled(False) self.greyscale_action.setDisabled(False) self.brightness_action.setDisabled(False) self.select_all_action.setDisabled(False) def convert_to_image(self): image = QImage(self.canvas.width, self.canvas.height, QImage.Format_ARGB32) x = 0 y = 0 for layer in self.canvas.layers: for column in layer.pixels: for pixel in column: image.setPixelColor(x, y, QColor(pixel.r, pixel.g, pixel.b, pixel.a)) y += 1 x += 1 y = 0 return image def open(self): load_file_name = QFileDialog.getOpenFileName(self, 'Chose Image File', '', 'PNG Files (*.png);;JPG Files (*.jpg);;BMP Files (*.bmp)')[0] if load_file_name == '': return False self.picture_name = load_file_name image = QImage(load_file_name) self.create_canvas(image.width(), image.height()) for x in range(self.canvas.width): for y in range(self.canvas.height): color = image.pixelColor(x, y) self.canvas.pixels[x][y] = Color(color.red(), color.green(), color.blue(), color.alpha()) self.update_canvas() self.to_draw_canvas = True self.update() def save(self): if self.picture_name is None: self.save_as() else: self.canvas_as_image.save(self.picture_name) def save_as(self): name = QFileDialog.getSaveFileName(self, 'Save', '', 'PNG Files (*.png);;JPG Files (*.jpg);;BMP Files (*.bmp)')[0] if name == '': return False self.picture_name = name self.save() def change_cursor(self): if self.canvas.current_tool == Tools.BRUSH: self.setCursor(self.get_brush_cursor()) elif self.canvas.current_tool == Tools.ERASER: self.setCursor(self.get_eraser_cursor()) elif self.canvas.current_tool == Tools.FILL: self.setCursor(self.get_fill_cursor()) def get_brush_cursor(self): size = (2 * self.canvas.brush_size - 1) * self.pixel_size cursor = QPixmap(size, size) cursor.fill(Qt.transparent) painter = QPainter() painter.begin(cursor) painter.setPen(QColor(self.canvas.current_color.r, self.canvas.current_color.g, self.canvas.current_color.b)) painter.drawEllipse(0, 0, size - 1, size - 1) painter.drawLine(size / 2, size / 4, size / 2, size / 4 * 3) painter.drawLine(size / 4, size / 2, size / 4 * 3, size / 2) painter.end() return QCursor(cursor) def get_eraser_cursor(self): size = (2 * self.canvas.brush_size - 1) * self.pixel_size cursor = QPixmap(size, size) cursor.fill(Qt.transparent) painter = QPainter() painter.begin(cursor) painter.setBrush(QBrush(QColor(255, 255, 255))) painter.drawEllipse(0, 0, size - 1, size - 1) painter.end() return QCursor(cursor) def get_fill_cursor(self): fill = QImage('images/FillCursor.png') cursor = QPixmap(fill.width(), fill.height()) cursor.fill(Qt.transparent) painter = QPainter() painter.begin(cursor) painter.setBrush(QColor(self.canvas.current_color.r, self.canvas.current_color.g, self.canvas.current_color.b)) painter.drawRect(10, 10, 10, 4) painter.drawImage(0, 0, fill) painter.end() return QCursor(cursor) def mouseMoveEvent(self, event): if not self.to_draw_canvas: return x = math.floor((event.pos().x() - self.canvas_left_side) / self.pixel_size) y = math.floor((event.pos().y() - self.canvas_upper_side) / self.pixel_size) if 0 <= x < self.canvas.width and 0 <= y < self.canvas.height: self.cursor_position = (x, y) self.change_cursor() self.cursor_on_canvas = True else: if not self.mouse_pressed: self.shape_start = None self.cursor_on_canvas = False self.setCursor(Qt.ArrowCursor) if self.mouse_pressed: self.move_tools() self.update_canvas() self.update() def move_tools(self): if self.canvas.current_tool in [Tools.BRUSH, Tools.ERASER]: self.canvas.paint(*self.cursor_position) if self.cursor_on_canvas and self.canvas.current_tool == Tools.SELECTION: self.update_selection_position() def update_canvas(self): if not self.to_draw_canvas: return while len(self.canvas.changed_pixels) > 0: x, y = self.canvas.changed_pixels.pop() layer = len(self.canvas.layers) - 1 pixel = self.canvas.get_pixel(x, y, layer) while layer > 0 and pixel.a == 0: layer -= 1 pixel = self.canvas.get_pixel(x, y, layer) self.canvas_as_image.setPixelColor(x, y, QColor(pixel.r, pixel.g, pixel.b, pixel.a)) def mousePressEvent(self, event): if self.cursor_on_canvas and event.button() == Qt.LeftButton: self.mouse_pressed = True self.press_tools() def press_tools(self): if self.canvas.current_tool in [Tools.LINE, Tools.SQUARE, Tools.TRIANGLE, Tools.CIRCLE, Tools.SELECTION]: self.shape_start = self.cursor_position if self.canvas.current_tool in [Tools.BRUSH, Tools.ERASER]: self.canvas.paint(*self.cursor_position) elif self.canvas.current_tool == Tools.LINE: self.to_draw_line = True elif self.canvas.current_tool == Tools.SQUARE: self.to_draw_rectangle = True elif self.canvas.current_tool == Tools.TRIANGLE: self.to_draw_triangle = True elif self.canvas.current_tool == Tools.CIRCLE: self.to_draw_ellipse = True elif self.canvas.current_tool == Tools.FILL: self.canvas.fill(*self.cursor_position) elif self.canvas.current_tool == Tools.SELECTION: self.deselect() self.deselect_action.setDisabled(False) self.delete_selection_action.setDisabled(False) self.to_draw_selection = True def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton and self.to_draw_canvas: self.release_tools() self.update_canvas() self.update() self.mouse_pressed = False def release_tools(self): if self.shape_start is None: return if self.canvas.current_tool == Tools.SELECTION: self.canvas.select(*self.shape_start, *self.cursor_position) if self.canvas.current_tool == Tools.LINE: self.to_draw_line = False self.canvas.draw_line(*self.shape_start, *self.cursor_position) elif self.canvas.current_tool == Tools.SQUARE: self.to_draw_rectangle = False self.canvas.draw_rectangle(*self.shape_start, *self.cursor_position) elif self.canvas.current_tool == Tools.TRIANGLE: self.to_draw_triangle = False self.canvas.draw_triangle(*self.shape_start, *self.cursor_position) elif self.canvas.current_tool == Tools.CIRCLE: self.to_draw_ellipse = False self.canvas.draw_ellipse(*self.shape_start, *self.cursor_position) def wheelEvent(self, event): if not self.to_draw_canvas: return delta = event.angleDelta().y() / 120 old_width = self.canvas_width old_height = self.canvas_height if self.canvas.width * (self.pixel_size + delta) > 0 and self.canvas.height * (self.pixel_size + delta) > 0: self.pixel_size += delta self.canvas_width = self.canvas.width * self.pixel_size self.canvas_height = self.canvas.height * self.pixel_size self.canvas_left_edge += (old_width - self.canvas_width) / 2 self.canvas_upper_edge += (old_height - self.canvas_height) / 2 self.change_cursor() self.update() if self.canvas_left_edge < self.MINIMUM_CANVAS_LEFT_SHIFT: max = self.MINIMUM_CANVAS_LEFT_SHIFT - self.canvas_left_edge self.horizontal_scrollbar.setMaximum(max) self.horizontal_scrollbar.setMinimum(-max) self.horizontal_scrollbar.show() else: self.horizontal_scrollbar.hide() if self.canvas_upper_edge < self.MENU_BAR_HEIGHT: max = self.MENU_BAR_HEIGHT - self.canvas_upper_edge self.vertical_scrollbar.setMinimum(-max) self.vertical_scrollbar.setMaximum(max) self.vertical_scrollbar.show() else: self.vertical_scrollbar.hide() def paintEvent(self, event): painter = QPainter() painter.begin(self) if self.to_draw_canvas: self.draw_pixels(painter) painter.setPen(QColor(self.canvas.current_color.r, self.canvas.current_color.g, self.canvas.current_color.b)) if self.to_draw_line: self.draw_line(painter) if self.to_draw_rectangle: self.draw_rectangle(painter) if self.to_draw_triangle: self.draw_triangle(painter) if self.to_draw_ellipse: self.draw_ellipse(painter) if self.to_draw_selection: self.draw_selection(painter) self.draw_edges(painter) self.draw_layers(painter) painter.drawImage(0, self.menu_bar.height(), QImage('images/ToolBar.png')) self.highlight_current_button(painter) self.draw_buttons(painter) painter.end() def draw_layers(self, painter): painter.drawImage(self.width() - self.TOOLBAR_WIDTH, self.menu_bar.height(), QImage('images/LayersBar.png')) self.draw_current_layer(painter) for table in self.layer_tables: table.draw(painter) def highlight_current_button(self, painter): painter.setBrush(QColor(255, 255, 255)) painter.setPen(Qt.transparent) button = self.buttons[self.canvas.current_tool] painter.drawRect(button.x(), button.y(), self.BUTTON_SIZE, self.BUTTON_SIZE) def get_start_end_position(self): start_x, start_y = self.shape_start end_x, end_y = self.cursor_position start_x = start_x * self.pixel_size + self.canvas_left_side + self.pixel_size / 2 start_y = start_y * self.pixel_size + self.canvas_upper_side + self.pixel_size / 2 end_x = end_x * self.pixel_size + self.canvas_left_side + self.pixel_size / 2 end_y = end_y * self.pixel_size + self.canvas_upper_side + self.pixel_size / 2 return start_x, start_y, end_x, end_y def draw_line(self, painter): start_x, start_y, end_x, end_y = self.get_start_end_position() painter.drawLine(start_x, start_y, end_x, end_y) def draw_rectangle(self, painter): start_x, start_y, end_x, end_y = self.get_start_end_position() left = min(start_x, end_x) up = min(start_y, end_y) painter.drawRect(left, up, abs(end_x - start_x), abs(end_y - start_y)) def draw_triangle(self, painter): start_x, start_y, end_x, end_y = self.get_start_end_position() left = min(start_x, end_x) right = max(start_x, end_x) up = min(start_y, end_y) down = max(start_y, end_y) width = right - left left_up = left + width // 2 right_up = left_up + (width % 2) painter.drawLine(left_up, up, left, down) painter.drawLine(right_up, up, right, down) painter.drawLine(left, down, right, down) def draw_ellipse(self, painter): start_x, start_y, end_x, end_y = self.get_start_end_position() left = min(start_x, end_x) up = min(start_y, end_y) painter.drawEllipse(left, up, abs(end_x - start_x), abs(end_y - start_y)) def update_selection_position(self): start_x = self.shape_start[0] * self.pixel_size + self.canvas_left_side start_y = self.shape_start[1] * self.pixel_size + self.canvas_upper_side end_x = self.cursor_position[0] * self.pixel_size + self.canvas_left_side end_y = self.cursor_position[1] * self.pixel_size + self.canvas_upper_side self.selection_left = min(start_x, end_x) self.selection_right = max(start_x, end_x) + self.pixel_size self.selection_up = min(start_y, end_y) self.selection_down = max(start_y, end_y) + self.pixel_size self.selection_width = abs(self.cursor_position[0] - self.shape_start[0]) + 1 self.selection_height = abs(self.cursor_position[1] - self.shape_start[1]) + 1 def draw_selection(self, painter): width = self.pixel_size / 4 black_pen = QPen(QColor(0, 0, 0)) black_pen.setWidth(3) white_pen = QPen(QColor(255, 255, 255)) white_pen.setWidth(3) painter.setPen(black_pen) for x in range(self.selection_width): painter.drawLine(self.selection_left + x * self.pixel_size, self.selection_up, self.selection_left + x * self.pixel_size + width, self.selection_up) painter.drawLine(self.selection_left + x * self.pixel_size, self.selection_down, self.selection_left + x * self.pixel_size + width, self.selection_down) painter.setPen(white_pen) painter.drawLine(self.selection_left + x * self.pixel_size + width, self.selection_up, self.selection_left + x * self.pixel_size + width * 3, self.selection_up) painter.drawLine(self.selection_left + x * self.pixel_size + width, self.selection_down, self.selection_left + x * self.pixel_size + width * 3, self.selection_down) painter.setPen(black_pen) painter.drawLine(self.selection_left + x * self.pixel_size + width * 3, self.selection_up, self.selection_left + x * self.pixel_size + width * 4, self.selection_up) painter.drawLine(self.selection_left + x * self.pixel_size + width * 3, self.selection_down, self.selection_left + x * self.pixel_size + width * 4, self.selection_down) for y in range(self.selection_height): painter.drawLine(self.selection_left, self.selection_up + y * self.pixel_size, self.selection_left, self.selection_up + y * self.pixel_size + width) painter.drawLine(self.selection_right, self.selection_up + y * self.pixel_size, self.selection_right, self.selection_up + y * self.pixel_size + width) painter.setPen(white_pen) painter.drawLine(self.selection_left, self.selection_up + y * self.pixel_size + width, self.selection_left, self.selection_up + y * self.pixel_size + width * 3) painter.drawLine(self.selection_right, self.selection_up + y * self.pixel_size + width, self.selection_right, self.selection_up + y * self.pixel_size + width * 3) painter.setPen(black_pen) painter.drawLine(self.selection_left, self.selection_up + y * self.pixel_size + width * 3, self.selection_left, self.selection_up + y * self.pixel_size + width * 4) painter.drawLine(self.selection_right, self.selection_up + y * self.pixel_size + width * 3, self.selection_right, self.selection_up + y * self.pixel_size + width * 4) def draw_pixels(self, painter): painter.drawImage(self.canvas_left_side, self.canvas_upper_side, self.canvas_as_image.scaled(self.canvas_width, self.canvas_height)) painter.setBrush(Qt.transparent) black_pen = QPen(QColor(0, 0, 0)) black_pen.setWidth(3) painter.setPen(black_pen) painter.drawRect(self.canvas_left_side, self.canvas_upper_side, self.canvas_width, self.canvas_height) painter.setPen(Qt.black) self.draw_grid(painter) def draw_grid(self, painter): if self.pixel_size > 10: for i in range(self.canvas.width + 1): x = self.canvas_left_side + i * self.pixel_size painter.drawLine(x, self.canvas_upper_side, x, self.canvas_upper_side + self.canvas_height) for i in range(self.canvas.height + 1): y = self.canvas_upper_side + i * self.pixel_size painter.drawLine(self.canvas_left_side, y, self.canvas_left_side + self.canvas_width, y) def draw_edges(self, painter): painter.setPen(Qt.transparent) painter.setBrush(QColor('#393939')) painter.drawRect(self.vertical_scrollbar.x(), self.vertical_scrollbar.y(), self.vertical_scrollbar.width(), self.vertical_scrollbar.height() + self.horizontal_scrollbar.height() + 1) painter.drawRect(self.horizontal_scrollbar.x(), self.horizontal_scrollbar.y(), self.horizontal_scrollbar.width(), self.horizontal_scrollbar.height() + 1) def draw_current_layer(self, painter): if not self.to_draw_canvas: return painter.setBrush(Qt.white) table = self.layer_tables[self.canvas.current_layer] painter.drawRect(table.x - 1, table.y - 1, LayerTable.WIDTH + 2, LayerTable.HEIGHT + 2) def draw_buttons(self, painter): painter.setPen(Qt.transparent) painter.setBrush(QColor(self.canvas.current_color.r, self.canvas.current_color.g, self.canvas.current_color.b)) for button, image in self.button_images.items(): painter.drawRect(button.x() + 3, button.y() + 3, self.BUTTON_SIZE - 6, self.BUTTON_SIZE - 6) painter.drawImage(button.x(), button.y(), image.scaled(self.BUTTON_SIZE, self.BUTTON_SIZE))
class ScrollBar(QWidget): """ 定义一个可以变换样式的滚动条 """ def __init__(self, externalScrollBar=None, parent=None): super().__init__(parent) self.externalScrollBar = externalScrollBar # 实例化两个滚动条 self.minScrollBar = QScrollBar(Qt.Vertical, self) self.maxScrollBar = QScrollBar(Qt.Vertical, self) # 实例化一个控制滚动条显示的计时器 self.timer = QTimer(self) # 初始化 self.initWidget() self.associateScrollBar() self.setQss() def initWidget(self): """ 初始化小部件 """ self.setFixedWidth(20) self.minScrollBar.move(15, 0) self.maxScrollBar.hide() self.timer.setInterval(2000) self.timer.timeout.connect(self.showMinScrollBar) self.setAttribute(Qt.WA_TranslucentBackground) # 分配ID self.setObjectName('father') self.minScrollBar.setObjectName('minScrollBar') self.maxScrollBar.setObjectName('maxScrollBar') def adjustSrollBarHeight(self): """ 根据父级窗口的高度调整滚动条高度 """ if self.parent(): self.minScrollBar.setFixedHeight(self.parent().height() - 153) self.maxScrollBar.setFixedHeight(self.parent().height() - 153) def enterEvent(self, e: QEnterEvent): """ 鼠标进入界面时显示大滚动条并停止秒表 """ self.maxScrollBar.show() self.minScrollBar.hide() self.timer.stop() def leaveEvent(self, e): """ 鼠标离开打开秒表 """ self.timer.start() def showMinScrollBar(self): """ 定时溢出时隐藏大滚动条 """ self.timer.stop() self.maxScrollBar.hide() self.minScrollBar.show() def setQss(self): """ 设置层叠样式 """ with open(r'resource\css\my_scrollBar.qss', encoding='utf-8') as f: self.setStyleSheet(f.read()) def associateScrollBar(self): """ 关联滚动条 """ if self.externalScrollBar: # 设置最大值 self.minScrollBar.setMaximum(self.externalScrollBar.maximum()) self.maxScrollBar.setMaximum(self.externalScrollBar.maximum()) # 关联滚动条 self.externalScrollBar.valueChanged.connect( lambda: self.minScrollBar.setValue(self.externalScrollBar. value())) self.minScrollBar.valueChanged.connect(self.__minScrollBarChanged) self.maxScrollBar.valueChanged.connect( lambda: self.minScrollBar.setValue(self.maxScrollBar.value())) def __minScrollBarChanged(self): """ minScrollBar改变时同时改变另外两个滚动条的值 """ self.maxScrollBar.setValue(self.minScrollBar.value()) self.externalScrollBar.setValue(self.minScrollBar.value())
class BitsWidget(QWidget): def __init__(self, offset, bit_size, row_width, grid_width, grid_height, bit_border_threshold) -> None: super().__init__() self._app = App() self._offset = offset self._bit_size = bit_size self._row_width = row_width self._grid_width = grid_width self._grid_h_offset = 0 self._grid_height = grid_height self._grid_v_offset = 0 self._one_color = Qt.blue self._zero_color = Qt.white self._color_table = [ QColor(self._zero_color).rgb(), QColor(self._one_color).rgb(), ] self._bit_border_threshold = bit_border_threshold self._easter_egg = 0 self._painting = False self._bits_area = QWidget() self._h_scrollbar = QScrollBar(orientation=Qt.Horizontal) self._h_scrollbar.setMinimum(0) self._h_scrollbar.valueChanged.connect(self._on_scrollbar_change) self._h_scrollbar.hide() self._v_scrollbar = QScrollBar(orientation=Qt.Vertical) self._v_scrollbar.setMinimum(0) self._v_scrollbar.valueChanged.connect(self._on_scrollbar_change) self._v_scrollbar.hide() inner_layout = QVBoxLayout() inner_layout.setContentsMargins(0, 0, 0, 0) inner_layout.setSpacing(0) inner_layout.addWidget(self._bits_area, stretch=1) inner_layout.addWidget(self._h_scrollbar) inner_widget = QWidget() inner_widget.setLayout(inner_layout) outer_layout = QHBoxLayout() outer_layout.setContentsMargins(0, 0, 0, 0) outer_layout.setSpacing(0) outer_layout.addWidget(inner_widget, stretch=1) outer_layout.addWidget(self._v_scrollbar) self.setLayout(outer_layout) @property def offset(self) -> int: return self._offset @offset.setter def offset(self, new_offset): self._offset = new_offset @property def bit_size(self) -> int: return self._bit_size @bit_size.setter def bit_size(self, size: int): self._bit_size = size @property def row_width(self) -> int: return self._row_width @row_width.setter def row_width(self, width: int): self._row_width = width @property def grid_width(self) -> int: return self._grid_width @grid_width.setter def grid_width(self, size: int): self._grid_width = size @property def grid_h_offset(self) -> int: return self._grid_h_offset @grid_h_offset.setter def grid_h_offset(self, offset: int): self._grid_h_offset = offset @property def grid_height(self) -> int: return self._grid_height @grid_height.setter def grid_height(self, size: int): self._grid_height = size @property def grid_v_offset(self) -> int: return self._grid_v_offset @grid_v_offset.setter def grid_v_offset(self, offset: int): self._grid_v_offset = offset @property def _bits_area_width(self): return self._bits_area.width() @property def _bits_area_height(self): return self._bits_area.height() @property def _num_rows(self): return ceil((self._app.num_bits - self._offset) / self._row_width) def set_bit_border_threshold(self, threshold): self._bit_border_threshold = threshold def load_file(self, filename, max_bytes): self._app.load_file(filename, max_bytes) def paintEvent(self, a0: QPaintEvent) -> None: super().paintEvent(a0) if not self._app.num_bits: return self._painting = True self._set_scrollbars() self._paint_bits() self._draw_grid() self._painting = False def _paint_bits(self): visible_columns = self._bits_area_width // self._bit_size visible_rows = self._bits_area_height // self._bit_size bitmap = self._app.create_bitmap( self._offset, self._row_width, self._h_scrollbar.value(), self._v_scrollbar.value(), visible_rows, visible_columns, ) pixmap = self._create_pixmap(bitmap.data, bitmap.width, bitmap.bytes_per_row) painter = QPainter(self) painter.drawPixmap(0, 0, pixmap) self._draw_bit_separators(painter, pixmap.width(), pixmap.height()) self._draw_last_row_of_bits(painter, bitmap.remainder, pixmap) painter.end() def _draw_bit_separators(self, painter: QPainter, right, bottom): if self._bit_size <= self._bit_border_threshold: return painter.setPen(QPen(Qt.black, 1)) self._draw_h_grid(painter, right, bottom, 1, 0) self._draw_v_grid(painter, right, bottom, 1, 0) def _draw_last_row_of_bits(self, painter, last_row_of_bits, pixmap): painter.setPen( QPen( Qt.black if self._bit_size > self._bit_border_threshold else Qt.transparent, 1, Qt.SolidLine, )) for i, b in enumerate(last_row_of_bits): self._draw_bit(painter, i, pixmap.height() // self._bit_size, b) def _draw_bit(self, painter: QPainter, x, y, bit): painter.setBrush( QBrush(self._one_color if bit else self._zero_color, Qt.SolidPattern)) painter.drawRect(x * self._bit_size, y * self._bit_size, self._bit_size, self._bit_size) def _draw_grid(self): if not self._grid_width and not self._grid_height: return if not self._num_rows: return painter = QPainter(self) painter.setPen(QPen(Qt.red, 1, Qt.SolidLine)) right = min( (self._row_width - self._h_scrollbar.value()) * self._bit_size, self._bits_area_width, ) bottom = min( (self._num_rows - self._v_scrollbar.value()) * self._bit_size, self._bits_area_height, ) self._draw_h_grid( painter, right, bottom, self._grid_width, self._grid_h_offset - self._h_scrollbar.value(), ) self._draw_v_grid( painter, right, bottom, self._grid_height, self._grid_v_offset - self._v_scrollbar.value(), ) def _create_pixmap(self, data: bytes, width, bytes_per_row: int): height = len(data) // bytes_per_row image = QImage(data, width, height, bytes_per_row, QImage.Format_Mono) image.setColorTable(self._color_table) return QPixmap.fromImage(image).scaled(width * self._bit_size, height * self._bit_size) def _draw_h_grid(self, painter, right, bottom, grid_width, grid_offset): if not grid_width: return start_offset = grid_offset % grid_width start = start_offset * self._bit_size for x in range(start, right + 1, grid_width * self._bit_size): painter.drawLine(x, 0, x, bottom) def _draw_v_grid(self, painter, right, bottom, grid_height, grid_offset): if not grid_height: return start_offset = grid_offset % grid_height start = start_offset * self._bit_size for y in range(start, bottom + 1, grid_height * self._bit_size): painter.drawLine(0, y, right, y) def _set_scrollbars(self): self._set_h_scrollbar() v_visibility_changed = self._set_v_scrollbar() if v_visibility_changed: h_visibility_changed = self._set_h_scrollbar() if h_visibility_changed: self._set_v_scrollbar() def _set_h_scrollbar(self): h_scroll_max = self._calc_h_scrollbar_max() was_visible = self._h_scrollbar.isVisible() if h_scroll_max > 0: self._h_scrollbar.setMaximum(h_scroll_max) self._h_scrollbar.show() return not was_visible else: self._h_scrollbar.setValue(0) self._h_scrollbar.hide() return was_visible def _set_v_scrollbar(self): v_scroll_max = self._calc_v_scrollbar_max() was_visible = self._v_scrollbar.isVisible() if v_scroll_max > 0: self._v_scrollbar.setMaximum(v_scroll_max) self._v_scrollbar.show() return not was_visible else: self._v_scrollbar.setValue(0) self._v_scrollbar.hide() return was_visible def _calc_h_scrollbar_max(self): return self._row_width - (self._bits_area_width // self.bit_size) def _calc_v_scrollbar_max(self): visible_rows = self._bits_area_height // self.bit_size return self._num_rows - visible_rows def wheelEvent(self, event: QWheelEvent) -> None: y_delta = event.angleDelta().y() if y_delta: notches = ceil(y_delta / 120) self._v_scrollbar.setValue(self._v_scrollbar.value() - notches) x_delta = event.angleDelta().x() if x_delta: notches = ceil(x_delta / 120) self._h_scrollbar.setValue(self._h_scrollbar.value() - notches) def mousePressEvent(self, a0: QMouseEvent) -> None: if (a0.x() <= self._bit_size and a0.y() <= self._bit_size and self._easter_egg < 9): self._easter_egg += 1 elif (self._bit_size <= a0.x() <= self._bit_size * 2 and self._bit_size <= a0.y() <= self._bit_size * 2 and 9 <= self._easter_egg < 12): self._easter_egg += 1 elif (self._bit_size * 2 <= a0.x() <= self._bit_size * 3 and self._bit_size * 2 <= a0.y() <= self._bit_size * 3 and self._easter_egg == 12): tmp = self._one_color self._one_color = self._zero_color self._zero_color = tmp self._color_table = [self._color_table[1], self._color_table[0]] self._easter_egg = 0 else: self._easter_egg = 0 def _on_scrollbar_change(self): if not self._painting: self.repaint()