+++ /dev/null
-package be.nikiroo.utils.ui;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Graphics;
-import java.awt.Image;
-import java.awt.image.BufferedImage;
-import java.awt.image.ImageObserver;
-
-/**
- * This class converts an {@link Image} into a textual representation that can
- * be displayed to the user in a TUI.
- *
- * @author niki
- */
-public class ImageTextAwt {
- private Image image;
- private Dimension size;
- private String text;
- private boolean ready;
- private Mode mode;
- private boolean invert;
-
- /**
- * The rendering modes supported by this {@link ImageTextAwt} to convert
- * {@link Image}s into text.
- *
- * @author niki
- *
- */
- public enum Mode {
- /**
- * Use 5 different "colours" which are actually Unicode
- * {@link Character}s representing
- * <ul>
- * <li>space (blank)</li>
- * <li>low shade (░)</li>
- * <li>medium shade (▒)</li>
- * <li>high shade (▓)</li>
- * <li>full block (█)</li>
- * </ul>
- */
- 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,
- }
-
- /**
- * Create a new {@link ImageTextAwt} with the given parameters. Defaults to
- * {@link Mode#DOUBLE_DITHERING} and no colour inversion.
- *
- * @param image
- * the source {@link Image}
- * @param size
- * the final text size to target
- */
- public ImageTextAwt(Image image, Dimension size) {
- this(image, size, Mode.DOUBLE_DITHERING, false);
- }
-
- /**
- * Create a new {@link ImageTextAwt} with the given parameters.
- *
- * @param image
- * the source {@link Image}
- * @param size
- * the final text size to target
- * @param mode
- * the mode of conversion
- * @param invert
- * TRUE to invert colours rendering
- */
- public ImageTextAwt(Image image, Dimension size, Mode mode, boolean invert) {
- setImage(image);
- setSize(size);
- setMode(mode);
- setColorInvert(invert);
- }
-
- /**
- * Change the source {@link Image}.
- *
- * @param image
- * the new {@link Image}
- */
- public void setImage(Image image) {
- this.text = null;
- this.ready = false;
- this.image = image;
- }
-
- /**
- * Change the target size of this {@link ImageTextAwt}.
- *
- * @param size
- * the new size
- */
- public void setSize(Dimension size) {
- this.text = null;
- this.ready = false;
- this.size = size;
- }
-
- /**
- * Change the image-to-text mode.
- *
- * @param mode
- * the new {@link Mode}
- */
- public void setMode(Mode mode) {
- this.mode = mode;
- this.text = null;
- this.ready = false;
- }
-
- /**
- * Set the colour-invert mode.
- *
- * @param invert
- * TRUE to inverse the colours
- */
- public void setColorInvert(boolean invert) {
- this.invert = invert;
- this.text = null;
- this.ready = false;
- }
-
- /**
- * Check if the colours are inverted.
- *
- * @return TRUE if the colours are inverted
- */
- public boolean isColorInvert() {
- return invert;
- }
-
- /**
- * Return the textual representation of the included {@link Image}.
- *
- * @return the {@link String} representation
- */
- public String getText() {
- if (text == null) {
- if (image == null || size == null || size.width == 0
- || size.height == 0) {
- return "";
- }
-
- int mult = 1;
- if (mode == Mode.DOUBLE_RESOLUTION || mode == Mode.DOUBLE_DITHERING) {
- mult = 2;
- }
-
- int w = size.width * mult;
- int h = size.height * mult;
-
- BufferedImage buff = new BufferedImage(w, h,
- BufferedImage.TYPE_INT_ARGB);
-
- Graphics gfx = buff.getGraphics();
-
- Dimension srcSize = getSize(image);
- srcSize = new Dimension(srcSize.width * 2, srcSize.height);
- int x = 0;
- int y = 0;
-
- if (srcSize.width < srcSize.height) {
- double ratio = (double) size.width / (double) size.height;
- ratio *= (double) srcSize.height / (double) srcSize.width;
-
- h = (int) Math.round(ratio * h);
- y = (buff.getHeight() - h) / 2;
- } else {
- double ratio = (double) size.height / (double) size.width;
- ratio *= (double) srcSize.width / (double) srcSize.height;
-
- 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) {
- ImageTextAwt.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) {
- char car = ' ';
- float brightness = getBrightness(buff.getRGB(col, row));
- if (mode == Mode.DITHERING)
- car = getDitheringChar(brightness, " ░▒▓█");
- if (mode == Mode.ASCII)
- car = getDitheringChar(brightness, " .-+=o8#");
-
- builder.append(car);
- } 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();
- }
-
- /**
- * Return the size of the given {@link Image}.
- *
- * @param img
- * the image to measure
- *
- * @return the size
- */
- static private Dimension getSize(Image img) {
- Dimension size = null;
- while (size == null) {
- int w = img.getWidth(null);
- int h = img.getHeight(null);
- if (w > -1 && h > -1) {
- size = new Dimension(w, h);
- } else {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- }
- }
- }
-
- return size;
- }
-
- /**
- * Return the {@link Character} corresponding to the given brightness level
- * from the evenly-separated given {@link Character}s.
- *
- * @param brightness
- * the brightness level
- * @param cars
- * the {@link Character}s to choose from, from less bright to
- * most bright; <b>MUST</b> contain at least one
- * {@link Character}
- *
- * @return the {@link Character} to use
- */
- private char getDitheringChar(float brightness, String cars) {
- int index = Math.round(brightness * (cars.length() - 1));
- return cars.charAt(index);
- }
-
- /**
- * Return the {@link Character} corresponding to the 4 given colours in
- * {@link Mode#DOUBLE_RESOLUTION} or {@link Mode#DOUBLE_DITHERING} mode.
- *
- * @param upperleft
- * the upper left colour
- * @param upperright
- * the upper right colour
- * @param lowerleft
- * the lower left colour
- * @param lowerright
- * the lower right colour
- * @param dithering
- * TRUE to use {@link Mode#DOUBLE_DITHERING}, FALSE for
- * {@link Mode#DOUBLE_RESOLUTION}
- *
- * @return the {@link Character} to use
- */
- private char getBlockChar(int upperleft, int upperright, int lowerleft,
- int lowerright, boolean dithering) {
- int choice = 0;
-
- if (getBrightness(upperleft) > 0.5f) {
- choice += 1;
- }
- if (getBrightness(upperright) > 0.5f) {
- choice += 2;
- }
- if (getBrightness(lowerleft) > 0.5f) {
- choice += 4;
- }
- if (getBrightness(lowerright) > 0.5f) {
- 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);
- avg += getBrightness(upperright);
- avg += getBrightness(lowerleft);
- avg += getBrightness(lowerright);
- avg /= 4;
-
- return getDitheringChar(avg, " ░▒▓█");
- }
-
- return '█';
- }
-
- return ' ';
- }
-
- /**
- * Temporary array used so not to create a lot of new ones.
- */
- private float[] tmp = new float[4];
-
- /**
- * Return the brightness value to use from the given ARGB colour.
- *
- * @param argb
- * the argb colour
- *
- * @return the brightness to sue for computations
- */
- private float getBrightness(int argb) {
- if (invert) {
- return 1 - rgb2hsb(argb, tmp)[2];
- }
-
- return rgb2hsb(argb, tmp)[2];
- }
-
- /**
- * Convert the given ARGB colour in HSL/HSB, either into the supplied array
- * or into a new one if array is NULL.
- *
- * <p>
- * ARGB pixels are given in 0xAARRGGBB format, while the returned array will
- * contain Hue, Saturation, Lightness/Brightness, Alpha, in this order. H,
- * S, L and A are all ranging from 0 to 1 (indeed, H is in 1/360th).
- * </p>
- * pixel
- *
- * @param argb
- * the ARGB colour pixel to convert
- * @param array
- * the array to convert into or NULL to create a new one
- *
- * @return the array containing the HSL/HSB converted colour
- */
- 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 };
- }
-}