Thanks to visit codestin.com
Credit goes to Github.com

Skip to content

Conversation

@braheezy
Copy link
Contributor

@braheezy braheezy commented Nov 8, 2025

Hi! This PR adds a JPEG encoder. I tried to add the right tests. I ran the encoder over a bunch of random files from the test suite, things seem to convert just fine. The implementation is loosely based on the one in the Go standard library.

Note that many formats are converted to rgb24 or grayscale8 internally before encoding.

If it helps, this test program will write a JPEG then use the existing reader to check that it's valid.

const std = @import("std");
const zigimg = @import("zigimg");

var read_buffer: [4096]u8 = undefined;
var write_buffer: [4096]u8 = undefined;

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len != 3) {
        std.debug.print("Usage: {s} <input_file> <output_file>\n", .{args[0]});
        return;
    }

    const input_file = args[1];
    const output_file = args[2];

    var input_stream = try std.fs.cwd().openFile(input_file, .{});
    defer input_stream.close();

    var output_stream = try std.fs.cwd().createFile(output_file, .{ .truncate = true });
    defer output_stream.close();

    var image = try zigimg.Image.fromFile(allocator, input_stream, &read_buffer);
    defer image.deinit(allocator);

    try image.writeToFilePath(allocator, output_file, &write_buffer, .{ .jpeg = .{} });

    std.debug.print("Image successfully written to {s}\n", .{output_file});

    // Test: Try to read the JPEG back to verify it's valid
    std.debug.print("Testing round-trip: reading back the JPEG...\n", .{});
    var read_file = try std.fs.cwd().openFile(output_file, .{});
    defer read_file.close();
    var read_back = try zigimg.Image.fromFile(allocator, read_file, &read_buffer);
    defer read_back.deinit(allocator);
    std.debug.print("Successfully read back JPEG: {d}x{d}, format: {s}\n", .{ read_back.width, read_back.height, @tagName(read_back.pixels) });
}

Copy link
Collaborator

@mlarouche mlarouche left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow! A big chunk that will help this project a lot, thanks a ton!

I did make requests for changes, the 2 big ones being pixel format conversion requiring explicit demand from the user and not using Image directly in JPEGWriter, the rest is mostly debug prints that needs to be removed and naming conventions violations.


// Calculate average delta
const avg_delta = averageDelta(original, decoded) catch continue;
std.debug.print("JPEG writer {s} q={d} avg_delta={d:.2} (<= {d:.2})\n", .{ tc.filename, tc.quality, avg_delta, tc.tolerance });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove all debug print in the tests

const helpers = @import("../helpers.zig");
const jpeg = zigimg.formats.jpeg;
const std = @import("std");
const testing = std.testing;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at the recent changes I did while migrating to 0.15, you'll see that I replaced almost all aliasing of namespaces. My personal rule is aliasing the module/struct you are testing is okay but the rest should be fully qualified

const zigimg = @import("zigimg");
const Image = zigimg.Image;
const color = zigimg.color;
const utils = @import("../../src/formats/jpeg/utils.zig");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't import files from the implementation in the tests. You can migrate the zig zag test into utils.zig directly.

try testing.expectEqual(img0.width, img1.width);
try testing.expectEqual(img0.height, img1.height);

const w = img0.width;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try avoid using single letter variables, it hurts readability.

const alloc = arena.allocator();

// Generous buffer for small test images
const buf = try alloc.alloc(u8, 1 << 20);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buf -> buffer

};

// Huffman table specifications
const theHuffmanSpec = [nHuffIndex]HuffmanSpec{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constant does not respect the naming convention

};

// div returns a/b rounded to the nearest integer, instead of rounded to zero
fn div(a: i32, b: i32) i32 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you try if @divFloor would work here?

var jpeg_writer = JPEGWriter.init(writer);

// Encode the converted image
try jpeg_writer.encode(converted_image, jpeg_options.quality);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass the PixelStorage, width and height to encode(). You should be able to write JPEG files without using Image. The interface implemented for Image in each format is just a convenience wrapper for the lower levels read() and write() of each format that works with PixelStorage.

// the tables according to its quality parameter.
// The values are derived from section K.1 of the spec, after converting from
// natural to zig-zag order.
const unscaledQuant = [quant_index][block_size]u8{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a constant, not a function

/// JPEGWriter handles encoding images to JPEG format
pub const JPEGWriter = struct {
writer: *std.Io.Writer,
err: ?Image.WriteError = null,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fail to see why you need to store an error and do early exits when the try keyword already does this for you.

@braheezy
Copy link
Contributor Author

Thanks for the detailed review! I believe I got it all. I prefer most of the style changes you recommended and will likely bring them back to my other projects.

@mlarouche mlarouche merged commit b53e228 into zigimg:master Nov 17, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants