619 lines
19 KiB
Zig
619 lines
19 KiB
Zig
const std = @import("std");
|
|
const testing = std.testing;
|
|
const assert = std.debug.assert;
|
|
|
|
pub fn init(allocator: std.mem.Allocator) void {
|
|
assert(mem_allocator == null);
|
|
mem_allocator = allocator;
|
|
mem_allocations = std.AutoHashMap(usize, usize).init(allocator);
|
|
|
|
// stb image
|
|
zstbiMallocPtr = zstbiMalloc;
|
|
zstbiReallocPtr = zstbiRealloc;
|
|
zstbiFreePtr = zstbiFree;
|
|
// stb image resize
|
|
zstbirMallocPtr = zstbirMalloc;
|
|
zstbirFreePtr = zstbirFree;
|
|
// stb image write
|
|
zstbiwMallocPtr = zstbiMalloc;
|
|
zstbiwReallocPtr = zstbiRealloc;
|
|
zstbiwFreePtr = zstbiFree;
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
assert(mem_allocator != null);
|
|
assert(mem_allocations.?.count() == 0);
|
|
|
|
setFlipVerticallyOnLoad(false);
|
|
setFlipVerticallyOnWrite(false);
|
|
|
|
mem_allocations.?.deinit();
|
|
mem_allocations = null;
|
|
mem_allocator = null;
|
|
}
|
|
|
|
pub const JpgWriteSettings = struct {
|
|
quality: u32,
|
|
};
|
|
|
|
pub const ImageWriteFormat = union(enum) {
|
|
png,
|
|
jpg: JpgWriteSettings,
|
|
};
|
|
|
|
pub const ImageWriteError = error{
|
|
CouldNotWriteImage,
|
|
};
|
|
|
|
pub const Image = struct {
|
|
data: []u8,
|
|
width: u32,
|
|
height: u32,
|
|
num_components: u32,
|
|
bytes_per_component: u32,
|
|
bytes_per_row: u32,
|
|
is_hdr: bool,
|
|
|
|
pub fn info(pathname: [:0]const u8) struct {
|
|
is_supported: bool,
|
|
width: u32,
|
|
height: u32,
|
|
num_components: u32,
|
|
} {
|
|
assert(mem_allocator != null);
|
|
|
|
var w: c_int = 0;
|
|
var h: c_int = 0;
|
|
var c: c_int = 0;
|
|
const is_supported = stbi_info(pathname, &w, &h, &c);
|
|
return .{
|
|
.is_supported = if (is_supported == 1) true else false,
|
|
.width = @as(u32, @intCast(w)),
|
|
.height = @as(u32, @intCast(h)),
|
|
.num_components = @as(u32, @intCast(c)),
|
|
};
|
|
}
|
|
|
|
pub fn loadFromFile(pathname: [:0]const u8, forced_num_components: u32) !Image {
|
|
assert(mem_allocator != null);
|
|
|
|
var width: u32 = 0;
|
|
var height: u32 = 0;
|
|
var num_components: u32 = 0;
|
|
var bytes_per_component: u32 = 0;
|
|
var bytes_per_row: u32 = 0;
|
|
var is_hdr = false;
|
|
|
|
const data = if (isHdr(pathname)) data: {
|
|
var x: c_int = undefined;
|
|
var y: c_int = undefined;
|
|
var ch: c_int = undefined;
|
|
const ptr = stbi_loadf(
|
|
pathname,
|
|
&x,
|
|
&y,
|
|
&ch,
|
|
@as(c_int, @intCast(forced_num_components)),
|
|
);
|
|
if (ptr == null) return error.ImageInitFailed;
|
|
|
|
num_components = if (forced_num_components == 0) @as(u32, @intCast(ch)) else forced_num_components;
|
|
width = @as(u32, @intCast(x));
|
|
height = @as(u32, @intCast(y));
|
|
bytes_per_component = 2;
|
|
bytes_per_row = width * num_components * bytes_per_component;
|
|
is_hdr = true;
|
|
|
|
// Convert each component from f32 to f16.
|
|
var ptr_f16 = @as([*]f16, @ptrCast(ptr.?));
|
|
const num = width * height * num_components;
|
|
var i: u32 = 0;
|
|
while (i < num) : (i += 1) {
|
|
ptr_f16[i] = @as(f16, @floatCast(ptr.?[i]));
|
|
}
|
|
break :data @as([*]u8, @ptrCast(ptr_f16))[0 .. height * bytes_per_row];
|
|
} else data: {
|
|
var x: c_int = undefined;
|
|
var y: c_int = undefined;
|
|
var ch: c_int = undefined;
|
|
const is_16bit = is16bit(pathname);
|
|
const ptr = if (is_16bit) @as(?[*]u8, @ptrCast(stbi_load_16(
|
|
pathname,
|
|
&x,
|
|
&y,
|
|
&ch,
|
|
@as(c_int, @intCast(forced_num_components)),
|
|
))) else stbi_load(
|
|
pathname,
|
|
&x,
|
|
&y,
|
|
&ch,
|
|
@as(c_int, @intCast(forced_num_components)),
|
|
);
|
|
if (ptr == null) return error.ImageInitFailed;
|
|
|
|
num_components = if (forced_num_components == 0) @as(u32, @intCast(ch)) else forced_num_components;
|
|
width = @as(u32, @intCast(x));
|
|
height = @as(u32, @intCast(y));
|
|
bytes_per_component = if (is_16bit) 2 else 1;
|
|
bytes_per_row = width * num_components * bytes_per_component;
|
|
is_hdr = false;
|
|
|
|
break :data @as([*]u8, @ptrCast(ptr))[0 .. height * bytes_per_row];
|
|
};
|
|
|
|
return Image{
|
|
.data = data,
|
|
.width = width,
|
|
.height = height,
|
|
.num_components = num_components,
|
|
.bytes_per_component = bytes_per_component,
|
|
.bytes_per_row = bytes_per_row,
|
|
.is_hdr = is_hdr,
|
|
};
|
|
}
|
|
|
|
pub fn loadFromMemory(data: []const u8, forced_num_components: u32) !Image {
|
|
assert(mem_allocator != null);
|
|
|
|
var width: u32 = 0;
|
|
var height: u32 = 0;
|
|
var num_components: u32 = 0;
|
|
var bytes_per_component: u32 = 0;
|
|
var bytes_per_row: u32 = 0;
|
|
var is_hdr = false;
|
|
|
|
const image_data = if (isHdrFromMem(data)) data: {
|
|
var x: c_int = undefined;
|
|
var y: c_int = undefined;
|
|
var ch: c_int = undefined;
|
|
const ptr = stbi_loadf_from_memory(
|
|
data.ptr,
|
|
@as(c_int, @intCast(data.len)),
|
|
&x,
|
|
&y,
|
|
&ch,
|
|
@as(c_int, @intCast(forced_num_components)),
|
|
);
|
|
if (ptr == null) return error.ImageInitFailed;
|
|
|
|
num_components = if (forced_num_components == 0) @as(u32, @intCast(ch)) else forced_num_components;
|
|
width = @as(u32, @intCast(x));
|
|
height = @as(u32, @intCast(y));
|
|
bytes_per_component = 2;
|
|
bytes_per_row = width * num_components * bytes_per_component;
|
|
is_hdr = true;
|
|
|
|
// Convert each component from f32 to f16.
|
|
var ptr_f16 = @as([*]f16, @ptrCast(ptr.?));
|
|
const num = width * height * num_components;
|
|
var i: u32 = 0;
|
|
while (i < num) : (i += 1) {
|
|
ptr_f16[i] = @as(f16, @floatCast(ptr.?[i]));
|
|
}
|
|
break :data @as([*]u8, @ptrCast(ptr_f16))[0 .. height * bytes_per_row];
|
|
} else data: {
|
|
var x: c_int = undefined;
|
|
var y: c_int = undefined;
|
|
var ch: c_int = undefined;
|
|
const ptr = stbi_load_from_memory(
|
|
data.ptr,
|
|
@as(c_int, @intCast(data.len)),
|
|
&x,
|
|
&y,
|
|
&ch,
|
|
@as(c_int, @intCast(forced_num_components)),
|
|
);
|
|
if (ptr == null) return error.ImageInitFailed;
|
|
|
|
num_components = if (forced_num_components == 0) @as(u32, @intCast(ch)) else forced_num_components;
|
|
width = @as(u32, @intCast(x));
|
|
height = @as(u32, @intCast(y));
|
|
bytes_per_component = 1;
|
|
bytes_per_row = width * num_components * bytes_per_component;
|
|
|
|
break :data @as([*]u8, @ptrCast(ptr))[0 .. height * bytes_per_row];
|
|
};
|
|
|
|
return Image{
|
|
.data = image_data,
|
|
.width = width,
|
|
.height = height,
|
|
.num_components = num_components,
|
|
.bytes_per_component = bytes_per_component,
|
|
.bytes_per_row = bytes_per_row,
|
|
.is_hdr = is_hdr,
|
|
};
|
|
}
|
|
|
|
pub fn createEmpty(width: u32, height: u32, num_components: u32, args: struct {
|
|
bytes_per_component: u32 = 0,
|
|
bytes_per_row: u32 = 0,
|
|
}) !Image {
|
|
assert(mem_allocator != null);
|
|
|
|
const bytes_per_component = if (args.bytes_per_component == 0) 1 else args.bytes_per_component;
|
|
const bytes_per_row = if (args.bytes_per_row == 0)
|
|
width * num_components * bytes_per_component
|
|
else
|
|
args.bytes_per_row;
|
|
|
|
const size = height * bytes_per_row;
|
|
|
|
const data = @as([*]u8, @ptrCast(zstbiMalloc(size)));
|
|
@memset(data[0..size], 0);
|
|
|
|
return Image{
|
|
.data = data[0..size],
|
|
.width = width,
|
|
.height = height,
|
|
.num_components = num_components,
|
|
.bytes_per_component = bytes_per_component,
|
|
.bytes_per_row = bytes_per_row,
|
|
.is_hdr = false,
|
|
};
|
|
}
|
|
|
|
pub fn resize(image: *const Image, new_width: u32, new_height: u32) Image {
|
|
assert(mem_allocator != null);
|
|
|
|
// TODO: Add support for HDR images
|
|
const new_bytes_per_row = new_width * image.num_components * image.bytes_per_component;
|
|
const new_size = new_height * new_bytes_per_row;
|
|
const new_data = @as([*]u8, @ptrCast(zstbiMalloc(new_size)));
|
|
stbir_resize_uint8(
|
|
image.data.ptr,
|
|
@as(c_int, @intCast(image.width)),
|
|
@as(c_int, @intCast(image.height)),
|
|
0,
|
|
new_data,
|
|
@as(c_int, @intCast(new_width)),
|
|
@as(c_int, @intCast(new_height)),
|
|
0,
|
|
@as(c_int, @intCast(image.num_components)),
|
|
);
|
|
return .{
|
|
.data = new_data[0..new_size],
|
|
.width = new_width,
|
|
.height = new_height,
|
|
.num_components = image.num_components,
|
|
.bytes_per_component = image.bytes_per_component,
|
|
.bytes_per_row = new_bytes_per_row,
|
|
.is_hdr = image.is_hdr,
|
|
};
|
|
}
|
|
|
|
pub fn writeToFile(
|
|
image: Image,
|
|
filename: [:0]const u8,
|
|
image_format: ImageWriteFormat,
|
|
) ImageWriteError!void {
|
|
assert(mem_allocator != null);
|
|
|
|
const w = @as(c_int, @intCast(image.width));
|
|
const h = @as(c_int, @intCast(image.height));
|
|
const comp = @as(c_int, @intCast(image.num_components));
|
|
const result = switch (image_format) {
|
|
.png => stbi_write_png(filename.ptr, w, h, comp, image.data.ptr, 0),
|
|
.jpg => |settings| stbi_write_jpg(
|
|
filename.ptr,
|
|
w,
|
|
h,
|
|
comp,
|
|
image.data.ptr,
|
|
@as(c_int, @intCast(settings.quality)),
|
|
),
|
|
};
|
|
// if the result is 0 then it means an error occured (per stb image write docs)
|
|
if (result == 0) {
|
|
return ImageWriteError.CouldNotWriteImage;
|
|
}
|
|
}
|
|
|
|
pub fn writeToFn(
|
|
image: Image,
|
|
write_fn: *const fn (ctx: ?*anyopaque, data: ?*anyopaque, size: c_int) callconv(.C) void,
|
|
context: ?*anyopaque,
|
|
image_format: ImageWriteFormat,
|
|
) ImageWriteError!void {
|
|
assert(mem_allocator != null);
|
|
|
|
const w = @as(c_int, @intCast(image.width));
|
|
const h = @as(c_int, @intCast(image.height));
|
|
const comp = @as(c_int, @intCast(image.num_components));
|
|
const result = switch (image_format) {
|
|
.png => stbi_write_png_to_func(write_fn, context, w, h, comp, image.data.ptr, 0),
|
|
.jpg => |settings| stbi_write_jpg_to_func(
|
|
write_fn,
|
|
context,
|
|
w,
|
|
h,
|
|
comp,
|
|
image.data.ptr,
|
|
@as(c_int, @intCast(settings.quality)),
|
|
),
|
|
};
|
|
// if the result is 0 then it means an error occured (per stb image write docs)
|
|
if (result == 0) {
|
|
return ImageWriteError.CouldNotWriteImage;
|
|
}
|
|
}
|
|
|
|
pub fn deinit(image: *Image) void {
|
|
stbi_image_free(image.data.ptr);
|
|
image.* = undefined;
|
|
}
|
|
};
|
|
|
|
/// `pub fn setHdrToLdrScale(scale: f32) void`
|
|
pub const setHdrToLdrScale = stbi_hdr_to_ldr_scale;
|
|
|
|
/// `pub fn setHdrToLdrGamma(gamma: f32) void`
|
|
pub const setHdrToLdrGamma = stbi_hdr_to_ldr_gamma;
|
|
|
|
/// `pub fn setLdrToHdrScale(scale: f32) void`
|
|
pub const setLdrToHdrScale = stbi_ldr_to_hdr_scale;
|
|
|
|
/// `pub fn setLdrToHdrGamma(gamma: f32) void`
|
|
pub const setLdrToHdrGamma = stbi_ldr_to_hdr_gamma;
|
|
|
|
pub fn isHdr(filename: [:0]const u8) bool {
|
|
return stbi_is_hdr(filename) != 0;
|
|
}
|
|
|
|
pub fn isHdrFromMem(buffer: []const u8) bool {
|
|
return stbi_is_hdr_from_memory(buffer.ptr, @as(c_int, @intCast(buffer.len))) != 0;
|
|
}
|
|
|
|
pub fn is16bit(filename: [:0]const u8) bool {
|
|
return stbi_is_16_bit(filename) != 0;
|
|
}
|
|
|
|
pub fn setFlipVerticallyOnLoad(should_flip: bool) void {
|
|
stbi_set_flip_vertically_on_load(if (should_flip) 1 else 0);
|
|
}
|
|
|
|
pub fn setFlipVerticallyOnWrite(should_flip: bool) void {
|
|
stbi_flip_vertically_on_write(if (should_flip) 1 else 0);
|
|
}
|
|
|
|
var mem_allocator: ?std.mem.Allocator = null;
|
|
var mem_allocations: ?std.AutoHashMap(usize, usize) = null;
|
|
var mem_mutex: std.Thread.Mutex = .{};
|
|
const mem_alignment = 16;
|
|
|
|
extern var zstbiMallocPtr: ?*const fn (size: usize) callconv(.C) ?*anyopaque;
|
|
extern var zstbiwMallocPtr: ?*const fn (size: usize) callconv(.C) ?*anyopaque;
|
|
|
|
fn zstbiMalloc(size: usize) callconv(.C) ?*anyopaque {
|
|
mem_mutex.lock();
|
|
defer mem_mutex.unlock();
|
|
|
|
const mem = mem_allocator.?.alignedAlloc(
|
|
u8,
|
|
mem_alignment,
|
|
size,
|
|
) catch @panic("zstbi: out of memory");
|
|
|
|
mem_allocations.?.put(@intFromPtr(mem.ptr), size) catch @panic("zstbi: out of memory");
|
|
|
|
return mem.ptr;
|
|
}
|
|
|
|
extern var zstbiReallocPtr: ?*const fn (ptr: ?*anyopaque, size: usize) callconv(.C) ?*anyopaque;
|
|
extern var zstbiwReallocPtr: ?*const fn (ptr: ?*anyopaque, size: usize) callconv(.C) ?*anyopaque;
|
|
|
|
fn zstbiRealloc(ptr: ?*anyopaque, size: usize) callconv(.C) ?*anyopaque {
|
|
mem_mutex.lock();
|
|
defer mem_mutex.unlock();
|
|
|
|
const old_size = if (ptr != null) mem_allocations.?.get(@intFromPtr(ptr.?)).? else 0;
|
|
const old_mem = if (old_size > 0)
|
|
@as([*]align(mem_alignment) u8, @ptrCast(@alignCast(ptr)))[0..old_size]
|
|
else
|
|
@as([*]align(mem_alignment) u8, undefined)[0..0];
|
|
|
|
const new_mem = mem_allocator.?.realloc(old_mem, size) catch @panic("zstbi: out of memory");
|
|
|
|
if (ptr != null) {
|
|
const removed = mem_allocations.?.remove(@intFromPtr(ptr.?));
|
|
std.debug.assert(removed);
|
|
}
|
|
|
|
mem_allocations.?.put(@intFromPtr(new_mem.ptr), size) catch @panic("zstbi: out of memory");
|
|
|
|
return new_mem.ptr;
|
|
}
|
|
|
|
extern var zstbiFreePtr: ?*const fn (maybe_ptr: ?*anyopaque) callconv(.C) void;
|
|
extern var zstbiwFreePtr: ?*const fn (maybe_ptr: ?*anyopaque) callconv(.C) void;
|
|
|
|
fn zstbiFree(maybe_ptr: ?*anyopaque) callconv(.C) void {
|
|
if (maybe_ptr) |ptr| {
|
|
mem_mutex.lock();
|
|
defer mem_mutex.unlock();
|
|
|
|
const size = mem_allocations.?.fetchRemove(@intFromPtr(ptr)).?.value;
|
|
const mem = @as([*]align(mem_alignment) u8, @ptrCast(@alignCast(ptr)))[0..size];
|
|
mem_allocator.?.free(mem);
|
|
}
|
|
}
|
|
|
|
extern var zstbirMallocPtr: ?*const fn (size: usize, maybe_context: ?*anyopaque) callconv(.C) ?*anyopaque;
|
|
|
|
fn zstbirMalloc(size: usize, _: ?*anyopaque) callconv(.C) ?*anyopaque {
|
|
return zstbiMalloc(size);
|
|
}
|
|
|
|
extern var zstbirFreePtr: ?*const fn (maybe_ptr: ?*anyopaque, maybe_context: ?*anyopaque) callconv(.C) void;
|
|
|
|
fn zstbirFree(maybe_ptr: ?*anyopaque, _: ?*anyopaque) callconv(.C) void {
|
|
zstbiFree(maybe_ptr);
|
|
}
|
|
|
|
extern fn stbi_info(filename: [*:0]const u8, x: *c_int, y: *c_int, comp: *c_int) c_int;
|
|
|
|
extern fn stbi_load(
|
|
filename: [*:0]const u8,
|
|
x: *c_int,
|
|
y: *c_int,
|
|
channels_in_file: *c_int,
|
|
desired_channels: c_int,
|
|
) ?[*]u8;
|
|
|
|
extern fn stbi_load_16(
|
|
filename: [*:0]const u8,
|
|
x: *c_int,
|
|
y: *c_int,
|
|
channels_in_file: *c_int,
|
|
desired_channels: c_int,
|
|
) ?[*]u16;
|
|
|
|
extern fn stbi_loadf(
|
|
filename: [*:0]const u8,
|
|
x: *c_int,
|
|
y: *c_int,
|
|
channels_in_file: *c_int,
|
|
desired_channels: c_int,
|
|
) ?[*]f32;
|
|
|
|
pub extern fn stbi_load_from_memory(
|
|
buffer: [*]const u8,
|
|
len: c_int,
|
|
x: *c_int,
|
|
y: *c_int,
|
|
channels_in_file: *c_int,
|
|
desired_channels: c_int,
|
|
) ?[*]u8;
|
|
|
|
pub extern fn stbi_loadf_from_memory(
|
|
buffer: [*]const u8,
|
|
len: c_int,
|
|
x: *c_int,
|
|
y: *c_int,
|
|
channels_in_file: *c_int,
|
|
desired_channels: c_int,
|
|
) ?[*]f32;
|
|
|
|
extern fn stbi_image_free(image_data: ?[*]u8) void;
|
|
|
|
extern fn stbi_hdr_to_ldr_scale(scale: f32) void;
|
|
extern fn stbi_hdr_to_ldr_gamma(gamma: f32) void;
|
|
extern fn stbi_ldr_to_hdr_scale(scale: f32) void;
|
|
extern fn stbi_ldr_to_hdr_gamma(gamma: f32) void;
|
|
|
|
extern fn stbi_is_16_bit(filename: [*:0]const u8) c_int;
|
|
extern fn stbi_is_hdr(filename: [*:0]const u8) c_int;
|
|
extern fn stbi_is_hdr_from_memory(buffer: [*]const u8, len: c_int) c_int;
|
|
|
|
extern fn stbi_set_flip_vertically_on_load(flag_true_if_should_flip: c_int) void;
|
|
extern fn stbi_flip_vertically_on_write(flag: c_int) void; // flag is non-zero to flip data vertically
|
|
|
|
extern fn stbir_resize_uint8(
|
|
input_pixels: [*]const u8,
|
|
input_w: c_int,
|
|
input_h: c_int,
|
|
input_stride_in_bytes: c_int,
|
|
output_pixels: [*]u8,
|
|
output_w: c_int,
|
|
output_h: c_int,
|
|
output_stride_in_bytes: c_int,
|
|
num_channels: c_int,
|
|
) void;
|
|
|
|
extern fn stbi_write_jpg(
|
|
filename: [*:0]const u8,
|
|
w: c_int,
|
|
h: c_int,
|
|
comp: c_int,
|
|
data: [*]const u8,
|
|
quality: c_int,
|
|
) c_int;
|
|
|
|
extern fn stbi_write_png(
|
|
filename: [*:0]const u8,
|
|
w: c_int,
|
|
h: c_int,
|
|
comp: c_int,
|
|
data: [*]const u8,
|
|
stride_in_bytes: c_int,
|
|
) c_int;
|
|
|
|
extern fn stbi_write_png_to_func(
|
|
func: *const fn (?*anyopaque, ?*anyopaque, c_int) callconv(.C) void,
|
|
context: ?*anyopaque,
|
|
w: c_int,
|
|
h: c_int,
|
|
comp: c_int,
|
|
data: [*]const u8,
|
|
stride_in_bytes: c_int,
|
|
) c_int;
|
|
|
|
extern fn stbi_write_jpg_to_func(
|
|
func: *const fn (?*anyopaque, ?*anyopaque, c_int) callconv(.C) void,
|
|
context: ?*anyopaque,
|
|
x: c_int,
|
|
y: c_int,
|
|
comp: c_int,
|
|
data: [*]const u8,
|
|
quality: c_int,
|
|
) c_int;
|
|
|
|
test "zstbi basic" {
|
|
init(testing.allocator);
|
|
defer deinit();
|
|
|
|
var im1 = try Image.createEmpty(8, 6, 4, .{});
|
|
defer im1.deinit();
|
|
|
|
try testing.expect(im1.width == 8);
|
|
try testing.expect(im1.height == 6);
|
|
try testing.expect(im1.num_components == 4);
|
|
}
|
|
|
|
test "zstbi resize" {
|
|
init(testing.allocator);
|
|
defer deinit();
|
|
|
|
var im1 = try Image.createEmpty(32, 32, 4, .{});
|
|
defer im1.deinit();
|
|
|
|
var im2 = im1.resize(8, 6);
|
|
defer im2.deinit();
|
|
|
|
try testing.expect(im2.width == 8);
|
|
try testing.expect(im2.height == 6);
|
|
try testing.expect(im2.num_components == 4);
|
|
}
|
|
|
|
test "zstbi write and load file" {
|
|
init(testing.allocator);
|
|
defer deinit();
|
|
|
|
const pth = try std.fs.selfExeDirPathAlloc(testing.allocator);
|
|
defer testing.allocator.free(pth);
|
|
try std.posix.chdir(pth);
|
|
|
|
var img = try Image.createEmpty(8, 6, 4, .{});
|
|
defer img.deinit();
|
|
|
|
try img.writeToFile("test_img.png", ImageWriteFormat.png);
|
|
try img.writeToFile("test_img.jpg", .{ .jpg = .{ .quality = 80 } });
|
|
|
|
var img_png = try Image.loadFromFile("test_img.png", 0);
|
|
defer img_png.deinit();
|
|
|
|
try testing.expect(img_png.width == img.width);
|
|
try testing.expect(img_png.height == img.height);
|
|
try testing.expect(img_png.num_components == img.num_components);
|
|
|
|
var img_jpg = try Image.loadFromFile("test_img.jpg", 0);
|
|
defer img_jpg.deinit();
|
|
|
|
try testing.expect(img_jpg.width == img.width);
|
|
try testing.expect(img_jpg.height == img.height);
|
|
try testing.expect(img_jpg.num_components == 3); // RGB JPEG
|
|
|
|
try std.fs.cwd().deleteFile("test_img.png");
|
|
try std.fs.cwd().deleteFile("test_img.jpg");
|
|
}
|