256 color mode fixed, grayscale support added
This commit is contained in:
parent
de5fb08cf0
commit
129c7f1f73
1 changed files with 88 additions and 24 deletions
|
@ -4,6 +4,7 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
|
@ -20,47 +21,61 @@ public class TerminalImageViewer {
|
|||
*/
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length == 0) {
|
||||
System.out.println("Image file name required. Use -w to set the width in characters (default: 80).");
|
||||
System.out.println(
|
||||
"Image file name required.\n\n - Use -w and -h to set the maximum width and height in characters" +
|
||||
" (defaults: 80, 24).\n - Use -256 for 256 color mode and -grayscale for grayscale.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
int w = 80 * 4;
|
||||
int maxWidth = 80;
|
||||
int maxHeight = 24;
|
||||
int mode = Ansi.MODE_24BIT;
|
||||
boolean grayscale = false;
|
||||
while (start < args.length && args[start].startsWith("-")) {
|
||||
String option = args[start];
|
||||
if (option.equals("-w") && args.length > start + 1) {
|
||||
w = 4 * Integer.parseInt(args[++start]);
|
||||
maxWidth = Integer.parseInt(args[++start]);
|
||||
} else if (option.equals("-h") && args.length > start + 1) {
|
||||
maxHeight = Integer.parseInt(args[++start]);
|
||||
} else if (option.equals("-256")) {
|
||||
mode = Ansi.MODE_256;
|
||||
mode = (mode & ~Ansi.MODE_24BIT) | Ansi.MODE_256;
|
||||
} else if (option.equals("-grayscale")) {
|
||||
grayscale = true;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
|
||||
if (start == args.length - 1) {
|
||||
maxWidth *= 4;
|
||||
maxHeight *= 8;
|
||||
|
||||
if (start == args.length - 1 && (isUrl(args[start]) || !new File(args[start]).isDirectory())) {
|
||||
String name = args[start];
|
||||
|
||||
BufferedImage original = loadImage(name);
|
||||
|
||||
int ow = original.getWidth();
|
||||
int oh = original.getHeight();
|
||||
int h = oh * w / ow;
|
||||
float originalWidth = original.getWidth();
|
||||
float originalHeight = original.getHeight();
|
||||
float scale = Math.min(maxWidth / originalWidth, maxHeight / originalHeight);
|
||||
int height = (int) (originalHeight * scale);
|
||||
int width = (int) (originalWidth * scale);
|
||||
|
||||
if (w == ow) {
|
||||
if (originalWidth == width && !grayscale) {
|
||||
dump(original, mode);
|
||||
} else {
|
||||
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage image = new BufferedImage(width, height, grayscale ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.drawImage(original, 0, 0, w, h, null);
|
||||
graphics.drawImage(original, 0, 0, width, height, null);
|
||||
dump(image, mode);
|
||||
}
|
||||
} else {
|
||||
// Directory-style rendering.
|
||||
int index = 0;
|
||||
int cw = (w - 2 * 3 * 4) / 16;
|
||||
int cw = (maxWidth - 2 * 3 * 4) / 16;
|
||||
int tw = cw * 4;
|
||||
|
||||
while (index < args.length) {
|
||||
BufferedImage image = new BufferedImage(tw * 4 + 24, tw, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage image = new BufferedImage(tw * 4 + 24, tw, grayscale ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
int count = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
@ -90,8 +105,12 @@ public class TerminalImageViewer {
|
|||
}
|
||||
}
|
||||
|
||||
static boolean isUrl(String name) {
|
||||
return name.startsWith("http://") || name.startsWith("https://");
|
||||
}
|
||||
|
||||
static BufferedImage loadImage(String name) throws IOException {
|
||||
if (name.startsWith("http://") || name.startsWith("https://")) {
|
||||
if (isUrl(name)) {
|
||||
URL url = new URL(name);
|
||||
return ImageIO.read(url);
|
||||
}
|
||||
|
@ -122,10 +141,36 @@ public class TerminalImageViewer {
|
|||
*/
|
||||
static class Ansi {
|
||||
public static final String RESET = "\u001b[0m";
|
||||
public static int FG = 0;
|
||||
public static int BG = 1;
|
||||
public static int MODE_256 = 2;
|
||||
public static int MODE_24BIT = 0;
|
||||
public static int FG = 1;
|
||||
public static int BG = 2;
|
||||
public static int MODE_256 = 4;
|
||||
public static int MODE_24BIT = 8;
|
||||
|
||||
public static final int[] COLOR_STEPS = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
|
||||
public static final int[] GRAYSCALE = {0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76,
|
||||
0x80, 0x8a, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee};
|
||||
|
||||
static int bestIndex(int v, int[] options) {
|
||||
int index = Arrays.binarySearch(options, v);
|
||||
if (index < 0) {
|
||||
index = -index - 1;
|
||||
// need to check [index] and [index - 1]
|
||||
if (index == options.length) {
|
||||
index = options.length - 1;
|
||||
} else if (index > 0) {
|
||||
int val0 = options[index - 1];
|
||||
int val1 = options[index];
|
||||
if (v - val0 < val1 - v) {
|
||||
index = index - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
static int sqr(int i) {
|
||||
return i * i;
|
||||
}
|
||||
|
||||
public static int clamp(int value, int min, int max) {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
|
@ -138,14 +183,33 @@ public class TerminalImageViewer {
|
|||
|
||||
boolean bg = (flags & BG) != 0;
|
||||
|
||||
if ((flags & MODE_256) != 0) {
|
||||
r = Math.round(r / 51f);
|
||||
g = Math.round(g / 51f);
|
||||
b = Math.round(b / 51f);
|
||||
return (bg ? "\u001B[48;5;" : "\u001B[38;5;") + (16 + 36 * r + 6 * g + b) + "m";
|
||||
} else {
|
||||
if ((flags & MODE_256) == 0) {
|
||||
return (bg ? "\u001b[48;2;" : "\u001b[38;2;") + r + ";" + g + ";" + b + "m";
|
||||
}
|
||||
int rIdx = bestIndex(r, COLOR_STEPS);
|
||||
int gIdx = bestIndex(g, COLOR_STEPS);
|
||||
int bIdx = bestIndex(b, COLOR_STEPS);
|
||||
|
||||
int rQ = COLOR_STEPS[rIdx];
|
||||
int gQ = COLOR_STEPS[gIdx];
|
||||
int bQ = COLOR_STEPS[bIdx];
|
||||
|
||||
int gray = Math.round(r * 0.2989f + g * 0.5870f + b * 0.1140f);
|
||||
|
||||
int grayIdx = bestIndex(gray, GRAYSCALE);
|
||||
int grayQ = GRAYSCALE[grayIdx];
|
||||
|
||||
System.out.println("Reconstructed RGB: " + Integer.toHexString((rQ << 16) | (gQ << 8) | bQ) + " gray:" +
|
||||
Integer.toHexString(grayQ));
|
||||
|
||||
int colorIndex;
|
||||
if (0.3 * sqr(rQ-r) + 0.59 * sqr(gQ-g) + 0.11 *sqr(bQ-b) <
|
||||
0.3 * sqr(grayQ-r) + 0.59 * sqr(grayQ-g) + 0.11 * sqr(grayQ-b)) {
|
||||
colorIndex = 16 + 36 * rIdx + 6 * gIdx + bIdx;
|
||||
} else {
|
||||
colorIndex = 232 + grayIdx; // 1..24 -> 232..255
|
||||
}
|
||||
return (bg ? "\u001B[48;5;" : "\u001B[38;5;") + colorIndex + "m";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue