//! By convention, root.zig is the root source file when making a package. const std = @import("std"); const debug = std.debug; const Io = std.Io; const default_header = PPMHeader{ .width = 100, .height = 100, .colors = 0 }; const endian = @import("builtin").target.cpu.arch.endian(); // https://netpbm.sourceforge.net/doc/ppm.html pub const PPMHeader = struct { width: u32, height: u32, colors: u16 }; pub const Pixel = struct { r: u8, g: u8, b: u8 }; pub fn readHeader(reader: *std.Io.Reader) !PPMHeader { const p3 = try reader.takeDelimiter('\n') orelse return default_header; if (!std.mem.eql(u8, p3, "P3")) { return default_header; } std.debug.print("Got identifier: {s}\n", .{p3}); const witdth_x_height_str = try reader.takeDelimiter('\n') orelse return default_header; const size = std.mem.cut(u8, witdth_x_height_str, " ") orelse return default_header; if (size.@"0".len == 0 or size.@"1".len == 0) { return default_header; } debug.print("Image Size: {s}: {s}\n", .{ size.@"0", size.@"1" }); const width = try std.fmt.parseInt(u32, size.@"0", 10); const height = try std.fmt.parseInt(u32, size.@"1", 10); const colors_str = try reader.takeDelimiter('\n') orelse return default_header; const colors = try std.fmt.parseInt(u16, colors_str, 10); debug.print("Image Colors: {d}\n", .{colors}); return PPMHeader{ .width = width, .height = height, .colors = colors, }; } pub fn readBody(list: *std.ArrayList(Pixel), reader: *std.Io.Reader) !void { while (reader.takeDelimiter('\n')) |result| if (result) |line| { var rgb: [3]u8 = undefined; parseLine(line, &rgb) catch return; list.appendAssumeCapacity(Pixel{ .r = rgb[0], .g = rgb[1], .b = rgb[2] }); } else break else |err| switch (err) { error.ReadFailed => { debug.print("Reading file failed!", .{}); }, error.StreamTooLong => { debug.print("Stream too long!!", .{}); }, } } pub fn parseLine(line: []u8, out: *[3]u8) !void { var iter = std.mem.splitScalar(u8, line, ' '); var i: u8 = 0; while (iter.next()) |byte| : (i += 1) { const int_byte = try std.fmt.parseInt(u8, byte, 10); out[i] = int_byte; } } test "readHeader" { var reader: std.Io.Reader = .fixed("P3\n1200 675\n255\n"); var header = try readHeader(&reader); var expected = PPMHeader{ .width = 1200, .height = 675, .colors = 255 }; try std.testing.expect(std.meta.eql(expected, header)); reader = .fixed(""); header = try readHeader(&reader); expected = PPMHeader{ .width = 100, .height = 100, .colors = 0 }; try std.testing.expect(std.meta.eql(expected, header)); // Partial, but still broken reader = .fixed("P3"); header = try readHeader(&reader); expected = PPMHeader{ .width = 100, .height = 100, .colors = 0 }; try std.testing.expect(std.meta.eql(expected, header)); // Partial, but still broken reader = .fixed("P3\n 0 \n"); header = try readHeader(&reader); expected = PPMHeader{ .width = 100, .height = 100, .colors = 0 }; try std.testing.expect(std.meta.eql(expected, header)); } test "readBody" { const gpa = std.testing.allocator; var reader: std.Io.Reader = .fixed( \\84 95 53 \\84 95 54 \\87 102 57 ); var pixel_list: std.ArrayList(Pixel) = try .initCapacity(gpa, 10); defer pixel_list.deinit(gpa); try readBody(&pixel_list, &reader); const expected: []const Pixel = &.{ .{ .r = 84, .g = 95, .b = 53 }, .{ .r = 84, .g = 95, .b = 54 }, .{ .r = 87, .g = 102, .b = 57 } }; debug.print("Pixels: {any}\n", .{pixel_list.items}); debug.print("Expect: {any}\n", .{expected}); for (pixel_list.items, expected) |p1, p2| { try std.testing.expect(std.meta.eql(p1, p2)); } }