1328 lines
38 KiB
C
1328 lines
38 KiB
C
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Prediction-Encoded Pixels (pep)
|
|
//
|
|
// author(s):
|
|
// ENDESGA - https://x.com/ENDESGA | https://bsky.app/profile/endesga.bsky.social
|
|
//
|
|
// contributer(s):
|
|
// Mariusz Dzikowski - https://github.com/demurzasty
|
|
// Akreson - https://github.com/Akreson
|
|
// Alepacho - https://github.com/Alepacho
|
|
//
|
|
// https://github.com/ENDESGA/pep
|
|
// 2026 - CC0 - FOSS forever
|
|
//
|
|
|
|
// The .pep type is a pixel art format made to compress as small as possible.
|
|
// It uses a custom "Prediction by Partial Matching" compression method
|
|
// designed from the ground up for palettized pixel art.
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma region - DEPENDENCIES
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include <stdint.h> // uint*_t
|
|
#include <stdlib.h> // mem-alloc
|
|
#include <stdio.h> // FILE
|
|
#include <string.h> // memset
|
|
|
|
#pragma endregion dependencies
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma region - MACROS
|
|
//
|
|
|
|
#define __PEP_STRINGIFY( VALUE ) #VALUE
|
|
#define _PEP_STRINGIFY( VALUE ) __PEP_STRINGIFY( VALUE )
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - memory
|
|
|
|
// This defines a set of macros that serve as wrappers for the standard
|
|
// C library memory management functions: `malloc`, `realloc`, and `free`.
|
|
// These macros can be used to easily replace the underlying memory allocation
|
|
// implementation in a project, for example, with a custom allocator or a
|
|
// debug-enabled version, without modifying all call sites.
|
|
#ifndef PEP_MALLOC
|
|
#define PEP_MALLOC( size ) malloc( size )
|
|
#define PEP_REALLOC( ptr, size ) realloc( ptr, size )
|
|
#define PEP_FREE( ptr ) free( ptr )
|
|
#endif
|
|
|
|
#pragma endregion memory
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - bits
|
|
|
|
// Provides a cross-platform macro to count leading zeros in a 32-bit integer.
|
|
#ifndef PEP_COUNT_LEADING_ZEROS
|
|
#ifdef _MSC_VER
|
|
// Microsoft Visual C++ compiler.
|
|
#define PEP_COUNT_LEADING_ZEROS( x ) __lzcnt( x )
|
|
#else
|
|
// GCC/Clang compilers.
|
|
#define PEP_COUNT_LEADING_ZEROS( x ) __builtin_clz( x )
|
|
#endif
|
|
#endif
|
|
|
|
// How many bits do we need to fit N values?
|
|
#define PEP_BITS_TO_FIT( N ) ( ( ( N ) <= 1 ) ? 1 : ( 32 - PEP_COUNT_LEADING_ZEROS( ( N ) - 1 ) ) )
|
|
|
|
#pragma endregion bits
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - hash
|
|
|
|
// Standard hash multipliers, there might be better ones:
|
|
#define _PEP_HASH_N 1540483477u // 0x5BD1E995 MurmurHash2
|
|
#define _PEP_HASH_W 3332679571u // 0xC6A4A793 MurmurHash64A
|
|
#define _PEP_HASH_NW 3432918353u // 0xCC9E2D51 MurmurHash3
|
|
#define _PEP_HASH_NE 2246822507u // 0x85EBCA6B MurmurHash3
|
|
// Hash-prospector, avalanche-optimized, by Chris Wellons:
|
|
#define _PEP_HASH_NN 2146121005u // 0x7FEB352D lowbias32
|
|
#define _PEP_HASH_WW 2890668881u // 0xAC4C1B51 triple32 round-2
|
|
#define _PEP_HASH_NNW 830770091u // 0x31848BAB triple32 round-3
|
|
#define _PEP_HASH_NNE 1935289751u // 0x735A2D97 prospector
|
|
#define _PEP_HASH_NWW 2943497623u // 0xAF723597 prospector
|
|
|
|
#pragma endregion hash
|
|
|
|
#pragma endregion macros
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma region - CONSTANTS
|
|
//
|
|
|
|
#define PEP_NAME "pep"
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - version
|
|
|
|
#define PEP_VERSION_MAJOR 0
|
|
#define PEP_VERSION_MINOR 6
|
|
#define PEP_VERSION_PATCH 0
|
|
#define PEP_VERSION _PEP_STRINGIFY( PEP_VERSION_MAJOR ) "." _PEP_STRINGIFY( PEP_VERSION_MINOR ) "." _PEP_STRINGIFY( PEP_VERSION_PATCH )
|
|
|
|
#pragma endregion version
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - frequency
|
|
|
|
// This is the amount of frequencies per context, and the amount of contexts,
|
|
// with [256] being the order0 context.
|
|
// Originally there were 256*256 contexts, but I found the image didn't get
|
|
// much bigger with the same amount of frequencies. Theoretically the more
|
|
// contexts you have the smaller the image is...
|
|
#define PEP_FREQ_N 257
|
|
#define PEP_FREQ_END ( PEP_FREQ_N - 1 )
|
|
#define PEP_CONTEXTS_MAX PEP_FREQ_END
|
|
|
|
// PEP_FREQ_MAX is the maximum accumulative frequency.
|
|
// This is the starting maximum, and is scaled as the image compresses, via
|
|
// the palette-delta; which seems to roughly correlate with the complexity.
|
|
// This value works better for low-res images.
|
|
#define PEP_FREQ_MAX ( PEP_FREQ_END >> 1 )
|
|
|
|
#pragma endregion frequency
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - arithmetic -coding
|
|
|
|
// These constants are for the 63bit arithmetic-coding, specifically not 64bit
|
|
// because of overflow.
|
|
#define PEP_CODE_BITS 24lu
|
|
#define PEP_CODE_BITS_INV ( 32lu - PEP_CODE_BITS )
|
|
#define PEP_FREQ_MAX_BITS 14lu
|
|
#define PEP_PROB_MAX_VALUE ( 1 << PEP_FREQ_MAX_BITS )
|
|
#define PEP_CODE_MAX_VALUE ( ( 1 << PEP_CODE_BITS ) - 1 )
|
|
|
|
#pragma endregion arithmetic -coding
|
|
|
|
#pragma endregion defaults
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma region - DECLARATIONS
|
|
//
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - formats
|
|
|
|
// .pep can automatically convert from RGBA to BGRA (little/big endian),
|
|
// which is often for low-level/backend rendering pipelines.
|
|
// Windows only supports ARGB for example, and so sometimes it's easier to
|
|
// make the whole application use that format.
|
|
typedef enum
|
|
{
|
|
pep_rgba,
|
|
pep_bgra,
|
|
|
|
pep_abgr,
|
|
pep_argb
|
|
}
|
|
pep_format;
|
|
|
|
// Palette colors can be restricted in the serialization phase to a maximum
|
|
// amount of bits per channel.
|
|
// The default is 8 bits per channel (standard 32 bit colors).
|
|
// This is NOT bits-per-pixel and does NOT cap palette_size.
|
|
typedef enum
|
|
{
|
|
pep_1bit,
|
|
pep_2bit,
|
|
pep_4bit,
|
|
pep_8bit
|
|
}
|
|
pep_channel_bits;
|
|
|
|
#pragma endregion formats
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - arithmetic -coding
|
|
|
|
// During the compression process the context per frequency-group needs to be
|
|
// tracked, with the sum of all frequencies being stored.
|
|
typedef struct
|
|
{
|
|
uint16_t freq[ PEP_FREQ_N ];
|
|
uint32_t sum;
|
|
}
|
|
_pep_context;
|
|
|
|
// Arithmetic coding structures:
|
|
typedef struct
|
|
{
|
|
uint8_t* data_ref;
|
|
uint32_t low;
|
|
uint32_t range;
|
|
}
|
|
_pep_ac_encode;
|
|
|
|
typedef struct
|
|
{
|
|
uint8_t* data_ref;
|
|
uint8_t* end_of_data;
|
|
uint32_t low;
|
|
uint32_t range;
|
|
uint32_t code;
|
|
}
|
|
_pep_ac_decode;
|
|
|
|
typedef struct
|
|
{
|
|
uint32_t high;
|
|
uint32_t low;
|
|
uint32_t scale;
|
|
}
|
|
_pep_prob;
|
|
|
|
typedef struct
|
|
{
|
|
_pep_prob prob;
|
|
uint32_t symbol;
|
|
}
|
|
_pep_sym_decode;
|
|
|
|
#pragma endregion arithmetic -coding
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - pep
|
|
|
|
// This is the main struct-type that contains values for using this format.
|
|
//
|
|
// `channel_bits` controls how many bits each R/G/B/A component of every
|
|
// palette entry is quantized to during serialization. It does NOT limit
|
|
// the number of palette entries (that's `palette_size`, up to 256).
|
|
// For example, pep_2bit means each channel has 4 possible values (0/85/170/255),
|
|
// so the palette can only draw from 4*4*4 = 64 distinct RGB colors.
|
|
typedef struct
|
|
{
|
|
uint8_t* bytes;
|
|
uint64_t bytes_size;
|
|
uint16_t width;
|
|
uint16_t height;
|
|
pep_format format;
|
|
uint32_t palette[ 256 ];
|
|
uint8_t palette_size;
|
|
pep_channel_bits channel_bits;
|
|
uint8_t model;
|
|
}
|
|
pep;
|
|
|
|
#pragma endregion pep
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - functions
|
|
|
|
pep pep_compress( const uint32_t* in_pixels, const uint16_t width, const uint16_t height, const pep_format in_format, const pep_channel_bits in_channel_bits );
|
|
uint32_t* pep_decompress( const pep* const in_pep, const pep_format out_format, const uint8_t first_color_transparent, uint8_t const pre_multiply );
|
|
void pep_free( pep* in_pep );
|
|
uint8_t* pep_serialize( const pep* in_pep, uint32_t* const out_size );
|
|
pep pep_deserialize( const uint8_t* const in_bytes, const uint32_t in_bytes_size );
|
|
uint8_t pep_save( const pep* const in_pep, const char* const file_path );
|
|
pep pep_load( const char* const file_path );
|
|
|
|
#pragma endregion functions
|
|
|
|
#pragma endregion declarations
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma region - DEFINITIONS
|
|
//
|
|
|
|
#ifdef PEP_IMPLEMENTATION
|
|
|
|
#pragma region - private / functions
|
|
|
|
static _pep_prob _pep_get_prob_from_context( const _pep_context* const context, const uint32_t symbol );
|
|
static void _pep_arith_encode( _pep_ac_encode* const ac, const _pep_prob prob );
|
|
static void _pep_arith_encode_normalize( _pep_ac_encode* const ac );
|
|
static uint32_t _pep_arith_decode_curr_freq( _pep_ac_decode* const ac, const uint32_t scale );
|
|
static void _pep_arith_decode_update( _pep_ac_decode* const ac, const _pep_prob prob );
|
|
static _pep_sym_decode _pep_get_sym_from_freq( const _pep_context* const context, const uint32_t target_freq );
|
|
static uint32_t _pep_pre_multiply( const uint32_t pixel, const pep_format format );
|
|
static uint32_t _pep_reformat( const uint32_t in_color, const pep_format in_format, const pep_format out_format );
|
|
|
|
#pragma endregion private / functions
|
|
|
|
#ifdef _MSC_VER
|
|
// Intrin header is only needed for implementation.
|
|
#include <intrin.h> // __lzcnt
|
|
|
|
// Disable unsafe fopen usage warning on MSVC compiler.
|
|
#pragma warning( push )
|
|
#pragma warning( disable : 4996 )
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - arithmetic
|
|
|
|
////////////////////////////////
|
|
#pragma region | encode
|
|
|
|
// Getting cumulative frequnce of symbol
|
|
static _pep_prob _pep_get_prob_from_context( const _pep_context* const context, const uint32_t symbol )
|
|
{
|
|
_pep_prob prob = { 0 };
|
|
prob.scale = context->sum;
|
|
|
|
for( uint32_t index = 0; index < symbol; ++index )
|
|
{
|
|
prob.low += context->freq[ index ];
|
|
}
|
|
|
|
prob.high = prob.low + context->freq[ symbol ];
|
|
return prob;
|
|
}
|
|
|
|
// This encodes a symbol into the arithmetic-coding range. It scales the
|
|
// current range based on the symbol's frequency and total frequency count.
|
|
static void _pep_arith_encode( _pep_ac_encode* const ac, const _pep_prob prob )
|
|
{
|
|
ac->range /= prob.scale;
|
|
ac->low += prob.low * ac->range;
|
|
ac->range *= prob.high - prob.low;
|
|
}
|
|
|
|
// Adjusts the arithmetic-coding range by removing a boundary value
|
|
// Main goal of this process is to keep our range from getting
|
|
// too small.
|
|
static void _pep_arith_encode_normalize( _pep_ac_encode* const ac )
|
|
{
|
|
while( 1 )
|
|
{
|
|
if( ( ac->low ^ ( ac->low + ac->range ) ) >= PEP_CODE_MAX_VALUE )
|
|
{
|
|
if( ac->range >= PEP_PROB_MAX_VALUE ) break;
|
|
|
|
ac->range = PEP_PROB_MAX_VALUE - ( ac->low & ( PEP_PROB_MAX_VALUE - 1 ) );
|
|
}
|
|
|
|
uint8_t byte = ac->low >> PEP_CODE_BITS;
|
|
ac->low <<= PEP_CODE_BITS_INV;
|
|
ac->range <<= PEP_CODE_BITS_INV;
|
|
*ac->data_ref++= byte;
|
|
}
|
|
}
|
|
|
|
#pragma endregion encode
|
|
|
|
////////////////////////////////
|
|
#pragma region | decode
|
|
|
|
// Getting current frequency by doing reverse trasformation
|
|
static uint32_t _pep_arith_decode_curr_freq( _pep_ac_decode* const ac, const uint32_t scale )
|
|
{
|
|
ac->range /= scale;
|
|
uint32_t result = ( ac->code - ac->low ) / ( ac->range );
|
|
return result;
|
|
}
|
|
|
|
// Same as with the encode_normalize, only on decode we reading in value
|
|
static void _pep_arith_decode_update( _pep_ac_decode* const ac, const _pep_prob prob )
|
|
{
|
|
ac->low += ac->range * prob.low;
|
|
ac->range *= prob.high - prob.low;
|
|
|
|
while( 1 )
|
|
{
|
|
if( ( ac->low ^ ( ac->low + ac->range ) ) >= PEP_CODE_MAX_VALUE )
|
|
{
|
|
if( ac->range < PEP_PROB_MAX_VALUE )
|
|
{
|
|
ac->range = PEP_PROB_MAX_VALUE - ( ac->low & ( PEP_PROB_MAX_VALUE - 1 ) );
|
|
}
|
|
else break;
|
|
}
|
|
|
|
uint8_t in_byte = 0;
|
|
if( ac->data_ref != ac->end_of_data )
|
|
{
|
|
in_byte = *ac->data_ref++;
|
|
}
|
|
|
|
ac->code = ( ac->code << 8 ) | in_byte;
|
|
ac->range <<= 8;
|
|
ac->low <<= 8;
|
|
}
|
|
}
|
|
|
|
static _pep_sym_decode _pep_get_sym_from_freq( const _pep_context* const context, const uint32_t target_freq )
|
|
{
|
|
_pep_sym_decode result = { 0 };
|
|
|
|
uint32_t s = 0;
|
|
uint32_t freq = 0;
|
|
for( ; s <= PEP_FREQ_END; ++s )
|
|
{
|
|
freq += context->freq[ s ];
|
|
if( freq > target_freq ) break;
|
|
}
|
|
if( s > PEP_FREQ_END ) s = PEP_FREQ_END;
|
|
|
|
result.prob.high = freq;
|
|
result.prob.low = freq - context->freq[ s ];
|
|
result.prob.scale = context->sum;
|
|
result.symbol = s;
|
|
|
|
return result;
|
|
}
|
|
|
|
#pragma endregion decode
|
|
|
|
#pragma endregion arithmetic
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - compression
|
|
|
|
////////////////////////////////
|
|
#pragma region | compression / hidden
|
|
|
|
// pep supports pre-multiplying the RGB channels with the A channel.
|
|
static uint32_t _pep_pre_multiply( const uint32_t pixel, const pep_format format )
|
|
{
|
|
uint8_t* bytes = ( uint8_t* )( &pixel );
|
|
|
|
if( format <= pep_bgra )
|
|
{
|
|
uint32_t scaled_a = ( uint32_t )( bytes[ 3 ] ) * 257;
|
|
bytes[ 1 ] = ( uint8_t )( ( ( uint32_t )( bytes[ 1 ] ) * scaled_a + 32896 ) >> 16 );
|
|
bytes[ 2 ] = ( uint8_t )( ( ( uint32_t )( bytes[ 2 ] ) * scaled_a + 32896 ) >> 16 );
|
|
bytes[ 3 ] = ( uint8_t )( ( ( uint32_t )( bytes[ 3 ] ) * scaled_a + 32896 ) >> 16 );
|
|
}
|
|
else
|
|
{
|
|
uint32_t scaled_a = ( uint32_t )( bytes[ 0 ] ) * 257;
|
|
bytes[ 0 ] = ( uint8_t )( ( ( uint32_t )( bytes[ 0 ] ) * scaled_a + 32896 ) >> 16 );
|
|
bytes[ 1 ] = ( uint8_t )( ( ( uint32_t )( bytes[ 1 ] ) * scaled_a + 32896 ) >> 16 );
|
|
bytes[ 2 ] = ( uint8_t )( ( ( uint32_t )( bytes[ 2 ] ) * scaled_a + 32896 ) >> 16 );
|
|
}
|
|
|
|
return pixel;
|
|
}
|
|
|
|
// pep supports "dynamic formats", where you can specify what the in-bytes are,
|
|
// and reformat to a different channel-order.
|
|
// This means two "identical" pep files can have different formats, but you
|
|
// can choose how to reformat it when it decompresses!
|
|
static uint32_t _pep_reformat( const uint32_t in_color, const pep_format in_format, const pep_format out_format )
|
|
{
|
|
if( in_format == out_format ) return in_color;
|
|
|
|
if( in_format <= pep_bgra && out_format <= pep_bgra )
|
|
{ // RGBA <-> BGRA: swap R and B
|
|
return( in_color & 0xff00ff00 ) | ( ( in_color & 0x000000ff ) << 16 ) | ( ( in_color & 0x00ff0000 ) >> 16 );
|
|
}
|
|
else if( in_format >= pep_abgr && out_format >= pep_abgr )
|
|
{ // ABGR <-> ARGB: swap R and B
|
|
return( in_color & 0x00ff00ff ) | ( ( in_color & 0x0000ff00 ) << 16 ) | ( ( in_color & 0xff000000 ) >> 16 );
|
|
}
|
|
else if( ( in_format ^ out_format ) == 2 )
|
|
{ // Alpha flip: RGBA <-> ARGB or BGRA <-> ABGR
|
|
return( ( in_color & 0x000000ff ) << 24 ) | ( ( in_color & 0x0000ff00 ) << 8 ) | ( ( in_color & 0x00ff0000 ) >> 8 ) | ( ( in_color & 0xff000000 ) >> 24 );
|
|
}
|
|
else if( in_format < out_format )
|
|
{ // Flip: RGBA/BGRA -> ABGR/ARGB
|
|
return( ( in_color & 0xff000000 ) >> 24 ) | ( ( in_color & 0x00ffffff ) << 8 );
|
|
}
|
|
else
|
|
{ // Flip: ABGR/ARGB -> RGBA/BGRA
|
|
return( ( in_color & 0x000000ff ) << 24 ) | ( ( in_color & 0xffffff00 ) >> 8 );
|
|
}
|
|
}
|
|
|
|
// Update the frequency table after encoding/decoding a symbol.
|
|
// This increments the symbol's frequency and the total sum.
|
|
// When we hit freq_max, we increase the freq_max via a complexity
|
|
// approximation via the palette-size, then we scale everything down
|
|
// to a quarter to keep the frequencies manageable.
|
|
// This dynamic part helps the compression adapt to the image's patterns.
|
|
#define _PEP_UPDATE( CONTEXT, SYMBOL, FREQ_MAX, PALETTE_SIZE )\
|
|
do\
|
|
{\
|
|
CONTEXT->freq[ SYMBOL ] += 2;\
|
|
CONTEXT->sum += 2;\
|
|
if( CONTEXT->freq[ SYMBOL ] >= FREQ_MAX || CONTEXT->sum >= PEP_PROB_MAX_VALUE )\
|
|
{\
|
|
FREQ_MAX += ( PEP_FREQ_END - PALETTE_SIZE ) >> 1;\
|
|
CONTEXT->sum = 0;\
|
|
for( uint64_t f = 0; f < PEP_FREQ_N; f++ )\
|
|
{\
|
|
const uint16_t _f = CONTEXT->freq[ f ];\
|
|
if( _f == 0 ) continue;\
|
|
const uint16_t scaled = ( _f + 1 ) >> 1;\
|
|
CONTEXT->freq[ f ] = scaled;\
|
|
CONTEXT->sum += scaled;\
|
|
}\
|
|
}\
|
|
}\
|
|
while( 0 )
|
|
|
|
#pragma endregion hidden
|
|
|
|
////////////////////////////////
|
|
#pragma region | compression / visible
|
|
|
|
#define PEP_HASH_BIT_CONTEXT ( 1u << 16 )
|
|
|
|
enum
|
|
{
|
|
_PEP_TAP_HASH,
|
|
_PEP_TAP_NW,
|
|
_PEP_TAP_W
|
|
};
|
|
|
|
// Different context models perform differently depending on the pixel data.
|
|
// From lots of testing it seems these 3 structures give the best results
|
|
// across the 3 major types of pixel art.
|
|
// Pep will sacrifice some time to ensure the file is as small as possible
|
|
// by testing all 3 of these without assumptions.
|
|
static const int8_t _pep_models[] =
|
|
{
|
|
_PEP_TAP_HASH, // typically for ~2 colors
|
|
_PEP_TAP_NW, // typically for ~4-8 colors
|
|
_PEP_TAP_W // typically for >=64 colors
|
|
};
|
|
#define PEP_NUM_MODELS ( ( uint8_t )( sizeof( _pep_models ) / sizeof( _pep_models[ 0 ] ) ) )
|
|
|
|
typedef struct
|
|
{
|
|
_pep_context* contexts;
|
|
uint32_t index_end;
|
|
uint32_t index_escape;
|
|
int8_t kind;
|
|
}
|
|
_pep_model_context;
|
|
|
|
// Allocate only the context regions a given model uses, then seed order-0.
|
|
static uint8_t _pep_model_init( _pep_model_context* m, int8_t kind, uint32_t pcount )
|
|
{
|
|
memset( m, 0, sizeof( *m ) );
|
|
m->kind = kind;
|
|
m->index_escape = pcount;
|
|
m->index_end = pcount + 1;
|
|
|
|
// order0 starts at offset 0, followed by the specific context level
|
|
uint32_t off = 1;
|
|
switch( kind )
|
|
{
|
|
case _PEP_TAP_HASH:
|
|
{
|
|
off += PEP_HASH_BIT_CONTEXT;
|
|
break;
|
|
}
|
|
case _PEP_TAP_NW:
|
|
{
|
|
off += m->index_end * m->index_end;
|
|
break;
|
|
}
|
|
case _PEP_TAP_W:
|
|
{
|
|
off += m->index_end;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m->contexts = ( _pep_context* )calloc( off, sizeof( _pep_context ) );
|
|
if( !m->contexts ) return 0;
|
|
|
|
_pep_context* order0 = &m->contexts[ 0 ];
|
|
for( uint32_t index = 0; index < pcount; index++ ) order0->freq[ index ] = 1;
|
|
order0->sum = pcount;
|
|
return 1;
|
|
}
|
|
|
|
static void _pep_model_free( _pep_model_context* m )
|
|
{
|
|
if( m->contexts )
|
|
{
|
|
free( m->contexts );
|
|
m->contexts = NULL;
|
|
}
|
|
}
|
|
|
|
// Resolve the context pointer for the specific level of this pixel.
|
|
static _pep_context* _pep_build_level( const _pep_model_context* m, const uint8_t* idx, uint32_t pos, uint32_t x, uint32_t y, uint16_t width )
|
|
{
|
|
// It's only possible to tap previous values:
|
|
/*
|
|
- NNW NN NNE
|
|
NWW NW N NE
|
|
WW W [x]
|
|
*/
|
|
// The 10th NNWW tap has dimishing returns
|
|
|
|
const uint32_t SENT = m->index_escape;
|
|
const uint32_t index_end = m->index_end;
|
|
const uint32_t Wv = ( x > 0 ) ? idx[ pos - 1 ] : SENT;
|
|
const uint32_t Nv = ( y > 0 ) ? idx[ pos - width ] : SENT;
|
|
|
|
switch( m->kind )
|
|
{
|
|
case _PEP_TAP_HASH:
|
|
{
|
|
const uint32_t NWv = ( x > 0 && y > 0 ) ? idx[ pos - width - 1 ] : SENT;
|
|
const uint32_t NNv = ( y > 1 ) ? idx[ pos - 2 * width ] : SENT;
|
|
const uint32_t WWv = ( x > 1 ) ? idx[ pos - 2 ] : SENT;
|
|
const uint32_t NEv = ( ( x + 1 ) < width && y > 0 ) ? idx[ pos - width + 1 ] : SENT;
|
|
const uint32_t NNWv = ( y > 1 && x > 0 ) ? idx[ pos - 2 * width - 1 ] : SENT;
|
|
const uint32_t NNEv = ( y > 1 && ( x + 1 ) < width ) ? idx[ pos - 2 * width + 1 ] : SENT;
|
|
const uint32_t NWWv = ( x > 1 && y > 0 ) ? idx[ pos - width - 2 ] : SENT;
|
|
|
|
uint32_t h = Nv * _PEP_HASH_N + Wv * _PEP_HASH_W + NWv * _PEP_HASH_NW + NEv * _PEP_HASH_NE + NNv * _PEP_HASH_NN + WWv * _PEP_HASH_WW + NNWv * _PEP_HASH_NNW + NNEv * _PEP_HASH_NNE + NWWv * _PEP_HASH_NWW;
|
|
h ^= h >> 15;
|
|
return &m->contexts[ 1 + ( h & ( PEP_HASH_BIT_CONTEXT - 1 ) ) ];
|
|
}
|
|
case _PEP_TAP_NW:
|
|
{
|
|
return &m->contexts[ 1 + Nv * index_end + Wv ];
|
|
}
|
|
case _PEP_TAP_W:
|
|
{
|
|
return &m->contexts[ 1 + Wv ];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Encode all pixels with one model and return the size of the byte stream.
|
|
static uint64_t _pep_encode_model( const uint8_t* idx, uint16_t width, uint16_t height, uint32_t pcount, uint8_t model, uint8_t* out )
|
|
{
|
|
_pep_model_context m;
|
|
if( !_pep_model_init( &m, _pep_models[ model ], pcount ) ) return 0;
|
|
const uint32_t index_escape = m.index_escape;
|
|
|
|
_pep_ac_encode ac = { 0 };
|
|
ac.range = ( uint32_t )( ( 1llu << 32 ) - 1 );
|
|
ac.data_ref = out;
|
|
uint16_t freq_max = PEP_FREQ_MAX;
|
|
_pep_context* order0 = &m.contexts[ 0 ];
|
|
|
|
uint32_t pos = 0;
|
|
for( uint32_t y = 0; y < height; y++ )
|
|
{
|
|
for( uint32_t x = 0; x < width; x++, pos++ )
|
|
{
|
|
const uint32_t symbol = idx[ pos ];
|
|
_pep_context* context = _pep_build_level( &m, idx, pos, x, y, width );
|
|
uint8_t encoded_in_level = 0;
|
|
|
|
if( context->sum != 0 )
|
|
{
|
|
if( context->freq[ symbol ] != 0 )
|
|
{
|
|
_pep_prob prob = _pep_get_prob_from_context( context, symbol );
|
|
_pep_arith_encode( &ac, prob );
|
|
_pep_arith_encode_normalize( &ac );
|
|
_PEP_UPDATE( context, symbol, freq_max, pcount );
|
|
encoded_in_level = 1;
|
|
}
|
|
else
|
|
{
|
|
_pep_prob prob = _pep_get_prob_from_context( context, index_escape );
|
|
_pep_arith_encode( &ac, prob );
|
|
_pep_arith_encode_normalize( &ac );
|
|
context->freq[ index_escape ] += 1;
|
|
context->sum += 1;
|
|
}
|
|
}
|
|
|
|
if( !encoded_in_level )
|
|
{
|
|
_pep_prob prob = _pep_get_prob_from_context( order0, symbol );
|
|
_pep_arith_encode( &ac, prob );
|
|
_pep_arith_encode_normalize( &ac );
|
|
_PEP_UPDATE( order0, symbol, freq_max, pcount );
|
|
|
|
if( context->sum == 0 )
|
|
{
|
|
context->freq[ index_escape ] = 1;
|
|
context->sum = 1;
|
|
}
|
|
context->freq[ symbol ] = 1;
|
|
context->sum += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
for( uint8_t index = 0; index < 4; index++ )
|
|
{
|
|
uint8_t byte = ac.low >> PEP_CODE_BITS;
|
|
ac.low <<= PEP_CODE_BITS_INV;
|
|
*ac.data_ref++= byte;
|
|
}
|
|
|
|
uint64_t size = ( uint64_t ) ( ac.data_ref - out );
|
|
_pep_model_free( &m );
|
|
return size;
|
|
}
|
|
|
|
// The format of the in_pixels has to be the same as in_format.
|
|
// out_format is the one applied to the newly compressed pep
|
|
|
|
// Compresses raw pixels into a pep, trying each context model and keeping the
|
|
// smallest result (the winning model index is stored in out.model).
|
|
pep pep_compress( const uint32_t* in_pixels, const uint16_t width, const uint16_t height, const pep_format in_format, const pep_channel_bits in_channel_bits )
|
|
{
|
|
pep out_pep = { 0 };
|
|
uint32_t area = ( uint32_t )width * height;
|
|
if( in_pixels == NULL || area == 0 ) return out_pep;
|
|
|
|
out_pep.width = width;
|
|
out_pep.height = height;
|
|
out_pep.format = in_format;
|
|
out_pep.channel_bits = in_channel_bits;
|
|
|
|
uint8_t* idx = ( uint8_t* )PEP_MALLOC( area );
|
|
|
|
uint32_t palette[ 256 ];
|
|
uint16_t count = 0; // 1-256 distinct colours
|
|
const uint32_t* p = in_pixels;
|
|
const uint32_t* p_end = p + area;
|
|
uint32_t last_p = 0,
|
|
this_p = 0;
|
|
while( p < p_end )
|
|
{
|
|
this_p = *p;
|
|
if( p > in_pixels && this_p == last_p )
|
|
{
|
|
p++;
|
|
continue;
|
|
}
|
|
uint16_t n = 0;
|
|
while( n < count && this_p != palette[ n ] ) n++;
|
|
if( n >= count && count < 256 ) palette[ count++ ] = this_p;
|
|
last_p = this_p;
|
|
p++;
|
|
}
|
|
memcpy( out_pep.palette, palette, ( size_t ) count * sizeof( uint32_t ) );
|
|
out_pep.palette_size = ( uint8_t )count;
|
|
for( uint32_t index = 0; index < area; index++ )
|
|
{
|
|
uint32_t c = in_pixels[ index ];
|
|
uint16_t k = 0;
|
|
while( k < count && palette[ k ] != c ) k++;
|
|
idx[ index ] = ( uint8_t )( k < count ? k : 0 );
|
|
}
|
|
const uint32_t pcount = out_pep.palette_size ? out_pep.palette_size : 256;
|
|
|
|
uint8_t* best = ( uint8_t* )PEP_MALLOC( ( size_t ) area * sizeof( uint32_t ) + 64 );
|
|
uint8_t* tmp = ( uint8_t* )PEP_MALLOC( ( size_t ) area * sizeof( uint32_t ) + 64 );
|
|
uint64_t best_size = 0;
|
|
uint8_t best_model = 0;
|
|
|
|
for( uint8_t model = 0; model < PEP_NUM_MODELS; model++ )
|
|
{
|
|
uint64_t size = _pep_encode_model( idx, width, height, pcount, model, tmp );
|
|
if( size == 0 ) continue;
|
|
|
|
if( best_size == 0 || size < best_size )
|
|
{
|
|
best_size = size;
|
|
best_model = model;
|
|
memcpy( best, tmp, size );
|
|
}
|
|
}
|
|
|
|
out_pep.bytes = ( uint8_t* )PEP_REALLOC( best, best_size );
|
|
out_pep.bytes_size = best_size;
|
|
out_pep.model = ( uint8_t )best_model;
|
|
|
|
PEP_FREE( tmp );
|
|
PEP_FREE( idx );
|
|
return out_pep;
|
|
}
|
|
|
|
// Decode all pixels with one model (mirror of _pep_encode_model).
|
|
static void _pep_decode_model( const uint8_t* in_bytes, uint64_t in_size, uint16_t width, uint16_t height, uint32_t pcount, uint8_t model, const uint32_t* palette, pep_format in_format, pep_format out_format, uint8_t pre_multiply, uint8_t* idx, uint32_t* out_pixels )
|
|
{
|
|
_pep_model_context m;
|
|
if( !_pep_model_init( &m, _pep_models[ model ], pcount ) ) return;
|
|
const uint32_t index_escape = m.index_escape;
|
|
|
|
_pep_ac_decode ac = { 0 };
|
|
ac.range = ( uint32_t )( ( 1llu << 32 ) - 1 );
|
|
ac.data_ref = ( uint8_t* )in_bytes;
|
|
ac.end_of_data = ( uint8_t* )in_bytes + in_size;
|
|
for( uint8_t index = 0; index < 4; ++index )
|
|
{
|
|
uint8_t b = 0;
|
|
if( ac.data_ref != ac.end_of_data ) b = *ac.data_ref++;
|
|
ac.code = ( ac.code << 8 ) | b;
|
|
}
|
|
|
|
uint16_t freq_max = PEP_FREQ_MAX;
|
|
_pep_context* order0 = &m.contexts[ 0 ];
|
|
uint32_t pos = 0;
|
|
|
|
for( uint32_t y = 0; y < height; y++ )
|
|
{
|
|
for( uint32_t x = 0; x < width; x++, pos++ )
|
|
{
|
|
_pep_context* context = _pep_build_level( &m, idx, pos, x, y, width );
|
|
uint32_t symbol = 0;
|
|
uint8_t decoded_in_level = 0;
|
|
|
|
if( context->sum != 0 )
|
|
{
|
|
uint32_t f = _pep_arith_decode_curr_freq( &ac, context->sum );
|
|
_pep_sym_decode r = _pep_get_sym_from_freq( context, f );
|
|
_pep_arith_decode_update( &ac, r.prob );
|
|
|
|
if( r.symbol != index_escape )
|
|
{
|
|
symbol = r.symbol;
|
|
_PEP_UPDATE( context, symbol, freq_max, pcount );
|
|
decoded_in_level = 1;
|
|
}
|
|
else
|
|
{
|
|
context->freq[ index_escape ] += 1;
|
|
context->sum += 1;
|
|
}
|
|
}
|
|
|
|
if( !decoded_in_level )
|
|
{
|
|
uint32_t f = _pep_arith_decode_curr_freq( &ac, order0->sum );
|
|
_pep_sym_decode r = _pep_get_sym_from_freq( order0, f );
|
|
_pep_arith_decode_update( &ac, r.prob );
|
|
symbol = r.symbol;
|
|
_PEP_UPDATE( order0, symbol, freq_max, pcount );
|
|
|
|
if( context->sum == 0 )
|
|
{
|
|
context->freq[ index_escape ] = 1;
|
|
context->sum = 1;
|
|
}
|
|
context->freq[ symbol ] = 1;
|
|
context->sum += 1;
|
|
}
|
|
|
|
idx[ pos ] = ( uint8_t )symbol;
|
|
uint32_t pixel = ( symbol < pcount ) ? _pep_reformat( palette[ symbol ], in_format, out_format ) : 0;
|
|
if( pre_multiply != 0 ) pixel = _pep_pre_multiply( pixel, out_format );
|
|
out_pixels[ pos ] = pixel;
|
|
}
|
|
}
|
|
_pep_model_free( &m );
|
|
}
|
|
|
|
uint32_t* pep_decompress( const pep* const in_pep, const pep_format out_format, const uint8_t transparent_first_color, uint8_t const pre_multiply )
|
|
{
|
|
if( in_pep == NULL ) return NULL;
|
|
if( in_pep->bytes == NULL || in_pep->bytes_size == 0 || in_pep->width == 0 || in_pep->height == 0 ) return NULL;
|
|
|
|
const uint16_t width = in_pep->width,
|
|
height = in_pep->height;
|
|
const uint32_t area = ( uint32_t )width * height;
|
|
uint32_t* out_pixels = ( uint32_t* )PEP_MALLOC( area * sizeof( uint32_t ) );
|
|
uint8_t* idx = ( uint8_t* )PEP_MALLOC( area );
|
|
|
|
const uint32_t pcount = in_pep->palette_size ? in_pep->palette_size : 256;
|
|
static uint32_t palette[ 256 ];
|
|
memcpy( palette, in_pep->palette, pcount * sizeof( uint32_t ) );
|
|
if( transparent_first_color != 0 )
|
|
{
|
|
if( in_pep->format <= pep_bgra ) palette[ 0 ] &= 0x00ffffff;
|
|
else palette[ 0 ] &= 0xffffff00;
|
|
}
|
|
|
|
uint8_t model = in_pep->model;
|
|
if( model >= PEP_NUM_MODELS ) model = 0;
|
|
|
|
_pep_decode_model( in_pep->bytes, in_pep->bytes_size, width, height, pcount, model, palette, in_pep->format, out_format, pre_multiply, idx, out_pixels );
|
|
|
|
PEP_FREE( idx );
|
|
return out_pixels;
|
|
}
|
|
|
|
void pep_free( pep* in_pep )
|
|
{
|
|
if( in_pep && in_pep->bytes )
|
|
{
|
|
free( in_pep->bytes );
|
|
in_pep->bytes = NULL;
|
|
in_pep->bytes_size = 0;
|
|
}
|
|
}
|
|
|
|
#pragma endregion visible
|
|
|
|
#pragma endregion compression
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - serialization
|
|
|
|
uint8_t* pep_serialize( const pep* in_pep, uint32_t* const out_size )
|
|
{
|
|
if( !in_pep || !in_pep->width || !in_pep->height || !in_pep->bytes_size || !in_pep->bytes )
|
|
{
|
|
*out_size = 0;
|
|
return NULL;
|
|
}
|
|
|
|
uint16_t palette_count = in_pep->palette_size ? in_pep->palette_size : 256;
|
|
|
|
const uint16_t w = in_pep->width - 1;
|
|
const uint16_t h = in_pep->height - 1;
|
|
const uint8_t is_small = ( w <= 255 && h <= 255 ) ? 1 : 0;
|
|
const uint8_t dim_bytes = is_small ? 2 : 3;
|
|
|
|
// calculate variable-length size bytes
|
|
uint8_t size_bytes = 0;
|
|
uint32_t temp_size = in_pep->bytes_size;
|
|
while( temp_size >= 0x80 )
|
|
{
|
|
size_bytes++;
|
|
temp_size >>= 7;
|
|
}
|
|
++size_bytes;
|
|
|
|
// check if bitmap (black and white)
|
|
uint8_t is_bitmap = 0;
|
|
if( palette_count == 2 )
|
|
{
|
|
const uint8_t* const color0 = ( uint8_t* )( &in_pep->palette[ 0 ] );
|
|
const uint8_t* const color1 = ( uint8_t* )( &in_pep->palette[ 1 ] );
|
|
|
|
const uint8_t is_white0 = ( color0[ 0 ] == 255 && color0[ 1 ] == 255 && color0[ 2 ] == 255 && color0[ 3 ] == 255 );
|
|
const uint8_t is_white1 = ( color1[ 0 ] == 255 && color1[ 1 ] == 255 && color1[ 2 ] == 255 && color1[ 3 ] == 255 );
|
|
|
|
if( in_pep->format <= pep_bgra )
|
|
{
|
|
is_bitmap = ( ( is_white0 && color1[ 0 ] == 0 && color1[ 1 ] == 0 && color1[ 2 ] == 0 && color1[ 3 ] == 255 ) || ( color0[ 0 ] == 0 && color0[ 1 ] == 0 && color0[ 2 ] == 0 && color0[ 3 ] == 255 && is_white1 ) );
|
|
}
|
|
else
|
|
{
|
|
is_bitmap = ( ( is_white0 && color1[ 0 ] == 255 && color1[ 1 ] == 0 && color1[ 2 ] == 0 && color1[ 3 ] == 0 ) || ( color0[ 0 ] == 255 && color0[ 1 ] == 0 && color0[ 2 ] == 0 && color0[ 3 ] == 0 && is_white1 ) );
|
|
}
|
|
}
|
|
|
|
// check if all palette alphas are 255
|
|
uint8_t only_rgb = 1;
|
|
if( !is_bitmap )
|
|
{
|
|
for( uint16_t index = 0; index < palette_count; index++ )
|
|
{
|
|
if( ( ( ( uint8_t* )( &in_pep->palette[ index ] ) )[ 3 ] ) != 0xff )
|
|
{
|
|
only_rgb = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// calculate palette bytes
|
|
uint16_t palette_bytes = 0;
|
|
if( !is_bitmap )
|
|
{
|
|
const uint16_t channel_bits = 1 << in_pep->channel_bits;
|
|
if( channel_bits == 8 )
|
|
{
|
|
palette_bytes = palette_count * ( only_rgb ? 3 : 4 );
|
|
}
|
|
else
|
|
{
|
|
const uint16_t channels = only_rgb ? 3 : 4;
|
|
palette_bytes = ( channel_bits * channels * palette_count + 7 ) >> 3;
|
|
}
|
|
}
|
|
|
|
// allocate the exact size (subtract 1 for palette_size byte if bitmap)
|
|
const uint64_t total_size = dim_bytes + size_bytes + ( is_bitmap ? 0 : 1 ) + palette_bytes + in_pep->bytes_size + 4 + 1;
|
|
uint8_t* out_bytes = ( uint8_t* )PEP_MALLOC( total_size );
|
|
uint8_t* out_bytes_ref = out_bytes;
|
|
|
|
// flags: format (2), channel_bits (2), is_small (1), only_rgb (1), is_bitmap (1)
|
|
*out_bytes_ref++= ( in_pep->format & 0x3 ) | ( ( in_pep->channel_bits & 0x3 ) << 2 ) | ( ( is_small & 0x1 ) << 4 ) | ( ( only_rgb & 0x1 ) << 5 ) | ( ( is_bitmap & 0x1 ) << 6 );
|
|
*out_bytes_ref++= in_pep->model; // takes the last extra byte
|
|
|
|
// width/height
|
|
if( is_small )
|
|
{
|
|
*out_bytes_ref++= w & 0xff;
|
|
*out_bytes_ref++= h & 0xff;
|
|
}
|
|
else
|
|
{
|
|
const uint32_t packed_dims = ( ( w & 0xfff ) << 12 ) | ( h & 0xfff );
|
|
*out_bytes_ref++= ( packed_dims >> 16 ) & 0xff;
|
|
*out_bytes_ref++= ( packed_dims >> 8 ) & 0xff;
|
|
*out_bytes_ref++= packed_dims & 0xff;
|
|
}
|
|
|
|
// variable-length size
|
|
uint32_t size = in_pep->bytes_size;
|
|
while( size >= 0x80 )
|
|
{
|
|
*out_bytes_ref++= ( size | 0x80 ) & 0xff;
|
|
size >>= 7;
|
|
}
|
|
*out_bytes_ref++= size;
|
|
|
|
if( !is_bitmap )
|
|
{
|
|
// palette size
|
|
*out_bytes_ref++= in_pep->palette_size;
|
|
|
|
// palette
|
|
const uint16_t channel_bits = 1 << in_pep->channel_bits;
|
|
const uint8_t shift = 8 - channel_bits;
|
|
const uint8_t mask = ( 1 << channel_bits ) - 1;
|
|
|
|
if( channel_bits == 8 )
|
|
{
|
|
for( uint16_t index = 0; index < palette_count; index++ )
|
|
{
|
|
uint8_t* color = ( uint8_t* )( &( in_pep->palette[ index ] ) );
|
|
*out_bytes_ref++= color[ 0 ];
|
|
*out_bytes_ref++= color[ 1 ];
|
|
*out_bytes_ref++= color[ 2 ];
|
|
if( !only_rgb ) *out_bytes_ref++= color[ 3 ];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32_t bit_buffer = 0;
|
|
uint8_t bit_count = 0;
|
|
|
|
for( uint16_t index = 0; index < palette_count; index++ )
|
|
{
|
|
uint8_t* color = ( uint8_t* )( &( in_pep->palette[ index ] ) );
|
|
|
|
bit_buffer = ( bit_buffer << channel_bits ) | ( ( color[ 0 ] >> shift ) &mask );
|
|
bit_buffer = ( bit_buffer << channel_bits ) | ( ( color[ 1 ] >> shift ) &mask );
|
|
bit_buffer = ( bit_buffer << channel_bits ) | ( ( color[ 2 ] >> shift ) &mask );
|
|
bit_count += channel_bits * 3;
|
|
|
|
if( !only_rgb )
|
|
{
|
|
bit_buffer = ( bit_buffer << channel_bits ) | ( ( color[ 3 ] >> shift ) &mask );
|
|
bit_count += channel_bits;
|
|
}
|
|
|
|
while( bit_count >= 8 )
|
|
{
|
|
bit_count -= 8;
|
|
*out_bytes_ref++= ( bit_buffer >> bit_count ) & 0xff;
|
|
}
|
|
}
|
|
|
|
if( bit_count > 0 )
|
|
{
|
|
*out_bytes_ref++= ( bit_buffer << ( 8 - bit_count ) ) & 0xff;
|
|
}
|
|
}
|
|
}
|
|
|
|
memcpy( out_bytes_ref, in_pep->bytes, in_pep->bytes_size );
|
|
out_bytes_ref += in_pep->bytes_size;
|
|
*out_bytes_ref = '\0';
|
|
|
|
*out_size = out_bytes_ref - out_bytes;
|
|
return out_bytes;
|
|
}
|
|
|
|
pep pep_deserialize( const uint8_t* const in_bytes, const uint32_t in_bytes_size )
|
|
{
|
|
pep out_pep = { 0 };
|
|
|
|
if( !in_bytes || in_bytes_size == 0 ) return out_pep;
|
|
|
|
const uint8_t* bytes_ref = in_bytes;
|
|
const uint8_t* const bytes_end = in_bytes + in_bytes_size;
|
|
|
|
// packed flags
|
|
if( bytes_ref + 1 > bytes_end ) return out_pep;
|
|
uint8_t packed_flags = *bytes_ref++;
|
|
if( bytes_ref + 1 > bytes_end ) return out_pep;
|
|
out_pep.model = *bytes_ref++;
|
|
out_pep.format = ( pep_format ) ( packed_flags & 0x3 );
|
|
out_pep.channel_bits = ( pep_channel_bits ) ( ( packed_flags >> 2 ) & 0x3 );
|
|
uint8_t is_small = ( packed_flags >> 4 ) & 0x1;
|
|
uint8_t only_rgb = ( packed_flags >> 5 ) & 0x1;
|
|
uint8_t is_bitmap = ( packed_flags >> 6 ) & 0x1;
|
|
|
|
// width/height
|
|
uint8_t dim_bytes = is_small ? 2 : 3;
|
|
if( bytes_ref + dim_bytes > bytes_end ) return out_pep;
|
|
uint16_t w,
|
|
h;
|
|
if( is_small )
|
|
{
|
|
w = *bytes_ref++;
|
|
h = *bytes_ref++;
|
|
}
|
|
else
|
|
{
|
|
uint32_t packed_dims = ( *bytes_ref++ << 16 );
|
|
packed_dims |= ( *bytes_ref++ << 8 );
|
|
packed_dims |= *bytes_ref++;
|
|
w = ( packed_dims >> 12 ) & 0xfff;
|
|
h = packed_dims & 0xfff;
|
|
}
|
|
out_pep.width = w + 1;
|
|
out_pep.height = h + 1;
|
|
|
|
// variable-length size
|
|
uint32_t bytes_size = 0;
|
|
uint8_t shift = 0;
|
|
uint8_t byte_val;
|
|
do
|
|
{
|
|
if( bytes_ref >= bytes_end ) return out_pep;
|
|
byte_val = *bytes_ref++;
|
|
if( shift < 32 ) bytes_size |= ( ( uint32_t )( byte_val &0x7f ) ) << shift;
|
|
shift += 7;
|
|
}
|
|
while( ( byte_val & 0x80 ) && shift < 35 );
|
|
out_pep.bytes_size = bytes_size;
|
|
|
|
// handle bitmap or read palette
|
|
uint32_t remaining = ( uint32_t )( bytes_end - bytes_ref );
|
|
|
|
if( is_bitmap )
|
|
{
|
|
if( remaining < bytes_size ) return out_pep;
|
|
|
|
out_pep.palette_size = 2;
|
|
out_pep.palette[ 0 ] = out_pep.format <= pep_bgra ? 0xff000000 : 0x000000ff;
|
|
out_pep.palette[ 1 ] = 0xffffffff;
|
|
}
|
|
else
|
|
{
|
|
// palette size
|
|
if( remaining < 1 ) return out_pep;
|
|
out_pep.palette_size = *bytes_ref++;
|
|
remaining--;
|
|
|
|
uint16_t palette_count = out_pep.palette_size ? out_pep.palette_size : 256;
|
|
|
|
// palette
|
|
const uint8_t channel_bits = 1 << out_pep.channel_bits;
|
|
const uint8_t mask = ( 1 << channel_bits ) - 1;
|
|
uint32_t palette_bytes;
|
|
if( channel_bits == 8 )
|
|
{
|
|
palette_bytes = palette_count * ( only_rgb ? 3 : 4 );
|
|
}
|
|
else
|
|
{
|
|
uint16_t channels = only_rgb ? 3 : 4;
|
|
palette_bytes = ( channel_bits * channels * palette_count + 7 ) >> 3;
|
|
}
|
|
|
|
if( remaining < palette_bytes || remaining - palette_bytes < bytes_size ) return out_pep;
|
|
|
|
if( channel_bits == 8 )
|
|
{
|
|
for( uint16_t index = 0; index < palette_count; index++ )
|
|
{
|
|
uint8_t* color = ( uint8_t* )( &( out_pep.palette[ index ] ) );
|
|
color[ 0 ] = *bytes_ref++;
|
|
color[ 1 ] = *bytes_ref++;
|
|
color[ 2 ] = *bytes_ref++;
|
|
color[ 3 ] = only_rgb ? 0xff : *bytes_ref++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32_t bit_buffer = 0;
|
|
uint8_t bit_count = 0;
|
|
|
|
for( uint16_t index = 0; index < palette_count; index++ )
|
|
{
|
|
uint8_t channels = only_rgb ? 3 : 4;
|
|
uint8_t channel_values[ 4 ];
|
|
|
|
for( uint8_t c = 0; c < channels; c++ )
|
|
{
|
|
while( bit_count < channel_bits )
|
|
{
|
|
bit_buffer = ( bit_buffer << 8 ) | *bytes_ref++;
|
|
bit_count += 8;
|
|
}
|
|
bit_count -= channel_bits;
|
|
channel_values[ c ] = ( bit_buffer >> bit_count ) & mask;
|
|
|
|
uint8_t scaled = channel_values[ c ] << ( 8 - channel_bits );
|
|
if( channel_bits < 8 )
|
|
{
|
|
scaled |= ( scaled >> channel_bits );
|
|
if( channel_bits < 4 )
|
|
{
|
|
scaled |= ( scaled >> ( 2 * channel_bits ) );
|
|
}
|
|
}
|
|
channel_values[ c ] = scaled;
|
|
}
|
|
|
|
uint8_t* color = ( uint8_t* )( &( out_pep.palette[ index ] ) );
|
|
color[ 0 ] = channel_values[ 0 ];
|
|
color[ 1 ] = channel_values[ 1 ];
|
|
color[ 2 ] = channel_values[ 2 ];
|
|
color[ 3 ] = only_rgb ? 0xff : channel_values[ 3 ];
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy image data
|
|
out_pep.bytes = ( uint8_t* )PEP_MALLOC( bytes_size );
|
|
if( out_pep.bytes )
|
|
{
|
|
memcpy( out_pep.bytes, bytes_ref, bytes_size );
|
|
}
|
|
|
|
return out_pep;
|
|
}
|
|
|
|
#pragma endregion serialization
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
#pragma region - file
|
|
|
|
// For both save/load, file_path should end in ".pep":
|
|
// e.g. "texture.pep", "assets/image.pep"
|
|
|
|
// Saves pep into a file.
|
|
// Returns 0 on failure, 1 on success
|
|
uint8_t pep_save( const pep* const in_pep, const char* const file_path )
|
|
{
|
|
if( !in_pep || !file_path )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
uint32_t bytes_size = 0;
|
|
uint8_t* bytes = pep_serialize( in_pep, &bytes_size );
|
|
|
|
if( !bytes || bytes_size == 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
FILE * file = fopen( file_path, "wb" );
|
|
if( !file )
|
|
{
|
|
PEP_FREE( bytes );
|
|
return 0;
|
|
}
|
|
|
|
size_t written = fwrite( bytes, 1, bytes_size, file );
|
|
|
|
fclose( file );
|
|
PEP_FREE( bytes );
|
|
|
|
#ifdef PEP_DEBUG
|
|
printf( "pep: %lld\nfile: %zu\nmodel: %d\n", in_pep->bytes_size, written, in_pep->model );
|
|
#endif
|
|
|
|
return written == bytes_size;
|
|
}
|
|
|
|
// Loads .pep file into returned pep struct
|
|
pep pep_load( const char* const file_path )
|
|
{
|
|
pep out_pep = { 0 };
|
|
|
|
if( !file_path )
|
|
{
|
|
return out_pep;
|
|
}
|
|
|
|
FILE * file = fopen( file_path, "rb" );
|
|
if( !file )
|
|
{
|
|
return out_pep;
|
|
}
|
|
|
|
fseek( file, 0, SEEK_END );
|
|
long file_size = ftell( file );
|
|
fseek( file, 0, SEEK_SET );
|
|
|
|
if( file_size <= 0 )
|
|
{
|
|
fclose( file );
|
|
return out_pep;
|
|
}
|
|
|
|
uint8_t* bytes = ( uint8_t* )PEP_MALLOC( file_size );
|
|
|
|
size_t read = fread( bytes, 1, file_size, file );
|
|
fclose( file );
|
|
|
|
if( read != ( size_t ) file_size )
|
|
{
|
|
PEP_FREE( bytes );
|
|
return out_pep;
|
|
}
|
|
|
|
out_pep = pep_deserialize( bytes, ( uint32_t )read );
|
|
PEP_FREE( bytes );
|
|
|
|
#ifdef PEP_DEBUG
|
|
printf( "\npep: %lld\nfile: %ld\n", out_pep.bytes_size, file_size );
|
|
#endif
|
|
|
|
return out_pep;
|
|
}
|
|
|
|
#pragma endregion file
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning( pop )
|
|
#endif
|
|
|
|
#endif // PEP_IMPLEMENTATION
|
|
|
|
|
|
#pragma endregion definitions
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|