feat: image editor support

typescript_changes
flop 3 weeks ago
parent 7316a9d3a8
commit 15b94d51f9
  1. 91
      ts/src/browser.ts
  2. 8
      ts/style.css

@ -424,6 +424,91 @@ function FieldInput(si: number, ei: number, field: ElFieldMeta, el: MonoDisplay.
}
}
// ─── Image2D pixel editor ─────────────────────────────────────────────────────
const PIXEL_SCALE = 4;
function getPixelIndex(img: MonoDisplay.MonoFormatPixelImage, x: number, y: number): number {
return y * img.width + x;
}
function drawImage2DCanvas(canvas: HTMLCanvasElement, img: MonoDisplay.MonoFormatPixelImage) {
const ctx = canvas.getContext("2d")!;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#EC0";
for (let y = 0; y < img.height; y++) {
for (let x = 0; x < img.width; x++) {
if (img.pixels[getPixelIndex(img, x, y)]) {
ctx.fillRect(x * PIXEL_SCALE, y * PIXEL_SCALE, PIXEL_SCALE, PIXEL_SCALE);
}
}
}
}
interface Image2DEditorState {
drawing: boolean;
drawValue: number; // 0 or 1
}
const Image2DEditor: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFormatImage2D }, Image2DEditorState> = {
drawing: false,
drawValue: 1,
oncreate({ dom }) {
const canvas = dom as HTMLCanvasElement;
const el = (canvas as any)._el as MonoDisplay.MonoFormatImage2D;
drawImage2DCanvas(canvas, el.image);
},
onupdate({ dom, attrs }) {
drawImage2DCanvas(dom as HTMLCanvasElement, attrs.el.image);
},
view({ attrs: { si, ei, el }, state }) {
const img = el.image;
function pixelFromEvent(e: MouseEvent): { x: number; y: number } | null {
const canvas = e.currentTarget as HTMLCanvasElement;
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const x = Math.floor((e.clientX - rect.left) * scaleX / PIXEL_SCALE);
const y = Math.floor((e.clientY - rect.top) * scaleY / PIXEL_SCALE);
if (x < 0 || x >= img.width || y < 0 || y >= img.height) return null;
return { x, y };
}
function paint(e: MouseEvent) {
const p = pixelFromEvent(e);
if (!p) return;
const idx = getPixelIndex(img, p.x, p.y);
img.pixels[idx] = state.drawValue;
markDirty(); triggerPreview();
drawImage2DCanvas(e.currentTarget as HTMLCanvasElement, img);
}
return m("canvas.pixel-editor", {
width: img.width * PIXEL_SCALE,
height: img.height * PIXEL_SCALE,
oncreate: ({ dom }: m.VnodeDOM) => {
(dom as any)._el = el;
drawImage2DCanvas(dom as HTMLCanvasElement, img);
},
onupdate: ({ dom }: m.VnodeDOM) => drawImage2DCanvas(dom as HTMLCanvasElement, img),
onmousedown: (e: MouseEvent) => {
state.drawing = true;
const p = pixelFromEvent(e);
if (p) state.drawValue = img.pixels[getPixelIndex(img, p.x, p.y)] ? 0 : 1;
paint(e);
},
onmousemove: (e: MouseEvent) => { if (state.drawing) paint(e); },
onmouseup: () => { state.drawing = false; },
onmouseleave: () => { state.drawing = false; },
});
},
};
const ElementItem: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFormatElement }> = {
view({ attrs: { si, ei, el } }) {
const isActive = activeSecIndex === si && activeElIndex === ei;
@ -473,6 +558,12 @@ const ElementItem: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFor
)
),
) : null,
el.type === MonoDisplay.ElementType.Image2D
? m(".field.full",
m("label", "Pixels"),
m(Image2DEditor, { si, ei, el: el as MonoDisplay.MonoFormatImage2D }),
)
: null,
),
);
},

@ -256,6 +256,14 @@ body {
display: contents;
}
canvas.pixel-editor {
display: block;
cursor: crosshair;
image-rendering: pixelated;
max-width: 100%;
border: 1px solid var(--bd-inner);
}
#sec-meta {
background: var(--bg-sunken);
border: 1px solid var(--bd-inner);

Loading…
Cancel
Save