move display config to separate file

This commit is contained in:
Emil Lerch 2023-04-04 08:15:30 -07:00
parent a7b917d3e9
commit f1e635d786
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
2 changed files with 56 additions and 53 deletions

14
src/display.zig Normal file
View File

@ -0,0 +1,14 @@
// Image specifications
pub const WIDTH = 128;
pub const HEIGHT = 64;
// Text specifications
pub const FONT_WIDTH = 5;
pub const FONT_HEIGHT = 8;
pub const CHARS_PER_LINE = 25; // 25 * 5 = 125 so we have 3px left over
pub const BORDER_LEFT = 1; // 1 empty px left, 2 empty on right
pub const LINES = 8;
// Device specifications
pub const PAGES = 8;

View File

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const display = @import("display.zig");
const chars = @import("images/images.zig").chars; const chars = @import("images/images.zig").chars;
// The package manager will install headers from our dependency in zig's build // The package manager will install headers from our dependency in zig's build
@ -9,22 +10,7 @@ const c = @cImport({
@cInclude("i2cdriver.h"); @cInclude("i2cdriver.h");
}); });
// Image specifications var lines: [display.LINES]*[:0]u8 = undefined;
const WIDTH = 128;
const HEIGHT = 64;
// Text specifications
const FONT_WIDTH = 5;
const FONT_HEIGHT = 8;
const CHARS_PER_LINE = 25; // 25 * 5 = 125 so we have 3px left over
const BORDER_LEFT = 1; // 1 empty px left, 2 empty on right
const LINES = 8;
// Device specifications
const PAGES = 8;
var lines: [LINES]*[:0]u8 = undefined;
fn usage(args: [][]u8) !void { fn usage(args: [][]u8) !void {
const writer = std.io.getStdErr().writer(); const writer = std.io.getStdErr().writer();
@ -52,10 +38,10 @@ pub fn main() !void {
// we take command line options. So cli will overwrite any lines specified // we take command line options. So cli will overwrite any lines specified
// from the file // from the file
const stdin_file = std.io.getStdIn(); const stdin_file = std.io.getStdIn();
var stdin_data: [WIDTH * HEIGHT + 1]u8 = undefined; var stdin_data: [display.WIDTH * display.HEIGHT + 1]u8 = undefined;
// Need this to support deallocation of memory // Need this to support deallocation of memory
var line_inx: usize = 0; var line_inx: usize = 0;
var stdin_lines: [LINES][:0]u8 = undefined; var stdin_lines: [display.LINES][:0]u8 = undefined;
defer { defer {
for (0..line_inx) |i| { for (0..line_inx) |i| {
alloc.free(stdin_lines[i]); alloc.free(stdin_lines[i]);
@ -84,7 +70,7 @@ pub fn main() !void {
const opts = try processArgs(alloc, args, &lines); const opts = try processArgs(alloc, args, &lines);
defer alloc.destroy(opts); defer alloc.destroy(opts);
if (opts.background_filename.len > 0) try stdout.print("Converting {s}\n", .{opts.background_filename}); if (opts.background_filename.len > 0) try stdout.print("Converting {s}\n", .{opts.background_filename});
var pixels: [WIDTH * HEIGHT]u8 = undefined; var pixels: [display.WIDTH * display.HEIGHT]u8 = undefined;
try convertImage(opts.background_filename, &pixels, textForLine); try convertImage(opts.background_filename, &pixels, textForLine);
try bw.flush(); try bw.flush();
@ -95,7 +81,7 @@ pub fn main() !void {
// try stdout.print("Run `zig build test` to run the tests.\n", .{}); // try stdout.print("Run `zig build test` to run the tests.\n", .{});
} }
fn processArgs(allocator: std.mem.Allocator, args: [][:0]u8, line_array: *[LINES]*const [:0]u8) !*Options { fn processArgs(allocator: std.mem.Allocator, args: [][:0]u8, line_array: *[display.LINES]*const [:0]u8) !*Options {
if (args.len < 2) try usage(args); if (args.len < 2) try usage(args);
const prefix = "/dev/ttyUSB"; const prefix = "/dev/ttyUSB";
var opts = try allocator.create(Options); var opts = try allocator.create(Options);
@ -119,10 +105,10 @@ fn processArgs(allocator: std.mem.Allocator, args: [][:0]u8, line_array: *[LINES
continue; continue;
} }
if (line_number) |line| { if (line_number) |line| {
if (arg.len > CHARS_PER_LINE) { if (arg.len > display.CHARS_PER_LINE) {
try std.io.getStdErr().writer().print( try std.io.getStdErr().writer().print(
"ERROR: text for line {d} has {d} chars, exceeding maximum length {d}\n", "ERROR: text for line {d} has {d} chars, exceeding maximum length {d}\n",
.{ line, arg.len, CHARS_PER_LINE }, .{ line, arg.len, display.CHARS_PER_LINE },
); );
std.os.exit(1); std.os.exit(1);
} }
@ -162,13 +148,16 @@ fn sendPixelsToStdOut(pixels: []const u8) !void {
var bw = std.io.bufferedWriter(stdout_file); var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer(); const stdout = bw.writer();
defer bw.flush() catch unreachable; // don't forget to flush! defer bw.flush() catch unreachable; // don't forget to flush!
for (0..HEIGHT) |i| { for (0..display.HEIGHT) |i| {
try stdout.print("{d:0>2}: {s}\n", .{ i, fmtSliceGreyscaleImage(pixels[(i * WIDTH)..((i + 1) * WIDTH)]) }); try stdout.print(
"{d:0>2}: {s}\n",
.{ i, fmtSliceGreyscaleImage(pixels[(i * display.WIDTH)..((i + 1) * display.WIDTH)]) },
);
} }
} }
fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id: u8) !void { fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id: u8) !void {
var pixels_write_command = [_]u8{0x00} ** ((WIDTH * PAGES) + 1); var pixels_write_command = [_]u8{0x00} ** ((display.WIDTH * display.PAGES) + 1);
pixels_write_command[0] = 0x40; pixels_write_command[0] = 0x40;
packPixelsToDeviceFormat(pixels, pixels_write_command[1..]); packPixelsToDeviceFormat(pixels, pixels_write_command[1..]);
var i2c = c.I2CDriver{ var i2c = c.I2CDriver{
@ -228,8 +217,8 @@ fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id
fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void { fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
// Each u8 in pixels is a single bit. We need to pack these bits // Each u8 in pixels is a single bit. We need to pack these bits
for (packed_pixels, 0..) |*b, i| { for (packed_pixels, 0..) |*b, i| {
const column = i % WIDTH; const column = i % display.WIDTH;
const page = i / WIDTH; const page = i / display.WIDTH;
// if (column == 0) std.debug.print("{d}: ", .{page}); // if (column == 0) std.debug.print("{d}: ", .{page});
// pixel array will be 8x as "high" as the data array we are sending to // pixel array will be 8x as "high" as the data array we are sending to
@ -238,7 +227,7 @@ fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
// //
// To convert from the pixel array above, we need to: // To convert from the pixel array above, we need to:
// 1. convert from device page to a base "row" in the pixel array // 1. convert from device page to a base "row" in the pixel array
const row = page * PAGES; const row = page * display.PAGES;
// 2. We will have 8 rows for each base row // 2. We will have 8 rows for each base row
// 3. Multiple each row by the width to get the index of the start of // 3. Multiple each row by the width to get the index of the start of
// the row // the row
@ -253,14 +242,14 @@ fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
// per source byte // per source byte
// 2. Shift that bit into the proper position in our destination byte // 2. Shift that bit into the proper position in our destination byte
b.* = (pixels[(0 + row) * WIDTH + column] & 0x01) << 0 | b.* = (pixels[(0 + row) * display.WIDTH + column] & 0x01) << 0 |
(pixels[(1 + row) * WIDTH + column] & 0x01) << 1 | (pixels[(1 + row) * display.WIDTH + column] & 0x01) << 1 |
(pixels[(2 + row) * WIDTH + column] & 0x01) << 2 | (pixels[(2 + row) * display.WIDTH + column] & 0x01) << 2 |
(pixels[(3 + row) * WIDTH + column] & 0x01) << 3 | (pixels[(3 + row) * display.WIDTH + column] & 0x01) << 3 |
(pixels[(4 + row) * WIDTH + column] & 0x01) << 4 | (pixels[(4 + row) * display.WIDTH + column] & 0x01) << 4 |
(pixels[(5 + row) * WIDTH + column] & 0x01) << 5 | (pixels[(5 + row) * display.WIDTH + column] & 0x01) << 5 |
(pixels[(6 + row) * WIDTH + column] & 0x01) << 6 | (pixels[(6 + row) * display.WIDTH + column] & 0x01) << 6 |
(pixels[(7 + row) * WIDTH + column] & 0x01) << 7; (pixels[(7 + row) * display.WIDTH + column] & 0x01) << 7;
// std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})}); // std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})});
// if (column == 127) std.debug.print("\n", .{}); // if (column == 127) std.debug.print("\n", .{});
@ -305,7 +294,7 @@ fn reportMagickError(mw: ?*c.MagickWand) !void {
fn textForLine(line: usize) []u8 { fn textForLine(line: usize) []u8 {
return lines[line].*; return lines[line].*;
} }
fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const fn (usize) []u8) !void { fn convertImage(filename: [:0]u8, pixels: *[display.WIDTH * display.HEIGHT]u8, text_fn: *const fn (usize) []u8) !void {
c.MagickWandGenesis(); c.MagickWandGenesis();
defer c.MagickWandTerminus(); defer c.MagickWandTerminus();
var mw = c.NewMagickWand(); var mw = c.NewMagickWand();
@ -343,7 +332,7 @@ fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const f
// This should be 48x64 with our test // This should be 48x64 with our test
// Command line resize works differently than this. Here we need to find // Command line resize works differently than this. Here we need to find
// new width and height based on the input aspect ratio ourselves // new width and height based on the input aspect ratio ourselves
const resize_dimensions = getNewDimensions(w, h, WIDTH, HEIGHT); const resize_dimensions = getNewDimensions(w, h, display.WIDTH, display.HEIGHT);
std.log.debug("Dimensions for resize: {d}x{d}\n", .{ resize_dimensions.width, resize_dimensions.height }); std.log.debug("Dimensions for resize: {d}x{d}\n", .{ resize_dimensions.width, resize_dimensions.height });
@ -369,21 +358,21 @@ fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const f
// around it means that the offset will be negative // around it means that the offset will be negative
status = c.MagickExtentImage( status = c.MagickExtentImage(
mw, mw,
WIDTH, display.WIDTH,
HEIGHT, display.HEIGHT,
-@intCast(isize, (WIDTH - resize_dimensions.width) / 2), -@intCast(isize, (display.WIDTH - resize_dimensions.width) / 2),
-@intCast(isize, (HEIGHT - resize_dimensions.height) / 2), -@intCast(isize, (display.HEIGHT - resize_dimensions.height) / 2),
); );
if (status == c.MagickFalse) if (status == c.MagickFalse)
return error.CouldNotSetExtent; return error.CouldNotSetExtent;
for (0..LINES) |i| { for (0..display.LINES) |i| {
const text = text_fn(i); const text = text_fn(i);
if (text.len == 0) continue; if (text.len == 0) continue;
// We have text! // We have text!
const y: isize = FONT_HEIGHT * @intCast(isize, i); const y: isize = display.FONT_HEIGHT * @intCast(isize, i);
var x: isize = BORDER_LEFT; var x: isize = display.BORDER_LEFT;
var left_spaces: isize = 0; var left_spaces: isize = 0;
for (text) |ch| { for (text) |ch| {
if (ch == ' ') { if (ch == ' ') {
@ -392,7 +381,7 @@ fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const f
} }
break; break;
} }
x += (FONT_WIDTH * left_spaces); x += (display.FONT_WIDTH * left_spaces);
mw = try drawString(mw, text[@intCast(usize, left_spaces)..], x, y); mw = try drawString(mw, text[@intCast(usize, left_spaces)..], x, y);
} }
@ -415,12 +404,12 @@ fn convertImage(filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const f
if (status == c.MagickFalse) if (status == c.MagickFalse)
return error.CouldNotQuantizeImage; return error.CouldNotQuantizeImage;
status = c.MagickExportImagePixels(mw, 0, 0, WIDTH, HEIGHT, "I", c.CharPixel, @ptrCast(*anyopaque, pixels)); status = c.MagickExportImagePixels(mw, 0, 0, display.WIDTH, display.HEIGHT, "I", c.CharPixel, @ptrCast(*anyopaque, pixels));
if (status == c.MagickFalse) if (status == c.MagickFalse)
return error.CouldNotExportImage; return error.CouldNotExportImage;
for (0..WIDTH * HEIGHT) |i| { for (0..display.WIDTH * display.HEIGHT) |i| {
switch (pixels[i]) { switch (pixels[i]) {
0x00 => pixels[i] = 0xFF, 0x00 => pixels[i] = 0xFF,
0xFF => pixels[i] = 0x00, 0xFF => pixels[i] = 0x00,
@ -434,7 +423,7 @@ fn drawString(mw: ?*c.MagickWand, str: []const u8, x: isize, y: isize) !?*c.Magi
rc = try drawCharacter( rc = try drawCharacter(
rc, rc,
ch, ch,
-(x + @intCast(isize, FONT_WIDTH * i)), -(x + @intCast(isize, display.FONT_WIDTH * i)),
-y, -y,
); );
} }
@ -476,8 +465,8 @@ fn drawCharacter(mw: ?*c.MagickWand, char: u8, x: isize, y: isize) !?*c.MagickWa
// I think our characters are offset by 6px in the x and 8 in the y // I think our characters are offset by 6px in the x and 8 in the y
status = c.MagickExtentImage( status = c.MagickExtentImage(
cw, cw,
WIDTH, display.WIDTH,
HEIGHT, display.HEIGHT,
x, x,
y, y,
); );
@ -526,9 +515,9 @@ test "gets correct bytes" {
} }
const line: [:0]u8 = @constCast("Hello\\!"); const line: [:0]u8 = @constCast("Hello\\!");
lines[5] = &line; lines[5] = &line;
var pixels: [WIDTH * HEIGHT]u8 = undefined; var pixels: [display.WIDTH * display.HEIGHT]u8 = undefined;
var expected_pixels: *const [WIDTH * HEIGHT]u8 = @embedFile("testExpectedBytes.bin"); var expected_pixels: *const [display.WIDTH * display.HEIGHT]u8 = @embedFile("testExpectedBytes.bin");
// [_]u8{..,..,..} // [_]u8{..,..,..}
try convertImage(opts.background_filename, &pixels, textForLine); try convertImage(opts.background_filename, &pixels, textForLine);