mirror of
https://github.com/Nexoniarz/YAPF.git
synced 2026-11-03 13:04: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
124
extensions/figma/src/ui.src.html
Normal file
124
extensions/figma/src/ui.src.html
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { font: 12px Inter, system-ui, sans-serif; margin: 0; padding: 14px;
|
||||
color: var(--figma-color-text, #333); background: var(--figma-color-bg, #fff); }
|
||||
h2 { font-size: 13px; margin: 0 0 10px; }
|
||||
.tabs { display: flex; gap: 6px; margin-bottom: 12px; }
|
||||
.tabs button { flex: 1; }
|
||||
button { font: inherit; padding: 7px 10px; border-radius: 6px; cursor: pointer;
|
||||
border: 1px solid var(--figma-color-border, #ccc);
|
||||
background: var(--figma-color-bg-secondary, #f5f5f5); color: inherit; }
|
||||
button.primary, .tabs button.on { background: var(--figma-color-bg-brand, #0d99ff);
|
||||
color: var(--figma-color-text-onbrand, #fff); border-color: transparent; }
|
||||
#drop { border: 2px dashed var(--figma-color-border, #ccc); border-radius: 8px; padding: 28px 10px;
|
||||
text-align: center; cursor: pointer; color: var(--figma-color-text-secondary, #777); }
|
||||
#drop.hover { border-color: var(--figma-color-border-brand, #0d99ff); }
|
||||
#status { margin-top: 10px; min-height: 32px; color: var(--figma-color-text-secondary, #777);
|
||||
white-space: pre-line; }
|
||||
.row { display: flex; gap: 8px; align-items: center; margin: 8px 0; }
|
||||
select { font: inherit; }
|
||||
[hidden] { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="tabs">
|
||||
<button id="tab-import">Import</button>
|
||||
<button id="tab-export">Export</button>
|
||||
</div>
|
||||
|
||||
<section id="import">
|
||||
<h2>Import YAPF images</h2>
|
||||
<div id="drop">Drop .yapf files here<br>or click to choose</div>
|
||||
<input id="file" type="file" accept=".yapf" multiple hidden>
|
||||
</section>
|
||||
|
||||
<section id="export" hidden>
|
||||
<h2>Export selection as YAPF</h2>
|
||||
<div id="sel">Nothing selected</div>
|
||||
<div class="row">Scale
|
||||
<select id="scale"><option>1</option><option>2</option><option>4</option></select>
|
||||
</div>
|
||||
<button id="go" class="primary">Export</button>
|
||||
</section>
|
||||
|
||||
<div id="status"></div>
|
||||
|
||||
<script>/*__YAPF_JS__*/</script>
|
||||
<script>/*__PNG_JS__*/</script>
|
||||
<script>
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const post = (m) => parent.postMessage({ pluginMessage: m }, '*');
|
||||
const status = (t) => { $('status').textContent = t; };
|
||||
|
||||
function tab(name) {
|
||||
$('import').hidden = name !== 'import';
|
||||
$('export').hidden = name !== 'export';
|
||||
$('tab-import').classList.toggle('on', name === 'import');
|
||||
$('tab-export').classList.toggle('on', name === 'export');
|
||||
status('');
|
||||
}
|
||||
$('tab-import').onclick = () => tab('import');
|
||||
$('tab-export').onclick = () => tab('export');
|
||||
|
||||
/* Import: .yapf → pixels → exact PNG → Figma image */
|
||||
async function importFiles(files) {
|
||||
const images = [];
|
||||
for (const f of files) {
|
||||
try {
|
||||
const img = YAPF.decode(new Uint8Array(await f.arrayBuffer()));
|
||||
const png = await PNG.encode(img.pixels, img.width, img.height, img.channels);
|
||||
images.push({ name: f.name.replace(/\.yapf$/i, ''), width: img.width, height: img.height, png });
|
||||
status('Decoded ' + f.name + ' (' + img.width + '×' + img.height + ')');
|
||||
} catch (e) {
|
||||
status(f.name + ': ' + e.message);
|
||||
}
|
||||
}
|
||||
if (images.length) post({ type: 'import', images });
|
||||
}
|
||||
$('drop').onclick = () => $('file').click();
|
||||
$('file').onchange = (e) => importFiles([...e.target.files]);
|
||||
$('drop').ondragover = (e) => { e.preventDefault(); $('drop').classList.add('hover'); };
|
||||
$('drop').ondragleave = () => $('drop').classList.remove('hover');
|
||||
$('drop').ondrop = (e) => { e.preventDefault(); $('drop').classList.remove('hover'); importFiles([...e.dataTransfer.files]); };
|
||||
|
||||
/* Export: Figma PNG → exact pixels → .yapf download */
|
||||
$('go').onclick = () => { status('Exporting…'); post({ type: 'export', scale: +$('scale').value }); };
|
||||
|
||||
async function exported(name, pngBytes) {
|
||||
let img = await PNG.decode(pngBytes);
|
||||
let { pixels, channels } = img;
|
||||
if (channels === 4) { // drop alpha when fully opaque
|
||||
let opaque = true;
|
||||
for (let i = 3; i < pixels.length && opaque; i += 4) opaque = pixels[i] === 255;
|
||||
if (opaque) {
|
||||
const rgb = new Uint8Array(img.width * img.height * 3);
|
||||
for (let i = 0, j = 0; i < pixels.length; i += 4, j += 3) rgb.set(pixels.subarray(i, i + 3), j);
|
||||
pixels = rgb; channels = 3;
|
||||
}
|
||||
}
|
||||
const file = YAPF.encode({ width: img.width, height: img.height, channels, pixels,
|
||||
flags: YAPF.FLAG.SRGB,
|
||||
gpuFormat: channels === 4 ? YAPF.GPU.SRGB8_A8 : YAPF.GPU.SRGB8 });
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(new Blob([file], { type: 'image/x-yapf' }));
|
||||
a.download = name.replace(/[\\/:*?"<>|]/g, '_') + '.yapf';
|
||||
document.body.appendChild(a); a.click(); a.remove();
|
||||
status('Saved ' + a.download + '\n' + img.width + '×' + img.height + ', ' +
|
||||
(file.length / 1024).toFixed(1) + ' KB (' +
|
||||
(100 * file.length / (img.width * img.height * channels)).toFixed(1) + '% of raw)');
|
||||
}
|
||||
|
||||
onmessage = (e) => {
|
||||
const m = e.data.pluginMessage;
|
||||
if (!m) return;
|
||||
if (m.type === 'mode') tab(m.mode === 'export' ? 'export' : 'import');
|
||||
if (m.type === 'selection')
|
||||
$('sel').textContent = m.names.length ? 'Selected: ' + m.names.join(', ') : 'Nothing selected';
|
||||
if (m.type === 'exported') exported(m.name, m.png).catch((err) => status(m.name + ': ' + err.message));
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue