diff --git a/src/java/clj_image2ascii/java/ImageToAscii.java b/src/java/clj_image2ascii/java/ImageToAscii.java new file mode 100644 index 0000000..a9d0f20 --- /dev/null +++ b/src/java/clj_image2ascii/java/ImageToAscii.java @@ -0,0 +1,64 @@ +package clj_image2ascii.java; + +import java.awt.image.BufferedImage; + +// This method of converting an image to ascii representation is based on the method used in +// Claskii (https://github.com/LauJensen/Claskii). Some improvements have been made, such as +// a better way of calculating pixel brightness and little tweaks to how the right ASCII +// character is selected, as well as obviously a conversion to Java purely for performance. + +public class ImageToAscii { + static final char[] asciiChars = {'#', 'A', '@', '%', '$', '+', '=', '*', ':', ',', '.', ' '}; + static final int numAsciiChars = asciiChars.length - 1; + static final int spanLength = "X".length(); + static final int lineTerminatorLength = "
".length(); + + public static String convert(BufferedImage image, boolean useColor) { + final int width = image.getWidth(); + final int height = image.getHeight(); + + final int maxLength = (useColor ? + (width * height * spanLength) + (height * lineTerminatorLength) : + (width * height) + height); + + final MicroStringBuilder sb = new MicroStringBuilder(maxLength); + + final int[] pixels = image.getRGB(0, 0, width, height, null, 0, width); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + final int argb = pixels[(y * width) + x]; + final int r = (0x00ff0000 & argb) >> 16; + final int g = (0x0000ff00 & argb) >> 8; + final int b = (0x000000ff & argb); + final double brightness = Math.sqrt((r * r * 0.241f) + + (g * g * 0.691f) + + (b * b * 0.068f)); + int charIndex; + if (brightness == 0.0f) + charIndex = numAsciiChars; + else + charIndex = (int)((brightness / 255.0f) * numAsciiChars); + + final char pixelChar = asciiChars[charIndex > 0 ? charIndex : 0]; + + if (useColor) { + sb.append(""); + sb.append(pixelChar); + sb.append(""); + } else + sb.append(pixelChar); + } + if (useColor) + sb.append("
"); + else + sb.append('\n'); + } + + return sb.toString(); + } +} diff --git a/src/java/clj_image2ascii/java/MicroStringBuilder.java b/src/java/clj_image2ascii/java/MicroStringBuilder.java new file mode 100644 index 0000000..9f106b6 --- /dev/null +++ b/src/java/clj_image2ascii/java/MicroStringBuilder.java @@ -0,0 +1,58 @@ +package clj_image2ascii.java; + +// I wanted a StringBuilder that had an append() method which allowed appending unsigned byte values +// as a 2-char hex representation (e.g. 255 => "FF") in a way that did not allocate any extra memory +// and could be done "in-place" with the StringBuilder's internal character array. This kind of evolved +// into this class being added since I was only using 2 append() methods from java.lang.StringBuilder +// anyway. Except for the appendAsHex(), this class does not perform noticeably faster then +// java.lang.StringBuilder does. + +public class MicroStringBuilder { + // copied from java.lang.Integer.digits (which is private so we can't just reference it, boourns) + static final char[] digits = { + '0' , '1' , '2' , '3' , '4' , '5' , + '6' , '7' , '8' , '9' , 'a' , 'b' , + 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , + 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , + 'o' , 'p' , 'q' , 'r' , 's' , 't' , + 'u' , 'v' , 'w' , 'x' , 'y' , 'z' + }; + + final char[] chars; + int length; + + public MicroStringBuilder(int capacity) { + chars = new char[capacity]; + length = 0; + } + + public void append(final String s) { + final int srcLength = s.length(); + s.getChars(0, srcLength, chars, length); + length += srcLength; + } + + public void append(final char c) { + chars[length] = c; + ++length; + } + + // modification of java.lang.Integer.toUnsignedString -- no garbage generated, but limited to max value + // of 255 ...hence the 'unsigned byte' thing :) + public void appendAsHex(int unsignedByte) { + for (int i = 0; i < 2; ++i) { + final int index = length + 1 - i; + if (unsignedByte != 0) { + chars[index] = digits[unsignedByte & 15]; + unsignedByte >>>= 4; + } else + chars[index] = '0'; + } + length += 2; + } + + @Override + public String toString() { + return new String(chars, 0, length); + } +}