Skip to content

Instantly share code, notes, and snippets.

@MateusAquino
Last active July 3, 2025 20:09
Show Gist options
  • Save MateusAquino/00afe2478b5f6eff5678f2357448fd7d to your computer and use it in GitHub Desktop.
Save MateusAquino/00afe2478b5f6eff5678f2357448fd7d to your computer and use it in GitHub Desktop.
OpenDrop - Inverted Text Renderer
import processing.serial.*;
import static javax.swing.JOptionPane.*;
Serial myPort;
// Configurable params
// i.e.: final String displayText = "Hello World ";
final String displayText = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
final int scrollInterval = 200;
final boolean WARN_RESERVOIR = true;
final boolean UI_MODE = true;
// Grid dimensions
final int GRID_WIDTH = 14;
final int GRID_HEIGHT = 8;
final int WINDOW_WIDTH = 700;
final int WINDOW_HEIGHT= 400;
// Electrode state
boolean[][] electrodes = new boolean[GRID_WIDTH][GRID_HEIGHT];
// Scrolling text state
int lastScrollTime;
int scrollOffset; // current column index into buffer
int charHeight = 7; // 6 + extra bottom line
int[][][] fontMap; // fontMap[char] → 3×6 pattern
void setup() {
noLoop(); // delay draw until after setup
windowResize(WINDOW_WIDTH, WINDOW_HEIGHT);
// —— serial init ——
String[] ports = Serial.list();
if (ports == null || ports.length == 0) {
showMessageDialog(null,
"No serial ports found. Connect your device and retry.",
"Serial Port Error", ERROR_MESSAGE);
exit();
}
myPort = new Serial(this, ports[0], 115200);
delay(100);
// —— initial board fill ——
initialTransmit();
if (WARN_RESERVOIR) showMessageDialog(null,
"Fill the board (except reservoir) with liquid *now*, then press OK.",
"Initial setup", PLAIN_MESSAGE);
// —— text font setup ——
buildFontMap();
// —— start scrolling ——
lastScrollTime = millis();
scrollOffset = 0;
// —— kickoff ——
if (UI_MODE) windowResize(WINDOW_WIDTH, WINDOW_HEIGHT);
frameRate(60);
loop();
}
void draw() {
if (millis() - lastScrollTime > scrollInterval) {
scrollOffset = (scrollOffset + 1) % totalColumns();
lastScrollTime = millis();
}
// render into electrodes then send + draw
drawBoard();
transmit();
if (UI_MODE) renderGrid();
}
// ————————————— text rendering —————————————————
void drawBoard() {
// clear all ON
for (int x = 0; x < GRID_WIDTH; x++)
for (int y = 0; y < GRID_HEIGHT; y++)
electrodes[x][y] = true;
int cols = totalColumns();
for (int gx = 0; gx < GRID_WIDTH; gx++) {
int bufCol = (scrollOffset + gx) % cols;
// walk through displayText to find which char this column belongs to
int running = 0;
char c = 0;
int colInChar = -1;
for (int i = 0; i < displayText.length(); i++) {
char tc = displayText.charAt(i);
int idx = (int)tc;
int[][] charData = fontMap[idx];
if (charData != null) {
int w = charData.length;
if (bufCol < running + w) {
c = tc;
colInChar = bufCol - running;
break;
}
running += w;
}
// one‐column spacing
if (bufCol < running + 1) {
c = 0;
break;
}
running += 1;
}
// if it’s part of a real character, render its pixels
if (c != 0 && colInChar >= 0) {
int idx = (int)c;
int[][] charData = fontMap[idx];
if (charData != null && colInChar < charData.length) {
for (int row = 0; row < charHeight; row++) {
boolean on = (charData[colInChar][row] == 1);
int gy = row + (GRID_HEIGHT - charHeight)/2 + 1;
electrodes[gx][gy] = !on;
}
}
}
}
}
int totalColumns() {
int sum = 0;
for (int i = 0; i < displayText.length(); i++) {
int idx = (int)displayText.charAt(i);
if (fontMap[idx] != null) {
sum += fontMap[idx].length; // character width
}
sum += 1; // spacing
}
return sum;
}
// ————————————— serial I/O —————————————————
void initialTransmit() {
for (int x = 0; x < GRID_WIDTH; x++)
for (int y = 0; y < GRID_HEIGHT; y++)
electrodes[x][y] = true;
byte[] packet = new byte[32];
packet[0] = 0;
for (int x = 0; x < GRID_WIDTH; x++) {
int b = 0;
for (int y = 0; y < GRID_HEIGHT; y++)
if (electrodes[x][y])
b |= 1<<y;
packet[x+1] = (byte)b;
}
packet[GRID_WIDTH+1] = 0;
for (byte pb : packet) myPort.write(pb);
}
void transmit() {
byte[] packet = new byte[32];
packet[0] = (byte)0xFF;
for (int x = 0; x < GRID_WIDTH; x++) {
int b = 0;
for (int y = 0; y < GRID_HEIGHT; y++)
if (electrodes[x][y])
b |= 1<<y;
packet[x+1] = (byte)b;
}
packet[GRID_WIDTH+1] = (byte)0xFF;
for (byte pb : packet) myPort.write(pb);
}
// ————————————— UI rendering —————————————————
void renderGrid() {
int cellW = width / GRID_WIDTH;
int cellH = height / GRID_HEIGHT;
stroke(80);
for (int x = 0; x < GRID_WIDTH; x++) {
for (int y = 0; y < GRID_HEIGHT; y++) {
fill(electrodes[x][y] ? color(0,200,0) : 50);
rect(x*cellW, y*cellH, cellW-2, cellH-2);
}
}
}
// ————————————— font definition —————————————————
void buildFontMap() {
fontMap = new int[128][][];
fontMap['A'] = new int[][] {
{0,1,1,1,1,1,0},
{1,0,0,1,0,0,0},
{0,1,1,1,1,1,0}
};
fontMap['a'] = new int[][] {
{0,0,1,0,1,1,0},
{0,0,1,0,1,1,0},
{0,0,0,1,1,1,0}
};
fontMap['B'] = new int[][] {
{1,1,1,1,1,1,0},
{1,0,1,1,0,1,0},
{0,1,0,0,1,1,0}
};
fontMap['b'] = new int[][] {
{0,1,1,1,1,1,0},
{0,0,0,1,0,1,0},
{0,0,0,0,1,0,0}
};
fontMap['C'] = new int[][] {
{0,1,1,1,1,0,0},
{1,0,0,0,0,1,0},
{1,0,0,0,0,1,0}
};
fontMap['c'] = new int[][] {
{0,0,0,1,1,0,0},
{0,0,1,0,0,1,0},
{0,0,1,0,0,1,0}
};
fontMap['D'] = new int[][] {
{1,1,1,1,1,1,0},
{1,0,0,0,0,1,0},
{0,1,1,1,1,0,0}
};
fontMap['d'] = new int[][] {
{0,0,0,0,1,0,0},
{0,0,0,1,0,1,0},
{0,1,1,1,1,1,0}
};
fontMap['E'] = new int[][] {
{1,1,1,1,1,1,0},
{1,0,1,0,0,1,0},
{1,0,1,0,0,1,0}
};
fontMap['e'] = new int[][] {
{0,0,1,1,1,1,0},
{0,0,1,1,0,1,0},
{0,0,1,0,0,1,0}
};
fontMap['F'] = new int[][] {
{1,1,1,1,1,1,0},
{1,0,1,0,0,0,0},
{1,0,1,0,0,0,0}
};
fontMap['f'] = new int[][] {
{0,0,1,1,1,1,0},
{0,1,0,1,0,0,0},
{0,1,0,0,0,0,0}
};
fontMap['G'] = new int[][] {
{0,1,1,1,1,1,0},
{1,0,0,0,0,1,0},
{1,0,0,1,1,1,0}
};
fontMap['g'] = new int[][] {
{0,0,0,1,1,0,1},
{0,0,1,0,1,0,1},
{0,0,0,1,1,1,1}
};
fontMap['H'] = new int[][] {
{1,1,1,1,1,1,0},
{0,0,1,1,0,0,0},
{1,1,1,1,1,1,0}
};
fontMap['h'] = new int[][] {
{0,1,1,1,1,1,0},
{0,0,0,1,0,0,0},
{0,0,0,0,1,1,0}
};
fontMap['I'] = new int[][] {
{1,0,0,0,0,1,0},
{1,1,1,1,1,1,0},
{1,0,0,0,0,1,0}
};
fontMap['i'] = new int[][] {
{0,1,0,1,1,1,0},
};
fontMap['J'] = new int[][] {
{0,0,0,0,1,0,0},
{0,0,0,0,0,1,0},
{1,1,1,1,1,0,0}
};
fontMap['j'] = new int[][] {
{0,0,0,0,0,1,0},
{0,0,0,0,0,0,1},
{0,1,0,1,1,1,0}
};
fontMap['K'] = new int[][] {
{1,1,1,1,1,1,0},
{0,0,1,1,0,0,0},
{1,1,0,0,1,1,0}
};
fontMap['k'] = new int[][] {
{0,1,1,1,1,1,0},
{0,0,0,0,1,0,0},
{0,0,0,1,0,1,0}
};
fontMap['L'] = new int[][] {
{1,1,1,1,1,1,0},
{0,0,0,0,0,1,0},
{0,0,0,0,0,1,0}
};
fontMap['l'] = new int[][] {
{0,1,1,1,1,1,0},
};
fontMap['M'] = new int[][] {
{1,1,1,1,1,1,0},
{0,1,0,0,0,0,0},
{0,0,1,1,0,0,0},
{0,1,0,0,0,0,0},
{1,1,1,1,1,1,0}
};
fontMap['m'] = new int[][] {
{0,0,1,1,1,1,0},
{0,0,1,0,0,0,0},
{0,0,0,1,1,1,0},
{0,0,1,0,0,0,0},
{0,0,0,1,1,1,0}
};
fontMap['N'] = new int[][] {
{1,1,1,1,1,1,0},
{0,1,1,0,0,0,0},
{0,0,0,1,1,0,0},
{1,1,1,1,1,1,0}
};
fontMap['n'] = new int[][] {
{0,0,1,1,1,1,0},
{0,0,1,0,0,0,0},
{0,0,0,1,1,1,0}
};
fontMap['O'] = new int[][] {
{0,1,1,1,1,0,0},
{1,0,0,0,0,1,0},
{0,1,1,1,1,0,0}
};
fontMap['o'] = new int[][] {
{0,0,0,1,1,0,0},
{0,0,1,0,0,1,0},
{0,0,0,1,1,0,0}
};
fontMap['P'] = new int[][] {
{1,1,1,1,1,1,0},
{1,0,1,0,0,0,0},
{0,1,0,0,0,0,0}
};
fontMap['p'] = new int[][] {
{0,0,1,1,1,1,1},
{0,0,1,0,1,0,0},
{0,0,0,1,0,0,0}
};
fontMap['Q'] = new int[][] {
{0,1,1,1,1,0,0},
{1,0,0,0,0,1,0},
{0,1,1,1,1,0,1}
};
fontMap['q'] = new int[][] {
{0,0,0,1,0,0,0},
{0,0,1,0,1,0,0},
{0,0,1,1,1,1,1}
};
fontMap['R'] = new int[][] {
{1,1,1,1,1,1,0},
{1,0,1,0,0,0,0},
{0,1,0,1,1,1,0}
};
fontMap['r'] = new int[][] {
{0,0,1,1,1,1,0},
{0,0,0,1,0,0,0},
{0,0,1,0,0,0,0}
};
fontMap['S'] = new int[][] {
{0,1,1,0,0,1,0},
{1,0,0,1,0,1,0},
{0,1,0,0,1,0,0}
};
fontMap['s'] = new int[][] {
{0,0,1,1,0,1,0},
{0,0,1,1,0,1,0},
{0,0,1,0,1,1,0},
{0,0,1,0,1,1,0}
};
fontMap['T'] = new int[][] {
{1,0,0,0,0,0,0},
{1,1,1,1,1,1,0},
{1,0,0,0,0,0,0}
};
fontMap['t'] = new int[][] {
{0,1,1,1,1,0,0},
{0,0,1,0,0,1,0},
{0,0,0,0,0,1,0}
};
fontMap['U'] = new int[][] {
{1,1,1,1,1,1,0},
{0,0,0,0,0,1,0},
{1,1,1,1,1,1,0}
};
fontMap['u'] = new int[][] {
{0,0,1,1,1,1,0},
{0,0,0,0,0,1,0},
{0,0,1,1,1,1,0}
};
fontMap['V'] = new int[][] {
{1,1,1,1,1,0,0},
{0,0,0,0,0,1,0},
{1,1,1,1,1,0,0}
};
fontMap['v'] = new int[][] {
{0,0,1,1,1,0,0},
{0,0,0,0,0,1,0},
{0,0,1,1,1,0,0}
};
fontMap['W'] = new int[][] {
{1,1,1,1,1,0,0},
{0,0,0,0,0,1,0},
{0,0,0,1,1,0,0},
{0,0,0,0,0,1,0},
{1,1,1,1,1,0,0}
};
fontMap['w'] = new int[][] {
{0,0,1,1,1,0,0},
{0,0,0,0,0,1,0},
{0,0,0,1,1,0,0},
{0,0,0,0,0,1,0},
{0,0,1,1,1,0,0}
};
fontMap['X'] = new int[][] {
{1,1,0,0,1,1,0},
{0,0,1,1,0,0,0},
{1,1,0,0,1,1,0}
};
fontMap['x'] = new int[][] {
{0,0,1,0,0,1,0},
{0,0,0,1,1,0,0},
{0,0,1,0,0,1,0}
};
fontMap['Y'] = new int[][] {
{1,1,1,0,0,0,0},
{0,0,0,1,1,1,0},
{1,1,1,0,0,0,0}
};
fontMap['y'] = new int[][] {
{0,0,1,1,0,0,1},
{0,0,0,0,1,1,0},
{0,0,1,1,0,0,0}
};
fontMap['Z'] = new int[][] {
{1,0,0,0,1,1,0},
{1,0,1,1,0,1,0},
{1,1,0,0,0,1,0}
};
fontMap['z'] = new int[][] {
{0,0,1,0,0,1,0},
{0,0,1,0,1,1,0},
{0,0,1,1,0,1,0},
{0,0,1,0,0,1,0}
};
fontMap[' '] = new int[][] {
{0,0,0,0,0,0,0},
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment