package be.nikiroo.jvcard.tui;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import com.googlecode.lanterna.TerminalSize;
public class ImageText {
private Image image;
private TerminalSize size;
private String text;
private boolean ready;
private Mode mode;
private boolean invert;
public enum Mode {
/**
* Use 5 different "colours" which are actually Unicode
* {@link Character}s representing
*
* - space (blank)
* - low shade (░)
* - medium shade (▒)
* - high shade (▓)
* - full block (█)
*
*/
DITHERING,
/**
* Use "block" Unicode {@link Character}s up to quarter blocks, thus in
* effect doubling the resolution both in vertical and horizontal space.
* Note that since 2 {@link Character}s next to each other are square,
* we will use 4 blocks per 2 blocks for w/h resolution.
*/
DOUBLE_RESOLUTION,
/**
* Use {@link Character}s from both {@link Mode#DOUBLE_RESOLUTION} and
* {@link Mode#DITHERING}.
*/
DOUBLE_DITHERING,
/**
* Only use ASCII {@link Character}s.
*/
ASCII,
}
public ImageText(Image image, TerminalSize size, Mode mode) {
setImage(image, size);
setMode(mode);
}
public void setImage(Image image) {
setImage(image, size);
}
public void setImage(TerminalSize size) {
setImage(image, size);
}
public void setImage(Image image, TerminalSize size) {
this.text = null;
this.ready = false;
this.size = size;
if (image != null) {
this.image = image;
}
}
public void setMode(Mode mode) {
this.mode = mode;
this.text = null;
this.ready = false;
}
public void setColorInvert(boolean invert) {
this.invert = invert;
this.text = null;
this.ready = false;
}
public boolean getColorInvert() {
return invert;
}
public String getText() {
if (text == null) {
if (image == null)
return "";
int mult = 1;
if (mode == Mode.DOUBLE_RESOLUTION || mode == Mode.DOUBLE_DITHERING)
mult = 2;
int w = size.getColumns() * mult;
int h = size.getRows() * mult;
BufferedImage buff = new BufferedImage(w, h,
BufferedImage.TYPE_INT_ARGB);
Graphics gfx = buff.getGraphics();
TerminalSize srcSize = getSize(image);
srcSize = new TerminalSize(srcSize.getColumns() * 2,
srcSize.getRows());
int x = 0;
int y = 0;
if (srcSize.getColumns() > srcSize.getRows()) {
double ratio = (double) size.getColumns()
/ (double) size.getRows();
ratio *= (double) srcSize.getRows()
/ (double) srcSize.getColumns();
h = (int) Math.round(ratio * h);
y = (buff.getHeight() - h) / 2;
} else {
double ratio = (double) size.getRows()
/ (double) size.getColumns();
ratio *= (double) srcSize.getColumns()
/ (double) srcSize.getRows();
w = (int) Math.round(ratio * w);
x = (buff.getWidth() - w) / 2;
}
if (gfx.drawImage(image, x, y, w, h, new ImageObserver() {
@Override
public boolean imageUpdate(Image img, int infoflags, int x,
int y, int width, int height) {
ImageText.this.ready = true;
return true;
}
})) {
ready = true;
}
while (!ready) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
gfx.dispose();
StringBuilder builder = new StringBuilder();
for (int row = 0; row < buff.getHeight(); row += mult) {
if (row > 0)
builder.append('\n');
for (int col = 0; col < buff.getWidth(); col += mult) {
if (mult == 1) {
if (mode == Mode.DITHERING)
builder.append(getDitheringChar(buff.getRGB(col,
row)));
else
// Mode.ASCII
builder.append(getAsciiChar(buff.getRGB(col, row)));
} else if (mult == 2) {
builder.append(getBlockChar( //
buff.getRGB(col, row),//
buff.getRGB(col + 1, row),//
buff.getRGB(col, row + 1),//
buff.getRGB(col + 1, row + 1),//
mode == Mode.DOUBLE_DITHERING//
));
}
}
}
text = builder.toString();
}
return text;
}
@Override
public String toString() {
return getText();
}
static private TerminalSize getSize(Image img) {
TerminalSize size = null;
while (size == null) {
int w = img.getWidth(null);
int h = img.getHeight(null);
if (w > -1 && h > -1) {
size = new TerminalSize(w, h);
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
return size;
}
private float[] tmp = new float[4];
private char getAsciiChar(int pixel) {
float brigthness = getBrightness(pixel, tmp);
if (brigthness < 0.20) {
return ' ';
} else if (brigthness < 0.40) {
return '.';
} else if (brigthness < 0.60) {
return '+';
} else if (brigthness < 0.80) {
return '*';
} else {
return '#';
}
}
private char getDitheringChar(int pixel) {
float brigthness = getBrightness(pixel, tmp);
if (brigthness < 0.20) {
return ' ';
} else if (brigthness < 0.40) {
return '░';
} else if (brigthness < 0.60) {
return '▒';
} else if (brigthness < 0.80) {
return '▓';
} else {
return '█';
}
}
private char getBlockChar(int upperleft, int upperright, int lowerleft,
int lowerright, boolean dithering) {
float trigger = dithering ? 0.20f : 0.50f;
int choice = 0;
if (getBrightness(upperleft, tmp) > trigger)
choice += 1;
if (getBrightness(upperright, tmp) > trigger)
choice += 2;
if (getBrightness(lowerleft, tmp) > trigger)
choice += 4;
if (getBrightness(lowerright, tmp) > trigger)
choice += 8;
switch (choice) {
case 0:
return ' ';
case 1:
return '▘';
case 2:
return '▝';
case 3:
return '▀';
case 4:
return '▖';
case 5:
return '▌';
case 6:
return '▞';
case 7:
return '▛';
case 8:
return '▗';
case 9:
return '▚';
case 10:
return '▐';
case 11:
return '▜';
case 12:
return '▄';
case 13:
return '▙';
case 14:
return '▟';
case 15:
if (dithering) {
float avg = 0;
avg += getBrightness(upperleft, tmp);
avg += getBrightness(upperright, tmp);
avg += getBrightness(lowerleft, tmp);
avg += getBrightness(lowerright, tmp);
avg /= 4;
if (avg < 0.20) {
return ' ';
} else if (avg < 0.40) {
return '░';
} else if (avg < 0.60) {
return '▒';
} else if (avg < 0.80) {
return '▓';
} else {
return '█';
}
} else {
return '█';
}
}
return ' ';
}
float getBrightness(int argb, float[] array) {
if (invert)
return 1 - rgb2hsb(argb, tmp)[2];
return rgb2hsb(argb, tmp)[2];
}
// return [h, s, l, a]; h/s/b/a: 0 to 1 (h is given in 1/360th)
// like RGBtoHSB, array can be null or used
static float[] rgb2hsb(int argb, float[] array) {
int a, r, g, b;
a = ((argb & 0xff000000) >> 24);
r = ((argb & 0x00ff0000) >> 16);
g = ((argb & 0x0000ff00) >> 8);
b = ((argb & 0x000000ff));
if (array == null)
array = new float[4];
Color.RGBtoHSB(r, g, b, array);
array[3] = a;
return array;
// // other implementation:
//
// float a, r, g, b;
// a = ((argb & 0xff000000) >> 24) / 255.0f;
// r = ((argb & 0x00ff0000) >> 16) / 255.0f;
// g = ((argb & 0x0000ff00) >> 8) / 255.0f;
// b = ((argb & 0x000000ff)) / 255.0f;
//
// float rgbMin, rgbMax;
// rgbMin = Math.min(r, Math.min(g, b));
// rgbMax = Math.max(r, Math.max(g, b));
//
// float l;
// l = (rgbMin + rgbMax) / 2;
//
// float s;
// if (rgbMin == rgbMax) {
// s = 0;
// } else {
// if (l <= 0.5) {
// s = (rgbMax - rgbMin) / (rgbMax + rgbMin);
// } else {
// s = (rgbMax - rgbMin) / (2.0f - rgbMax - rgbMin);
// }
// }
//
// float h;
// if (r > g && r > b) {
// h = (g - b) / (rgbMax - rgbMin);
// } else if (g > b) {
// h = 2.0f + (b - r) / (rgbMax - rgbMin);
// } else {
// h = 4.0f + (r - g) / (rgbMax - rgbMin);
// }
// h /= 6; // from 0 to 1
//
// return new float[] { h, s, l, a };
//
// // // natural mode:
// //
// // int aa = (int) Math.round(100 * a);
// // int hh = (int) (360 * h);
// // if (hh < 0)
// // hh += 360;
// // int ss = (int) Math.round(100 * s);
// // int ll = (int) Math.round(100 * l);
// //
// // return new int[] { hh, ss, ll, aa };
}
}