Skip to content

Commit 43a2f97

Browse files
committed
feat: added image support
1 parent b4df42f commit 43a2f97

17 files changed

Lines changed: 2503 additions & 1 deletion

File tree

examples/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
<version>1.0-SNAPSHOT</version>
3030
<scope>compile</scope>
3131
</dependency>
32+
<dependency>
33+
<groupId>org.codejive.twinkle</groupId>
34+
<artifactId>twinkle-image</artifactId>
35+
<version>1.0-SNAPSHOT</version>
36+
<scope>compile</scope>
37+
</dependency>
3238
<dependency>
3339
<groupId>com.github.lalyos</groupId>
3440
<artifactId>jfiglet</artifactId>
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package org.codejive.twinkle.demos;
2+
3+
// spotless:off
4+
//DEPS org.codejive.twinkle:twinkle-terminal-aesh:1.0-SNAPSHOT
5+
//DEPS org.codejive.twinkle:twinkle-image:1.0-SNAPSHOT
6+
// spotless:on
7+
8+
import java.awt.Color;
9+
import java.awt.Graphics2D;
10+
import java.awt.image.BufferedImage;
11+
import java.io.IOException;
12+
import java.io.PrintWriter;
13+
import java.util.List;
14+
import org.codejive.twinkle.image.ImageEncoder;
15+
import org.codejive.twinkle.image.ImageEncoders;
16+
import org.codejive.twinkle.terminal.Terminal;
17+
18+
/**
19+
* Demo application showing how to use the terminal image encoding framework.
20+
*
21+
* <p>This example demonstrates rendering images to the terminal using different encoders (Sixel,
22+
* Kitty, iTerm2, and block-based Unicode rendering).
23+
*
24+
* <p>Usage: {@code ImageEncoderDemo [--encoder=<name>] [--all]}
25+
*
26+
* <p>Supported encoder names: sixel, kitty, iterm2, block-full, block-half, block-quadrant,
27+
* block-sextant, block-octant.
28+
*/
29+
public class ImageEncoderDemo {
30+
31+
public static void main(String[] args) throws Exception {
32+
try (Terminal terminal = Terminal.getDefault()) {
33+
PrintWriter writer = terminal.writer();
34+
35+
// Create a simple test image
36+
BufferedImage testImage = createTestImage(200, 150);
37+
38+
// Define target size in terminal rows/columns
39+
int targetWidth = 20; // 20 columns wide
40+
int targetHeight = 10; // 10 rows tall
41+
42+
writer.println("=== Image Encoder Demo ===");
43+
44+
boolean fitImage = true;
45+
46+
String encoderName = getEncoderArg(args);
47+
48+
if (encoderName != null) {
49+
// Use a specific encoder requested via --encoder=
50+
ImageEncoder.Provider provider = findProvider(encoderName);
51+
if (provider == null) {
52+
writer.println("Unknown encoder: " + encoderName);
53+
writer.println(
54+
"Available: sixel, kitty, iterm2, block-full, block-half,"
55+
+ " block-quadrant, block-sextant, block-octant");
56+
writer.flush();
57+
return;
58+
}
59+
writer.println("Using encoder: " + provider.name());
60+
writer.println();
61+
writer.println("Rendering with " + provider.name() + " encoder:");
62+
writer.flush();
63+
renderImage(
64+
provider.create(testImage, targetWidth, targetHeight, fitImage), writer);
65+
writer.println("\n");
66+
} else {
67+
// Detect the best encoder for the current terminal
68+
ImageEncoder.Provider bestProvider = ImageEncoders.best();
69+
ImageEncoder detectedEncoder =
70+
bestProvider.create(testImage, targetWidth, targetHeight, fitImage);
71+
writer.println("Detected encoder: " + bestProvider.name());
72+
writer.println();
73+
74+
// Try rendering with the detected encoder
75+
writer.println("Rendering with " + bestProvider.name() + " encoder:");
76+
writer.flush();
77+
renderImage(detectedEncoder, writer);
78+
writer.println("\n");
79+
80+
// Optionally try all available encoders
81+
if (shouldTestAllEncoders(args)) {
82+
writer.println("\n--- Testing all encoders ---\n");
83+
84+
for (ImageEncoder.Provider provider : ImageEncoders.providers()) {
85+
testEncoder(
86+
provider.name(),
87+
provider.create(testImage, targetWidth, targetHeight, fitImage),
88+
writer);
89+
}
90+
}
91+
}
92+
93+
writer.println("\nDemo complete!");
94+
writer.flush();
95+
}
96+
}
97+
98+
private static String getEncoderArg(String[] args) {
99+
for (String arg : args) {
100+
if (arg.startsWith("--encoder=")) {
101+
return arg.substring("--encoder=".length());
102+
}
103+
}
104+
return null;
105+
}
106+
107+
private static ImageEncoder.Provider findProvider(String name) {
108+
String normalized = name.toLowerCase();
109+
List<ImageEncoder.Provider> all = ImageEncoders.providers();
110+
for (ImageEncoder.Provider provider : all) {
111+
String providerKey = provider.name().toLowerCase();
112+
if (providerKey.equals(normalized)) {
113+
return provider;
114+
}
115+
}
116+
return null;
117+
}
118+
119+
private static void testEncoder(String name, ImageEncoder encoder, PrintWriter writer)
120+
throws IOException {
121+
writer.println(name + " encoder:");
122+
writer.flush();
123+
renderImage(encoder, writer);
124+
writer.println("\n");
125+
}
126+
127+
private static void renderImage(ImageEncoder encoder, Appendable output) throws IOException {
128+
encoder.render(output);
129+
}
130+
131+
/**
132+
* Creates a simple test image with a gradient and some shapes.
133+
*
134+
* @param width the image width
135+
* @param height the image height
136+
* @return the created test image
137+
*/
138+
private static BufferedImage createTestImage(int width, int height) {
139+
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
140+
Graphics2D g = image.createGraphics();
141+
142+
// Draw gradient background
143+
for (int y = 0; y < height; y++) {
144+
float hue = (float) y / height;
145+
Color color = Color.getHSBColor(hue, 0.8f, 0.9f);
146+
g.setColor(color);
147+
g.fillRect(0, y, width, 1);
148+
}
149+
150+
// Draw some shapes
151+
g.setColor(Color.WHITE);
152+
g.fillOval(width / 4, height / 4, width / 2, height / 2);
153+
154+
g.setColor(Color.BLACK);
155+
g.drawString("Test Image", width / 3, height / 2);
156+
157+
g.dispose();
158+
return image;
159+
}
160+
161+
private static boolean shouldTestAllEncoders(String[] args) {
162+
for (String arg : args) {
163+
if ("--all".equals(arg) || "-a".equals(arg)) {
164+
return true;
165+
}
166+
}
167+
return false;
168+
}
169+
}

jbang-catalog.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"bounce": {
44
"description": "A simple TUI that demonstrates a bouncing Twinkle.",
55
"script-ref": "examples/src/main/java/org/codejive/twinkle/demos/BouncingTwinkleDemo.java"
6+
},
7+
"image": {
8+
"description": "A simple TUI that demonstrates image rendering.",
9+
"script-ref": "examples/src/main/java/org/codejive/twinkle/demos/ImageEncoderDemo.java"
610
}
711
}
8-
}
12+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<module>twinkle-text</module>
2323
<module>twinkle-screen</module>
2424
<module>twinkle-shapes</module>
25+
<module>twinkle-image</module>
2526
<module>twinkle-terminal</module>
2627
<module>twinkle-terminal-aesh</module>
2728
<module>twinkle-terminal-jline</module>

0 commit comments

Comments
 (0)