For our next trick, we'll tackle the most common headache in the C ecosystem: Dependency Hell. Usually, if you want to manipulate images, you need libpng, libjpeg, zlib, and headers for all of them installed on your system. If your user is on a different OS, your script fails. To avoid that, let's use Affix::Build to compile the famous stb single-header libraries.

The Recipe

Here we'll load a JPG/PNG, resize it to a thumbnail using high-quality linear scaling, and save it as a PNG.

In other words, we'll turn this:

avatar

...into this:

avatar_small

Simple enough.

This recipe demonstrates two advanced techniques:

  1. Output Parameters: Passing pointers to variables so C can populate them with data.
  2. Raw Buffers: Passing the memory address of Perl scalars to C for high-performance data manipulation.
use v5.40;
use Affix qw[:all];
use Affix::Build;
use HTTP::Tiny;
use Path::Tiny;
use Config;
$|++;

# 1. Setup Persistent Cache
# Like Inline::C, we keep artifacts in a local subdirectory.
my $build_dir = path('.')->child('_build');
$build_dir->mkpath;

# Calculate the expected library name (e.g., stb_lib.dll or libstb_lib.so)
my $lib_file = $build_dir->child( ( $^O eq 'MSWin32' ? '' : 'lib' ) . 'stb_lib.' . $Config{so} );

# 2. Check Cache
if ( $lib_file->exists ) {
    say "Using cached library: $lib_file";
}
else {
    say "Library not found. Building...";
    build_library($build_dir);
}

# 3. Bindings
my $lib = Affix::load_library($lib_file);

# stbir_pixel_layout enum
use constant STBIR_RGBA => 4;

# "Buffer" passes the raw memory address of a Perl string to C.
affix $lib, 'stbi_load',                 [ String, Pointer [Int], Pointer [Int], Pointer [Int], Int ] => Buffer;
affix $lib, 'stbi_write_png',            [ String, Int, Int, Int, Buffer, Int ]                       => Int;
affix $lib, 'stbir_resize_uint8_linear', [ Buffer, Int, Int, Int, Buffer, Int, Int, Int, Int ]        => Buffer;

# 4. The Application
my $input_file  = 'avatar.jpg';
my $output_file = 'avatar_small.png';

# Generate dummy input if needed
unless ( -e $input_file ) {
    say 'Creating dummy input file...';
    my $w      = 100;
    my $h      = 100;
    my $pixels = '';

    # Gradient Pattern
    for my $y ( 0 .. $h - 1 ) {
        for my $x ( 0 .. $w - 1 ) {
            $pixels .= pack( 'C4', int( $x * 2.5 ), int( $y * 2.5 ), 0, 255 );
        }
    }
    stbi_write_png( $input_file, $w, $h, 4, $pixels, $w * 4 );
}

# Step A: Load
my ( $w, $h, $ch ) = ( 0, 0, 0 );
say "Loading $input_file...";
my $img_data = stbi_load( $input_file, \$w, \$h, \$ch, 4 );
die 'Failed to load image.' if is_null($img_data);
say "Original: ${w}x${h} ($ch channels)";

# Step B: Resize
my $new_w      = int( $w / 2 );
my $new_h      = int( $h / 2 );
my $in_stride  = $w * 4;
my $out_stride = $new_w * 4;

# Allocate output memory (Buffer)
my $out_data = "\0" x ( $new_h * $out_stride );
say "Resizing to ${new_w}x${new_h}...";
my $res = stbir_resize_uint8_linear( $img_data, $w, $h, $in_stride, $out_data, $new_w, $new_h, $out_stride, STBIR_RGBA );
die 'Resize failed!' if is_null($res);

# Step C: Save
say "Saving to $output_file...";
stbi_write_png( $output_file, $new_w, $new_h, 4, $out_data, $out_stride );
say 'Done.';

# Subroutines
sub build_library($dir) {
    my $http = HTTP::Tiny->new;

    # Download headers if missing
    for my $file (qw(stb_image.h stb_image_resize2.h stb_image_write.h)) {
        my $path = $dir->child($file);
        next if $path->exists;
        say "Downloading $file...";
        my $url = "https://raw.githubusercontent.com/nothings/stb/master/$file";
        my $res = $http->get($url);
        die "Failed to download $file" unless $res->{success};
        $path->spew_raw( $res->{content} );
    }
    my $c = Affix::Build->new(
        name      => 'stb_lib',
        build_dir => $dir,
        flags     => { cflags => "-I$dir -O3", ldflags => ( $^O eq 'MSWin32' ? '-Wl,--export-all-symbols' : '' ) }
    );
    $c->add( \<<~'C', lang => 'c' );
        #if defined(_WIN32)
          #define STBIDEF __declspec(dllexport)
          #define STBIWDEF __declspec(dllexport)
          #define STBIRDEF __declspec(dllexport)
        #else
          #define STBIDEF __attribute__((visibility("default")))
          #define STBIWDEF __attribute__((visibility("default")))
          #define STBIRDEF __attribute__((visibility("default")))
        #endif

        #define STB_IMAGE_IMPLEMENTATION
        #define STB_IMAGE_WRITE_IMPLEMENTATION
        #define STB_IMAGE_RESIZE_IMPLEMENTATION

        #include "stb_image.h"
        #include "stb_image_resize2.h"
        #include "stb_image_write.h"
    C
    $c->compile_and_link;
}

How It Works