|
|
|
@ -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 }> = { |
|
|
|
const ElementItem: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFormatElement }> = { |
|
|
|
view({ attrs: { si, ei, el } }) { |
|
|
|
view({ attrs: { si, ei, el } }) { |
|
|
|
const isActive = activeSecIndex === si && activeElIndex === ei; |
|
|
|
const isActive = activeSecIndex === si && activeElIndex === ei; |
|
|
|
@ -473,6 +558,12 @@ const ElementItem: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFor |
|
|
|
) |
|
|
|
) |
|
|
|
), |
|
|
|
), |
|
|
|
) : null, |
|
|
|
) : null, |
|
|
|
|
|
|
|
el.type === MonoDisplay.ElementType.Image2D |
|
|
|
|
|
|
|
? m(".field.full", |
|
|
|
|
|
|
|
m("label", "Pixels"), |
|
|
|
|
|
|
|
m(Image2DEditor, { si, ei, el: el as MonoDisplay.MonoFormatImage2D }), |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
: null, |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
); |
|
|
|
}, |
|
|
|
}, |
|
|
|
|