📄 plaintext public

Untitled Paste

Guest 1h ago 9 views Text Paste
Raw
📄 plaintext
1
<!DOCTYPE html>
2
<html lang="th">
3
<head>
4
    <meta charset="UTF-8">
5
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
    <title>AI Image Detector & Editor (Canva Style)</title>
7
    <script src="https://cdn.tailwindcss.com"></script>
8
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
9
    <!-- นำเข้า Google Fonts ภาษาไทย -->
10
    <link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;700&family=Prompt:wght@300;400;500;700&display=swap" rel="stylesheet">
11
    <!-- นำเข้า Fabric.js สำหรับระบบ Layer และการจัดการ Object แบบ Canva -->
12
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
13
    <style>
14
        ::-webkit-scrollbar { width: 8px; }
15
        ::-webkit-scrollbar-track { background: #f1f1f1; }
16
        ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
17
        ::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
18
        
19
        /* จัดการ Container ของ Canvas ที่ถูกสร้างโดย Fabric.js */
20
        .canvas-container {
21
            margin: 0 auto;
22
            box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
23
        }
24
    </style>
25
</head>
26
<body class="bg-slate-50 text-slate-800 font-sans min-h-screen">
27
 
28
    <div class="max-w-7xl mx-auto px-4 py-8">
29
        
30
        <!-- Header -->
31
        <header class="text-center mb-10">
32
            <h1 class="text-3xl md:text-4xl font-bold text-indigo-700 mb-2">
33
                <i class="fa-solid fa-layer-group mr-2"></i> AI Image Detector & Editor
34
            </h1>
35
            <p class="text-slate-500">ตรวจสอบภาพ AI และระบบแก้ไขภาพแบบเลเยอร์ (Canva Style)</p>
36
        </header>
37
 
38
        <!-- Main Content Grid -->
39
        <div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
40
            
41
            <!-- Left Column: Controls -->
42
            <div class="lg:col-span-4 xl:col-span-3 space-y-6">
43
                
44
                <!-- Upload Section -->
45
                <div class="bg-white p-5 rounded-2xl shadow-sm border border-slate-200">
46
                    <h2 class="text-lg font-semibold mb-4 text-slate-700">1. ภาพหลัก (Background)</h2>
47
                    <label for="imageUpload" class="flex flex-col items-center justify-center w-full h-28 border-2 border-slate-300 border-dashed rounded-xl cursor-pointer bg-slate-50 hover:bg-slate-100 transition">
48
                        <div class="flex flex-col items-center justify-center pt-5 pb-6">
49
                            <i class="fa-solid fa-cloud-arrow-up text-2xl text-indigo-500 mb-2"></i>
50
                            <p class="text-sm text-slate-500"><span class="font-semibold">อัปโหลดภาพหลัก</span></p>
51
                        </div>
52
                        <input id="imageUpload" type="file" class="hidden" accept="image/*" />
53
                    </label>
54
                </div>
55
 
56
                <!-- Detection Status -->
57
                <div id="detectionPanel" class="bg-white p-5 rounded-2xl shadow-sm border border-slate-200 hidden">
58
                    <h2 class="text-lg font-semibold mb-4 text-slate-700">2. ผลการวิเคราะห์ภาพ</h2>
59
                    
60
                    <div id="detectionLoading" class="flex flex-col items-center py-4">
61
                        <i class="fa-solid fa-circle-notch fa-spin text-4xl text-indigo-500 mb-3"></i>
62
                        <p class="text-sm text-slate-600 font-medium" id="loadingText">กำลังตรวจสอบ Metadata...</p>
63
                        <div class="w-full bg-slate-200 rounded-full h-2.5 mt-4">
64
                            <div id="loadingBar" class="bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
65
                        </div>
66
                    </div>
67
 
68
                    <div id="detectionResult" class="hidden text-center">
69
                        <div id="resultIcon" class="text-5xl mb-3"></div>
70
                        <h3 id="resultTitle" class="text-lg font-bold mb-1"></h3>
71
                        <p id="resultDesc" class="text-xs text-slate-500 mb-4"></p>
72
                        
73
                        <div class="bg-slate-50 rounded-lg p-3 text-left text-xs text-slate-600 space-y-2 mb-4">
74
                            <div class="flex justify-between"><span>โอกาสเป็นภาพ AI:</span> <span id="scoreAi" class="font-semibold"></span></div>
75
                            <div class="flex justify-between"><span>Noise Pattern:</span> <span id="scoreNoise" class="font-semibold"></span></div>
76
                        </div>
77
                        
78
                        <button id="startEditBtn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-4 rounded-lg transition">
79
                            <i class="fa-solid fa-wand-magic-sparkles mr-2"></i>เปิดเครื่องมือแก้ไข (Layers)
80
                        </button>
81
                    </div>
82
                </div>
83
 
84
                <!-- Editor Tools (Layers, Filters, Draw) -->
85
                <div id="editorPanel" class="bg-white p-5 rounded-2xl shadow-sm border border-slate-200 hidden max-h-[800px] overflow-y-auto">
86
                    <h2 class="text-lg font-semibold mb-4 text-slate-700">3. เครื่องมือแก้ไข (Canva Style)</h2>
87
                    
88
                    <!-- Add Elements / Layers -->
89
                    <div class="mb-5">
90
                        <label class="block text-sm font-medium text-slate-700 mb-2"><i class="fa-solid fa-shapes mr-1"></i> เพิ่มองค์ประกอบ</label>
91
                        <div class="grid grid-cols-2 gap-2">
92
                            <button id="addTextBtn" class="bg-slate-100 hover:bg-slate-200 text-slate-700 py-2 rounded text-sm transition border border-slate-200">
93
                                <i class="fa-solid fa-font"></i> ข้อความ
94
                            </button>
95
                            <label class="bg-slate-100 hover:bg-slate-200 text-slate-700 py-2 rounded text-sm transition border border-slate-200 text-center cursor-pointer">
96
                                <i class="fa-regular fa-image"></i> เพิ่มรูปภาพ
97
                                <input type="file" id="addLayerImage" class="hidden" accept="image/*">
98
                            </label>
99
                            <button id="addRectBtn" class="bg-slate-100 hover:bg-slate-200 text-slate-700 py-2 rounded text-sm transition border border-slate-200">
100
                                <i class="fa-solid fa-square"></i> สี่เหลี่ยม
101
                            </button>
102
                            <button id="addCircleBtn" class="bg-slate-100 hover:bg-slate-200 text-slate-700 py-2 rounded text-sm transition border border-slate-200">
103
                                <i class="fa-solid fa-circle"></i> วงกลม
104
                            </button>
105
                        </div>
106
                        
107
                        <!-- เครื่องมือลบและแก้ไขข้อความบนภาพ -->
108
                        <div class="mt-3">
109
                            <button id="magicTextBtn" class="w-full bg-gradient-to-r from-purple-500 to-indigo-500 hover:from-purple-600 hover:to-indigo-600 text-white py-2 rounded-lg text-sm font-medium transition shadow-sm">
110
                                <i class="fa-solid fa-wand-magic-sparkles mr-1"></i> แก้ไขข้อความเดิมบนภาพ (Magic Edit)
111
                            </button>
112
                            <p class="text-[10px] text-slate-500 mt-1 text-center">คลิกปุ่ม แล้วลากคลุมข้อความเดิม เพื่อลบและพิมพ์ใหม่</p>
113
                        </div>
114
                    </div>
115
 
116
                    <!-- Manage Selected Object -->
117
                    <div id="objectControls" class="mb-5 p-3 bg-indigo-50 border border-indigo-100 rounded-lg opacity-50 pointer-events-none transition-opacity">
118
                        <div class="flex justify-between items-center mb-2">
119
                            <label class="block text-sm font-medium text-indigo-900">จัดการวัตถุที่เลือก</label>
120
                            <input type="color" id="objColorPicker" class="w-6 h-6 rounded cursor-pointer border-0 p-0 hidden" title="เปลี่ยนสีวัตถุ/ข้อความ">
121
                        </div>
122
                        <div class="flex space-x-2">
123
                            <button id="bringForwardBtn" class="flex-1 bg-white hover:bg-indigo-100 text-indigo-700 py-1.5 rounded text-xs transition border border-indigo-200" title="นำมาไว้ข้างหน้า">
124
                                <i class="fa-solid fa-layer-group"></i> ขึ้นบน
125
                            </button>
126
                            <button id="sendBackwardBtn" class="flex-1 bg-white hover:bg-indigo-100 text-indigo-700 py-1.5 rounded text-xs transition border border-indigo-200" title="ส่งไปข้างหลัง">
127
                                <i class="fa-solid fa-layer-group fa-flip-vertical"></i> ลงล่าง
128
                            </button>
129
                            <button id="deleteObjBtn" class="flex-1 bg-white hover:bg-red-100 text-red-600 py-1.5 rounded text-xs transition border border-red-200">
130
                                <i class="fa-solid fa-trash"></i> ลบ
131
                            </button>
132
                        </div>
133
                        
134
                        <!-- Text Specific Controls -->
135
                        <div id="textControls" class="hidden mt-3 pt-3 border-t border-indigo-200">
136
                            <label class="block text-xs font-medium text-indigo-900 mb-2"><i class="fa-solid fa-font"></i> รูปแบบข้อความ</label>
137
                            <div class="grid grid-cols-2 gap-2">
138
                                <div>
139
                                    <label class="text-[10px] text-indigo-700">แบบอักษร</label>
140
                                    <select id="fontFamilySelect" class="w-full text-xs p-1.5 border border-indigo-200 rounded bg-white">
141
                                        <option value="sans-serif">Sans-serif</option>
142
                                        <option value="serif">Serif</option>
143
                                        <option value="Arial">Arial</option>
144
                                        <option value="Kanit">Kanit (ไทย)</option>
145
                                        <option value="Prompt">Prompt (ไทย)</option>
146
                                    </select>
147
                                </div>
148
                                <div>
149
                                    <label class="text-[10px] text-indigo-700">ขนาด</label>
150
                                    <input type="number" id="fontSizeInput" min="10" max="200" value="40" class="w-full text-xs p-1.5 border border-indigo-200 rounded bg-white">
151
                                </div>
152
                            </div>
153
                        </div>
154
                    </div>
155
 
156
                    <hr class="border-slate-100 my-4">
157
 
158
                    <!-- Filters (Applies to selected image or background) -->
159
                    <div class="space-y-3 mb-5">
160
                        <label class="block text-sm font-medium text-slate-700 mb-1"><i class="fa-solid fa-sliders mr-1"></i> ปรับแต่งสี/แสง (ภาพที่เลือก)</label>
161
                        <div>
162
                            <label class="flex justify-between text-xs text-slate-500 mb-1">
163
                                <span>ความสว่าง</span> <span id="valBright">0</span>
164
                            </label>
165
                            <input type="range" id="filterBright" min="-100" max="100" value="0" class="w-full h-1.5 bg-slate-200 rounded-lg appearance-none cursor-pointer">
166
                        </div>
167
                        <div>
168
                            <label class="flex justify-between text-xs text-slate-500 mb-1">
169
                                <span>ความเปรียบต่าง</span> <span id="valContrast">0</span>
170
                            </label>
171
                            <input type="range" id="filterContrast" min="-100" max="100" value="0" class="w-full h-1.5 bg-slate-200 rounded-lg appearance-none cursor-pointer">
172
                        </div>
173
                        <div>
174
                            <label class="flex justify-between text-xs text-slate-500 mb-1">
175
                                <span>ความอิ่มตัวสี</span> <span id="valSaturate">0</span>
176
                            </label>
177
                            <input type="range" id="filterSaturate" min="-100" max="100" value="0" class="w-full h-1.5 bg-slate-200 rounded-lg appearance-none cursor-pointer">
178
                        </div>
179
                        <button id="resetFiltersBtn" class="text-xs text-indigo-600 hover:text-indigo-800">รีเซ็ตฟิลเตอร์</button>
180
                    </div>
181
 
182
                    <hr class="border-slate-100 my-4">
183
 
184
                    <!-- Drawing -->
185
                    <div class="mb-5">
186
                        <label class="block text-sm font-medium text-slate-700 mb-2"><i class="fa-solid fa-pen mr-1"></i> วาดเขียนอิสระ</label>
187
                        <div class="flex items-center space-x-3 mb-3">
188
                            <input type="color" id="brushColor" value="#ff0000" class="w-8 h-8 rounded cursor-pointer border-0 p-0">
189
                            <input type="range" id="brushSize" min="1" max="50" value="5" class="flex-1 h-1.5 bg-slate-200 rounded-lg appearance-none cursor-pointer">
190
                        </div>
191
                        <button id="toggleDrawBtn" class="w-full bg-slate-100 hover:bg-slate-200 text-slate-700 py-2 rounded-lg text-sm font-medium transition border border-slate-300">
192
                            <i class="fa-solid fa-pen-nib mr-1"></i> เปิดโหมดวาดเขียน
193
                        </button>
194
                    </div>
195
 
196
                    <!-- Actions -->
197
                    <div class="flex flex-col space-y-2 mt-6">
198
                        <button id="downloadBtn" class="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-medium py-2.5 px-4 rounded-lg transition flex justify-center items-center shadow-sm">
199
                            <i class="fa-solid fa-download mr-2"></i> ดาวน์โหลดผลงาน
200
                        </button>
201
                    </div>
202
                </div>
203
            </div>
204
 
205
            <!-- Right Column: Canvas Display (Fabric.js) -->
206
            <div class="lg:col-span-8 xl:col-span-9 flex flex-col">
207
                <!-- Toolbar hint -->
208
                <div class="bg-indigo-50 text-indigo-800 text-sm py-2 px-4 rounded-t-xl border border-indigo-100 flex justify-between">
209
                    <span><i class="fa-solid fa-circle-info mr-1"></i> ดับเบิ้ลคลิกที่ข้อความเพื่อแก้ไข / ลากเพื่อย้ายตำแหน่ง</span>
210
                    <span id="canvasDimInfo" class="font-mono text-xs text-indigo-500"></span>
211
                </div>
212
                
213
                <div id="canvasWrapper" class="bg-slate-200 p-2 rounded-b-xl shadow-inner border border-slate-300 min-h-[500px] flex items-center justify-center overflow-auto flex-1 relative bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+CjxyZWN0IHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2ZmZmZmZiIvPgo8cmVjdCB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIGZpbGw9IiNmM2Y0ZjYiLz4KPHJlY3QgeD0iMTAiIHk9IjEwIiB3aWR0aD0iMTAiIGhlaWdodD0iMTAiIGZpbGw9IiNmM2Y0ZjYiLz4KPC9zdmc+')]">
214
                    
215
                    <!-- Placeholder text before upload -->
216
                    <div id="canvasPlaceholder" class="text-slate-400 text-center pointer-events-none absolute z-10">
217
                        <i class="fa-regular fa-images text-6xl mb-3 opacity-50"></i>
218
                        <p>อัปโหลดภาพหลักเพื่อเริ่มสร้างชิ้นงาน</p>
219
                    </div>
220
 
221
                    <!-- Fabric Canvas Canvas Element -->
222
                    <canvas id="mainCanvas"></canvas>
223
                </div>
224
            </div>
225
            
226
        </div>
227
    </div>
228
 
229
    <script>
230
        // --- Fabric.js Initialization ---
231
        const canvas = new fabric.Canvas('mainCanvas', {
232
            isDrawingMode: false,
233
            preserveObjectStacking: true // Keep objects in order when selected
234
        });
235
        
236
        let baseImage = null; // The background image uploaded first
237
 
238
        // --- DOM Elements ---
239
        const imageUpload = document.getElementById('imageUpload');
240
        const detectionPanel = document.getElementById('detectionPanel');
241
        const editorPanel = document.getElementById('editorPanel');
242
        const canvasPlaceholder = document.getElementById('canvasPlaceholder');
243
        const canvasDimInfo = document.getElementById('canvasDimInfo');
244
 
245
        // Layers & Objects controls
246
        const addTextBtn = document.getElementById('addTextBtn');
247
        const addLayerImage = document.getElementById('addLayerImage');
248
        const addRectBtn = document.getElementById('addRectBtn');
249
        const addCircleBtn = document.getElementById('addCircleBtn');
250
        const magicTextBtn = document.getElementById('magicTextBtn');
251
        const objectControls = document.getElementById('objectControls');
252
        const objColorPicker = document.getElementById('objColorPicker');
253
        const bringForwardBtn = document.getElementById('bringForwardBtn');
254
        const sendBackwardBtn = document.getElementById('sendBackwardBtn');
255
        const deleteObjBtn = document.getElementById('deleteObjBtn');
256
        const textControls = document.getElementById('textControls');
257
        const fontFamilySelect = document.getElementById('fontFamilySelect');
258
        const fontSizeInput = document.getElementById('fontSizeInput');
259
 
260
        // Filter Controls
261
        const filterBright = document.getElementById('filterBright');
262
        const filterContrast = document.getElementById('filterContrast');
263
        const filterSaturate = document.getElementById('filterSaturate');
264
        const resetFiltersBtn = document.getElementById('resetFiltersBtn');
265
 
266
        // Drawing Controls
267
        const toggleDrawBtn = document.getElementById('toggleDrawBtn');
268
        const brushColor = document.getElementById('brushColor');
269
        const brushSize = document.getElementById('brushSize');
270
 
271
        // Action
272
        const downloadBtn = document.getElementById('downloadBtn');
273
 
274
        // Magic Text Variables
275
        let isMagicTextMode = false;
276
        let magicRect = null;
277
        let origX, origY;
278
 
279
        // --- 1. Load Main Image ---
280
        imageUpload.addEventListener('change', function(e) {
281
            const file = e.target.files[0];
282
            if (!file) return;
283
 
284
            const reader = new FileReader();
285
            reader.onload = function(event) {
286
                // Remove placeholder
287
                canvasPlaceholder.classList.add('hidden');
288
                
289
                fabric.Image.fromURL(event.target.result, function(img) {
290
                    // Set canvas dimensions
291
                    // Limit max width to prevent extremely large canvases in browser, scale proportionally
292
                    const MAX_WIDTH = 1000; 
293
                    let scale = 1;
294
                    if (img.width > MAX_WIDTH) {
295
                        scale = MAX_WIDTH / img.width;
296
                    }
297
                    
298
                    const finalWidth = img.width * scale;
299
                    const finalHeight = img.height * scale;
300
 
301
                    canvas.setWidth(finalWidth);
302
                    canvas.setHeight(finalHeight);
303
                    
304
                    // Scale image
305
                    img.scale(scale);
306
                    
307
                    // Set as background image so it acts as the base canvas
308
                    canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
309
                    baseImage = img; // Store reference for filters
310
 
311
                    canvasDimInfo.innerText = `${Math.round(finalWidth)} x ${Math.round(finalHeight)} px`;
312
 
313
                    // Trigger fake detection
314
                    startDetectionSimulation();
315
                });
316
            }
317
            reader.readAsDataURL(file);
318
        });
319
 
320
        // --- 2. Element / Layer Additions (Canva style) ---
321
        addTextBtn.addEventListener('click', () => {
322
            const text = new fabric.IText('ข้อความใหม่', {
323
                left: canvas.width / 2,
324
                top: canvas.height / 2,
325
                originX: 'center',
326
                originY: 'center',
327
                fontFamily: 'sans-serif',
328
                fill: '#333333',
329
                fontSize: 40,
330
                fontWeight: 'bold',
331
                transparentCorners: false,
332
                cornerColor: 'blue',
333
                cornerSize: 10
334
            });
335
            canvas.add(text);
336
            canvas.setActiveObject(text);
337
        });
338
 
339
        addRectBtn.addEventListener('click', () => {
340
            const rect = new fabric.Rect({
341
                left: canvas.width / 2,
342
                top: canvas.height / 2,
343
                originX: 'center',
344
                originY: 'center',
345
                fill: '#4f46e5',
346
                width: 100,
347
                height: 100,
348
                rx: 10,
349
                ry: 10,
350
                transparentCorners: false,
351
                cornerColor: 'blue',
352
                cornerSize: 10
353
            });
354
            canvas.add(rect);
355
            canvas.setActiveObject(rect);
356
        });
357
 
358
        addCircleBtn.addEventListener('click', () => {
359
            const circle = new fabric.Circle({
360
                left: canvas.width / 2,
361
                top: canvas.height / 2,
362
                originX: 'center',
363
                originY: 'center',
364
                fill: '#10b981',
365
                radius: 50,
366
                transparentCorners: false,
367
                cornerColor: 'blue',
368
                cornerSize: 10
369
            });
370
            canvas.add(circle);
371
            canvas.setActiveObject(circle);
372
        });
373
 
374
        addLayerImage.addEventListener('change', function(e) {
375
            const file = e.target.files[0];
376
            if (!file) return;
377
            const reader = new FileReader();
378
            reader.onload = function(event) {
379
                fabric.Image.fromURL(event.target.result, function(img) {
380
                    // scale down if it's too big for the canvas
381
                    if(img.width > canvas.width / 2) {
382
                        img.scaleToWidth(canvas.width / 2);
383
                    }
384
                    img.set({
385
                        left: canvas.width / 2,
386
                        top: canvas.height / 2,
387
                        originX: 'center',
388
                        originY: 'center',
389
                        transparentCorners: false,
390
                        cornerColor: 'blue',
391
                        cornerSize: 10
392
                    });
393
                    canvas.add(img);
394
                    canvas.setActiveObject(img);
395
                });
396
            }
397
            reader.readAsDataURL(file);
398
            this.value = ''; // Reset input
399
        });
400
 
401
        // --- 3. Manage Object State (Enable/Disable tools) ---
402
        function updateObjectControls() {
403
            const activeObj = canvas.getActiveObject();
404
            if (activeObj) {
405
                // Enable layer tools
406
                objectControls.classList.remove('opacity-50', 'pointer-events-none');
407
                
408
                // Show/hide text controls
409
                if (activeObj.type === 'i-text' || activeObj.type === 'text') {
410
                    textControls.classList.remove('hidden');
411
                    fontFamilySelect.value = activeObj.fontFamily || 'sans-serif';
412
                    fontSizeInput.value = activeObj.fontSize || 40;
413
                } else {
414
                    textControls.classList.add('hidden');
415
                }
416
 
417
                // Update filter sliders if an image is selected
418
                if (activeObj.type === 'image') {
419
                    updateFilterSlidersFromObject(activeObj);
420
                    objColorPicker.classList.add('hidden');
421
                } else {
422
                    // Show color picker for text and shapes
423
                    objColorPicker.classList.remove('hidden');
424
                    if (activeObj.fill && typeof activeObj.fill === 'string') {
425
                        if (activeObj.fill.startsWith('#')) {
426
                            objColorPicker.value = activeObj.fill.substring(0, 7);
427
                        } else if (activeObj.fill.startsWith('rgb')) {
428
                           const rgb = activeObj.fill.match(/\d+/g);
429
                           if(rgb && rgb.length >= 3) {
430
                               const hex = "#" + (1 << 24 | rgb[0] << 16 | rgb[1] << 8 | rgb[2]).toString(16).slice(1);
431
                               objColorPicker.value = hex;
432
                           }
433
                        }
434
                    }
435
                }
436
            } else {
437
                // Disable layer tools
438
                objectControls.classList.add('opacity-50', 'pointer-events-none');
439
                objColorPicker.classList.add('hidden');
440
                textControls.classList.add('hidden');
441
                
442
                // If nothing selected, show filters of the background image
443
                if (baseImage) {
444
                    updateFilterSlidersFromObject(baseImage);
445
                }
446
            }
447
        }
448
 
449
        canvas.on('selection:created', updateObjectControls);
450
        canvas.on('selection:updated', updateObjectControls);
451
        canvas.on('selection:cleared', updateObjectControls);
452
 
453
        objColorPicker.addEventListener('input', (e) => {
454
            const activeObj = canvas.getActiveObject();
455
            if (activeObj) {
456
                activeObj.set('fill', e.target.value);
457
                canvas.renderAll();
458
            }
459
        });
460
 
461
        deleteObjBtn.addEventListener('click', () => {
462
            const activeObjects = canvas.getActiveObjects();
463
            if (activeObjects.length) {
464
                activeObjects.forEach(obj => canvas.remove(obj));
465
                canvas.discardActiveObject();
466
            }
467
        });
468
 
469
        fontFamilySelect.addEventListener('change', (e) => {
470
            const activeObj = canvas.getActiveObject();
471
            if (activeObj && (activeObj.type === 'i-text' || activeObj.type === 'text')) {
472
                activeObj.set('fontFamily', e.target.value);
473
                canvas.renderAll();
474
            }
475
        });
476
 
477
        fontSizeInput.addEventListener('input', (e) => {
478
            const activeObj = canvas.getActiveObject();
479
            if (activeObj && (activeObj.type === 'i-text' || activeObj.type === 'text')) {
480
                const size = parseInt(e.target.value, 10);
481
                if (!isNaN(size) && size > 0) {
482
                    activeObj.set('fontSize', size);
483
                    canvas.renderAll();
484
                }
485
            }
486
        });
487
 
488
        bringForwardBtn.addEventListener('click', () => {
489
            const activeObj = canvas.getActiveObject();
490
            if (activeObj) canvas.bringForward(activeObj);
491
        });
492
 
493
        sendBackwardBtn.addEventListener('click', () => {
494
            const activeObj = canvas.getActiveObject();
495
            if (activeObj) canvas.sendBackwards(activeObj);
496
        });
497
 
498
        // --- 3.5 Magic Text Editor (Masking Old Text & Replace) ---
499
        magicTextBtn.addEventListener('click', () => {
500
            isMagicTextMode = true;
501
            canvas.isDrawingMode = false;
502
            canvas.defaultCursor = 'crosshair';
503
            canvas.discardActiveObject();
504
            canvas.renderAll();
505
            
506
            // Highlight button to show it's active
507
            magicTextBtn.classList.replace('from-purple-500', 'from-pink-500');
508
            magicTextBtn.classList.replace('to-indigo-500', 'to-rose-500');
509
            magicTextBtn.innerHTML = '<i class="fa-solid fa-check mr-1"></i> ลากคลุมข้อความบนภาพเลย...';
510
        });
511
 
512
        canvas.on('mouse:down', function(o) {
513
            if (!isMagicTextMode) return;
514
            var pointer = canvas.getPointer(o.e);
515
            origX = pointer.x;
516
            origY = pointer.y;
517
            
518
            magicRect = new fabric.Rect({
519
                left: origX,
520
                top: origY,
521
                width: 0,
522
                height: 0,
523
                fill: 'rgba(236, 72, 153, 0.3)', // Pinkish selection box
524
                stroke: '#ec4899',
525
                strokeWidth: 2,
526
                selectable: false
527
            });
528
            canvas.add(magicRect);
529
        });
530
 
531
        canvas.on('mouse:move', function(o) {
532
            if (!isMagicTextMode || !magicRect) return;
533
            var pointer = canvas.getPointer(o.e);
534
            
535
            if (origX > pointer.x) {
536
                magicRect.set({ left: Math.abs(pointer.x) });
537
            }
538
            if (origY > pointer.y) {
539
                magicRect.set({ top: Math.abs(pointer.y) });
540
            }
541
            
542
            magicRect.set({ width: Math.abs(origX - pointer.x) });
543
            magicRect.set({ height: Math.abs(origY - pointer.y) });
544
            canvas.renderAll();
545
        });
546
 
547
        canvas.on('mouse:up', function(o) {
548
            if (!isMagicTextMode || !magicRect) return;
549
 
550
            // Ensure selection is big enough
551
            if (magicRect.width < 10 || magicRect.height < 10) {
552
                canvas.remove(magicRect);
553
                resetMagicMode();
554
                return;
555
            }
556
 
557
            // 1. Hide selection box temporarily to read actual background pixels
558
            magicRect.set('visible', false);
559
            canvas.renderAll();
560
            
561
            // 2. Read background color from the Top-Left of the selection
562
            const ctx = canvas.getContext('2d');
563
            const multiplier = canvas.getRetinaScaling();
564
            const pixel = ctx.getImageData(magicRect.left * multiplier, magicRect.top * multiplier, 1, 1).data;
565
            const bgColor = `rgb(${pixel[0]}, ${pixel[1]}, ${pixel[2]})`;
566
            
567
            // 3. Make the box visible again, filled with background color to "Erase" old text
568
            magicRect.set({
569
                visible: true,
570
                fill: bgColor,
571
                strokeWidth: 0,
572
                selectable: true // allow user to resize mask later if needed
573
            });
574
 
575
            // 4. Create new editable text over the erased area
576
            const text = new fabric.IText('ข้อความใหม่', {
577
                left: magicRect.left + (magicRect.width * 0.05),
578
                top: magicRect.top + (magicRect.height * 0.1),
579
                fontFamily: 'sans-serif',
580
                fill: '#333333', // Default text color
581
                fontSize: Math.max(16, magicRect.height * 0.6),
582
                fontWeight: 'bold',
583
                transparentCorners: false,
584
                cornerColor: 'blue',
585
                cornerSize: 10
586
            });
587
            canvas.add(text);
588
            
589
            // 5. Select the new text and open edit mode automatically
590
            canvas.setActiveObject(text);
591
            text.enterEditing();
592
            text.selectAll();
593
 
594
            resetMagicMode();
595
        });
596
 
597
        function resetMagicMode() {
598
            isMagicTextMode = false;
599
            magicRect = null;
600
            canvas.defaultCursor = 'default';
601
            magicTextBtn.classList.replace('from-pink-500', 'from-purple-500');
602
            magicTextBtn.classList.replace('to-rose-500', 'to-indigo-500');
603
            magicTextBtn.innerHTML = '<i class="fa-solid fa-wand-magic-sparkles mr-1"></i> แก้ไขข้อความเดิมบนภาพ (Magic Edit)';
604
            canvas.renderAll();
605
        }
606
 
607
        // --- 4. Filters (Fabric Image Filters) ---
608
        // Range slider value is -100 to 100. Fabric expects -1 to 1.
609
        function applyImageFilters() {
610
            // Apply to active image object, or background baseImage if none selected
611
            const target = canvas.getActiveObject() && canvas.getActiveObject().type === 'image' 
612
                           ? canvas.getActiveObject() 
613
                           : baseImage;
614
            
615
            if (!target) return;
616
 
617
            document.getElementById('valBright').innerText = filterBright.value;
618
            document.getElementById('valContrast').innerText = filterContrast.value;
619
            document.getElementById('valSaturate').innerText = filterSaturate.value;
620
 
621
            // Fabric 5 filter API
622
            target.filters[0] = new fabric.Image.filters.Brightness({ brightness: parseFloat(filterBright.value) / 100 });
623
            target.filters[1] = new fabric.Image.filters.Contrast({ contrast: parseFloat(filterContrast.value) / 100 });
624
            target.filters[2] = new fabric.Image.filters.Saturation({ saturation: parseFloat(filterSaturate.value) / 100 });
625
            
626
            target.applyFilters();
627
            canvas.renderAll();
628
        }
629
 
630
        function updateFilterSlidersFromObject(obj) {
631
            if (!obj.filters || obj.filters.length === 0) {
632
                filterBright.value = 0; filterContrast.value = 0; filterSaturate.value = 0;
633
            } else {
634
                filterBright.value = (obj.filters[0] && obj.filters[0].brightness) ? Math.round(obj.filters[0].brightness * 100) : 0;
635
                filterContrast.value = (obj.filters[1] && obj.filters[1].contrast) ? Math.round(obj.filters[1].contrast * 100) : 0;
636
                filterSaturate.value = (obj.filters[2] && obj.filters[2].saturation) ? Math.round(obj.filters[2].saturation * 100) : 0;
637
            }
638
            document.getElementById('valBright').innerText = filterBright.value;
639
            document.getElementById('valContrast').innerText = filterContrast.value;
640
            document.getElementById('valSaturate').innerText = filterSaturate.value;
641
        }
642
 
643
        filterBright.addEventListener('input', applyImageFilters);
644
        filterContrast.addEventListener('input', applyImageFilters);
645
        filterSaturate.addEventListener('input', applyImageFilters);
646
 
647
        resetFiltersBtn.addEventListener('click', () => {
648
            filterBright.value = 0; filterContrast.value = 0; filterSaturate.value = 0;
649
            applyImageFilters();
650
        });
651
 
652
        // --- 5. Free Drawing ---
653
        // Setup brush
654
        canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
655
        canvas.freeDrawingBrush.color = brushColor.value;
656
        canvas.freeDrawingBrush.width = parseInt(brushSize.value, 10) || 5;
657
 
658
        brushColor.addEventListener('change', (e) => {
659
            canvas.freeDrawingBrush.color = e.target.value;
660
        });
661
 
662
        brushSize.addEventListener('input', (e) => {
663
            canvas.freeDrawingBrush.width = parseInt(e.target.value, 10);
664
        });
665
 
666
        toggleDrawBtn.addEventListener('click', () => {
667
            canvas.isDrawingMode = !canvas.isDrawingMode;
668
            if (canvas.isDrawingMode) {
669
                toggleDrawBtn.classList.replace('bg-slate-100', 'bg-indigo-600');
670
                toggleDrawBtn.classList.replace('text-slate-700', 'text-white');
671
                canvas.discardActiveObject();
672
                canvas.requestRenderAll();
673
            } else {
674
                toggleDrawBtn.classList.replace('bg-indigo-600', 'bg-slate-100');
675
                toggleDrawBtn.classList.replace('text-white', 'text-slate-700');
676
            }
677
        });
678
 
679
        // --- 6. Download ---
680
        downloadBtn.addEventListener('click', () => {
681
            // Unselect everything before export so bounding boxes aren't visible
682
            canvas.discardActiveObject();
683
            canvas.renderAll();
684
            
685
            const link = document.createElement('a');
686
            link.download = 'ai-canvas-edited.png';
687
            link.href = canvas.toDataURL({ format: 'png', quality: 1 });
688
            link.click();
689
        });
690
 
691
        // --- 7. Detection Simulation ---
692
        const startEditBtn = document.getElementById('startEditBtn');
693
        startEditBtn.addEventListener('click', () => {
694
            detectionPanel.classList.add('hidden');
695
            editorPanel.classList.remove('hidden');
696
        });
697
 
698
        function startDetectionSimulation() {
699
            editorPanel.classList.add('hidden');
700
            detectionPanel.classList.remove('hidden');
701
            document.getElementById('detectionLoading').classList.remove('hidden');
702
            document.getElementById('detectionResult').classList.add('hidden');
703
            
704
            const loadingBar = document.getElementById('loadingBar');
705
            const loadingText = document.getElementById('loadingText');
706
            loadingBar.style.width = '0%';
707
            
708
            setTimeout(() => { loadingBar.style.width = '30%'; loadingText.innerText = 'ตรวจสอบ Noise Pattern...'; }, 800);
709
            setTimeout(() => { loadingBar.style.width = '60%'; loadingText.innerText = 'วิเคราะห์ขอบเขตเลเยอร์ (Edge Analysis)...'; }, 1600);
710
            setTimeout(() => { loadingBar.style.width = '90%'; loadingText.innerText = 'ประมวลผลผลลัพธ์...'; }, 2400);
711
            
712
            setTimeout(() => {
713
                showDetectionResult();
714
            }, 3000);
715
        }
716
 
717
        function showDetectionResult() {
718
            document.getElementById('detectionLoading').classList.add('hidden');
719
            document.getElementById('detectionResult').classList.remove('hidden');
720
            
721
            const isLikelyAI = Math.random() > 0.4;
722
            const resultIcon = document.getElementById('resultIcon');
723
            const resultTitle = document.getElementById('resultTitle');
724
            const resultDesc = document.getElementById('resultDesc');
725
            const scoreAi = document.getElementById('scoreAi');
726
            const scoreNoise = document.getElementById('scoreNoise');
727
 
728
            if (isLikelyAI) {
729
                resultIcon.innerHTML = '<i class="fa-solid fa-robot text-orange-500"></i>';
730
                resultTitle.innerText = 'โอกาสสูงที่เป็นภาพสร้างจาก AI';
731
                resultTitle.className = 'text-lg font-bold mb-1 text-orange-600';
732
                resultDesc.innerText = 'พบร่องรอยการกระจายพิกเซลผิดปกติ (Generative Artifacts)';
733
                scoreAi.innerText = (85 + Math.floor(Math.random() * 14)) + '%';
734
                scoreAi.className = 'font-semibold text-orange-600';
735
                scoreNoise.innerText = 'สังเคราะห์ (Synthetic)';
736
            } else {
737
                resultIcon.innerHTML = '<i class="fa-solid fa-camera text-emerald-500"></i>';
738
                resultTitle.innerText = 'น่าจะเป็นภาพถ่ายจริง';
739
                resultTitle.className = 'text-lg font-bold mb-1 text-emerald-600';
740
                resultDesc.innerText = 'ไม่พบร่องรอยการสังเคราะห์ของ AI ในระดับที่ผิดสังเกต';
741
                scoreAi.innerText = (2 + Math.floor(Math.random() * 20)) + '%';
742
                scoreAi.className = 'font-semibold text-emerald-600';
743
                scoreNoise.innerText = 'ปกติ (Natural)';
744
            }
745
        }
746
    </script>
747
</body>
748
</html>
\n \n \n \n \n \n \n\n\n\n
\n \n \n
\n

\n AI Image Detector & Editor\n

\n

ตรวจสอบภาพ AI และระบบแก้ไขภาพแบบเลเยอร์ (Canva Style)

\n
\n\n \n
\n \n \n
\n \n \n
\n

1. ภาพหลัก (Background)

\n \n
\n\n \n
\n

2. ผลการวิเคราะห์ภาพ

\n \n
\n \n

กำลังตรวจสอบ Metadata...

\n
\n
\n
\n
\n\n
\n
\n

\n

\n \n
\n
โอกาสเป็นภาพ AI:
\n
Noise Pattern:
\n
\n \n \n
\n
\n\n \n \n
\n\n \n
\n \n
\n ดับเบิ้ลคลิกที่ข้อความเพื่อแก้ไข / ลากเพื่อย้ายตำแหน่ง\n \n
\n \n
\n \n \n
\n \n

อัปโหลดภาพหลักเพื่อเริ่มสร้างชิ้นงาน

\n
\n\n \n \n
\n
\n \n
\n
\n\n \n\n"; const PASTE_SHORT_ID = 'bX5nLd'; const PASTE_TITLE = "Untitled Paste";