Last active
July 3, 2025 20:09
-
-
Save MateusAquino/00afe2478b5f6eff5678f2357448fd7d to your computer and use it in GitHub Desktop.
OpenDrop - Inverted Text Renderer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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