Skip to content

Commit 84ab382

Browse files
authored
Refactor (#4)
Entirely refactors it, created new files to handle better functions. Stil have color output issues but wer'e getting there
2 parents 5c58e80 + 79bc314 commit 84ab382

File tree

12 files changed

+559
-492
lines changed

12 files changed

+559
-492
lines changed

brushes.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# brushes.py
2+
from PyQt6.QtGui import QPainter, QPen, QColor, QBrush, QPainterPath, QPixmap
3+
from PyQt6.QtCore import Qt, QPointF
4+
from color_utils import create_pen
5+
6+
def draw_brushes(canvas, cursor_point):
7+
painter = QPainter(canvas.image)
8+
painter.setRenderHint(canvas.render_hint)
9+
10+
if canvas.tool == "pencil" or canvas.tool == "pen":
11+
draw_pencil(painter, canvas, cursor_point, canvas.brush_color, canvas.brush_shape, canvas.brush_size)
12+
if canvas.tool == "ink":
13+
draw_ink(painter, canvas, cursor_point, canvas.brush_color, canvas.brush_shape, canvas.brush_size)
14+
elif canvas.tool == "paint":
15+
draw_line(painter, canvas, cursor_point, canvas.brush_color, canvas.brush_shape, canvas.brush_size)
16+
elif canvas.tool == "airbrush" and canvas.custom_brush:
17+
draw_custom_brush(painter, canvas, cursor_point)
18+
elif canvas.tool == "eraser":
19+
draw_eraser(painter, canvas, cursor_point)
20+
21+
22+
def draw_pencil(painter, canvas, cursor_point, color, brush_shape, brush_size):
23+
pen = create_pen(color, brush_size / 2, brush_shape)
24+
painter.setPen(pen)
25+
painter.drawLine(canvas.last_point, cursor_point)
26+
canvas.last_point = cursor_point
27+
canvas.update()
28+
29+
def draw_ink(painter, canvas, cursor_point, color, brush_shape, brush_size):
30+
pen = create_pen(color.darker(), brush_size, brush_shape)
31+
painter.setPen(pen)
32+
painter.drawLine(canvas.last_point, cursor_point)
33+
canvas.last_point = cursor_point
34+
canvas.update()
35+
36+
def draw_eraser(painter, canvas, cursor_point):
37+
pen = create_pen(Qt.GlobalColor.white, canvas.brush_size, canvas.brush_shape)
38+
painter.setPen(pen)
39+
brush_half_size = canvas.brush_size // 2
40+
painter.drawEllipse(int(cursor_point.x() - brush_half_size), int(cursor_point.y() - brush_half_size),
41+
canvas.brush_size, canvas.brush_size)
42+
canvas.last_point = cursor_point
43+
canvas.update()
44+
45+
def draw_line(painter, canvas, cursor_point, color, brush_shape, brush_size):
46+
pen = create_pen(color, brush_size, brush_shape)
47+
painter.setPen(pen)
48+
brush_half_size = brush_size // 2
49+
painter.drawEllipse(int(cursor_point.x() - brush_half_size), int(cursor_point.y() - brush_half_size),
50+
brush_size, brush_size)
51+
canvas.last_point = cursor_point
52+
canvas.update()
53+
54+
def draw_custom_brush(painter, canvas, cursor_point):
55+
brush_half_size = canvas.brush_size // 2
56+
painter.drawPixmap(int(cursor_point.x() - brush_half_size), int(cursor_point.y() - brush_half_size),
57+
canvas.custom_brush.scaled(canvas.brush_size, canvas.brush_size))
58+
canvas.last_point = cursor_point
59+
canvas.update()

canvas.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# canvas.py
2+
from PyQt6.QtCore import Qt, QPoint, QDateTime, QSize, QPointF
3+
from PyQt6.QtGui import QPainter, QPen, QPixmap, QColor, QBrush, QPainterPath, QImage, QFont, QCursor
4+
from PyQt6.QtWidgets import QWidget
5+
import math
6+
from brushes import draw_brushes
7+
8+
class OekakiCanvas(QWidget):
9+
def __init__(self):
10+
super().__init__()
11+
self.image = QPixmap(800, 600)
12+
self.image.fill(Qt.GlobalColor.white)
13+
self.undo_stack = []
14+
self.drawing = False
15+
self.brush_size = 5
16+
self.brush_color = QColor(Qt.GlobalColor.black) # Start with a QColor
17+
self.brush_shape = Qt.PenCapStyle.RoundCap
18+
self.last_point = QPointF() # Stored as QPointF now
19+
self.tool = "pencil"
20+
self.custom_brush = None
21+
self.current_shape = None # Added for shapes
22+
self.shape_start = None # Added for shapes
23+
self.canvas_history = [] # Added for shape tracking
24+
self.canvas_history.append(self.image.copy())
25+
self.path = QPainterPath() # For smooth lines
26+
self.setMouseTracking(True) # Added Mouse Tracking.
27+
self.render_hint = QPainter.RenderHint.Antialiasing
28+
self.ghost_opacity = 0.2 # Default ghosting opacity
29+
self.blender_palette = [] # Added blender palette
30+
self.text_input = "" # Added for text tool
31+
self.font = QFont("Arial", 12) # default font
32+
self.font.setPixelSize(20)
33+
34+
35+
def paintEvent(self, event):
36+
canvas_painter = QPainter(self)
37+
canvas_painter.setRenderHint(self.render_hint) # Set antialiasing
38+
canvas_painter.drawPixmap(self.rect(), self.image, self.image.rect())
39+
40+
def mousePressEvent(self, event):
41+
if event.button() == Qt.MouseButton.LeftButton:
42+
self.drawing = True
43+
self.last_point = event.position() # Correctly set to QPointF
44+
self.save_undo_state() # Undo point
45+
if self.tool == "rectangle":
46+
self.shape_start = event.position().toPoint()
47+
elif self.tool in ["pencil", "pen", "ink", "paint"]: # Added new pathing for smooth lines
48+
self.path.moveTo(event.position())
49+
elif self.tool == "blender":
50+
self.pick_blender_color(event.position()) # Get the color to blend.
51+
elif self.tool == "text":
52+
self.draw_text(event.position())
53+
54+
def mouseMoveEvent(self, event):
55+
if event.buttons() & Qt.MouseButton.LeftButton and self.drawing:
56+
if self.tool == "rectangle":
57+
self.draw_shape(event.position().toPoint())
58+
elif self.tool in ["pencil", "pen", "ink", "paint"]:
59+
self.draw_smooth_line(event.position())
60+
elif self.tool == "blender":
61+
self.draw_blender(event.position())
62+
else:
63+
self.draw(event.position().toPoint())
64+
65+
def mouseReleaseEvent(self, event):
66+
if event.button() == Qt.MouseButton.LeftButton:
67+
self.drawing = False
68+
if self.tool == "rectangle":
69+
self.shape_start = None # clear the point so we can make a new shape.
70+
elif self.tool in ["pencil", "pen", "ink", "paint"]:
71+
self.path = QPainterPath() # Clear the path
72+
73+
def pick_blender_color(self, position):
74+
image = self.image.toImage()
75+
if image and position.x() < self.image.width() and position.y() < self.image.height() and position.x() >= 0 and position.y() >= 0:
76+
pixel_color = image.pixelColor(position.toPoint())
77+
if len(self.blender_palette) < 10:
78+
self.blender_palette.append(pixel_color)
79+
else:
80+
self.blender_palette.pop(0)
81+
self.blender_palette.append(pixel_color)
82+
self.update()
83+
84+
def draw_shape(self, current_point):
85+
if self.shape_start: # if we have a previous point
86+
self.image = self.canvas_history[-1].copy() # set the image as the most recent image
87+
painter = QPainter(self.image)
88+
painter.setRenderHint(self.render_hint)
89+
pen = QPen(self.brush_color, self.brush_size, Qt.PenStyle.SolidLine, self.brush_shape, Qt.PenJoinStyle.RoundJoin)
90+
painter.setPen(pen)
91+
painter.setBrush(QBrush(self.brush_color)) # Set the fill color if you want
92+
painter.drawRect(self.shape_start.x(), self.shape_start.y(),
93+
current_point.x() - self.shape_start.x(), current_point.y() - self.shape_start.y())
94+
self.update()
95+
96+
def draw_smooth_line(self, current_point):
97+
self.draw(current_point)
98+
99+
def draw_blender(self, current_point):
100+
painter = QPainter(self.image)
101+
painter.setRenderHint(self.render_hint)
102+
103+
brush_size_pixels = self.brush_size
104+
for color in self.blender_palette:
105+
pen = QPen(color, self.brush_size, Qt.PenStyle.SolidLine, self.brush_shape, Qt.PenJoinStyle.RoundJoin)
106+
pen.setColor(QColor(color.red(), color.green(), color.blue(), 50))
107+
painter.setPen(pen)
108+
painter.drawEllipse(int(cursor_point.x() - brush_half_size), int(cursor_point.y() - brush_half_size),
109+
brush_size_pixels, brush_size_pixels)
110+
111+
self.last_point = current_point
112+
self.update()
113+
114+
def draw_text(self, current_point):
115+
painter = QPainter(self.image)
116+
painter.setRenderHint(self.render_hint)
117+
painter.setFont(self.font)
118+
painter.setPen(self.brush_color)
119+
painter.drawText(current_point.toPoint(), self.text_input)
120+
self.last_point = current_point
121+
self.update()
122+
123+
def draw(self, cursor_point):
124+
draw_brushes(self, cursor_point)
125+
126+
def set_brush_color(self, color):
127+
self.brush_color = color
128+
129+
def set_brush_size(self, size):
130+
self.brush_size = size
131+
132+
def set_brush_shape(self, shape):
133+
if shape == "round":
134+
self.brush_shape = Qt.PenCapStyle.RoundCap
135+
elif shape == "square":
136+
self.brush_shape = Qt.PenCapStyle.SquareCap
137+
138+
def set_tool(self, tool):
139+
self.tool = tool
140+
self.set_cursor_for_tool()
141+
142+
def set_custom_brush(self, brush_path):
143+
self.custom_brush = QPixmap(brush_path)
144+
145+
def clear_canvas(self):
146+
self.save_undo_state()
147+
self.image.fill(Qt.GlobalColor.white)
148+
self.update()
149+
150+
def save_canvas(self, path):
151+
self.image.save(path, "PNG")
152+
153+
def save_undo_state(self):
154+
if self.ghost_opacity != 0 and len(self.canvas_history) > 0:
155+
# Add basic ghosting effect
156+
ghost_image = self.canvas_history[-1].copy()
157+
ghost_painter = QPainter(ghost_image)
158+
ghost_painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
159+
ghost_painter.setOpacity(self.ghost_opacity)
160+
ghost_painter.drawPixmap(self.rect(), self.image, self.image.rect())
161+
ghost_painter.end()
162+
self.image = ghost_image
163+
164+
self.canvas_history.append(self.image.copy())
165+
self.undo_stack.append(self.image.copy())
166+
if len(self.undo_stack) > 10: # Limit undo stack to 10
167+
self.undo_stack.pop(0)
168+
169+
def undo(self):
170+
if self.undo_stack:
171+
self.image = self.undo_stack.pop()
172+
self.update()
173+
174+
def set_cursor_for_tool(self):
175+
if self.tool == "text":
176+
self.setCursor(Qt.CursorShape.IBeamCursor)
177+
else:
178+
self.setCursor(Qt.CursorShape.ArrowCursor)
179+
180+
def set_canvas_size(self, width, height):
181+
self.setFixedSize(width, height)
182+
self.image = QPixmap(width, height)
183+
self.image.fill(Qt.GlobalColor.white)
184+
self.update()

color_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# color_utils.py
2+
from PyQt6.QtGui import QPen, QColor
3+
from PyQt6.QtCore import Qt
4+
def create_pen(color, brush_size, brush_shape):
5+
"""Creates a QPen object with the passed parameters. Returns a QPen object"""
6+
if isinstance(color, QColor):
7+
return QPen(color, brush_size, Qt.PenStyle.SolidLine, brush_shape, Qt.PenJoinStyle.RoundJoin)
8+
else:
9+
return QPen(QColor(Qt.GlobalColor.black), brush_size, Qt.PenStyle.SolidLine, brush_shape, Qt.PenJoinStyle.RoundJoin)

data_io.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# data_io.py
2+
import os
3+
from PyQt6.QtWidgets import QMessageBox, QFileDialog
4+
def save_image_and_label(canvas, text, file_path):
5+
if file_path:
6+
# Generate the image name and description name
7+
base_name = os.path.splitext(file_path)[0]
8+
9+
image_path = f"{base_name}.png"
10+
description_path = f"{base_name}.txt"
11+
12+
# Save image and label
13+
canvas.save_canvas(image_path)
14+
15+
with open(description_path, "w") as f:
16+
f.write(text)
17+
18+
QMessageBox.information(None, "Success!",
19+
f"Image and label saved to {os.path.basename(os.path.dirname(file_path))}\\{os.path.basename(base_name)}.[png or txt]")

0 commit comments

Comments
 (0)