One-file library (pcx.zig) for loading and saving 8-bit PCX images in Zig; works both at compile-time and at run-time.
Tested to work with Zig 0.11.0, 0.12.1 and 0.13.0.
The loading API has two stages: “preload” and “load”.
First, you call preload. This reads the PCX header and returns some basic
information including its width and height (and some other things which are
used internally).
Preload function:
preload(reader: anytype) !PreloadedInfo
Note: In all these functions, reader is anything that fulfills the
std.io.Reader interface. You don’t need to pass a pointer to a reader
because readers usually box their own pointers.
Now, if you want to proceed with decoding the image, you call one of the
load* functions. You pass it the value returned by preload, as well as a
byte slice with enough space to fit the decompressed image. (The caller is
responsible for allocating this.)
Load functions:
// loads the color index values into `out_buffer` without doing palette lookup.
// if `out_palette` is supplied, loads the palette in RGB format.
// `out_buffer` size should be `width * height` bytes.
// `out_palette` size should be 768 bytes (256 colors of 3 bytes each).
loadIndexed(reader: anytype, preloaded: PreloadedInfo, out_buffer: []u8, out_palette: ?[]u8) !void
// uses palette internally to resolve pixel colors.
// `out_buffer` size should be `width * height * 3` bytes.
loadRGB(reader: anytype, preloaded: PreloadedInfo, out_buffer: []u8) !void
// reads into an RGBA buffer. if you pass a `transparent_index`, pixels with
// that value will given a 0 alpha value instead of 255.
// `out_buffer` size should be `width * height * 4` bytes.
loadRGBA(reader: anytype, preloaded: PreloadedInfo, transparent_index: ?u8, out_buffer: []u8) !void
Note: the PCX format stores width and height as 16-bit integers. So be sure
to upcast them to usize before multiplying them together, otherwise you’ll
get an overflow with images bigger than ~256x256.
Example usage:
var file = try std.fs.cwd().openFile("image.pcx", .{});
defer file.close();
const reader = file.reader();
const preloaded = try pcx.preload(reader);
const width: usize = preloaded.width;
const height: usize = preloaded.height;
// load indexed:
const pixels = try allocator.alloc(u8, width * height);
defer allocator.free(pixels);
var palette: [768]u8 = undefined;
try pcx.loadIndexed(reader, preloaded, pixels, &palette);
// or, load rgb:
const pixels = try allocator.alloc(u8, width * height * 3);
defer allocator.free(pixels);
try pcx.loadRGB(reader, preloaded, pixels);
// or, load rgba:
const pixels = try allocator.alloc(u8, width * height * 4);
defer allocator.free(pixels);
const transparent: ?u8 = 255;
try pcx.loadRGBA(reader, preloaded, transparent, pixels);
Compile-time example:
comptime {
@setEvalBranchQuota(100000);
const input = @embedFile("image.pcx");
var fbs = std.io.fixedBufferStream(input);
const reader = fbs.reader();
const preloaded = try pcx.preload(reader);
const width: usize = preloaded.width;
const height: usize = preloaded.height;
// no need to use allocators at compile-time
var rgb: [width * height * 3]u8 = undefined;
try pcx.loadRGB(reader, preloaded, &rgb);
}
Only saving to indexed color is supported.
Example:
const w = 32;
const h = 32;
const pixels: [32 * 32]u8 = ...;
const palette: [768]u8 = ...;
var file = try std.fs.cwd().createFile("image.pcx", .{});
defer file.close();
try pcx.saveIndexed(file.writer(), w, h, &pixels, &palette);
The basic test suite can be run with zig test pcx_test.zig.
There are two additional “demo” programs which render an ASCII translation of a stock image. The comptime version renders it in the form of a compile error, the runtime version prints it to stderr.
zig run demo_comptime.zig
zig run demo_runtime.zig
Space merc image (used in demos) from https://opengameart.org/content/space-merc