mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-20 00:18:53 +03:00
wuffs: update, add jpeg decoding, add simple tests
This commit is contained in:
60
.github/workflows/test.yml
vendored
60
.github/workflows/test.yml
vendored
@ -478,3 +478,63 @@ jobs:
|
||||
useDaemon: false # sometimes fails on short jobs
|
||||
- name: typos check
|
||||
run: nix develop -c typos
|
||||
|
||||
test-pkg-linux:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkg: ["wuffs"]
|
||||
name: Run pkg/${{ matrix.pkg }} tests on Linux
|
||||
runs-on: namespace-profile-ghostty-sm
|
||||
needs: test
|
||||
env:
|
||||
ZIG_LOCAL_CACHE_DIR: /zig/local-cache
|
||||
ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Cache
|
||||
uses: namespacelabs/nscloud-cache-action@v1.2.0
|
||||
with:
|
||||
path: |
|
||||
/nix
|
||||
/zig
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: Test ${{ matrix.pkg }} Build
|
||||
run: |
|
||||
nix develop -c sh -c "cd pkg/${{ matrix.pkg }} ; zig build test"
|
||||
|
||||
test-pkg-macos:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pkg: ["wuffs"]
|
||||
name: Run pkg/${{ matrix.pkg }} tests on macOS
|
||||
runs-on: namespace-profile-ghostty-macos
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Install Nix and use that to run our tests so our environment matches exactly.
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
- uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: ghostty
|
||||
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||
|
||||
- name: Test ${{ matrix.pkg }} Build
|
||||
run: |
|
||||
nix develop -c sh -c "cd pkg/${{ matrix.pkg }} ; zig build test"
|
||||
|
@ -1,3 +1,3 @@
|
||||
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
||||
# more details.
|
||||
"sha256-ot5onG1yq7EWQkNUgTNBuqvsnLuaoFs2UDS96IqgJmU="
|
||||
"sha256-njCce+r1DPTKLNrmrD2ObEoBS9nR7q03hqegQWe1UuY="
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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" },
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
146
pkg/wuffs/src/jpeg.zig
Normal file
146
pkg/wuffs/src/jpeg.zig
Normal file
@ -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);
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user