Generates ASCII art from a given image.
Table of contents |
---|
Demo Features Quick start Documentation (with Screenshots) Motivation Development |
Original | ASCII art |
---|---|
"Wallpaper: The Way" by OiMax is licensed with CC BY 2.0. |
Screenshot of the resulting text file |
- Supported image formats: bmp, gif, jpg, jpeg, png, tiff, wbmp
- Independent width and height pre-scaling
- Multiple conversion parameters
- Interpolation types for pre-scaling the image: Bicubic, Bilinear, Nearest Neighbor
- Rounding method for quantization: Ceil, Floor, Round
- Character variation: 10 characters, 70 characters
The program requires at least the installation of Java 8 or higher to run Gradle.
To start the application simply run the following command from the command line.
Linux / macOS | Windows |
---|---|
./gradlew run |
.\gradlew.bat run |
Choose an image file in one of the supported formats to start.
Supported formats: bmp, gif, jpg, jpeg, png, tiff, wbmp
Change the factors for pre-scaling the input image. It may be useful to change the aspect ratio of the input image since pixels are converted 1:1 into characters and the font you are displaying the ASCII image with is likely to not have an aspect ratio of 1:1. You can also take a tradeoff in image resolution and therefore reduce the resulting file size.
Input range for width and height: 5-500% (Default: 100%)
Half height: Set the height to one half the width resulting in a 2:1 ratio.
Proportional: Set the height to the width resulting in a 1:1 ratio.
Change the appearance of the resulting ASCII image by changing the following settings to influence the conversion algorithm.
Interpolation type for image scaling:
Bicubic: Uses Bicubic interpolation for scaling. This results in a smoother image. This takes 16 pixels into account.
Bilinear (default): Uses Bilinear interpolation for scaling. This is good for most purposes but may lead to some interpolation artifacts. This takes 4 pixels into account.
Nearest Neighbor: Uses Nearest-neighbor interpolation for scaling. This is useful for pixel art and preserves the blocky details.
Rounding method for quantization:
Ceil: Use Math::ceil
for rounding values in the quantization process. This leads to more white details and reduces almost black details.
Floor: Use Math::floor
for rounding values in the quantization process. This leads to more black details and reduces almost white details.
Round (default): Use Math::round
for rounding values in the quantization process. This leads to an even distribution between almost white and almost black details.
Character variation in resulting image:
10 characters (default): Uses the character sequence @%#*+=-:.
to represent the different levels of grey in the ASCII image.
70 characters: Uses the character sequence $@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'.
to represent the different levels of grey in the ASCII image.
The image has been successfully converted and the resulting file name of the ASCII image is indicated.
There were many aspects that motivated me to start this project and to program some things the way they are now:
- Create awesome ASCII art with a simple algorithm.
- Recreate a known grayscale algorithm from mathematical notation in self-describing code referencing the given notation (after realizing that an algorithm like that is not as simple as taking the average of all color channels).
- Work with some low level bit operations in Java.
- Explore the usage of Java Swing in a small side project in contrast to JavaFX.
- Explore the benefits and drawbacks of using
java.util.Optional
instead ofnull
as a return value.
Task | Description |
---|---|
run |
Runs this project as a JVM application. |
debug |
Runs this project as a JVM application with debugging enabled. - add program arguments with -Pargs="..." - attach a remote debugger via port localhost:5005 |
showJavadoc |
Opens the generated Javadoc API documentation in the default browser. |
The color of a pixel is represented as a 32-bit integer as descibed by the
ARGB32 color model.
The 32 bits represent 4 channels of 8 bits each where the first one is alpha (A
) and
the three remaining channels are for red (R
), green (G
) and blue (B
).
The range of values for each channel is based on the sample length.
So in this case the 8 bit wide channels allow for values ranging from 0
to 255
in decimal or
00
to FF
in hexadecimal notation.
00
means black (in case of channels R
, G
or B
) or
fully transparent (in case of the alpha channel A
).
FF
means full brightness of the color or fully opaque.
To get the different channel values out of a full representation of a pixel color some bit manipulation is needed. In Java this may look like the following:
// Get a pixel sample
BufferedImage image;
int pixel = image.getRGB(0, 0); // 0xff306090 (#FF306090)
// Extract the channel values
int alpha = pixel >>> 24; // 0xff (255)
int red = (pixel >>> 16) & 0xff; // 0x30 (48)
int green = (pixel >>> 8) & 0xff; // 0x60 (96)
int blue = pixel & 0xff; // 0x90 (144)
A more detailed explanation with visualized bit operations steps:
pixel = [11111111 00110000 01100000 10010000]
pixel >>> 24 = [00000000 00000000 00000000 11111111] = alpha
pixel = [11111111 00110000 01100000 10010000]
pixel >>> 16 = [00000000 00000000 11111111 00110000]
& [00000000 00000000 00000000 11111111]
[00000000 00000000 00000000 00110000] = red
pixel = [11111111 00110000 01100000 10010000]
pixel >>> 8 = [00000000 11111111 00110000 01100000]
& [00000000 00000000 00000000 11111111]
[00000000 00000000 00000000 01100000] = green
pixel = [11111111 00110000 01100000 10010000]
& [00000000 00000000 00000000 11111111]
[00000000 00000000 00000000 10010000] = blue
In order to convert a color that is typically gamma-compressed and therefore represented in a nonlinear color model to a grayscale representation with preserved luminance several steps for calculation are necessary. The algorithm used in this application is based on this article about "colorimetric conversion to grayscale".
At first the gamma-compression must be removed by expanding it.
After that the color is in a linear colorspace and
a weighted sum of the color channel values gives the overall linear luminance.
The final step is to convert it back to a gamma-compressed color and
set all color channels to this value.
The color is now converted to grayscale.
Implementation details may be seen in the Grayscale.java
class.
This generator uses different characters to represent the brightness of pixels. Some characters are more dense and cover more area than others and therefore are used for darker pixels. Some characters such as a simple dot or a space are used for brighter pixels. This application makes use of two known character sequences with a length (or variation in levels of grey) of 10 and 70 characters. They are ordered from dark to bright and each includes a space character for white pixels.
10 characters: @%#*+=-:.
70 characters: $@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'.