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
|
useDaemon: false # sometimes fails on short jobs
|
||||||
- name: typos check
|
- name: typos check
|
||||||
run: nix develop -c typos
|
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
|
# This file is auto-generated! check build-support/check-zig-cache-hash.sh for
|
||||||
# more details.
|
# 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"),
|
.file = wuffs.path("release/c/wuffs-v0.4.c"),
|
||||||
.flags = flags.items,
|
.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",
|
.version = "0.0.0",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.wuffs = .{
|
.wuffs = .{
|
||||||
.url = "https://github.com/google/wuffs/archive/refs/tags/v0.4.0-alpha.8.tar.gz",
|
.url = "https://github.com/google/wuffs/archive/refs/tags/v0.4.0-alpha.9.tar.gz",
|
||||||
.hash = "12200984439edc817fbcbbaff564020e5104a0d04a2d0f53080700827052de700462",
|
.hash = "122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd",
|
||||||
|
},
|
||||||
|
|
||||||
|
.pixels = .{
|
||||||
|
.url = "git+https://github.com/make-github-pseudonymous-again/pixels?ref=main#d843c2714d32e15b48b8d7eeb480295af537f877",
|
||||||
|
.hash = "12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806",
|
||||||
},
|
},
|
||||||
|
|
||||||
.apple_sdk = .{ .path = "../apple-sdk" },
|
.apple_sdk = .{ .path = "../apple-sdk" },
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
|
||||||
pub const Error = std.mem.Allocator.Error || error{WuffsError};
|
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 png = @import("png.zig");
|
||||||
|
pub const jpeg = @import("jpeg.zig");
|
||||||
pub const swizzle = @import("swizzle.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 Allocator = std.mem.Allocator;
|
||||||
const c = @import("c.zig").c;
|
const c = @import("c.zig").c;
|
||||||
const Error = @import("error.zig").Error;
|
const Error = @import("error.zig").Error;
|
||||||
|
const check = @import("error.zig").check;
|
||||||
|
|
||||||
const log = std.log.scoped(.wuffs_png);
|
const log = std.log.scoped(.wuffs_png);
|
||||||
|
|
||||||
@ -29,11 +30,7 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
|
|||||||
c.WUFFS_VERSION,
|
c.WUFFS_VERSION,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
if (!c.wuffs_base__status__is_ok(&status)) {
|
try check(log, &status);
|
||||||
const e = c.wuffs_base__status__message(&status);
|
|
||||||
log.warn("decode err={s}", .{e});
|
|
||||||
return error.WuffsError;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var source_buffer: c.wuffs_base__io_buffer = .{
|
var source_buffer: c.wuffs_base__io_buffer = .{
|
||||||
@ -53,11 +50,7 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
|
|||||||
&image_config,
|
&image_config,
|
||||||
&source_buffer,
|
&source_buffer,
|
||||||
);
|
);
|
||||||
if (!c.wuffs_base__status__is_ok(&status)) {
|
try check(log, &status);
|
||||||
const e = c.wuffs_base__status__message(&status);
|
|
||||||
log.warn("decode err={s}", .{e});
|
|
||||||
return error.WuffsError;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const width = c.wuffs_base__pixel_config__width(&image_config.pixcfg);
|
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,
|
&image_config.pixcfg,
|
||||||
c.wuffs_base__make_slice_u8(destination.ptr, destination.len),
|
c.wuffs_base__make_slice_u8(destination.ptr, destination.len),
|
||||||
);
|
);
|
||||||
if (!c.wuffs_base__status__is_ok(&status)) {
|
try check(log, &status);
|
||||||
const e = c.wuffs_base__status__message(&status);
|
|
||||||
log.warn("decode err={s}", .{e});
|
|
||||||
return error.WuffsError;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var frame_config: c.wuffs_base__frame_config = undefined;
|
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,
|
&frame_config,
|
||||||
&source_buffer,
|
&source_buffer,
|
||||||
);
|
);
|
||||||
if (!c.wuffs_base__status__is_ok(&status)) {
|
try check(log, &status);
|
||||||
const e = c.wuffs_base__status__message(&status);
|
|
||||||
log.warn("decode err={s}", .{e});
|
|
||||||
return error.WuffsError;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -132,11 +117,7 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
|
|||||||
work_slice,
|
work_slice,
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
if (!c.wuffs_base__status__is_ok(&status)) {
|
try check(log, &status);
|
||||||
const e = c.wuffs_base__status__message(&status);
|
|
||||||
log.warn("decode err={s}", .{e});
|
|
||||||
return error.WuffsError;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
@ -145,3 +126,21 @@ pub fn decode(alloc: Allocator, data: []const u8) Error!struct {
|
|||||||
.data = destination,
|
.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