Open-Source-Lab commited on
Commit
aec7315
·
verified ·
1 Parent(s): 83861ae

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +970 -0
index.html ADDED
@@ -0,0 +1,970 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html>
2
+ <style>
3
+ body {
4
+ display: flex;
5
+ flex-direction: column;
6
+ align-items: center;
7
+ justify-content: center;
8
+ height: 100vh;
9
+ margin: 0;
10
+ background-color: #f0f0f0;
11
+ font-family: Arial, sans-serif;
12
+ }
13
+
14
+ .container {
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: 15px;
18
+ max-width: 800px;
19
+ width: 100%;
20
+ }
21
+
22
+ .canvas-container {
23
+ position: relative;
24
+ width: 100%;
25
+ }
26
+
27
+ canvas {
28
+ border: 1px solid #000;
29
+ cursor: crosshair;
30
+ background-color: white;
31
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
32
+ width: 100%;
33
+ height: auto;
34
+ }
35
+
36
+ .toolbar {
37
+ display: flex;
38
+ flex-wrap: wrap;
39
+ gap: 10px;
40
+ background-color: #ffffff;
41
+ padding: 12px;
42
+ border-radius: 5px;
43
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
44
+ }
45
+
46
+ .tool-group {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 8px;
50
+ padding: 6px;
51
+ border-radius: 4px;
52
+ background-color: #f7f7f7;
53
+ flex-wrap: wrap;
54
+ }
55
+
56
+ button {
57
+ padding: 8px 12px;
58
+ font-size: 14px;
59
+ border: none;
60
+ border-radius: 4px;
61
+ background-color: #4a90e2;
62
+ color: white;
63
+ cursor: pointer;
64
+ transition: all 0.2s;
65
+ }
66
+
67
+ button:hover {
68
+ background-color: #357abd;
69
+ transform: translateY(-1px);
70
+ }
71
+
72
+ button:active {
73
+ transform: translateY(0);
74
+ }
75
+
76
+ button.active {
77
+ background-color: #2c5990;
78
+ }
79
+
80
+ button.danger {
81
+ background-color: #e74c3c;
82
+ }
83
+
84
+ button.danger:hover {
85
+ background-color: #c0392b;
86
+ }
87
+
88
+ button.secondary {
89
+ background-color: #95a5a6;
90
+ }
91
+
92
+ button.secondary:hover {
93
+ background-color: #7f8c8d;
94
+ }
95
+
96
+ input[type="color"] {
97
+ width: 40px;
98
+ height: 40px;
99
+ padding: 0;
100
+ border: none;
101
+ border-radius: 4px;
102
+ cursor: pointer;
103
+ }
104
+
105
+ input[type="range"] {
106
+ width: 100px;
107
+ }
108
+
109
+ .color-preview {
110
+ width: 24px;
111
+ height: 24px;
112
+ border-radius: 50%;
113
+ border: 2px solid #ddd;
114
+ }
115
+
116
+ .tool-option {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 5px;
120
+ }
121
+
122
+ label {
123
+ font-size: 14px;
124
+ }
125
+
126
+ .size-display {
127
+ min-width: 30px;
128
+ text-align: center;
129
+ }
130
+
131
+ .layer-panel {
132
+ display: flex;
133
+ flex-direction: column;
134
+ gap: 8px;
135
+ padding: 12px;
136
+ background-color: #ffffff;
137
+ border-radius: 5px;
138
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
139
+ }
140
+
141
+ .layer-list {
142
+ display: flex;
143
+ flex-direction: column;
144
+ gap: 5px;
145
+ }
146
+
147
+ .layer-item {
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: space-between;
151
+ padding: 6px;
152
+ background-color: #f7f7f7;
153
+ border-radius: 4px;
154
+ }
155
+
156
+ .layer-item.active {
157
+ background-color: #d3e8ff;
158
+ }
159
+
160
+ .layer-buttons {
161
+ display: flex;
162
+ gap: 5px;
163
+ }
164
+
165
+ .status-bar {
166
+ display: flex;
167
+ justify-content: space-between;
168
+ width: 100%;
169
+ padding: 6px 12px;
170
+ background-color: #ffffff;
171
+ border-radius: 5px;
172
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
173
+ font-size: 12px;
174
+ color: #555;
175
+ }
176
+
177
+ #colorPalette {
178
+ display: flex;
179
+ gap: 5px;
180
+ flex-wrap: wrap;
181
+ }
182
+
183
+ .palette-color {
184
+ width: 24px;
185
+ height: 24px;
186
+ border-radius: 4px;
187
+ cursor: pointer;
188
+ border: 1px solid #ddd;
189
+ }
190
+
191
+ .palette-color:hover {
192
+ transform: scale(1.1);
193
+ }
194
+
195
+ .brush-preview {
196
+ width: 40px;
197
+ height: 40px;
198
+ border-radius: 4px;
199
+ background-color: #f7f7f7;
200
+ position: relative;
201
+ }
202
+
203
+ .brush-preview-dot {
204
+ position: absolute;
205
+ top: 50%;
206
+ left: 50%;
207
+ transform: translate(-50%, -50%);
208
+ border-radius: 50%;
209
+ background-color: #000;
210
+ }
211
+
212
+ .modal {
213
+ display: none;
214
+ position: fixed;
215
+ top: 0;
216
+ left: 0;
217
+ width: 100%;
218
+ height: 100%;
219
+ background-color: rgba(0, 0, 0, 0.5);
220
+ z-index: 1000;
221
+ justify-content: center;
222
+ align-items: center;
223
+ }
224
+
225
+ .modal-content {
226
+ background-color: white;
227
+ padding: 20px;
228
+ border-radius: 5px;
229
+ max-width: 400px;
230
+ width: 100%;
231
+ }
232
+
233
+ .modal-title {
234
+ margin-top: 0;
235
+ }
236
+
237
+ .modal-footer {
238
+ display: flex;
239
+ justify-content: flex-end;
240
+ gap: 10px;
241
+ margin-top: 20px;
242
+ }
243
+
244
+ .image-preview {
245
+ max-width: 100%;
246
+ margin-top: 10px;
247
+ border: 1px solid #ddd;
248
+ border-radius: 4px;
249
+ }
250
+
251
+ .loading {
252
+ display: flex;
253
+ align-items: center;
254
+ justify-content: center;
255
+ padding: 20px;
256
+ }
257
+
258
+ .loading:after {
259
+ content: " ";
260
+ display: block;
261
+ width: 24px;
262
+ height: 24px;
263
+ border-radius: 50%;
264
+ border: 6px solid #5D5CDE;
265
+ border-color: #5D5CDE transparent #5D5CDE transparent;
266
+ animation: loading 1.2s linear infinite;
267
+ }
268
+
269
+ @keyframes loading {
270
+ 0% {
271
+ transform: rotate(0deg);
272
+ }
273
+ 100% {
274
+ transform: rotate(360deg);
275
+ }
276
+ }
277
+
278
+ .transform-modal {
279
+ max-width: 90%;
280
+ max-height: 90vh;
281
+ overflow-y: auto;
282
+ }
283
+
284
+ .transform-preview {
285
+ text-align: center;
286
+ margin-top: 15px;
287
+ }
288
+
289
+ .transform-preview img {
290
+ max-width: 100%;
291
+ max-height: 60vh;
292
+ border-radius: 4px;
293
+ border: 1px solid #ddd;
294
+ }
295
+
296
+ @media (prefers-color-scheme: dark) {
297
+ body {
298
+ background-color: #181818;
299
+ color: #e0e0e0;
300
+ }
301
+
302
+ .toolbar, .status-bar, .modal-content, .layer-panel {
303
+ background-color: #292929;
304
+ color: #e0e0e0;
305
+ }
306
+
307
+ .tool-group, .layer-item {
308
+ background-color: #383838;
309
+ }
310
+
311
+ .layer-item.active {
312
+ background-color: #3a4d64;
313
+ }
314
+
315
+ canvas {
316
+ border-color: #444;
317
+ }
318
+
319
+ .palette-color {
320
+ border-color: #444;
321
+ }
322
+ }
323
+ </style>
324
+ </head>
325
+ <body>
326
+ <div class="container">
327
+ <div class="toolbar">
328
+ <div class="tool-group">
329
+ <button id="pencilTool" class="active" title="Pencil (P)">Pencil</button>
330
+ <button id="brushTool" title="Brush (B)">Brush</button>
331
+ <button id="eraserTool" title="Eraser (E)">Eraser</button>
332
+ <button id="fillTool" title="Fill (F)">Fill</button>
333
+ <button id="lineTool" title="Line (L)">Line</button>
334
+ <button id="rectangleTool" title="Rectangle (R)">Rectangle</button>
335
+ <button id="circleTool" title="Circle (C)">Circle</button>
336
+ </div>
337
+ <div class="tool-group">
338
+ <div class="tool-option">
339
+ <input type="color" id="colorPicker" value="#000000">
340
+ <div id="currentColor" class="color-preview" style="background-color: #000000;"></div>
341
+ </div>
342
+ <div class="tool-option">
343
+ <label for="brushSize">Size:</label>
344
+ <input type="range" id="brushSize" min="1" max="50" value="5">
345
+ <span id="sizeDisplay" class="size-display">5px</span>
346
+ </div>
347
+ <div class="tool-option">
348
+ <label for="opacityRange">Opacity:</label>
349
+ <input type="range" id="opacityRange" min="1" max="100" value="100">
350
+ <span id="opacityDisplay" class="size-display">100%</span>
351
+ </div>
352
+ </div>
353
+ </div>
354
+
355
+ <div class="canvas-container">
356
+ <canvas id="paintCanvas" width="800" height="600"></canvas>
357
+ </div>
358
+
359
+ <div class="toolbar">
360
+ <div class="tool-group">
361
+ <button id="undoButton" title="Undo (Ctrl+Z)">Undo</button>
362
+ <button id="redoButton" title="Redo (Ctrl+Y)">Redo</button>
363
+ <button id="clearButton" class="danger" title="Clear">Clear</button>
364
+ </div>
365
+ <div class="tool-group">
366
+ <button id="saveButton" title="Save (Ctrl+S)">Save</button>
367
+ <button id="loadButton" title="Load">Load</button>
368
+ <button id="exportButton" title="Export as PNG">Export</button>
369
+ <button id="downloadButton" title="Download Image">Download Image</button>
370
+ <button id="transformButton" title="Transform with Image-Photo">Transform Image</button>
371
+ </div>
372
+ <div class="tool-group">
373
+ <div id="colorPalette">
374
+ <div class="palette-color" style="background-color: #000000;"></div>
375
+ <div class="palette-color" style="background-color: #ffffff;"></div>
376
+ <div class="palette-color" style="background-color: #ff0000;"></div>
377
+ <div class="palette-color" style="background-color: #00ff00;"></div>
378
+ <div class="palette-color" style="background-color: #0000ff;"></div>
379
+ <div class="palette-color" style="background-color: #ffff00;"></div>
380
+ </div>
381
+ </div>
382
+ </div>
383
+
384
+ <div class="status-bar">
385
+ <span id="positionDisplay">Position: 0, 0</span>
386
+ <span id="toolInfo">Pencil Tool | Size: 5px | Color: #000000</span>
387
+ </div>
388
+ </div>
389
+
390
+ <div id="saveModal" class="modal">
391
+ <div class="modal-content">
392
+ <h3 class="modal-title">Save Drawing</h3>
393
+ <div>
394
+ <label for="saveFilename">Filename:</label>
395
+ <input type="text" id="saveFilename" value="my-drawing" style="width: 100%; margin-top: 5px; padding: 5px;">
396
+ </div>
397
+ <div class="modal-footer">
398
+ <button id="cancelSave" class="secondary">Cancel</button>
399
+ <button id="confirmSave">Save</button>
400
+ </div>
401
+ </div>
402
+ </div>
403
+
404
+ <div id="transformModal" class="modal">
405
+ <div class="modal-content transform-modal">
406
+ <h3 class="modal-title">Transform Image</h3>
407
+ <div>
408
+ <label for="transformPrompt">Enter a prompt for @Image-Photo:</label>
409
+ <input type="text" id="transformPrompt" placeholder="Describe how to transform the image..." style="width: 100%; margin-top: 5px; padding: 5px; font-size: 16px;">
410
+ </div>
411
+ <div id="transformLoading" class="loading" style="display: none;"></div>
412
+ <div id="transformPreview" class="transform-preview"></div>
413
+ <div class="modal-footer">
414
+ <button id="cancelTransform" class="secondary">Cancel</button>
415
+ <button id="confirmTransform">Send to Image-Photo</button>
416
+ <button id="downloadTransformed" style="display: none;">Download Transformed</button>
417
+ </div>
418
+ </div>
419
+ </div>
420
+
421
+ <script>
422
+ // Check if dark mode is preferred
423
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
424
+ document.documentElement.classList.add('dark');
425
+ }
426
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
427
+ if (event.matches) {
428
+ document.documentElement.classList.add('dark');
429
+ } else {
430
+ document.documentElement.classList.remove('dark');
431
+ }
432
+ });
433
+
434
+ const canvas = document.getElementById('paintCanvas');
435
+ const ctx = canvas.getContext('2d');
436
+ const colorPicker = document.getElementById('colorPicker');
437
+ const currentColor = document.getElementById('currentColor');
438
+ const brushSize = document.getElementById('brushSize');
439
+ const sizeDisplay = document.getElementById('sizeDisplay');
440
+ const opacityRange = document.getElementById('opacityRange');
441
+ const opacityDisplay = document.getElementById('opacityDisplay');
442
+ const positionDisplay = document.getElementById('positionDisplay');
443
+ const toolInfo = document.getElementById('toolInfo');
444
+ const pencilTool = document.getElementById('pencilTool');
445
+ const brushTool = document.getElementById('brushTool');
446
+ const eraserTool = document.getElementById('eraserTool');
447
+ const fillTool = document.getElementById('fillTool');
448
+ const lineTool = document.getElementById('lineTool');
449
+ const rectangleTool = document.getElementById('rectangleTool');
450
+ const circleTool = document.getElementById('circleTool');
451
+ const undoButton = document.getElementById('undoButton');
452
+ const redoButton = document.getElementById('redoButton');
453
+ const clearButton = document.getElementById('clearButton');
454
+ const saveButton = document.getElementById('saveButton');
455
+ const loadButton = document.getElementById('loadButton');
456
+ const exportButton = document.getElementById('exportButton');
457
+ const downloadButton = document.getElementById('downloadButton');
458
+ const transformButton = document.getElementById('transformButton');
459
+
460
+ const saveModal = document.getElementById('saveModal');
461
+ const saveFilename = document.getElementById('saveFilename');
462
+ const cancelSave = document.getElementById('cancelSave');
463
+ const confirmSave = document.getElementById('confirmSave');
464
+
465
+ const transformModal = document.getElementById('transformModal');
466
+ const transformPrompt = document.getElementById('transformPrompt');
467
+ const transformLoading = document.getElementById('transformLoading');
468
+ const transformPreview = document.getElementById('transformPreview');
469
+ const cancelTransform = document.getElementById('cancelTransform');
470
+ const confirmTransform = document.getElementById('confirmTransform');
471
+ const downloadTransformed = document.getElementById('downloadTransformed');
472
+
473
+ let isDrawing = false;
474
+ let lastX = 0;
475
+ let lastY = 0;
476
+ let currentTool = 'pencil';
477
+ let undoStack = [];
478
+ let redoStack = [];
479
+ let startX = 0;
480
+ let startY = 0;
481
+ let transformedImageUrl = null;
482
+
483
+ function initCanvas() {
484
+ ctx.fillStyle = 'white';
485
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
486
+ saveState();
487
+ }
488
+
489
+ function saveState() {
490
+ redoStack = [];
491
+ undoStack.push(canvas.toDataURL());
492
+ updateButtons();
493
+ }
494
+
495
+ function updateButtons() {
496
+ undoButton.disabled = undoStack.length <= 1;
497
+ redoButton.disabled = redoStack.length === 0;
498
+ }
499
+
500
+ function undo() {
501
+ if (undoStack.length <= 1) return;
502
+ redoStack.push(undoStack.pop());
503
+ const img = new Image();
504
+ img.src = undoStack[undoStack.length - 1];
505
+ img.onload = () => {
506
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
507
+ ctx.drawImage(img, 0, 0);
508
+ updateButtons();
509
+ };
510
+ }
511
+
512
+ function redo() {
513
+ if (redoStack.length === 0) return;
514
+ const img = new Image();
515
+ img.src = redoStack.pop();
516
+ img.onload = () => {
517
+ undoStack.push(img.src);
518
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
519
+ ctx.drawImage(img, 0, 0);
520
+ updateButtons();
521
+ };
522
+ }
523
+
524
+ function clearCanvas() {
525
+ saveState();
526
+ ctx.fillStyle = 'white';
527
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
528
+ }
529
+
530
+ function startDrawing(e) {
531
+ isDrawing = true;
532
+ const rect = canvas.getBoundingClientRect();
533
+ lastX = e.clientX - rect.left;
534
+ lastY = e.clientY - rect.top;
535
+ startX = lastX;
536
+ startY = lastY;
537
+
538
+ if (['line', 'rectangle', 'circle'].includes(currentTool)) {
539
+ saveState();
540
+ return;
541
+ }
542
+
543
+ ctx.beginPath();
544
+ if (currentTool === 'fill') {
545
+ floodFill(Math.floor(lastX), Math.floor(lastY), hexToRgb(colorPicker.value));
546
+ isDrawing = false;
547
+ saveState();
548
+ return;
549
+ }
550
+
551
+ setupContext();
552
+ if (['pencil', 'brush'].includes(currentTool)) {
553
+ ctx.arc(lastX, lastY, 0.5, 0, Math.PI * 2);
554
+ ctx.fillStyle = ctx.strokeStyle;
555
+ ctx.fill();
556
+ }
557
+ saveState();
558
+ }
559
+
560
+ function draw(e) {
561
+ if (!isDrawing) return;
562
+ const rect = canvas.getBoundingClientRect();
563
+ const currentX = e.clientX - rect.left;
564
+ const currentY = e.clientY - rect.top;
565
+ positionDisplay.textContent = `Position: ${Math.floor(currentX)}, ${Math.floor(currentY)}`;
566
+
567
+ if (currentTool === 'pencil' || currentTool === 'brush' || currentTool === 'eraser') {
568
+ ctx.beginPath();
569
+ ctx.moveTo(lastX, lastY);
570
+ ctx.lineTo(currentX, currentY);
571
+ ctx.stroke();
572
+ lastX = currentX;
573
+ lastY = currentY;
574
+ } else if (['line', 'rectangle', 'circle'].includes(currentTool)) {
575
+ const img = new Image();
576
+ img.src = undoStack[undoStack.length - 1];
577
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
578
+ ctx.drawImage(img, 0, 0);
579
+ setupContext();
580
+
581
+ if (currentTool === 'line') {
582
+ ctx.beginPath();
583
+ ctx.moveTo(startX, startY);
584
+ ctx.lineTo(currentX, currentY);
585
+ ctx.stroke();
586
+ } else if (currentTool === 'rectangle') {
587
+ const width = currentX - startX;
588
+ const height = currentY - startY;
589
+ ctx.strokeRect(startX, startY, width, height);
590
+ } else if (currentTool === 'circle') {
591
+ const radius = Math.sqrt(Math.pow(currentX - startX, 2) + Math.pow(currentY - startY, 2));
592
+ ctx.beginPath();
593
+ ctx.arc(startX, startY, radius, 0, Math.PI * 2);
594
+ ctx.stroke();
595
+ }
596
+ }
597
+ }
598
+
599
+ function endDrawing() {
600
+ if (!isDrawing) return;
601
+ isDrawing = false;
602
+ if (['line', 'rectangle', 'circle'].includes(currentTool)) {
603
+ saveState();
604
+ }
605
+ }
606
+
607
+ function setupContext() {
608
+ const size = parseInt(brushSize.value);
609
+ const opacity = parseInt(opacityRange.value) / 100;
610
+ ctx.lineWidth = size;
611
+ ctx.lineCap = 'round';
612
+ ctx.lineJoin = 'round';
613
+
614
+ if (currentTool === 'eraser') {
615
+ ctx.globalCompositeOperation = 'destination-out';
616
+ ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
617
+ } else {
618
+ ctx.globalCompositeOperation = 'source-over';
619
+ const color = colorPicker.value;
620
+ ctx.strokeStyle = `${color}${Math.round(opacity * 255).toString(16).padStart(2, '0')}`;
621
+ }
622
+ }
623
+
624
+ function floodFill(x, y, targetColor) {
625
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
626
+ const data = imageData.data;
627
+ const width = canvas.width;
628
+ const height = canvas.height;
629
+ const index = (y * width + x) * 4;
630
+ const clickedColor = { r: data[index], g: data[index + 1], b: data[index + 2], a: data[index + 3] };
631
+
632
+ if (colorMatch(clickedColor, targetColor)) return;
633
+
634
+ const stack = [{x, y}];
635
+ while (stack.length > 0) {
636
+ const pixel = stack.pop();
637
+ const px = pixel.x;
638
+ const py = pixel.y;
639
+
640
+ if (px < 0 || px >= width || py < 0 || py >= height) continue;
641
+
642
+ const currentIndex = (py * width + px) * 4;
643
+ const currentColor = {
644
+ r: data[currentIndex],
645
+ g: data[currentIndex + 1],
646
+ b: data[currentIndex + 2],
647
+ a: data[currentIndex + 3]
648
+ };
649
+
650
+ if (colorMatch(currentColor, clickedColor)) {
651
+ data[currentIndex] = targetColor.r;
652
+ data[currentIndex + 1] = targetColor.g;
653
+ data[currentIndex + 2] = targetColor.b;
654
+ data[currentIndex + 3] = 255;
655
+
656
+ stack.push({x: px + 1, y: py});
657
+ stack.push({x: px - 1, y: py});
658
+ stack.push({x: px, y: py + 1});
659
+ stack.push({x: px, y: py - 1});
660
+ }
661
+ }
662
+
663
+ ctx.putImageData(imageData, 0, 0);
664
+ }
665
+
666
+ function colorMatch(color1, color2, tolerance = 10) {
667
+ return Math.abs(color1.r - color2.r) <= tolerance &&
668
+ Math.abs(color1.g - color2.g) <= tolerance &&
669
+ Math.abs(color1.b - color2.b) <= tolerance;
670
+ }
671
+
672
+ function hexToRgb(hex) {
673
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
674
+ return result ? {
675
+ r: parseInt(result[1], 16),
676
+ g: parseInt(result[2], 16),
677
+ b: parseInt(result[3], 16)
678
+ } : {r: 0, g: 0, b: 0};
679
+ }
680
+
681
+ function saveDrawing() {
682
+ const filename = saveFilename.value.trim() || 'my-drawing';
683
+ const dataURL = canvas.toDataURL();
684
+ try {
685
+ localStorage.setItem(`painting-${filename}`, dataURL);
686
+ alert(`Drawing saved as "${filename}"`);
687
+ } catch (e) {
688
+ alert('Error saving drawing. Local storage might be full or disabled.');
689
+ }
690
+ saveModal.style.display = 'none';
691
+ }
692
+
693
+ function loadDrawing() {
694
+ const savedDrawings = [];
695
+ for (let i = 0; i < localStorage.length; i++) {
696
+ const key = localStorage.key(i);
697
+ if (key.startsWith('painting-')) {
698
+ savedDrawings.push(key.replace('painting-', ''));
699
+ }
700
+ }
701
+
702
+ if (savedDrawings.length === 0) {
703
+ alert('No saved drawings found.');
704
+ return;
705
+ }
706
+
707
+ const drawing = prompt(`Enter filename to load (available: ${savedDrawings.join(', ')})`);
708
+ if (!drawing) return;
709
+
710
+ const dataURL = localStorage.getItem(`painting-${drawing}`);
711
+ if (!dataURL) {
712
+ alert(`Drawing "${drawing}" not found.`);
713
+ return;
714
+ }
715
+
716
+ const img = new Image();
717
+ img.src = dataURL;
718
+ img.onload = () => {
719
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
720
+ ctx.drawImage(img, 0, 0);
721
+ saveState();
722
+ };
723
+ }
724
+
725
+ function exportDrawing() {
726
+ const link = document.createElement('a');
727
+ link.download = 'drawing.png';
728
+ link.href = canvas.toDataURL('image/png');
729
+ link.click();
730
+ }
731
+
732
+ // New function for downloading image
733
+ function downloadImage() {
734
+ const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
735
+ const link = document.createElement('a');
736
+ link.download = `drawing-${timestamp}.png`;
737
+ link.href = canvas.toDataURL('image/png');
738
+ link.click();
739
+ }
740
+
741
+ // New function to open transform modal
742
+ function openTransformModal() {
743
+ transformPrompt.value = '';
744
+ transformPreview.innerHTML = '';
745
+ downloadTransformed.style.display = 'none';
746
+ transformModal.style.display = 'flex';
747
+ }
748
+
749
+ // New function to transform image with Image-Photo
750
+ async function transformImage() {
751
+ const prompt = transformPrompt.value.trim();
752
+ if (!prompt) {
753
+ alert('Please enter a prompt for the transformation');
754
+ return;
755
+ }
756
+
757
+ // Show loading state
758
+ transformLoading.style.display = 'block';
759
+ confirmTransform.disabled = true;
760
+ transformPreview.innerHTML = '';
761
+ downloadTransformed.style.display = 'none';
762
+ transformedImageUrl = null;
763
+
764
+ try {
765
+ // Convert canvas to File
766
+ const dataUrl = canvas.toDataURL('image/png');
767
+ const res = await fetch(dataUrl);
768
+ const blob = await res.blob();
769
+ const file = new File([blob], 'drawing.png', { type: 'image/png' });
770
+
771
+ // Register handler for Image-Photo response
772
+ const handlerId = `image-transform-${Date.now()}`;
773
+ window.Poe.registerHandler(handlerId, (result) => {
774
+ const response = result.responses[0];
775
+
776
+ if (response.status === "error") {
777
+ transformLoading.style.display = 'none';
778
+ transformPreview.innerHTML = `<p style="color: red;">Error: ${response.statusText || 'Failed to transform image'}</p>`;
779
+ confirmTransform.disabled = false;
780
+ }
781
+ else if (response.status === "complete") {
782
+ transformLoading.style.display = 'none';
783
+ confirmTransform.disabled = false;
784
+
785
+ if (response.attachments?.length > 0) {
786
+ const imageAttachment = response.attachments[0];
787
+ transformedImageUrl = imageAttachment.url;
788
+
789
+ transformPreview.innerHTML = `
790
+ <img src="${imageAttachment.url}" alt="Transformed image">
791
+ <p>${response.content}</p>
792
+ `;
793
+
794
+ downloadTransformed.style.display = 'inline-block';
795
+ } else {
796
+ transformPreview.innerHTML = `<p>${response.content}</p>
797
+ <p style="color: #f59e0b;">No image was returned. Try a different prompt.</p>`;
798
+ }
799
+ }
800
+ });
801
+
802
+ // Send message to Image-Photo
803
+ await window.Poe.sendUserMessage(
804
+ "@Image-Photo " + prompt,
805
+ {
806
+ handler: handlerId,
807
+ stream: false,
808
+ openChat: false,
809
+ attachments: [file]
810
+ }
811
+ );
812
+ await window.Poe.sendUserMessage(
813
+ "@Free-Thinking-Server " + prompt,
814
+ {
815
+ handler: handlerId,
816
+ stream: false,
817
+ openChat: false,
818
+ attachments: [file]
819
+ }
820
+ );
821
+ } catch (err) {
822
+ transformLoading.style.display = 'none';
823
+ confirmTransform.disabled = false;
824
+ transformPreview.innerHTML = `<p style="color: red;">Error: ${err.message || 'Failed to send image'}</p>`;
825
+ console.error("Error:", err);
826
+ }
827
+ }
828
+
829
+ // Function to download transformed image
830
+ function downloadTransformedImage() {
831
+ if (!transformedImageUrl) return;
832
+
833
+ const link = document.createElement('a');
834
+ link.href = transformedImageUrl;
835
+ link.download = `transformed-${Date.now()}.png`;
836
+ link.click();
837
+ }
838
+
839
+ function setCurrentTool(tool) {
840
+ currentTool = tool;
841
+ document.querySelectorAll('.tool-group button').forEach(btn => {
842
+ btn.classList.remove('active');
843
+ });
844
+ document.getElementById(`${tool}Tool`).classList.add('active');
845
+ updateToolInfo();
846
+ }
847
+
848
+ function updateToolInfo() {
849
+ const size = brushSize.value;
850
+ const color = colorPicker.value;
851
+ const opacity = opacityRange.value;
852
+ let toolName;
853
+
854
+ switch (currentTool) {
855
+ case 'pencil': toolName = 'Pencil'; break;
856
+ case 'brush': toolName = 'Brush'; break;
857
+ case 'eraser': toolName = 'Eraser'; break;
858
+ case 'fill': toolName = 'Fill'; break;
859
+ case 'line': toolName = 'Line'; break;
860
+ case 'rectangle': toolName = 'Rectangle'; break;
861
+ case 'circle': toolName = 'Circle'; break;
862
+ default: toolName = 'Unknown';
863
+ }
864
+
865
+ toolInfo.textContent = `${toolName} Tool | Size: ${size}px | Color: ${color} | Opacity: ${opacity}%`;
866
+ }
867
+
868
+ function handleKeyDown(e) {
869
+ if (document.activeElement.tagName === 'INPUT') return;
870
+
871
+ if (e.key === 'z' && (e.ctrlKey || e.metaKey)) {
872
+ e.preventDefault();
873
+ undo();
874
+ } else if (e.key === 'y' && (e.ctrlKey || e.metaKey)) {
875
+ e.preventDefault();
876
+ redo();
877
+ } else if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
878
+ e.preventDefault();
879
+ saveModal.style.display = 'flex';
880
+ } else if (e.key === 'p' || e.key === 'b' || e.key === 'e' || e.key === 'f' || e.key === 'l' || e.key === 'r' || e.key === 'c') {
881
+ switch (e.key) {
882
+ case 'p': setCurrentTool('pencil'); break;
883
+ case 'b': setCurrentTool('brush'); break;
884
+ case 'e': setCurrentTool('eraser'); break;
885
+ case 'f': setCurrentTool('fill'); break;
886
+ case 'l': setCurrentTool('line'); break;
887
+ case 'r': setCurrentTool('rectangle'); break;
888
+ case 'c': setCurrentTool('circle'); break;
889
+ }
890
+ }
891
+ }
892
+
893
+ function trackMousePosition(e) {
894
+ const rect = canvas.getBoundingClientRect();
895
+ const x = e.clientX - rect.left;
896
+ const y = e.clientY - rect.top;
897
+ positionDisplay.textContent = `Position: ${Math.floor(x)}, ${Math.floor(y)}`;
898
+ }
899
+
900
+ function rgbToHex(rgb) {
901
+ const match = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
902
+ if (match) {
903
+ return "#" + ((1 << 24) + (parseInt(match[1]) << 16) + (parseInt(match[2]) << 8) + parseInt(match[3])).toString(16).slice(1);
904
+ }
905
+ return rgb;
906
+ }
907
+
908
+ // Event listeners
909
+ canvas.addEventListener('mousedown', startDrawing);
910
+ canvas.addEventListener('mousemove', draw);
911
+ canvas.addEventListener('mouseup', endDrawing);
912
+ canvas.addEventListener('mouseout', endDrawing);
913
+ canvas.addEventListener('mousemove', trackMousePosition);
914
+
915
+ pencilTool.addEventListener('click', () => setCurrentTool('pencil'));
916
+ brushTool.addEventListener('click', () => setCurrentTool('brush'));
917
+ eraserTool.addEventListener('click', () => setCurrentTool('eraser'));
918
+ fillTool.addEventListener('click', () => setCurrentTool('fill'));
919
+ lineTool.addEventListener('click', () => setCurrentTool('line'));
920
+ rectangleTool.addEventListener('click', () => setCurrentTool('rectangle'));
921
+ circleTool.addEventListener('click', () => setCurrentTool('circle'));
922
+
923
+ colorPicker.addEventListener('input', () => {
924
+ currentColor.style.backgroundColor = colorPicker.value;
925
+ updateToolInfo();
926
+ });
927
+
928
+ brushSize.addEventListener('input', () => {
929
+ sizeDisplay.textContent = `${brushSize.value}px`;
930
+ updateToolInfo();
931
+ });
932
+
933
+ opacityRange.addEventListener('input', () => {
934
+ opacityDisplay.textContent = `${opacityRange.value}%`;
935
+ updateToolInfo();
936
+ });
937
+
938
+ undoButton.addEventListener('click', undo);
939
+ redoButton.addEventListener('click', redo);
940
+ clearButton.addEventListener('click', clearCanvas);
941
+ saveButton.addEventListener('click', () => saveModal.style.display = 'flex');
942
+ loadButton.addEventListener('click', loadDrawing);
943
+ exportButton.addEventListener('click', exportDrawing);
944
+
945
+ // New button event listeners
946
+ downloadButton.addEventListener('click', downloadImage);
947
+ transformButton.addEventListener('click', openTransformModal);
948
+ cancelTransform.addEventListener('click', () => transformModal.style.display = 'none');
949
+ confirmTransform.addEventListener('click', transformImage);
950
+ downloadTransformed.addEventListener('click', downloadTransformedImage);
951
+
952
+ cancelSave.addEventListener('click', () => saveModal.style.display = 'none');
953
+ confirmSave.addEventListener('click', saveDrawing);
954
+
955
+ document.querySelectorAll('.palette-color').forEach(colorEl => {
956
+ colorEl.addEventListener('click', () => {
957
+ const color = colorEl.style.backgroundColor;
958
+ const hex = rgbToHex(color);
959
+ colorPicker.value = hex;
960
+ currentColor.style.backgroundColor = color;
961
+ updateToolInfo();
962
+ });
963
+ });
964
+
965
+ document.addEventListener('keydown', handleKeyDown);
966
+
967
+ initCanvas();
968
+ updateButtons();
969
+
970
+ </html>