{"id":52033966,"url":"https://github.com/lieff/miniwtpc","last_synced_at":"2026-08-01T18:01:19.403Z","repository":{"id":371028240,"uuid":"1297770083","full_name":"lieff/miniwtpc","owner":"lieff","description":"Small Wavelet Thumbnail \u0026 Preview Codec - WTPC","archived":false,"fork":false,"pushed_at":"2026-07-29T22:37:22.000Z","size":21284,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-30T00:10:50.680Z","etag":null,"topics":["c","codec","decoder","encoder","image-codec","image-compression","lossy-compression","minimal","minimalist","minimalistic","single-header","single-header-lib","single-header-library","small","tiny","wavelet","wavelet-compression"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lieff.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-11T20:42:31.000Z","updated_at":"2026-07-29T22:37:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lieff/miniwtpc","commit_stats":null,"previous_names":["lieff/miniwtpc"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lieff/miniwtpc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lieff%2Fminiwtpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lieff%2Fminiwtpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lieff%2Fminiwtpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lieff%2Fminiwtpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lieff","download_url":"https://codeload.github.com/lieff/miniwtpc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lieff%2Fminiwtpc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36165355,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-08-01T02:00:05.789Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","codec","decoder","encoder","image-codec","image-compression","lossy-compression","minimal","minimalist","minimalistic","single-header","single-header-lib","single-header-library","small","tiny","wavelet","wavelet-compression"],"created_at":"2026-08-01T18:01:18.718Z","updated_at":"2026-08-01T18:01:19.396Z","avatar_url":"https://github.com/lieff.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Small Wavelet Thumbnail \u0026 Preview Codec - WTPC\n\nA simple, drop-in image codec in the style of stb_image (single header library).\nIt targets low sizes from 200 B to 36 KB at resolutions around 256x256, but supports\nany image dimensions up to 65536x65536 (practically tested up to ~27K with\nimages loadable by stb_image).\nThe main target for thumbnails is 1400 B -- designed to fit within one MTU\npacket, so the user sees *something* while the main preview downloads.\n\nIt has two modes: fast Huffman and slower EBCOT-lite (much simpler than\nJPEG 2000 -- not even Tier-1, since that would need far more code).\nDespite its simplicity, WTPC outperforms JPEG 2000 and JPEG XL on this\nsmall-image benchmark, likely because its quantization is tuned to sharpen\nat low bitrates and the test dataset is relatively small (~3000 images).\n\nCurrently WIP, bitstream format is not yet stable and may change if further quality improvements are found.\n\n**Confirmed to outperform** (by ssimulacra2, 256x256, 200 B - 36 KB):\n- JPEG (libjpeg)\n- JPEG 2000 (OpenJPEG 2.5.4)\n- HTJ2K (openjph 0.26.0, clear win in our range 200 B - 36 KB +1..+24 ssim2, HTJ2K wins only at \u003e62 KB where WTPC hits int16 quality ceiling; encode speed comparable 3-4ms vs WTPC 1-9ms)\n- JPEG XL (libjxl 0.11.1)\n- AVIF --speed 10 (avifenc 1.3.0; speeds 0/6 partially win but encoding is 8-370x slower)\n- WebP (libwebp 1.5.0, clear win across all sizes +2..+15 ssim2, similar encode speed 1-9ms vs WebP 6-9ms)\n- WebP 2 (libwebp2 0.0.1, partial: WTPC wins at \u003c=2.6 KB and 36 KB, WebP2 wins at 2.9-21 KB but encoding is 4-10x slower)\n- HEIF (libheif 1.21.2 / x265 4.1, partial: WTPC wins at \u003c=3 KB and \u003e=15 KB, HEIF wins at 3-15 KB but encoding is 3-5x slower and quality plateaus at ~91.8 ssim2)\n- BPG 0.9.8 x265 (libbpg 0.9.8 / x265 4.1, partial: WTPC wins at \u003c526 B (BPG minimum) and \u003e=21 KB, BPG wins at 0.5-21 KB by +2..+12 ssim2 but encoding is 3-5x slower; quality plateaus at ~93 ssim2 vs WTPC 95)\n- BPG 0.9.8 JCTVC (libbpg 0.9.8, partial: WTPC wins at \u003c380 B (BPG minimum) and \u003e=21 KB, BPG wins at 0.4-21 KB by +0.7..+12 ssim2 but encoding is 20-50x slower; quality plateaus at ~93 ssim2 vs WTPC 95)\n- VC-2 SMPTE 2042-1 (reference encoder 0.1.0.2, Daub97 DD97 wavelets, clear win across all sizes +29..+50 ssim2)\n- SPIHT (TiLib 1.0, Daub97 wavelet)\n- GFWX 1.2 (Golomb-Rice entropy coder)\n- SQZ (5/3 wavelet + WDR, no entropy coding)\n- Ako 0.3.0 (CDF 9/7 + Kagari/ANS)\n- NHW 0.3.3 (simple wavelet, speed-oriented, only 512x512)\n\n## API and usage\n\n### Integration (single-header library)\n\nWTPC follows the stb_image pattern: the header `wtpc_image.h` contains both\nthe API declarations and the implementation, gated by a macro.\n\n```c\n// In ONE .c file, define the implementation macro before including:\n#define WTPC_IMAGE_IMPLEMENTATION\n#include \"wtpc_image.h\"\n\n// Everywhere else, just include the header to get the API declarations:\n#include \"wtpc_image.h\"\n```\n\nThe header pulls in `\u003cstdint.h\u003e`, `\u003cstdlib.h\u003e`, `\u003cstring.h\u003e`, `\u003cmath.h\u003e`,\nand optionally `\u003cstdio.h\u003e` (exclude with `WTPC_NO_STDIO`).  On x86-64 the\nbuild script auto-enables AVX; add `-mavx` manually on other compilers.\nLink with `-lm`.\n\n### C API\n\n```c\n   === API ===\n\n   typedef struct {\n       int encoded_bytes;   - output number of bytes\n       int result_q;        - resulting quantization factor if target_bytes provided, or same as 'quality' if target_bytes=0\n       int search_steps;    - number of iterations to search target bytes quantization \n       int ebcot;           - 1 = ebcot or 0 = huffman mode for best pick if auto huffman_mode used\n       int huffman_y_size;  - in bits if not picked static table\n       int huffman_u_size;\n       int huffman_v_size;\n       int huffman_y_table; - 0..NUM_DEF_TABLES-1 - static, NUM_DEF_TABLES - custom written in bitstream\n       int huffman_u_table;\n       int huffman_v_table;\n   } wtpc_enc_info;\n\n   unsigned char *wtpc_encode_mem(const unsigned char *rgb, wtpc_enc_info *info,\n       int w, int h, int target_bytes, int quality, int chroma_420,\n       int huffman_mode, int huf_extra_ctx, int has_alpha, int stride);\n     Encode an RGB/RGBA image in memory. Returns malloc'd WTPC bitstream,\n     or NULL on error. Caller must free().\n       rgb           : input pixels, h rows of stride bytes each.\n                        Each row has w pixels, 3 bytes/pixel (RGB) or 4 (RGBA).\n       info          : output struct, filled with encoding details (may be NULL).\n       w, h          : image dimensions (\u003e= 1).\n       target_bytes  : desired output size in bytes. 0 = use 'quality' instead.\n                       When \u003e 0, the encoder does a binary search over the\n                       quality range [1..MAX_QUALITY] to hit the target.\n       quality       : quantization level 1..MAX_QUALITY (1024). Lower = better\n                       quality / larger file. Used only when target_bytes == 0.\n       chroma_420    : 0 = 4:4:4 (full chroma), 1 = 4:2:0 (half chroma).\n                       4:2:0 saves ~15-30% bytes with minor visual loss.\n       huffman_mode  : 0 = auto-pick smaller of ebcot/huffman,\n                       1 = huffman, 2 = ebcot.\n       huf_extra_ctx : 0 = single Huffman table (faster),\n                       1 = two context-switched tables (slightly better).\n       has_alpha     : 0 = RGB (3 channels), 1 = RGBA (4 channels).\n       stride        : bytes per row (0 = tightly packed = w * pixel_bytes).\n                        Allows BMP-like padded data without repacking.\n\n   unsigned char *wtpc_decode_mem(const unsigned char *data, int data_len,\n       int *w, int *h, int *out_quality, int *out_comp);\n     Decode a WTPC bitstream from memory. Returns malloc'd pixel buffer\n     (w*h*3 for RGB, w*h*4 for RGBA). Caller must free().\n       data          : input WTPC bitstream bytes.\n       data_len      : number of bytes in 'data'.\n       w, h          : output image dimensions.\n       out_quality   : quality level used for encoding (may be NULL).\n       out_comp      : number of color components: 3 = RGB, 4 = RGBA (may be NULL).\n\n   int wtpc_encode_file(const char *out_path, const unsigned char *rgb,\n       wtpc_enc_info *info, int w, int h, int target_bytes, int quality,\n       int chroma_420, int huffman_mode, int huf_extra_ctx, int has_alpha, int stride);\n     Same as wtpc_encode_mem but writes directly to a file.\n     Returns 0 on success, -1 on error.\n\n   unsigned char *wtpc_decode_file(const char *in_path,\n       int *w, int *h, int *out_quality, int *out_comp);\n     Same as wtpc_decode_mem but reads from a file.\n\n   === Build-time options ===\n     #define WTPC_NO_STDIO        : exclude file I/O functions.\n     #define DEBUG_WAVELET        : dump wavelet coefficient images (needs stb).\n     #define STANDARD_CDF97       : enable standard CDF 9/7 K-scaling.\n     #define BAC_USE_TABLE        : use 64 KB reciprocal lookup table for\n                                    BAC division (~+1-3% speed, 64 KB memory).\n                                    Default: 64-bit integer division.\n     #define WTPC_TUNE_PARAMS     : mutable quantization tables for grid-search tuning.\n     #define WTPC_TUNE_CTX        : tune ebcot contexts\n     #define WTPC_NO_SIMD         : do not use sse/avx/neon intrinsics.\n     #define WTPC_RC_ONLY_LESS_THAN_TARGET : rate control never overshoots\n                                    target_bytes (picks the largest size \u003c= target\n                                    instead of the closest). Implied by\n                                    WTPC_TUNE_PARAMS.\n```\n\n### CLI tool flags\n\nThe standalone `wtpc` binary (built via `build.sh` or `gcc -O3 wtpc.c -o wtpc -lm -lpng16`) uses these flags:\n\n| Flag | Description |\n|------|-------------|\n| `-e in.png` | Encode mode (requires `-o out.wtp`) |\n| `-d in.wtp` | Decode mode (requires `-o out.png`) |\n| `-t in.png` | Self-test: encode + decode + compare PSNR |\n| `-q N` | Quality 1..1024 (lower = better/larger) |\n| `-b N` | Target file size in bytes (auto-finds q) |\n| `-c` | Use 4:2:0 chroma subsampling |\n| `-m best\\|ebcot\\|huffman` | Encoding mode (default: ebcot) |\n| `-h 1` | Context-aware Huffman tables (slower, slightly better) |\n| `-o file` | Output file path |\n| `-G dir` | Generate Huffman tables from images in directory |\n| `-P dir` | Tune EBCOT contexts from images (needs `WTPC_TUNE_CTX`) |\n| `-T dir` | Tune quantization parameters (needs `WTPC_TUNE_PARAMS`) |\n| `-R dir` | Train DC priors (needs `WTPC_TUNE_PARAMS`) |\n| `-S N` | Start tuning from parameter set N |\n| `-420` | Tune 4:2:0 mode (with `-T` / `-R`) |\n| `-v` | Verbose tuning output |\n\n### Tuning and retraining\n\nYou can retrain quantization parameters, DC priors, Huffman tables, and\nEBCOT contexts on your own dataset:\n\n1. Build with `WTPC_TUNE_PARAMS` (and optionally `WTPC_TUNE_CTX` for -P)\n2. Run `./wtpc -T images/` to tune quantization tables\n3. Run `./wtpc -R images/` to train DC priors\n4. Run `./wtpc -G images/` to generate Huffman tables\n5. Run `./wtpc -P images/` to tune EBCOT contexts (requires WTPC_TUNE_CTX instead of WTPC_TUNE_PARAMS)\n6. Paste the printed tables back into `wtpc_image.h`\n\nUse `-420` to tune the 4:2:0 variants, `-S N` to continue from a specific\nparameter set, and `-v` for verbose progress output.\n\n**Note:** tuning changes the bitstream format, making it incompatible with\nthe release version.\n\n## Benchmark: WTPC vs JPEG vs JPEG 2000 vs JPEG XL\n\n**Test image:** `lena256.png` (256x256, 24-bit RGB)  \n**Target range:** 200 B -- 36 KB  \n**Metrics:** PSNR (dB, higher is better), ssimulacra2 (higher is better)  \n**Full results:** [results.md](results.md)\n\n### Best Codec by Target Size (by PSNR)\n\n| Target | Best Codec         | Size   | PSNR   | ssimulacra2 |\n|--------|--------------------|--------|--------|-------------|\n| 200 B | WTPC 4:2:0 EBCOT | 205 B | 19.69 | -60.51 |\n| 400 B | WTPC 4:2:0 EBCOT | 402 B | 22.04 | -39.34 |\n| 600 B | WTPC 4:4:4 EBCOT | 605 B | 23.09 | -21.70 |\n| 800 B | WTPC 4:4:4 EBCOT | 804 B | 24.02 | -9.24 |\n| 1 KB | WTPC 4:4:4 EBCOT | 1405 B | 25.90 | 19.97 |\n| 2 KB | WTPC 4:4:4 EBCOT | 1997 B | 27.14 | 35.16 |\n| 3 KB | WTPC 4:4:4 EBCOT | 3001 B | 28.49 | 50.28 |\n| 4 KB | WTPC 4:4:4 EBCOT | 3999 B | 29.57 | 58.21 |\n| 5 KB | WTPC 4:4:4 EBCOT | 5007 B | 30.53 | 64.56 |\n| 6 KB | WTPC 4:4:4 EBCOT | 6000 B | 31.47 | 69.72 |\n| 8 KB | WTPC 4:4:4 EBCOT | 8018 B | 33.08 | 75.64 |\n| 10 KB | WTPC 4:4:4 EBCOT | 9993 B | 34.40 | 79.99 |\n| 13 KB | WTPC 4:4:4 EBCOT | 13035 B | 35.96 | 84.06 |\n| 15 KB | WTPC 4:4:4 EBCOT | 15026 B | 36.74 | 85.96 |\n| 18 KB | WTPC 4:4:4 EBCOT | 17990 B | 37.80 | 88.52 |\n| 22 KB | WTPC 4:4:4 EBCOT | 22054 B | 39.10 | 90.42 |\n| 28 KB | WTPC 4:4:4 EBCOT | 28004 B | 40.76 | 92.34 |\n| 36 KB | WTPC 4:4:4 EBCOT | 36067 B | 42.61 | 93.75 |\n\n### Speed Summary (lena 256x256, representative q=244)\n\n| Codec               | Encode (ms) | Decode (ms) |\n|---------------------|-------------|-------------|\n| WTPC EBCOT 4:4:4 | 8 | 7 |\n| WTPC Huffman 4:4:4 | 1 | 1 |\n| WTPC EBCOT 4:2:0 | 4 | 4 |\n| WTPC Huffman 4:2:0 | 1 | 1 |\n| JPEG 2000 | 16 | 5 |\n| JPEG XL | 103 | 3 |\n| JPEG | 4 | 3 |\n\nSee [results.md](results.md) for the complete per-size breakdown, speed\nmeasurements across all quality levels, mermaid charts, and raw data.\n\n### Visual Comparison (lena 256x256)\n\nClick any image to view full size.\n\n**1.4 KB** -- thumbnail target (worst quality)\n\n| WTPC EBCOT | WTPC Huffman | JPEG 2000 | JPEG XL | JPEG |\n|:----------:|:------------:|:---------:|:-------:|:----:|\n| ![](samples/WTPC_E_worst_1.4kb.png) | ![](samples/WTPC_H_worst_1.4kb.png) | ![](samples/JP2K_worst_1.4kb.png) | ![](samples/JXL_worst_1.4kb.png) | ![](samples/JPEG_worst_1.4kb.jpg) |\n\n**6 KB** -- preview (mid quality)\n\n| WTPC EBCOT | WTPC Huffman | JPEG 2000 | JPEG XL | JPEG |\n|:----------:|:------------:|:---------:|:-------:|:----:|\n| ![](samples/WTPC_E_mid_6kb.png) | ![](samples/WTPC_H_mid_6kb.png) | ![](samples/JP2K_mid_6kb.png) | ![](samples/JXL_mid_6kb.png) | ![](samples/JPEG_mid_6kb.jpg) |\n\n**13 KB** -- good quality\n\n| WTPC EBCOT | WTPC Huffman | JPEG 2000 | JPEG XL | JPEG |\n|:----------:|:------------:|:---------:|:-------:|:----:|\n| ![](samples/WTPC_E_good_13kb.png) | ![](samples/WTPC_H_good_13kb.png) | ![](samples/JP2K_good_13kb.png) | ![](samples/JXL_good_13kb.png) | ![](samples/JPEG_good_13kb.jpg) |\n\n**36 KB** -- best quality\n\n| WTPC EBCOT | WTPC Huffman | JPEG 2000 | JPEG XL | JPEG |\n|:----------:|:------------:|:---------:|:-------:|:----:|\n| ![](samples/WTPC_E_best_36kb.png) | ![](samples/WTPC_H_best_36kb.png) | ![](samples/JP2K_best_36kb.png) | ![](samples/JXL_best_36kb.png) | ![](samples/JPEG_best_36kb.jpg) |\n\n**200 B -- 1.2 KB** -- ultra-low bitrates (JPEG XL cannot reach this range)\n\n| Size | WTPC EBCOT | JPEG 2000 | JPEG |\n|:----:|:----------:|:---------:|:----:|\n| 200 B | ![](samples/WTPC_200b.png) | ![](samples/JP2K_200b.png) | - |\n| 400 B | ![](samples/WTPC_400b.png) | ![](samples/JP2K_400b.png) | - |\n| 600 B | ![](samples/WTPC_600b.png) | ![](samples/JP2K_600b.png) | - |\n| 800 B | ![](samples/WTPC_800b.png) | ![](samples/JP2K_800b.png) | - |\n| 1000 B | ![](samples/WTPC_1000b.png) | ![](samples/JP2K_1000b.png) | ![](samples/JPEG_1000b.jpg) |\n| 1200 B | ![](samples/WTPC_1200b.png) | ![](samples/JP2K_1200b.png) | ![](samples/JPEG_1200b.jpg) |\n\n**AVIF --speed 6 vs WTPC (best ssim2)** -- mid-speed AVIF vs best WTPC at equal file sizes\n\n| Size | AVIF (--speed 6) | WTPC (best by ssim2) |\n|:----:|:----------------:|:--------------------:|\n| ~726 B | ![](samples/AVIF_S6_726b.png) | ![](samples/WTPC_vs_AVIF_726b.png) |\n| 1 KB | ![](samples/AVIF_S6_1kb.png) | ![](samples/WTPC_vs_AVIF_1kb.png) |\n| 1.4 KB | ![](samples/AVIF_S6_1.4kb.png) | ![](samples/WTPC_vs_AVIF_1.4kb.png) |\n| 2 KB | ![](samples/AVIF_S6_2kb.png) | ![](samples/WTPC_vs_AVIF_2kb.png) |\n| 4 KB | ![](samples/AVIF_S6_4kb.png) | ![](samples/WTPC_vs_AVIF_4kb.png) |\n| 16 KB | ![](samples/AVIF_S6_16kb.png) | ![](samples/WTPC_vs_AVIF_16kb.png) |\n| 36 KB | ![](samples/AVIF_S6_36kb.png) | ![](samples/WTPC_vs_AVIF_36kb.png) |\n\n## Interesting Links\n\n * https://github.com/nothings/stb\n * https://github.com/kalcutter/gfwx\n * https://github.com/Themaister/pyrowave\n * https://github.com/MarcioPais/SQZ\n * https://github.com/josejuansanchez/bgp-image-format\n * https://bellard.org/bpg/\n * https://github.com/LMP88959/Digital-Subband-Video-2\n * https://github.com/curioustorvald/TAV-video-codec\n * https://github.com/datocms/fast_thumbhash\n * https://github.com/gopro/cineform-sdk\n * https://github.com/emericg/libcineform\n * https://github.com/bbc/vc2-reference\n * https://github.com/rcanut/nhwcodec\n * https://github.com/baAlex/Ako\n * https://github.com/Special-graphic-formats/tilib\n * https://themaister.net/blog/2025/06/16/i-designed-my-own-ridiculously-fast-game-streaming-video-codec-pyrowave/\n\n## Image Datasets\n\n * https://github.com/imazen/codec-corpus\n * https://github.com/castano/image-datasets\n * https://jpegai.github.io/test_images/\n * https://github.com/EliSchwartz/imagenet-sample-images\n * https://cloudinary.com/labs/cid22\n * https://www.imageprocessingplace.com/root_files_V3/image_databases.htm\n * https://samplelib.com/sample-png.html\n * https://www.stickpng.com/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flieff%2Fminiwtpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flieff%2Fminiwtpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flieff%2Fminiwtpc/lists"}