mirror of
https://github.com/Nexoniarz/YAPF.git
synced 2026-11-02 20:44:27 +00:00
- yapf_encode() / yapf_free_buffer(): encode to memory; yapf_save uses it - yapf.c also compiles as C++ - `yapf compare`: size and decode speed of the original format vs YAPF, with a lossless check - bindings with tested examples: C++ (header-only), C# (P/Invoke), Rust (crate, builds the C code), Go (cgo), Java (FFM API, Java 22+), Zig (@cImport), LuaJIT (FFI), TypeScript types for the JS port; Python now encodes in memory - docs/LANGUAGES.md and examples/ with measured results - KDE Dolphin thumbnail plugin (KF6, CMake) - README: honest status for Photoshop / VS Code / Figma (not run in the apps yet) and macOS (cross-compiled only), platform table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1515 lines
59 KiB
C
1515 lines
59 KiB
C
/*
|
|
* yapf.c — Yet Another Picture Format
|
|
* Encoder and decoder implementation.
|
|
*
|
|
* Copyright 2026 Nexoniarz
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE)
|
|
# define _POSIX_C_SOURCE 200809L
|
|
#endif
|
|
#if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE)
|
|
# define _DARWIN_C_SOURCE /* sysconf(_SC_NPROCESSORS_ONLN) on macOS */
|
|
#endif
|
|
|
|
#include "yapf.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* ── Constants ───────────────────────────────────────────────────────── */
|
|
|
|
#define YAPF__MAGIC "YAPF"
|
|
#define YAPF__VERSION 1
|
|
#define YAPF__HDR_LEN 20
|
|
#define YAPF__MIP_ENTRY 8
|
|
#define YAPF__TILE_PX (YAPF_TILE_SIZE * YAPF_TILE_SIZE)
|
|
|
|
#define YAPF__GROUP 8 /* residuals per group: w bits each = w bytes */
|
|
#define YAPF__MAX_GROUPS (YAPF__TILE_PX / YAPF__GROUP)
|
|
#define YAPF__BAND 8 /* rows sharing one filter byte */
|
|
#define YAPF__STORED 0x80 /* tile header: raw pixels follow */
|
|
|
|
/* Control nibble → number of groups it covers.
|
|
* 0..8: one group of that width; 9..15: that many groups repeating the
|
|
* width of the last 0..8 nibble (0 at the start of a plane). */
|
|
static const uint8_t yapf__nib_groups[16] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 8, 16, 32, 64
|
|
};
|
|
#define YAPF__NIB_REPEAT 9
|
|
|
|
/* Row filters (one byte per band of 8 rows, per plane). L / A / D / AR
|
|
* are the left, above, above-left and above-right samples. Every filter
|
|
* depends on the previous sample through at most one add; MED and
|
|
* average-with-left predictors were measured and dropped (~1 % smaller,
|
|
* half the decode speed). */
|
|
enum {
|
|
YAPF__F_LEFT = 0, /* L */
|
|
YAPF__F_GRAD = 1, /* L + A - D */
|
|
YAPF__F_UPRIGHT = 2, /* AR */
|
|
YAPF__F_UPAVG = 3, /* (A + AR) >> 1 */
|
|
YAPF__F_UP = 16 /* 16 + k - 1: the sample k rows above, */
|
|
/* k = 1..8 (repeating textures, dither) */
|
|
};
|
|
#define YAPF__UP_MAX 8
|
|
#define YAPF__RING 16 /* decoder row ring, >= UP_MAX + 1, power of 2 */
|
|
|
|
static int yapf__filter_valid(uint8_t f) {
|
|
return f <= YAPF__F_UPAVG || (f >= YAPF__F_UP && f < YAPF__F_UP + YAPF__UP_MAX);
|
|
}
|
|
|
|
/* Filters the encoder tries, cheapest to decode first: the up-row filters
|
|
* vectorise, Left and Gradient carry a serial dependency. On equal cost
|
|
* the earlier one wins. */
|
|
static const uint8_t yapf__filters[] = {
|
|
YAPF__F_UP + 0, YAPF__F_UP + 1, YAPF__F_UP + 2, YAPF__F_UP + 3,
|
|
YAPF__F_UP + 4, YAPF__F_UP + 5, YAPF__F_UP + 6, YAPF__F_UP + 7,
|
|
YAPF__F_UPRIGHT, YAPF__F_UPAVG, YAPF__F_LEFT, YAPF__F_GRAD
|
|
};
|
|
|
|
static uint32_t yapf__r32le(const uint8_t *p) {
|
|
return (uint32_t)p[0]
|
|
| ((uint32_t)p[1] << 8)
|
|
| ((uint32_t)p[2] << 16)
|
|
| ((uint32_t)p[3] << 24);
|
|
}
|
|
|
|
static void yapf__w32le(uint8_t *p, uint32_t v) {
|
|
p[0] = (uint8_t)(v);
|
|
p[1] = (uint8_t)(v >> 8);
|
|
p[2] = (uint8_t)(v >> 16);
|
|
p[3] = (uint8_t)(v >> 24);
|
|
}
|
|
|
|
/* Little-endian 64-bit load; compilers turn this into a single load. */
|
|
static uint64_t yapf__r64le(const uint8_t *p) {
|
|
return (uint64_t)p[0]
|
|
| ((uint64_t)p[1] << 8)
|
|
| ((uint64_t)p[2] << 16)
|
|
| ((uint64_t)p[3] << 24)
|
|
| ((uint64_t)p[4] << 32)
|
|
| ((uint64_t)p[5] << 40)
|
|
| ((uint64_t)p[6] << 48)
|
|
| ((uint64_t)p[7] << 56);
|
|
}
|
|
|
|
/* ── Growable output buffer (encoder) ────────────────────────────────── */
|
|
|
|
typedef struct {
|
|
uint8_t *buf;
|
|
size_t len;
|
|
size_t cap;
|
|
} yapf__out;
|
|
|
|
static int yapf__reserve(yapf__out *o, size_t n) {
|
|
if (o->len + n <= o->cap) return 0;
|
|
size_t nc = o->cap ? o->cap : 4096;
|
|
while (nc < o->len + n) nc *= 2;
|
|
uint8_t *nb = (uint8_t *)realloc(o->buf, nc);
|
|
if (!nb) return -1;
|
|
o->buf = nb;
|
|
o->cap = nc;
|
|
return 0;
|
|
}
|
|
|
|
/* ── YCoCg-R, 8-bit modular form ─────────────────────────────────────── */
|
|
/*
|
|
* The usual YCoCg-R lifting steps, computed mod 256, with Co and Cg biased
|
|
* by 128 so neutral colours sit mid-range. Every lifting step adds a
|
|
* function of the other channel, so the transform stays exactly
|
|
* reversible while every plane remains one byte per sample.
|
|
*/
|
|
static void yapf__forward(uint8_t *y, uint8_t *co, uint8_t *cg,
|
|
uint8_t r, uint8_t g, uint8_t b) {
|
|
uint8_t o = (uint8_t)(r - b);
|
|
uint8_t t = (uint8_t)(b + ((int8_t)o >> 1));
|
|
uint8_t q = (uint8_t)(g - t);
|
|
*y = (uint8_t)(t + ((int8_t)q >> 1));
|
|
*co = (uint8_t)(o + 128);
|
|
*cg = (uint8_t)(q + 128);
|
|
}
|
|
|
|
/* ── Prediction ──────────────────────────────────────────────────────── */
|
|
/* Samples outside the tile (left of column 0, above row 0) read as 0. */
|
|
|
|
|
|
/* Residuals are folded so small magnitudes become small codes:
|
|
* 0, -1, 1, -2, 2, ... → 0, 1, 2, 3, 4, ... */
|
|
static uint8_t yapf__zz(uint8_t r) {
|
|
return (uint8_t)((r << 1) ^ (uint8_t)((int8_t)r >> 7));
|
|
}
|
|
|
|
static uint8_t yapf__unzz(uint8_t z) {
|
|
return (uint8_t)((z >> 1) ^ (uint8_t)-(z & 1));
|
|
}
|
|
|
|
/* Prediction for one row: R = the row, U = the row above, K = the row k
|
|
* rows above for YAPF__F_UP + k - 1 (rows outside the tile are all 0).
|
|
* Writes pred[0..tw). The encoder knows every sample, so this has no
|
|
* serial dependency. */
|
|
static void yapf__predict_row(uint8_t f, const uint8_t *R, const uint8_t *U,
|
|
const uint8_t *K, uint8_t *pred, uint32_t tw)
|
|
{
|
|
uint32_t x;
|
|
switch (f) {
|
|
case YAPF__F_LEFT:
|
|
pred[0] = 0;
|
|
for (x = 1; x < tw; x++) pred[x] = R[x - 1];
|
|
break;
|
|
case YAPF__F_GRAD:
|
|
pred[0] = U[0];
|
|
for (x = 1; x < tw; x++) pred[x] = (uint8_t)(R[x - 1] + U[x] - U[x - 1]);
|
|
break;
|
|
case YAPF__F_UPRIGHT:
|
|
for (x = 0; x + 1 < tw; x++) pred[x] = U[x + 1];
|
|
pred[tw - 1] = U[tw - 1];
|
|
break;
|
|
case YAPF__F_UPAVG:
|
|
for (x = 0; x + 1 < tw; x++) pred[x] = (uint8_t)((U[x] + U[x + 1]) >> 1);
|
|
pred[tw - 1] = U[tw - 1];
|
|
break;
|
|
default:
|
|
memcpy(pred, K, tw);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int yapf__width(uint8_t z) {
|
|
int w = 0;
|
|
while (z) { z >>= 1; w++; }
|
|
return w;
|
|
}
|
|
|
|
static const uint8_t yapf__zero_row[YAPF_TILE_SIZE + 1] = { 0 };
|
|
|
|
/* ── SIMD ────────────────────────────────────────────────────────────── */
|
|
/*
|
|
* On by default; define YAPF_NO_SIMD for plain C everywhere.
|
|
* x86 / x64 (GCC, Clang, MSVC, MinGW): SSSE3 kernels, chosen at run time
|
|
* with CPUID, so the same binary still runs on CPUs without SSSE3.
|
|
* ARM64 (GCC, Clang, MSVC): NEON kernels; NEON is always present.
|
|
* Every kernel produces exactly the same bytes as the scalar code. Row
|
|
* kernels work in whole 16-sample chunks: they may read and write up to
|
|
* the end of the 64 (+1) sample row buffers, never past them, and pixels
|
|
* of narrow edge tiles go through a scratch row before being copied out.
|
|
*
|
|
* Group unpack, both ISAs: lane i of 16 bits gathers the two bytes holding
|
|
* value i (table yapf__simd_shuf), shifts right by that value's bit offset
|
|
* and masks to w bits. SSSE3 has no per-lane shift, so it multiplies:
|
|
* (2u * 2^(15 - s)) >> 16 = u >> s; overflow of 2u only touches bits >= 8.
|
|
*/
|
|
#if !defined(YAPF_NO_SIMD) && \
|
|
(defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86))
|
|
# define YAPF__SIMD_X86 1
|
|
#elif !defined(YAPF_NO_SIMD) && (defined(__aarch64__) || defined(_M_ARM64))
|
|
# define YAPF__SIMD_NEON 1
|
|
#endif
|
|
|
|
#if defined(YAPF__SIMD_X86) || defined(YAPF__SIMD_NEON)
|
|
# define YAPF__SIMD 1
|
|
static const uint8_t yapf__simd_shuf[9][16] = {
|
|
{ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 },
|
|
{ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01 },
|
|
{ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02 },
|
|
{ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x02, 0x03, 0x02, 0x03 },
|
|
{ 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x03, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04 },
|
|
{ 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05 },
|
|
{ 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06 },
|
|
{ 0x00, 0x01, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07 },
|
|
{ 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x80 }
|
|
};
|
|
#else
|
|
# define YAPF__SIMD 0
|
|
#endif
|
|
|
|
#if defined(YAPF__SIMD_X86)
|
|
# if defined(_MSC_VER)
|
|
# include <intrin.h>
|
|
# else
|
|
# include <cpuid.h>
|
|
# endif
|
|
# include <tmmintrin.h>
|
|
# if defined(__GNUC__) || defined(__clang__)
|
|
# define YAPF__SIMD_FN static __attribute__((target("ssse3")))
|
|
# else
|
|
# define YAPF__SIMD_FN static /* MSVC allows intrinsics anywhere */
|
|
# endif
|
|
|
|
static const uint16_t yapf__simd_mul[9][8] = {
|
|
{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 },
|
|
{ 0x8000, 0x4000, 0x2000, 0x1000, 0x0800, 0x0400, 0x0200, 0x0100 },
|
|
{ 0x8000, 0x2000, 0x0800, 0x0200, 0x8000, 0x2000, 0x0800, 0x0200 },
|
|
{ 0x8000, 0x1000, 0x0200, 0x4000, 0x0800, 0x0100, 0x2000, 0x0400 },
|
|
{ 0x8000, 0x0800, 0x8000, 0x0800, 0x8000, 0x0800, 0x8000, 0x0800 },
|
|
{ 0x8000, 0x0400, 0x2000, 0x0100, 0x0800, 0x4000, 0x0200, 0x1000 },
|
|
{ 0x8000, 0x0200, 0x0800, 0x2000, 0x8000, 0x0200, 0x0800, 0x2000 },
|
|
{ 0x8000, 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 },
|
|
{ 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000 }
|
|
};
|
|
|
|
/* Shuffle masks: R, G, B rows of 16 → 48 interleaved bytes. */
|
|
static const uint8_t yapf__simd_rgb[3][3][16] = {
|
|
{
|
|
{ 0x00, 0x80, 0x80, 0x01, 0x80, 0x80, 0x02, 0x80, 0x80, 0x03, 0x80, 0x80, 0x04, 0x80, 0x80, 0x05 },
|
|
{ 0x80, 0x00, 0x80, 0x80, 0x01, 0x80, 0x80, 0x02, 0x80, 0x80, 0x03, 0x80, 0x80, 0x04, 0x80, 0x80 },
|
|
{ 0x80, 0x80, 0x00, 0x80, 0x80, 0x01, 0x80, 0x80, 0x02, 0x80, 0x80, 0x03, 0x80, 0x80, 0x04, 0x80 }
|
|
},
|
|
{
|
|
{ 0x80, 0x80, 0x06, 0x80, 0x80, 0x07, 0x80, 0x80, 0x08, 0x80, 0x80, 0x09, 0x80, 0x80, 0x0A, 0x80 },
|
|
{ 0x05, 0x80, 0x80, 0x06, 0x80, 0x80, 0x07, 0x80, 0x80, 0x08, 0x80, 0x80, 0x09, 0x80, 0x80, 0x0A },
|
|
{ 0x80, 0x05, 0x80, 0x80, 0x06, 0x80, 0x80, 0x07, 0x80, 0x80, 0x08, 0x80, 0x80, 0x09, 0x80, 0x80 }
|
|
},
|
|
{
|
|
{ 0x80, 0x0B, 0x80, 0x80, 0x0C, 0x80, 0x80, 0x0D, 0x80, 0x80, 0x0E, 0x80, 0x80, 0x0F, 0x80, 0x80 },
|
|
{ 0x80, 0x80, 0x0B, 0x80, 0x80, 0x0C, 0x80, 0x80, 0x0D, 0x80, 0x80, 0x0E, 0x80, 0x80, 0x0F, 0x80 },
|
|
{ 0x0A, 0x80, 0x80, 0x0B, 0x80, 0x80, 0x0C, 0x80, 0x80, 0x0D, 0x80, 0x80, 0x0E, 0x80, 0x80, 0x0F }
|
|
}
|
|
};
|
|
|
|
/* CPUID leaf 1, ECX bit 9 = SSSE3. Called once per load. */
|
|
static int yapf__simd_available(void) {
|
|
# if defined(_MSC_VER)
|
|
int r[4];
|
|
__cpuid(r, 1);
|
|
return (r[2] >> 9) & 1;
|
|
# else
|
|
unsigned a, b, c, d;
|
|
if (!__get_cpuid(1, &a, &b, &c, &d)) return 0;
|
|
return (int)((c >> 9) & 1u);
|
|
# endif
|
|
}
|
|
|
|
YAPF__SIMD_FN const uint8_t *yapf__simd_unpack(const uint8_t *p,
|
|
const uint8_t *end,
|
|
const uint8_t *gw, uint32_t ng,
|
|
uint8_t *z)
|
|
{
|
|
for (uint32_t g = 0; g < ng; g++) {
|
|
int w = gw[g];
|
|
__m128i x;
|
|
if ((size_t)(end - p) >= 8) {
|
|
x = _mm_loadl_epi64((const __m128i *)(const void *)p);
|
|
} else {
|
|
uint8_t tmp[8] = {0};
|
|
memcpy(tmp, p, (size_t)w);
|
|
x = _mm_loadl_epi64((const __m128i *)(const void *)tmp);
|
|
}
|
|
__m128i u = _mm_shuffle_epi8(x, _mm_loadu_si128(
|
|
(const __m128i *)(const void *)yapf__simd_shuf[w]));
|
|
u = _mm_add_epi16(u, u);
|
|
__m128i v = _mm_mulhi_epu16(u, _mm_loadu_si128(
|
|
(const __m128i *)(const void *)yapf__simd_mul[w]));
|
|
v = _mm_and_si128(v, _mm_set1_epi16((short)((1 << w) - 1)));
|
|
_mm_storel_epi64((__m128i *)(void *)(z + g * YAPF__GROUP),
|
|
_mm_packus_epi16(v, v));
|
|
p += w;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/* Left / Gradient for a full 64-sample row: the per-sample increments are
|
|
* independent (r, or r + A - D), so compute them 16 at a time and turn the
|
|
* serial chain into a prefix sum. */
|
|
YAPF__SIMD_FN void yapf__simd_serial_row(uint8_t f, uint8_t *R,
|
|
const uint8_t *U, const uint8_t *Z,
|
|
uint32_t tw)
|
|
{
|
|
const __m128i zero = _mm_setzero_si128();
|
|
__m128i carry = zero, uprev = zero;
|
|
for (uint32_t k = 0; k < tw; k += 16) {
|
|
__m128i zz = _mm_loadu_si128((const __m128i *)(const void *)(Z + k));
|
|
__m128i t = _mm_xor_si128(
|
|
_mm_and_si128(_mm_srli_epi16(zz, 1), _mm_set1_epi8(0x7F)),
|
|
_mm_sub_epi8(zero, _mm_and_si128(zz, _mm_set1_epi8(1))));
|
|
if (f == YAPF__F_GRAD) {
|
|
__m128i u = _mm_loadu_si128((const __m128i *)(const void *)(U + k));
|
|
t = _mm_add_epi8(t, _mm_sub_epi8(u, _mm_alignr_epi8(u, uprev, 15)));
|
|
uprev = u;
|
|
}
|
|
t = _mm_add_epi8(t, _mm_slli_si128(t, 1));
|
|
t = _mm_add_epi8(t, _mm_slli_si128(t, 2));
|
|
t = _mm_add_epi8(t, _mm_slli_si128(t, 4));
|
|
t = _mm_add_epi8(t, _mm_slli_si128(t, 8));
|
|
t = _mm_add_epi8(t, carry);
|
|
_mm_storeu_si128((__m128i *)(void *)(R + k), t);
|
|
carry = _mm_shuffle_epi8(t, _mm_set1_epi8(15));
|
|
}
|
|
}
|
|
|
|
/* Bytes as int8, arithmetic shift right by one: ((v ^ 0x80) >> 1) - 64. */
|
|
YAPF__SIMD_FN __m128i yapf__sse_half_s8(__m128i v) {
|
|
__m128i x = _mm_xor_si128(v, _mm_set1_epi8((char)0x80));
|
|
x = _mm_and_si128(_mm_srli_epi16(x, 1), _mm_set1_epi8(0x7F));
|
|
return _mm_sub_epi8(x, _mm_set1_epi8(64));
|
|
}
|
|
|
|
YAPF__SIMD_FN __m128i yapf__sse_unzz(__m128i z) {
|
|
return _mm_xor_si128(_mm_and_si128(_mm_srli_epi16(z, 1), _mm_set1_epi8(0x7F)),
|
|
_mm_sub_epi8(_mm_setzero_si128(), _mm_and_si128(z, _mm_set1_epi8(1))));
|
|
}
|
|
|
|
/* Vertical filters for a full 64-sample row: R = ((a + b) >> 1) + r. */
|
|
YAPF__SIMD_FN void yapf__simd_vertical_row(uint8_t *R, const uint8_t *A,
|
|
const uint8_t *B, const uint8_t *Z,
|
|
uint32_t tw)
|
|
{
|
|
for (uint32_t k = 0; k < tw; k += 16) {
|
|
__m128i a = _mm_loadu_si128((const __m128i *)(const void *)(A + k));
|
|
__m128i b = _mm_loadu_si128((const __m128i *)(const void *)(B + k));
|
|
__m128i avg = _mm_sub_epi8(_mm_avg_epu8(a, b), /* avg rounds up */
|
|
_mm_and_si128(_mm_xor_si128(a, b), _mm_set1_epi8(1)));
|
|
__m128i z = _mm_loadu_si128((const __m128i *)(const void *)(Z + k));
|
|
_mm_storeu_si128((__m128i *)(void *)(R + k), _mm_add_epi8(avg, yapf__sse_unzz(z)));
|
|
}
|
|
}
|
|
|
|
/* Planes of one 64-pixel row → interleaved output pixels. */
|
|
YAPF__SIMD_FN void yapf__simd_inverse_row(uint8_t *d, const uint8_t *P0,
|
|
const uint8_t *P1, const uint8_t *P2,
|
|
const uint8_t *P3, int ch, int xf)
|
|
{
|
|
const __m128i bias = _mm_set1_epi8((char)0x80);
|
|
for (int k = 0; k < 64; k += 16) {
|
|
__m128i p0 = _mm_loadu_si128((const __m128i *)(const void *)(P0 + k));
|
|
if (ch == 1) {
|
|
_mm_storeu_si128((__m128i *)(void *)(d + k), p0);
|
|
continue;
|
|
}
|
|
__m128i p1 = _mm_loadu_si128((const __m128i *)(const void *)(P1 + k));
|
|
if (ch == 2) {
|
|
_mm_storeu_si128((__m128i *)(void *)(d + 2 * k), _mm_unpacklo_epi8(p0, p1));
|
|
_mm_storeu_si128((__m128i *)(void *)(d + 2 * k + 16), _mm_unpackhi_epi8(p0, p1));
|
|
continue;
|
|
}
|
|
__m128i p2 = _mm_loadu_si128((const __m128i *)(const void *)(P2 + k));
|
|
__m128i r, g, b;
|
|
if (xf) { /* subtract-green */
|
|
g = p0;
|
|
r = _mm_add_epi8(_mm_sub_epi8(p1, bias), g);
|
|
b = _mm_add_epi8(_mm_sub_epi8(p2, bias), g);
|
|
} else { /* YCoCg-R */
|
|
__m128i co = _mm_sub_epi8(p1, bias), cg = _mm_sub_epi8(p2, bias);
|
|
__m128i t = _mm_sub_epi8(p0, yapf__sse_half_s8(cg));
|
|
b = _mm_sub_epi8(t, yapf__sse_half_s8(co));
|
|
r = _mm_add_epi8(b, co);
|
|
g = _mm_add_epi8(cg, t);
|
|
}
|
|
if (ch == 3) {
|
|
uint8_t *o = d + 3 * k;
|
|
for (int j = 0; j < 3; j++) {
|
|
const __m128i *m = (const __m128i *)(const void *)yapf__simd_rgb[j];
|
|
__m128i v = _mm_or_si128(
|
|
_mm_or_si128(_mm_shuffle_epi8(r, _mm_loadu_si128(m)),
|
|
_mm_shuffle_epi8(g, _mm_loadu_si128(m + 1))),
|
|
_mm_shuffle_epi8(b, _mm_loadu_si128(m + 2)));
|
|
_mm_storeu_si128((__m128i *)(void *)(o + 16 * j), v);
|
|
}
|
|
} else {
|
|
__m128i a = _mm_loadu_si128((const __m128i *)(const void *)(P3 + k));
|
|
__m128i rg0 = _mm_unpacklo_epi8(r, g), rg1 = _mm_unpackhi_epi8(r, g);
|
|
__m128i ba0 = _mm_unpacklo_epi8(b, a), ba1 = _mm_unpackhi_epi8(b, a);
|
|
uint8_t *o = d + 4 * k;
|
|
_mm_storeu_si128((__m128i *)(void *)(o), _mm_unpacklo_epi16(rg0, ba0));
|
|
_mm_storeu_si128((__m128i *)(void *)(o + 16), _mm_unpackhi_epi16(rg0, ba0));
|
|
_mm_storeu_si128((__m128i *)(void *)(o + 32), _mm_unpacklo_epi16(rg1, ba1));
|
|
_mm_storeu_si128((__m128i *)(void *)(o + 48), _mm_unpackhi_epi16(rg1, ba1));
|
|
}
|
|
}
|
|
}
|
|
|
|
#elif defined(YAPF__SIMD_NEON)
|
|
# include <arm_neon.h>
|
|
# define YAPF__SIMD_FN static
|
|
|
|
static const int16_t yapf__simd_nshift[9][8] = {
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 0, -1, -2, -3, -4, -5, -6, -7 },
|
|
{ 0, -2, -4, -6, 0, -2, -4, -6 },
|
|
{ 0, -3, -6, -1, -4, -7, -2, -5 },
|
|
{ 0, -4, 0, -4, 0, -4, 0, -4 },
|
|
{ 0, -5, -2, -7, -4, -1, -6, -3 },
|
|
{ 0, -6, -4, -2, 0, -6, -4, -2 },
|
|
{ 0, -7, -6, -5, -4, -3, -2, -1 },
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0 }
|
|
};
|
|
|
|
static int yapf__simd_available(void) { return 1; }
|
|
|
|
YAPF__SIMD_FN const uint8_t *yapf__simd_unpack(const uint8_t *p,
|
|
const uint8_t *end,
|
|
const uint8_t *gw, uint32_t ng,
|
|
uint8_t *z)
|
|
{
|
|
for (uint32_t g = 0; g < ng; g++) {
|
|
int w = gw[g];
|
|
uint8_t tmp[8] = {0};
|
|
const uint8_t *src = p;
|
|
if ((size_t)(end - p) < 8) { memcpy(tmp, p, (size_t)w); src = tmp; }
|
|
uint8x16_t x = vcombine_u8(vld1_u8(src), vdup_n_u8(0));
|
|
uint16x8_t u = vreinterpretq_u16_u8(vqtbl1q_u8(x, vld1q_u8(yapf__simd_shuf[w])));
|
|
uint16x8_t v = vandq_u16(vshlq_u16(u, vld1q_s16(yapf__simd_nshift[w])),
|
|
vdupq_n_u16((uint16_t)((1u << w) - 1)));
|
|
vst1_u8(z + g * YAPF__GROUP, vmovn_u16(v));
|
|
p += w;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
YAPF__SIMD_FN void yapf__simd_serial_row(uint8_t f, uint8_t *R,
|
|
const uint8_t *U, const uint8_t *Z,
|
|
uint32_t tw)
|
|
{
|
|
const uint8x16_t zero = vdupq_n_u8(0);
|
|
uint8x16_t carry = zero, uprev = zero;
|
|
for (uint32_t k = 0; k < tw; k += 16) {
|
|
uint8x16_t zz = vld1q_u8(Z + k);
|
|
uint8x16_t t = veorq_u8(vshrq_n_u8(zz, 1),
|
|
vsubq_u8(zero, vandq_u8(zz, vdupq_n_u8(1))));
|
|
if (f == YAPF__F_GRAD) {
|
|
uint8x16_t u = vld1q_u8(U + k);
|
|
t = vaddq_u8(t, vsubq_u8(u, vextq_u8(uprev, u, 15)));
|
|
uprev = u;
|
|
}
|
|
t = vaddq_u8(t, vextq_u8(zero, t, 15));
|
|
t = vaddq_u8(t, vextq_u8(zero, t, 14));
|
|
t = vaddq_u8(t, vextq_u8(zero, t, 12));
|
|
t = vaddq_u8(t, vextq_u8(zero, t, 8));
|
|
t = vaddq_u8(t, carry);
|
|
vst1q_u8(R + k, t);
|
|
carry = vdupq_laneq_u8(t, 15);
|
|
}
|
|
}
|
|
|
|
YAPF__SIMD_FN void yapf__simd_vertical_row(uint8_t *R, const uint8_t *A,
|
|
const uint8_t *B, const uint8_t *Z,
|
|
uint32_t tw)
|
|
{
|
|
for (uint32_t k = 0; k < tw; k += 16) {
|
|
uint8x16_t z = vld1q_u8(Z + k);
|
|
uint8x16_t r = veorq_u8(vshrq_n_u8(z, 1),
|
|
vsubq_u8(vdupq_n_u8(0), vandq_u8(z, vdupq_n_u8(1))));
|
|
vst1q_u8(R + k, vaddq_u8(vhaddq_u8(vld1q_u8(A + k), vld1q_u8(B + k)), r));
|
|
}
|
|
}
|
|
|
|
YAPF__SIMD_FN void yapf__simd_inverse_row(uint8_t *d, const uint8_t *P0,
|
|
const uint8_t *P1, const uint8_t *P2,
|
|
const uint8_t *P3, int ch, int xf)
|
|
{
|
|
const uint8x16_t bias = vdupq_n_u8(0x80);
|
|
for (int k = 0; k < 64; k += 16) {
|
|
uint8x16_t p0 = vld1q_u8(P0 + k);
|
|
if (ch == 1) { vst1q_u8(d + k, p0); continue; }
|
|
uint8x16_t p1 = vld1q_u8(P1 + k);
|
|
if (ch == 2) {
|
|
uint8x16x2_t v; v.val[0] = p0; v.val[1] = p1;
|
|
vst2q_u8(d + 2 * k, v);
|
|
continue;
|
|
}
|
|
uint8x16_t p2 = vld1q_u8(P2 + k), r, g, b;
|
|
if (xf) {
|
|
g = p0;
|
|
r = vaddq_u8(vsubq_u8(p1, bias), g);
|
|
b = vaddq_u8(vsubq_u8(p2, bias), g);
|
|
} else {
|
|
uint8x16_t co = vsubq_u8(p1, bias), cg = vsubq_u8(p2, bias);
|
|
uint8x16_t t = vsubq_u8(p0, vreinterpretq_u8_s8(vshrq_n_s8(vreinterpretq_s8_u8(cg), 1)));
|
|
b = vsubq_u8(t, vreinterpretq_u8_s8(vshrq_n_s8(vreinterpretq_s8_u8(co), 1)));
|
|
r = vaddq_u8(b, co);
|
|
g = vaddq_u8(cg, t);
|
|
}
|
|
if (ch == 3) {
|
|
uint8x16x3_t v; v.val[0] = r; v.val[1] = g; v.val[2] = b;
|
|
vst3q_u8(d + 3 * k, v);
|
|
} else {
|
|
uint8x16x4_t v; v.val[0] = r; v.val[1] = g; v.val[2] = b; v.val[3] = vld1q_u8(P3 + k);
|
|
vst4q_u8(d + 4 * k, v);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/* ── Plane coding ────────────────────────────────────────────────────── */
|
|
/*
|
|
* Plane payload (all byte aligned):
|
|
* filter[ceil(th / 8)] one YAPF__F_* byte per band of 8 rows
|
|
* control nibbles low nibble first; one per group of 8 folded
|
|
* residuals, in raster order:
|
|
* 0..8 width w of this group
|
|
* 9..15 2, 3, 4, 8, 16, 32 or 64 groups repeating
|
|
* the last 0..8 width (0 at plane start)
|
|
* an odd count is padded with a zero nibble
|
|
* group data w bytes per group: the 8 values packed
|
|
* little-endian, value i at bit i*w
|
|
* The last group of a plane is padded with zeros.
|
|
*/
|
|
|
|
/* Chooses a filter per band and writes the folded residuals to z (zero
|
|
* padded to a whole group) and the filters to filt. Returns the estimated
|
|
* coded size in 1/16 byte units. */
|
|
static uint32_t yapf__choose_filters(const uint8_t *P, uint32_t tw, uint32_t th,
|
|
uint8_t *z, uint8_t *filt)
|
|
{
|
|
uint32_t total = tw * th;
|
|
uint32_t nb = (th + YAPF__BAND - 1) / YAPF__BAND;
|
|
uint32_t sum = 0;
|
|
|
|
/* Per band, pick the filter whose residuals code smallest: group
|
|
* widths plus control nibbles, in 1/16 byte units (zero groups mostly
|
|
* end up in runs, so they are counted as cheap). Left and Gradient
|
|
* decode serially, so they must win by more than 4 %. The previous
|
|
* band's winner is tried first and the others stop as soon as they
|
|
* cannot win; ties go to the earlier filter in yapf__filters. */
|
|
size_t prev_fi = 0;
|
|
for (uint32_t b = 0; b < nb; b++) {
|
|
uint32_t y0 = b * YAPF__BAND;
|
|
uint32_t y1 = y0 + YAPF__BAND < th ? y0 + YAPF__BAND : th;
|
|
uint32_t n = (y1 - y0) * tw;
|
|
uint8_t *Z = z + y0 * tw;
|
|
uint8_t tmp[YAPF__BAND * YAPF_TILE_SIZE + YAPF__GROUP];
|
|
uint32_t best_cost = UINT32_MAX;
|
|
size_t best_fi = 0;
|
|
|
|
for (size_t t = 0; t < sizeof(yapf__filters); t++) {
|
|
size_t fi = t == 0 ? prev_fi : (t <= prev_fi ? t - 1 : t);
|
|
uint8_t f = yapf__filters[fi];
|
|
uint32_t k = f >= YAPF__F_UP ? (uint32_t)(f - YAPF__F_UP + 1) : 1;
|
|
int serial = f == YAPF__F_LEFT || f == YAPF__F_GRAD;
|
|
uint32_t cost = 0, done = 0;
|
|
int lost = 0;
|
|
|
|
for (uint32_t y = y0; y < y1 && !lost; y++) {
|
|
const uint8_t *R = P + y * tw;
|
|
const uint8_t *U = y ? R - tw : yapf__zero_row;
|
|
const uint8_t *K = y >= k ? R - k * tw : yapf__zero_row;
|
|
uint8_t *T = tmp + (y - y0) * tw;
|
|
uint8_t pred[YAPF_TILE_SIZE];
|
|
yapf__predict_row(f, R, U, K, pred, tw);
|
|
for (uint32_t x = 0; x < tw; x++)
|
|
T[x] = yapf__zz((uint8_t)(R[x] - pred[x]));
|
|
|
|
/* cost of the groups completed so far */
|
|
uint32_t end = y + 1 == y1 ? n : (y + 1 - y0) * tw;
|
|
if (y + 1 == y1) memset(tmp + n, 0, YAPF__GROUP);
|
|
for (; done + YAPF__GROUP <= end || (y + 1 == y1 && done < end);
|
|
done += YAPF__GROUP) {
|
|
uint8_t m = 0;
|
|
for (int i = 0; i < YAPF__GROUP; i++) m |= tmp[done + i];
|
|
int w = yapf__width(m);
|
|
cost += w ? (uint32_t)(16 * w + 8) : 2;
|
|
}
|
|
uint32_t c = serial ? cost + cost / 25 : cost;
|
|
if (c > best_cost || (c == best_cost && fi > best_fi)) lost = 1;
|
|
}
|
|
if (lost) continue;
|
|
if (serial) cost += cost / 25;
|
|
if (cost < best_cost || (cost == best_cost && fi < best_fi)) {
|
|
best_cost = cost;
|
|
best_fi = fi;
|
|
memcpy(Z, tmp, n);
|
|
}
|
|
}
|
|
prev_fi = best_fi;
|
|
filt[b] = yapf__filters[best_fi];
|
|
sum += best_cost;
|
|
}
|
|
memset(z + total, 0, YAPF__GROUP);
|
|
return sum;
|
|
}
|
|
|
|
/* Writes a plane payload from residuals and filters made by
|
|
* yapf__choose_filters. */
|
|
static int yapf__emit_plane(yapf__out *o, const uint8_t *z, const uint8_t *filt,
|
|
uint32_t tw, uint32_t th)
|
|
{
|
|
uint32_t total = tw * th;
|
|
uint32_t ng = (total + YAPF__GROUP - 1) / YAPF__GROUP;
|
|
uint32_t nb = (th + YAPF__BAND - 1) / YAPF__BAND;
|
|
uint8_t gw[YAPF__MAX_GROUPS];
|
|
uint8_t nib[YAPF__MAX_GROUPS];
|
|
uint32_t nn = 0;
|
|
|
|
/* worst case: filters + a nibble and 8 bytes per group */
|
|
if (yapf__reserve(o, nb + ng + (size_t)ng * 8) != 0) return -1;
|
|
memcpy(o->buf + o->len, filt, nb);
|
|
o->len += nb;
|
|
|
|
/* Group widths → control nibbles. A group may be stored wider than
|
|
* it needs if that lets a repeat code cover it; a small dynamic
|
|
* program over (group, current width) finds the cheapest sequence,
|
|
* counted in nibbles (1 per code, 2 per data byte). */
|
|
uint8_t need[YAPF__MAX_GROUPS];
|
|
for (uint32_t g = 0; g < ng; g++) {
|
|
uint8_t m = 0;
|
|
for (int i = 0; i < YAPF__GROUP; i++) m |= z[g * YAPF__GROUP + i];
|
|
need[g] = (uint8_t)yapf__width(m);
|
|
}
|
|
{
|
|
static const uint8_t lens[7] = { 2, 3, 4, 8, 16, 32, 64 };
|
|
uint32_t cost[YAPF__MAX_GROUPS + 1][9];
|
|
uint8_t pick[YAPF__MAX_GROUPS][9]; /* nibble code */
|
|
|
|
/* span[j][g] = widest need in groups [g, g + 2^j) */
|
|
static const int levels = 7;
|
|
uint8_t span[7][YAPF__MAX_GROUPS];
|
|
memcpy(span[0], need, ng);
|
|
for (int j = 1; j < levels; j++)
|
|
for (uint32_t g = 0; g + (1u << j) <= ng; g++) {
|
|
uint8_t a = span[j - 1][g], c = span[j - 1][g + (1u << (j - 1))];
|
|
span[j][g] = a > c ? a : c;
|
|
}
|
|
|
|
for (int pw = 0; pw <= 8; pw++) cost[ng][pw] = 0;
|
|
for (uint32_t g = ng; g-- > 0; ) {
|
|
/* Best literal: independent of the current width. */
|
|
uint32_t lc = UINT32_MAX;
|
|
uint8_t lk = 0;
|
|
for (int w = need[g]; w <= 8; w++) {
|
|
uint32_t c = 1 + 2u * (uint32_t)w + cost[g + 1][w];
|
|
if (c < lc) { lc = c; lk = (uint8_t)w; }
|
|
}
|
|
/* Widest need over each repeat length that fits, from the
|
|
* power-of-two max table (length 3 = 2 + one more). */
|
|
uint8_t mx[7];
|
|
int nl = 0;
|
|
for (; nl < 7 && g + lens[nl] <= ng; nl++) {
|
|
uint32_t L = lens[nl];
|
|
if (L == 3) {
|
|
uint8_t a = span[1][g], c = need[g + 2];
|
|
mx[nl] = a > c ? a : c;
|
|
} else {
|
|
int j = 0;
|
|
while ((1u << j) < L) j++;
|
|
mx[nl] = span[j][g];
|
|
}
|
|
}
|
|
for (int pw = 0; pw <= 8; pw++) {
|
|
uint32_t bc = lc;
|
|
uint8_t bk = lk;
|
|
for (int li = 0; li < nl && mx[li] <= pw; li++) {
|
|
uint32_t c = 1 + 2u * (uint32_t)pw * lens[li] + cost[g + lens[li]][pw];
|
|
if (c < bc) { bc = c; bk = (uint8_t)(YAPF__NIB_REPEAT + li); }
|
|
}
|
|
cost[g][pw] = bc;
|
|
pick[g][pw] = bk;
|
|
}
|
|
}
|
|
uint8_t pw = 0;
|
|
for (uint32_t g = 0; g < ng; ) {
|
|
uint8_t k = pick[g][pw];
|
|
nib[nn++] = k;
|
|
if (k < YAPF__NIB_REPEAT) {
|
|
pw = k;
|
|
gw[g++] = k;
|
|
} else {
|
|
for (uint32_t n = 0; n < yapf__nib_groups[k]; n++) gw[g++] = pw;
|
|
}
|
|
}
|
|
}
|
|
for (uint32_t k = 0; k < nn; k += 2)
|
|
o->buf[o->len++] = (uint8_t)(nib[k] | (k + 1 < nn ? nib[k + 1] << 4 : 0));
|
|
|
|
/* Packed group data: 8 values of w bits = w bytes */
|
|
for (uint32_t g = 0; g < ng; g++) {
|
|
int w = gw[g];
|
|
uint64_t acc = 0;
|
|
for (int i = 0; i < YAPF__GROUP; i++)
|
|
acc |= (uint64_t)z[g * YAPF__GROUP + i] << (i * w);
|
|
for (int k = 0; k < w; k++) o->buf[o->len++] = (uint8_t)(acc >> (8 * k));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Parses one plane payload from [p, end) and unpacks its folded residuals
|
|
* into z; *filt receives the band filters. Returns the end of the payload
|
|
* or NULL on corrupt input. All sizes are validated before the unpack. */
|
|
static const uint8_t *yapf__unpack_plane(const uint8_t *p, const uint8_t *end,
|
|
uint8_t *z, const uint8_t **filt_out,
|
|
uint32_t tw, uint32_t th, int simd)
|
|
{
|
|
uint32_t total = tw * th;
|
|
uint32_t ng = (total + YAPF__GROUP - 1) / YAPF__GROUP;
|
|
uint32_t nb = (th + YAPF__BAND - 1) / YAPF__BAND;
|
|
uint8_t gw[YAPF__MAX_GROUPS + 64]; /* a final run may overshoot */
|
|
|
|
if ((size_t)(end - p) < nb) return NULL;
|
|
const uint8_t *filt = p;
|
|
*filt_out = filt;
|
|
p += nb;
|
|
for (uint32_t b = 0; b < nb; b++)
|
|
if (!yapf__filter_valid(filt[b])) return NULL;
|
|
|
|
/* Control nibbles → group widths, and the total data size. Every
|
|
* nibble stores its width into the next 8 entries with one 64-bit
|
|
* write (the array has room); only runs longer than 8 groups, which
|
|
* are rare, take a branch to fill the rest. */
|
|
size_t data = 0;
|
|
uint32_t g = 0;
|
|
int hi = 0;
|
|
uint8_t w = 0;
|
|
while (g < ng) {
|
|
if (p >= end) return NULL;
|
|
uint8_t c = (uint8_t)((*p >> (hi * 4)) & 15);
|
|
uint32_t n = yapf__nib_groups[c];
|
|
p += hi;
|
|
hi ^= 1;
|
|
w = c < YAPF__NIB_REPEAT ? c : w;
|
|
{
|
|
uint64_t w8 = (uint64_t)w * 0x0101010101010101ull;
|
|
memcpy(gw + g, &w8, 8);
|
|
if (n > 8) memset(gw + g + 8, w, 56);
|
|
}
|
|
data += (size_t)w * n;
|
|
g += n;
|
|
}
|
|
if (g != ng) return NULL; /* run past the last group */
|
|
if (hi) { /* padding nibble must be 0 */
|
|
if (*p >> 4) return NULL;
|
|
p++;
|
|
}
|
|
if ((size_t)(end - p) < data) return NULL;
|
|
|
|
#if YAPF__SIMD
|
|
if (simd) return yapf__simd_unpack(p, end, gw, ng, z);
|
|
#else
|
|
(void)simd;
|
|
#endif
|
|
|
|
/* Unpack. Branch-free per group: one 64-bit load, 8 shifts. */
|
|
for (g = 0; g < ng; g++) {
|
|
int w = gw[g];
|
|
uint64_t x;
|
|
if ((size_t)(end - p) >= 8) {
|
|
x = yapf__r64le(p);
|
|
} else {
|
|
uint8_t tmp[8] = {0};
|
|
memcpy(tmp, p, (size_t)w);
|
|
x = yapf__r64le(tmp);
|
|
}
|
|
uint64_t m = (1u << w) - 1;
|
|
uint8_t *o = z + g * YAPF__GROUP;
|
|
for (int i = 0; i < YAPF__GROUP; i++)
|
|
o[i] = (uint8_t)((x >> (i * w)) & m);
|
|
p += w;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
/* Undo filter f for one row. R = reconstructed row, U = row above,
|
|
* K = row k above (for YAPF__F_UP + k - 1), Z = folded residuals. Rows
|
|
* carry one extra sample, R[tw] = R[tw - 1], so "above-right" needs no
|
|
* edge case. All vertical filters share one loop, pred = (a + b) >> 1:
|
|
* Up k: a = b = K Up-right: a = b = U + 1
|
|
* Up-avg: a = U, b = U + 1
|
|
* which keeps the per-row dispatch down to three targets. */
|
|
static void yapf__unfilter_row(uint8_t f, uint8_t *R, const uint8_t *U,
|
|
const uint8_t *K, const uint8_t *Z, uint32_t tw)
|
|
{
|
|
uint32_t x;
|
|
if (f == YAPF__F_LEFT) {
|
|
uint8_t L = 0;
|
|
for (x = 0; x < tw; x++) R[x] = L = (uint8_t)(L + yapf__unzz(Z[x]));
|
|
} else if (f == YAPF__F_GRAD) {
|
|
/* L + (A - D + r): only one add depends on the previous sample. */
|
|
uint8_t L = (uint8_t)(U[0] + yapf__unzz(Z[0]));
|
|
R[0] = L;
|
|
for (x = 1; x < tw; x++) {
|
|
uint8_t t = (uint8_t)(U[x] - U[x - 1] + yapf__unzz(Z[x]));
|
|
R[x] = L = (uint8_t)(L + t);
|
|
}
|
|
} else {
|
|
const uint8_t *a = f >= YAPF__F_UP ? K : f == YAPF__F_UPAVG ? U : U + 1;
|
|
const uint8_t *b = f >= YAPF__F_UP ? K : U + 1;
|
|
for (x = 0; x < tw; x++)
|
|
R[x] = (uint8_t)(((a[x] + b[x]) >> 1) + yapf__unzz(Z[x]));
|
|
}
|
|
R[tw] = R[tw - 1];
|
|
}
|
|
|
|
/* ── Tile encode / decode ────────────────────────────────────────────── */
|
|
/*
|
|
* Tile layout (byte aligned):
|
|
* header byte: 0x80 stored: tw*th*ch raw interleaved bytes follow
|
|
* bits 0..3 coded: bit c set = plane c is constant
|
|
* bit 4 coded RGB(A): planes are G, R-G, B-G (+ A)
|
|
* instead of Y, Co, Cg (+ A)
|
|
* coded: one value byte per constant plane, in plane order,
|
|
* then a plane payload per non-constant plane, in plane order
|
|
* The encoder tries both colour transforms and keeps the smaller tile, and
|
|
* stores the tile raw whenever coding would not make it smaller.
|
|
*/
|
|
#define YAPF__SUBGREEN 0x10
|
|
|
|
/* Encoder scratch, heap allocated once per level (too big for small
|
|
* thread stacks). */
|
|
typedef struct {
|
|
uint8_t plane[2][4][YAPF__TILE_PX];
|
|
uint8_t z[2][4][YAPF__TILE_PX + YAPF__GROUP];
|
|
uint8_t filt[2][4][YAPF_TILE_SIZE / YAPF__BAND];
|
|
} yapf__scratch;
|
|
|
|
static int yapf__encode_tile(
|
|
yapf__out *o, yapf__scratch *sc,
|
|
const uint8_t *pixels, uint32_t img_w,
|
|
uint32_t tx, uint32_t ty, uint32_t tw, uint32_t th,
|
|
int ch)
|
|
{
|
|
uint32_t total = tw * th;
|
|
size_t start = o->len;
|
|
size_t raw = (size_t)total * ch;
|
|
int tries = ch >= 3 ? 2 : 1;
|
|
uint8_t mask[2] = { 0, 0 };
|
|
uint32_t est[2] = { 0, 0 };
|
|
|
|
/* For each colour transform: planes, constant mask, filters, and an
|
|
* estimate of the coded size. Only the cheaper one is written. */
|
|
for (int t = 0; t < tries; t++) {
|
|
uint8_t (*plane)[YAPF__TILE_PX] = sc->plane[t];
|
|
for (uint32_t y = 0; y < th; y++) {
|
|
const uint8_t *row = pixels + ((size_t)(ty + y) * img_w + tx) * (size_t)ch;
|
|
for (uint32_t x = 0; x < tw; x++) {
|
|
const uint8_t *s = row + (size_t)x * ch;
|
|
uint32_t i = y * tw + x;
|
|
if (ch < 3) {
|
|
for (int c = 0; c < ch; c++) plane[c][i] = s[c];
|
|
continue;
|
|
}
|
|
if (t == 0) {
|
|
yapf__forward(&plane[0][i], &plane[1][i], &plane[2][i],
|
|
s[0], s[1], s[2]);
|
|
} else {
|
|
plane[0][i] = s[1];
|
|
plane[1][i] = (uint8_t)(s[0] - s[1] + 128);
|
|
plane[2][i] = (uint8_t)(s[2] - s[1] + 128);
|
|
}
|
|
if (ch == 4) plane[3][i] = s[3];
|
|
}
|
|
}
|
|
for (int c = 0; c < ch; c++) {
|
|
uint32_t i = 1;
|
|
while (i < total && plane[c][i] == plane[c][0]) i++;
|
|
if (i == total) { mask[t] |= (uint8_t)(1u << c); est[t] += 16; continue; }
|
|
est[t] += yapf__choose_filters(plane[c], tw, th, sc->z[t][c], sc->filt[t][c]);
|
|
}
|
|
}
|
|
int t = (tries == 2 && est[1] < est[0]) ? 1 : 0;
|
|
|
|
if (yapf__reserve(o, 1 + 4) != 0) return -1;
|
|
o->buf[o->len++] = (uint8_t)(mask[t] | (t ? YAPF__SUBGREEN : 0));
|
|
for (int c = 0; c < ch; c++)
|
|
if (mask[t] & (1u << c)) o->buf[o->len++] = sc->plane[t][c][0];
|
|
for (int c = 0; c < ch; c++)
|
|
if (!(mask[t] & (1u << c)) &&
|
|
yapf__emit_plane(o, sc->z[t][c], sc->filt[t][c], tw, th) != 0) return -1;
|
|
|
|
/* Fall back to a stored tile if coding did not pay off. */
|
|
if (o->len - start > raw + 1) {
|
|
o->len = start;
|
|
if (yapf__reserve(o, raw + 1) != 0) return -1;
|
|
o->buf[o->len++] = YAPF__STORED;
|
|
for (uint32_t y = 0; y < th; y++) {
|
|
const uint8_t *row = pixels + ((size_t)(ty + y) * img_w + tx) * (size_t)ch;
|
|
memcpy(o->buf + o->len, row, (size_t)tw * ch);
|
|
o->len += (size_t)tw * ch;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int yapf__decode_tile(
|
|
const uint8_t *data, size_t len,
|
|
uint8_t *pixels, uint32_t img_w,
|
|
uint32_t tx, uint32_t ty, uint32_t tw, uint32_t th,
|
|
int ch, int simd)
|
|
{
|
|
uint8_t rows[4][YAPF__RING][YAPF_TILE_SIZE + 1]; /* last rows, ring */
|
|
uint8_t z[4][YAPF__TILE_PX + YAPF__GROUP];
|
|
const uint8_t *filt[4] = { NULL, NULL, NULL, NULL };
|
|
size_t rowb = (size_t)tw * ch;
|
|
const uint8_t *p = data, *end = data + len;
|
|
|
|
if (len == 0) return -1;
|
|
uint8_t hdr = *p++;
|
|
|
|
if (hdr == YAPF__STORED) {
|
|
if (len != 1 + rowb * th) return -1;
|
|
for (uint32_t y = 0; y < th; y++)
|
|
memcpy(pixels + ((size_t)(ty + y) * img_w + tx) * (size_t)ch,
|
|
p + rowb * y, rowb);
|
|
return 0;
|
|
}
|
|
if (hdr & 0xE0) return -1; /* reserved bits set */
|
|
uint8_t xf = hdr & YAPF__SUBGREEN;
|
|
hdr &= 0x0F;
|
|
if ((hdr >> ch) || (xf && ch < 3)) return -1; /* unknown bits set */
|
|
|
|
for (int c = 0; c < ch; c++) {
|
|
if (!(hdr & (1u << c))) continue;
|
|
if (p >= end) return -1;
|
|
memset(rows[c], *p++, sizeof(rows[c]));
|
|
}
|
|
for (int c = 0; c < ch; c++) {
|
|
if (hdr & (1u << c)) continue;
|
|
p = yapf__unpack_plane(p, end, z[c], &filt[c], tw, th, simd);
|
|
if (!p) return -1;
|
|
/* SIMD row kernels read whole 16-sample chunks; keep the unused
|
|
* tail of narrow tiles defined. */
|
|
if (tw != YAPF_TILE_SIZE) {
|
|
size_t used = ((size_t)tw * th + YAPF__GROUP - 1) / YAPF__GROUP * YAPF__GROUP;
|
|
memset(z[c] + used, 0, sizeof(z[c]) - used);
|
|
}
|
|
}
|
|
if (p != end) return -1;
|
|
|
|
/* Row by row: undo the filters of every plane (independent dependency
|
|
* chains the CPU can overlap), then the colour transform while the
|
|
* row is still in L1. */
|
|
for (uint32_t y = 0; y < th; y++) {
|
|
for (int c = 0; c < ch; c++) {
|
|
if (!filt[c]) continue;
|
|
uint8_t f = filt[c][y / YAPF__BAND];
|
|
uint32_t k = f >= YAPF__F_UP ? (uint32_t)(f - YAPF__F_UP + 1) : 1;
|
|
uint8_t *R = rows[c][y % YAPF__RING];
|
|
const uint8_t *U = y ? rows[c][(y - 1) % YAPF__RING] : yapf__zero_row;
|
|
const uint8_t *K = y >= k ? rows[c][(y - k) % YAPF__RING] : yapf__zero_row;
|
|
const uint8_t *Z = z[c] + y * tw;
|
|
#if YAPF__SIMD
|
|
if (simd) {
|
|
if (f == YAPF__F_LEFT || f == YAPF__F_GRAD)
|
|
yapf__simd_serial_row(f, R, U, Z, tw);
|
|
else if (f >= YAPF__F_UP)
|
|
yapf__simd_vertical_row(R, K, K, Z, tw);
|
|
else if (f == YAPF__F_UPAVG)
|
|
yapf__simd_vertical_row(R, U, U + 1, Z, tw);
|
|
else
|
|
yapf__simd_vertical_row(R, U + 1, U + 1, Z, tw);
|
|
R[tw] = R[tw - 1];
|
|
continue;
|
|
}
|
|
#endif
|
|
yapf__unfilter_row(f, R, U, K, Z, tw);
|
|
}
|
|
|
|
uint8_t *d = pixels + ((size_t)(ty + y) * img_w + tx) * (size_t)ch;
|
|
const uint8_t *P0 = rows[0][y % YAPF__RING], *P1 = rows[1][y % YAPF__RING];
|
|
const uint8_t *P2 = rows[2][y % YAPF__RING], *P3 = rows[3][y % YAPF__RING];
|
|
#if YAPF__SIMD
|
|
if (simd) {
|
|
if (tw == YAPF_TILE_SIZE) {
|
|
yapf__simd_inverse_row(d, P0, P1, P2, P3, ch, xf);
|
|
} else {
|
|
uint8_t tmp[YAPF_TILE_SIZE * 4];
|
|
yapf__simd_inverse_row(tmp, P0, P1, P2, P3, ch, xf);
|
|
memcpy(d, tmp, (size_t)tw * ch);
|
|
}
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
#define YAPF__INVERSE(N, ALPHA) \
|
|
for (uint32_t x = 0; x < tw; x++, d += N) { \
|
|
uint8_t co = (uint8_t)(P1[x] - 128), cg = (uint8_t)(P2[x] - 128); \
|
|
uint8_t t = (uint8_t)(P0[x] - ((int8_t)cg >> 1)); \
|
|
uint8_t b = (uint8_t)(t - ((int8_t)co >> 1)); \
|
|
d[0] = (uint8_t)(b + co); \
|
|
d[1] = (uint8_t)(cg + t); \
|
|
d[2] = b; \
|
|
ALPHA; \
|
|
}
|
|
#define YAPF__INVERSE_SG(N, ALPHA) \
|
|
for (uint32_t x = 0; x < tw; x++, d += N) { \
|
|
uint8_t g = P0[x]; \
|
|
d[0] = (uint8_t)(P1[x] + g - 128); \
|
|
d[1] = g; \
|
|
d[2] = (uint8_t)(P2[x] + g - 128); \
|
|
ALPHA; \
|
|
}
|
|
if (ch == 3) {
|
|
/* Planes → separate R, G, B rows (vectorises), then interleave. */
|
|
uint8_t rgb[3][YAPF_TILE_SIZE];
|
|
if (xf) {
|
|
for (uint32_t x = 0; x < tw; x++) {
|
|
uint8_t g = P0[x];
|
|
rgb[0][x] = (uint8_t)(P1[x] + g - 128);
|
|
rgb[1][x] = g;
|
|
rgb[2][x] = (uint8_t)(P2[x] + g - 128);
|
|
}
|
|
} else {
|
|
for (uint32_t x = 0; x < tw; x++) {
|
|
uint8_t co = (uint8_t)(P1[x] - 128), cg = (uint8_t)(P2[x] - 128);
|
|
uint8_t t = (uint8_t)(P0[x] - ((int8_t)cg >> 1));
|
|
uint8_t b = (uint8_t)(t - ((int8_t)co >> 1));
|
|
rgb[0][x] = (uint8_t)(b + co);
|
|
rgb[1][x] = (uint8_t)(cg + t);
|
|
rgb[2][x] = b;
|
|
}
|
|
}
|
|
d = pixels + ((size_t)(ty + y) * img_w + tx) * 3;
|
|
for (uint32_t x = 0; x < tw; x++, d += 3) {
|
|
d[0] = rgb[0][x]; d[1] = rgb[1][x]; d[2] = rgb[2][x];
|
|
}
|
|
} else if (xf) {
|
|
YAPF__INVERSE_SG(4, d[3] = P3[x])
|
|
} else if (ch == 4) {
|
|
YAPF__INVERSE(4, d[3] = P3[x])
|
|
} else if (ch == 2) {
|
|
for (uint32_t x = 0; x < tw; x++, d += 2) { d[0] = P0[x]; d[1] = P1[x]; }
|
|
} else {
|
|
memcpy(d, P0, tw);
|
|
}
|
|
#undef YAPF__INVERSE
|
|
#undef YAPF__INVERSE_SG
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ── Level encode / decode ───────────────────────────────────────────── */
|
|
/*
|
|
* Level layout:
|
|
* uint32 tile_size[tile_count] byte size of each tile, row-major order
|
|
* tile data, back to back
|
|
* Tiles are independent, so they can be decoded in any order or in
|
|
* parallel, and a sub-rectangle can be decoded without the rest.
|
|
*/
|
|
|
|
static uint32_t yapf__tiles(uint32_t n) {
|
|
return (n + YAPF_TILE_SIZE - 1) / YAPF_TILE_SIZE;
|
|
}
|
|
|
|
static int yapf__encode_level(
|
|
yapf__out *o, const uint8_t *pixels, uint32_t mw, uint32_t mh, int ch)
|
|
{
|
|
uint32_t tcx = yapf__tiles(mw), tcy = yapf__tiles(mh);
|
|
size_t tcount = (size_t)tcx * tcy;
|
|
|
|
/* Each level has its own buffer, so the table starts at offset 0.
|
|
* Reserve the size table, fill it after each tile is written. */
|
|
if (yapf__reserve(o, tcount * 4) != 0) return -1;
|
|
memset(o->buf, 0, tcount * 4);
|
|
o->len = tcount * 4;
|
|
|
|
yapf__scratch *sc = (yapf__scratch *)malloc(sizeof(yapf__scratch));
|
|
if (!sc) return -1;
|
|
|
|
size_t t = 0;
|
|
int rc = 0;
|
|
for (uint32_t ty = 0; ty < tcy && rc == 0; ty++) {
|
|
for (uint32_t tx = 0; tx < tcx && rc == 0; tx++, t++) {
|
|
uint32_t x0 = tx * YAPF_TILE_SIZE, y0 = ty * YAPF_TILE_SIZE;
|
|
uint32_t tw = mw - x0 < YAPF_TILE_SIZE ? mw - x0 : YAPF_TILE_SIZE;
|
|
uint32_t th = mh - y0 < YAPF_TILE_SIZE ? mh - y0 : YAPF_TILE_SIZE;
|
|
size_t start = o->len;
|
|
rc = yapf__encode_tile(o, sc, pixels, mw, x0, y0, tw, th, ch);
|
|
if (rc == 0) yapf__w32le(o->buf + t * 4, (uint32_t)(o->len - start));
|
|
}
|
|
}
|
|
free(sc);
|
|
return rc;
|
|
}
|
|
|
|
/* A level whose tile table has been validated; tiles can then be decoded
|
|
* in any order, from any thread. */
|
|
typedef struct {
|
|
const uint8_t *data;
|
|
size_t *offs; /* tcount + 1 byte offsets into data */
|
|
uint8_t *pixels;
|
|
uint32_t mw, mh, tcx, tcy;
|
|
int ch;
|
|
int simd; /* SIMD kernels usable on this CPU */
|
|
} yapf__level;
|
|
|
|
static int yapf__level_init(yapf__level *lv, const uint8_t *data, size_t len,
|
|
uint8_t *pixels, uint32_t mw, uint32_t mh, int ch)
|
|
{
|
|
lv->data = data;
|
|
lv->pixels = pixels;
|
|
lv->mw = mw;
|
|
lv->mh = mh;
|
|
lv->tcx = yapf__tiles(mw);
|
|
lv->tcy = yapf__tiles(mh);
|
|
lv->ch = ch;
|
|
|
|
size_t tcount = (size_t)lv->tcx * lv->tcy;
|
|
if (len / 4 < tcount) return -1;
|
|
lv->offs = (size_t *)malloc((tcount + 1) * sizeof(size_t));
|
|
if (!lv->offs) return -1;
|
|
|
|
size_t pos = tcount * 4;
|
|
for (size_t t = 0; t < tcount; t++) {
|
|
uint32_t sz = yapf__r32le(data + t * 4);
|
|
if (sz > len - pos) return -1;
|
|
lv->offs[t] = pos;
|
|
pos += sz;
|
|
}
|
|
lv->offs[tcount] = pos;
|
|
return 0;
|
|
}
|
|
|
|
/* Decode tile rows first_row, first_row + step, ... */
|
|
static int yapf__level_decode_rows(const yapf__level *lv,
|
|
uint32_t first_row, uint32_t step)
|
|
{
|
|
for (uint32_t ty = first_row; ty < lv->tcy; ty += step) {
|
|
for (uint32_t tx = 0; tx < lv->tcx; tx++) {
|
|
size_t t = (size_t)ty * lv->tcx + tx;
|
|
uint32_t x0 = tx * YAPF_TILE_SIZE, y0 = ty * YAPF_TILE_SIZE;
|
|
uint32_t tw = lv->mw - x0 < YAPF_TILE_SIZE ? lv->mw - x0 : YAPF_TILE_SIZE;
|
|
uint32_t th = lv->mh - y0 < YAPF_TILE_SIZE ? lv->mh - y0 : YAPF_TILE_SIZE;
|
|
if (yapf__decode_tile(lv->data + lv->offs[t],
|
|
lv->offs[t + 1] - lv->offs[t],
|
|
lv->pixels, lv->mw, x0, y0, tw, th, lv->ch,
|
|
lv->simd) != 0)
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ── Threads ─────────────────────────────────────────────────────────── */
|
|
/*
|
|
* Worker t of n decodes tile rows t, t + n, t + 2n, ... of every level.
|
|
* Define YAPF_NO_THREADS to build without any threading support.
|
|
*/
|
|
|
|
typedef struct {
|
|
const yapf__level *levels;
|
|
int nlevels;
|
|
uint32_t index, count;
|
|
int rc;
|
|
} yapf__job;
|
|
|
|
static void yapf__job_run(yapf__job *j) {
|
|
j->rc = 0;
|
|
for (int m = 0; m < j->nlevels && j->rc == 0; m++)
|
|
j->rc = yapf__level_decode_rows(&j->levels[m], j->index, j->count);
|
|
}
|
|
|
|
#if !defined(YAPF_NO_THREADS) && defined(_WIN32)
|
|
# include <windows.h>
|
|
# define YAPF__THREADS 1
|
|
typedef HANDLE yapf__thread;
|
|
static DWORD WINAPI yapf__thread_main(LPVOID arg) {
|
|
yapf__job_run((yapf__job *)arg);
|
|
return 0;
|
|
}
|
|
static int yapf__thread_start(yapf__thread *t, yapf__job *j) {
|
|
*t = CreateThread(NULL, 0, yapf__thread_main, j, 0, NULL);
|
|
return *t ? 0 : -1;
|
|
}
|
|
static void yapf__thread_join(yapf__thread t) {
|
|
WaitForSingleObject(t, INFINITE);
|
|
CloseHandle(t);
|
|
}
|
|
static int yapf__cpu_count(void) {
|
|
SYSTEM_INFO si;
|
|
GetSystemInfo(&si);
|
|
return (int)si.dwNumberOfProcessors;
|
|
}
|
|
#elif !defined(YAPF_NO_THREADS) && (defined(__unix__) || defined(__APPLE__))
|
|
# include <pthread.h>
|
|
# include <unistd.h>
|
|
# define YAPF__THREADS 1
|
|
typedef pthread_t yapf__thread;
|
|
static void *yapf__thread_main(void *arg) {
|
|
yapf__job_run((yapf__job *)arg);
|
|
return NULL;
|
|
}
|
|
static int yapf__thread_start(yapf__thread *t, yapf__job *j) {
|
|
return pthread_create(t, NULL, yapf__thread_main, j) == 0 ? 0 : -1;
|
|
}
|
|
static void yapf__thread_join(yapf__thread t) {
|
|
pthread_join(t, NULL);
|
|
}
|
|
static int yapf__cpu_count(void) {
|
|
long n = sysconf(_SC_NPROCESSORS_ONLN);
|
|
return n > 0 ? (int)n : 1;
|
|
}
|
|
#else
|
|
# define YAPF__THREADS 0
|
|
#endif
|
|
|
|
#define YAPF__MAX_THREADS 64
|
|
#define YAPF__TILES_PER_THREAD 32
|
|
|
|
/* Decode all levels with up to `threads` threads (0 = one per core). */
|
|
static int yapf__decode_levels(const yapf__level *levels, int nlevels,
|
|
int threads)
|
|
{
|
|
yapf__job jobs[YAPF__MAX_THREADS];
|
|
int n = 1;
|
|
|
|
#if YAPF__THREADS
|
|
yapf__thread th[YAPF__MAX_THREADS];
|
|
n = threads > 0 ? threads : yapf__cpu_count();
|
|
if (n > YAPF__MAX_THREADS) n = YAPF__MAX_THREADS;
|
|
if ((uint32_t)n > levels[0].tcy) n = (int)levels[0].tcy;
|
|
/* Starting a thread costs about as much as decoding a few tiles, so
|
|
* give each one at least YAPF__TILES_PER_THREAD tiles of work. */
|
|
{
|
|
size_t tiles = 0;
|
|
for (int m = 0; m < nlevels; m++) tiles += (size_t)levels[m].tcx * levels[m].tcy;
|
|
if ((size_t)n > tiles / YAPF__TILES_PER_THREAD)
|
|
n = (int)(tiles / YAPF__TILES_PER_THREAD);
|
|
}
|
|
if (n < 1) n = 1;
|
|
#else
|
|
(void)threads;
|
|
#endif
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
jobs[i].levels = levels;
|
|
jobs[i].nlevels = nlevels;
|
|
jobs[i].index = (uint32_t)i;
|
|
jobs[i].count = (uint32_t)n;
|
|
}
|
|
|
|
#if YAPF__THREADS
|
|
int started = 1;
|
|
for (; started < n; started++)
|
|
if (yapf__thread_start(&th[started], &jobs[started]) != 0) break;
|
|
/* If a thread failed to start, the calling thread takes its rows. */
|
|
yapf__job_run(&jobs[0]);
|
|
for (int i = started; i < n; i++) yapf__job_run(&jobs[i]);
|
|
for (int i = 1; i < started; i++) yapf__thread_join(th[i]);
|
|
#else
|
|
yapf__job_run(&jobs[0]);
|
|
#endif
|
|
|
|
for (int i = 0; i < n; i++)
|
|
if (jobs[i].rc != 0) return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* yapf_load_memory / yapf_load
|
|
* ======================================================================== */
|
|
|
|
static yapf_image_t *yapf__load(const uint8_t *buf, size_t size, int threads) {
|
|
if (!buf || size < YAPF__HDR_LEN) return NULL;
|
|
if (memcmp(buf, YAPF__MAGIC, 4) != 0) return NULL;
|
|
|
|
uint8_t version = buf[4];
|
|
uint8_t channels = buf[5];
|
|
uint8_t mip_levels = buf[7];
|
|
uint32_t base_w = yapf__r32le(buf + 12);
|
|
uint32_t base_h = yapf__r32le(buf + 16);
|
|
|
|
if (version != YAPF__VERSION) return NULL;
|
|
if (channels < 1 || channels > 4) return NULL;
|
|
if (mip_levels < 1 || mip_levels > YAPF_MAX_MIPS) return NULL;
|
|
if (base_w == 0 || base_w > YAPF_MAX_DIM) return NULL;
|
|
if (base_h == 0 || base_h > YAPF_MAX_DIM) return NULL;
|
|
|
|
size_t idx_end = YAPF__HDR_LEN + (size_t)mip_levels * YAPF__MIP_ENTRY;
|
|
if (size < idx_end) return NULL;
|
|
|
|
yapf__level levels[YAPF_MAX_MIPS];
|
|
memset(levels, 0, sizeof(levels));
|
|
int ok = 0;
|
|
|
|
yapf_image_t *img = (yapf_image_t *)calloc(1, sizeof(yapf_image_t));
|
|
if (!img) return NULL;
|
|
img->width = base_w;
|
|
img->height = base_h;
|
|
img->channels = channels;
|
|
img->gpu_format = buf[6];
|
|
img->flags = buf[8];
|
|
img->mip_levels = mip_levels;
|
|
img->mips = (uint8_t **)calloc(mip_levels, sizeof(uint8_t *));
|
|
if (!img->mips) goto done;
|
|
|
|
for (uint8_t m = 0; m < mip_levels; m++) {
|
|
const uint8_t *e = buf + YAPF__HDR_LEN + (size_t)m * YAPF__MIP_ENTRY;
|
|
uint32_t off = yapf__r32le(e);
|
|
uint32_t len = yapf__r32le(e + 4);
|
|
uint32_t mw = base_w >> m; if (mw < 1) mw = 1;
|
|
uint32_t mh = base_h >> m; if (mh < 1) mh = 1;
|
|
|
|
if (off < idx_end || off > size || len > size - off) goto done;
|
|
|
|
img->mips[m] = (uint8_t *)malloc((size_t)mw * mh * channels);
|
|
if (!img->mips[m]) goto done;
|
|
|
|
if (yapf__level_init(&levels[m], buf + off, len,
|
|
img->mips[m], mw, mh, channels) != 0)
|
|
goto done;
|
|
}
|
|
|
|
#if YAPF__SIMD
|
|
{
|
|
int simd = yapf__simd_available();
|
|
for (uint8_t m = 0; m < mip_levels; m++) levels[m].simd = simd;
|
|
}
|
|
#endif
|
|
ok = yapf__decode_levels(levels, mip_levels, threads) == 0;
|
|
|
|
done:
|
|
for (unsigned m = 0; m < YAPF_MAX_MIPS; m++) free(levels[m].offs);
|
|
if (!ok) { yapf_free(img); return NULL; }
|
|
img->pixels = img->mips[0];
|
|
return img;
|
|
}
|
|
|
|
yapf_image_t *yapf_load_memory(const void *buffer, size_t size) {
|
|
return yapf__load((const uint8_t *)buffer, size, 1);
|
|
}
|
|
|
|
yapf_image_t *yapf_load_memory_mt(const void *buffer, size_t size, int threads) {
|
|
return yapf__load((const uint8_t *)buffer, size, threads);
|
|
}
|
|
|
|
static yapf_image_t *yapf__load_file(const char *filename, int threads) {
|
|
if (!filename) return NULL;
|
|
FILE *f = fopen(filename, "rb");
|
|
if (!f) return NULL;
|
|
|
|
/* Read the whole file in chunks; avoids ftell() limits on 32-bit long. */
|
|
size_t cap = 1 << 16, len = 0;
|
|
uint8_t *buf = (uint8_t *)malloc(cap);
|
|
while (buf) {
|
|
size_t n = fread(buf + len, 1, cap - len, f);
|
|
len += n;
|
|
if (len < cap) break;
|
|
uint8_t *nb = (uint8_t *)realloc(buf, cap * 2);
|
|
if (!nb) { free(buf); buf = NULL; break; }
|
|
buf = nb;
|
|
cap *= 2;
|
|
}
|
|
int err = ferror(f);
|
|
fclose(f);
|
|
if (!buf) return NULL;
|
|
if (err) { free(buf); return NULL; }
|
|
|
|
yapf_image_t *img = yapf__load(buf, len, threads);
|
|
free(buf);
|
|
return img;
|
|
}
|
|
|
|
yapf_image_t *yapf_load(const char *filename) {
|
|
return yapf__load_file(filename, 1);
|
|
}
|
|
|
|
yapf_image_t *yapf_load_mt(const char *filename, int threads) {
|
|
return yapf__load_file(filename, threads);
|
|
}
|
|
|
|
/* ========================================================================
|
|
* yapf_encode / yapf_save
|
|
* ======================================================================== */
|
|
|
|
int yapf_encode(const yapf_image_t *img, void **out_data, size_t *out_size) {
|
|
if (!out_data || !out_size) return YAPF_ERR_INVALID;
|
|
*out_data = NULL;
|
|
*out_size = 0;
|
|
if (!img || !img->pixels) return YAPF_ERR_INVALID;
|
|
if (img->width == 0 || img->width > YAPF_MAX_DIM) return YAPF_ERR_INVALID;
|
|
if (img->height == 0 || img->height > YAPF_MAX_DIM) return YAPF_ERR_INVALID;
|
|
if (img->channels < 1 || img->channels > 4) return YAPF_ERR_INVALID;
|
|
if (img->mip_levels < 1 || img->mip_levels > YAPF_MAX_MIPS) return YAPF_ERR_INVALID;
|
|
|
|
int ch = (int)img->channels;
|
|
uint8_t mip_levels = img->mip_levels;
|
|
int err = YAPF_OK;
|
|
|
|
yapf__out bws[YAPF_MAX_MIPS];
|
|
memset(bws, 0, sizeof(bws));
|
|
|
|
for (uint8_t m = 0; m < mip_levels && err == YAPF_OK; m++) {
|
|
uint32_t mw = img->width >> m; if (mw < 1) mw = 1;
|
|
uint32_t mh = img->height >> m; if (mh < 1) mh = 1;
|
|
|
|
const uint8_t *src = (m == 0) ? img->pixels
|
|
: (img->mips ? img->mips[m] : NULL);
|
|
if (!src) { err = YAPF_ERR_INVALID; break; }
|
|
|
|
if (yapf__reserve(&bws[m], (size_t)mw * mh * (size_t)ch / 2 + 1024) != 0 ||
|
|
yapf__encode_level(&bws[m], src, mw, mh, ch) != 0)
|
|
err = YAPF_ERR_OOM;
|
|
}
|
|
|
|
/* Offsets are 32-bit: refuse files that would not fit. */
|
|
uint64_t total = YAPF__HDR_LEN + (uint64_t)mip_levels * YAPF__MIP_ENTRY;
|
|
for (uint8_t m = 0; m < mip_levels && err == YAPF_OK; m++) {
|
|
total += bws[m].len;
|
|
if (total > 0xFFFFFFFFull) err = YAPF_ERR_INVALID;
|
|
}
|
|
|
|
uint8_t *file = NULL;
|
|
if (err == YAPF_OK && !(file = (uint8_t *)malloc((size_t)total))) err = YAPF_ERR_OOM;
|
|
|
|
if (err == YAPF_OK) {
|
|
memset(file, 0, YAPF__HDR_LEN);
|
|
memcpy(file, YAPF__MAGIC, 4);
|
|
file[4] = YAPF__VERSION;
|
|
file[5] = img->channels;
|
|
file[6] = img->gpu_format;
|
|
file[7] = mip_levels;
|
|
file[8] = img->flags;
|
|
yapf__w32le(file + 12, img->width);
|
|
yapf__w32le(file + 16, img->height);
|
|
|
|
size_t pos = YAPF__HDR_LEN + (size_t)mip_levels * YAPF__MIP_ENTRY;
|
|
for (uint8_t m = 0; m < mip_levels; m++) {
|
|
uint8_t *entry = file + YAPF__HDR_LEN + (size_t)m * YAPF__MIP_ENTRY;
|
|
yapf__w32le(entry + 0, (uint32_t)pos);
|
|
yapf__w32le(entry + 4, (uint32_t)bws[m].len);
|
|
memcpy(file + pos, bws[m].buf, bws[m].len);
|
|
pos += bws[m].len;
|
|
}
|
|
*out_data = file;
|
|
*out_size = (size_t)total;
|
|
}
|
|
|
|
for (uint8_t m = 0; m < mip_levels; m++) free(bws[m].buf);
|
|
return err;
|
|
}
|
|
|
|
void yapf_free_buffer(void *data) {
|
|
free(data);
|
|
}
|
|
|
|
int yapf_save(const char *filename, const yapf_image_t *img) {
|
|
if (!filename) return YAPF_ERR_INVALID;
|
|
void *data = NULL;
|
|
size_t size = 0;
|
|
int err = yapf_encode(img, &data, &size);
|
|
if (err != YAPF_OK) return err;
|
|
|
|
FILE *f = fopen(filename, "wb");
|
|
if (!f) err = YAPF_ERR_IO;
|
|
else {
|
|
if (fwrite(data, 1, size, f) != size) err = YAPF_ERR_IO;
|
|
if (fclose(f) != 0 && err == YAPF_OK) err = YAPF_ERR_IO;
|
|
}
|
|
yapf_free_buffer(data);
|
|
return err;
|
|
}
|
|
|
|
/* ========================================================================
|
|
* yapf_free
|
|
* ======================================================================== */
|
|
|
|
void yapf_free(yapf_image_t *img) {
|
|
if (!img) return;
|
|
if (img->mips) {
|
|
for (uint8_t m = 0; m < img->mip_levels; m++)
|
|
free(img->mips[m]);
|
|
free(img->mips);
|
|
}
|
|
free(img);
|
|
}
|