Compare commits
	
		
			10 commits
		
	
	
		
			60098caf01
			...
			26c52dd278
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 26c52dd278 | |||
| 5cd582b2e4 | |||
| 175899a539 | |||
| 7b30811ead | |||
| 5187ccffa2 | |||
| 853b5bdf5a | |||
| b04a8f53b2 | |||
| 1de48ff236 | |||
| 556e14532f | |||
| 7fa10612e7 | 
					 4 changed files with 132 additions and 30 deletions
				
			
		| 
						 | 
				
			
			@ -46,6 +46,10 @@ pub fn build(b: *std.build.Builder) !void {
 | 
			
		|||
    exe.addIncludePath("lib/i2cdriver");
 | 
			
		||||
    exe.install();
 | 
			
		||||
 | 
			
		||||
    // TODO: I believe we can use runArtifact on a second
 | 
			
		||||
    // exe with a different source file for font generation
 | 
			
		||||
    // taking us to a series of 5 byte arrays for each
 | 
			
		||||
    // character in a font.
 | 
			
		||||
    exe.step.dependOn(&AsciiPrintableStep.create(b, .{ .path = "src/images" }).step);
 | 
			
		||||
    // exe.step.dependOn((try fontGeneration(b, target)));
 | 
			
		||||
    const run_cmd = exe.run();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -177,7 +177,13 @@ int readFromSerialPort(int fd, uint8_t *b, size_t s)
 | 
			
		|||
 | 
			
		||||
void writeToSerialPort(int fd, const uint8_t *b, size_t s)
 | 
			
		||||
{
 | 
			
		||||
  write(fd, b, s);
 | 
			
		||||
  if (write(fd, b, s) == -1){
 | 
			
		||||
    printf("WRITE FAILED %u: ", (int)s);
 | 
			
		||||
    int i;
 | 
			
		||||
    for (i = 0; i < s; i++)
 | 
			
		||||
      printf("%02x ", 0xff & b[i]);
 | 
			
		||||
    printf("\n");
 | 
			
		||||
  }
 | 
			
		||||
#ifdef VERBOSE
 | 
			
		||||
  printf("WRITE %u: ", (int)s);
 | 
			
		||||
  int i;
 | 
			
		||||
| 
						 | 
				
			
			@ -526,7 +532,7 @@ int i2c_commands(I2CDriver *sd, int argc, char *argv[])
 | 
			
		|||
 | 
			
		||||
        i2c_monitor(sd, 1);
 | 
			
		||||
        printf("[Hit return to exit monitor mode]\n");
 | 
			
		||||
        fgets(line, sizeof(line) - 1, stdin);
 | 
			
		||||
        if (!fgets(line, sizeof(line) - 1, stdin)) return 1;
 | 
			
		||||
        i2c_monitor(sd, 0);
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										
											BIN
										
									
								
								src/images/blank.bmp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/images/blank.bmp
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										148
									
								
								src/main.zig
									
										
									
									
									
								
							
							
						
						
									
										148
									
								
								src/main.zig
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -17,15 +17,20 @@ const HEIGHT = 64;
 | 
			
		|||
const FONT_WIDTH = 5;
 | 
			
		||||
const FONT_HEIGHT = 8;
 | 
			
		||||
 | 
			
		||||
const CHARS_PER_LINE = 21; // 21 * 6 = 126 so we have 2px left over
 | 
			
		||||
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]*const [:0]u8 = undefined;
 | 
			
		||||
 | 
			
		||||
fn usage(args: [][]u8) !void {
 | 
			
		||||
    const stderr = std.io.getStdErr();
 | 
			
		||||
    try stderr.writer().print("usage: {s} <image file> <device>\n", .{args[0]}); // TODO: will need more
 | 
			
		||||
    const writer = std.io.getStdErr().writer();
 | 
			
		||||
    try writer.print("usage: {s} <device> [-bg <image file>] [-<num> text]...\n", .{args[0]});
 | 
			
		||||
    try writer.print("\t-<num> text: line number and text to display\n", .{});
 | 
			
		||||
    try writer.print("\te.g. \"-1 'hello world'\" will display \"hello world\" on line 1\n", .{});
 | 
			
		||||
    std.os.exit(1);
 | 
			
		||||
}
 | 
			
		||||
pub fn main() !void {
 | 
			
		||||
| 
						 | 
				
			
			@ -33,11 +38,11 @@ pub fn main() !void {
 | 
			
		|||
    //defer alloc.deinit();
 | 
			
		||||
    const args = try std.process.argsAlloc(alloc);
 | 
			
		||||
    defer std.process.argsFree(alloc, args);
 | 
			
		||||
    if (args.len < 3) try usage(args);
 | 
			
		||||
    if (args.len < 2) try usage(args);
 | 
			
		||||
    const prefix = "/dev/ttyUSB";
 | 
			
		||||
    const device = try alloc.dupeZ(u8, args[2]);
 | 
			
		||||
    const device = try alloc.dupeZ(u8, args[1]);
 | 
			
		||||
    defer alloc.free(device);
 | 
			
		||||
    if (!std.mem.startsWith(u8, device, prefix)) try usage(args);
 | 
			
		||||
    if (!std.mem.eql(u8, device, "-") and !std.mem.startsWith(u8, device, prefix)) try usage(args);
 | 
			
		||||
 | 
			
		||||
    // stdout is for the actual output of your application, for example if you
 | 
			
		||||
    // are implementing gzip, then only the compressed bytes should be sent to
 | 
			
		||||
| 
						 | 
				
			
			@ -47,11 +52,45 @@ pub fn main() !void {
 | 
			
		|||
    const stdout = bw.writer();
 | 
			
		||||
    defer bw.flush() catch unreachable; // don't forget to flush!
 | 
			
		||||
 | 
			
		||||
    const filename = args[1];
 | 
			
		||||
    try stdout.print("Converting {s}\n", .{filename});
 | 
			
		||||
    var filename: [:0]u8 = @constCast("");
 | 
			
		||||
    var b: [1]u8 = undefined;
 | 
			
		||||
    const nothing: [:0]u8 = try std.fmt.bufPrintZ(&b, "", .{});
 | 
			
		||||
    for (lines, 0..) |_, i| {
 | 
			
		||||
        lines[i] = ¬hing;
 | 
			
		||||
    }
 | 
			
		||||
    var is_filename = false;
 | 
			
		||||
    var line_number: ?usize = null;
 | 
			
		||||
    for (args, 0..) |arg, i| {
 | 
			
		||||
        if (std.mem.eql(u8, "-bg", arg)) {
 | 
			
		||||
            is_filename = true;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (is_filename) {
 | 
			
		||||
            filename = args[i]; // arg capture changes value...
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if ((arg[0] == '-' and arg.len > 1) and areDigits(arg[1..])) {
 | 
			
		||||
            line_number = try std.fmt.parseInt(usize, arg[1..], 10);
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (line_number) |line| {
 | 
			
		||||
            if (arg.len > CHARS_PER_LINE) {
 | 
			
		||||
                try std.io.getStdErr().writer().print(
 | 
			
		||||
                    "ERROR: text for line {d} has {d} chars, exceeding maximum length {d}\n",
 | 
			
		||||
                    .{ line, arg.len, CHARS_PER_LINE },
 | 
			
		||||
                );
 | 
			
		||||
                std.os.exit(1);
 | 
			
		||||
            }
 | 
			
		||||
            std.debug.print("line {d} text: \"{s}\"\n", .{ line, arg });
 | 
			
		||||
            lines[line] = &args[i];
 | 
			
		||||
            line_number = null;
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (filename.len > 0) try stdout.print("Converting {s}\n", .{filename});
 | 
			
		||||
    var pixels: [WIDTH * HEIGHT]u8 = undefined;
 | 
			
		||||
    try convertImage(alloc, filename, &pixels);
 | 
			
		||||
    // try convertImage(alloc, filename, &pixels);
 | 
			
		||||
    try convertImage(alloc, filename, &pixels, textForLine);
 | 
			
		||||
    try stdout.print("Sending pixels to display\n", .{});
 | 
			
		||||
    // var i: usize = 0;
 | 
			
		||||
    // while (i < HEIGHT) {
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +107,17 @@ pub fn main() !void {
 | 
			
		|||
    // try stdout.print("Run `zig build test` to run the tests.\n", .{});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn areDigits(bytes: []u8) bool {
 | 
			
		||||
    for (bytes) |byte| {
 | 
			
		||||
        if (!std.ascii.isDigit(byte)) return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void {
 | 
			
		||||
    if (std.mem.eql(u8, file, "-"))
 | 
			
		||||
        return sendPixelsToStdOut(pixels);
 | 
			
		||||
 | 
			
		||||
    if (@import("builtin").os.tag != .linux)
 | 
			
		||||
        @compileError("Linux only please!");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,6 +129,16 @@ fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void {
 | 
			
		|||
    return error.LinuxNativeNotImplemented;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn sendPixelsToStdOut(pixels: []const u8) !void {
 | 
			
		||||
    const stdout_file = std.io.getStdOut().writer();
 | 
			
		||||
    var bw = std.io.bufferedWriter(stdout_file);
 | 
			
		||||
    const stdout = bw.writer();
 | 
			
		||||
    defer bw.flush() catch unreachable; // don't forget to flush!
 | 
			
		||||
    for (0..HEIGHT) |i| {
 | 
			
		||||
        try stdout.print("{d:0>2}: {s}\n", .{ i, fmtSliceGreyscaleImage(pixels[(i * WIDTH)..((i + 1) * WIDTH)]) });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn sendPixelsThroughI2CDriver(pixels: []const u8, file: [*:0]const u8, device_id: u8) !void {
 | 
			
		||||
    var pixels_write_command = [_]u8{0x00} ** ((WIDTH * PAGES) + 1);
 | 
			
		||||
    pixels_write_command[0] = 0x40;
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +200,7 @@ fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
 | 
			
		|||
        const column = i % WIDTH;
 | 
			
		||||
        const page = i / 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
 | 
			
		||||
        // the device. So the device column above is only our starter
 | 
			
		||||
        // Display has 8 pages, which is a set of 8 pixels with LSB at top of page
 | 
			
		||||
| 
						 | 
				
			
			@ -172,8 +231,8 @@ fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
 | 
			
		|||
            (pixels[(6 + row) * WIDTH + column] & 0x01) << 6 |
 | 
			
		||||
            (pixels[(7 + row) * WIDTH + column] & 0x01) << 7;
 | 
			
		||||
 | 
			
		||||
        std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})});
 | 
			
		||||
        if (column == 127) std.debug.print("\n", .{});
 | 
			
		||||
        // std.debug.print("{s}", .{std.fmt.fmtSliceHexLower(&[_]u8{b.*})});
 | 
			
		||||
        // if (column == 127) std.debug.print("\n", .{});
 | 
			
		||||
 | 
			
		||||
        // Last 2 pages are yellow...16 pixels vertical
 | 
			
		||||
        // if (page == 6 or page == 7) b.* = 0xff;
 | 
			
		||||
| 
						 | 
				
			
			@ -212,7 +271,10 @@ fn reportMagickError(mw: ?*c.MagickWand) !void {
 | 
			
		|||
    defer description = @ptrCast([*c]u8, c.MagickRelinquishMemory(description));
 | 
			
		||||
    try std.io.getStdErr().writer().print("{s}\n", .{description});
 | 
			
		||||
}
 | 
			
		||||
fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8) !void {
 | 
			
		||||
fn textForLine(line: usize) []u8 {
 | 
			
		||||
    return lines[line].*;
 | 
			
		||||
}
 | 
			
		||||
fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HEIGHT]u8, text_fn: *const fn (usize) []u8) !void {
 | 
			
		||||
    _ = alloc;
 | 
			
		||||
    c.MagickWandGenesis();
 | 
			
		||||
    defer c.MagickWandTerminus();
 | 
			
		||||
| 
						 | 
				
			
			@ -223,8 +285,19 @@ fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HE
 | 
			
		|||
 | 
			
		||||
    // Reading an image into ImageMagick is problematic if it isn't a bmp
 | 
			
		||||
    // as the library needs a bunch of dependencies available
 | 
			
		||||
    // var status = c.MagickReadImage(mw, "logo:");
 | 
			
		||||
    var status = c.MagickReadImage(mw, filename);
 | 
			
		||||
    var status: c.MagickBooleanType = undefined;
 | 
			
		||||
    if (filename.len > 0) {
 | 
			
		||||
        status = c.MagickReadImage(mw, filename);
 | 
			
		||||
    } else {
 | 
			
		||||
        // TODO: if there is no background image AND
 | 
			
		||||
        // we precompute monochrome bit patterns for our font
 | 
			
		||||
        // we can completely avoid ImageMagick here. Even with
 | 
			
		||||
        // a background we can do the conversion, then do our
 | 
			
		||||
        // own text overlay after monochrome conversion.
 | 
			
		||||
        // Faster and smaller binary (maybe multi-font support?)
 | 
			
		||||
        const blob = @embedFile("images/blank.bmp");
 | 
			
		||||
        status = c.MagickReadImageBlob(mw, blob, blob.len);
 | 
			
		||||
    }
 | 
			
		||||
    if (status == c.MagickFalse) {
 | 
			
		||||
        if (!std.mem.eql(u8, filename[filename.len - 3 ..], "bmp"))
 | 
			
		||||
            try std.io.getStdErr().writer().print("File is not .bmp. That is probably the problem\n", .{});
 | 
			
		||||
| 
						 | 
				
			
			@ -275,18 +348,25 @@ fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HE
 | 
			
		|||
    if (status == c.MagickFalse)
 | 
			
		||||
        return error.CouldNotSetExtent;
 | 
			
		||||
 | 
			
		||||
    mw = try drawCharacter(
 | 
			
		||||
        mw.?,
 | 
			
		||||
        '4',
 | 
			
		||||
        -5 * 3,
 | 
			
		||||
        -8,
 | 
			
		||||
    );
 | 
			
		||||
    mw = try drawCharacter(
 | 
			
		||||
        mw.?,
 | 
			
		||||
        '2',
 | 
			
		||||
        -5 * 4,
 | 
			
		||||
        -8,
 | 
			
		||||
    );
 | 
			
		||||
    for (0..LINES) |i| {
 | 
			
		||||
        const text = text_fn(i);
 | 
			
		||||
        if (text.len == 0) continue;
 | 
			
		||||
        // We have text!
 | 
			
		||||
        const y: isize = FONT_HEIGHT * @intCast(isize, i);
 | 
			
		||||
        var x: isize = BORDER_LEFT;
 | 
			
		||||
        var left_spaces: isize = 0;
 | 
			
		||||
        for (text) |ch| {
 | 
			
		||||
            if (ch == ' ') {
 | 
			
		||||
                left_spaces += 1;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        std.debug.print("left_spaces: {d}. Text: \"{s}\"\n", .{ left_spaces, text });
 | 
			
		||||
        x += (FONT_WIDTH * left_spaces);
 | 
			
		||||
        mw = try drawString(mw, text[@intCast(usize, left_spaces)..], x, y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We make the image monochrome by quantizing the image with 2 colors in the
 | 
			
		||||
    // gray colorspace. See:
 | 
			
		||||
    // https://www.imagemagick.org/Usage/quantize/#monochrome
 | 
			
		||||
| 
						 | 
				
			
			@ -319,6 +399,18 @@ fn convertImage(alloc: std.mem.Allocator, filename: [:0]u8, pixels: *[WIDTH * HE
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
fn drawString(mw: ?*c.MagickWand, str: []const u8, x: isize, y: isize) !?*c.MagickWand {
 | 
			
		||||
    var rc = mw;
 | 
			
		||||
    for (str, 0..) |ch, i| {
 | 
			
		||||
        rc = try drawCharacter(
 | 
			
		||||
            rc,
 | 
			
		||||
            ch,
 | 
			
		||||
            -(x + @intCast(isize, FONT_WIDTH * i)),
 | 
			
		||||
            -y,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    return rc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn drawCharacter(mw: ?*c.MagickWand, char: u8, x: isize, y: isize) !?*c.MagickWand {
 | 
			
		||||
    // Create a second wand. Does this need to exist after the block?
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue