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 ()
0 commit comments