diff --git a/ts-editor/index.html b/ts-editor/index.html
index 8a2373c..d8c8633 100644
--- a/ts-editor/index.html
+++ b/ts-editor/index.html
@@ -103,27 +103,6 @@
No sections yet.
Use Add section to get started.
-
diff --git a/ts-editor/src/browser.ts b/ts-editor/src/browser.ts
index 685760a..e50c1d1 100644
--- a/ts-editor/src/browser.ts
+++ b/ts-editor/src/browser.ts
@@ -518,82 +518,99 @@ const PixelCanvas: m.Component<{ img: MonoFormatPixelImage; onpaint: () => void
vnode.state.drawing = false;
vnode.state.drawValue = 1;
},
- view({ attrs: { img, onpaint }, state }) {
- function pixelFromEvent(e: MouseEvent): { x: number; y: number } | null {
- const canvas = e.currentTarget as HTMLCanvasElement;
+ view({ attrs: { img, onpaint }, state: s }) {
+ function pixelFromCoords(canvas: HTMLCanvasElement, clientX: number, clientY: number) {
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);
+ const x = Math.floor((clientX - rect.left) * scaleX / PIXEL_SCALE);
+ const y = Math.floor((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);
+ function paintAt(canvas: HTMLCanvasElement, clientX: number, clientY: number) {
+ const p = pixelFromCoords(canvas, clientX, clientY);
if (!p) return;
- img.pixels[getPixelIndex(img, p.x, p.y)] = state.drawValue;
+ img.pixels[getPixelIndex(img, p.x, p.y)] = s.drawValue;
onpaint();
- drawPixelCanvas(e.currentTarget as HTMLCanvasElement, img);
+ drawPixelCanvas(canvas, img);
+ }
+
+ function paint(e: MouseEvent) {
+ paintAt(e.currentTarget as HTMLCanvasElement, e.clientX, e.clientY);
+ }
+
+ function attachTouch(canvas: HTMLCanvasElement) {
+ canvas.addEventListener("touchstart", (e: TouchEvent) => {
+ e.preventDefault();
+ s.drawing = true;
+ const touch = e.changedTouches[0];
+ const p = pixelFromCoords(canvas, touch.clientX, touch.clientY);
+ if (p) s.drawValue = img.pixels[getPixelIndex(img, p.x, p.y)] ? 0 : 1;
+ paintAt(canvas, touch.clientX, touch.clientY);
+ }, { passive: false });
+
+ canvas.addEventListener("touchmove", (e: TouchEvent) => {
+ e.preventDefault();
+ if (!s.drawing) return;
+ const touch = e.changedTouches[0];
+ paintAt(canvas, touch.clientX, touch.clientY);
+ }, { passive: false });
+
+ canvas.addEventListener("touchend", (e) => { e.preventDefault(); s.drawing = false; }, { passive: false });
+ canvas.addEventListener("touchcancel", (e) => { e.preventDefault(); s.drawing = false; }, { passive: false });
}
return m("canvas.pixel-editor", {
width: img.width * PIXEL_SCALE,
height: img.height * PIXEL_SCALE,
- oncreate: ({ dom }: m.VnodeDOM) => drawPixelCanvas(dom as HTMLCanvasElement, img),
+ oncreate: ({ dom }: m.VnodeDOM) => {
+ const canvas = dom as HTMLCanvasElement;
+ drawPixelCanvas(canvas, img);
+ attachTouch(canvas);
+ },
onupdate: ({ dom }: m.VnodeDOM) => drawPixelCanvas(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;
+ s.drawing = true;
+ const p = pixelFromCoords(e.currentTarget as HTMLCanvasElement, e.clientX, e.clientY);
+ if (p) s.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; },
+ onmousemove: (e: MouseEvent) => { if (s.drawing) paint(e); },
+ onmouseup: () => { s.drawing = false; },
+ onmouseleave: () => { s.drawing = false; },
+
ondragover: (e: DragEvent) => {
e.preventDefault();
e.dataTransfer!.dropEffect = "copy";
},
-
ondrop: (e: DragEvent) => {
e.preventDefault();
const file = e.dataTransfer?.files[0];
if (!file || !file.type.startsWith("image/")) return;
-
const url = URL.createObjectURL(file);
const htmlImg = new Image();
htmlImg.onload = () => {
- // Draw the dropped image into an offscreen canvas to read pixel data
const offscreen = document.createElement("canvas");
- offscreen.width = htmlImg.width;
- offscreen.height = htmlImg.height;
const ctx = offscreen.getContext("2d")!;
- ctx.drawImage(htmlImg, 0, 0);
-
- const imageData = ctx.getImageData(0, 0, htmlImg.width, htmlImg.height);
-
- // Convert to your pixel format — adjust this to match your img structure
const scale = Math.min(1, 120 / htmlImg.width, 60 / htmlImg.height);
const w = Math.round(htmlImg.width * scale);
const h = Math.round(htmlImg.height * scale);
-
offscreen.width = w;
offscreen.height = h;
- ctx.drawImage(htmlImg, 0, 0, w, h); // scales the image down while drawing
+ ctx.drawImage(htmlImg, 0, 0, w, h);
+ const imageData = ctx.getImageData(0, 0, w, h);
img.width = w;
img.height = h;
- // then read imageData from the scaled offscreen canvas as before
img.pixels = new Uint8Array(w * h).map((_, i) => {
- const r = imageData.data[i * 4] || 0;
- const g = imageData.data[i * 4 + 1] || 0;
- const b = imageData.data[i * 4 + 2] || 0;
- const a = imageData.data[i * 4 + 3] || 0;
- // transparent = 0, dark pixels = 1, light pixels = 0
+ const r = imageData.data[i * 4];
+ const g = imageData.data[i * 4 + 1];
+ const b = imageData.data[i * 4 + 2];
+ const a = imageData.data[i * 4 + 3];
return a > 128 && (r * 299 + g * 587 + b * 114) < 128_000 ? 0 : 1;
});
-
URL.revokeObjectURL(url);
m.redraw();
};
diff --git a/ts-editor/style.css b/ts-editor/style.css
index 8a2c032..adebd99 100644
--- a/ts-editor/style.css
+++ b/ts-editor/style.css
@@ -681,4 +681,10 @@ select.meta-val {
background: var(--bg-active)
}
-#mob-add-section { display: none; }
\ No newline at end of file
+#mob-add-section { display: none; }
+
+canvas.pixel-editor {
+ touch-action: none; /* belt-and-suspenders alongside preventDefault */
+ -webkit-user-select: none;
+ user-select: none;
+}
\ No newline at end of file