· 11 months ago · Oct 23, 2024, 04:20 AM
1==++"Graffiti.cpp" File SourceCode::++==
2#include <vector>
3#include <windows.h>
4#include <fstream>
5#include <string>
6#include <commdlg.h>
7#include <codecvt>
8#include <algorithm>
9
10using namespace std;
11
12struct Brushstroke;
13struct Eraserstroke;
14HWND hWnd;
15HINSTANCE hInst;
16bool isDirty = false;
17bool IsPointInClientRect(HWND hwnd, int x, int y) {
18 RECT rect;
19 GetClientRect(hwnd, &rect);
20 return (x >= 0 && x < rect.right&& y >= 0 && y < rect.bottom);
21}
22
23struct Brushstroke {
24 int x;
25 int y;
26 int size;
27 COLORREF color;
28 void serialize(ofstream& outFile) const {
29 outFile << "B " << x << " " << y << " " << size << " "
30 << (int)GetRValue(color) << " "
31 << (int)GetGValue(color) << " "
32 << (int)GetBValue(color) << endl;
33 }
34};
35
36struct Eraserstroke {
37 int x;
38 int y;
39 int size;
40 COLORREF color;
41 void serialize(ofstream& outFile) const {
42 outFile << "E " << x << " " << y << " " << size << endl;
43 }
44};
45
46std::vector<Brushstroke> storedBrushstrokes;
47std::vector<Eraserstroke> storedEraserstrokes;
48
49void RedrawStrokes();
50void SaveDrawing(const string& filename);
51void LoadDrawing(const string& filename);
52
53POINT previousPoint;
54HDC hdc, memDC;
55HBITMAP memBitmap;
56
57bool isPaintbrushSelected = true;
58bool isDrawing = false;
59bool isErasing = false;
60bool isClearing = false;
61bool isEraserSelected = false;
62int brushSize = 10;
63
64void DrawBrush(HDC hdc, int x, int y) {
65 if (!IsPointInClientRect(hWnd, x, y)) return;
66 HGDIOBJ originalBrush = SelectObject(hdc, CreateSolidBrush(RGB(255, 0, 0)));
67 HGDIOBJ originalPen = SelectObject(hdc, GetStockObject(NULL_PEN));
68 Ellipse(hdc, x - brushSize, y - brushSize, x + brushSize, y + brushSize);
69 DeleteObject(SelectObject(hdc, originalBrush));
70 DeleteObject(SelectObject(hdc, originalPen));
71}
72
73void Erase(HDC hdc, int x, int y, int eraserSize) {
74 if (!IsPointInClientRect(hWnd, x, y)) return;
75 HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
76 SelectObject(hdc, GetStockObject(NULL_PEN));
77 SelectObject(hdc, hBrush);
78 Ellipse(hdc, x - eraserSize, y - eraserSize, x + eraserSize, y + eraserSize);
79 DeleteObject(hBrush);
80
81 // Remove brushstrokes that intersect with the eraser
82 storedBrushstrokes.erase(
83 std::remove_if(storedBrushstrokes.begin(), storedBrushstrokes.end(),
84 [x, y, eraserSize](const Brushstroke& stroke) {
85 int dx = stroke.x - x;
86 int dy = stroke.y - y;
87 return (dx * dx + dy * dy) <= (eraserSize + stroke.size) * (eraserSize + stroke.size);
88 }),
89 storedBrushstrokes.end()
90 );
91 isDirty = true; // Add this line at the end of the function
92}
93
94// Update the ClearDrawing function
95void ClearDrawing(HWND hwnd) {
96 RECT rect;
97 GetClientRect(hwnd, &rect);
98 HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
99
100 // Clear both hdc AND memDC
101 HDC hdc = GetDC(hwnd);
102 FillRect(hdc, &rect, hBrush);
103 FillRect(memDC, &rect, hBrush);
104 ReleaseDC(hwnd, hdc);
105
106 DeleteObject(hBrush);
107 storedBrushstrokes.clear();
108
109 InvalidateRect(hwnd, NULL, FALSE);
110 UpdateWindow(hwnd);
111 isDirty = true; // Add this line at the end of the function
112}
113
114void DrawSmoothBrush(HDC hdc, int x, int y) {
115 if (isDrawing && (isPaintbrushSelected || isEraserSelected)) {
116 int numPoints = 3;
117 POINT currentPoint = { x, y };
118 for (int i = 1; i <= numPoints; i++) {
119 float t = (float)i / (float)numPoints;
120 int smoothX = (int)(previousPoint.x + t * (currentPoint.x - previousPoint.x));
121 int smoothY = (int)(previousPoint.y + t * (currentPoint.y - previousPoint.y));
122 if (isPaintbrushSelected) {
123 DrawBrush(hdc, smoothX, smoothY);
124 Brushstroke newBrushstroke;
125 newBrushstroke.x = smoothX;
126 newBrushstroke.y = smoothY;
127 newBrushstroke.size = brushSize;
128 newBrushstroke.color = RGB(255, 0, 0);
129 storedBrushstrokes.push_back(newBrushstroke);
130 }
131 else if (isEraserSelected) {
132 Erase(hdc, smoothX, smoothY, brushSize);
133 Eraserstroke newEraserstroke;
134 newEraserstroke.x = smoothX;
135 newEraserstroke.y = smoothY;
136 newEraserstroke.size = brushSize;
137 newEraserstroke.color = RGB(255, 255, 255);
138 storedEraserstrokes.push_back(newEraserstroke);
139 }
140 }
141 previousPoint = currentPoint;
142 isDirty = true; // Add this line at the end of the function
143 }
144}
145
146void RedrawStrokes() {
147 // Clear the entire drawing area
148 RECT rect;
149 GetClientRect(hWnd, &rect);
150 HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
151 FillRect(memDC, &rect, hBrush);
152 DeleteObject(hBrush);
153
154 // Redraw all brushstrokes
155 for (const auto& brushstroke : storedBrushstrokes) {
156 HGDIOBJ originalBrush = SelectObject(memDC, CreateSolidBrush(brushstroke.color));
157 HGDIOBJ originalPen = SelectObject(memDC, GetStockObject(NULL_PEN));
158 Ellipse(memDC, brushstroke.x - brushstroke.size, brushstroke.y - brushstroke.size,
159 brushstroke.x + brushstroke.size, brushstroke.y + brushstroke.size);
160 DeleteObject(SelectObject(memDC, originalBrush));
161 DeleteObject(SelectObject(memDC, originalPen));
162 }
163
164 // Update the window
165 InvalidateRect(hWnd, NULL, FALSE);
166 UpdateWindow(hWnd);
167}
168
169void SaveDrawing(const string& filename) {
170 ofstream outFile(filename);
171 if (outFile.is_open()) {
172 outFile << storedBrushstrokes.size() << endl;
173 for (const auto& brushstroke : storedBrushstrokes) {
174 brushstroke.serialize(outFile);
175 }
176 outFile.close();
177 isDirty = false; // Add this line at the end of the function
178 }
179}
180
181void LoadDrawing(const string& filename) {
182 ifstream inFile(filename);
183 bool fileIsEmpty = true;
184
185 if (inFile.is_open()) {
186 int numBrushstrokes;
187 inFile >> numBrushstrokes;
188
189 if (!inFile.fail()) {
190 fileIsEmpty = false;
191 storedBrushstrokes.clear();
192 storedBrushstrokes.reserve(numBrushstrokes);
193 for (int i = 0; i < numBrushstrokes; ++i) {
194 char type;
195 Brushstroke brushstroke;
196 int r, g, b;
197 inFile >> type >> brushstroke.x >> brushstroke.y >> brushstroke.size >> r >> g >> b;
198 brushstroke.color = RGB(r, g, b);
199 storedBrushstrokes.push_back(brushstroke);
200 }
201 }
202 inFile.close();
203 }
204
205 // Always clear the background to white first
206 RECT rect;
207 GetClientRect(hWnd, &rect);
208 HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
209 FillRect(memDC, &rect, hBrush);
210 DeleteObject(hBrush);
211
212 if (!fileIsEmpty) {
213 RedrawStrokes();
214 }
215 else {
216 InvalidateRect(hWnd, NULL, FALSE);
217 UpdateWindow(hWnd);
218 }
219
220 isDirty = false; // Reset the dirty flag after loading
221}
222
223void SaveDrawingDialog(HWND hwnd) {
224 OPENFILENAME ofn;
225 wchar_t szFileName[MAX_PATH] = L"";
226
227 ZeroMemory(&ofn, sizeof(ofn));
228 ofn.lStructSize = sizeof(ofn);
229 ofn.hwndOwner = hwnd;
230 ofn.lpstrFilter = L"Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
231 ofn.lpstrFile = szFileName;
232 ofn.nMaxFile = MAX_PATH;
233 ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT;
234 ofn.lpstrDefExt = L"txt";
235
236 if (GetSaveFileName(&ofn)) {
237 std::wstring_convert < std::codecvt_utf8 < wchar_t>> converter;
238 std::string filenameStr = converter.to_bytes(szFileName);
239 SaveDrawing(filenameStr);
240 MessageBox(hwnd, L"Save Complete", L"Doodle App", MB_OK | MB_ICONINFORMATION);
241 }
242}
243
244void LoadDrawingDialog(HWND hwnd) {
245 OPENFILENAME ofn;
246 wchar_t szFileName[MAX_PATH] = L"";
247
248 ZeroMemory(&ofn, sizeof(ofn));
249 ofn.lStructSize = sizeof(ofn);
250 ofn.hwndOwner = hwnd;
251 ofn.lpstrFilter = L"Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
252 ofn.lpstrFile = szFileName;
253 ofn.nMaxFile = MAX_PATH;
254 ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST;
255
256 if (GetOpenFileName(&ofn)) {
257 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
258 std::string filenameStr = converter.to_bytes(szFileName);
259 LoadDrawing(filenameStr);
260 }
261}
262
263void SanitizeAndSaveDrawing(const string& filename) {
264 ofstream outFile(filename);
265 if (outFile.is_open()) {
266 RECT rect;
267 GetClientRect(hWnd, &rect);
268
269 vector<Brushstroke> validBrushstrokes;
270 for (const auto& brushstroke : storedBrushstrokes) {
271 if (IsPointInClientRect(hWnd, brushstroke.x, brushstroke.y)) {
272 validBrushstrokes.push_back(brushstroke);
273 }
274 }
275
276 outFile << validBrushstrokes.size() << endl;
277 for (const auto& brushstroke : validBrushstrokes) {
278 brushstroke.serialize(outFile);
279 }
280 outFile.close();
281 }
282}
283LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
284 switch (uMsg) {
285 case WM_CREATE:
286 {
287 hWnd = hwnd;
288 RECT rect;
289 GetClientRect(hwnd, &rect);
290 HDC hdc = GetDC(hwnd);
291 memDC = CreateCompatibleDC(hdc);
292 memBitmap = CreateCompatibleBitmap(hdc, rect.right - rect.left, rect.bottom - rect.top);
293 SelectObject(memDC, memBitmap);
294 ReleaseDC(hwnd, hdc);
295 HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
296 FillRect(memDC, &rect, hBrush);
297 DeleteObject(hBrush);
298 isPaintbrushSelected = true;
299 LoadDrawing("painting.txt");
300 }
301 break;
302 case WM_KEYDOWN:
303 if (wParam == VK_ESCAPE) {
304 if (isDirty) {
305 int result = MessageBox(hwnd, L"Do you want to save your work before exiting?", L"Save Changes", MB_YESNOCANCEL | MB_ICONQUESTION);
306 if (result == IDYES) {
307 // Implement save functionality here
308 SanitizeAndSaveDrawing("painting.txt");
309 // After saving, set g_isModified to false
310 isDirty = false;
311 PostQuitMessage(0);
312 }
313 else if (result == IDNO) {
314 PostQuitMessage(0);
315 }
316 // If IDCANCEL, do nothing and return to the application
317 }
318 else {
319 PostQuitMessage(0);
320 }
321 return 0;
322 }
323 //break;
324 if (wParam == VK_F1) {
325 MessageBox(hwnd, L"PaintCanvas 1.7 Programmed in C++ Win32 API (494 lines of code) by Entisoft Software\nCopyright (c) 2024 Evans Thorpemorton", L"Information", MB_OK | MB_ICONINFORMATION);
326 return 0;
327 }
328 if (wParam == VK_ADD) {
329 brushSize += 5;
330 }
331 else if (wParam == VK_SUBTRACT) {
332 if (brushSize > 5) {
333 brushSize -= 5;
334 }
335 }
336 else if (wParam == 0x43) { // 'C' key
337 if (!(GetKeyState(VK_CONTROL) & 0x8000)) {
338 ClearDrawing(hwnd);
339 }
340 }
341 else if (wParam == 0x50) {
342 isPaintbrushSelected = true;
343 isEraserSelected = false;
344 }
345 else if (wParam == 0x45) {
346 isEraserSelected = true;
347 isPaintbrushSelected = false;
348 }
349 else if (wParam == 'S' && (GetKeyState(VK_CONTROL) & 0x8000)) {
350 SaveDrawingDialog(hwnd);
351 }
352 else if (wParam == 'O' && (GetKeyState(VK_CONTROL) & 0x8000)) {
353 LoadDrawingDialog(hwnd);
354 }
355 break;
356 case WM_LBUTTONDOWN:
357 if (isPaintbrushSelected || isEraserSelected) {
358 previousPoint.x = LOWORD(lParam);
359 previousPoint.y = HIWORD(lParam);
360 }
361 isDrawing = true;
362 break;
363 case WM_LBUTTONUP:
364 isDrawing = false;
365 break;
366 case WM_MOUSEMOVE:
367 if (isDrawing && isPaintbrushSelected) {
368 int x = LOWORD(lParam);
369 int y = HIWORD(lParam);
370 DrawSmoothBrush(memDC, x, y);
371 InvalidateRect(hwnd, NULL, FALSE);
372 if (IsPointInClientRect(hWnd, x, y)) {
373 Brushstroke newBrushstroke;
374 newBrushstroke.x = x;
375 newBrushstroke.y = y;
376 newBrushstroke.size = brushSize;
377 newBrushstroke.color = RGB(255, 0, 0);
378 storedBrushstrokes.push_back(newBrushstroke);
379 }
380 }
381 else if (isDrawing && isEraserSelected) {
382 int x = LOWORD(lParam);
383 int y = HIWORD(lParam);
384 Erase(memDC, x, y, brushSize);
385 InvalidateRect(hwnd, NULL, FALSE);
386 }
387 break;
388 case WM_SIZE:
389 {
390 static bool isMinimized = false;
391 if (wParam == SIZE_MINIMIZED)
392 {
393 isMinimized = true;
394 }
395 else if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)
396 {
397 if (isMinimized)
398 {
399 isMinimized = false;
400 }
401 InvalidateRect(hwnd, NULL, TRUE);
402 }
403 RECT rect;
404 GetClientRect(hwnd, &rect);
405 if (memDC)
406 {
407 DeleteDC(memDC);
408 DeleteObject(memBitmap);
409 }
410 HDC hdc = GetDC(hwnd);
411 memDC = CreateCompatibleDC(hdc);
412 memBitmap = CreateCompatibleBitmap(hdc, rect.right - rect.left, rect.bottom - rect.top);
413 SelectObject(memDC, memBitmap);
414 ReleaseDC(hwnd, hdc);
415 HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
416 FillRect(memDC, &rect, hBrush);
417 DeleteObject(hBrush);
418 RedrawStrokes();
419 }
420 break;
421 case WM_PAINT:
422 {
423 PAINTSTRUCT ps;
424 HDC hdc = BeginPaint(hwnd, &ps);
425 BitBlt(hdc, 0, 0, ps.rcPaint.right, ps.rcPaint.bottom, memDC, 0, 0, SRCCOPY);
426 EndPaint(hwnd, &ps);
427 }
428 break;
429 case WM_SETCURSOR:
430 if (LOWORD(lParam) == HTCLIENT) {
431 SetCursor(LoadCursor(NULL, IDC_ARROW));
432 return TRUE;
433 }
434 break;
435 case WM_CLOSE:
436 // Sanitize and save before closing
437 if (isDirty) {
438 int result = MessageBox(hwnd, L"Do you want to save your changes?", L"Doodle App", MB_YESNOCANCEL | MB_ICONQUESTION);
439 if (result == IDYES) {
440 SanitizeAndSaveDrawing("painting.txt");
441 }
442 else if (result == IDCANCEL) {
443 return 0; // Don't close the window
444 }
445 }
446 DestroyWindow(hwnd);
447 break;
448 case WM_DESTROY:
449 DeleteDC(memDC);
450 DeleteObject(memBitmap);
451 PostQuitMessage(0);
452 break;
453 default:
454 return DefWindowProc(hwnd, uMsg, wParam, lParam);
455 }
456}
457
458int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
459 const wchar_t CLASS_NAME[] = L"PaintCanvasWindowClass";
460 WNDCLASS wc = { };
461
462 wc.lpfnWndProc = WindowProc;
463 wc.hInstance = hInstance;
464 wc.lpszClassName = CLASS_NAME;
465
466 RegisterClass(&wc);
467 HWND hwnd = CreateWindowEx(
468 0, // Optional window styles
469 CLASS_NAME, // Window class
470 L"Paint Canvas (P=Brush E=Eraser C=Clear +-=BrushSize Ctrl+S=Save Ctrl+O=Load)", // Window text
471 WS_OVERLAPPEDWINDOW | WS_MAXIMIZE, // Window style
472
473 // Size and position
474 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
475
476 NULL, // Parent window
477 NULL, // Menu
478 hInstance, // Instance handle
479 NULL // Additional application data
480 );
481
482 if (hwnd == NULL) {
483 return 0;
484 }
485
486 ShowWindow(hwnd, SW_SHOWMAXIMIZED);
487 // Run the message loop
488 MSG msg = { };
489 while (GetMessage(&msg, NULL, 0, 0)) {
490 TranslateMessage(&msg);
491 DispatchMessage(&msg);
492 }
493
494 return 0;
495}