summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorJeffrey C. Ollie <jeff@ocjtech.us>2025-01-01 00:03:29 -0600
committerJeffrey C. Ollie <jeff@ocjtech.us>2025-01-01 22:48:30 -0600
commit652079b26ccb7d76fbc6726f04b40bb3552db06c (patch)
tree1b194f40676be5a4a089ca0c71a511da2d68d8dd /pkg
parent94599102e9fb8247af08cbbbcb7ee25e3d31e1bd (diff)
wuffs: update, add jpeg decoding, add simple tests
Diffstat (limited to 'pkg')
-rw-r--r--pkg/wuffs/build.zig32
-rw-r--r--pkg/wuffs/build.zig.zon9
-rw-r--r--pkg/wuffs/src/error.zig10
-rw-r--r--pkg/wuffs/src/jpeg.zig146
-rw-r--r--pkg/wuffs/src/main.zig7
-rw-r--r--pkg/wuffs/src/png.zig49
6 files changed, 226 insertions, 27 deletions
diff --git a/pkg/wuffs/build.zig b/pkg/wuffs/build.zig
index 36bb5a07c..438f714d3 100644
--- a/pkg/wuffs/build.zig
+++ b/pkg/wuffs/build.zig
@@ -30,4 +30,36 @@ pub fn build(b: *std.Build) !void {
.file = wuffs.path("release/c/wuffs-v0.4.c"),
.flags = flags.items,
});
+
+ const unit_tests = b.addTest(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ unit_tests.linkLibC();
+ unit_tests.addIncludePath(wuffs.path("release/c"));
+ unit_tests.addCSourceFile(.{
+ .file = wuffs.path("release/c/wuffs-v0.4.c"),
+ .flags = flags.items,
+ });
+
+ const pixels = b.dependency("pixels", .{});
+
+ inline for (.{ "000000", "FFFFFF" }) |color| {
+ inline for (.{ "gif", "jpg", "png", "ppm" }) |extension| {
+ const filename = std.fmt.comptimePrint("1x1#{s}.{s}", .{ color, extension });
+ unit_tests.root_module.addAnonymousImport(
+ filename,
+ .{
+ .root_source_file = pixels.path(filename),
+ },
+ );
+ }
+ }
+
+ const run_unit_tests = b.addRunArtifact(unit_tests);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_unit_tests.step);
}
diff --git a/pkg/wuffs/build.zig.zon b/pkg/wuffs/build.zig.zon
index 126e43aba..d84d6957e 100644
--- a/pkg/wuffs/build.zig.zon
+++ b/pkg/wuffs/build.zig.zon
@@ -3,8 +3,13 @@
.version = "0.0.0",
.dependencies = .{
.wuffs = .{
- .url = "https://github.com/google/wuffs/archive/refs/tags/v0.4.0-alpha.8.tar.gz",
- .hash = "12200984439edc817fbcbbaff564020e5104a0d04a2d0f53080700827052de700462",
+ .url = "https://github.com/google/wuffs/archive/refs/tags/v0.4.0-alpha.9.tar.gz",
+ .hash = "122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd",
+ },
+
+ .pixels = .{
+ .url = "git+https://github.com/make-github-pseudonymous-again/pixels?ref=main#d843c2714d32e15b48b8d7eeb480295af537f877",
+ .hash = "12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806",
},
.apple_sdk = .{ .path = "../apple-sdk" },
diff --git a/pkg/wuffs/src/error.zig b/pkg/wuffs/src/error.zig
index 609deec9c..c75188718 100644
--- a/pkg/wuffs/src/error.zig
+++ b/pkg/wuffs/src/error.zig
@@ -1,3 +1,13 @@
const std = @import("std");
+const c = @import("c.zig").c;
+
pub const Error = std.mem.Allocator.Error || error{WuffsError};
+
+pub fn check(log: anytype, status: *const c.struct_wuffs_base__status__struct) error{WuffsError}!void {
+ if (!c.wuffs_base__status__is_ok(status)) {
+ const e = c.wuffs_base__status__message(status);
+ log.warn("decode err={s}", .{e});
+ return error.WuffsError;
+ }
+}
diff --git a/pkg/wuffs/src/jpeg.zig b/pkg/wuffs/src/jpeg.zig
new file mode 100644
index 000000000..63ca428d1
--- /dev/null
+++ b/pkg/wuffs/src/jpeg.zig
@@ -0,0 +1,146 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const c = @import("c.zig").c;
+const Error = @import("error.zig").Error;
+const check = @import("error.zig").check;
+
+const log = std.log.scoped(.wuffs_jpeg);
+
+/// Decode a JPEG image.
+pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
+ width: u32,
+ height: u32,
+ data: []const u8,
+} {
+ // Work around some weirdness in WUFFS/Zig, there are some structs that
+ // are defined as "extern" by the Zig compiler which means that Zig won't
+ // allocate them on the stack at compile time. WUFFS has functions for
+ // dynamically allocating these structs but they use the C malloc/free. This
+ // gets around that by using the Zig allocator to allocate enough memory for
+ // the struct and then casts it to the appropriate pointer.
+
+ const decoder_buf = try alloc.alloc(u8, c.sizeof__wuffs_jpeg__decoder());
+ defer alloc.free(decoder_buf);
+
+ const decoder: ?*c.wuffs_jpeg__decoder = @ptrCast(decoder_buf);
+ {
+ const status = c.wuffs_jpeg__decoder__initialize(
+ decoder,
+ c.sizeof__wuffs_jpeg__decoder(),
+ c.WUFFS_VERSION,
+ 0,
+ );
+ try check(log, &status);
+ }
+
+ var source_buffer: c.wuffs_base__io_buffer = .{
+ .data = .{ .ptr = @constCast(@ptrCast(data.ptr)), .len = data.len },
+ .meta = .{
+ .wi = data.len,
+ .ri = 0,
+ .pos = 0,
+ .closed = true,
+ },
+ };
+
+ var image_config: c.wuffs_base__image_config = undefined;
+ {
+ const status = c.wuffs_jpeg__decoder__decode_image_config(
+ decoder,
+ &image_config,
+ &source_buffer,
+ );
+ try check(log, &status);
+ }
+
+ const width = c.wuffs_base__pixel_config__width(&image_config.pixcfg);
+ const height = c.wuffs_base__pixel_config__height(&image_config.pixcfg);
+
+ c.wuffs_base__pixel_config__set(
+ &image_config.pixcfg,
+ c.WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL,
+ c.WUFFS_BASE__PIXEL_SUBSAMPLING__NONE,
+ width,
+ height,
+ );
+
+ const destination = try alloc.alloc(
+ u8,
+ width * height * @sizeOf(c.wuffs_base__color_u32_argb_premul),
+ );
+ errdefer alloc.free(destination);
+
+ // temporary buffer for intermediate processing of image
+ const work_buffer = try alloc.alloc(
+ u8,
+
+ // The type of this is a u64 on all systems but our allocator
+ // uses a usize which is a u32 on 32-bit systems.
+ std.math.cast(
+ usize,
+ c.wuffs_jpeg__decoder__workbuf_len(decoder).max_incl,
+ ) orelse return error.OutOfMemory,
+ );
+ defer alloc.free(work_buffer);
+
+ const work_slice = c.wuffs_base__make_slice_u8(
+ work_buffer.ptr,
+ work_buffer.len,
+ );
+
+ var pixel_buffer: c.wuffs_base__pixel_buffer = undefined;
+ {
+ const status = c.wuffs_base__pixel_buffer__set_from_slice(
+ &pixel_buffer,
+ &image_config.pixcfg,
+ c.wuffs_base__make_slice_u8(destination.ptr, destination.len),
+ );
+ try check(log, &status);
+ }
+
+ var frame_config: c.wuffs_base__frame_config = undefined;
+ {
+ const status = c.wuffs_jpeg__decoder__decode_frame_config(
+ decoder,
+ &frame_config,
+ &source_buffer,
+ );
+ try check(log, &status);
+ }
+
+ {
+ const status = c.wuffs_jpeg__decoder__decode_frame(
+ decoder,
+ &pixel_buffer,
+ &source_buffer,
+ c.WUFFS_BASE__PIXEL_BLEND__SRC,
+ work_slice,
+ null,
+ );
+ try check(log, &status);
+ }
+
+ return .{
+ .width = width,
+ .height = height,
+ .data = destination,
+ };
+}
+
+test "jpeg_decode_000000" {
+ const data = try decode(std.testing.allocator, @embedFile("1x1#000000.jpg"));
+ defer std.testing.allocator.free(data.data);
+
+ try std.testing.expectEqual(1, data.width);
+ try std.testing.expectEqual(1, data.height);
+ try std.testing.expectEqualSlices(u8, &.{ 0, 0, 0, 255 }, data.data);
+}
+
+test "jpeg_decode_FFFFFF" {
+ const data = try decode(std.testing.allocator, @embedFile("1x1#FFFFFF.jpg"));
+ defer std.testing.allocator.free(data.data);
+
+ try std.testing.expectEqual(1, data.width);
+ try std.testing.expectEqual(1, data.height);
+ try std.testing.expectEqualSlices(u8, &.{ 255, 255, 255, 255 }, data.data);
+}
diff --git a/pkg/wuffs/src/main.zig b/pkg/wuffs/src/main.zig
index 3f03a4158..f5fc01501 100644
--- a/pkg/wuffs/src/main.zig
+++ b/pkg/wuffs/src/main.zig
@@ -1,2 +1,9 @@
+const std = @import("std");
+
pub const png = @import("png.zig");
+pub const jpeg = @import("jpeg.zig");
pub const swizzle = @import("swizzle.zig");
+
+test {
+ std.testing.refAllDeclsRecursive(@This());
+}
diff --git a/pkg/wuffs/src/png.zig b/pkg/wuffs/src/png.zig
index 3a3ac9a35..4597c6ccb 100644
--- a/pkg/wuffs/src/png.zig
+++ b/pkg/wuffs/src/png.zig
@@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
const Error = @import("error.zig").Error;
+const check = @import("error.zig").check;
const log = std.log.scoped(.wuffs_png);
@@ -29,11 +30,7 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
c.WUFFS_VERSION,
0,
);
- if (!c.wuffs_base__status__is_ok(&status)) {
- const e = c.wuffs_base__status__message(&status);
- log.warn("decode err={s}", .{e});
- return error.WuffsError;
- }
+ try check(log, &status);
}
var source_buffer: c.wuffs_base__io_buffer = .{
@@ -53,11 +50,7 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
&image_config,
&source_buffer,
);
- if (!c.wuffs_base__status__is_ok(&status)) {
- const e = c.wuffs_base__status__message(&status);
- log.warn("decode err={s}", .{e});
- return error.WuffsError;
- }
+ try check(log, &status);
}
const width = c.wuffs_base__pixel_config__width(&image_config.pixcfg);
@@ -102,11 +95,7 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
&image_config.pixcfg,
c.wuffs_base__make_slice_u8(destination.ptr, destination.len),
);
- if (!c.wuffs_base__status__is_ok(&status)) {
- const e = c.wuffs_base__status__message(&status);
- log.warn("decode err={s}", .{e});
- return error.WuffsError;
- }
+ try check(log, &status);
}
var frame_config: c.wuffs_base__frame_config = undefined;
@@ -116,11 +105,7 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
&frame_config,
&source_buffer,
);
- if (!c.wuffs_base__status__is_ok(&status)) {
- const e = c.wuffs_base__status__message(&status);
- log.warn("decode err={s}", .{e});
- return error.WuffsError;
- }
+ try check(log, &status);
}
{
@@ -132,11 +117,7 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
work_slice,
null,
);
- if (!c.wuffs_base__status__is_ok(&status)) {
- const e = c.wuffs_base__status__message(&status);
- log.warn("decode err={s}", .{e});
- return error.WuffsError;
- }
+ try check(log, &status);
}
return .{
@@ -145,3 +126,21 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
.data = destination,
};
}
+
+test "png_decode_000000" {
+ const data = try decode(std.testing.allocator, @embedFile("1x1#000000.png"));
+ defer std.testing.allocator.free(data.data);
+
+ try std.testing.expectEqual(1, data.width);
+ try std.testing.expectEqual(1, data.height);
+ try std.testing.expectEqualSlices(u8, &.{ 0, 0, 0, 255 }, data.data);
+}
+
+test "png_decode_FFFFFF" {
+ const data = try decode(std.testing.allocator, @embedFile("1x1#FFFFFF.png"));
+ defer std.testing.allocator.free(data.data);
+
+ try std.testing.expectEqual(1, data.width);
+ try std.testing.expectEqual(1, data.height);
+ try std.testing.expectEqualSlices(u8, &.{ 255, 255, 255, 255 }, data.data);
+}