Merge branch 'master' of https://github.com/stefanhaustein/TerminalImageViewer
This commit is contained in:
commit
d543b3b2c9
2 changed files with 86 additions and 78 deletions
12
README.md
12
README.md
|
@ -1,6 +1,6 @@
|
||||||
# TerminalImageViewer
|
# TerminalImageViewer
|
||||||
|
|
||||||
Small Java program to display images in a (modern) terminal using RGB ANSI codes and unicode block graphic characters
|
Small Java\* program to display images in a (modern) terminal using RGB ANSI codes and unicode block graphic characters.
|
||||||
|
|
||||||
Algorithm (for each 4x8 pixel cell mapped to a unicode block graphics character):
|
Algorithm (for each 4x8 pixel cell mapped to a unicode block graphics character):
|
||||||
|
|
||||||
|
@ -9,8 +9,9 @@ Algorithm (for each 4x8 pixel cell mapped to a unicode block graphics character)
|
||||||
3. Average the colors above and below and create a corresponding bitmap for the cell
|
3. Average the colors above and below and create a corresponding bitmap for the cell
|
||||||
3. Compare the bitmap to the assumed bitmaps for various unicode block graphics characters
|
3. Compare the bitmap to the assumed bitmaps for various unicode block graphics characters
|
||||||
|
|
||||||
|
\*) **C++ port** available at at https://github.com/stefanhaustein/tiv
|
||||||
|
|
||||||
Usage:
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
javac TerminalImageViewer.java
|
javac TerminalImageViewer.java
|
||||||
|
@ -19,6 +20,13 @@ java TerminalImageViewer [-w <width-in-characters>] <image-filename-or-url>
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Common problems
|
||||||
|
|
||||||
|
- If you see strange horizontal lines, the characters don't fully fill the character cell. Remove additional line spacing in your terminal app
|
||||||
|
- Wrong colors? Try -256 to use a 256 color palette instead of 24 bit colors or -grayscale for grayscale.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
![Examples](http://i.imgur.com/8UyGjg8.png)
|
![Examples](http://i.imgur.com/8UyGjg8.png)
|
||||||
|
|
||||||
If multiple images match the filename spec, thumbnails are shown.
|
If multiple images match the filename spec, thumbnails are shown.
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class TerminalImageViewer {
|
||||||
"Image file name required.\n\n" +
|
"Image file name required.\n\n" +
|
||||||
" - Use -w and -h to set the maximum width and height in characters (defaults: 80, 24).\n" +
|
" - Use -w and -h to set the maximum width and height in characters (defaults: 80, 24).\n" +
|
||||||
" - Use -256 for 256 color mode, -grayscale for grayscale and -stdin to obtain file names from stdin.\n" +
|
" - Use -256 for 256 color mode, -grayscale for grayscale and -stdin to obtain file names from stdin.\n" +
|
||||||
" - When multiple files are supplied, -c sets the number of images per row (default: 4).");
|
" - When multiple files are supplied, -c sets the number of images per row (default: 4).");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ public class TerminalImageViewer {
|
||||||
} else if (start == args.length - 1 && (isUrl(args[start]) || !new File(args[start]).isDirectory())) {
|
} else if (start == args.length - 1 && (isUrl(args[start]) || !new File(args[start]).isDirectory())) {
|
||||||
convert(args[start], maxWidth, maxHeight);
|
convert(args[start], maxWidth, maxHeight);
|
||||||
} else {
|
} else {
|
||||||
// Directory-style rendering.
|
// Directory-style rendering.
|
||||||
int index = 0;
|
int index = 0;
|
||||||
int cw = (maxWidth - 2 * (columns - 1) * 4) / (4 * columns);
|
int cw = (maxWidth - 2 * (columns - 1) * 4) / (4 * columns);
|
||||||
int tw = cw * 4;
|
int tw = cw * 4;
|
||||||
|
@ -102,7 +102,7 @@ public class TerminalImageViewer {
|
||||||
sb.setLength(sl - 2);
|
sb.setLength(sl - 2);
|
||||||
sb.append(" ");
|
sb.append(" ");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Probably no image; ignore.
|
// Probably no image; ignore.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dump(image, mode);
|
dump(image, mode);
|
||||||
|
@ -165,7 +165,7 @@ public class TerminalImageViewer {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ANSI control code helpers
|
* ANSI control code helpers
|
||||||
*/
|
*/
|
||||||
static class Ansi {
|
static class Ansi {
|
||||||
public static final String RESET = "\u001b[0m";
|
public static final String RESET = "\u001b[0m";
|
||||||
|
@ -182,7 +182,7 @@ public class TerminalImageViewer {
|
||||||
int index = Arrays.binarySearch(options, v);
|
int index = Arrays.binarySearch(options, v);
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
index = -index - 1;
|
index = -index - 1;
|
||||||
// need to check [index] and [index - 1]
|
// need to check [index] and [index - 1]
|
||||||
if (index == options.length) {
|
if (index == options.length) {
|
||||||
index = options.length - 1;
|
index = options.length - 1;
|
||||||
} else if (index > 0) {
|
} else if (index > 0) {
|
||||||
|
@ -232,7 +232,7 @@ public class TerminalImageViewer {
|
||||||
0.3 * sqr(grayQ-r) + 0.59 * sqr(grayQ-g) + 0.11 * sqr(grayQ-b)) {
|
0.3 * sqr(grayQ-r) + 0.59 * sqr(grayQ-g) + 0.11 * sqr(grayQ-b)) {
|
||||||
colorIndex = 16 + 36 * rIdx + 6 * gIdx + bIdx;
|
colorIndex = 16 + 36 * rIdx + 6 * gIdx + bIdx;
|
||||||
} else {
|
} else {
|
||||||
colorIndex = 232 + grayIdx; // 1..24 -> 232..255
|
colorIndex = 232 + grayIdx; // 1..24 -> 232..255
|
||||||
}
|
}
|
||||||
return (bg ? "\u001B[48;5;" : "\u001B[38;5;") + colorIndex + "m";
|
return (bg ? "\u001B[48;5;" : "\u001B[38;5;") + colorIndex + "m";
|
||||||
}
|
}
|
||||||
|
@ -252,94 +252,94 @@ public class TerminalImageViewer {
|
||||||
static int[] BITMAPS = new int[] {
|
static int[] BITMAPS = new int[] {
|
||||||
0x00000000, '\u00a0',
|
0x00000000, '\u00a0',
|
||||||
|
|
||||||
// Block graphics
|
// Block graphics
|
||||||
|
|
||||||
// 0xffff0000, '\u2580', // upper 1/2; redundant with inverse lower 1/2
|
// 0xffff0000, '\u2580', // upper 1/2; redundant with inverse lower 1/2
|
||||||
|
|
||||||
0x0000000f, '\u2581', // lower 1/8
|
0x0000000f, '\u2581', // lower 1/8
|
||||||
0x000000ff, '\u2582', // lower 1/4
|
0x000000ff, '\u2582', // lower 1/4
|
||||||
0x00000fff, '\u2583',
|
0x00000fff, '\u2583',
|
||||||
0x0000ffff, '\u2584', // lower 1/2
|
0x0000ffff, '\u2584', // lower 1/2
|
||||||
0x000fffff, '\u2585',
|
0x000fffff, '\u2585',
|
||||||
0x00ffffff, '\u2586', // lower 3/4
|
0x00ffffff, '\u2586', // lower 3/4
|
||||||
0x0fffffff, '\u2587',
|
0x0fffffff, '\u2587',
|
||||||
// 0xffffffff, '\u2588', // full; redundant with inverse space
|
// 0xffffffff, '\u2588', // full; redundant with inverse space
|
||||||
|
|
||||||
0xeeeeeeee, '\u258a', // left 3/4
|
0xeeeeeeee, '\u258a', // left 3/4
|
||||||
0xcccccccc, '\u258c', // left 1/2
|
0xcccccccc, '\u258c', // left 1/2
|
||||||
0x88888888, '\u258e', // left 1/4
|
0x88888888, '\u258e', // left 1/4
|
||||||
|
|
||||||
0x0000cccc, '\u2596', // quadrant lower left
|
0x0000cccc, '\u2596', // quadrant lower left
|
||||||
0x00003333, '\u2597', // quadrant lower right
|
0x00003333, '\u2597', // quadrant lower right
|
||||||
0xcccc0000, '\u2598', // quadrant upper left
|
0xcccc0000, '\u2598', // quadrant upper left
|
||||||
// 0xccccffff, '\u2599', // 3/4 redundant with inverse 1/4
|
// 0xccccffff, '\u2599', // 3/4 redundant with inverse 1/4
|
||||||
0xcccc3333, '\u259a', // diagonal 1/2
|
0xcccc3333, '\u259a', // diagonal 1/2
|
||||||
// 0xffffcccc, '\u259b', // 3/4 redundant
|
// 0xffffcccc, '\u259b', // 3/4 redundant
|
||||||
// 0xffff3333, '\u259c', // 3/4 redundant
|
// 0xffff3333, '\u259c', // 3/4 redundant
|
||||||
0x33330000, '\u259d', // quadrant upper right
|
0x33330000, '\u259d', // quadrant upper right
|
||||||
// 0x3333cccc, '\u259e', // 3/4 redundant
|
// 0x3333cccc, '\u259e', // 3/4 redundant
|
||||||
// 0x3333ffff, '\u259f', // 3/4 redundant
|
// 0x3333ffff, '\u259f', // 3/4 redundant
|
||||||
|
|
||||||
// Line drawing subset: no double lines, no complex light lines
|
// Line drawing subset: no double lines, no complex light lines
|
||||||
// Simple light lines duplicated because there is no center pixel int the 4x8 matrix
|
// Simple light lines duplicated because there is no center pixel int the 4x8 matrix
|
||||||
|
|
||||||
0x000ff000, '\u2501', // Heavy horizontal
|
0x000ff000, '\u2501', // Heavy horizontal
|
||||||
0x66666666, '\u2503', // Heavy vertical
|
0x66666666, '\u2503', // Heavy vertical
|
||||||
|
|
||||||
0x00077666, '\u250f', // Heavy down and right
|
0x00077666, '\u250f', // Heavy down and right
|
||||||
0x000ee666, '\u2513', // Heavy down and left
|
0x000ee666, '\u2513', // Heavy down and left
|
||||||
0x66677000, '\u2517', // Heavy up and right
|
0x66677000, '\u2517', // Heavy up and right
|
||||||
0x666ee000, '\u251b', // Heavy up and left
|
0x666ee000, '\u251b', // Heavy up and left
|
||||||
|
|
||||||
0x66677666, '\u2523', // Heavy vertical and right
|
0x66677666, '\u2523', // Heavy vertical and right
|
||||||
0x666ee666, '\u252b', // Heavy vertical and left
|
0x666ee666, '\u252b', // Heavy vertical and left
|
||||||
0x000ff666, '\u2533', // Heavy down and horizontal
|
0x000ff666, '\u2533', // Heavy down and horizontal
|
||||||
0x666ff000, '\u253b', // Heavy up and horizontal
|
0x666ff000, '\u253b', // Heavy up and horizontal
|
||||||
0x666ff666, '\u254b', // Heavy cross
|
0x666ff666, '\u254b', // Heavy cross
|
||||||
|
|
||||||
0x000cc000, '\u2578', // Bold horizontal left
|
0x000cc000, '\u2578', // Bold horizontal left
|
||||||
0x00066000, '\u2579', // Bold horizontal up
|
0x00066000, '\u2579', // Bold horizontal up
|
||||||
0x00033000, '\u257a', // Bold horizontal right
|
0x00033000, '\u257a', // Bold horizontal right
|
||||||
0x00066000, '\u257b', // Bold horizontal down
|
0x00066000, '\u257b', // Bold horizontal down
|
||||||
|
|
||||||
0x06600660, '\u254f', // Heavy double dash vertical
|
0x06600660, '\u254f', // Heavy double dash vertical
|
||||||
|
|
||||||
0x000f0000, '\u2500', // Light horizontal
|
0x000f0000, '\u2500', // Light horizontal
|
||||||
0x0000f000, '\u2500', //
|
0x0000f000, '\u2500', //
|
||||||
0x44444444, '\u2502', // Light vertical
|
0x44444444, '\u2502', // Light vertical
|
||||||
0x22222222, '\u2502',
|
0x22222222, '\u2502',
|
||||||
|
|
||||||
0x000e0000, '\u2574', // light left
|
0x000e0000, '\u2574', // light left
|
||||||
0x0000e000, '\u2574', // light left
|
0x0000e000, '\u2574', // light left
|
||||||
0x44440000, '\u2575', // light up
|
0x44440000, '\u2575', // light up
|
||||||
0x22220000, '\u2575', // light up
|
0x22220000, '\u2575', // light up
|
||||||
0x00030000, '\u2576', // light right
|
0x00030000, '\u2576', // light right
|
||||||
0x00003000, '\u2576', // light right
|
0x00003000, '\u2576', // light right
|
||||||
0x00004444, '\u2575', // light down
|
0x00004444, '\u2575', // light down
|
||||||
0x00002222, '\u2575', // light down
|
0x00002222, '\u2575', // light down
|
||||||
|
|
||||||
// Misc technical
|
// Misc technical
|
||||||
|
|
||||||
0x44444444, '\u23a2', // [ extension
|
0x44444444, '\u23a2', // [ extension
|
||||||
0x22222222, '\u23a5', // ] extension
|
0x22222222, '\u23a5', // ] extension
|
||||||
|
|
||||||
//12345678
|
//12345678
|
||||||
0x0f000000, '\u23ba', // Horizontal scanline 1
|
0x0f000000, '\u23ba', // Horizontal scanline 1
|
||||||
0x00f00000, '\u23bb', // Horizontal scanline 3
|
0x00f00000, '\u23bb', // Horizontal scanline 3
|
||||||
0x00000f00, '\u23bc', // Horizontal scanline 7
|
0x00000f00, '\u23bc', // Horizontal scanline 7
|
||||||
0x000000f0, '\u23bd', // Horizontal scanline 9
|
0x000000f0, '\u23bd', // Horizontal scanline 9
|
||||||
|
|
||||||
// Geometrical shapes. Tricky because some of them are too wide.
|
// Geometrical shapes. Tricky because some of them are too wide.
|
||||||
|
|
||||||
// 0x00ffff00, '\u25fe', // Black medium small square
|
// 0x00ffff00, '\u25fe', // Black medium small square
|
||||||
0x00066000, '\u25aa', // Black small square
|
0x00066000, '\u25aa', // Black small square
|
||||||
|
|
||||||
/*
|
/*
|
||||||
0x11224488, '\u2571', // diagonals
|
0x11224488, '\u2571', // diagonals
|
||||||
0x88442211, '\u2572',
|
0x88442211, '\u2572',
|
||||||
0x99666699, '\u2573',
|
0x99666699, '\u2573',
|
||||||
|
|
||||||
0x000137f0, '\u25e2', // Triangles
|
0x000137f0, '\u25e2', // Triangles
|
||||||
0x0008cef0, '\u25e3',
|
0x0008cef0, '\u25e3',
|
||||||
0x000fec80, '\u25e4',
|
0x000fec80, '\u25e4',
|
||||||
0x000f7310, '\u25e5'
|
0x000f7310, '\u25e5'
|
||||||
|
@ -372,7 +372,7 @@ public class TerminalImageViewer {
|
||||||
Arrays.fill(bgColor, 0);
|
Arrays.fill(bgColor, 0);
|
||||||
Arrays.fill(fgColor, 0);
|
Arrays.fill(fgColor, 0);
|
||||||
|
|
||||||
// Determine the minimum and maximum value for each color channel
|
// Determine the minimum and maximum value for each color channel
|
||||||
int pos = p0;
|
int pos = p0;
|
||||||
for (int y = 0; y < 8; y++) {
|
for (int y = 0; y < 8; y++) {
|
||||||
for (int x = 0; x < 4; x++) {
|
for (int x = 0; x < 4; x++) {
|
||||||
|
@ -381,12 +381,12 @@ public class TerminalImageViewer {
|
||||||
min[i] = Math.min(min[i], d);
|
min[i] = Math.min(min[i], d);
|
||||||
max[i] = Math.max(max[i], d);
|
max[i] = Math.max(max[i], d);
|
||||||
}
|
}
|
||||||
pos++; // Alpha
|
pos++; // Alpha
|
||||||
}
|
}
|
||||||
pos += scanWidth - 16;
|
pos += scanWidth - 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the color channel with the greatest range.
|
// Determine the color channel with the greatest range.
|
||||||
int splitIndex = 0;
|
int splitIndex = 0;
|
||||||
int bestSplit = 0;
|
int bestSplit = 0;
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
|
@ -395,10 +395,10 @@ public class TerminalImageViewer {
|
||||||
splitIndex = i;
|
splitIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We just split at the middle of the interval instead of computing the median.
|
// We just split at the middle of the interval instead of computing the median.
|
||||||
int splitValue = min[splitIndex] + bestSplit / 2;
|
int splitValue = min[splitIndex] + bestSplit / 2;
|
||||||
|
|
||||||
// Compute a bitmap using the given split and sum the color values for both buckets.
|
// Compute a bitmap using the given split and sum the color values for both buckets.
|
||||||
int bits = 0;
|
int bits = 0;
|
||||||
int fgCount = 0;
|
int fgCount = 0;
|
||||||
int bgCount = 0;
|
int bgCount = 0;
|
||||||
|
@ -419,12 +419,12 @@ public class TerminalImageViewer {
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
avg[i] += data[pos++] & 255;
|
avg[i] += data[pos++] & 255;
|
||||||
}
|
}
|
||||||
pos++; // Alpha
|
pos++; // Alpha
|
||||||
}
|
}
|
||||||
pos += scanWidth - 16;
|
pos += scanWidth - 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the average color value for each bucket
|
// Calculate the average color value for each bucket
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
if (bgCount != 0) {
|
if (bgCount != 0) {
|
||||||
bgColor[i] /= bgCount;
|
bgColor[i] /= bgCount;
|
||||||
|
@ -434,8 +434,8 @@ public class TerminalImageViewer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the best bitmap match by counting the bits that don't match, including
|
// Find the best bitmap match by counting the bits that don't match, including
|
||||||
// the inverted bitmaps.
|
// the inverted bitmaps.
|
||||||
int bestDiff = Integer.MAX_VALUE;
|
int bestDiff = Integer.MAX_VALUE;
|
||||||
boolean invert = false;
|
boolean invert = false;
|
||||||
for (int i = 0; i < BITMAPS.length; i += 2) {
|
for (int i = 0; i < BITMAPS.length; i += 2) {
|
||||||
|
@ -453,13 +453,13 @@ public class TerminalImageViewer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the match is quite bad, use a shade image instead.
|
// If the match is quite bad, use a shade image instead.
|
||||||
if (bestDiff > 10) {
|
if (bestDiff > 10) {
|
||||||
invert = false;
|
invert = false;
|
||||||
character = " \u2591\u2592\u2593\u2588".charAt(Math.min(4, fgCount * 5 / 32));
|
character = " \u2591\u2592\u2593\u2588".charAt(Math.min(4, fgCount * 5 / 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we use an inverted character, we need to swap the colors.
|
// If we use an inverted character, we need to swap the colors.
|
||||||
if (invert) {
|
if (invert) {
|
||||||
int[] tmp = bgColor;
|
int[] tmp = bgColor;
|
||||||
bgColor = fgColor;
|
bgColor = fgColor;
|
||||||
|
|
Loading…
Reference in a new issue