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.addIncludePath("lib/i2cdriver");
|
||||||
exe.install();
|
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(&AsciiPrintableStep.create(b, .{ .path = "src/images" }).step);
|
||||||
// exe.step.dependOn((try fontGeneration(b, target)));
|
// exe.step.dependOn((try fontGeneration(b, target)));
|
||||||
const run_cmd = exe.run();
|
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)
|
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
|
#ifdef VERBOSE
|
||||||
printf("WRITE %u: ", (int)s);
|
printf("WRITE %u: ", (int)s);
|
||||||
int i;
|
int i;
|
||||||
|
@ -526,7 +532,7 @@ int i2c_commands(I2CDriver *sd, int argc, char *argv[])
|
||||||
|
|
||||||
i2c_monitor(sd, 1);
|
i2c_monitor(sd, 1);
|
||||||
printf("[Hit return to exit monitor mode]\n");
|
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);
|
i2c_monitor(sd, 0);
|
||||||
}
|
}
|
||||||
break;
|
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_WIDTH = 5;
|
||||||
const FONT_HEIGHT = 8;
|
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;
|
const LINES = 8;
|
||||||
|
|
||||||
// Device specifications
|
// Device specifications
|
||||||
const PAGES = 8;
|
const PAGES = 8;
|
||||||
|
|
||||||
|
var lines: [LINES]*const [:0]u8 = undefined;
|
||||||
|
|
||||||
fn usage(args: [][]u8) !void {
|
fn usage(args: [][]u8) !void {
|
||||||
const stderr = std.io.getStdErr();
|
const writer = std.io.getStdErr().writer();
|
||||||
try stderr.writer().print("usage: {s} <image file> <device>\n", .{args[0]}); // TODO: will need more
|
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);
|
std.os.exit(1);
|
||||||
}
|
}
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
@ -33,11 +38,11 @@ pub fn main() !void {
|
||||||
//defer alloc.deinit();
|
//defer alloc.deinit();
|
||||||
const args = try std.process.argsAlloc(alloc);
|
const args = try std.process.argsAlloc(alloc);
|
||||||
defer std.process.argsFree(alloc, args);
|
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 prefix = "/dev/ttyUSB";
|
||||||
const device = try alloc.dupeZ(u8, args[2]);
|
const device = try alloc.dupeZ(u8, args[1]);
|
||||||
defer alloc.free(device);
|
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
|
// 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
|
// are implementing gzip, then only the compressed bytes should be sent to
|
||||||
|
@ -47,11 +52,45 @@ pub fn main() !void {
|
||||||
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!
|
||||||
|
|
||||||
const filename = args[1];
|
var filename: [:0]u8 = @constCast("");
|
||||||
try stdout.print("Converting {s}\n", .{filename});
|
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;
|
var pixels: [WIDTH * HEIGHT]u8 = undefined;
|
||||||
try convertImage(alloc, filename, &pixels);
|
try convertImage(alloc, filename, &pixels, textForLine);
|
||||||
// try convertImage(alloc, filename, &pixels);
|
|
||||||
try stdout.print("Sending pixels to display\n", .{});
|
try stdout.print("Sending pixels to display\n", .{});
|
||||||
// var i: usize = 0;
|
// var i: usize = 0;
|
||||||
// while (i < HEIGHT) {
|
// while (i < HEIGHT) {
|
||||||
|
@ -68,7 +107,17 @@ 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 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 {
|
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)
|
if (@import("builtin").os.tag != .linux)
|
||||||
@compileError("Linux only please!");
|
@compileError("Linux only please!");
|
||||||
|
|
||||||
|
@ -80,6 +129,16 @@ fn sendPixels(pixels: []const u8, file: [:0]const u8, device_id: u8) !void {
|
||||||
return error.LinuxNativeNotImplemented;
|
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 {
|
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} ** ((WIDTH * PAGES) + 1);
|
||||||
pixels_write_command[0] = 0x40;
|
pixels_write_command[0] = 0x40;
|
||||||
|
@ -141,7 +200,7 @@ fn packPixelsToDeviceFormat(pixels: []const u8, packed_pixels: []u8) void {
|
||||||
const column = i % WIDTH;
|
const column = i % WIDTH;
|
||||||
const page = 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
|
// 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
|
// 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
|
// 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[(6 + row) * WIDTH + column] & 0x01) << 6 |
|
||||||
(pixels[(7 + row) * WIDTH + column] & 0x01) << 7;
|
(pixels[(7 + row) * 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", .{});
|
||||||
|
|
||||||
// Last 2 pages are yellow...16 pixels vertical
|
// Last 2 pages are yellow...16 pixels vertical
|
||||||
// if (page == 6 or page == 7) b.* = 0xff;
|
// 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));
|
defer description = @ptrCast([*c]u8, c.MagickRelinquishMemory(description));
|
||||||
try std.io.getStdErr().writer().print("{s}\n", .{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;
|
_ = alloc;
|
||||||
c.MagickWandGenesis();
|
c.MagickWandGenesis();
|
||||||
defer c.MagickWandTerminus();
|
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
|
// Reading an image into ImageMagick is problematic if it isn't a bmp
|
||||||
// as the library needs a bunch of dependencies available
|
// as the library needs a bunch of dependencies available
|
||||||
// var status = c.MagickReadImage(mw, "logo:");
|
var status: c.MagickBooleanType = undefined;
|
||||||
var status = c.MagickReadImage(mw, filename);
|
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 (status == c.MagickFalse) {
|
||||||
if (!std.mem.eql(u8, filename[filename.len - 3 ..], "bmp"))
|
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", .{});
|
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)
|
if (status == c.MagickFalse)
|
||||||
return error.CouldNotSetExtent;
|
return error.CouldNotSetExtent;
|
||||||
|
|
||||||
mw = try drawCharacter(
|
for (0..LINES) |i| {
|
||||||
mw.?,
|
const text = text_fn(i);
|
||||||
'4',
|
if (text.len == 0) continue;
|
||||||
-5 * 3,
|
// We have text!
|
||||||
-8,
|
const y: isize = FONT_HEIGHT * @intCast(isize, i);
|
||||||
);
|
var x: isize = BORDER_LEFT;
|
||||||
mw = try drawCharacter(
|
var left_spaces: isize = 0;
|
||||||
mw.?,
|
for (text) |ch| {
|
||||||
'2',
|
if (ch == ' ') {
|
||||||
-5 * 4,
|
left_spaces += 1;
|
||||||
-8,
|
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
|
// We make the image monochrome by quantizing the image with 2 colors in the
|
||||||
// gray colorspace. See:
|
// gray colorspace. See:
|
||||||
// https://www.imagemagick.org/Usage/quantize/#monochrome
|
// 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 {
|
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?
|
// Create a second wand. Does this need to exist after the block?
|
||||||
|
|
Loading…
Reference in New Issue
Block a user