clap.zig
: Command line argumentdraw.zig
: Provide a canvas context 'like' interface for drawingio-adapter
:- Keyboard/mouse input + graphic output
- One adapter for SDL for now
misc.zig
:- Load a file
- Write an image (pgm or pam)
Last active
May 1, 2025 09:05
-
-
Save jdmichaud/eb8659052f3f74dbfb2b62ad0bfb7897 to your computer and use it in GitHub Desktop.
Zig utils
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
// A very simple command line argument parser. | |
// | |
// The purpose is to be simple, modifiable and easily embeddable. Just | |
// copy-paste is into your project and that's it. | |
// | |
// Usage: | |
// ```zig | |
// const args = try std.process.argsAlloc(allocator); | |
// defer std.process.argsFree(allocator, args); | |
// | |
// const parsedArgs = clap.parser(clap.ArgDescriptor{ | |
// .name = "qvm", | |
// .description = "A QuakeC virtual machine", | |
// .withHelp = true, | |
// .version = "0.1.0", | |
// .expectArgs = &[_][]const u8{ "datfile" }, | |
// .options = &[_]clap.OptionDescription{ .{ | |
// .short = "t", | |
// .long = "trace", | |
// .help = "Enable tracing of instructions", | |
// }, .{ | |
// .short = "e", | |
// .long = "verbose", | |
// .help = "Display additional information about the VM", | |
// }, .{ | |
// .short = "m", | |
// .long = "memory-size", | |
// .arg = .{ .name = "memory", .type = []const u8 }, | |
// .help = "Amount of memory to allocate for the VM (-m 12, -m 64K, -m 1M)", | |
// }, .{ | |
// .short = "j", | |
// .long = "jump-to", | |
// .arg = .{ .name = "function", .type = []const u8 }, | |
// .help = "Jump to function on startup", | |
// }, .{ | |
// .short = "b", | |
// .long = "bsp-file", | |
// .arg = .{ .name = "bspfile", .type = []const u8 }, | |
// .help = "Load a BSP file", | |
// }, .{ | |
// .short = "r", | |
// .long = "run", | |
// .help = "Run the event loop (triggering the nextthink timers)", | |
// } }, | |
// }).parse(args); | |
// | |
// const filepath = parsedArgs.arguments.items[0]; | |
// const memsize = if (args.getOption([]const u8, "memory-size")) |memsizeArg| blkinner: { | |
// const lastChar = memsizeArg[memsizeArg.len - 1]; | |
// break :blkinner switch (lastChar) { | |
// 'k', 'K' => try std.fmt.parseInt(usize, memsizeArg[0..memsizeArg.len - 1], 10) * 1024, | |
// 'm', 'M' => try std.fmt.parseInt(usize, memsizeArg[0..memsizeArg.len - 1], 10) * 1024 * 1024, | |
// else => try std.fmt.parseInt(usize, memsizeArg, 10), | |
// }; | |
// } else 1024 * 1024 * 1; // 1Mb by default; | |
// // Create the VM | |
// var vm = try VM.init(allocator, .{ | |
// .entries = null, | |
// }, .{ | |
// .trace = parsedArgs.getSwitch("trace"), | |
// .memsize = memsize, | |
// .verbose = parsedArgs.getSwitch("verbose"), | |
// }); | |
// defer vm.deinit(); | |
// ``` | |
// Running with the `--help` option will show: | |
// ``` | |
// qvm (0.1.0) A QuakeC virtual machine | |
// Usage: qvm [OPTIONS] datfile | |
// | |
// Options: | |
// -t,--trace Enable tracing of instructions | |
// -e,--verbose Display additional information about the VM | |
// -m,--memory-size memory | |
// Amount of memory to allocate for the VM (-m 12, -m 64K, -m 1M) | |
// -j,--jump-to function | |
// Jump to function on startup | |
// -b,--bsp-file bspfile | |
// Load a BSP file | |
// -r,--run Run the event loop (triggering the nextthink timers) | |
// | |
// ``` | |
const std = @import("std"); | |
const stdout = std.io.getStdOut().writer(); | |
const stderr = std.io.getStdErr().writer(); | |
pub const OptionDescription = struct { | |
short: ?[]const u8, | |
long: []const u8, | |
arg: ?struct { name: []const u8, type: type } = null, | |
help: []const u8, | |
}; | |
pub const ArgDescriptor = struct { | |
bufferSize: usize = 1024, | |
name: []const u8, | |
description: ?[]const u8 = null, | |
withHelp: bool = true, | |
version: ?[]const u8 = null, | |
expectArgs: []const []const u8 = &[_][]const u8{}, | |
options: []const OptionDescription, | |
}; | |
pub fn findOption(comptime T: type, value: anytype, argsInfo: std.builtin.Type.Struct, | |
name: []const u8) ?type { | |
inline for (argsInfo.fields) |field| { | |
if (std.mem.eql(u8, field.name, name) and field.type == T) { | |
return @field(value, field.name); | |
} | |
} | |
return null; | |
} | |
pub fn printUsage(allocator: std.mem.Allocator, argsDescriptor: ArgDescriptor) void { | |
stdout.print("Usage: {s}{s}{s}\n", .{ | |
argsDescriptor.name, | |
if (argsDescriptor.options.len > 0) " [OPTIONS]" else "", | |
if (argsDescriptor.expectArgs.len > 0) blk: { | |
const argsStr = std.mem.join(allocator, " ", argsDescriptor.expectArgs) | |
catch @panic("increase fixed buffer size"); | |
break :blk std.fmt.allocPrint(allocator, " {s}", .{ argsStr }) | |
catch @panic("increase fixed buffer size"); | |
} else "", | |
}) catch unreachable; | |
} | |
pub fn printHelp(allocator: std.mem.Allocator, argsDescriptor: ArgDescriptor) void { | |
stdout.print("{s}{s} {s}\n", .{ | |
argsDescriptor.name, | |
if (argsDescriptor.version) |version| " (" ++ version ++ ")" else "", | |
argsDescriptor.description orelse "", | |
}) catch unreachable; | |
stdout.print("\n", .{}) catch unreachable; | |
printUsage(allocator, argsDescriptor); | |
stdout.print("\nOptions:\n", .{}) catch unreachable; | |
inline for (argsDescriptor.options) |option| { | |
var buffer: [argsDescriptor.bufferSize]u8 = undefined; | |
const printed = std.fmt.bufPrint(&buffer, " {s}{s}{s}", .{ | |
if (option.short) |short| "-" ++ short ++ "," else " ", | |
"--" ++ option.long, | |
if (option.arg) |arg| " " ++ arg.name else "", | |
}) catch @panic("increase fixed buffer size"); | |
if (printed.len > 23) { | |
stdout.print("{s}\n {s}\n", .{ printed, option.help }) | |
catch unreachable; | |
} else { | |
stdout.print("{s: <24} {s}\n", .{ printed, option.help }) catch unreachable; | |
} | |
} | |
} | |
pub const Args = struct { | |
const Self = @This(); | |
switchMap: std.StringHashMap(bool), | |
optionMap: std.StringHashMap([]const u8), | |
arguments: std.ArrayList([]const u8), | |
pub fn getSwitch(self: Self, name: []const u8) bool { | |
return self.switchMap.get(name) orelse false; | |
} | |
pub fn getOption(self: Self, comptime T: type, name: []const u8) ?T { | |
return self.optionMap.get(name); | |
} | |
}; | |
pub fn parser(argsDescriptor: ArgDescriptor) type { | |
return struct { | |
var buffer: [argsDescriptor.bufferSize]u8 = undefined; | |
var fba = std.heap.FixedBufferAllocator.init(&buffer); | |
const allocator = fba.allocator(); | |
var argsStore = Args{ | |
.switchMap = std.StringHashMap(bool).init(allocator), | |
.optionMap = std.StringHashMap([]const u8).init(allocator), | |
.arguments = std.ArrayList([]const u8).init(allocator), | |
}; | |
pub fn parse(args: [][:0]u8) Args { | |
if (argsDescriptor.withHelp) { | |
// Look for help and print it | |
for (args) |arg| { | |
if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) { | |
printHelp(allocator, argsDescriptor); | |
std.posix.exit(0); | |
} | |
} | |
var i: u16 = 1; | |
while (i < args.len) { | |
const arg = args[i]; | |
if (arg[0] == '-') { | |
// Handle option in the block. i might be incremented additionally | |
// it the option expects an argument. | |
inline for (argsDescriptor.options) |option| { | |
if ((option.short != null and std.mem.eql(u8, arg[1..], option.short.?)) or | |
std.mem.eql(u8, arg[2..], option.long)) { | |
argsStore.switchMap.put(option.long, true) | |
catch @panic("increase fixed buffer size"); | |
if (option.arg) |optionArg| { | |
_ = optionArg; | |
// We have an argument to the option | |
if (i > args.len - 1 or args[i + 1][0] == '-') { | |
// Missing argument | |
stderr.print("error: option {s} expected an argument\n", .{ arg }) | |
catch unreachable; | |
printUsage(allocator, argsDescriptor); | |
} | |
argsStore.optionMap.put(option.long, args[i + 1]) | |
catch @panic("increase fixed buffer size"); | |
i += 1; | |
} | |
break; | |
} | |
} else { | |
// An option was provided but not described. | |
stderr.print("error: unknown option {s}\n", .{ arg }) catch unreachable; | |
printUsage(allocator, argsDescriptor); | |
std.posix.exit(1); | |
} | |
} else { | |
// Here are the argument to the program. | |
argsStore.arguments.append(args[i]) catch unreachable; | |
} | |
i += 1; | |
} | |
} | |
if (argsStore.arguments.items.len != argsDescriptor.expectArgs.len) { | |
stderr.print("error: incorrect number of arguments. Expected {} arguments, {} given.\n", .{ | |
argsDescriptor.expectArgs.len, argsStore.arguments.items.len, | |
}) catch unreachable; | |
printUsage(allocator, argsDescriptor); | |
std.posix.exit(1); | |
} | |
return argsStore; | |
} | |
}; | |
} |
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 fontfile = @embedFile("dos_8x8_font_white.pbm"); | |
pub fn DrawContext(comptime pwidth: u32, comptime pheight: u32) type { | |
return struct { | |
const Self = @This(); | |
pub const contextWidth = pwidth; | |
pub const contextHeight = pheight; | |
pub var buffer = [_]u32 { 0 } ** (pwidth * pheight); | |
pub var color: u32 = 0xFFFFFFFF; | |
pub var thickness: u32 = 0; | |
var _transform: [6]f32 = .{ 1, 0, 0, 1, 0, 0 }; | |
const _a = 0; const _b = 1; const _c = 2; const _d = 3; const _e = 4; const _f = 5; | |
var stack: [6]f32 = .{ 1, 0, 0, 1, 0, 0 }; | |
// pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { | |
// allocator.free(self.font); | |
// } | |
// resets (overrides) the current transformation to the identity matrix, and | |
// then invokes a transformation described by the arguments of this method. | |
// This lets you scale, rotate, translate (move), and skew the context. | |
// a c e | |
// The transformation matrix is described by: [ b d f ] | |
// 0 0 1 | |
pub fn setTransform(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) void { | |
_transform = .{ a, b, c, d, e, f }; | |
} | |
// multiplies the current transformation with the matrix described by the | |
// arguments of this method. This lets you scale, rotate, translate (move), | |
// and skew the context. | |
pub fn transform(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) void { | |
_transform[_a] = _transform[_a] * a + _transform[_c] * b; | |
_transform[_b] = _transform[_b] * a + _transform[_d] * b; | |
_transform[_c] = _transform[_a] * c + _transform[_c] * d; | |
_transform[_d] = _transform[_b] * c + _transform[_d] * d; | |
_transform[_e] = _transform[_a] * e + _transform[_c] * f + _transform[_e] * 1; | |
_transform[_f] = _transform[_b] * e + _transform[_d] * f + _transform[_f] * 1; | |
} | |
// retrieves the current transformation matrix being applied to the context. | |
pub fn getTransform() [6]f32 { | |
return _transform; | |
} | |
// Saves the entire state of the canvas by pushing the current state onto a | |
// stack. | |
// ⚠️ Only one level of stack for now. | |
pub fn save() !void { | |
stack = _transform; | |
} | |
// Restores the most recently saved canvas state by popping the top entry | |
// in the drawing state stack. If there is no saved state, this method does | |
// nothing. | |
pub fn restore() void { | |
_transform = stack; | |
} | |
// resets the rendering context to its default state, allowing it to be | |
// reused for drawing something else without having to explicitly reset all | |
// the properties. | |
pub fn reset() void { | |
_transform = .{ 1, 0, 0, 1, 0, 0 }; | |
} | |
// adds a translation transformation to the current matrix. | |
pub fn translate(x: f32, y: f32) void { | |
_transform[_e] += x; | |
_transform[_f] += y; | |
} | |
// erases the pixels in a rectangular area by setting them to transparent | |
// black. | |
// ⚠️ Operates in buffer space (do not take the transformation matrix into account) | |
pub fn clearRect(x: i16, y: i16, width: i16, height: i16) void { | |
if (x == 0 and y == 0 and width == pwidth and height == pheight) { | |
@memset(&buffer, color); | |
} else { | |
@panic("clearReact on sizes different from the canvas is not yet implemented"); | |
} | |
} | |
// Renders a rectangle with a starting point is at (x, y) and whose size is | |
// specified by width and height. | |
pub fn rect(x: i16, y: i16, width: i16, height: i16) void { | |
line(x, y, x + width, y); | |
line(x + width, y, x + width, y + height); | |
line(x + width, y + height, x, y + height); | |
line(x, y + height, x, y); | |
} | |
// Draws a line. | |
pub fn line(startx: i16, starty: i16, endx: i16, endy: i16) void { | |
// std.debug.assert(startx >= 0 and starty >= 0 and endx >= 0 and endy >= 0); | |
// This is commented because, although debug performances are much better, | |
// release performances are worst! | |
const builtin = @import("builtin"); | |
if (builtin.mode == .Debug) { | |
// This whole block is an optimization for vertical and horizontal line | |
const ux = | |
_transform[_a] * @as(f32, @floatFromInt(startx)) + | |
_transform[_c] * @as(f32, @floatFromInt(starty)) + _transform[_e]; | |
var uy = | |
_transform[_b] * @as(f32, @floatFromInt(startx)) + | |
_transform[_d] * @as(f32, @floatFromInt(starty)) + _transform[_f]; | |
const vx = | |
_transform[_a] * @as(f32, @floatFromInt(endx)) + | |
_transform[_c] * @as(f32, @floatFromInt(endy)) + _transform[_e]; | |
var vy = | |
_transform[_b] * @as(f32, @floatFromInt(endx)) + | |
_transform[_d] * @as(f32, @floatFromInt(endy)) + _transform[_f]; | |
if (ux > 0 and uy > 0 and vx > 0 and vy > 0 and | |
ux < contextWidth and uy < contextHeight and vx < contextWidth and vy < contextHeight) { | |
// If a line is entirely in the canvas | |
if (ux == vx) { | |
// vertical line | |
const x: u16 = @intFromFloat(ux); | |
if (uy > vy) std.mem.swap(@TypeOf(uy), &uy, &vy); | |
var y: u16 = @intFromFloat(uy); | |
while (y < @as(u16, @intFromFloat(vy))) : (y += 1) { | |
buffer[y * contextWidth + x] = color; | |
} | |
return; | |
} else if (uy == vy) { | |
// horizontal line | |
var startBuffer = @as(u16, @intFromFloat(uy)) * contextWidth + @as(u16, @intFromFloat(ux)); | |
var endBuffer = @as(u16, @intFromFloat(vy)) * contextWidth + @as(u16, @intFromFloat(vx)); | |
if (startBuffer > endBuffer) std.mem.swap(@TypeOf(startBuffer), &startBuffer, &endBuffer); | |
@memset(buffer[startBuffer..endBuffer], color); | |
return; | |
} | |
} | |
} | |
// Otherwise, we use a general but slow algorithm | |
drawThickLine(startx, starty, endx, endy); | |
// drawLineOverlap(startx, starty, endx, endy, 0); | |
// drawLineWu(startx, starty, endx, endy, 0); | |
} | |
// Draws a point. | |
pub inline fn plot(x: i16, y: i16, acolor: u32) void { | |
// std.log.debug("plot x {} y {} width {} height {} index {} buffer.len {}", .{ | |
// x, y, contextWidth, contextHeight, | |
// @as(u16, @bitCast(y)) * width + @as(u16, @bitCast(x)), | |
// buffer.len, | |
// }); | |
const vx = _transform[_a] * @as(f32, @floatFromInt(x)) + _transform[_c] * @as(f32, @floatFromInt(y)) + _transform[_e]; | |
const vy = _transform[_b] * @as(f32, @floatFromInt(x)) + _transform[_d] * @as(f32, @floatFromInt(y)) + _transform[_f]; | |
if (vx >= 0 and vx < contextWidth and vy >= 0 and vy < contextHeight) { | |
buffer[@as(u16, @intFromFloat(vy)) * contextWidth + @as(u16, @intFromFloat(vx))] = acolor; | |
} | |
} | |
// Writes text at the specified position. x and y specifies the top left | |
// corner of the text box to be printed. | |
pub fn printText(x: i16, y: i16, text: []const u8) void { | |
// @setEvalBranchQuota(10000); | |
const fontparams = comptime lbl: { | |
// Check we deal with a P1 netpbm file (ASCII text) | |
std.debug.assert(fontfile[0] == 'P' and fontfile[1] == '1'); | |
// Retrieve width and height | |
var i = 2; | |
while (std.ascii.isWhitespace(fontfile[i])) i += 1; | |
var j = i; | |
while (!std.ascii.isWhitespace(fontfile[j])) j += 1; | |
const fontwidth = try std.fmt.parseInt(usize, fontfile[i..j], 10); | |
i = j; | |
while (std.ascii.isWhitespace(fontfile[i])) i += 1; | |
j = i; | |
while (!std.ascii.isWhitespace(fontfile[j])) j += 1; | |
const fontheight = try std.fmt.parseInt(usize, fontfile[i..j], 10); | |
// Get position of first value | |
while (std.ascii.isWhitespace(fontfile[j])) j += 1; | |
break :lbl .{ fontwidth, fontheight, j }; | |
}; | |
const fontwidth = fontparams[0]; | |
const fontindex = fontparams[2]; | |
for (text, 0..) |c, cindex| { | |
const cusize = @as(usize, @intCast(c)); | |
for (0..8) |j| { | |
for (0..8) |i| { | |
// fontwidth + 1 because of the \n | |
if (fontfile[fontindex + j * (fontwidth + 1) + cusize * 8 + i] != '0') { | |
plot(x + @as(i16, @intCast(i)) + @as(i16, @intCast(cindex * 8)), y + @as(i16, @intCast(j)), color); | |
} | |
} | |
} | |
} | |
} | |
// Modified Bresenham draw(line) with optional overlap. Required for drawThickLine(). | |
// Overlap draws additional pixel when changing minor direction. For standard bresenham overlap, choose LINE_OVERLAP_NONE (0). | |
// | |
// Sample line: | |
// | |
// 00+ | |
// -0000+ | |
// -0000+ | |
// -00 | |
// | |
// 0 pixels are drawn for normal line without any overlap LINE_OVERLAP_NONE | |
// + pixels are drawn if LINE_OVERLAP_MAJOR | |
// - pixels are drawn if LINE_OVERLAP_MINOR | |
const Overlap = enum(u8) { | |
LINE_OVERLAP_NONE = 0, | |
LINE_OVERLAP_MINOR = 1, | |
LINE_OVERLAP_MAJOR = 2, | |
LINE_OVERLAP_BOTH = 3, | |
}; | |
fn drawLineOverlap(pstartx: i16, pstarty: i16, endx: i16, endy: i16, aOverlap: u8) void { | |
var tStepX: i16 = 0; | |
var tStepY: i16 = 0; | |
var tDeltaXTimes2: i16 = 0; | |
var tDeltaYTimes2: i16 = 0; | |
var tError: i16 = 0; | |
var startx = pstartx; | |
var starty = pstarty; | |
// calculate direction | |
var tDeltaX = endx - startx; | |
var tDeltaY = endy - starty; | |
if (tDeltaX < 0) { | |
tDeltaX = -tDeltaX; | |
tStepX = -1; | |
} else { | |
tStepX = 1; | |
} | |
if (tDeltaY < 0) { | |
tDeltaY = -tDeltaY; | |
tStepY = -1; | |
} else { | |
tStepY = 1; | |
} | |
tDeltaXTimes2 = tDeltaX << 1; | |
tDeltaYTimes2 = tDeltaY << 1; | |
// draw start pixel | |
plot(startx, starty, color); | |
if (tDeltaX > tDeltaY) { | |
// start value represents a half step in Y direction | |
tError = tDeltaYTimes2 - tDeltaX; | |
while (startx != endx) { | |
// step in main direction | |
startx += tStepX; | |
if (tError >= 0) { | |
if (aOverlap & @intFromEnum(Overlap.LINE_OVERLAP_MAJOR) != 0) { | |
// draw pixel in main direction before changing | |
plot(startx, starty, color); | |
} | |
// change Y | |
starty += tStepY; | |
if (aOverlap & @intFromEnum(Overlap.LINE_OVERLAP_MINOR) != 0) { | |
// draw pixel in minor direction before changing | |
plot(startx - tStepX, starty, color); | |
} | |
tError -= tDeltaXTimes2; | |
} | |
tError += tDeltaYTimes2; | |
plot(startx, starty, color); | |
} | |
} else { | |
tError = tDeltaXTimes2 - tDeltaY; | |
while (starty != endy) { | |
starty += tStepY; | |
if (tError >= 0) { | |
if (aOverlap & @intFromEnum(Overlap.LINE_OVERLAP_MAJOR) != 0) { | |
// draw pixel in main direction before changing | |
plot(startx, starty, color); | |
} | |
startx += tStepX; | |
if (aOverlap & @intFromEnum(Overlap.LINE_OVERLAP_MINOR) != 0) { | |
// draw pixel in minor direction before changing | |
plot(startx, starty - tStepY, color); | |
} | |
tError -= tDeltaYTimes2; | |
} | |
tError += tDeltaXTimes2; | |
plot(startx, starty, color); | |
} | |
} | |
} | |
// fractional part of x | |
fn fpart(x: f32) f32 { | |
return x - @floor(x); | |
} | |
fn rfpart(x: f32) f32 { | |
return 1 - fpart(x); | |
} | |
fn shadeColor(R: f32, G: f32, B: f32, ratio: f32) u32 { | |
return @as(u32, @intFromFloat(R * ratio)) | | |
@as(u32, @intFromFloat(G * ratio)) << 8 | | |
@as(u32, @intFromFloat(B * ratio)) << 16 | | |
0xFF000000; // Always full alpha | |
} | |
fn drawLineWu(px0: i16, py0: i16, px1: i16, py1: i16, unused: u8) void { | |
std.log.debug("drawLineWu {} {} {} {}", .{ px0, py0, px1, py1 }); | |
_ = unused; | |
var x0: f32 = @floatFromInt(px0); | |
var y0: f32 = @floatFromInt(py0); | |
var x1: f32 = @floatFromInt(px1); | |
var y1: f32 = @floatFromInt(py1); | |
const R: f32 = @floatFromInt(color & 0x000000FF); | |
const G: f32 = @floatFromInt((color & 0x0000FF00) >> 8); | |
const B: f32 = @floatFromInt((color & 0x00FF0000) >> 16); | |
const steep = @abs(y1 - y0) > @abs(x1 - x0); | |
if (steep) { | |
std.mem.swap(f32, &x0, &y0); | |
std.mem.swap(f32, &x1, &y1); | |
std.log.debug("drawLineWu2 {d} {d} {d} {d}", .{ x0, y0, x1, y1 }); | |
} | |
if (x0 > x1) { | |
std.mem.swap(f32, &x0, &x1); | |
std.mem.swap(f32, &y0, &y1); | |
std.log.debug("drawLineWu3 {d} {d} {d} {d}", .{ x0, y0, x1, y1 }); | |
} | |
const dx = x1 - x0; | |
const dy = y1 - y0; | |
var gradient: f32 = 1.0; | |
if (dx != 0.0) { | |
gradient = dy / dx; | |
} | |
// handle first endpoint | |
var xend = @round(x0); | |
var yend = y0 + gradient * (xend - x0); | |
var xgap = rfpart(x0 + 0.5); | |
const xpxl1: i16 = @intFromFloat(xend); // this will be used in the main loop | |
const ypxl1: i16 = @intFromFloat(@floor(yend)); | |
if (steep) { | |
plot(ypxl1 , xpxl1 , shadeColor(R, G, B, rfpart(yend) * xgap)); | |
plot(ypxl1 + 1, xpxl1 , shadeColor(R, G, B, fpart(yend) * xgap)); | |
} else { | |
plot(xpxl1 , ypxl1 , shadeColor(R, G, B, rfpart(yend) * xgap)); | |
plot(xpxl1 , ypxl1 + 1, shadeColor(R, G, B, fpart(yend) * xgap)); | |
} | |
var intery = yend + gradient; // first y-intersection for the main loop | |
// handle second endpoint | |
std.log.debug("x1 {d} y1 {d}", .{ x1, y1 }); | |
xend = @round(x1); | |
yend = y1 + gradient * (xend - x1); | |
std.log.debug("xend {d} yend {d}", .{ xend, yend }); | |
xgap = fpart(x1 + 0.5); | |
const xpxl2: i16 = @intFromFloat(xend); //this will be used in the main loop | |
const ypxl2: i16 = @intFromFloat(@floor(yend)); | |
std.log.debug("xpxl2 {} ypxl2 {}", .{ xpxl2, ypxl2 }); | |
if (steep) { | |
plot(ypxl2 , xpxl2 , shadeColor(R, G, B, rfpart(yend) * xgap)); | |
plot(ypxl2 + 1, xpxl2 , shadeColor(R, G, B, fpart(yend) * xgap)); | |
} else { | |
plot(xpxl2 , ypxl2 , shadeColor(R, G, B, rfpart(yend) * xgap)); | |
plot(xpxl2 , ypxl2 + 1, shadeColor(R, G, B, fpart(yend) * xgap)); | |
} | |
// main loop | |
if (steep) { | |
var i = xpxl1 + 1; | |
while (i < xpxl2 - 1) { | |
plot(@as(i16, @intFromFloat(@floor(intery))) , i, shadeColor(R, G, B, rfpart(intery))); | |
plot(@as(i16, @intFromFloat(@floor(intery))) + 1, i, shadeColor(R, G, B, fpart(intery))); | |
intery = intery + gradient; | |
i += 1; | |
} | |
} else { | |
var i = xpxl1 + 1; | |
while (i < xpxl2 - 1) { | |
plot(i, @as(i16, @intFromFloat(@floor(intery))) , shadeColor(R, G, B, rfpart(intery))); | |
plot(i, @as(i16, @intFromFloat(@floor(intery))) + 1, shadeColor(R, G, B, fpart(intery))); | |
intery = intery + gradient; | |
i += 1; | |
} | |
} | |
} | |
// | |
// The same as before, but no clipping to display range, some pixel are drawn twice (because of using LINE_OVERLAP_BOTH) | |
// and direction of thickness changes for each octant (except for LINE_THICKNESS_MIDDLE and thickness value is odd) | |
// thicknessMode can be LINE_THICKNESS_MIDDLE or any other value | |
// | |
fn drawThickLine(pstartx: i16, pstarty: i16, pendx: i16, pendy: i16) void { | |
var tStepX: i16 = 0; | |
var tStepY: i16 = 0; | |
var tDeltaXTimes2: i16 = 0; | |
var tDeltaYTimes2: i16 = 0; | |
var tError: i16 = 0; | |
var startx = pstartx; | |
var starty = pstarty; | |
var endx = pendx; | |
var endy = pendy; | |
var tDeltaY = startx - endx; | |
var tDeltaX = endy - startx; | |
// mirror 4 quadrants to one and adjust deltas and stepping direction | |
if (tDeltaX < 0) { | |
tDeltaX = -tDeltaX; | |
tStepX = -1; | |
} else { | |
tStepX = 1; | |
} | |
if (tDeltaY < 0) { | |
tDeltaY = -tDeltaY; | |
tStepY = -1; | |
} else { | |
tStepY = 1; | |
} | |
tDeltaXTimes2 = tDeltaX << 1; | |
tDeltaYTimes2 = tDeltaY << 1; | |
var tOverlap: Overlap = Overlap.LINE_OVERLAP_NONE; | |
// which octant are we now | |
if (tDeltaX > tDeltaY) { | |
// if (we want to draw the original coordinate in the middle of the thick line) | |
{ | |
// adjust draw start point | |
tError = tDeltaYTimes2 - tDeltaX; | |
var i = thickness / 2; | |
while (i > 0) { | |
// change X (main direction here) | |
startx -= tStepX; | |
endx -= tStepX; | |
if (tError >= 0) { | |
// change Y | |
starty -= tStepY; | |
endy -= tStepY; | |
tError -= tDeltaXTimes2; | |
} | |
tError += tDeltaYTimes2; | |
i -= 1; | |
} | |
} | |
drawLineOverlap(startx, starty, endx, endy, @intFromEnum(tOverlap)); | |
// drawLineWu(startx, starty, endx, endy, @intFromEnum(tOverlap)); | |
// draw thickness lines | |
tError = tDeltaYTimes2 - tDeltaX; | |
var i = thickness; | |
while (i > 1) { | |
// change X (main direction here) | |
startx += tStepX; | |
endx += tStepX; | |
tOverlap = Overlap.LINE_OVERLAP_NONE; | |
if (tError >= 0) { | |
// change Y | |
startx += tStepY; | |
endy += tStepY; | |
tError -= tDeltaXTimes2; | |
tOverlap = Overlap.LINE_OVERLAP_BOTH; | |
} | |
tError += tDeltaYTimes2; | |
drawLineOverlap(startx, starty, endx, endy, @intFromEnum(tOverlap)); | |
// drawLineWu(startx, starty, endx, endy, @intFromEnum(tOverlap)); | |
i -= 1; | |
} | |
} else { | |
// if (we want to draw the original coordinate in the middle of the thick line) | |
{ | |
tError = tDeltaXTimes2 - tDeltaY; | |
var i = thickness / 2; | |
while (i > 0) { | |
starty -= tStepY; | |
endy -= tStepY; | |
if (tError >= 0) { | |
startx -= tStepX; | |
endx -= tStepX; | |
tError -= tDeltaYTimes2; | |
} | |
tError += tDeltaXTimes2; | |
i -= 1; | |
} | |
} | |
drawLineOverlap(startx, starty, endx, endy, @intFromEnum(tOverlap)); | |
// drawLineWu(startx, starty, endx, endy, @intFromEnum(tOverlap)); | |
tError = tDeltaXTimes2 - tDeltaY; | |
var i = thickness; | |
while (i > 1) { | |
starty += tStepY; | |
endy += tStepY; | |
tOverlap = Overlap.LINE_OVERLAP_NONE; | |
if (tError >= 0) { | |
startx += tStepX; | |
endx += tStepX; | |
tError -= tDeltaYTimes2; | |
tOverlap = Overlap.LINE_OVERLAP_BOTH; | |
} | |
tError += tDeltaXTimes2; | |
drawLineOverlap(startx, starty, endx, endy, @intFromEnum(tOverlap)); | |
// drawLineWu(startx, starty, endx, endy, @intFromEnum(tOverlap)); | |
i -= 1; | |
} | |
} | |
} | |
}; | |
} |
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
// https://zig.news/david_vanderson/interfaces-in-zig-o1c | |
const std = @import("std"); | |
pub const sdl = @cImport({ | |
@cInclude("SDL2/SDL.h"); | |
}); | |
pub const MouseMove = struct { | |
x: i32, | |
y: i32, | |
dx: i32, | |
dy: i32, | |
}; | |
pub const MouseButton = enum(u8) { | |
Left = 1, | |
Center = 2, | |
Right = 3, | |
}; | |
pub const MouseButtonEvent = struct { | |
button: MouseButton, | |
}; | |
pub const MouseWheel = struct { | |
y: i32, | |
}; | |
// From SDL_Scancode | |
// These values are from usage page 0x07 (USB keyboard page). | |
pub const Scancode = enum(u32) { | |
A = 4, | |
B = 5, | |
C = 6, | |
D = 7, | |
E = 8, | |
F = 9, | |
G = 10, | |
H = 11, | |
I = 12, | |
J = 13, | |
K = 14, | |
L = 15, | |
M = 16, | |
N = 17, | |
O = 18, | |
P = 19, | |
Q = 20, | |
R = 21, | |
S = 22, | |
T = 23, | |
U = 24, | |
V = 25, | |
W = 26, | |
X = 27, | |
Y = 28, | |
Z = 29, | |
K1 = 30, | |
K2 = 31, | |
K3 = 32, | |
K4 = 33, | |
K5 = 34, | |
K6 = 35, | |
K7 = 36, | |
K8 = 37, | |
K9 = 38, | |
K0 = 39, | |
RETURN = 40, | |
ESCAPE = 41, | |
BACKSPACE = 42, | |
TAB = 43, | |
SPACE = 44, | |
MINUS = 45, | |
EQUALS = 46, | |
LEFTBRACKET = 47, | |
RIGHTBRACKET = 48, | |
BACKSLASH = 49, // < Located at the lower left of the return | |
// key on ISO keyboards and at the right end | |
// of the QWERTY row on ANSI keyboards. | |
// Produces REVERSE SOLIDUS (backslash) and | |
// VERTICAL LINE in a US layout, REVERSE | |
// SOLIDUS and VERTICAL LINE in a UK Mac | |
// layout, NUMBER SIGN and TILDE in a UK | |
// Windows layout, DOLLAR SIGN and POUND SIGN | |
// in a Swiss German layout, NUMBER SIGN and | |
// APOSTROPHE in a German layout, GRAVE | |
// ACCENT and POUND SIGN in a French Mac | |
// layout, and ASTERISK and MICRO SIGN in a | |
// French Windows layout. | |
// | |
NONUSHASH = 50, // < ISO USB keyboards actually use this code | |
// instead of 49 for the same key, but all | |
// OSes I've seen treat the two codes | |
// identically. So, as an implementor, unless | |
// your keyboard generates both of those | |
// codes and your OS treats them differently, | |
// you should generate BACKSLASH | |
// instead of this code. As a user, you | |
// should not rely on this code because SDL | |
// will never generate it with most (all?) | |
// keyboards. | |
// | |
SEMICOLON = 51, | |
APOSTROPHE = 52, | |
GRAVE = 53, // < Located in the top left corner (on both ANSI | |
// and ISO keyboards). Produces GRAVE ACCENT and | |
// TILDE in a US Windows layout and in US and UK | |
// Mac layouts on ANSI keyboards, GRAVE ACCENT | |
// and NOT SIGN in a UK Windows layout, SECTION | |
// SIGN and PLUS-MINUS SIGN in US and UK Mac | |
// layouts on ISO keyboards, SECTION SIGN and | |
// DEGREE SIGN in a Swiss German layout (Mac: | |
// only on ISO keyboards), CIRCUMFLEX ACCENT and | |
// DEGREE SIGN in a German layout (Mac: only on | |
// ISO keyboards), SUPERSCRIPT TWO and TILDE in a | |
// French Windows layout, COMMERCIAL AT and | |
// NUMBER SIGN in a French Mac layout on ISO | |
// keyboards, and LESS-THAN SIGN and GREATER-THAN | |
// SIGN in a Swiss German, German, or French Mac | |
// layout on ANSI keyboards. | |
// | |
COMMA = 54, | |
PERIOD = 55, | |
SLASH = 56, | |
CAPSLOCK = 57, | |
F1 = 58, | |
F2 = 59, | |
F3 = 60, | |
F4 = 61, | |
F5 = 62, | |
F6 = 63, | |
F7 = 64, | |
F8 = 65, | |
F9 = 66, | |
F10 = 67, | |
F11 = 68, | |
F12 = 69, | |
PRINTSCREEN = 70, | |
SCROLLLOCK = 71, | |
PAUSE = 72, | |
INSERT = 73, // < insert on PC, help on some Mac keyboards (but | |
// does send code 73, not 117) | |
HOME = 74, | |
PAGEUP = 75, | |
DELETE = 76, | |
END = 77, | |
PAGEDOWN = 78, | |
RIGHT = 79, | |
LEFT = 80, | |
DOWN = 81, | |
UP = 82, | |
NUMLOCKCLEAR = 83, // < num lock on PC, clear on Mac keyboards | |
KP_DIVIDE = 84, | |
KP_MULTIPLY = 85, | |
KP_MINUS = 86, | |
KP_PLUS = 87, | |
KP_ENTER = 88, | |
KP_1 = 89, | |
KP_2 = 90, | |
KP_3 = 91, | |
KP_4 = 92, | |
KP_5 = 93, | |
KP_6 = 94, | |
KP_7 = 95, | |
KP_8 = 96, | |
KP_9 = 97, | |
KP_0 = 98, | |
KP_PERIOD = 99, | |
NONUSBACKSLASH = 100, // < This is the additional key that ISO | |
// keyboards have over ANSI ones, | |
// located between left shift and Y. | |
// Produces GRAVE ACCENT and TILDE in a | |
// US or UK Mac layout, REVERSE SOLIDUS | |
// (backslash) and VERTICAL LINE in a | |
// US or UK Windows layout, and | |
// LESS-THAN SIGN and GREATER-THAN SIGN | |
// in a Swiss German, German, or French | |
// layout. | |
APPLICATION = 101, // < windows contextual menu, compose | |
POWER = 102, // < The USB document says this is a status flag, | |
// not a physical key - but some Mac keyboards | |
// do have a power key. | |
KP_EQUALS = 103, | |
F13 = 104, | |
F14 = 105, | |
F15 = 106, | |
F16 = 107, | |
F17 = 108, | |
F18 = 109, | |
F19 = 110, | |
F20 = 111, | |
F21 = 112, | |
F22 = 113, | |
F23 = 114, | |
F24 = 115, | |
EXECUTE = 116, | |
HELP = 117, // < AL Integrated Help Center | |
MENU = 118, // < Menu (show menu) | |
SELECT = 119, | |
STOP = 120, // < AC Stop | |
AGAIN = 121, // < AC Redo/Repeat | |
UNDO = 122, // < AC Undo | |
CUT = 123, // < AC Cut | |
COPY = 124, // < AC Copy | |
PASTE = 125, // < AC Paste | |
FIND = 126, // < AC Find | |
MUTE = 127, | |
VOLUMEUP = 128, | |
VOLUMEDOWN = 129, | |
// not sure whether there's a reason to enable these | |
// LOCKINGCAPSLOCK = 130, | |
// LOCKINGNUMLOCK = 131, | |
// LOCKINGSCROLLLOCK = 132, | |
KP_COMMA = 133, | |
KP_EQUALSAS400 = 134, | |
INTERNATIONAL1 = 135, // < used on Asian keyboards, see | |
// footnotes in USB doc | |
INTERNATIONAL2 = 136, | |
INTERNATIONAL3 = 137, // < Yen | |
INTERNATIONAL4 = 138, | |
INTERNATIONAL5 = 139, | |
INTERNATIONAL6 = 140, | |
INTERNATIONAL7 = 141, | |
INTERNATIONAL8 = 142, | |
INTERNATIONAL9 = 143, | |
LANG1 = 144, // < Hangul/English toggle | |
LANG2 = 145, // < Hanja conversion | |
LANG3 = 146, // < Katakana | |
LANG4 = 147, // < Hiragana | |
LANG5 = 148, // < Zenkaku/Hankaku | |
LANG6 = 149, // < reserved | |
LANG7 = 150, // < reserved | |
LANG8 = 151, // < reserved | |
LANG9 = 152, // < reserved | |
ALTERASE = 153, // < Erase-Eaze | |
SYSREQ = 154, | |
CANCEL = 155, // < AC Cancel | |
CLEAR = 156, | |
PRIOR = 157, | |
RETURN2 = 158, | |
SEPARATOR = 159, | |
OUT = 160, | |
OPER = 161, | |
CLEARAGAIN = 162, | |
CRSEL = 163, | |
EXSEL = 164, | |
KP_00 = 176, | |
KP_000 = 177, | |
THOUSANDSSEPARATOR = 178, | |
DECIMALSEPARATOR = 179, | |
CURRENCYUNIT = 180, | |
CURRENCYSUBUNIT = 181, | |
KP_LEFTPAREN = 182, | |
KP_RIGHTPAREN = 183, | |
KP_LEFTBRACE = 184, | |
KP_RIGHTBRACE = 185, | |
KP_TAB = 186, | |
KP_BACKSPACE = 187, | |
KP_A = 188, | |
KP_B = 189, | |
KP_C = 190, | |
KP_D = 191, | |
KP_E = 192, | |
KP_F = 193, | |
KP_XOR = 194, | |
KP_POWER = 195, | |
KP_PERCENT = 196, | |
KP_LESS = 197, | |
KP_GREATER = 198, | |
KP_AMPERSAND = 199, | |
KP_DBLAMPERSAND = 200, | |
KP_VERTICALBAR = 201, | |
KP_DBLVERTICALBAR = 202, | |
KP_COLON = 203, | |
KP_HASH = 204, | |
KP_SPACE = 205, | |
KP_AT = 206, | |
KP_EXCLAM = 207, | |
KP_MEMSTORE = 208, | |
KP_MEMRECALL = 209, | |
KP_MEMCLEAR = 210, | |
KP_MEMADD = 211, | |
KP_MEMSUBTRACT = 212, | |
KP_MEMMULTIPLY = 213, | |
KP_MEMDIVIDE = 214, | |
KP_PLUSMINUS = 215, | |
KP_CLEAR = 216, | |
KP_CLEARENTRY = 217, | |
KP_BINARY = 218, | |
KP_OCTAL = 219, | |
KP_DECIMAL = 220, | |
KP_HEXADECIMAL = 221, | |
LCTRL = 224, | |
LSHIFT = 225, | |
LALT = 226, // < alt, option | |
LGUI = 227, // < windows, command (apple), meta | |
RCTRL = 228, | |
RSHIFT = 229, | |
RALT = 230, // < alt gr, option | |
RGUI = 231, // < windows, command (apple), meta | |
MODE = 257, // < I'm not sure if this is really not covered | |
// by any of the above, but since there's a | |
// special KMOD_MODE for it I'm adding it here | |
// | |
}; | |
pub const Keycode = enum(u8) { | |
None, | |
}; | |
pub const Mod = enum(u16) { | |
LSHIFT = 1, | |
RSHIFT = 2, | |
LCTRL = 64, | |
}; | |
pub const KeyEvent = struct { | |
mod: u16, | |
scancode: Scancode, | |
keycode: Keycode, | |
}; | |
pub const EventType = enum { | |
MouseMove, | |
MouseClick, | |
MouseDown, | |
MouseUp, | |
MouseWheel, | |
KeyDown, | |
}; | |
pub const InputEvent = union(EventType) { | |
MouseMove: MouseMove, | |
MouseClick: MouseButtonEvent, | |
MouseDown: MouseButtonEvent, | |
MouseUp: MouseButtonEvent, | |
MouseWheel: MouseWheel, | |
KeyDown: KeyEvent, | |
}; | |
pub const IOAdapter = struct { | |
getEventFn: *const fn (*IOAdapter) ?InputEvent, | |
drawImageFn: *const fn (*IOAdapter, []const u32, u16, u16, u16, u16) void, | |
renderSceneFn: *const fn (*IOAdapter) void, | |
pub fn getEvent(adapter: *IOAdapter) ?InputEvent { | |
return adapter.getEventFn(adapter); | |
} | |
pub fn drawImage(adapter: *IOAdapter, image: []const u32, sx: u16, sy: u16, sWidth: u16, sHeight: u16) void { | |
return adapter.drawImageFn(adapter, image, sx, sy, sWidth, sHeight); | |
} | |
pub fn renderScene(adapter: *IOAdapter) void { | |
return adapter.renderSceneFn(adapter); | |
} | |
}; | |
pub const SDLAdapter = struct { | |
const Self = @This(); | |
window: *sdl.SDL_Window, | |
texture: *sdl.SDL_Texture, | |
renderer: *sdl.SDL_Renderer, | |
width: usize, | |
height: usize, | |
// tv: std.posix.timeval, | |
interface: IOAdapter, | |
pub fn init(width: u16, height: u16) anyerror!Self { | |
if (sdl.SDL_Init(sdl.SDL_INIT_VIDEO) != 0) { | |
sdl.SDL_Log("Unable to initialize SDL: %s", sdl.SDL_GetError()); | |
return error.SDLInitializationFailed; | |
} | |
errdefer sdl.SDL_Quit(); | |
const window = sdl.SDL_CreateWindow("level", sdl.SDL_WINDOWPOS_UNDEFINED, | |
sdl.SDL_WINDOWPOS_UNDEFINED, width, height, sdl.SDL_WINDOW_OPENGL) orelse { | |
sdl.SDL_Log("Unable to create window: %s", sdl.SDL_GetError()); | |
return error.SDLInitializationFailed; | |
}; | |
errdefer sdl.SDL_DestroyWindow(window); | |
const renderer = sdl.SDL_CreateRenderer(window, -1, 0) orelse { | |
sdl.SDL_Log("Unable to create renderer: %s", sdl.SDL_GetError()); | |
return error.SDLInitializationFailed; | |
}; | |
errdefer sdl.SDL_DestroyRenderer(renderer); | |
const texture = sdl.SDL_CreateTexture(renderer, sdl.SDL_PIXELFORMAT_RGBA32, | |
sdl.SDL_TEXTUREACCESS_STREAMING, width, height) orelse { | |
sdl.SDL_Log("Unable to create texture: %s", sdl.SDL_GetError()); | |
return error.SDLInitializationFailed; | |
}; | |
errdefer sdl.SDL_DestroyTexture(texture); | |
return Self{ | |
.window = window, | |
.texture = texture, | |
.renderer = renderer, | |
.width = width, | |
.height = height, | |
.interface = IOAdapter { | |
.getEventFn = getEvent, | |
.drawImageFn = drawImage, | |
.renderSceneFn = renderScene, | |
}, | |
}; | |
} | |
pub fn deinit(self: *Self) void { | |
sdl.SDL_DestroyRenderer(self.renderer); | |
sdl.SDL_DestroyTexture(self.texture); | |
sdl.SDL_DestroyWindow(self.window); | |
sdl.SDL_Quit(); | |
} | |
pub fn getEvent(adapter: *IOAdapter) ?InputEvent { | |
const self: *SDLAdapter = @fieldParentPtr("interface", adapter); | |
_ = self; | |
var event: sdl.SDL_Event = undefined; | |
if (sdl.SDL_PollEvent(&event) != 0) { | |
switch (event.type) { | |
sdl.SDL_KEYDOWN => { | |
return InputEvent{ | |
.KeyDown = KeyEvent{ | |
.mod = event.key.keysym.mod, | |
.scancode = @enumFromInt(event.key.keysym.scancode), | |
.keycode = Keycode.None, | |
}, | |
}; | |
}, | |
sdl.SDL_MOUSEMOTION => { | |
return InputEvent{ | |
.MouseMove = MouseMove{ | |
.x = event.motion.x, | |
.y = event.motion.y, | |
.dx = event.motion.xrel, | |
.dy = event.motion.yrel, | |
}, | |
}; | |
}, | |
sdl.SDL_MOUSEBUTTONDOWN => { | |
return InputEvent{ .MouseDown = MouseButtonEvent{ .button = @enumFromInt(event.button.button) } }; | |
}, | |
sdl.SDL_MOUSEBUTTONUP => { | |
return InputEvent{ .MouseUp = MouseButtonEvent{ .button = @enumFromInt(event.button.button) } }; | |
}, | |
sdl.SDL_MOUSEWHEEL => { | |
return InputEvent{ .MouseWheel = MouseWheel{ .y = event.wheel.y } }; | |
}, | |
else => {}, | |
} | |
} | |
return null; | |
} | |
pub fn drawImage(adapter: *IOAdapter, image: []const u32, sx: u16, sy: u16, sWidth: u16, sHeight: u16) void { | |
const self: *SDLAdapter = @fieldParentPtr("interface", adapter); | |
const texture = self.texture; | |
var buffer: [*c]u32 = undefined; | |
var pitch: i32 = undefined; | |
const res = sdl.SDL_LockTexture(texture, null, @as([*c]?*anyopaque, @ptrCast(&buffer)), &pitch); | |
if (res < 0) { | |
sdl.SDL_Log("Unable to lock texture: %s", sdl.SDL_GetError()); | |
std.posix.exit(0); | |
} | |
if (sx == 0 and sy == 0 and sWidth == self.width and sHeight == self.height) { | |
std.mem.copyForwards(u32, buffer[0..image.len], image); | |
} else if (sWidth * 2 == self.width and sHeight * 2 == self.height) { | |
@setRuntimeSafety(false); // Too slow otherwise | |
for (0..self.height) |j| { | |
const offset = j * self.width; | |
const sourceOffset = j / 2 * sWidth; | |
for (0..self.width) |i| { | |
const imageIndex: usize = sourceOffset + i / 2; | |
buffer[offset + i] = image[imageIndex]; | |
} | |
} | |
@setRuntimeSafety(true); | |
} else { | |
@setRuntimeSafety(false); // Too slow otherwise | |
const ifactor: f32 = @as(f32, @floatFromInt(sWidth)) / @as(f32, @floatFromInt(self.width)); | |
const jfactor: f32 = @as(f32, @floatFromInt(sHeight)) / @as(f32, @floatFromInt(self.height)); | |
for (0..self.height) |j| { | |
const offset = j * self.width; | |
const sourceOffset = @as(usize, @intFromFloat(@as(f32, @floatFromInt(j)) * jfactor)) * sWidth; | |
for (0..self.width) |i| { | |
const imageIndex: usize = sourceOffset + @as(usize, @intFromFloat(@as(f32, @floatFromInt(i)) * ifactor)); | |
buffer[offset + i] = image[imageIndex]; | |
} | |
} | |
@setRuntimeSafety(true); | |
} | |
sdl.SDL_UnlockTexture(texture); | |
} | |
pub fn renderScene(adapter: *IOAdapter) void { | |
const self: *SDLAdapter = @fieldParentPtr("interface", adapter); | |
if (sdl.SDL_SetRenderDrawColor(self.renderer, 0x00, 0x00, 0x00, 0xFF) < 0) { | |
sdl.SDL_Log("Unable to draw color: %s", sdl.SDL_GetError()); | |
std.posix.exit(0); | |
} | |
if (sdl.SDL_RenderClear(self.renderer) < 0) { | |
sdl.SDL_Log("Unable to clear: %s", sdl.SDL_GetError()); | |
std.posix.exit(0); | |
} | |
// blit the surface | |
if (sdl.SDL_RenderCopy(self.renderer, self.texture, null, null) < 0) { | |
sdl.SDL_Log("Unable to copy texture: %s", sdl.SDL_GetError()); | |
std.posix.exit(0); | |
} | |
sdl.SDL_RenderPresent(self.renderer); | |
} | |
}; |
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"); | |
// Load a file into a buffer using mmap | |
pub fn load(pathname: []const u8) ![]align(4096) const u8 { | |
var file = try std.fs.cwd().openFile(pathname, .{}); | |
defer file.close(); | |
const size = try file.getEndPos(); | |
const buffer = try std.posix.mmap( | |
null, | |
size, | |
std.posix.PROT.READ, | |
.{ .TYPE = .SHARED }, | |
file.handle, | |
0, | |
); | |
errdefer std.posix.munmap(buffer); | |
return buffer; | |
} | |
const Ptype = enum { | |
P1, P2, P3, P4, P5, P6, | |
}; | |
pub fn writePgm(comptime P: Ptype, width: usize, height: usize, pixels: []const u8, filepath: []const u8) !void { | |
if (width * height != pixels.len) { | |
return error.IncorrectSize; | |
} | |
// Create file | |
const file = try std.fs.cwd().createFile( | |
filepath, | |
.{ .read = true }, | |
); | |
defer file.close(); | |
switch (P) { | |
.P1 => { | |
// Prepare PGM header (https://en.wikipedia.org/wiki/Netpbm) | |
var buffer: [255]u8 = [_]u8{ 0 } ** 255; | |
const pgmHeader = try std.fmt.bufPrint(&buffer, "P1\n{} {}\n", .{ width, height }); | |
// Write to file | |
try file.writeAll(pgmHeader); | |
for (0..height) |j| { | |
for (0..width) |i| { | |
if (pixels[j * width + i] != 0) { | |
try file.writeAll("1 "); | |
} else { | |
try file.writeAll("0 "); | |
} | |
} | |
try file.writeAll("\n"); | |
} | |
}, | |
.P6 => { | |
// Prepare PGM header (https://en.wikipedia.org/wiki/Netpbm) | |
var buffer: [255]u8 = [_]u8{ 0 } ** 255; | |
const pgmHeader = try std.fmt.bufPrint(&buffer, "P6\n{} {}\n255\n", .{ width, height }); | |
// Write to file | |
try file.writeAll(pgmHeader); | |
const stdout = std.io.getStdOut().writer(); | |
for (pixels) |p| { try stdout.print("{} ", .{ p }); } | |
try file.writeAll(pixels); | |
}, | |
else => return error.NotImplemented, | |
} | |
} | |
pub fn writePam32(width: usize, height: usize, pixels: []const u8, filepath: []const u8) !void { | |
// Create file | |
const file = try std.fs.cwd().createFile( | |
filepath, | |
.{ .read = true }, | |
); | |
defer file.close(); | |
// Prepare PGM header (https://en.wikipedia.org/wiki/Netpbm) | |
var buffer: [255]u8 = [_]u8{ 0 } ** 255; | |
const pgmHeader = try std.fmt.bufPrint(&buffer, "P7\nWIDTH {}\nHEIGHT {}\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n", .{ width, height }); | |
// Write to file | |
try file.writeAll(pgmHeader); | |
try file.writeAll(pixels); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment