Compare commits
10 Commits
60098caf01
...
26c52dd278
Author | SHA1 | Date | |
---|---|---|---|
26c52dd278 | |||
5cd582b2e4 | |||
175899a539 | |||
7b30811ead | |||
5187ccffa2 | |||
853b5bdf5a | |||
b04a8f53b2 | |||
1de48ff236 | |||
556e14532f | |||
7fa10612e7 |
|
@ -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…
Reference in New Issue
Block a user