-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
269 lines (216 loc) · 8.83 KB
/
script.js
File metadata and controls
269 lines (216 loc) · 8.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
/**
* ChoiPixel - Main Application Logic
*/
document.addEventListener('DOMContentLoaded', () => {
// DOM Elements
const uploadZone = document.getElementById('upload-zone');
const imageInput = document.getElementById('image-input');
const editorArea = document.getElementById('editor-area');
const canvas = document.getElementById('image-canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const magnifier = document.getElementById('magnifier');
const previewLocked = document.getElementById('preview-locked');
const previewHover = document.getElementById('preview-hover');
const hexInput = document.getElementById('hex-value');
const rgbInput = document.getElementById('rgb-value');
const resetBtn = document.getElementById('reset-btn');
const toast = document.getElementById('toast');
// State
let isImageLoaded = false;
let isLocked = false;
let canvasRect = null;
let scaleX = 1;
let scaleY = 1;
// --- Event Listeners ---
// Upload Zone
uploadZone.addEventListener('click', () => imageInput.click());
uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
uploadZone.classList.add('drag-over');
});
uploadZone.addEventListener('dragleave', () => {
uploadZone.classList.remove('drag-over');
});
uploadZone.addEventListener('drop', (e) => {
e.preventDefault();
uploadZone.classList.remove('drag-over');
handleFiles(e.dataTransfer.files);
});
imageInput.addEventListener('change', (e) => handleFiles(e.target.files));
// Paste Support
document.addEventListener('paste', handlePaste);
// Canvas Interactions
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseleave', () => {
magnifier.classList.add('hidden');
});
canvas.addEventListener('click', toggleLock);
// Copy Buttons
document.getElementById('copy-hex').addEventListener('click', () => copyToClipboard(hexInput.value));
document.getElementById('copy-rgb').addEventListener('click', () => copyToClipboard(rgbInput.value));
// Reset
resetBtn.addEventListener('click', resetApp);
// Window Resize
window.addEventListener('resize', () => {
if (isImageLoaded) updateCanvasMetrics();
});
// --- Core Functions ---
function handleFiles(files) {
if (files.length > 0) {
const file = files[0];
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = (e) => loadImage(e.target.result);
reader.readAsDataURL(file);
} else {
showToast('Please upload a valid image file', true);
}
}
}
function handlePaste(e) {
const items = e.clipboardData.items;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
const blob = items[i].getAsFile();
const reader = new FileReader();
reader.onload = (event) => loadImage(event.target.result);
reader.readAsDataURL(blob);
return; // Only load the first image found
}
}
}
function loadImage(src) {
const img = new Image();
img.onload = () => {
renderToCanvas(img);
showEditor();
};
img.src = src;
}
function renderToCanvas(img) {
// Reset state
isLocked = false;
magnifier.dataset.imgSrc = '';
magnifier.style.backgroundImage = 'none';
// Set canvas to actual image dimensions for precision
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
// Draw image
ctx.drawImage(img, 0, 0);
// Prepare magnifier background (optimization)
magnifier.style.backgroundImage = `url(${canvas.toDataURL()})`;
magnifier.style.backgroundRepeat = 'no-repeat';
magnifier.dataset.imgSrc = 'set';
isImageLoaded = true;
// Update metrics after a brief delay to ensure layout rendering
requestAnimationFrame(updateCanvasMetrics);
}
function updateCanvasMetrics() {
canvasRect = canvas.getBoundingClientRect();
scaleX = canvas.width / canvasRect.width;
scaleY = canvas.height / canvasRect.height;
}
function showEditor() {
uploadZone.classList.add('hidden'); // Or style it to be hidden
uploadZone.style.display = 'none';
editorArea.classList.remove('hidden');
editorArea.style.display = 'grid'; // Ensure grid display
}
function resetApp() {
isImageLoaded = false;
isLocked = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Show Upload Zone
uploadZone.classList.remove('hidden');
uploadZone.style.display = ''; // Clear inline style
// Hide Editor Area
editorArea.classList.add('hidden');
editorArea.style.display = 'none'; // Ensure it is hidden
imageInput.value = ''; // Reset input
}
function handleMouseMove(e) {
if (!isImageLoaded) return;
// Recalculate metrics occasionally to be safe, or rely on resize event
if (!canvasRect) updateCanvasMetrics();
const x = (e.clientX - canvasRect.left) * scaleX;
const y = (e.clientY - canvasRect.top) * scaleY;
// Clamp coordinates
const clampedX = Math.max(0, Math.min(x, canvas.width - 1));
const clampedY = Math.max(0, Math.min(y, canvas.height - 1));
// Get pixel data
const pixel = ctx.getImageData(clampedX, clampedY, 1, 1).data;
const [r, g, b, a] = pixel;
// Update UI
updateColorDisplay(r, g, b);
updateMagnifier(e.clientX, e.clientY, clampedX, clampedY);
}
function updateColorDisplay(r, g, b) {
const hex = rgbToHex(r, g, b);
const rgb = `rgb(${r}, ${g}, ${b})`;
// Always update Hover view
previewHover.style.backgroundColor = rgb;
// If NOT locked, update Locked view and values to mirror current selection
if (!isLocked) {
previewLocked.style.backgroundColor = rgb;
hexInput.value = hex;
rgbInput.value = rgb;
}
}
function updateMagnifier(mouseX, mouseY, imageX, imageY) {
magnifier.classList.remove('hidden');
// Magnifier position (follow cursor)
// Offset it slightly so it doesn't block the cursor
const offset = 20;
magnifier.style.left = `${mouseX + offset}px`;
magnifier.style.top = `${mouseY - offset}px`;
const zoomLevel = 4; // 4x zoom
const bgWidth = canvasRect.width * zoomLevel;
const bgHeight = canvasRect.height * zoomLevel;
magnifier.style.backgroundSize = `${bgWidth}px ${bgHeight}px`;
// Calculate background position
// We need to shift the background so that the point (imageX, imageY) is at the center of the magnifier.
// Relative position of point on the displayed canvas:
const relX = (mouseX - canvasRect.left);
const relY = (mouseY - canvasRect.top);
// The background position needs to be negative to shift the image.
// pos_x = - (relX * zoomLevel - magnifierWidth/2)
const bgPosX = -(relX * zoomLevel - magnifier.offsetWidth / 2);
const bgPosY = -(relY * zoomLevel - magnifier.offsetHeight / 2);
magnifier.style.backgroundPosition = `${bgPosX}px ${bgPosY}px`;
// Optional: Add a crosshair in CSS (done via border or pseudo element)
}
function resetMagnifier() {
magnifier.dataset.imgSrc = '';
magnifier.style.backgroundImage = 'none';
}
// --- Helpers ---
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
showToast(`Copied ${text}`);
});
}
function toggleLock(e) {
isLocked = !isLocked;
if (isLocked) {
// Force one last update to ensure we grabbed specific pixel if it was a click
handleMouseMove(e);
showToast("Color Selection Locked");
previewLocked.style.borderColor = "#22c55e"; // Green border to show locked
} else {
showToast("Unlocked");
previewLocked.style.borderColor = "var(--border-color)";
handleMouseMove(e); // Update immediately to new pos
}
}
function showToast(msg, isError = false) {
toast.textContent = msg;
toast.style.backgroundColor = isError ? '#ef4444' : '#22c55e';
toast.classList.remove('hidden');
setTimeout(() => {
toast.classList.add('hidden');
}, 2000);
}
});