const std = @import("std"); const debug = std.debug; const ppm_view = @import("ppmview"); const knots = @import("knots"); const Canvas = knots.component.Canvas; var app_width: u32 = 800; var app_height: u32 = 600; const Context = struct { app: knots.App, filename: [:0]const u8, file_data: std.ArrayList(ppm_view.Pixel), is_drawn: bool, }; pub fn main(init: std.process.Init) !void { // Read the file: // stat the file to get the length // allocate a buffer of that length // read the bytes into the buffer const gpa = init.gpa; const io = init.io; var iterator = try init.minimal.args.iterateAllocator(gpa); defer iterator.deinit(); const read_filename = readFileName(&iterator); if (read_filename) |filename| { debug.print("Opening file: {s}\n", .{filename}); const cwd = std.Io.Dir.cwd(); const file = try std.Io.Dir.openFile(cwd, io, filename, .{ .mode = .read_only }); defer file.close(io); var ppm_buffer: [1024]u8 = undefined; var file_reader = file.reader(io, &ppm_buffer); const reader = &file_reader.interface; const ppm_header = ppm_view.readHeader(reader) catch return; debug.print("Header: {}\n", .{ppm_header}); app_width = ppm_header.width; app_height = ppm_header.height; const window_title = try std.mem.concat(gpa, u8, &.{ "PPMView - ", filename }); defer gpa.free(window_title); var ppm_data: std.ArrayList(ppm_view.Pixel) = try .initCapacity(gpa, app_width * app_height); defer ppm_data.deinit(gpa); try ppm_view.readBody(&ppm_data, reader); const app = try knots.App.init(init.io, init.gpa, .{ .window = .{ .width = app_width, .height = app_height, .title = window_title, }, }); var ctx = Context{ .app = app, .filename = filename, .file_data = ppm_data, .is_drawn = false, }; defer ctx.app.deinit(); try ctx.app.start(frameCb); } } fn frameCb(app: *knots.App) !void { const size = app.viewport.window.getSize(); return app.e(.{ knots.component.Rect{ .width = .fixed(@floatFromInt(size.width)), .height = .fixed(@floatFromInt(size.height)), .padding = .init(16, 16, 16, 16), .dir = .column, .key = .src(@src()), }, Canvas{ .width = .fixed(@floatFromInt(size.width)), .height = .fixed(@floatFromInt(size.height)), .key = .src(@src()), .onDraw = onDraw, }, }); } fn onDraw(app: *knots.App, painter: *Canvas.Painter) !void { const ctx: *Context = @fieldParentPtr("app", app); const ppm_data = ctx.file_data; const size = app.viewport.window.getSize(); for (0..size.height) |i| { for (0..size.width) |j| { const pixel = ppm_data.items[j + (i * size.width)]; const f_x: f32 = @floatFromInt(j); const f_y: f32 = @floatFromInt(i); const r: f32 = srgbToLinear(@as(f32, @floatFromInt(pixel.r)) / 255.0); const g: f32 = srgbToLinear(@as(f32, @floatFromInt(pixel.g)) / 255.0); const b: f32 = srgbToLinear(@as(f32, @floatFromInt(pixel.b)) / 255.0); //debug.print("Pixel: {d} {d} {d}\n", .{ r, g, b }); try painter.fillRect(.{ .x = f_x, .y = f_y, .w = 1.0, .h = 1.0, .color = .{ r, g, b, 1.0 }, }); } } ctx.is_drawn = true; app.requestFrame(); } fn srgbToLinear(c: f32) f32 { return if (c <= 0.04045) c / 12.92 else std.math.pow(f32, (c + 0.055) / 1.055, 2.4); } fn readFileName(iterator: *std.process.Args.Iterator) ?[:0]const u8 { // Program name is always first, then the arg we actually want var idx: u2 = 0; const file_name: ?[:0]const u8 = blk: while (iterator.next()) |key| { if (idx == 0) { idx += 1; continue; } debug.print("Key ({d}): {s}\n", .{ key.len, key }); break :blk key; } else null; return file_name; }