[FEAT] Add eye drop to pixel editor

This commit is contained in:
2026-01-03 18:25:24 +01:00
parent e290eb21a4
commit 8e71d7379a
2 changed files with 52 additions and 11 deletions

View File

@@ -56,6 +56,11 @@
<i class="fas fa-eraser text-gray-600 dark:text-gray-300"></i> <i class="fas fa-eraser text-gray-600 dark:text-gray-300"></i>
</button> </button>
</Tooltip> </Tooltip>
<Tooltip text="Eyedropper (I)">
<button @click="editor.currentTool.value = 'picker'" :class="editor.currentTool.value === 'picker' ? 'bg-white dark:bg-gray-600 shadow-sm' : 'hover:bg-gray-200 dark:hover:bg-gray-600'" class="p-2 rounded-md transition-all">
<i class="fas fa-eye-dropper text-gray-600 dark:text-gray-300"></i>
</button>
</Tooltip>
</div> </div>
<!-- Color Picker & History --> <!-- Color Picker & History -->
@@ -130,15 +135,15 @@
<span class="text-xs text-gray-600 dark:text-gray-400 select-none">Grid</span> <span class="text-xs text-gray-600 dark:text-gray-400 select-none">Grid</span>
</label> </label>
<label class="flex items-center gap-2 cursor-pointer"> <label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="editor.showPointerLocation.value" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" /> <input type="checkbox" v-model="showPixelHighlight" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
<span class="text-xs text-gray-600 dark:text-gray-400 select-none">Coords</span> <span class="text-xs text-gray-600 dark:text-gray-400 select-none">Highlight</span>
</label> </label>
<!-- Pointer Location Display --> <!-- Pointer Location Display -->
<div v-if="editor.showPointerLocation.value" class="text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded min-w-[60px] text-center">{{ editor.pointerX.value }}, {{ editor.pointerY.value }}</div> <div v-if="editor.showPointerLocation.value" class="text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded min-w-[60px] text-center">{{ editor.pointerX.value }}, {{ editor.pointerY.value }}</div>
<!-- Canvas Size --> <!-- Canvas Size -->
<div class="text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">{{ editor.canvasWidth.value }} × {{ editor.canvasHeight.value }}</div> <div class="text-xs font-mono text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">W: {{ editor.canvasWidth.value }} H: {{ editor.canvasHeight.value }}</div>
</div> </div>
</div> </div>
@@ -182,6 +187,18 @@
backgroundSize: `${editor.zoom.value}px ${editor.zoom.value}px`, backgroundSize: `${editor.zoom.value}px ${editor.zoom.value}px`,
}" }"
></div> ></div>
<!-- Active Pixel Highlight -->
<div
v-if="showPixelHighlight"
class="absolute pointer-events-none z-30 border border-indigo-500/80 bg-indigo-500/20"
:style="{
left: `${editor.pointerX.value * editor.zoom.value}px`,
top: `${editor.pointerY.value * editor.zoom.value}px`,
width: `${editor.zoom.value}px`,
height: `${editor.zoom.value}px`,
}"
></div>
</div> </div>
</div> </div>
</div> </div>
@@ -269,6 +286,7 @@
const resizeWidth = ref(32); const resizeWidth = ref(32);
const resizeHeight = ref(32); const resizeHeight = ref(32);
const resizeAnchor = ref('top-left'); const resizeAnchor = ref('top-left');
const showPixelHighlight = ref(true);
const recentColors = ref<string[]>(['#ffffff', '#000000', '#ff0000', '#00ff00', '#0000ff']); const recentColors = ref<string[]>(['#ffffff', '#000000', '#ff0000', '#00ff00', '#0000ff']);
@@ -425,6 +443,8 @@
editor.currentTool.value = 'pencil'; editor.currentTool.value = 'pencil';
} else if (event.key === 'e' || event.key === 'E') { } else if (event.key === 'e' || event.key === 'E') {
editor.currentTool.value = 'eraser'; editor.currentTool.value = 'eraser';
} else if (event.key === 'i' || event.key === 'I') {
editor.currentTool.value = 'picker';
} }
// Undo/Redo // Undo/Redo

View File

@@ -20,7 +20,7 @@ export const usePixelEditor = (options: PixelEditorOptions = {}) => {
const canvasWidth = ref(options.initialWidth || 32); const canvasWidth = ref(options.initialWidth || 32);
const canvasHeight = ref(options.initialHeight || 32); const canvasHeight = ref(options.initialHeight || 32);
const currentTool = ref<'pencil' | 'eraser'>('pencil'); const currentTool = ref<'pencil' | 'eraser' | 'picker'>('pencil');
const currentColor = ref('#000000'); const currentColor = ref('#000000');
const brushSize = ref(1); const brushSize = ref(1);
const zoom = ref(1); const zoom = ref(1);
@@ -31,7 +31,7 @@ export const usePixelEditor = (options: PixelEditorOptions = {}) => {
const pointerX = ref(0); const pointerX = ref(0);
const pointerY = ref(0); const pointerY = ref(0);
const showPointerLocation = ref(false); const showPointerLocation = ref(true);
const showCheckerboard = ref(false); const showCheckerboard = ref(false);
// History management // History management
@@ -185,14 +185,31 @@ export const usePixelEditor = (options: PixelEditorOptions = {}) => {
} }
}; };
const pickColor = (x: number, y: number) => {
if (!ctx.value) return;
const p = ctx.value.getImageData(x, y, 1, 1).data;
// Ignore if fully transparent
if (p[3] === 0) return;
const hex = '#' + ('00' + p[0].toString(16)).slice(-2) + ('00' + p[1].toString(16)).slice(-2) + ('00' + p[2].toString(16)).slice(-2);
currentColor.value = hex;
};
const startDrawing = (event: MouseEvent) => { const startDrawing = (event: MouseEvent) => {
if (!canvas.value) return; if (!canvas.value) return;
isDrawing.value = true; isDrawing.value = true;
const { x, y } = getPixelCoords(event, canvas.value); const { x, y } = getPixelCoords(event, canvas.value);
lastX.value = x;
lastY.value = y; if (currentTool.value === 'picker') {
drawPixel(x, y); pickColor(x, y);
} else {
lastX.value = x;
lastY.value = y;
drawPixel(x, y);
}
}; };
const continueDrawing = (event: MouseEvent) => { const continueDrawing = (event: MouseEvent) => {
@@ -204,9 +221,13 @@ export const usePixelEditor = (options: PixelEditorOptions = {}) => {
if (!isDrawing.value) return; if (!isDrawing.value) return;
drawLine(lastX.value, lastY.value, x, y); if (currentTool.value === 'picker') {
lastX.value = x; pickColor(x, y);
lastY.value = y; } else {
drawLine(lastX.value, lastY.value, x, y);
lastX.value = x;
lastY.value = y;
}
}; };
const stopDrawing = () => { const stopDrawing = () => {