Reading JPEG files into raw bytes

Howdy - I’m trying to read in JPEG files and I haven’t explored this with Zig at this point. I need to just get the raw image content because I’m going to unquantize it for data to train an image classification transformer. I’ve resized all of them so that the dimensions are correct, now I just need to get to the image byte content.

I’m aware that there’s meta-data that I need to skip over and I’m looking up how that’s done but I’m wondering if anyone here has some experience with JPEGs and Zig that they’re willing to share? Thanks in advance :slight_smile:

Looks like this could be a place to start - it’s in python but I can translate it pretty easily: Extract JPG image from binary data · GitHub

1 Like

So here’s a working example… it certainly reads and finds those markers but I still have a few more questions.

const std = @import("std");
const image_head: []const u8 = "\xff\xd8";
const image_tail: []const u8 = "\xff\xd9";

pub fn main() !void {

    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    const jpeg = try std.fs.cwd().readFileAlloc(
        arena.allocator(), "01F4.jpeg", 3000 // image is ~2.7kb
    );
    
    const head: usize = blk: {
        const pos = std.mem.indexOf(u8, jpeg, image_head);

        if (pos) |i| break :blk i + image_head.len;

        return std.log.err("No image head found.", .{});
    };

    const tail: usize = blk: {
        const pos = std.mem.indexOfPos(u8, jpeg, head, image_tail);

        if (pos) |i| break :blk i;

        return std.log.err("No image tail found.", .{});
    };

    std.log.info(
        \\
        \\  jpeg head: {}
        \\  jpeg tail: {}
        \\  byte size: {}
        , .{ head, tail, jpeg[head..tail].len });
}

Here’s what it’s outputting:

info: 
  jpeg head: 2
  jpeg tail: 2672
  byte size: 2670

Meanwhile my file system is telling me that the size of the file is 2674 bytes long. That basically means that the search found the head at the very beginning of the file and the tail at the very end - the whole file is bounded between the two. I’m a little confused by that because I was under the impression that there’s a lot more meta-data upfront, so if anyone can offer some insights here then that’d be grand.

1 Like

I haven’t read all of it but this seems like a good resource.

1 Like

Thanks! I looked into it and it turns out that reading the raw bytes won’t be a great strategy, so I went with writing a python script to convert it back to RGB and write it out to a file from there. I looked for some jpg utils online and found several file handling libraries for Zig but jpg’s were unimplemented. Looks like there’s still a lot of work to do be done there.

Stb image handles the most common subtype of JPEG 1 and is trivial to integrate into a C or Zig project. If you need more subtypes, libjpeg-turbo can handle almost everything from JPEG 1, but integrating it is annoying. I had to to have the build system compile the assembly files using NASM, and manually create the header files from the templates, as Zig’s header file creation from templates is buggy. I can share what I did to integrate it, if you need.
If you need the more recent versions of JPEG, it becomes a real pain.

Thanks @LucasSantos91, I really appreciate the offer. It may come to that at some point - I’ll let you know if I do. I’m sure a lot of these rough patches will get smoothed over as Zig has more time to develop and libraries continue to mature.

Raylib supports a bunch of image formats raylib/src/rtextures.c at 414229bcf93816daaa253a82cd54fbd7b18e6449 · raysan5/raylib · GitHub it seems via stb image stb/stb_image.h at master · nothings/stb · GitHub

Maybe using something like GitHub - MasterQ32/zig-qoi: Quite OK Image format encoder/decoder written in Zig would make sense too.

1 Like

zigimg has a JPEG decoder (actually, I think it’s partially broken). Note that it won’t give you the raw YCbCr data, just RGB. I’m currently working on a multimedia library for zig, but it is in the early stages. @nektro wrote a simple (and , in my opinion, quite beautiful) JPEG decoder in Zig for her UI toolkit: ~nektro/magnolia-desktop: src/e/Image/jpg.zig - sourcehut git. It will also just give you RGB.

1 Like

Just so that I can sleep at night, you forgot to defer deallocating the jpeg… :slight_smile:

After that deep contribution, I will now take my leave.

3 Likes

I’m using an arena allocator. The deinit on the arena takes care of that:

    pub fn deinit(self: ArenaAllocator) void {
        // NOTE: When changing this, make sure `reset()` is adjusted accordingly!

        var it = self.state.buffer_list.first;
        while (it) |node| {
            // this has to occur before the free because the free frees node
            const next_it = node.next;
            const align_bits = std.math.log2_int(usize, @alignOf(BufNode));
            const alloc_buf = @as([*]u8, @ptrCast(node))[0..node.data];
            self.child_allocator.rawFree(alloc_buf, align_bits, @returnAddress());
            it = next_it;
        }
    }

So, we’re all good :slight_smile:

6 Likes

I think it might be helpful to see how its done with uncompressed BMP, like if you want to do a pure Zig implementation with GL. You could also write a command line app which compresses BMP into JPEG. I’m not sure how practical all that is for your specific case, but it should give you a better perspective on dealing with JPEG in general

Probably not what you want, but I’ll leave it here anyway.

I’ve also wanted to work with images in Zig, so I decided to add some simple bindings to load and save JPEG-XL images.

I can convert JPEG and PNG to JPEG-XL losslessly, and even recover the original JPEG image from the transcoded JPEG-XL. See the migrate.sh script.

I just hurried to push those changes. I wanted to upload it at some point, but maybe clean up the code a bit. Happy to get feedback.

2 Likes