mirror of
https://github.com/Nexoniarz/YAPF.git
synced 2026-11-03 04:54:26 +00:00
YAPF v1: fast byte-aligned format, SIMD, tools and plugins
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>
This commit is contained in:
parent
b9b8c93a22
commit
2ca726e3af
48 changed files with 14905 additions and 710 deletions
BIN
extensions/vscode/media/icon.png
Normal file
BIN
extensions/vscode/media/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
26
extensions/vscode/media/preview.css
Normal file
26
extensions/vscode/media/preview.css
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
html, body { height: 100%; margin: 0; }
|
||||
body {
|
||||
display: flex; flex-direction: column;
|
||||
background: var(--vscode-editor-background, #1e1e1e);
|
||||
color: var(--vscode-foreground, #ccc);
|
||||
font: 12px var(--vscode-font-family, system-ui, sans-serif);
|
||||
}
|
||||
#bar {
|
||||
display: flex; justify-content: space-between; align-items: center; gap: 12px;
|
||||
padding: 6px 12px; border-bottom: 1px solid var(--vscode-panel-border, #333);
|
||||
}
|
||||
#bar select {
|
||||
background: var(--vscode-dropdown-background, #3c3c3c);
|
||||
color: var(--vscode-dropdown-foreground, #ccc);
|
||||
border: 1px solid var(--vscode-dropdown-border, #555);
|
||||
}
|
||||
#stage { flex: 1; overflow: auto; display: grid; place-items: center; padding: 16px; cursor: zoom-in; }
|
||||
body:not(.fit) #stage { cursor: zoom-out; }
|
||||
canvas {
|
||||
background-color: #fff;
|
||||
background-image: conic-gradient(#ccc 25%, #fff 0 50%, #ccc 0 75%, #fff 0);
|
||||
background-size: 16px 16px;
|
||||
box-shadow: 0 0 0 1px var(--vscode-panel-border, #333);
|
||||
}
|
||||
canvas.pixelated { image-rendering: pixelated; }
|
||||
#error { padding: 16px; color: var(--vscode-errorForeground, #f48771); }
|
||||
91
extensions/vscode/media/preview.js
Normal file
91
extensions/vscode/media/preview.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// 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
|
||||
})();
|
||||
Loading…
Reference in a new issue