initial extract from ghostty+jpeg
This commit is contained in:
commit
8271e7e4a1
12 changed files with 684 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/.zig-cache
|
||||||
|
/zig-out
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright © 2004 Jeffrey C. Ollie
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
65
build.zig
Normal file
65
build.zig
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) !void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const wuffs = b.dependency("wuffs", .{});
|
||||||
|
|
||||||
|
const module = b.addModule("wuffs", .{
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// if (target.result.isDarwin()) {
|
||||||
|
// const apple_sdk = @import("apple_sdk");
|
||||||
|
// try apple_sdk.addPaths(b, module);
|
||||||
|
// }
|
||||||
|
|
||||||
|
var flags = std.ArrayList([]const u8).init(b.allocator);
|
||||||
|
defer flags.deinit();
|
||||||
|
try flags.append("-DWUFFS_IMPLEMENTATION");
|
||||||
|
inline for (@import("src/c.zig").defines) |key| {
|
||||||
|
try flags.append("-D" ++ key);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.addIncludePath(wuffs.path("release/c"));
|
||||||
|
module.addCSourceFile(.{
|
||||||
|
.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);
|
||||||
|
}
|
21
build.zig.zon
Normal file
21
build.zig.zon
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
.{
|
||||||
|
.name = "wuffs",
|
||||||
|
.version = "0.0.0",
|
||||||
|
.dependencies = .{
|
||||||
|
.wuffs = .{
|
||||||
|
.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=d843c2714d32e15b48b8d7eeb480295af537f877",
|
||||||
|
.hash = "12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806",
|
||||||
|
}
|
||||||
|
// .apple_sdk = .{ .path = "../apple-sdk" },
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
},
|
||||||
|
}
|
101
flake.lock
Normal file
101
flake.lock
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1696426674,
|
||||||
|
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1734424634,
|
||||||
|
"narHash": "sha256-cHar1vqHOOyC7f1+tVycPoWTfKIaqkoe1Q6TnKzuti4=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "d3c42f187194c26d9f0309a8ecc469d6c878ce33",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"zig": "zig"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zig": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"flake-utils": [
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1734740962,
|
||||||
|
"narHash": "sha256-5YsE/uxeHJSa1S86553fgjjYDRJl7iLs41cSSXW/K04=",
|
||||||
|
"owner": "mitchellh",
|
||||||
|
"repo": "zig-overlay",
|
||||||
|
"rev": "956fa2f98490537625f582122b6f7f8adabf9686",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "mitchellh",
|
||||||
|
"repo": "zig-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
39
flake.nix
Normal file
39
flake.nix
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
description = "zig-wuffs";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs = {
|
||||||
|
url = "nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
flake-utils = {
|
||||||
|
url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
zig = {
|
||||||
|
url = "github:mitchellh/zig-overlay";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
inputs.flake-utils.follows = "flake-utils";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {
|
||||||
|
nixpkgs,
|
||||||
|
flake-utils,
|
||||||
|
zig,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
in
|
||||||
|
flake-utils.lib.eachDefaultSystem (
|
||||||
|
system: let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
name = "zig-wuffs";
|
||||||
|
nativeBuildInputs = [
|
||||||
|
zig.packages.${system}.master
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
18
src/c.zig
Normal file
18
src/c.zig
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
pub const c = @cImport({
|
||||||
|
for (defines) |d| @cDefine(d, "1");
|
||||||
|
@cInclude("wuffs-v0.4.c");
|
||||||
|
});
|
||||||
|
|
||||||
|
/// All the C macros defined so that the header matches the build.
|
||||||
|
pub const defines: []const []const u8 = &[_][]const u8{
|
||||||
|
"WUFFS_CONFIG__MODULES",
|
||||||
|
"WUFFS_CONFIG__MODULE__AUX__BASE",
|
||||||
|
"WUFFS_CONFIG__MODULE__AUX__IMAGE",
|
||||||
|
"WUFFS_CONFIG__MODULE__BASE",
|
||||||
|
"WUFFS_CONFIG__MODULE__ADLER32",
|
||||||
|
"WUFFS_CONFIG__MODULE__CRC32",
|
||||||
|
"WUFFS_CONFIG__MODULE__DEFLATE",
|
||||||
|
"WUFFS_CONFIG__MODULE__JPEG",
|
||||||
|
"WUFFS_CONFIG__MODULE__PNG",
|
||||||
|
"WUFFS_CONFIG__MODULE__ZLIB",
|
||||||
|
};
|
13
src/error.zig
Normal file
13
src/error.zig
Normal file
|
@ -0,0 +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
src/jpeg.zig
Normal file
146
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);
|
||||||
|
}
|
9
src/main.zig
Normal file
9
src/main.zig
Normal file
|
@ -0,0 +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());
|
||||||
|
}
|
146
src/png.zig
Normal file
146
src/png.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_png);
|
||||||
|
|
||||||
|
/// Decode a PNG 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_png__decoder());
|
||||||
|
defer alloc.free(decoder_buf);
|
||||||
|
|
||||||
|
const decoder: ?*c.wuffs_png__decoder = @ptrCast(decoder_buf);
|
||||||
|
{
|
||||||
|
const status = c.wuffs_png__decoder__initialize(
|
||||||
|
decoder,
|
||||||
|
c.sizeof__wuffs_png__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_png__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_png__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_png__decoder__decode_frame_config(
|
||||||
|
decoder,
|
||||||
|
&frame_config,
|
||||||
|
&source_buffer,
|
||||||
|
);
|
||||||
|
try check(log, &status);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const status = c.wuffs_png__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 "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);
|
||||||
|
}
|
103
src/swizzle.zig
Normal file
103
src/swizzle.zig
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const c = @import("c.zig").c;
|
||||||
|
const Error = @import("error.zig").Error;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.wuffs_swizzler);
|
||||||
|
|
||||||
|
pub fn gToRgba(alloc: Allocator, src: []const u8) Error![]u8 {
|
||||||
|
return swizzle(
|
||||||
|
alloc,
|
||||||
|
src,
|
||||||
|
c.WUFFS_BASE__PIXEL_FORMAT__Y,
|
||||||
|
c.WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gaToRgba(alloc: Allocator, src: []const u8) Error![]u8 {
|
||||||
|
return swizzle(
|
||||||
|
alloc,
|
||||||
|
src,
|
||||||
|
c.WUFFS_BASE__PIXEL_FORMAT__YA_PREMUL,
|
||||||
|
c.WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rgbToRgba(alloc: Allocator, src: []const u8) Error![]u8 {
|
||||||
|
return swizzle(
|
||||||
|
alloc,
|
||||||
|
src,
|
||||||
|
c.WUFFS_BASE__PIXEL_FORMAT__RGB,
|
||||||
|
c.WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swizzle(
|
||||||
|
alloc: Allocator,
|
||||||
|
src: []const u8,
|
||||||
|
comptime src_pixel_format: u32,
|
||||||
|
comptime dst_pixel_format: u32,
|
||||||
|
) Error![]u8 {
|
||||||
|
const src_slice = c.wuffs_base__make_slice_u8(
|
||||||
|
@constCast(src.ptr),
|
||||||
|
src.len,
|
||||||
|
);
|
||||||
|
|
||||||
|
const dst_fmt = c.wuffs_base__make_pixel_format(
|
||||||
|
dst_pixel_format,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(c.wuffs_base__pixel_format__is_direct(&dst_fmt));
|
||||||
|
assert(c.wuffs_base__pixel_format__is_interleaved(&dst_fmt));
|
||||||
|
assert(c.wuffs_base__pixel_format__bits_per_pixel(&dst_fmt) % 8 == 0);
|
||||||
|
|
||||||
|
const dst_size = c.wuffs_base__pixel_format__bits_per_pixel(&dst_fmt) / 8;
|
||||||
|
|
||||||
|
const src_fmt = c.wuffs_base__make_pixel_format(
|
||||||
|
src_pixel_format,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(c.wuffs_base__pixel_format__is_direct(&src_fmt));
|
||||||
|
assert(c.wuffs_base__pixel_format__is_interleaved(&src_fmt));
|
||||||
|
assert(c.wuffs_base__pixel_format__bits_per_pixel(&src_fmt) % 8 == 0);
|
||||||
|
|
||||||
|
const src_size = c.wuffs_base__pixel_format__bits_per_pixel(&src_fmt) / 8;
|
||||||
|
|
||||||
|
assert(src.len % src_size == 0);
|
||||||
|
|
||||||
|
const dst = try alloc.alloc(u8, src.len * dst_size / src_size);
|
||||||
|
errdefer alloc.free(dst);
|
||||||
|
|
||||||
|
const dst_slice = c.wuffs_base__make_slice_u8(
|
||||||
|
dst.ptr,
|
||||||
|
dst.len,
|
||||||
|
);
|
||||||
|
|
||||||
|
var swizzler: c.wuffs_base__pixel_swizzler = undefined;
|
||||||
|
{
|
||||||
|
const status = c.wuffs_base__pixel_swizzler__prepare(
|
||||||
|
&swizzler,
|
||||||
|
dst_fmt,
|
||||||
|
c.wuffs_base__empty_slice_u8(),
|
||||||
|
src_fmt,
|
||||||
|
c.wuffs_base__empty_slice_u8(),
|
||||||
|
c.WUFFS_BASE__PIXEL_BLEND__SRC,
|
||||||
|
);
|
||||||
|
if (!c.wuffs_base__status__is_ok(&status)) {
|
||||||
|
const e = c.wuffs_base__status__message(&status);
|
||||||
|
log.warn("{s}", .{e});
|
||||||
|
return error.WuffsError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
_ = c.wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(
|
||||||
|
&swizzler,
|
||||||
|
dst_slice,
|
||||||
|
c.wuffs_base__empty_slice_u8(),
|
||||||
|
src_slice,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
}
|
Loading…
Reference in a new issue