Merge branch 'master' of https://github.com/stefanhaustein/TerminalImageViewer
This commit is contained in:
		
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| # 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): | ||||
|  | ||||
| @@ -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. 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 | ||||
| @@ -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 | ||||
|  | ||||
|  | ||||
|  | ||||
| If multiple images match the filename spec, thumbnails are shown. | ||||
|   | ||||
| @@ -32,7 +32,7 @@ public class TerminalImageViewer { | ||||
|           "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, -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; | ||||
|     } | ||||
|  | ||||
| @@ -76,7 +76,7 @@ public class TerminalImageViewer { | ||||
|     } else if (start == args.length - 1 && (isUrl(args[start]) || !new File(args[start]).isDirectory())) { | ||||
|       convert(args[start], maxWidth, maxHeight); | ||||
|     } else { | ||||
|       // Directory-style rendering. | ||||
|       // Directory-style rendering. | ||||
|       int index = 0; | ||||
|       int cw = (maxWidth - 2 * (columns - 1) * 4) / (4 * columns); | ||||
|       int tw = cw * 4; | ||||
| @@ -102,7 +102,7 @@ public class TerminalImageViewer { | ||||
|             sb.setLength(sl - 2); | ||||
|             sb.append("  "); | ||||
|           } catch (Exception e) { | ||||
|             // Probably no image; ignore. | ||||
|             // Probably no image; ignore. | ||||
|           } | ||||
|         } | ||||
|         dump(image, mode); | ||||
| @@ -165,7 +165,7 @@ public class TerminalImageViewer { | ||||
|  | ||||
|  | ||||
|   /** | ||||
|    * ANSI control code helpers | ||||
|    * ANSI control code helpers | ||||
|    */ | ||||
|   static class Ansi { | ||||
|     public static final String RESET = "\u001b[0m"; | ||||
| @@ -182,7 +182,7 @@ public class TerminalImageViewer { | ||||
|       int index = Arrays.binarySearch(options, v); | ||||
|       if (index < 0) { | ||||
|         index = -index - 1; | ||||
|         // need to check [index] and [index - 1] | ||||
|         // need to check [index] and [index - 1] | ||||
|         if (index == options.length) { | ||||
|           index = options.length - 1; | ||||
|         } 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)) { | ||||
|         colorIndex = 16 + 36 * rIdx + 6 * gIdx + bIdx; | ||||
|       } 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"; | ||||
|     } | ||||
| @@ -252,94 +252,94 @@ public class TerminalImageViewer { | ||||
|     static int[] BITMAPS = new int[] { | ||||
|         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 | ||||
|         0x000000ff, '\u2582',  // lower 1/4 | ||||
|         0x0000000f, '\u2581',  // lower 1/8 | ||||
|         0x000000ff, '\u2582',  // lower 1/4 | ||||
|         0x00000fff, '\u2583', | ||||
|         0x0000ffff, '\u2584',  // lower 1/2 | ||||
|         0x0000ffff, '\u2584',  // lower 1/2 | ||||
|         0x000fffff, '\u2585', | ||||
|         0x00ffffff, '\u2586',  // lower 3/4 | ||||
|         0x00ffffff, '\u2586',  // lower 3/4 | ||||
|         0x0fffffff, '\u2587', | ||||
|      // 0xffffffff, '\u2588',  // full; redundant with inverse space | ||||
|      // 0xffffffff, '\u2588',  // full; redundant with inverse space | ||||
|  | ||||
|         0xeeeeeeee, '\u258a',  // left 3/4 | ||||
|         0xcccccccc, '\u258c',  // left 1/2 | ||||
|         0x88888888, '\u258e',  // left 1/4 | ||||
|         0xeeeeeeee, '\u258a',  // left 3/4 | ||||
|         0xcccccccc, '\u258c',  // left 1/2 | ||||
|         0x88888888, '\u258e',  // left 1/4 | ||||
|  | ||||
|         0x0000cccc, '\u2596',  // quadrant lower left | ||||
|         0x00003333, '\u2597',  // quadrant lower right | ||||
|         0xcccc0000, '\u2598',  // quadrant upper left | ||||
|      // 0xccccffff, '\u2599',  // 3/4 redundant with inverse 1/4 | ||||
|         0xcccc3333, '\u259a',  // diagonal 1/2 | ||||
|      // 0xffffcccc, '\u259b',  // 3/4 redundant | ||||
|      // 0xffff3333, '\u259c',  // 3/4 redundant | ||||
|         0x33330000, '\u259d',  // quadrant upper right | ||||
|      // 0x3333cccc, '\u259e',  // 3/4 redundant | ||||
|      // 0x3333ffff, '\u259f',  // 3/4 redundant | ||||
|         0x0000cccc, '\u2596',  // quadrant lower left | ||||
|         0x00003333, '\u2597',  // quadrant lower right | ||||
|         0xcccc0000, '\u2598',  // quadrant upper left | ||||
|      // 0xccccffff, '\u2599',  // 3/4 redundant with inverse 1/4 | ||||
|         0xcccc3333, '\u259a',  // diagonal 1/2 | ||||
|      // 0xffffcccc, '\u259b',  // 3/4 redundant | ||||
|      // 0xffff3333, '\u259c',  // 3/4 redundant | ||||
|         0x33330000, '\u259d',  // quadrant upper right | ||||
|      // 0x3333cccc, '\u259e',  // 3/4 redundant | ||||
|      // 0x3333ffff, '\u259f',  // 3/4 redundant | ||||
|  | ||||
|         // Line drawing subset: no double lines, no complex light lines | ||||
|         // Simple light lines duplicated because there is no center pixel int the 4x8 matrix | ||||
|         // Line drawing subset: no double lines, no complex light lines | ||||
|         // Simple light lines duplicated because there is no center pixel int the 4x8 matrix | ||||
|  | ||||
|         0x000ff000, '\u2501',  // Heavy horizontal | ||||
|         0x66666666, '\u2503',  // Heavy vertical | ||||
|         0x000ff000, '\u2501',  // Heavy horizontal | ||||
|         0x66666666, '\u2503',  // Heavy vertical | ||||
|  | ||||
|         0x00077666, '\u250f',  // Heavy down and right | ||||
|         0x000ee666, '\u2513',  // Heavy down and left | ||||
|         0x66677000, '\u2517',  // Heavy up and right | ||||
|         0x666ee000, '\u251b',  // Heavy up and left | ||||
|         0x00077666, '\u250f',  // Heavy down and right | ||||
|         0x000ee666, '\u2513',  // Heavy down and left | ||||
|         0x66677000, '\u2517',  // Heavy up and right | ||||
|         0x666ee000, '\u251b',  // Heavy up and left | ||||
|  | ||||
|         0x66677666, '\u2523',  // Heavy vertical and right | ||||
|         0x666ee666, '\u252b',  // Heavy vertical and left | ||||
|         0x000ff666, '\u2533',  // Heavy down and horizontal | ||||
|         0x666ff000, '\u253b',  // Heavy up and horizontal | ||||
|         0x666ff666, '\u254b',  // Heavy cross | ||||
|         0x66677666, '\u2523',  // Heavy vertical and right | ||||
|         0x666ee666, '\u252b',  // Heavy vertical and left | ||||
|         0x000ff666, '\u2533',  // Heavy down and horizontal | ||||
|         0x666ff000, '\u253b',  // Heavy up and horizontal | ||||
|         0x666ff666, '\u254b',  // Heavy cross | ||||
|  | ||||
|         0x000cc000, '\u2578',  // Bold horizontal left | ||||
|         0x00066000, '\u2579',  // Bold horizontal up | ||||
|         0x00033000, '\u257a',  // Bold horizontal right | ||||
|         0x00066000, '\u257b',  // Bold horizontal down | ||||
|         0x000cc000, '\u2578',  // Bold horizontal left | ||||
|         0x00066000, '\u2579',  // Bold horizontal up | ||||
|         0x00033000, '\u257a',  // Bold horizontal right | ||||
|         0x00066000, '\u257b',  // Bold horizontal down | ||||
|  | ||||
|         0x06600660, '\u254f',  // Heavy double dash vertical | ||||
|         0x06600660, '\u254f',  // Heavy double dash vertical | ||||
|  | ||||
|         0x000f0000, '\u2500',  // Light horizontal | ||||
|         0x0000f000, '\u2500',  // | ||||
|         0x44444444, '\u2502',  // Light vertical | ||||
|         0x44444444, '\u2502',  // Light vertical | ||||
|         0x22222222, '\u2502', | ||||
|  | ||||
|         0x000e0000, '\u2574',  // light left | ||||
|         0x0000e000, '\u2574',  // light left | ||||
|         0x44440000, '\u2575',  // light up | ||||
|         0x22220000, '\u2575',  // light up | ||||
|         0x00030000, '\u2576',  // light right | ||||
|         0x00003000, '\u2576',  // light right | ||||
|         0x00004444, '\u2575',  // light down | ||||
|         0x00002222, '\u2575',  // light down | ||||
|         0x000e0000, '\u2574',  // light left | ||||
|         0x0000e000, '\u2574',  // light left | ||||
|         0x44440000, '\u2575',  // light up | ||||
|         0x22220000, '\u2575',  // light up | ||||
|         0x00030000, '\u2576',  // light right | ||||
|         0x00003000, '\u2576',  // light right | ||||
|         0x00004444, '\u2575',  // light down | ||||
|         0x00002222, '\u2575',  // light down | ||||
|  | ||||
|         // Misc technical | ||||
|         // Misc technical | ||||
|  | ||||
|         0x44444444, '\u23a2',  // [ extension | ||||
|         0x22222222, '\u23a5',  // ] extension | ||||
|         0x44444444, '\u23a2',  // [ extension | ||||
|         0x22222222, '\u23a5',  // ] extension | ||||
|  | ||||
|         //12345678 | ||||
|         0x0f000000, '\u23ba',  // Horizontal scanline 1 | ||||
|         0x00f00000, '\u23bb',  // Horizontal scanline 3 | ||||
|         0x00000f00, '\u23bc',  // Horizontal scanline 7 | ||||
|         0x000000f0, '\u23bd',  // Horizontal scanline 9 | ||||
|         0x0f000000, '\u23ba',  // Horizontal scanline 1 | ||||
|         0x00f00000, '\u23bb',  // Horizontal scanline 3 | ||||
|         0x00000f00, '\u23bc',  // Horizontal scanline 7 | ||||
|         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 | ||||
|         0x00066000, '\u25aa',  // Black small square | ||||
| //      0x00ffff00, '\u25fe',  // Black medium small square | ||||
|         0x00066000, '\u25aa',  // Black small square | ||||
|  | ||||
| /* | ||||
|         0x11224488, '\u2571',  // diagonals | ||||
|         0x11224488, '\u2571',  // diagonals | ||||
|         0x88442211, '\u2572', | ||||
|         0x99666699, '\u2573', | ||||
|  | ||||
|         0x000137f0, '\u25e2',  // Triangles | ||||
|         0x000137f0, '\u25e2',  // Triangles | ||||
|         0x0008cef0, '\u25e3', | ||||
|         0x000fec80, '\u25e4', | ||||
|         0x000f7310, '\u25e5' | ||||
| @@ -372,7 +372,7 @@ public class TerminalImageViewer { | ||||
|       Arrays.fill(bgColor, 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; | ||||
|       for (int y = 0; y < 8; y++) { | ||||
|         for (int x = 0; x < 4; x++) { | ||||
| @@ -381,12 +381,12 @@ public class TerminalImageViewer { | ||||
|             min[i] = Math.min(min[i], d); | ||||
|             max[i] = Math.max(max[i], d); | ||||
|           } | ||||
|           pos++;  // Alpha | ||||
|           pos++;  // Alpha | ||||
|         } | ||||
|         pos += scanWidth - 16; | ||||
|       } | ||||
|  | ||||
|       // Determine the color channel with the greatest range. | ||||
|       // Determine the color channel with the greatest range. | ||||
|       int splitIndex = 0; | ||||
|       int bestSplit = 0; | ||||
|       for (int i = 0; i < 3; i++) { | ||||
| @@ -395,10 +395,10 @@ public class TerminalImageViewer { | ||||
|           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; | ||||
|  | ||||
|       // 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 fgCount = 0; | ||||
|       int bgCount = 0; | ||||
| @@ -419,12 +419,12 @@ public class TerminalImageViewer { | ||||
|           for (int i = 0; i < 3; i++) { | ||||
|             avg[i] += data[pos++] & 255; | ||||
|           } | ||||
|           pos++;  // Alpha | ||||
|           pos++;  // Alpha | ||||
|         } | ||||
|         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++) { | ||||
|         if (bgCount != 0) { | ||||
|           bgColor[i] /= bgCount; | ||||
| @@ -434,8 +434,8 @@ public class TerminalImageViewer { | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       // Find the best bitmap match by counting the bits that don't match, including | ||||
|       // the inverted bitmaps. | ||||
|       // Find the best bitmap match by counting the bits that don't match, including | ||||
|       // the inverted bitmaps. | ||||
|       int bestDiff = Integer.MAX_VALUE; | ||||
|       boolean invert = false; | ||||
|       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) { | ||||
|         invert = false; | ||||
|         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) { | ||||
|         int[] tmp = bgColor; | ||||
|         bgColor = fgColor; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 stefan.haustein@gmail.com
					stefan.haustein@gmail.com