8
\$\begingroup\$

enter image description here

Introduction

For the purpose of learning how to work with pictures in Java, I created an ASCII-Art Generator. The program can do two things:

  1. Convert pictures into ASCII-Art
  2. Convert text into ASCII-Art

I splitted the task into several steps:

  1. Convert text to image
  2. Read image and its height and width
  3. Saving data of each pixel
  4. Convert pixel data into ASCII-char
  5. Printing

Code

Control.java

This class is responsible for the user-interaction and the creation of an instance of the needed class(-es).

import java.io.IOException;
import java.util.InputMismatchException;
import java.util.Scanner;

public class Control {

    public static void main(String[] args) {
        System.out.println("Picture to ASCII (1) or text to ASCII (2)?");
        Scanner scan = new Scanner(System.in);
        int decision;
        while(true) {
            try {
                decision = scan.nextInt();
                if(decision > 2 || decision < 1) {
                    throw new InputMismatchException();
                }
                break;
            }
            catch(InputMismatchException e) {   //User enters not a number or a number > 2 or < 1
                System.out.println("Enter (1) or (2):");
                scan.nextLine();    //Clear Scanner
            }
        }

        scan.nextLine(); //To clear scanner

        switch(decision) {
            case(1):
                System.out.println("Want to reverse brightness? (yes = 1, no = 2)");
                int reverse;
                while(true) {
                    try {
                        reverse = scan.nextInt();
                        if(reverse > 2 || reverse < 1) {
                            throw new InputMismatchException();
                        }
                        break;
                    }
                    catch(InputMismatchException e) {   //User enters not a number or a number > 2 or < 1
                        System.out.println("Enter (1) or (2):");
                        scan.nextLine();
                    }
                }
                boolean reverseBrightness;
                reverseBrightness = reverse == 1 ? true : false; //if(reserve == 1) {reverseBrightness = true} else {reverseBrightness = false};

                System.out.println("Enter filename:");
                scan.nextLine();
                String filename;
                while(true) {
                    try {
                        filename = scan.nextLine();
                        Picture picture = new Picture(filename, reverseBrightness);
                        System.out.println("Successful!");
                        break;
                    }
                    catch(IOException e) {  //IIOException occurs when file not found
                        System.out.println("File not found. Enter filename:");
                    }
                }
                break;

            case(2):
                System.out.println("Enter text:");
                String str = scan.nextLine();
                try {
                    AsciiText text = new AsciiText(str);
                }
                catch(IOException e) {
                    e.printStackTrace();
                }
                System.out.println("Successful!");
                break;

            default:
                System.out.println("An Error occured!");    //Impossible to happen, just to adhere to best practice
                break;
        }
        scan.close();
    }
}

Picture.java

Responsible for the conversion of pictures into ASCII-art.

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import javax.imageio.ImageIO;

public class Picture {
    private BufferedImage img;
    private int height;
    private int width;
    private Triplet[][] array;
    private int[][] brightness;
    private final int MAX_BRIGHTNESS = 255;
    private char[][] ascii;
    private final String str = "$@B%8&MW#*oahkbdpqwmZ0OQLCJUYXzcvunxrjft/\\\\(|)1{}][?-_+~i!Il:;,\"\\^`"; //ASCII-chars


    public Picture(String filename, boolean reverse) throws IOException {
        img = ImageIO.read(new File(filename));
        this.height = img.getHeight();
        this.width = img.getWidth();

        this.fillArray();
        this.fillBrightnessArray();
        if(reverse) {
            this.reverseBrightness();
        }

        this.fillAscii();
        this.printAscii();
    }

    //Saving the proportion of red, green and blue of each pixel
    private void fillArray() {
        array = new Triplet[height][width];
        for(int i = 0; i < height; i++) {
            for(int j = 0; j < width; j++) {
                int getRGB = img.getRGB(j, i);
                int red = (getRGB>>16) & 0xff;
                int green = (getRGB>>8) & 0xff;
                int blue = getRGB & 0xff;
                array[i][j] = new Triplet(red, green, blue);
            }
        }
    }

    //Calculating the brightness of each pixel
    private void fillBrightnessArray() {
        brightness = new int[height][width];
        for(int i = 0; i < height; i++) {
            for(int j = 0; j < width; j++) {
                brightness[i][j] = (array[i][j].getFirst() + array[i][j].getSecond() + array[i][j].getThird()) / 3;
            }
        }
    }

    //Reversing the brightness-values
    private void reverseBrightness() {
        for(int i = 0; i < height; i++) {
            for(int j = 0; j < width; j++) {
                brightness[i][j] = MAX_BRIGHTNESS - brightness[i][j];
                if(brightness[i][j] < 0) {
                    brightness[i][j] *= -1;
                }
            }
        }
    }

    //Converting brightness into appropriate ASCII-char
    private void fillAscii() {
        ascii = new char[height][width];
        for(int i = 0; i < height; i++) {
            for(int j = 0; j < width; j++) {
                ascii[i][j] = str.charAt(brightness[i][j] / 4);
            }
        }
    }

    //print completed ASCII-art to file
    private void printAscii() {
        try {
            FileWriter writer = new FileWriter("ascii.txt");
            for(int i = 0; i < height; i++) {
                for(int j = 0; j < width; j++) {
                    writer.write(ascii[i][j] + "" + ascii[i][j] + "" + ascii[i][j]);
                }
                writer.write("\n");
            }
            writer.close();
        }
        catch(IOException e) {
            e.printStackTrace();
        }

    }
}

AsciiText.java

Responsible for the second task: The conversion of text.

import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class AsciiText {
    private final String filename = "text.jpg";

    public AsciiText(String text) throws IOException {
        int width = 200;
        int height = 40;
        int imageType = BufferedImage.TYPE_INT_RGB;

        //Creating image with text
        BufferedImage image = new BufferedImage(width, height, imageType);

        Graphics graphic = image.getGraphics();
        int fontSize = 15;
        graphic.setFont(new Font("Arial", Font.PLAIN, fontSize));

        Graphics2D graphics = (Graphics2D) graphic;
        int xCoordinate = 5;
        int yCoordinate = 25;
        graphics.drawString(text, xCoordinate, yCoordinate);

        ImageIO.write(image, "jpg", new File(filename));

        //Converting created image to ASCII-art
        Picture picture = new Picture(filename, true);
    }
}

Triplet.java

Simple datastructure that is able to save three integers (a triplet of the form (a, b, c)); in this case the (RGB-values).

public class Triplet {
    private int first;
    private int second;
    private int third;

    public Triplet(int first, int second, int third) {
        this.first = first;
        this.second = second;
        this.third = third;
    }

    public int getFirst() {
        return first;
    }

    public int getSecond() {
        return second;
    }

    public int getThird() {
        return third;
    }
}

Attribution

/*
 * Attribution:
 * The code in this question was created with the help of the following question(s) and their answer(s). 
 * These come from the Stack Exchange network, where content is licensed under CC-BY-SA (https://creativecommons.org/licenses/by-sa/4.0/):
 * Question by Jeel Shah (https://stackoverflow.com/users/681159/jeel-shah): https://stackoverflow.com/questions/7098972/ascii-art-java
 * Answer by Peter Lawrey (https://stackoverflow.com/users/57695/peter-lawrey)
 * Question by aneuryzm (https://stackoverflow.com/users/257022/aneuryzm): https://stackoverflow.com/questions/6010843/java-how-to-store-data-triple-in-a-list
 * Answer by Bala R (https://stackoverflow.com/users/273200/bala-r)
*/

Example

One example of the text to ascii-art-conversion is the "Hello World!"-message at the beginning of this question.

To demonstrate the conversion of pictures, I used the well known poison-symbol, which you can find here:

enter image description here


Question

How can I improve the code? What about the general code structure? Did I miss anything significant? Do you have any other suggestions?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I have no idea what the Triplet class does, other than hold 3 integer values. Your Control class is one main method. See if you can create some methods to better document what's going on in the code. Usually, because ASCII characters are approximately 8 pixels high and 5 pixels wide, there's not a one to one conversion of pixels to ASCII characters. I can't tell from your ASCII images, but I suspect the aspect ratio of your ASCII images isn't the same as the original picture. \$\endgroup\$ Apr 27, 2020 at 22:57
  • \$\begingroup\$ Thanks for your feedback! I just added some information about the triplet-class. \$\endgroup\$
    – user214772
    Apr 28, 2020 at 4:47
  • \$\begingroup\$ It's funny that the JPEG artifacts are clearly visible in the generated ASCII version. \$\endgroup\$ Apr 28, 2020 at 5:22

1 Answer 1

4
\$\begingroup\$

Control.java

This is just my opinion, but interactive specialized command line interfaces are weird. Did you consider command line arguments with a ready made command line parser library (free software)?

Anyway, you have duplicated the code for handling a "1 or 2" input twice. You should refactor that into a reusable utility method or class.

Triplet.java

Java specifically does not have a generic class for a tuple or triple, for the sole reason that they would be abused as specific types everwhere making code less maintainable and readable. Same here, instead of defining a generic type for a specific purpose, you should define a specific Rgb class with fields "red", "green" and "blue". Or first, shuffle through the standard libraries to see if there is one already. java.awt.Color comes to mind but I don't remember if it fits your purpose.

I the color components have minimum and maximum values (0 to 255 for example) your class should document and enforce them.

Picture.java

private Triplet[][] array;

Use descriptive field names. Array means just an array and that information already is in the field type (it's actually a two dimensional array, so the name is a bit misleading now). Maybe name it "rgbValues". When I do image processing, I like to store the image data in a one dimensional array and onvert x/y coordinates if needed. If it's not needed, the operations on the arrays become much simpler. This may come the fact that the image manipulation operations in the Java standard libraries also use one dimensional arrays so I adopted it from there.

It's also quite common to use a plain integer to represent an RGB value in the standard libraries with each component (and alpha) assigned 8 bits.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Sorry, I had to cut this short. Have to run errands. \$\endgroup\$ Apr 28, 2020 at 6:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.