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.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
@ -20,47 +21,61 @@ public class TerminalImageViewer {
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
if (args.length == 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int start = 0;
|
int start = 0;
|
||||||
int w = 80 * 4;
|
int maxWidth = 80;
|
||||||
|
int maxHeight = 24;
|
||||||
int mode = Ansi.MODE_24BIT;
|
int mode = Ansi.MODE_24BIT;
|
||||||
|
boolean grayscale = false;
|
||||||
while (start < args.length && args[start].startsWith("-")) {
|
while (start < args.length && args[start].startsWith("-")) {
|
||||||
String option = args[start];
|
String option = args[start];
|
||||||
if (option.equals("-w") && args.length > start + 1) {
|
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")) {
|
} 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++;
|
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];
|
String name = args[start];
|
||||||
|
|
||||||
BufferedImage original = loadImage(name);
|
BufferedImage original = loadImage(name);
|
||||||
|
|
||||||
int ow = original.getWidth();
|
float originalWidth = original.getWidth();
|
||||||
int oh = original.getHeight();
|
float originalHeight = original.getHeight();
|
||||||
int h = oh * w / ow;
|
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);
|
dump(original, mode);
|
||||||
} else {
|
} 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();
|
Graphics2D graphics = image.createGraphics();
|
||||||
graphics.drawImage(original, 0, 0, w, h, null);
|
graphics.drawImage(original, 0, 0, width, height, null);
|
||||||
dump(image, mode);
|
dump(image, mode);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Directory-style rendering.
|
// Directory-style rendering.
|
||||||
int index = 0;
|
int index = 0;
|
||||||
int cw = (w - 2 * 3 * 4) / 16;
|
int cw = (maxWidth - 2 * 3 * 4) / 16;
|
||||||
int tw = cw * 4;
|
int tw = cw * 4;
|
||||||
|
|
||||||
while (index < args.length) {
|
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();
|
Graphics2D graphics = image.createGraphics();
|
||||||
int count = 0;
|
int count = 0;
|
||||||
StringBuilder sb = new StringBuilder();
|
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 {
|
static BufferedImage loadImage(String name) throws IOException {
|
||||||
if (name.startsWith("http://") || name.startsWith("https://")) {
|
if (isUrl(name)) {
|
||||||
URL url = new URL(name);
|
URL url = new URL(name);
|
||||||
return ImageIO.read(url);
|
return ImageIO.read(url);
|
||||||
}
|
}
|
||||||
|
@ -122,10 +141,36 @@ public class TerminalImageViewer {
|
||||||
*/
|
*/
|
||||||
static class Ansi {
|
static class Ansi {
|
||||||
public static final String RESET = "\u001b[0m";
|
public static final String RESET = "\u001b[0m";
|
||||||
public static int FG = 0;
|
public static int FG = 1;
|
||||||
public static int BG = 1;
|
public static int BG = 2;
|
||||||
public static int MODE_256 = 2;
|
public static int MODE_256 = 4;
|
||||||
public static int MODE_24BIT = 0;
|
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) {
|
public static int clamp(int value, int min, int max) {
|
||||||
return Math.min(Math.max(value, min), max);
|
return Math.min(Math.max(value, min), max);
|
||||||
|
@ -138,14 +183,33 @@ public class TerminalImageViewer {
|
||||||
|
|
||||||
boolean bg = (flags & BG) != 0;
|
boolean bg = (flags & BG) != 0;
|
||||||
|
|
||||||
if ((flags & MODE_256) != 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 {
|
|
||||||
return (bg ? "\u001b[48;2;" : "\u001b[38;2;") + r + ";" + g + ";" + b + "m";
|
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