Skip to content

Instantly share code, notes, and snippets.

@moomerman
Last active December 13, 2025 10:49
Show Gist options
  • Select an option

  • Save moomerman/2009445e6ee4ca59e9eafce511f29f7d to your computer and use it in GitHub Desktop.

Select an option

Save moomerman/2009445e6ee4ca59e9eafce511f29f7d to your computer and use it in GitHub Desktop.
/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb
no warranty implied; use at your own risk
Do this:
#define STB_IMAGE_IMPLEMENTATION
before you include this file in *one* C or C++ file to create the implementation.
// i.e. it should look like this:
#include ...
#include ...
#include ...
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
You can #define STBI_ASSERT(x) before the #include to avoid using assert.h.
And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free
QUICK NOTES:
Primarily of interest to game developers and other people who can
avoid problematic images and only need the trivial interface
JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)
PNG 1/2/4/8/16-bit-per-channel
TGA (not sure what subset, if a subset)
BMP non-1bpp, non-RLE
PSD (composited view only, no extra channels, 8/16 bit-per-channel)
GIF (*comp always reports as 4-channel)
HDR (radiance rgbE format)
PIC (Softimage PIC)
PNM (PPM and PGM binary only)
Animated GIF still needs a proper API, but here's one way to do it:
http://gist.github.com/urraka/685d9a6340b26b830d49
- decode from memory or through FILE (define STBI_NO_STDIO to remove code)
- decode from arbitrary I/O callbacks
- SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
Full documentation under "DOCUMENTATION" below.
LICENSE
See end of file for license information.
RECENT REVISION HISTORY:
2.30 (2024-05-31) avoid erroneous gcc warning
2.29 (2023-05-xx) optimizations
2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
2.26 (2020-07-13) many minor fixes
2.25 (2020-02-02) fix warnings
2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically
2.23 (2019-08-11) fix clang static analysis warning
2.22 (2019-03-04) gif fixes, fix warnings
2.21 (2019-02-25) fix typo in comment
2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
2.19 (2018-02-11) fix warning
2.18 (2018-01-30) fix warnings
2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings
2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes
2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC
2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes
2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64
RGB-format JPEG; remove white matting in PSD;
allocate large structures on the stack;
correct channel count for PNG & BMP
2.10 (2016-01-22) avoid warning introduced in 2.09
2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED
See end of file for full revision history.
============================ Contributors =========================
Image formats Extensions, features
Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info)
Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info)
Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG)
Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks)
Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG)
Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip)
Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD)
github:urraka (animated gif) Junggon Kim (PNM comments)
Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA)
socks-the-fox (16-bit PNG)
Jeremy Sawicki (handle all ImageNet JPGs)
Optimizations & bugfixes Mikhail Morozov (1-bit BMP)
Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query)
Arseny Kapoulkine Simon Breuss (16-bit PNM)
John-Mark Allen
Carmelo J Fdez-Aguera
Bug & warning fixes
Marc LeBlanc David Woo Guillaume George Martins Mozeiko
Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski
Phil Jordan Dave Moore Roy Eltham
Hayaki Saito Nathan Reed Won Chun
Luke Graham Johan Duparc Nick Verigakis the Horde3D community
Thomas Ruf Ronny Chevalier github:rlyeh
Janez Zemva John Bartholomew Michal Cichon github:romigrou
Jonathan Blow Ken Hamada Tero Hanninen github:svdijk
Eugene Golushkov Laurent Gomila Cort Stratton github:snagar
Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex
Cass Everitt Ryamond Barbiero github:grim210
Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw
Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus
Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo
Julian Raschke Gregory Mullen Christian Floisand github:darealshinji
Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007
Brad Weinberger Matvey Cherevko github:mosra
Luca Sas Alexander Veselov Zack Middleton [reserved]
Ryan C. Gordon [reserved] [reserved]
DO NOT ADD YOUR NAME HERE
Jacko Dirks
To add your name to the credits, pick a random blank space in the middle and fill it.
80% of merge conflicts on stb PRs are due to people adding their name at the end
of the credits.
*/
module stb_image::stbi;
const int STBI_VERSION = 1;
const int STBI_DEFAULT = 0;
const int STBI_GREY_ALPHA = 2;
const int STBI_GREY = 1;
const int STBI_RGB = 3;
const int STBI_RGB_ALPHA = 4;
//
// load image by filename, open file, or memory buffer
//
alias StbiIoCallbacksReadFn = fn int(void*, ZString, int);
alias StbiIoCallbacksSkipFn = fn void(void*, int);
alias StbiIoCallbacksEofFn = fn int(void*);
struct StbiIoCallbacks
{
StbiIoCallbacksReadFn read; // fill 'data' with 'size' bytes. return number of bytes actually read
StbiIoCallbacksSkipFn skip; // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
StbiIoCallbacksEofFn eof; // returns nonzero if we are at end of file/data
}
//
// load image by filename, open file, or memory buffer
//
////////////////////////////////////
//
// 8-bits-per-channel interface
//
extern fn char* load_from_memory(char* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_load_from_memory");
extern fn char* load_from_callbacks(StbiIoCallbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_load_from_callbacks");
extern fn char* load(ZString filename, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_load");
extern fn char* load_from_file(int* f, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_load_from_file");
extern fn char* load_gif_from_memory(char* buffer, int len, int** delays, int* x, int* y, int* z, int* comp, int req_comp) @cname("stbi_load_gif_from_memory");
////////////////////////////////////
//
// 16-bits-per-channel interface
//
extern fn ushort* load_16_from_memory(char* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_load_16_from_memory");
extern fn ushort* load_16_from_callbacks(StbiIoCallbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_load_16_from_callbacks");
extern fn ushort* load_16(ZString filename, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_load_16");
extern fn ushort* load_from_file_16(int* f, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_load_from_file_16");
extern fn float* loadf_from_memory(char* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_loadf_from_memory");
extern fn float* loadf_from_callbacks(StbiIoCallbacks* clbk, void* user, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_loadf_from_callbacks");
extern fn float* loadf(ZString filename, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_loadf");
extern fn float* loadf_from_file(int* f, int* x, int* y, int* channels_in_file, int desired_channels) @cname("stbi_loadf_from_file");
extern fn void hdr_to_ldr_gamma(float gamma) @cname("stbi_hdr_to_ldr_gamma");
extern fn void hdr_to_ldr_scale(float scale) @cname("stbi_hdr_to_ldr_scale");
extern fn void ldr_to_hdr_gamma(float gamma) @cname("stbi_ldr_to_hdr_gamma");
extern fn void ldr_to_hdr_scale(float scale) @cname("stbi_ldr_to_hdr_scale");
// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR
extern fn int is_hdr_from_callbacks(StbiIoCallbacks* clbk, void* user) @cname("stbi_is_hdr_from_callbacks");
extern fn int is_hdr_from_memory(char* buffer, int len) @cname("stbi_is_hdr_from_memory");
extern fn int is_hdr(ZString filename) @cname("stbi_is_hdr");
extern fn int is_hdr_from_file(int* f) @cname("stbi_is_hdr_from_file");
// get a VERY brief reason for failure
// on most compilers (and ALL modern mainstream compilers) this is threadsafe
extern fn ZString failure_reason() @cname("stbi_failure_reason");
// free the loaded image -- this is just free()
extern fn void image_free(void* retval_from_stbi_load) @cname("stbi_image_free");
// get image dimensions & components without fully decoding
extern fn int info_from_memory(char* buffer, int len, int* x, int* y, int* comp) @cname("stbi_info_from_memory");
extern fn int info_from_callbacks(StbiIoCallbacks* clbk, void* user, int* x, int* y, int* comp) @cname("stbi_info_from_callbacks");
extern fn int is_16_bit_from_memory(char* buffer, int len) @cname("stbi_is_16_bit_from_memory");
extern fn int is_16_bit_from_callbacks(StbiIoCallbacks* clbk, void* user) @cname("stbi_is_16_bit_from_callbacks");
extern fn int info(ZString filename, int* x, int* y, int* comp) @cname("stbi_info");
extern fn int info_from_file(int* f, int* x, int* y, int* comp) @cname("stbi_info_from_file");
extern fn int is_16_bit(ZString filename) @cname("stbi_is_16_bit");
extern fn int is_16_bit_from_file(int* f) @cname("stbi_is_16_bit_from_file");
// for image formats that explicitly notate that they have premultiplied alpha,
// we just return the colors as stored in the file. set this flag to force
// unpremultiplication. results are undefined if the unpremultiply overflow.
extern fn void set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) @cname("stbi_set_unpremultiply_on_load");
// indicate whether we should process iphone images back to canonical format,
// or just pass them through "as-is"
extern fn void convert_iphone_png_to_rgb(int flag_true_if_should_convert) @cname("stbi_convert_iphone_png_to_rgb");
// flip the image vertically, so the first pixel in the output array is the bottom left
extern fn void set_flip_vertically_on_load(int flag_true_if_should_flip) @cname("stbi_set_flip_vertically_on_load");
// as above, but only applies to images loaded on the thread that calls the function
// this function is only available if your compiler supports thread-local variables;
// calling it will fail to link if your compiler doesn't
extern fn void set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) @cname("stbi_set_unpremultiply_on_load_thread");
extern fn void convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) @cname("stbi_convert_iphone_png_to_rgb_thread");
extern fn void set_flip_vertically_on_load_thread(int flag_true_if_should_flip) @cname("stbi_set_flip_vertically_on_load_thread");
// ZLIB client - used by PNG, available for other purposes
extern fn ZString zlib_decode_malloc_guesssize(ZString buffer, int len, int initial_size, int* outlen) @cname("stbi_zlib_decode_malloc_guesssize");
extern fn ZString zlib_decode_malloc_guesssize_headerflag(ZString buffer, int len, int initial_size, int* outlen, int parse_header) @cname("stbi_zlib_decode_malloc_guesssize_headerflag");
extern fn ZString zlib_decode_malloc(ZString buffer, int len, int* outlen) @cname("stbi_zlib_decode_malloc");
extern fn int zlib_decode_buffer(ZString obuffer, int olen, ZString ibuffer, int ilen) @cname("stbi_zlib_decode_buffer");
extern fn ZString zlib_decode_noheader_malloc(ZString buffer, int len, int* outlen) @cname("stbi_zlib_decode_noheader_malloc");
extern fn int zlib_decode_noheader_buffer(ZString obuffer, int olen, ZString ibuffer, int ilen) @cname("stbi_zlib_decode_noheader_buffer");
// Idiomatic C3 wrapper macros for stb_image
// These provide optional-based error handling for a more C3-native feel
faultdef IMAGE_LOAD_FAILED, IMAGE_INFO_FAILED;
// Load an image file, returning an optional that fails on error
macro char*? load_image(ZString filename, int* x, int* y, int* channels_in_file, int desired_channels = 0)
{
char* res = load(filename, x, y, channels_in_file, desired_channels);
if (!res) return IMAGE_LOAD_FAILED?;
return res;
}
// Load a 16-bit image file, returning an optional that fails on error
macro ushort*? load_image_16(ZString filename, int* x, int* y, int* channels_in_file, int desired_channels = 0)
{
ushort* res = load_16(filename, x, y, channels_in_file, desired_channels);
if (!res) return IMAGE_LOAD_FAILED?;
return res;
}
// Load an HDR image file, returning an optional that fails on error
macro float*? load_imagef(ZString filename, int* x, int* y, int* channels_in_file, int desired_channels = 0)
{
float* res = loadf(filename, x, y, channels_in_file, desired_channels);
if (!res) return IMAGE_LOAD_FAILED?;
return res;
}
// Load image from memory buffer, returning an optional that fails on error
macro char*? load_image_from_memory(char[] buffer, int* x, int* y, int* channels_in_file, int desired_channels = 0)
{
char* res = load_from_memory(buffer.ptr, (int)buffer.len, x, y, channels_in_file, desired_channels);
if (!res) return IMAGE_LOAD_FAILED?;
return res;
}
// Load 16-bit image from memory buffer, returning an optional that fails on error
macro ushort*? load_image_16_from_memory(char[] buffer, int* x, int* y, int* channels_in_file, int desired_channels = 0)
{
ushort* res = load_16_from_memory(buffer.ptr, (int)buffer.len, x, y, channels_in_file, desired_channels);
if (!res) return IMAGE_LOAD_FAILED?;
return res;
}
// Load HDR image from memory buffer, returning an optional that fails on error
macro float*? load_imagef_from_memory(char[] buffer, int* x, int* y, int* channels_in_file, int desired_channels = 0)
{
float* res = loadf_from_memory(buffer.ptr, (int)buffer.len, x, y, channels_in_file, desired_channels);
if (!res) return IMAGE_LOAD_FAILED?;
return res;
}
// Get image info without loading, returning an optional that fails on error
macro void? get_info(ZString filename, int* x, int* y, int* comp)
{
int result = info(filename, x, y, comp);
if (result == 0) return IMAGE_INFO_FAILED?;
}
// Get image info from memory without loading, returning an optional that fails on error
macro void? get_info_from_memory(char[] buffer, int* x, int* y, int* comp)
{
int result = info_from_memory(buffer.ptr, (int)buffer.len, x, y, comp);
if (result == 0) return IMAGE_INFO_FAILED?;
}
// Test program for stb_image C3 bindings
// Compile with: c3c compile test_stb_image.c3 ../stb_image/stb_image.c3 stb_image_impl.o
module test_stb_image;
import std::io;
import stb_image::stbi;
fn int main()
{
io::printn("=== stb_image Bindings Test ===\n");
// Test constants
io::printfn("STBI_VERSION = %d", stbi::STBI_VERSION);
io::printfn("STBI_DEFAULT = %d", stbi::STBI_DEFAULT);
io::printfn("STBI_GREY = %d", stbi::STBI_GREY);
io::printfn("STBI_GREY_ALPHA = %d", stbi::STBI_GREY_ALPHA);
io::printfn("STBI_RGB = %d", stbi::STBI_RGB);
io::printfn("STBI_RGB_ALPHA = %d", stbi::STBI_RGB_ALPHA);
// Test struct size (verify StbiIoCallbacks compiles correctly)
io::printfn("\nStruct sizes:");
io::printfn(" StbiIoCallbacks size: %d bytes", (int)stbi::StbiIoCallbacks.sizeof);
// Test basic types (aliases StbiUc and StbiUs were suppressed per C3 creator feedback)
io::printfn("\nBasic type sizes:");
io::printfn(" char size: %d bytes", (int)char.sizeof);
io::printfn(" ushort size: %d bytes", (int)ushort.sizeof);
// Test calling a simple function - get failure reason (should be null/empty initially)
io::printn("\nTesting function calls:");
ZString reason = stbi::failure_reason();
if (reason != null)
{
io::printfn(" Initial failure reason: %s", reason);
}
else
{
io::printn(" Initial failure reason: (none)");
}
// =========================================
// Test idiomatic C3 wrapper macros
// =========================================
io::printn("\n=== Testing Idiomatic C3 Wrappers ===\n");
// Test get_info with non-existent file using optional error handling
io::printn("Testing stbi::get_info with non-existent file:");
int x, y, comp;
if (catch err = stbi::get_info("nonexistent.png", &x, &y, &comp))
{
io::printfn(" Got expected error: %s", err);
io::printfn(" Failure reason: %s", stbi::failure_reason());
}
else
{
io::printn(" ERROR: Should have failed!");
return 1;
}
// Test load_image with non-existent file using optional error handling
io::printn("\nTesting stbi::load_image with non-existent file:");
int img_x, img_y, img_channels;
if (catch err = stbi::load_image("nonexistent.jpg", &img_x, &img_y, &img_channels))
{
io::printfn(" Got expected error: %s", err);
io::printfn(" Failure reason: %s", stbi::failure_reason());
}
else
{
io::printn(" ERROR: Should have failed!");
return 1;
}
// Test load_image with actual file - success case
io::printn("\nTesting stbi::load_image with snoopy.jpg (idiomatic style):");
if (try image_data = stbi::load_image("snoopy.jpg", &img_x, &img_y, &img_channels))
{
io::printfn(" Successfully loaded snoopy.jpg!");
io::printfn(" Width: %d pixels", img_x);
io::printfn(" Height: %d pixels", img_y);
io::printfn(" Channels: %d", img_channels);
io::printfn(" Total pixels: %d", img_x * img_y);
io::printfn(" Data size: %d bytes", img_x * img_y * img_channels);
// Free the image data
stbi::image_free(image_data);
io::printn(" Image data freed successfully.");
}
else
{
io::printfn(" ERROR: Failed to load snoopy.jpg: %s", stbi::failure_reason());
return 1;
}
// Test get_info on the same image - success case
io::printn("\nTesting stbi::get_info with snoopy.jpg (idiomatic style):");
int info_x, info_y, info_comp;
if (catch err = stbi::get_info("snoopy.jpg", &info_x, &info_y, &info_comp))
{
io::printfn(" ERROR: get_info failed: %s", stbi::failure_reason());
return 1;
}
io::printfn(" Info reports: %dx%d, %d channels", info_x, info_y, info_comp);
// Verify info matches what we got from load
if (info_x == img_x && info_y == img_y && info_comp == img_channels)
{
io::printn(" Info matches loaded image dimensions!");
}
else
{
io::printn(" WARNING: Info doesn't match loaded dimensions");
}
// =========================================
// Compare with raw bindings style
// =========================================
io::printn("\n=== Comparison: Raw Bindings Style ===\n");
io::printn("Testing raw stbi::load (for comparison):");
int raw_x, raw_y, raw_channels;
char* raw_data = stbi::load("snoopy.jpg", &raw_x, &raw_y, &raw_channels, 0);
if (raw_data != null)
{
io::printfn(" Loaded: %dx%d, %d channels", raw_x, raw_y, raw_channels);
stbi::image_free(raw_data);
io::printn(" Freed successfully.");
}
else
{
io::printfn(" ERROR: %s", stbi::failure_reason());
return 1;
}
io::printn("\n=== All binding tests passed! ===");
return 0;
}
@moomerman
Copy link
Author

moomerman commented Dec 12, 2025

=== stb_image Bindings Test ===

STBI_VERSION = 1
STBI_DEFAULT = 0
STBI_GREY = 1
STBI_GREY_ALPHA = 2
STBI_RGB = 3
STBI_RGB_ALPHA = 4

Struct sizes:
StbiIoCallbacks size: 24 bytes

Basic type sizes:
char size: 1 bytes
ushort size: 2 bytes

Testing function calls:
Initial failure reason: (none)

=== Testing Idiomatic C3 Wrappers ===

Testing stbi::get_info with non-existent file:
Got expected error: stbi::IMAGE_INFO_FAILED
Failure reason: can't fopen

Testing stbi::load_image with non-existent file:
Got expected error: stbi::IMAGE_LOAD_FAILED
Failure reason: can't fopen

Testing stbi::load_image with snoopy.jpg (idiomatic style):
Successfully loaded snoopy.jpg!
Width: 180 pixels
Height: 101 pixels
Channels: 3
Total pixels: 18180
Data size: 54540 bytes
Image data freed successfully.

Testing stbi::get_info with snoopy.jpg (idiomatic style):
Info reports: 180x101, 3 channels
Info matches loaded image dimensions!

=== Comparison: Raw Bindings Style ===

Testing raw stbi::load (for comparison):
Loaded: 180x101, 3 channels
Freed successfully.

=== All binding tests passed! ===

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment