media: add stb_image
This commit is contained in:
@@ -6,6 +6,18 @@ pub fn build(b: *std.Build) void {
|
|||||||
|
|
||||||
const root_module = b.addModule("media", .{
|
const root_module = b.addModule("media", .{
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.link_libc = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
root_module.addCSourceFile(.{
|
||||||
|
.file = b.path("src/stbi/stb_image.c"),
|
||||||
|
.flags = &.{
|
||||||
|
"-std=c17",
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra",
|
||||||
|
},
|
||||||
|
.language = .c,
|
||||||
|
});
|
||||||
|
|
||||||
root_module.addImport("vecmath", vm_module);
|
root_module.addImport("vecmath", vm_module);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ pub const audio = @import("audio.zig");
|
|||||||
pub const image = @import("image.zig");
|
pub const image = @import("image.zig");
|
||||||
pub const qoa = @import("qoa.zig");
|
pub const qoa = @import("qoa.zig");
|
||||||
pub const qoi = @import("qoi.zig");
|
pub const qoi = @import("qoi.zig");
|
||||||
|
pub const stbi = @import("stbi.zig");
|
||||||
|
|||||||
222
packages/media/src/stbi.zig
Normal file
222
packages/media/src/stbi.zig
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const image = @import("image.zig");
|
||||||
|
const vm = @import("vecmath");
|
||||||
|
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
allocations: std.AutoHashMapUnmanaged(*anyopaque, usize) = .empty,
|
||||||
|
mutex: std.Thread.Mutex = .{},
|
||||||
|
allocated_bytes: usize = 0,
|
||||||
|
|
||||||
|
const alignment: std.mem.Alignment = .@"16";
|
||||||
|
const log = std.log.scoped(.stbi);
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) void {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
std.log.scoped(.deinit).debug("Deinitializing {*}", .{self});
|
||||||
|
if (self.allocated_bytes > 0) {
|
||||||
|
log.warn("{d} byte(s) still allocated while deinitializing", .{self.allocated_bytes});
|
||||||
|
}
|
||||||
|
if (self.allocations.size > 0) {
|
||||||
|
log.warn("{d} allocation(s) still tracked while deinitializing", .{self.allocations.size});
|
||||||
|
var it = self.allocations.iterator();
|
||||||
|
var index: usize = 0;
|
||||||
|
while (it.next()) |entry| : (index += 1) {
|
||||||
|
log.warn("Leaked allocation ({d}/{d}) at 0x{x} of {d} byte(s)", .{
|
||||||
|
index + 1,
|
||||||
|
self.allocations.size,
|
||||||
|
@intFromPtr(entry.key_ptr.*),
|
||||||
|
entry.value_ptr.*,
|
||||||
|
});
|
||||||
|
const memory = @as([*]align(alignment.toByteUnits()) u8, @ptrCast(@alignCast(entry.key_ptr.*)))[0..entry.value_ptr.*];
|
||||||
|
self.allocator.free(memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.allocations.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const import = struct {
|
||||||
|
extern fn stbi_load_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]u8;
|
||||||
|
extern fn stbi_load_16_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]u16;
|
||||||
|
extern fn stbi_loadf_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]f32;
|
||||||
|
|
||||||
|
extern fn stbi_load_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]u8;
|
||||||
|
extern fn stbi_load_16_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]u16;
|
||||||
|
extern fn stbi_loadf_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]f32;
|
||||||
|
|
||||||
|
extern fn stbi_info_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32) i32;
|
||||||
|
extern fn stbi_is_16_bit_from_memory(buffer: [*]const u8, len: i32) i32;
|
||||||
|
extern fn stbi_is_hdr_from_memory(buffer: [*]const u8, len: i32) i32;
|
||||||
|
|
||||||
|
extern fn stbi_info_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32) i32;
|
||||||
|
extern fn stbi_is_16_bit_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque) i32;
|
||||||
|
extern fn stbi_is_hdr_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque) i32;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn loadStaticBuf(self: *Self, comptime W: u32, comptime H: u32, buf: []const u8) !image.Static(W, H) {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
var x: i32 = undefined;
|
||||||
|
var y: i32 = undefined;
|
||||||
|
var comp: i32 = undefined;
|
||||||
|
|
||||||
|
const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, &comp, 4) orelse return error.StbiError;
|
||||||
|
defer castle_media_stbi_free(res);
|
||||||
|
|
||||||
|
if (x != W or y != H) return error.WrongDimensions;
|
||||||
|
if (comp != 4) return error.WrongComponentCount;
|
||||||
|
|
||||||
|
return .{ .data = @as(*const [W * H]vm.Color, @ptrCast(@alignCast(res))).* };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On success, must free memory by calling `freeDynamic` method.
|
||||||
|
pub fn loadDynamicBuf(self: *Self, buf: []const u8) !image.Dynamic {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
var x: i32 = undefined;
|
||||||
|
var y: i32 = undefined;
|
||||||
|
var comp: i32 = undefined;
|
||||||
|
|
||||||
|
const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, &comp, 4) orelse return error.StbiError;
|
||||||
|
|
||||||
|
if (comp != 4) return error.WrongComponentCount;
|
||||||
|
|
||||||
|
return .initBuffer(@intCast(x), @intCast(y), res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On success, must free memory by calling `freeDynamic` method.
|
||||||
|
pub fn loadDynamicIo(self: *Self, reader: *std.io.Reader) !image.Dynamic {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
var x: i32 = undefined;
|
||||||
|
var y: i32 = undefined;
|
||||||
|
var comp: i32 = undefined;
|
||||||
|
|
||||||
|
const res = import.stbi_load_from_callbacks(.std_io_reader_interface, reader, &x, &y, &comp, 4) orelse return error.StbiError;
|
||||||
|
|
||||||
|
if (comp != 4) return error.WrongComponentCount;
|
||||||
|
|
||||||
|
return .initBuffer(@intCast(x), @intCast(y), res);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn freeDynamic(self: *Self, img: image.Dynamic) void {
|
||||||
|
current_self = self;
|
||||||
|
defer current_self = undefined;
|
||||||
|
|
||||||
|
castle_media_stbi_free(img.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- IO INTERFACE ------------------------------------------------------------
|
||||||
|
|
||||||
|
pub const IoCallbacks = extern struct {
|
||||||
|
/// Fill `data` with `size` bytes. Return number of bytes actually read.
|
||||||
|
read: ?*const fn (ctx: ?*anyopaque, data: ?[*]u8, size: i32) callconv(.c) i32,
|
||||||
|
/// Skip the next `n` bytes, or backtrack `-n` bytes if `n < 0`.
|
||||||
|
skip: ?*const fn (ctx: ?*anyopaque, n: i32) callconv(.c) i32,
|
||||||
|
/// Return non-zero value if at the end of file/data.
|
||||||
|
eof: ?*const fn (cxt: ?*anyopaque) callconv(.c) i32,
|
||||||
|
|
||||||
|
pub const std_io_reader_interface: IoCallbacks = .{
|
||||||
|
.read = stdIoReader_ReadFn,
|
||||||
|
.skip = stdIoReader_SkipFn,
|
||||||
|
.eof = stdIoReader_EofFn,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn stdIoReader_ReadFn(ctx: ?*anyopaque, data: ?[*]u8, size: i32) callconv(.c) i32 {
|
||||||
|
const reader: *std.Io.Reader = @ptrCast(ctx.?);
|
||||||
|
const bytes_read = reader.readSliceShort(data[0..size]) catch return 0;
|
||||||
|
return bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stdIoReader_SkipFn(ctx: ?*anyopaque, n: i32) callconv(.c) i32 {
|
||||||
|
const reader: *std.Io.Reader = @ptrCast(ctx.?);
|
||||||
|
// NOTE stb_image.h actually discards the return value from this
|
||||||
|
// callback. If an actual error occurs, we're cooked (but it will be
|
||||||
|
// very likely caught as a parsing error later).
|
||||||
|
_ = reader.discardAll(n) catch return 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stdIoReader_EofFn(ctx: ?*anyopaque) callconv(.c) i32 {
|
||||||
|
const reader: *std.Io.Reader = @ptrCast(ctx.?);
|
||||||
|
|
||||||
|
_ = reader.peekByte() catch return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- MALLOC INTERFACE --------------------------------------------------------
|
||||||
|
|
||||||
|
threadlocal var current_self: *Self = undefined;
|
||||||
|
|
||||||
|
export fn castle_media_stbi_malloc(size: usize) callconv(.c) ?*anyopaque {
|
||||||
|
const self = current_self;
|
||||||
|
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
|
||||||
|
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
|
||||||
|
const memory = self.allocator.alignedAlloc(u8, alignment, size) catch return null;
|
||||||
|
|
||||||
|
self.allocations.putAssumeCapacityNoClobber(memory.ptr, size);
|
||||||
|
self.allocated_bytes += size;
|
||||||
|
//log.debug("Allocated {d} bytes(s) at 0x{x}", .{ size, @intFromPtr(memory.ptr) });
|
||||||
|
|
||||||
|
return memory.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn castle_media_stbi_realloc(maybe_ptr: ?*anyopaque, size: usize) callconv(.c) ?*anyopaque {
|
||||||
|
const self = current_self;
|
||||||
|
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
|
||||||
|
// NOTE If we were pedantic, we would consider the fact that we might not
|
||||||
|
// need unused capacity if the memory doesn't get relocated.
|
||||||
|
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
|
||||||
|
|
||||||
|
const old_memory = if (maybe_ptr) |ptr| blk_then: {
|
||||||
|
const old_size = self.allocations.get(ptr).?;
|
||||||
|
break :blk_then @as([*]align(alignment.toByteUnits()) u8, @ptrCast(@alignCast(ptr)))[0..old_size];
|
||||||
|
} else blk_else: {
|
||||||
|
break :blk_else @as([]align(alignment.toByteUnits()) u8, &.{});
|
||||||
|
};
|
||||||
|
|
||||||
|
const memory = self.allocator.realloc(old_memory, size) catch return null;
|
||||||
|
|
||||||
|
if (maybe_ptr) |ptr| {
|
||||||
|
const old_size = self.allocations.fetchRemove(ptr).?.value;
|
||||||
|
self.allocated_bytes -= old_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.allocations.putAssumeCapacityNoClobber(memory.ptr, size);
|
||||||
|
self.allocated_bytes += size;
|
||||||
|
//log.debug("Reallocated into {d} bytes(s) at 0x{x} from 0x{x}", .{ size, @intFromPtr(memory.ptr), @intFromPtr(maybe_ptr) });
|
||||||
|
|
||||||
|
return memory.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn castle_media_stbi_free(maybe_ptr: ?*anyopaque) callconv(.c) void {
|
||||||
|
const self = current_self;
|
||||||
|
|
||||||
|
if (maybe_ptr) |ptr| {
|
||||||
|
self.mutex.lock();
|
||||||
|
defer self.mutex.unlock();
|
||||||
|
|
||||||
|
const size = self.allocations.fetchRemove(ptr).?.value;
|
||||||
|
self.allocated_bytes -= size;
|
||||||
|
const memory = @as([*]align(alignment) u8, @ptrCast(@alignCast(ptr)))[0..size];
|
||||||
|
|
||||||
|
self.allocator.free(memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
packages/media/src/stbi/stb_image.c
Normal file
17
packages/media/src/stbi/stb_image.c
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
void castle_media_stbi_malloc(size_t size);
|
||||||
|
void castle_media_stbi_realloc(void *ptr, size_t size);
|
||||||
|
void castle_media_stbi_free(void *ptr);
|
||||||
|
|
||||||
|
#define STBI_MALLOC(size) castle_media_stbi_malloc(size)
|
||||||
|
#define STBI_REALLOC(ptr, size) castle_media_stbi_realloc(ptr, size)
|
||||||
|
#define STBI_FREE(ptr) castle_media_stbi_free(ptr)
|
||||||
|
|
||||||
|
#define STBI_NO_STDIO
|
||||||
|
#define STBI_ONLY_JPEG
|
||||||
|
#define STBI_ONLY_PNG
|
||||||
|
#define STBI_ONLY_HDR
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
7988
packages/media/src/stbi/stb_image.h
Normal file
7988
packages/media/src/stbi/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user