Last active
February 15, 2025 10:55
-
-
Save jjrv/cb373bcbde3f5d6287977f5ad0c510f8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
pub const Sizes = @Vector(4, u6); | |
const debug = true; | |
pub const BitReader = struct { | |
word_buf: [*]const u32, | |
bit_count: u6, | |
bits: u64, | |
// For debugging, bit offset within stream. | |
pos: usize = 0, | |
pub fn init(data: []const u8, pos: usize) BitReader { | |
const offset: u6 = @intCast(@intFromPtr(&data[pos / 32]) & 3); | |
const word_buf: [*]const u32 = @ptrCast(@alignCast(@as([*]const u8, @ptrCast(data[pos / 32 ..])) - offset)); | |
const pos5: u5 = @truncate(pos); | |
const bit_count = 32 - offset * 8 - pos5; | |
return .{ // | |
// Without initial refill: | |
// .word_buf = word_buf + 1, | |
// .bit_count = bit_count, | |
// .bits = word_buf[0] >> pos5, | |
// With initial refill: | |
.word_buf = word_buf + ((bit_count >> 5) ^ 1) + 1, | |
.bit_count = bit_count | 32, | |
.bits = (word_buf[0] >> pos5) | (@as(u64, word_buf[1]) << bit_count), | |
.pos = pos, | |
}; | |
} | |
// General bit stream operations | |
// ----------------------------- | |
/// Fill buffer so at least 32 bits are available. | |
/// No bounds checking, caller must avoid reading past end of input. | |
pub inline fn refill(self: *BitReader) void { | |
self.bits |= @as(u64, self.word_buf[0]) << self.bit_count; | |
self.word_buf += (self.bit_count >> 5) ^ 1; | |
self.bit_count |= 32; | |
} | |
/// Read bits from buffer without consuming them. | |
/// You can also peek any integer type u1-u32 by directly accessing @truncate(bit_reader.bits). | |
pub inline fn peek(self: *BitReader, count: u6) u32 { | |
std.debug.assert(count <= 32); | |
std.debug.assert(self.bit_count >= count); | |
return @intCast(self.bits & ((@as(u64, 1) << count) - 1)); | |
} | |
/// Discard 0-32 bits of input. | |
pub inline fn consume(self: *BitReader, count: u6) void { | |
std.debug.assert(count <= 32); | |
std.debug.assert(self.bit_count >= count); | |
self.bits >>= count; | |
self.bit_count -= count; | |
if (debug) self.pos += count; | |
} | |
/// Discard 0-32 bits of input and fill buffer so at least 32 bits are available. | |
/// No bounds checking, caller must avoid reading past end of input. | |
pub inline fn consumeRefill(self: *BitReader, count: u6) void { | |
std.debug.assert(count <= 32); | |
std.debug.assert(self.bit_count >= count); | |
const remain_count = self.bit_count - count; | |
self.bits = (self.bits >> count) | (@as(u64, self.word_buf[0]) << remain_count); | |
self.word_buf += (remain_count >> 5) ^ 1; | |
self.bit_count = remain_count | 32; | |
if (debug) self.pos += count; | |
} | |
/// Return given type of value (u1-u32 or equivalent) filled with input bits, | |
/// consume them from input buffer and re-fill it. | |
/// No bounds checking, caller must avoid reading past end of input. | |
pub fn get(self: *BitReader, Type: type) Type { | |
const info = @typeInfo(Type); | |
// If type is an enum or bool, use size of the represented integer. | |
const Int = switch (info) { | |
.int => Type, | |
.bool => u1, | |
.@"enum" => info.@"enum".tag_type, | |
else => @compileError("Unsupported type"), | |
}; | |
const count = @typeInfo(Int).int.bits; | |
if (count > 32) @compileError("Type has too many bits"); | |
defer self.consumeRefill(count); | |
const value: Int = @truncate(self.bits); | |
return switch (info) { | |
.int => value, | |
.bool => @bitCast(value), | |
.@"enum" => @enumFromInt(value), | |
else => @compileError("Unsupported type"), | |
}; | |
} | |
/// Return given number of input bits (0-32), consume them from input buffer and re-fill it. | |
/// No bounds checking, caller must avoid reading past end of input. | |
pub fn get32(self: *BitReader, count: u6) u32 { | |
defer self.consumeRefill(count); | |
return self.peek(count); | |
} | |
// JPEG XL -specific operations | |
// ---------------------------- | |
fn distribute(self: *BitReader, sizes: Sizes, comptime max_size: u6, Offset: type, offsets: @Vector(4, Offset)) u32 { | |
const distribution: u2 = @truncate(self.bits); | |
const prefix = if (max_size > 30) 0 else 2; | |
const count = (sizes + @as(Sizes, @splat(prefix)))[distribution]; | |
if (max_size > 30) self.consumeRefill(2); | |
defer self.consumeRefill(count); | |
return (self.peek(count) >> prefix) + if (Offset == u1) 0 else offsets[distribution]; | |
} | |
/// B.2.2 Read variable-length encoded u32. | |
pub inline fn getVar32(self: *BitReader, comptime sizes: Sizes, comptime offsets: ?@Vector(4, u32)) u32 { | |
const max_size = @reduce(.Max, sizes); | |
if (offsets) |off| { | |
const max_offset = @reduce(.Max, off); | |
const Offset = if (max_offset >= 0x10000) u32 else if (max_offset >= 0x100) u16 else u8; | |
return self.distribute(sizes, max_size, Offset, @Vector(4, Offset){ off[0], off[1], off[2], off[3] }); | |
} else { | |
return self.distribute(sizes, max_size, u1, @splat(0)); | |
} | |
} | |
/// B.2.3 Read variable-length encoded u64 (here only up to u32). | |
pub fn getVar64(self: *BitReader) u32 { | |
const distribution: u2 = @truncate(self.bits); | |
return if (distribution == 3) block: { | |
var result: u64 = self.get(u12); | |
var shift: u6 = 12; | |
while (self.get(bool)) { | |
if (shift == 60) { | |
result |= @as(u64, self.get(u4)) << 60; | |
break; | |
} | |
result |= @as(u64, self.get(u8)) << shift; | |
shift += 8; | |
} | |
std.debug.assert(result <= 0xffffffff); | |
break :block @intCast(result); | |
} else self.getVar32(.{ 0, 4, 8, 0 }, .{ 0, 1, 17, 0 }); | |
} | |
/// B.2.4 Read 16-bit float. | |
pub fn getF16(self: *BitReader) f32 { | |
const bits: u32 = self.get(u16); | |
const sign = (bits & 0x8000) << 16; | |
const exp_mantissa = ((bits & 0x7c00) + 0x1c000 + (bits & 0x3ff)) << 13; | |
return @bitCast(sign | exp_mantissa); | |
} | |
/// B.2.6 Read enum. | |
pub fn getEnum(self: *BitReader) u6 { | |
return @intCast(self.getVar32(.{ 0, 0, 4, 6 }, .{ 0, 1, 2, 18 })); | |
} | |
/// B.2.7 Skip to byte boundary. | |
pub fn skipToByte(self: *BitReader) void { | |
const count = self.bit_count & 7; | |
std.debug.assert(self.peek(count) == 0); | |
self.consume(count); | |
} | |
}; | |
test { | |
var rng = std.Random.DefaultPrng.init(1); | |
const expect = std.testing.expect; | |
const rand = rng.random(); | |
var buf: [256]u8 = undefined; | |
for (0..10) |_| { | |
for (0..buf.len) |i| { | |
buf[i] = rand.uintLessThan(u8, 255); | |
} | |
inline for (1..33) |size| { | |
comptime var info = @typeInfo(u32); | |
info.int.bits = size; | |
const Type = @Type(info); | |
for (0..size) |offset| { | |
var pos: usize = offset; | |
var br1 = BitReader.init(buf[0..buf.len], pos); | |
var br2 = BitReader.init(buf[0..buf.len], pos); | |
while (pos + size < buf.len * 8) { | |
var correct: u64 = 0; | |
for (0..size) |i| { | |
correct |= @as(u64, (buf[(pos + i) / 8] >> @truncate(pos + i)) & 1) << @truncate(i); | |
} | |
try expect(br1.get32(@truncate(size)) == correct); | |
if (size == 1) { | |
try expect(br2.get(bool) == (correct != 0)); | |
} else { | |
try expect(br2.get(Type) == correct); | |
} | |
pos += size; | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const BitReader = @import("BitReader.zig").BitReader; | |
const R = @import("Reader.zig"); | |
const tables = @import("tables.zig"); | |
const BundleReader = R.BundleReader; | |
const some = R.some; | |
const neq = R.neq; | |
const If = R.If; | |
const Repeat = R.Repeat; | |
pub fn getName(br: *BitReader, allocator: std.mem.Allocator) !?[]u8 { | |
const name_len: u16 = @truncate(br.getVar32(.{ 0, 4, 5, 10 }, .{ 0, 0, 16, 48 })); | |
if (name_len == 0) return null; | |
var name = try allocator.alloc(u8, name_len); | |
for (0..name_len) |n| name[n] = br.get(u8); | |
return name; | |
} | |
/// B.3 | |
pub const Extensions = struct { // | |
extensions: u32 = 0, | |
extension_bits: ?[]u32 = null, | |
pub fn init(br: *BitReader, allocator: std.mem.Allocator) !Extensions { | |
var r = BundleReader(Extensions, void).init(br, allocator, null); | |
errdefer r.target.deinit(allocator); | |
return try r.get(.{ | |
.{ .extensions, .Var64 }, | |
.{ Repeat(.extensions), .extension_bits, .Var64 }, | |
}); | |
} | |
pub fn deinit(self: *const Extensions, allocator: std.mem.Allocator) void { | |
if (self.extension_bits) |bits| { | |
allocator.free(bits); | |
} | |
} | |
}; | |
/// D.1 | |
pub const ImageHeader = struct { // | |
size: SizeHeader = .{}, | |
metadata: ImageMetadata = .{}, | |
pub fn init(br: *BitReader, allocator: std.mem.Allocator) !ImageHeader { | |
var r = BundleReader(ImageHeader, void).init(br, allocator, null); | |
return try r.get(.{ | |
.{.size}, // | |
.{ .metadata, .{allocator} }, | |
}); | |
} | |
pub fn deinit(self: *const ImageHeader, allocator: std.mem.Allocator) void { | |
self.metadata.deinit(allocator); | |
} | |
/// D.2 | |
pub const SizeHeader = struct { // | |
width: u32 = 0, | |
height: u32 = 0, | |
pub fn init(br: *BitReader) SizeHeader { | |
const div8 = br.get(bool); | |
const height = getDimension(br, div8); | |
const ratio = br.get(u3); | |
return .{ // | |
.width = if (ratio == 0) getDimension(br, div8) else applyRatio(height, ratio), | |
.height = height, | |
}; | |
} | |
fn getDimension(br: *BitReader, div8: bool) u32 { | |
return if (div8) | |
(br.get32(5) + 1) << 3 | |
else | |
br.getVar32(.{ 9, 13, 18, 30 }, null) + 1; | |
} | |
pub fn applyRatio(height: u32, ratio: u3) u32 { | |
return @truncate( // | |
@as(u64, height) * | |
([_]u8{ 0, 1, 6, 4, 3, 16, 5, 2 })[ratio] / | |
([_]u8{ 0, 1, 5, 3, 2, 9, 4, 1 })[ratio] // | |
); | |
} | |
}; | |
/// D.3.1 | |
pub const ImageMetadata = struct { | |
orientation: Orientation = .top_left, | |
intrinsic_size: ?SizeHeader = null, | |
preview: ?PreviewHeader = null, | |
animation: ?AnimationHeader = null, | |
bit_depth: ?BitDepth = null, | |
modular_16bit_buffers: bool = true, | |
num_extra: u16 = 0, | |
ec_info: ?[]ExtraChannelInfo = null, | |
xyb_encoded: bool = true, | |
colour_encoding: ?ColourEncoding = null, | |
tone_mapping: ?ToneMapping = null, | |
extensions: ?Extensions = null, | |
opsin_inverse_matrix: ?OpsinInverseMatrix = null, | |
up2_weight: [15]f32 = tables.up2_weight, | |
up4_weight: [55]f32 = tables.up4_weight, | |
up8_weight: [210]f32 = tables.up8_weight, | |
pub fn init(br: *BitReader, allocator: std.mem.Allocator) !ImageMetadata { | |
var flags: struct { // | |
all_default: bool = false, | |
extra_fields: bool = false, | |
have_intr_size: bool = false, | |
have_preview: bool = false, | |
have_animation: bool = false, | |
default_m: bool = false, | |
cw_mask: u3 = 0, | |
} = .{}; | |
var r = BundleReader(ImageMetadata, @TypeOf(flags)).init(br, allocator, &flags); | |
errdefer r.target.deinit(allocator); | |
const custom_schema = .{ | |
.{.extra_fields}, | |
.{ | |
If(.extra_fields, true), .{ | |
.{.orientation}, // | |
.{.have_intr_size}, | |
.{ If(.have_intr_size, true), .intrinsic_size }, | |
.{.have_preview}, | |
.{ If(.have_preview, true), .preview }, | |
.{.have_animation}, | |
.{ If(.have_animation, true), .animation }, | |
}, | |
}, | |
.{.bit_depth}, // | |
.{.modular_16bit_buffers}, | |
.{ .num_extra, .{ 0, 0, 4, 12 }, .{ 0, 1, 2, 1 } }, | |
.{ Repeat(.num_extra), .ec_info, .{allocator} }, | |
.{.xyb_encoded}, | |
.{.colour_encoding}, | |
.{ If(.extra_fields, true), .tone_mapping }, | |
.{ .extensions, .{allocator} }, | |
.{.default_m}, | |
.{ | |
If(.default_m, false), .{ // | |
.{ If(.xyb_encoded, true), .opsin_inverse_matrix }, | |
.{.cw_mask}, | |
.{ If(.cw_mask, some(u3, 1)), .up2_weight }, | |
.{ If(.cw_mask, some(u3, 2)), .up4_weight }, | |
.{ If(.cw_mask, some(u3, 4)), .up8_weight }, | |
}, | |
}, | |
}; | |
return try r.get(.{ | |
.{.all_default}, | |
.{ If(.all_default, false), custom_schema }, | |
}); | |
} | |
pub fn deinit(self: *const ImageMetadata, allocator: std.mem.Allocator) void { | |
if (self.ec_info) |ec_info| { | |
for (0..self.num_extra) |n| ec_info[n].deinit(allocator); | |
allocator.free(ec_info); | |
} | |
if (self.extensions) |extensions| extensions.deinit(allocator); | |
} | |
/// D.3.2 | |
pub const Orientation = enum(u3) { // | |
top_left = 0, | |
top_right = 1, | |
bottom_right = 2, | |
bottom_left = 3, | |
left_top = 4, | |
right_top = 5, | |
right_bottom = 6, | |
left_bottom = 7, | |
}; | |
/// D.3.3 | |
pub const PreviewHeader = struct { | |
width: u32 = 8, | |
height: u32 = 8, | |
pub fn init(br: *BitReader) PreviewHeader { | |
const div8 = br.get(bool); | |
const height = getDimension(br, div8); | |
const ratio = br.get(u3); | |
return .{ // | |
.width = if (ratio == 0) getDimension(br, div8) else SizeHeader.applyRatio(height, ratio), | |
.height = height, | |
}; | |
} | |
fn getDimension(br: *BitReader, div8: bool) u32 { | |
return if (div8) | |
br.getVar32(.{ 0, 0, 5, 9 }, .{ 16, 32, 1, 33 }) << 3 | |
else | |
br.getVar32(.{ 6, 8, 10, 12 }, .{ 1, 65, 321, 1345 }); | |
} | |
}; | |
/// D.3.4 | |
pub const AnimationHeader = struct { // | |
tps_numerator: u32 = 0, | |
tps_denominator: u16 = 0, | |
num_loops: u32 = 0, | |
have_timecodes: bool = false, | |
pub fn init(br: *BitReader) AnimationHeader { | |
var r = BundleReader(AnimationHeader, void).init(br, null, null); | |
return try r.get(.{ | |
.{ .tps_numerator, .{ 0, 0, 10, 30 }, .{ 100, 1000, 1, 1 } }, | |
.{ .tps_denominator, .{ 0, 0, 8, 10 }, .{ 1, 1001, 1, 1 } }, | |
.{ .num_loops, .{ 0, 3, 16, 32 } }, | |
.{.have_timecodes}, | |
}); | |
} | |
}; | |
/// D.3.5 | |
pub const BitDepth = struct { // | |
bits_per_sample: u8 = 8, | |
exp_bits: u8 = 0, | |
pub fn init(br: *BitReader) BitDepth { | |
var flags: struct { float_sample: bool = false } = .{}; | |
var r = BundleReader(BitDepth, @TypeOf(flags)).init(br, null, &flags); | |
return try r.get(.{ | |
.{.float_sample}, | |
.{ If(.float_sample, true), .{ | |
.{ .bits_per_sample, .{ 0, 0, 0, 6 }, .{ 32, 16, 24, 1 } }, | |
.{ .exp_bits, 4, 1 }, | |
} }, | |
.{ If(.float_sample, false), .bits_per_sample, .{ 0, 0, 0, 6 }, .{ 8, 10, 12, 1 } }, | |
}); | |
} | |
}; | |
/// D.3.6 | |
pub const ExtraChannelInfo = struct { // | |
channel_type: ExtraChannelType = .alpha, | |
bit_depth: ?BitDepth = null, | |
dim_shift: u8 = 0, | |
name: ?[]const u8 = null, | |
alpha_associated: bool = false, | |
red: f32 = 0, | |
green: f32 = 0, | |
blue: f32 = 0, | |
solidity: f32 = 0, | |
cfa_channel: u16 = 1, | |
pub fn init(br: *BitReader, allocator: std.mem.Allocator) !ExtraChannelInfo { | |
var flags: struct { d_alpha: bool = false } = .{}; | |
var r = BundleReader(ExtraChannelInfo, @TypeOf(flags)).init(br, null, &flags); | |
var info = try r.get(.{ | |
.{.d_alpha}, | |
.{ If(.d_alpha, false), .{ | |
.{ .channel_type, .Enum }, | |
.{.bit_depth}, | |
.{ .dim_shift, .{ 0, 0, 0, 3 }, .{ 0, 3, 4, 1 } }, | |
} }, | |
}); | |
// TODO move to schema on lines above. | |
if (!flags.d_alpha) { | |
info.name = try getName(br, allocator); | |
switch (info.channel_type) { | |
.alpha => info.alpha_associated = br.get(bool), | |
.spot_colour => { | |
info.red = br.getF16(); | |
info.green = br.getF16(); | |
info.blue = br.getF16(); | |
info.solidity = br.getF16(); | |
}, | |
.cfa => { | |
info.cfa_channel = @truncate(br.getVar32(.{ 0, 2, 4, 8 }, .{ 1, 0, 3, 19 })); | |
}, | |
else => {}, | |
} | |
} | |
return info; | |
} | |
pub fn deinit(self: *const ExtraChannelInfo, allocator: std.mem.Allocator) void { | |
if (self.name) |name| allocator.free(name); | |
} | |
pub const ExtraChannelType = enum(u6) { // | |
alpha = 0, | |
depth = 1, | |
spot_colour = 2, | |
selection_mask = 3, | |
black = 4, | |
cfa = 5, | |
thermal = 6, | |
non_optional = 15, | |
optional = 16, | |
_, | |
}; | |
}; | |
/// E.2 / Table E.1 | |
pub const ColourEncoding = struct { // | |
want_icc: bool = false, | |
colour_space: ColourSpace = .rgb, | |
white_point: WhitePoint = .d65, | |
white: ?CustomXY = null, | |
primaries: Primaries = .srgb, | |
red: ?CustomXY = null, | |
green: ?CustomXY = null, | |
blue: ?CustomXY = null, | |
tf: ?CustomTransferFunction = null, | |
rendering_intent: RenderingIntent = .relative, | |
pub fn init(br: *BitReader) ColourEncoding { | |
var flags: struct { all_default: bool = false } = .{}; | |
var r = BundleReader(ColourEncoding, @TypeOf(flags)).init(br, null, &flags); | |
const custom_schema = .{ | |
.{.want_icc}, | |
.{ .colour_space, .Enum }, | |
.{ | |
If(.want_icc, false), .{ | |
.{ | |
If(.colour_space, neq(ColourSpace, .xyb)), | |
.{ | |
.{ .white_point, .Enum }, | |
.{ If(.white_point, WhitePoint.custom), .white }, | |
.{ If(.colour_space, neq(ColourSpace, .grey)), .{ | |
.{ .primaries, .Enum }, | |
.{ If(.primaries, Primaries.custom), .{ | |
.{.red}, | |
.{.green}, | |
.{.blue}, | |
} }, | |
} }, | |
// SPEC missing not_xyb | |
.{.tf}, | |
}, | |
}, | |
.{ .rendering_intent, .Enum }, | |
}, | |
}, | |
}; | |
return try r.get(.{ | |
.{.all_default}, // | |
.{ If(.all_default, false), custom_schema }, | |
}); | |
} | |
/// Table E.2 | |
pub const CustomXY = struct { | |
// TODO: Implement signed unpack, then change these to i32. | |
ux: u32 = 0, | |
uy: u32 = 0, | |
pub fn init(br: *BitReader) CustomXY { | |
var r = BundleReader(CustomXY, void).init(br, null, null); | |
return try r.get(.{ .{.ux}, .{.uy} }); | |
} | |
}; | |
/// Table E.3 | |
pub const ColourSpace = enum(u6) { // | |
rgb = 0, | |
grey = 1, | |
xyb = 2, | |
unknown = 3, | |
_, | |
}; | |
/// Table E.4 | |
pub const WhitePoint = enum(u6) { // | |
d65 = 1, | |
custom = 2, | |
e = 10, | |
dci = 11, | |
_, | |
}; | |
/// Table E.5 | |
pub const Primaries = enum(u6) { // | |
srgb = 1, | |
custom = 2, | |
bt2100 = 9, | |
p3 = 11, | |
_, | |
}; | |
/// Table E.7 | |
pub const CustomTransferFunction = struct { | |
gamma: ?u32 = null, | |
transfer_function: TransferFunction = .srgb, | |
pub fn init(br: *BitReader) CustomTransferFunction { | |
var flags: struct { have_gamma: bool = false } = .{}; | |
var r = BundleReader(CustomTransferFunction, @TypeOf(flags)).init(br, null, &flags); | |
return try r.get(.{ | |
.{.have_gamma}, | |
.{ If(.have_gamma, true), .gamma, 24 }, | |
.{ If(.have_gamma, false), .transfer_function, .Enum }, | |
}); | |
} | |
/// Table E.6 | |
pub const TransferFunction = enum(u6) { // | |
bt709 = 1, | |
unknown = 2, | |
linear = 8, | |
srgb = 13, | |
pq = 16, | |
dci = 17, | |
hlg = 18, | |
}; | |
}; | |
/// Table E.8 | |
pub const RenderingIntent = enum(u6) { // | |
perceptual = 0, | |
relative = 1, | |
saturation = 2, | |
absolute = 3, | |
_, | |
}; | |
}; | |
/// E.3 / Table E.9 | |
pub const ToneMapping = struct { // | |
intensity_target: f32 = 255, | |
min_nits: f32 = 0, | |
relative_to_max_display: bool = false, | |
linear_below: f32 = 0, | |
pub fn init(br: *BitReader) ToneMapping { | |
var flags: struct { all_default: bool = false } = .{}; | |
var r = BundleReader(ToneMapping, @TypeOf(flags)).init(br, null, &flags); | |
return try r.get(.{ | |
.{.all_default}, | |
.{ | |
If(.all_default, false), .{ // | |
.{.intensity_target}, | |
.{.min_nits}, | |
.{.relative_to_max_display}, | |
.{.linear_below}, | |
}, | |
}, | |
}); | |
} | |
}; | |
pub const OpsinInverseMatrix = struct { // | |
inv_mat: [9]f32 = [_]f32{ // | |
11.031566901960783, -9.866943921568629, -0.16462299647058826, | |
-3.254147380392157, 4.418770392156863, -0.16462299647058826, | |
-3.6588512862745097, 2.7129230470588235, 1.9459282392156863, | |
}, | |
opsin_bias: [3]f32 = [_]f32{ opsin_bias_term, opsin_bias_term, opsin_bias_term }, | |
quant_bias: [3]f32 = [_]f32{ // | |
1 - 0.05465007330715401, | |
1 - 0.07005449891748593, | |
1 - 0.049935103337343655, | |
}, | |
quant_bias_numerator: f32 = 0.145, | |
const opsin_bias_term = 0.0037930732552754493; | |
pub fn init(br: *BitReader) OpsinInverseMatrix { | |
var flags: struct { all_default: bool = false } = .{}; | |
var r = BundleReader(OpsinInverseMatrix, @TypeOf(flags)).init(br, null, &flags); | |
return try r.get(.{ | |
.{.all_default}, | |
.{ | |
If(.all_default, false), .{ // | |
.{.inv_mat}, | |
.{.opsin_bias}, | |
.{.quant_bias}, | |
.{.quant_bias_numerator}, | |
}, | |
}, | |
}); | |
} | |
}; | |
}; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const BitReader = @import("BitReader.zig").BitReader; | |
const FlowKind = enum(u8) { If, Repeat }; | |
pub fn If(flag: anytype, equals: anytype) type { | |
return struct { | |
const kind: FlowKind = .If; | |
const name = @tagName(flag); | |
const value = equals; | |
}; | |
} | |
pub fn Repeat(flag: anytype) type { | |
return struct { | |
const kind: FlowKind = .Repeat; | |
const name = if (@typeInfo(@TypeOf(flag)) == .enum_literal) @tagName(flag) else @compileError("Repeat(flag): flag must be an enum literal"); | |
}; | |
} | |
/// Create an "iterator" for reading structs from a JPEG XL bit stream based on schemas, | |
/// storing values for different fields in target or scratch (for condition flags) structs. | |
pub fn BundleReader(Type: type, Scratch: type) type { | |
return struct { | |
br: *BitReader, | |
allocator: ?std.mem.Allocator = null, | |
target: Type = .{}, | |
scratch: ?*Scratch = null, | |
pub fn init(br: *BitReader, allocator: ?std.mem.Allocator, scratch: ?*Scratch) @This() { | |
return .{ .br = br, .allocator = allocator, .scratch = scratch }; | |
} | |
// Parse a JPEG XL bundle according to given schema. | |
pub fn get(self: *@This(), schema: anytype) !Type { | |
inline for (schema) |entry| { | |
comptime var arg = 0; | |
comptime var entry_type = @typeInfo(@TypeOf(entry[arg])); | |
var repeat: u32 = 1; | |
// Entry starting with a type represents flow control. | |
if (entry_type == .type) { | |
const target = if (@hasField(Type, entry[arg].name)) &self.target else self.scratch.?; | |
repeat = switch (entry[arg].kind) { | |
.If => block: { | |
if (@typeInfo(@TypeOf(entry[arg].value)) == .@"fn") { | |
// If condition is a function, call it passing the parsed struct so far. | |
// Only parse the entry if it returns true. | |
break :block if (@call(.always_inline, entry[arg].value, .{@field(target, entry[arg].name)})) 1 else 0; | |
} else { | |
// Otherwise condition should be the name of a boolean field | |
// in target or scratch struct. Only parse the entry if it's true. | |
break :block if (@field(target, entry[arg].name) == entry[arg].value) 1 else 0; | |
} | |
}, | |
// Repeat defines how many times entry should be stored in target slice. | |
// Don't use if target is a fixed size array (repeat count is known). | |
.Repeat => @field(target, entry[arg].name), | |
}; | |
// Consume flow control directive and parse rest of entry. | |
arg += 1; | |
entry_type = @typeInfo(@TypeOf(entry[arg])); | |
} | |
// Entry starting with a struct represents a list of entries, | |
// grouped into a block for flow control. | |
if (entry_type == .@"struct") { | |
if (repeat == 1) _ = try self.get(entry[arg]); | |
continue; | |
} | |
// Entry starting with an enum tag represents storing a value to target or scratch struct. | |
const key_name = @tagName(entry[arg]); | |
const is_pub = @hasField(Type, key_name); | |
if (!is_pub and !@hasField(Scratch, key_name)) @compileError("Unknown field name"); | |
// If enum tag matches a field name in target struct, store value there, otherwise matching field in scratch struct. | |
const target = if (is_pub) &self.target else self.scratch.?; | |
arg += 1; | |
// Get type of field in target or scratch struct. | |
comptime var FieldType = @FieldType(if (is_pub) Type else Scratch, key_name); | |
comptime var field_info = @typeInfo(FieldType); | |
// Type of field possibly still wrapped in an optional. | |
const Wrapped = FieldType; | |
if (field_info == .optional) { | |
// Unwrap optional type. | |
FieldType = field_info.optional.child; | |
field_info = @typeInfo(FieldType); | |
} | |
const is_slice = field_info == .pointer and field_info.pointer.size == .slice; | |
var dst = switch (field_info) { | |
.array => block: { | |
// If target is an array, repeat entry to fill it. | |
FieldType = field_info.array.child; | |
field_info = @typeInfo(FieldType); | |
repeat = @field(target, key_name).len; | |
break :block @as([*]FieldType, @ptrCast(&@field(target, key_name)[0])); | |
}, | |
.pointer => block: { | |
// If target is a slice, allocate memory to fit given number of repeated entries. | |
FieldType = field_info.pointer.child; | |
field_info = @typeInfo(FieldType); | |
const slice = try self.allocator.?.alloc(FieldType, repeat); | |
@field(target, key_name) = slice; | |
break :block @as([*]FieldType, @ptrCast(slice)); | |
}, | |
// Otherwise, store value directly in the field. If the field is optional, | |
// the pointer type we write to must reflect that for the field to be readable. | |
else => @as([*]Wrapped, @ptrCast(&@field(target, key_name))), | |
}; | |
errdefer if (is_slice) { | |
// On error deallocate memory allocated for this entry. | |
// Any previous entries have already been written to the target or scratch struct, | |
// so the caller can deinit them. | |
if (field_info == .@"struct" and @hasDecl(FieldType, "deinit")) { | |
const slice = @field(target, key_name).?; | |
const deinit_args = @typeInfo(@TypeOf(FieldType.deinit)).@"fn".params; | |
// Deinit already inited items before deallocating slice. | |
for (0..dst - @as([*]FieldType, @ptrCast(slice))) |n| { | |
if (deinit_args.len == 2) slice[n].deinit(self.allocator.?) else slice[n].deinit(); | |
} | |
} | |
self.allocator.?.free(@field(target, key_name).?); | |
// Clear field in target struct since now it points to deallocated memory. | |
@field(target, key_name) = null; | |
}; | |
// Print field name and bit offset. TODO: Comment this out... | |
std.debug.print("{s}: {}\n", .{ key_name, self.br.pos }); | |
while (repeat > 0) { | |
const value = if (field_info == .@"struct" and @hasDecl(FieldType, "init")) block: { | |
// If field is a struct with an init function, call it. | |
const init_fn = FieldType.init; | |
const init_info = @typeInfo(@TypeOf(init_fn)).@"fn"; | |
// Always pass bit reader as the first arg, then any other args given. | |
const args = .{self.br} ++ (if (entry.len > arg) entry[arg] else .{}); | |
// Call init handling possible error. | |
break :block if (@typeInfo(init_info.return_type.?) == .error_union) try @call(.auto, init_fn, args) else @call(.auto, init_fn, args); | |
} else if (entry.len <= arg) | |
// If field is a bool or integer and no other args given, fill it using bit reader. | |
// Read floats encoded using 16 bits. | |
if (FieldType == f32) self.br.getF16() else self.br.get(FieldType) | |
else switch (@typeInfo(@TypeOf(entry[arg]))) { | |
// If first given arg is a number, treat it as number of bits to read. | |
// Second arg, if present, is added to the value that was read. | |
.comptime_int => self.br.get32(entry[arg]) + if (entry.len > arg + 1) entry[arg + 1] else 0, | |
// If first given arg is a literal, it signals some special field types. | |
.enum_literal => switch (entry[arg]) { | |
// Enums have special encoding supporting values 0-81. | |
.Enum => self.br.getEnum(), | |
// Variable-length 64-bit encoding. | |
.Var64 => self.br.getVar64(), | |
else => @compileError("Unsupported entry argument type"), | |
}, | |
// If first given arg is a struct, treat it as a list of 4 possible bit counts to read, | |
// after reading a 2-bit "distribution" selector. Second arg, if present, | |
// is a list of 4 corresponding numbers to add to the value that was read. | |
.@"struct" => self.br.getVar32(entry[arg], if (entry.len > arg + 1) entry[arg + 1] else null), | |
else => @compileError("Unsupported entry argument type"), | |
}; | |
switch (field_info) { | |
.@"enum" => dst[0] = if (@typeInfo(@TypeOf(value)) == .@"enum") value else @enumFromInt(value), | |
// TODO: Unpack signed as needed | |
.int => dst[0] = @intCast(value), | |
// TODO: Bitcast if packed struct backed by integer. | |
else => dst[0] = value, | |
} | |
dst += 1; | |
repeat -= 1; | |
} | |
} | |
return self.target; | |
} | |
}; | |
} | |
pub fn some(Type: type, mask: Type) fn (Type) bool { | |
return struct { | |
pub fn check(value: Type) bool { | |
return value & mask != 0; | |
} | |
}.check; | |
} | |
pub fn neq(Type: type, not_equals: Type) fn (Type) bool { | |
return struct { | |
pub fn check(value: Type) bool { | |
return value != not_equals; | |
} | |
}.check; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment