mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-28 16:52:22 +01:00
feat(photo-editor): Ajax Image Editor client (Photoshop-parity tools)
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).
Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c74f475679
commit
5fac693c1c
@@ -5,6 +5,24 @@ function hexByte(value) {
|
||||
return Math.max(0, Math.min(255, value)).toString(16).padStart(2, '0');
|
||||
}
|
||||
|
||||
// Average an N-by-N block centred on (cx, cy), clamped to the canvas edges.
|
||||
// Photoshop's "Sample Size" option: 1 = Point Sample, 3/5/11/31/51 = N-by-N.
|
||||
function averageBlock(ctx, cx, cy, size, width, height) {
|
||||
const half = Math.floor(size / 2);
|
||||
const x0 = Math.max(0, cx - half);
|
||||
const y0 = Math.max(0, cy - half);
|
||||
const x1 = Math.min(width - 1, cx + half);
|
||||
const y1 = Math.min(height - 1, cy + half);
|
||||
const w = x1 - x0 + 1;
|
||||
const h = y1 - y0 + 1;
|
||||
if (w <= 0 || h <= 0) return null;
|
||||
const data = ctx.getImageData(x0, y0, w, h).data;
|
||||
let r = 0, g = 0, b = 0, a = 0;
|
||||
const n = data.length / 4;
|
||||
for (let i = 0; i < data.length; i += 4) { r += data[i]; g += data[i + 1]; b += data[i + 2]; a += data[i + 3]; }
|
||||
return { r: Math.round(r / n), g: Math.round(g / n), b: Math.round(b / n), a: a / n };
|
||||
}
|
||||
|
||||
export function createEyedropperTool({ activeLayer, composite, syncColor, syncPreview, showToast }) {
|
||||
function sample(e, refreshComposite = false) {
|
||||
const point = canvasCoords(e, state.mainCanvas);
|
||||
@@ -21,11 +39,23 @@ export function createEyedropperTool({ activeLayer, composite, syncColor, syncPr
|
||||
} else if (refreshComposite) {
|
||||
composite();
|
||||
}
|
||||
if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) return null;
|
||||
const pixel = canvas.getContext('2d').getImageData(Math.floor(x), Math.floor(y), 1, 1).data;
|
||||
const rgb = [pixel[0], pixel[1], pixel[2]];
|
||||
if (!pixel[3]) return { color: null, rgb, canvas, x, y };
|
||||
return { color: `#${hexByte(pixel[0])}${hexByte(pixel[1])}${hexByte(pixel[2])}`, rgb, canvas, x, y };
|
||||
const ix = Math.floor(x);
|
||||
const iy = Math.floor(y);
|
||||
if (ix < 0 || iy < 0 || ix >= canvas.width || iy >= canvas.height) return null;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const size = Math.max(1, Math.round(Number(state.eyedropperSampleSize) || 1));
|
||||
let r, g, b, a;
|
||||
if (size <= 1) {
|
||||
const pixel = ctx.getImageData(ix, iy, 1, 1).data;
|
||||
r = pixel[0]; g = pixel[1]; b = pixel[2]; a = pixel[3];
|
||||
} else {
|
||||
const avg = averageBlock(ctx, ix, iy, size, canvas.width, canvas.height);
|
||||
if (!avg) return null;
|
||||
r = avg.r; g = avg.g; b = avg.b; a = avg.a;
|
||||
}
|
||||
const rgb = [r, g, b];
|
||||
if (!a) return { color: null, rgb, canvas, x, y };
|
||||
return { color: `#${hexByte(r)}${hexByte(g)}${hexByte(b)}`, rgb, canvas, x, y };
|
||||
}
|
||||
return {
|
||||
preview(e) {
|
||||
|
||||
Reference in New Issue
Block a user