Skip to content

Commit 0c613aa

Browse files
committed
System Touch
1 parent 398fa42 commit 0c613aa

1 file changed

Lines changed: 124 additions & 0 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package terminal;
2+
3+
import javax.imageio.ImageIO;
4+
import java.awt.*;
5+
import java.awt.image.BufferedImage;
6+
import java.io.File;
7+
8+
/**
9+
* TerminalImageRenderer
10+
*
11+
* Renders a JPEG/PNG/BMP image to a GNOME terminal using
12+
* Unicode half-block characters and 24-bit ANSI TrueColor.
13+
*
14+
* Usage:
15+
* javac TerminalImageRenderer.java
16+
* java TerminalImageRenderer path/to/image.png [width]
17+
*
18+
* If width is omitted, defaults to 80 characters.
19+
*/
20+
public class TerminalImageRenderer
21+
{
22+
23+
// ANSI escape sequences
24+
private static final String ESC = "\u001b[";
25+
private static final String RESET = ESC + "0m";
26+
27+
public static void main(String[] args) throws Exception
28+
{
29+
if (args.length < 1) {
30+
System.err.println("Usage: java TerminalImageRenderer <image> [width]");
31+
System.exit(1);
32+
}
33+
34+
String path = args[0];
35+
int targetWidth = (args.length >= 2) ? Integer.parseInt(args[1]) : 80;
36+
37+
BufferedImage img = ImageIO.read(new File(path));
38+
39+
if (img == null) {
40+
System.err.println("Could not read image: " + path);
41+
System.exit(1);
42+
}
43+
44+
BufferedImage scaled = scaleToWidth(img, targetWidth);
45+
renderHalfBlocksTrueColor(scaled);
46+
System.out.print(RESET);
47+
}
48+
49+
/**
50+
* Scale image to target terminal width, preserving aspect ratio.
51+
* Height is scaled accordingly; we later compress two vertical pixels
52+
* into one terminal row using half-block characters.
53+
*/
54+
private static BufferedImage scaleToWidth(BufferedImage src, int targetWidth)
55+
{
56+
int srcWidth = src.getWidth();
57+
int srcHeight = src.getHeight();
58+
59+
if (srcWidth <= targetWidth) {
60+
return src; // no scaling needed
61+
}
62+
63+
double scale = (double) targetWidth / srcWidth;
64+
int newWidth = targetWidth;
65+
int newHeight = (int) Math.round(srcHeight * scale);
66+
67+
BufferedImage dst = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
68+
Graphics2D g = dst.createGraphics();
69+
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
70+
g.drawImage(src, 0, 0, newWidth, newHeight, null);
71+
g.dispose();
72+
return dst;
73+
}
74+
75+
/**
76+
* Render using Unicode upper half-block (▀) with foreground color
77+
* for the top pixel and background color for the bottom pixel.
78+
*
79+
* Each terminal cell represents two vertical pixels:
80+
* - top pixel -> ANSI 38;2;r;g;b (foreground)
81+
* - bottom pixel -> ANSI 48;2;r;g;b (background)
82+
*/
83+
private static void renderHalfBlocksTrueColor(BufferedImage img)
84+
{
85+
int width = img.getWidth();
86+
int height = img.getHeight();
87+
88+
// Ensure even height for pairing top/bottom pixels
89+
if (height % 2 != 0) {
90+
height -= 1;
91+
}
92+
93+
StringBuilder sb = new StringBuilder();
94+
95+
for (int y = 0; y < height; y += 2) {
96+
for (int x = 0; x < width; x++) {
97+
int topRGB = img.getRGB(x, y);
98+
int bottomRGB = img.getRGB(x, y + 1);
99+
100+
Color top = new Color(topRGB);
101+
Color bottom = new Color(bottomRGB);
102+
103+
// ANSI TrueColor: foreground (top), background (bottom)
104+
sb.append(ESC)
105+
.append("38;2;")
106+
.append(top.getRed()).append(";")
107+
.append(top.getGreen()).append(";")
108+
.append(top.getBlue()).append("m");
109+
110+
sb.append(ESC)
111+
.append("48;2;")
112+
.append(bottom.getRed()).append(";")
113+
.append(bottom.getGreen()).append(";")
114+
.append(bottom.getBlue()).append("m");
115+
116+
// Upper half block character
117+
sb.append('▀');
118+
}
119+
sb.append(RESET).append('\n');
120+
}
121+
122+
System.out.print(sb.toString());
123+
}
124+
}

0 commit comments

Comments
 (0)