Add a few more mesh types
This commit is contained in:
+10
-10
@@ -35,9 +35,9 @@ const DTF = (() => {
|
||||
// When format is FORMAT_ALPHA and redAsAlpha is true, the red channel is
|
||||
// used as the alpha value instead of the actual alpha channel.
|
||||
function encode(width, height, rgbaData, format, redAsAlpha) {
|
||||
if (format === undefined) format = FORMAT_RGBA;
|
||||
if(format === undefined) format = FORMAT_RGBA;
|
||||
const bpp = BPP[format];
|
||||
if (bpp === undefined) throw new Error(`Unknown DTF format: 0x${format.toString(16)}`);
|
||||
if(bpp === undefined) throw new Error(`Unknown DTF format: 0x${format.toString(16)}`);
|
||||
|
||||
const src = rgbaData instanceof Uint8ClampedArray ? rgbaData : new Uint8ClampedArray(rgbaData);
|
||||
const buf = new ArrayBuffer(HEADER_SIZE + width * height * bpp);
|
||||
@@ -53,7 +53,7 @@ const DTF = (() => {
|
||||
bytes[12] = format;
|
||||
|
||||
let dst = HEADER_SIZE;
|
||||
for (let i = 0; i < width * height; i++) {
|
||||
for(let i = 0; i < width * height; i++) {
|
||||
const o = i * 4;
|
||||
switch (format) {
|
||||
case FORMAT_ALPHA:
|
||||
@@ -82,13 +82,13 @@ const DTF = (() => {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const view = new DataView(buffer);
|
||||
|
||||
if (bytes.length < HEADER_SIZE) throw new Error("File too small to be a valid DTF");
|
||||
if (bytes[0] !== MAGIC[0] || bytes[1] !== MAGIC[1] || bytes[2] !== MAGIC[2]) {
|
||||
if(bytes.length < HEADER_SIZE) throw new Error("File too small to be a valid DTF");
|
||||
if(bytes[0] !== MAGIC[0] || bytes[1] !== MAGIC[1] || bytes[2] !== MAGIC[2]) {
|
||||
throw new Error("Invalid DTF magic bytes – not a DTF file");
|
||||
}
|
||||
|
||||
const version = bytes[3];
|
||||
if (version !== VERSION) {
|
||||
if(version !== VERSION) {
|
||||
throw new Error(`Unsupported DTF version: 0x${version.toString(16).padStart(2, "0")}`);
|
||||
}
|
||||
|
||||
@@ -97,18 +97,18 @@ const DTF = (() => {
|
||||
const format = bytes[12];
|
||||
const bpp = BPP[format];
|
||||
|
||||
if (bpp === undefined) {
|
||||
if(bpp === undefined) {
|
||||
throw new Error(`Unsupported DTF format: 0x${format.toString(16).padStart(2, "0")}`);
|
||||
}
|
||||
|
||||
const expected = HEADER_SIZE + width * height * bpp;
|
||||
if (bytes.length < expected) {
|
||||
if(bytes.length < expected) {
|
||||
throw new Error(`DTF pixel data truncated (expected ${expected} bytes, got ${bytes.length})`);
|
||||
}
|
||||
|
||||
const rgba = new Uint8ClampedArray(width * height * 4);
|
||||
let src = HEADER_SIZE;
|
||||
for (let i = 0; i < width * height; i++) {
|
||||
for(let i = 0; i < width * height; i++) {
|
||||
const o = i * 4;
|
||||
switch (format) {
|
||||
case FORMAT_ALPHA:
|
||||
@@ -141,7 +141,7 @@ const DTF = (() => {
|
||||
const src = rgbaData instanceof Uint8ClampedArray ? rgbaData : new Uint8ClampedArray(rgbaData);
|
||||
const out = new Uint8ClampedArray(width * height * 4);
|
||||
|
||||
for (let i = 0; i < width * height; i++) {
|
||||
for(let i = 0; i < width * height; i++) {
|
||||
const o = i * 4;
|
||||
switch (format) {
|
||||
case FORMAT_ALPHA: {
|
||||
|
||||
@@ -11,14 +11,14 @@ const DuskPNG = (() => {
|
||||
// Encode RGBA pixel data into a PNG Buffer via pngjs.
|
||||
// Returns a Uint8Array (Buffer) if pngjs is available, otherwise null.
|
||||
function encode(width, height, rgbaData) {
|
||||
if (!_pngAvailable()) return null;
|
||||
if(!_pngAvailable()) return null;
|
||||
|
||||
const png = new PNG({ width, height });
|
||||
const src = rgbaData instanceof Uint8ClampedArray
|
||||
? rgbaData
|
||||
: new Uint8ClampedArray(rgbaData);
|
||||
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
for(let i = 0; i < src.length; i++) {
|
||||
png.data[i] = src[i];
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ const DuskPNG = (() => {
|
||||
function download(filename, width, height, rgbaData) {
|
||||
const buf = encode(width, height, rgbaData);
|
||||
|
||||
if (buf) {
|
||||
if(buf) {
|
||||
// pngjs path
|
||||
const blob = new Blob([buf], { type: "image/png" });
|
||||
_triggerDownload(URL.createObjectURL(blob), filename);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// Returns the smallest power of two >= n.
|
||||
function nextPow2(n) {
|
||||
if (n <= 0) return 1;
|
||||
if(n <= 0) return 1;
|
||||
let p = 1;
|
||||
while (p < n) p <<= 1;
|
||||
return p;
|
||||
@@ -57,10 +57,10 @@ function computeOutputSize() {
|
||||
let outW = state.width;
|
||||
let outH = state.height;
|
||||
// Apply minimum-size constraints first so pow2 rounding accounts for them.
|
||||
if (state.minWidth4 && outW < 4) outW = 4;
|
||||
if (state.minHeight4 && outH < 4) outH = 4;
|
||||
if (state.padWidthPow2) outW = nextPow2(outW);
|
||||
if (state.padHeightPow2) outH = nextPow2(outH);
|
||||
if(state.minWidth4 && outW < 4) outW = 4;
|
||||
if(state.minHeight4 && outH < 4) outH = 4;
|
||||
if(state.padWidthPow2) outW = nextPow2(outW);
|
||||
if(state.padHeightPow2) outH = nextPow2(outH);
|
||||
return { outW, outH };
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ function buildPaddedPixels() {
|
||||
const src = state.pixels;
|
||||
// Zero-filled Uint8ClampedArray = fully transparent black padding.
|
||||
const out = new Uint8ClampedArray(outW * outH * 4);
|
||||
for (let y = 0; y < state.height; y++) {
|
||||
for (let x = 0; x < state.width; x++) {
|
||||
for(let y = 0; y < state.height; y++) {
|
||||
for(let x = 0; x < state.width; x++) {
|
||||
const si = (y * state.width + x) * 4;
|
||||
const di = (y * outW + x) * 4;
|
||||
out[di] = src[si];
|
||||
@@ -87,8 +87,8 @@ function buildPaddedPixels() {
|
||||
const CHECKER_CELL = 8;
|
||||
|
||||
function drawCheckerboard(w, h) {
|
||||
for (let y = 0; y < h; y += CHECKER_CELL) {
|
||||
for (let x = 0; x < w; x += CHECKER_CELL) {
|
||||
for(let y = 0; y < h; y += CHECKER_CELL) {
|
||||
for(let x = 0; x < w; x += CHECKER_CELL) {
|
||||
ctx.fillStyle = ((x / CHECKER_CELL + y / CHECKER_CELL) % 2 === 0) ? "#c8c8c8" : "#ffffff";
|
||||
ctx.fillRect(x, y, Math.min(CHECKER_CELL, w - x), Math.min(CHECKER_CELL, h - y));
|
||||
}
|
||||
@@ -96,7 +96,7 @@ function drawCheckerboard(w, h) {
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
|
||||
const { pixels, width, height } = buildPaddedPixels();
|
||||
const cw = width * state.scale;
|
||||
@@ -105,7 +105,7 @@ function render() {
|
||||
canvas.width = cw;
|
||||
canvas.height = ch;
|
||||
|
||||
if (state.bg === "grid") {
|
||||
if(state.bg === "grid") {
|
||||
drawCheckerboard(cw, ch);
|
||||
} else {
|
||||
ctx.fillStyle = state.bg;
|
||||
@@ -122,7 +122,7 @@ function render() {
|
||||
// ─── Info update ─────────────────────────────────────────────────────────────
|
||||
|
||||
function updateInfo() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const { outW, outH } = computeOutputSize();
|
||||
const bytes = DTF.HEADER_SIZE + outW * outH * DTF.BPP[state.format];
|
||||
infoInputSize.textContent = `${state.width} × ${state.height}`;
|
||||
@@ -189,8 +189,8 @@ function applyImageData(width, height, data, filename) {
|
||||
}
|
||||
|
||||
function handleFile(file) {
|
||||
if (!file) return;
|
||||
if (file.name.toLowerCase().endsWith(".dtf")) {
|
||||
if(!file) return;
|
||||
if(file.name.toLowerCase().endsWith(".dtf")) {
|
||||
loadDTF(file);
|
||||
} else {
|
||||
loadStandardImage(file);
|
||||
@@ -200,7 +200,7 @@ function handleFile(file) {
|
||||
// ─── Export ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function exportDTF() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const { pixels, width, height } = buildPaddedPixels();
|
||||
const buf = DTF.encode(width, height, pixels, state.format);
|
||||
const blob = new Blob([buf], { type: "application/octet-stream" });
|
||||
@@ -214,7 +214,7 @@ function exportDTF() {
|
||||
}
|
||||
|
||||
function exportPNG() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const { pixels, width, height } = buildPaddedPixels();
|
||||
DuskPNG.download(`${state.filename}.png`, width, height, pixels);
|
||||
}
|
||||
@@ -232,7 +232,7 @@ scaleInput.addEventListener("input", () => {
|
||||
|
||||
bgSwatches.addEventListener("click", e => {
|
||||
const btn = e.target.closest(".bg-swatch");
|
||||
if (!btn) return;
|
||||
if(!btn) return;
|
||||
bgSwatches.querySelectorAll(".bg-swatch").forEach(b => b.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
state.bg = btn.dataset.bg;
|
||||
|
||||
@@ -43,8 +43,8 @@ const redAsAlphaCheck = document.getElementById("red-as-alpha");
|
||||
const CHECKER_CELL = 8;
|
||||
|
||||
function drawCheckerboard(w, h) {
|
||||
for (let y = 0; y < h; y += CHECKER_CELL) {
|
||||
for (let x = 0; x < w; x += CHECKER_CELL) {
|
||||
for(let y = 0; y < h; y += CHECKER_CELL) {
|
||||
for(let x = 0; x < w; x += CHECKER_CELL) {
|
||||
ctx.fillStyle = ((x / CHECKER_CELL + y / CHECKER_CELL) % 2 === 0) ? "#c8c8c8" : "#ffffff";
|
||||
ctx.fillRect(x, y, Math.min(CHECKER_CELL, w - x), Math.min(CHECKER_CELL, h - y));
|
||||
}
|
||||
@@ -52,7 +52,7 @@ function drawCheckerboard(w, h) {
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
|
||||
const { pixels, width, height, scale, bg, format } = state;
|
||||
const cw = width * scale;
|
||||
@@ -62,7 +62,7 @@ function render() {
|
||||
canvas.height = ch;
|
||||
|
||||
// 1. Background
|
||||
if (bg === "grid") {
|
||||
if(bg === "grid") {
|
||||
drawCheckerboard(cw, ch);
|
||||
} else {
|
||||
ctx.fillStyle = bg;
|
||||
@@ -128,17 +128,17 @@ function loadDTF(file) {
|
||||
function updateWarnings() {
|
||||
const warnings = [];
|
||||
|
||||
if (state.pixels) {
|
||||
if(state.pixels) {
|
||||
const { width, height } = state;
|
||||
const isPow2 = n => n > 0 && (n & (n - 1)) === 0;
|
||||
|
||||
if (width < 4) warnings.push(`Width is below 4 px (${width})`);
|
||||
if (height < 4) warnings.push(`Height is below 4 px (${height})`);
|
||||
if (!isPow2(width)) warnings.push(`Width is not a power of two (${width})`);
|
||||
if (!isPow2(height)) warnings.push(`Height is not a power of two (${height})`);
|
||||
if(width < 4) warnings.push(`Width is below 4 px (${width})`);
|
||||
if(height < 4) warnings.push(`Height is below 4 px (${height})`);
|
||||
if(!isPow2(width)) warnings.push(`Width is not a power of two (${width})`);
|
||||
if(!isPow2(height)) warnings.push(`Height is not a power of two (${height})`);
|
||||
|
||||
const bytes = DTF.HEADER_SIZE + width * height * DTF.BPP[state.format];
|
||||
if (bytes > 256 * 1024) {
|
||||
if(bytes > 256 * 1024) {
|
||||
warnings.push(`Output exceeds 256 KB (${(bytes / 1024).toFixed(1)} KB)`);
|
||||
}
|
||||
}
|
||||
@@ -150,7 +150,7 @@ function updateWarnings() {
|
||||
}
|
||||
|
||||
function updateDtfSize() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const bytes = DTF.HEADER_SIZE + state.width * state.height * DTF.BPP[state.format];
|
||||
infoDtfSize.textContent = `${(bytes / 1024).toFixed(1)} KB`;
|
||||
updateWarnings();
|
||||
@@ -163,7 +163,7 @@ function applyImageData(width, height, data, filename, formatLabel, format) {
|
||||
state.filename = filename.replace(/\.[^/.]+$/, "");
|
||||
|
||||
// Sync format selector when loading an existing DTF
|
||||
if (format !== undefined) {
|
||||
if(format !== undefined) {
|
||||
state.format = format;
|
||||
formatSelect.value = format;
|
||||
redAsAlphaRow.hidden = format !== DTF.FORMAT_ALPHA;
|
||||
@@ -189,8 +189,8 @@ function applyImageData(width, height, data, filename, formatLabel, format) {
|
||||
}
|
||||
|
||||
function handleFile(file) {
|
||||
if (!file) return;
|
||||
if (file.name.toLowerCase().endsWith(".dtf")) {
|
||||
if(!file) return;
|
||||
if(file.name.toLowerCase().endsWith(".dtf")) {
|
||||
loadDTF(file);
|
||||
} else {
|
||||
loadStandardImage(file);
|
||||
@@ -204,7 +204,7 @@ function showError(msg) {
|
||||
// ─── Export ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function exportDTF() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
const buf = DTF.encode(state.width, state.height, state.pixels, state.format, state.redAsAlpha);
|
||||
const blob = new Blob([buf], { type: "application/octet-stream" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -217,7 +217,7 @@ function exportDTF() {
|
||||
}
|
||||
|
||||
function exportPNG() {
|
||||
if (!state.pixels) return;
|
||||
if(!state.pixels) return;
|
||||
DuskPNG.download(`${state.filename}.png`, state.width, state.height, state.pixels);
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ scaleInput.addEventListener("input", () => {
|
||||
|
||||
bgSwatches.addEventListener("click", e => {
|
||||
const btn = e.target.closest(".bg-swatch");
|
||||
if (!btn) return;
|
||||
if(!btn) return;
|
||||
bgSwatches.querySelectorAll(".bg-swatch").forEach(b => b.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
state.bg = btn.dataset.bg;
|
||||
|
||||
Reference in New Issue
Block a user