mirror of
https://github.com/Nexoniarz/YAPF.git
synced 2026-11-03 04:54:26 +00:00
Format (version 1, replaces the earlier experimental versions): - per-tile YCoCg-R or subtract-green colour transform, 8-bit modular - 12 row filters per 8-row band (Left, Gradient, Up-right, Up-average, Up 1-8 rows), none with more than a one-add serial dependency - residuals in groups of 8 at the group's width (w bits x 8 = w bytes), 4-bit control codes with repeat-previous-width runs chosen by DP - constant and stored tiles, independent 64x64 tiles, mips, GPU hints - all sizes validated before decoding; corrupt input is rejected Library: - SIMD on by default: SSSE3 (runtime CPUID check; GCC, Clang, MSVC, MinGW) and NEON on ARM64; -DYAPF_NO_SIMD for plain C - yapf_load_memory, yapf_load_mt, yapf_load_memory_mt - 6-10x faster decoding than PNG (stb_image) on one core; -O2 and -O3 builds perform the same - fixes for macOS (sysconf) and the Windows import library exports Tools and integrations: - `yapf` CLI: convert PNG/JPG/TGA/BMP/PSD/GIF <-> YAPF, info, bench, thumbnail, mips - JavaScript and Python bindings (byte-identical to the C encoder) - GIMP 3 plugin: exports the flattened image, optional mipmaps - Blender extension: import (also as plane), export, drag and drop - VS Code preview, Figma plugin, Photoshop scripts, Linux MIME type and thumbnailer - Makefile, updated build.sh / build.bat, tools/build_release.sh - tests/test_yapf.c (784 checks) and bindings/js/test.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
3.7 KiB
JavaScript
91 lines
3.7 KiB
JavaScript
// Webview side of the YAPF preview: decode, draw, zoom, mip picker, info.
|
||
(function () {
|
||
const vscode = typeof acquireVsCodeApi === 'function' ? acquireVsCodeApi() : null;
|
||
const canvas = document.getElementById('canvas');
|
||
const info = document.getElementById('info');
|
||
const controls = document.getElementById('controls');
|
||
const errorBox = document.getElementById('error');
|
||
const GPU_NAMES = ['RGBA8', 'RGB8', 'RG8', 'R8', 'SRGB8_A8', 'SRGB8'];
|
||
const CH_NAMES = ['', 'Gray', 'Gray + Alpha', 'RGB', 'RGBA'];
|
||
|
||
let image = null, level = 0, fit = true, zoom = 1;
|
||
|
||
function kb(n) { return n < 1024 * 1024 ? (n / 1024).toFixed(1) + ' KB' : (n / 1048576).toFixed(2) + ' MB'; }
|
||
|
||
function draw() {
|
||
const w = Math.max(1, image.width >>> level), h = Math.max(1, image.height >>> level);
|
||
canvas.width = w; canvas.height = h;
|
||
canvas.getContext('2d').putImageData(new ImageData(YAPF.toRGBA(image, level), w, h), 0, 0);
|
||
layout();
|
||
}
|
||
|
||
function layout() {
|
||
if (!image) return;
|
||
const w = canvas.width, h = canvas.height;
|
||
const stage = document.getElementById('stage');
|
||
const scale = fit ? Math.min(1, (stage.clientWidth - 32) / w, (stage.clientHeight - 32) / h) : zoom;
|
||
canvas.style.width = Math.max(1, Math.round(w * scale)) + 'px';
|
||
canvas.style.height = Math.max(1, Math.round(h * scale)) + 'px';
|
||
canvas.classList.toggle('pixelated', scale >= 2);
|
||
document.body.classList.toggle('fit', fit);
|
||
stage.title = fit ? 'Click for 100%, Ctrl+scroll to zoom' : 'Click to fit';
|
||
}
|
||
|
||
function show(name, bytes) {
|
||
errorBox.hidden = true;
|
||
const t = performance.now();
|
||
try {
|
||
image = YAPF.decode(bytes);
|
||
} catch (e) {
|
||
image = null;
|
||
errorBox.hidden = false;
|
||
errorBox.textContent = name + ': ' + e.message;
|
||
info.textContent = '';
|
||
return;
|
||
}
|
||
const ms = performance.now() - t;
|
||
const raw = image.width * image.height * image.channels;
|
||
level = Math.min(level, image.mipLevels - 1);
|
||
info.textContent = [
|
||
image.width + ' × ' + image.height,
|
||
CH_NAMES[image.channels],
|
||
(image.flags & 2 ? 'sRGB' : 'linear') + (image.flags & 1 ? ', premultiplied' : ''),
|
||
'GPU ' + (GPU_NAMES[image.gpuFormat] || '?'),
|
||
kb(bytes.length) + ' (' + (100 * bytes.length / raw).toFixed(1) + '% of raw)',
|
||
'decoded in ' + ms.toFixed(1) + ' ms',
|
||
].join(' · ');
|
||
|
||
controls.textContent = '';
|
||
if (image.mipLevels > 1) {
|
||
const sel = document.createElement('select');
|
||
for (let m = 0; m < image.mipLevels; m++) {
|
||
const o = document.createElement('option');
|
||
o.value = m;
|
||
o.textContent = 'Mip ' + m + ' (' + Math.max(1, image.width >>> m) + '×' + Math.max(1, image.height >>> m) + ')';
|
||
sel.appendChild(o);
|
||
}
|
||
sel.value = level;
|
||
sel.onchange = () => { level = +sel.value; draw(); };
|
||
controls.appendChild(sel);
|
||
}
|
||
draw();
|
||
}
|
||
|
||
document.getElementById('stage').addEventListener('click', () => { fit = !fit; zoom = 1; layout(); });
|
||
document.getElementById('stage').addEventListener('wheel', (e) => {
|
||
if (!e.ctrlKey || !image) return;
|
||
e.preventDefault();
|
||
fit = false;
|
||
zoom = Math.min(64, Math.max(1 / 64, zoom * (e.deltaY < 0 ? 1.25 : 0.8)));
|
||
layout();
|
||
}, { passive: false });
|
||
window.addEventListener('resize', layout);
|
||
|
||
window.addEventListener('message', (e) => {
|
||
const m = e.data;
|
||
if (m.type === 'file') show(m.name, m.bytes instanceof Uint8Array ? m.bytes : new Uint8Array(m.bytes));
|
||
if (m.type === 'error') { errorBox.hidden = false; errorBox.textContent = m.message; }
|
||
});
|
||
if (vscode) vscode.postMessage({ type: 'ready' });
|
||
window.yapfPreviewShow = show; // used by the browser test page
|
||
})();
|