Initial commit
This commit is contained in:
22
packages/media/src/audio.zig
Normal file
22
packages/media/src/audio.zig
Normal file
@@ -0,0 +1,22 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Static = struct {
|
||||
samples: []Sample,
|
||||
sample_rate: u32,
|
||||
|
||||
pub fn lengthInSeconds(self: Static) f32 {
|
||||
const samples: f64 = @floatFromInt(self.samples);
|
||||
const sample_rate: f64 = @floatFromInt(self.sample_rate);
|
||||
return @floatCast(samples / sample_rate);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Sample = extern struct {
|
||||
left: i16,
|
||||
right: i16,
|
||||
};
|
||||
|
||||
pub const Stream = struct {
|
||||
source: std.io.Reader,
|
||||
sample_rate: u32,
|
||||
};
|
||||
86
packages/media/src/color.zig
Normal file
86
packages/media/src/color.zig
Normal file
@@ -0,0 +1,86 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Color = extern struct {
|
||||
vector: Vector,
|
||||
|
||||
pub const Vector = @Vector(4, u8);
|
||||
|
||||
pub const clear = Color.init(0, 0, 0, 0);
|
||||
pub const black = Color.init(0, 0, 0, 255);
|
||||
pub const white = Color.init(255, 255, 255, 255);
|
||||
|
||||
pub fn init(r: u8, g: u8, b: u8, a: u8) Color {
|
||||
return .{ .vector = .{ r, g, b, a } };
|
||||
}
|
||||
|
||||
pub fn fromFloat(color: ColorFloat) Color {
|
||||
const clamped = std.math.clamp(color, @splat(0.0), @splat(1.0));
|
||||
const vector = @round(clamped * @as(ColorFloat.Vector, @splat(255.0)));
|
||||
return .{
|
||||
.vector = .{
|
||||
@intFromFloat(vector[0]),
|
||||
@intFromFloat(vector[1]),
|
||||
@intFromFloat(vector[2]),
|
||||
@intFromFloat(vector[3]),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const ColorFloat = extern struct {
|
||||
vector: Vector,
|
||||
|
||||
pub const Vector = @Vector(4, f32);
|
||||
|
||||
pub const clear = ColorFloat.init(0, 0, 0, 0);
|
||||
pub const black = ColorFloat.init(0, 0, 0, 255);
|
||||
pub const white = ColorFloat.init(255, 255, 255, 255);
|
||||
|
||||
pub inline fn init(r: f32, g: f32, b: f32, a: f32) ColorFloat {
|
||||
return .{ .vector = .{ r, g, b, a } };
|
||||
}
|
||||
|
||||
pub inline fn fromInteger(color: Color) ColorFloat {
|
||||
return .{
|
||||
.vector = .{
|
||||
@as(f32, @floatFromInt(color.r)) / 255.0,
|
||||
@as(f32, @floatFromInt(color.g)) / 255.0,
|
||||
@as(f32, @floatFromInt(color.b)) / 255.0,
|
||||
@as(f32, @floatFromInt(color.a)) / 255.0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn add(self: ColorFloat, other: ColorFloat) ColorFloat {
|
||||
return .{ .vector = self.vector + other.vector };
|
||||
}
|
||||
|
||||
pub inline fn sub(self: ColorFloat, other: ColorFloat) ColorFloat {
|
||||
return .{ .vector = self.vector - other.vector };
|
||||
}
|
||||
|
||||
pub inline fn mul(self: ColorFloat, other: ColorFloat) ColorFloat {
|
||||
return .{ .vector = self.vector * other.vector };
|
||||
}
|
||||
|
||||
pub inline fn div(self: ColorFloat, other: ColorFloat) ColorFloat {
|
||||
return .{ .vector = self.vector / other.vector };
|
||||
}
|
||||
|
||||
pub inline fn mulScalar(self: ColorFloat, scalar: f32) ColorFloat {
|
||||
const vector: Vector = @splat(scalar);
|
||||
return .{ .vector = self.vector * vector };
|
||||
}
|
||||
|
||||
pub inline fn divScalar(self: ColorFloat, scalar: f32) ColorFloat {
|
||||
const vector: Vector = @splat(scalar);
|
||||
return .{ .vector = self.vector / vector };
|
||||
}
|
||||
|
||||
pub inline fn lerp(self: ColorFloat, other: ColorFloat, t: f32) ColorFloat {
|
||||
const s = 1.0 - t;
|
||||
const t_vector: Vector = @splat(t);
|
||||
const s_vector: Vector = @splat(s);
|
||||
return .{ .vector = self * t_vector + other * s_vector };
|
||||
}
|
||||
};
|
||||
69
packages/media/src/image.zig
Normal file
69
packages/media/src/image.zig
Normal file
@@ -0,0 +1,69 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Color = @import("color.zig").Color;
|
||||
|
||||
pub fn Static(comptime W: u32, comptime H: u32) type {
|
||||
return struct {
|
||||
data: [W * H]Color,
|
||||
|
||||
pub const width = W;
|
||||
pub const height = H;
|
||||
|
||||
pub fn getPixel(self: *const @This(), x: u32, y: u32) Color {
|
||||
std.debug.assert(x < width and y < height);
|
||||
return self.data[y * width + x];
|
||||
}
|
||||
|
||||
pub fn setPixel(self: *@This(), x: u32, y: u32, color: Color) void {
|
||||
std.debug.assert(x < width and y < height);
|
||||
self.data[y * width + x] = color;
|
||||
}
|
||||
|
||||
pub fn fill(self: *@This(), color: Color) void {
|
||||
@memset(self.data, color);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const Dynamic = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
data: [*]Color,
|
||||
|
||||
pub fn initBuffer(width: u32, height: u32, buffer: []Color) @This() {
|
||||
std.debug.assert(buffer.len == width * height);
|
||||
return .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.data = buffer.ptr,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initAlloc(width: u32, height: u32, allocator: std.mem.Allocator) !@This() {
|
||||
const buffer = try allocator.alloc(Color, width * height);
|
||||
return .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.data = buffer.ptr,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||
allocator.free(self.data[0 .. self.width * self.height]);
|
||||
}
|
||||
|
||||
pub fn getPixel(self: *const @This(), x: u32, y: u32) Color {
|
||||
std.debug.assert(x < self.width and y < self.height);
|
||||
return self.data[y * self.width + x];
|
||||
}
|
||||
|
||||
pub fn setPixel(self: *@This(), x: u32, y: u32, color: Color) void {
|
||||
std.debug.assert(x < self.width and y < self.height);
|
||||
self.data[y * self.width + x] = color;
|
||||
}
|
||||
|
||||
pub fn fill(self: *@This(), color: Color) void {
|
||||
@memset(self.data[0 .. self.width * self.height], color);
|
||||
}
|
||||
};
|
||||
50
packages/media/src/qoa.zig
Normal file
50
packages/media/src/qoa.zig
Normal file
@@ -0,0 +1,50 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Header = union(enum) {
|
||||
streaming: HeaderStreaming,
|
||||
static: HeaderStatic,
|
||||
};
|
||||
|
||||
const HeaderStreaming = void;
|
||||
|
||||
const HeaderStatic = struct {
|
||||
samples: u32,
|
||||
channels: u8,
|
||||
sample_rate: u24,
|
||||
|
||||
pub fn lengthInSeconds(self: HeaderStatic) f32 {
|
||||
const samples: f64 = @floatFromInt(self.samples);
|
||||
const sample_rate: f64 = @floatFromInt(self.sample_rate);
|
||||
return @floatCast(samples / sample_rate);
|
||||
}
|
||||
};
|
||||
|
||||
/// The caller asserts that the buffer is at least 12 bytes long, which can
|
||||
/// contain the entirety of a QOA file header and the relevant information in
|
||||
/// the first frame header.
|
||||
pub fn info(buffer: []const u8) ?Header {
|
||||
std.debug.assert(buffer.len >= 12);
|
||||
|
||||
const magic = buffer[0..4];
|
||||
const samples = std.mem.readInt(u32, buffer[4..8], .big);
|
||||
const channels = buffer[8];
|
||||
const sample_rate = std.mem.readInt(u24, buffer[9..12], .big);
|
||||
|
||||
if (!std.mem.eql(u8, magic, "qoaf") or channels == 0 or channels > 8 or sample_rate == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (samples == 0) {
|
||||
return .{
|
||||
.streaming = {},
|
||||
};
|
||||
} else {
|
||||
return .{
|
||||
.static = .{
|
||||
.samples = samples,
|
||||
.channels = channels,
|
||||
.sample_rate = sample_rate,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
41
packages/media/src/qoi.zig
Normal file
41
packages/media/src/qoi.zig
Normal file
@@ -0,0 +1,41 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Channels = enum(u8) {
|
||||
rgb = 3,
|
||||
rgba = 4,
|
||||
};
|
||||
|
||||
const ColorSpace = enum(u8) {
|
||||
srgb_linear_alpha = 0,
|
||||
linear = 1,
|
||||
};
|
||||
|
||||
const Header = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
channels: Channels,
|
||||
color_space: ColorSpace,
|
||||
};
|
||||
|
||||
/// The caller asserts that the buffer is at least 14 bytes long, which can
|
||||
/// contain the entirety of a QOI header.
|
||||
pub fn info(buffer: []const u8) ?Header {
|
||||
std.debug.assert(buffer.len >= 14);
|
||||
|
||||
const magic = buffer[0..4];
|
||||
const width = std.mem.readInt(u32, buffer[4..8], .big);
|
||||
const height = std.mem.readInt(u32, buffer[8..12], .big);
|
||||
const channels = buffer[12];
|
||||
const color_space = buffer[13];
|
||||
|
||||
if (!std.mem.eql(u8, magic, "qoif") or width == 0 or height == 0 or channels < 3 or channels > 4 or color_space > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.channels = @enumFromInt(channels),
|
||||
.color_space = @enumFromInt(color_space),
|
||||
};
|
||||
}
|
||||
5
packages/media/src/root.zig
Normal file
5
packages/media/src/root.zig
Normal file
@@ -0,0 +1,5 @@
|
||||
pub const audio = @import("audio.zig");
|
||||
pub const color = @import("color.zig");
|
||||
pub const image = @import("image.zig");
|
||||
pub const qoa = @import("qoa.zig");
|
||||
pub const qoi = @import("qoi.zig");
|
||||
Reference in New Issue
Block a user