mnist-classify/ppm.html
Arija A. 3d6b3e31b6
Make the model more optimised.
Signed-off-by: Arija A. <ari@ari.lt>
2025-05-18 00:22:21 +03:00

334 lines
13 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ari::web -&gt; PPM</title>
<meta
name="description"
content="Create PPM files online: P6 and P3 format."
/>
<meta property="og:type" content="website" />
<meta name="color-scheme" content="dark" />
<meta name="theme-color" content="#121212" />
<meta name="foss:src" content="/git/" />
<meta name="license" content="AGPL-3.0-or-later" />
<link rel="manifest" href="/manifest.json" />
<script type="text/javascript">
<!--//--><![CDATA[//><!--
/**
* @licstart The following is the entire license notice for the JavaScript
* code in this page.
*
* Copyright (C) 2024 Ari Archer
*
* The JavaScript code in this page is free software: you can redistribute
* it and/or modify it under the terms of the GNU Affero General Public License
* (AGPL) as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version. The code is
* distributed WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the AGPL
* for more details.
*
* As additional permission under AGPL version 3 section 7, you may
* distribute non-source (e.g., minimized or compacted) forms of that code
* without the copy of the AGPL normally required by section 4, provided
* you include this license notice and a URL through which recipients can
* access the Corresponding Source.
*
* @licend The above is the entire license notice for the JavaScript code
* in this page.
*/
//--><!]]>
</script>
<style>
#canvas {
border: 1px solid red;
image-rendering: pixelated;
cursor: crosshair;
display: block;
margin-top: 10px;
}
#controls {
margin-top: 10px;
}
label,
input,
select,
button {
margin-right: 10px;
margin-bottom: 5px;
}
#brush_size_input {
width: 50px;
}
</style>
</head>
<body>
<article>
<header>
<h1>Create P6 or P3 PPM Image online</h1>
<p>Enjoy. Created this in a rush.</p>
<div id="controls">
<label for="width_input">width:</label>
<input
type="number"
id="width_input"
value="100"
min="1"
max="1000"
/>
<label for="height_input">height:</label>
<input
type="number"
id="height_input"
value="100"
min="1"
max="1000"
/>
<label for="colour_picker">choose background colour:</label>
<input type="color" id="colour_picker_bg" value="#000000" />
<label for="scale_factor_input">scale factor:</label>
<input
type="number"
id="scale_factor_input"
value="10"
min="1"
max="20"
/>
<button id="save_canvas_btn">save canvas</button>
<br />
<label for="brush_size_input">brush size:</label>
<input
type="number"
id="brush_size_input"
value="3"
min="1"
max="50"
/>
<br />
<label for="format_select">choose format:</label>
<select id="format_select">
<option value="P6">P6 (binary)</option>
<option value="P3">P3 (ASCII)</option>
</select>
<button id="save_btn">save image</button>
<br /><br />
<label for="colour_picker">choose brush colour:</label>
<input type="color" id="colour_picker" value="#ffffff" />
</div>
</header>
<main>
<div align="center">
<canvas id="canvas" width="100" height="100"></canvas>
</div>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const width_input = document.getElementById("width_input");
const height_input =
document.getElementById("height_input");
const scale_factor_input =
document.getElementById("scale_factor_input");
const save_canvas_btn =
document.getElementById("save_canvas_btn");
const format_select =
document.getElementById("format_select");
const brush_size_input =
document.getElementById("brush_size_input");
const save_btn = document.getElementById("save_btn");
const colour_picker =
document.getElementById("colour_picker");
const colour_picker_bg =
document.getElementById("colour_picker_bg");
function clear_canvas() {
canvas.style.width =
canvas.width *
parseInt(scale_factor_input.value, 10) +
"px";
canvas.style.height =
canvas.height *
parseInt(scale_factor_input.value, 10) +
"px";
ctx.fillStyle = colour_picker_bg.value;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
clear_canvas();
save_canvas_btn.addEventListener("click", () => {
const width = parseInt(width_input.value, 10);
const height = parseInt(height_input.value, 10);
if (isNaN(width) || width < 1 || width > 1000) {
alert("width must be a number between 1 and 1000");
return;
}
if (isNaN(height) || height < 1 || height > 1000) {
alert("height must be a number between 1 and 1000");
return;
}
canvas.width = width;
canvas.height = height;
clear_canvas();
});
let drawing = false;
let last_x = 0;
let last_y = 0;
function get_mouse_pos(canvas, event) {
const rect = canvas.getBoundingClientRect();
const scale_x = canvas.width / rect.width;
const scale_y = canvas.height / rect.height;
return {
x: Math.floor(
(event.clientX - rect.left) * scale_x,
),
y: Math.floor((event.clientY - rect.top) * scale_y),
};
}
canvas.addEventListener("mousedown", (e) => {
drawing = true;
const pos = get_mouse_pos(canvas, e);
last_x = pos.x;
last_y = pos.y;
});
canvas.addEventListener("mousemove", (e) => {
if (!drawing) return;
const pos = get_mouse_pos(canvas, e);
ctx.strokeStyle = colour_picker.value;
ctx.lineWidth =
parseInt(brush_size_input.value, 10) || 1;
ctx.lineCap = "square";
ctx.beginPath();
ctx.moveTo(last_x + 0.5, last_y + 0.5);
ctx.lineTo(pos.x + 0.5, pos.y + 0.5);
ctx.stroke();
last_x = pos.x;
last_y = pos.y;
});
canvas.addEventListener("mouseup", () => {
drawing = false;
});
canvas.addEventListener("mouseleave", () => {
drawing = false;
});
function get_pixel_data() {
const image_data = ctx.getImageData(
0,
0,
canvas.width,
canvas.height,
);
const data = image_data.data;
const pixels = [];
for (let i = 0; i < data.length; i += 4) {
pixels.push([data[i], data[i + 1], data[i + 2]]);
}
return pixels;
}
function create_p3(width, height, pixels) {
let header = `P3\n${width} ${height}\n255\n`;
let body = "";
for (let i = 0; i < pixels.length; i++) {
const [r, g, b] = pixels[i];
body += `${r} ${g} ${b} `;
if ((i + 1) % width === 0) body += "\n";
}
return header + body;
}
function create_p6(width, height, pixels) {
const header = `P6\n${width} ${height}\n255\n`;
const header_bytes = new TextEncoder().encode(header);
const pixel_bytes = new Uint8Array(width * height * 3);
for (let i = 0; i < pixels.length; i++) {
const [r, g, b] = pixels[i];
pixel_bytes[i * 3] = r;
pixel_bytes[i * 3 + 1] = g;
pixel_bytes[i * 3 + 2] = b;
}
const combined = new Uint8Array(
header_bytes.length + pixel_bytes.length,
);
combined.set(header_bytes, 0);
combined.set(pixel_bytes, header_bytes.length);
return combined;
}
function save_file(data, filename, mime_type) {
const blob =
data instanceof Uint8Array
? new Blob([data], { type: mime_type })
: new Blob([data], { type: mime_type });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
save_btn.addEventListener("click", () => {
const width = canvas.width;
const height = canvas.height;
const pixels = get_pixel_data();
const format = format_select.value;
if (format === "P3") {
const ppm_text = create_p3(width, height, pixels);
save_file(
ppm_text,
"image_p3.ppm",
"image/x-portable-pixmap",
);
} else if (format === "P6") {
const ppm_binary = create_p6(width, height, pixels);
save_file(
ppm_binary,
"image_p6.ppm",
"image/x-portable-pixmap",
);
}
});
</script>
</main>
</article>
</body>
</html>