c393c46201b362e299c60a4706852e438878b9fb
[jvcard.git] / src / be / nikiroo / jvcard / tui / ImageText.java
1 package be.nikiroo.jvcard.tui;
2
3 import java.awt.Color;
4 import java.awt.Graphics;
5 import java.awt.Image;
6 import java.awt.image.BufferedImage;
7 import java.awt.image.ImageObserver;
8
9 import com.googlecode.lanterna.TerminalSize;
10
11 public class ImageText {
12 private Image image;
13 private TerminalSize size;
14 private String text;
15 private boolean ready;
16 private Mode mode;
17 private boolean invert;
18
19 public enum Mode {
20 /**
21 * Use 5 different "colours" which are actually Unicode
22 * {@link Character}s representing
23 * <ul>
24 * <li>space (blank)</li>
25 * <li>low shade (░)</li>
26 * <li>medium shade (▒)</li>
27 * <li>high shade (▓)</li>
28 * <li>full block (█)</li>
29 * </ul>
30 */
31 DITHERING,
32 /**
33 * Use "block" Unicode {@link Character}s up to quarter blocks, thus in
34 * effect doubling the resolution both in vertical and horizontal space.
35 * Note that since 2 {@link Character}s next to each other are square,
36 * we will use 4 blocks per 2 blocks for w/h resolution.
37 */
38 DOUBLE_RESOLUTION,
39 /**
40 * Use {@link Character}s from both {@link Mode#DOUBLE_RESOLUTION} and
41 * {@link Mode#DITHERING}.
42 */
43 DOUBLE_DITHERING,
44 /**
45 * Only use ASCII {@link Character}s.
46 */
47 ASCII,
48 }
49
50 public ImageText(Image image, TerminalSize size, Mode mode) {
51 setImage(image, size);
52 setMode(mode);
53 }
54
55 public void setImage(Image image) {
56 setImage(image, size);
57 }
58
59 public void setImage(TerminalSize size) {
60 setImage(image, size);
61 }
62
63 public void setImage(Image image, TerminalSize size) {
64 this.text = null;
65 this.ready = false;
66 this.size = size;
67 if (image != null) {
68 this.image = image;
69 }
70 }
71
72 public void setMode(Mode mode) {
73 this.mode = mode;
74 this.text = null;
75 this.ready = false;
76 }
77
78 public void setColorInvert(boolean invert) {
79 this.invert = invert;
80 this.text = null;
81 this.ready = false;
82 }
83
84 public boolean getColorInvert() {
85 return invert;
86 }
87
88 public String getText() {
89 if (text == null) {
90 if (image == null)
91 return "";
92
93 int mult = 1;
94 if (mode == Mode.DOUBLE_RESOLUTION || mode == Mode.DOUBLE_DITHERING)
95 mult = 2;
96
97 int w = size.getColumns() * mult;
98 int h = size.getRows() * mult;
99
100 BufferedImage buff = new BufferedImage(w, h,
101 BufferedImage.TYPE_INT_ARGB);
102
103 Graphics gfx = buff.getGraphics();
104
105 TerminalSize srcSize = getSize(image);
106 srcSize = new TerminalSize(srcSize.getColumns() * 2,
107 srcSize.getRows());
108 int x = 0;
109 int y = 0;
110
111 if (srcSize.getColumns() > srcSize.getRows()) {
112 double ratio = (double) size.getColumns()
113 / (double) size.getRows();
114 ratio *= (double) srcSize.getRows()
115 / (double) srcSize.getColumns();
116
117 h = (int) Math.round(ratio * h);
118 y = (buff.getHeight() - h) / 2;
119 } else {
120 double ratio = (double) size.getRows()
121 / (double) size.getColumns();
122 ratio *= (double) srcSize.getColumns()
123 / (double) srcSize.getRows();
124
125 w = (int) Math.round(ratio * w);
126 x = (buff.getWidth() - w) / 2;
127 }
128
129 if (gfx.drawImage(image, x, y, w, h, new ImageObserver() {
130 @Override
131 public boolean imageUpdate(Image img, int infoflags, int x,
132 int y, int width, int height) {
133 ImageText.this.ready = true;
134 return true;
135 }
136 })) {
137 ready = true;
138 }
139
140 while (!ready) {
141 try {
142 Thread.sleep(100);
143 } catch (InterruptedException e) {
144 }
145 }
146
147 gfx.dispose();
148
149 StringBuilder builder = new StringBuilder();
150
151 for (int row = 0; row < buff.getHeight(); row += mult) {
152 if (row > 0)
153 builder.append('\n');
154
155 for (int col = 0; col < buff.getWidth(); col += mult) {
156 if (mult == 1) {
157 if (mode == Mode.DITHERING)
158 builder.append(getDitheringChar(buff.getRGB(col,
159 row)));
160 else
161 // Mode.ASCII
162 builder.append(getAsciiChar(buff.getRGB(col, row)));
163 } else if (mult == 2) {
164 builder.append(getBlockChar( //
165 buff.getRGB(col, row),//
166 buff.getRGB(col + 1, row),//
167 buff.getRGB(col, row + 1),//
168 buff.getRGB(col + 1, row + 1),//
169 mode == Mode.DOUBLE_DITHERING//
170 ));
171 }
172 }
173 }
174
175 text = builder.toString();
176 }
177
178 return text;
179 }
180
181 @Override
182 public String toString() {
183 return getText();
184 }
185
186 static private TerminalSize getSize(Image img) {
187 TerminalSize size = null;
188 while (size == null) {
189 int w = img.getWidth(null);
190 int h = img.getHeight(null);
191 if (w > -1 && h > -1) {
192 size = new TerminalSize(w, h);
193 } else {
194 try {
195 Thread.sleep(100);
196 } catch (InterruptedException e) {
197 }
198 }
199 }
200
201 return size;
202 }
203
204 private float[] tmp = new float[4];
205
206 private char getAsciiChar(int pixel) {
207 float brigthness = getBrightness(pixel, tmp);
208 if (brigthness < 0.20) {
209 return ' ';
210 } else if (brigthness < 0.40) {
211 return '.';
212 } else if (brigthness < 0.60) {
213 return '+';
214 } else if (brigthness < 0.80) {
215 return '*';
216 } else {
217 return '#';
218 }
219 }
220
221 private char getDitheringChar(int pixel) {
222 float brigthness = getBrightness(pixel, tmp);
223 if (brigthness < 0.20) {
224 return ' ';
225 } else if (brigthness < 0.40) {
226 return '░';
227 } else if (brigthness < 0.60) {
228 return '▒';
229 } else if (brigthness < 0.80) {
230 return '▓';
231 } else {
232 return '█';
233 }
234 }
235
236 private char getBlockChar(int upperleft, int upperright, int lowerleft,
237 int lowerright, boolean dithering) {
238 float trigger = dithering ? 0.20f : 0.50f;
239 int choice = 0;
240 if (getBrightness(upperleft, tmp) > trigger)
241 choice += 1;
242 if (getBrightness(upperright, tmp) > trigger)
243 choice += 2;
244 if (getBrightness(lowerleft, tmp) > trigger)
245 choice += 4;
246 if (getBrightness(lowerright, tmp) > trigger)
247 choice += 8;
248
249 switch (choice) {
250 case 0:
251 return ' ';
252 case 1:
253 return '▘';
254 case 2:
255 return '▝';
256 case 3:
257 return '▀';
258 case 4:
259 return '▖';
260 case 5:
261 return '▌';
262 case 6:
263 return '▞';
264 case 7:
265 return '▛';
266 case 8:
267 return '▗';
268 case 9:
269 return '▚';
270 case 10:
271 return '▐';
272 case 11:
273 return '▜';
274 case 12:
275 return '▄';
276 case 13:
277 return '▙';
278 case 14:
279 return '▟';
280 case 15:
281 if (dithering) {
282 float avg = 0;
283 avg += getBrightness(upperleft, tmp);
284 avg += getBrightness(upperright, tmp);
285 avg += getBrightness(lowerleft, tmp);
286 avg += getBrightness(lowerright, tmp);
287 avg /= 4;
288
289 if (avg < 0.20) {
290 return ' ';
291 } else if (avg < 0.40) {
292 return '░';
293 } else if (avg < 0.60) {
294 return '▒';
295 } else if (avg < 0.80) {
296 return '▓';
297 } else {
298 return '█';
299 }
300 } else {
301 return '█';
302 }
303 }
304
305 return ' ';
306 }
307
308 float getBrightness(int argb, float[] array) {
309 if (invert)
310 return 1 - rgb2hsb(argb, tmp)[2];
311 return rgb2hsb(argb, tmp)[2];
312 }
313
314 // return [h, s, l, a]; h/s/b/a: 0 to 1 (h is given in 1/360th)
315 // like RGBtoHSB, array can be null or used
316 static float[] rgb2hsb(int argb, float[] array) {
317 int a, r, g, b;
318 a = ((argb & 0xff000000) >> 24);
319 r = ((argb & 0x00ff0000) >> 16);
320 g = ((argb & 0x0000ff00) >> 8);
321 b = ((argb & 0x000000ff));
322
323 if (array == null)
324 array = new float[4];
325 Color.RGBtoHSB(r, g, b, array);
326
327 array[3] = a;
328
329 return array;
330
331 // // other implementation:
332 //
333 // float a, r, g, b;
334 // a = ((argb & 0xff000000) >> 24) / 255.0f;
335 // r = ((argb & 0x00ff0000) >> 16) / 255.0f;
336 // g = ((argb & 0x0000ff00) >> 8) / 255.0f;
337 // b = ((argb & 0x000000ff)) / 255.0f;
338 //
339 // float rgbMin, rgbMax;
340 // rgbMin = Math.min(r, Math.min(g, b));
341 // rgbMax = Math.max(r, Math.max(g, b));
342 //
343 // float l;
344 // l = (rgbMin + rgbMax) / 2;
345 //
346 // float s;
347 // if (rgbMin == rgbMax) {
348 // s = 0;
349 // } else {
350 // if (l <= 0.5) {
351 // s = (rgbMax - rgbMin) / (rgbMax + rgbMin);
352 // } else {
353 // s = (rgbMax - rgbMin) / (2.0f - rgbMax - rgbMin);
354 // }
355 // }
356 //
357 // float h;
358 // if (r > g && r > b) {
359 // h = (g - b) / (rgbMax - rgbMin);
360 // } else if (g > b) {
361 // h = 2.0f + (b - r) / (rgbMax - rgbMin);
362 // } else {
363 // h = 4.0f + (r - g) / (rgbMax - rgbMin);
364 // }
365 // h /= 6; // from 0 to 1
366 //
367 // return new float[] { h, s, l, a };
368 //
369 // // // natural mode:
370 // //
371 // // int aa = (int) Math.round(100 * a);
372 // // int hh = (int) (360 * h);
373 // // if (hh < 0)
374 // // hh += 360;
375 // // int ss = (int) Math.round(100 * s);
376 // // int ll = (int) Math.round(100 * l);
377 // //
378 // // return new int[] { hh, ss, ll, aa };
379 }
380 }