-1

I am trying to make ASCII Art from an image, but for some reason the output is always rotated and I went through my code a bunch of times and I simply can not find the mistake. I am guessing it's something to do with imageWidth and imageHeight but it all looks fine to me.

Code can be found on github

randomboiguyhere
  • 525
  • 5
  • 17

4 Answers4

3

Draw an ASCII art from an image

Let's assume that one character occupies an area of 21×8 pixels. So first you have to scale down the original image and get the average color of this area, then get the average brightness of this color, and then convert it to a character.

Original picture:

Original picture

ASCII picture:

ASCII picture


This code reads an image from a file, scales it down to 1/21 of the height and 1/8 of the width, calculates the average colors for the scaled areas, then calculates the average brightness for each color and picks a character of the corresponding density, and then saves these characters to a text file.

Without scaling scH=1 and scW=1, the number of characters is equal to the number of pixels in the original image.

public static void main(String[] args) throws IOException {
  // assume that one character occupies an area of 21×8 pixels
  char[][] chars = readImage("/tmp/image.jpg", 21, 8);
  writeToFile("/tmp/image.txt", chars);
}

static char[][] readImage(String path, int scH, int scW) throws IOException {
  BufferedImage image = ImageIO.read(new File(path));
  int height = image.getHeight() / scH;
  int width = image.getWidth() / scW;
  char[][] chars = new char[height][width];
  for (int i = 0; i < height; i++) {
    for (int j = 0; j < width; j++) {
      // scaling image and accumulating colors
      int colorRGB = 0;
      for (int k = 0; k < scH; k++)
        for (int p = 0; p < scW; p++)
          colorRGB += image.getRGB(j * scW + p, i * scH + k);
      // get the average color
      Color color = new Color(colorRGB / (scH * scW));
      // read the R, G, B values of the color and get the average brightness
      int brightness = (color.getRed() + color.getGreen() + color.getBlue()) / 3;
      // get a character depending on the brightness value
      chars[i][j] = getDensity(brightness);
    }
  }
  return chars;
}

static final String DENSITY =
      "@QB#NgWM8RDHdOKq9$6khEPXwmeZaoS2yjufF]}{tx1zv7lciL/\\|?*>r^;:_\"~,'.-`";

static char getDensity(int value) {
  // Since we don't have 255 characters, we have to use percentages
  int charValue = (int) Math.round(DENSITY.length() / 255.0 * value);
  charValue = Math.max(charValue, 0);
  charValue = Math.min(charValue, DENSITY.length() - 1);
  return DENSITY.charAt(charValue);
}

static void writeToFile(String path, char[][] chars) throws IOException {
  FileWriter writer = new FileWriter(path);
  for (char[] row : chars) {
    String str = String.valueOf(row);
    writer.append(str).write("\n");
    System.out.println(str);
  }
  writer.flush();
  writer.close();
}

Output:

***************************************
***************************************
*************o/xiz|{,/1ctx*************
************77L*```````*_1{j***********
**********?i```````````````FZ**********
**********l`````````````````7**********
**********x`````````````````L**********
**********m?i`````````````iz1**********
************]x```````````\x{***********
********?1w]c>```````````La{]}r********
******jSF~```````````````````^xv>******
*****l1,```````````````````````*Sj*****
****7t```````````````````````````v7****
***uL`````````````````````````````t]***

See also: Restore an image from an ASCII art with JavaConvert image to ASCII art

1

Creating character density and brightness maps

You can create your own character density map from any range of characters, and then, since the densities are not evenly distributed, convert it to a brightness map and further call the ceilingEntry and floorEntry methods.

First draw each character as a black-and-white picture using java.awt package and count the number of pixels - you get a density map. Then for each entry from this map, calculate the percentage of brightness on the scale 0-255 - you get a brightness map.

Pictures:

Original picture ASCII: 0 - 255
0x0000 - 0x00FF
Runic:
0x16A0 - 0x16FF
Box Drawing:
0x2500 - 0x257F
Block Elements:
0x2580 - 0x259F
Geometric Shapes:
0x25A0 - 0x25FF
Hiragana:
0x3040 - 0x309F
enter link description here enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here

Density scales:

Unicode block Range of characters Density scale
ASCII
0-255
¶@ØÆMåBNÊßÔR#8Q&mÃ0À$GXZA5ñk2S%±3Fz¢yÝCJf1t7ªLc¿+?(r/¤²!*;"^:,'.` 
Runic
0x16A0-0x16FF
ᛥᛤᛞᚥᚸᛰᛖᚻᚣᛄᚤᛒᚢᚱᛱᚷᚫᛪᚧᚬᚠᛏᚨᚰᚩᚮᚪᚳᚽᚿᛊᛁᛵᛍ᛬ᚲᛌ᛫
Box Drawing
0x2500-0x257F
╬╠╫╋║╉╩┣╦╂╳╇╈┠╚┃╃┻╅┳┡┢┹╀╧┱╙┗┞┇┸┋┯┰┖╲╱┎╘━┭┕┍┅╾│┬┉╰╭╸└┆╺┊─╌┄┈╴╶
Block Elements
0x2580-0x259F
█▉▇▓▊▆▅▌▚▞▀▒▐▍▃▖▂░▁▏
Geometric Shapes
0x25A0-0x25FF
◙◘■▩●▦▣◚◛◕▨▧◉▤◐◒▮◍◑▼▪◤▬◗◭◖◈◎◮◊◫▰◄◯□▯▷▫▽◹△◁▸▭◅▵◌▱▹▿◠◃◦◟◞◜
Hiragana
0x3040-0x309F
ぽぼゑぜぬあおゆぎゐはせぢがきぱびほげばゟぁたかぞぷれひずどらさでけぉちごえすゎにづぇとょついこぐうぅぃくっしへゞゝ゚゙

Code:

public static void main(String[] args) {
  Font font = new Font(Font.MONOSPACED, Font.PLAIN, 22);
  // ASCII characters: 0 - 255
  // Runic: 0x16A0 - 0x16FF
  // Box Drawing: 0x2500 - 0x257F
  // Block Elements: 0x2580 - 0x259F
  // Geometric Shapes: 0x25A0 - 0x25FF
  // Hiragana: 0x3040 - 0x309F
  TreeMap<Integer, List<String>> density = getDensityMap(font,0x25A0,0x25FF,0);
  // the map of brightness of pixels [0, 255]
  TreeMap<Integer, List<String>> brightness = getBrightnessMap(density);
  // output, geometric shapes
  for (List<String> value : brightness.values()) System.out.print(value.get(0));
  // ◙◘■▩●▦▣◚◛◕▨▧◉▤◐◒▮◍◑▼▪◤▬◗◭◖◈◎◮◊◫▰◄◯□▯▷▫▽◹△◁▸▭◅▵◌▱▹▿◠◃◦◟◞◜
}

/**
 * @param density character density map
 * @return the pixel brightness map [0, 255],
 * based on percentages of character density
 */
static TreeMap<Integer, List<String>> getBrightnessMap(
        TreeMap<Integer, List<String>> density) {
  int max = density.lastKey(); // maximum density
  TreeMap<Integer, List<String>> brightness = new TreeMap<>();
  for (Map.Entry<Integer, List<String>> entry : density.entrySet()) {
    // pixel brightness, based on the percentage of character density
    int key = (int) Math.round(255.0 - entry.getKey() * 255.0 / max);
    List<String> value = entry.getValue();
    List<String> val = brightness.remove(key);
    if (val == null) val = new ArrayList<>();
    val.addAll(value);
    brightness.put(key, val);
  }
  return brightness;
}

/**
 * @param f   font to render text
 * @param min character codepoint range, lower bound
 * @param max character codepoint range, upper bound
 * @param pd  padding as a precaution, in most cases 0
 * @return the character density map:
 * key - density, value - list of characters
 */
static TreeMap<Integer, List<String>> getDensityMap(
        Font f, int min, int max, int pd) {
  // key - density, value - list of characters
  TreeMap<Integer, List<String>> density = new TreeMap<>();
  for (int i = min; i <= max; i++) {
    // printable characters
    if (f.canDisplay(i) && Character.isDefined(i)
          && !Character.isISOControl(i)
          && !Character.isIdentifierIgnorable(i)) {
      String str = String.valueOf(Character.toChars(i));
      int key = getDensity(str, f, pd);
      List<String> list = density.remove(key);
      if (list == null) list = new ArrayList<>();
      list.add(str);
      density.put(key, list);
    }
  }
  return density;
}

/**
 * @param text source text to draw
 * @param f    font to render text
 * @param pd   padding as a precaution, in most cases 0
 * @return the density of the characters in this text
 */
static int getDensity(String text, Font f, int pd) {
  FontRenderContext ctx = new FontRenderContext(f.getTransform(), false, false);
  Rectangle bounds = f.getStringBounds(text, ctx).getBounds();
  int width = bounds.width + pd * 2;
  int height = bounds.height + pd * 2;
  BufferedImage image =
          new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
  Graphics2D graphics = (Graphics2D) image.getGraphics();
  graphics.setFont(f);
  graphics.drawString(text, pd + bounds.x, pd - bounds.y);
  //ImageIO.write(image, "png", new File("text.png"));
  int density = 0;
  for (int i = 0; i < height; i++)
    for (int j = 0; j < width; j++)
      if (image.getRGB(j, i) == 0xFFFFFFFF)
        density++;
  return density;
}
0

In ImageWriting.java, line 34:

this.writer.append(Density.DENSITY.getDensityFor(this.brightnessValues[j][i]));

Mihai
  • 9,526
  • 2
  • 18
  • 40
0

I strongly suspect that you are stepping through one coordinate in some for loop with the other loop nested inside. (Not going to chase after code on another site.)

Try swapping the nesting of the for loops or the order of accessing an element by array indices (replacing the [i][j] code fragment with [j][i] or similar according to whatever code you have on another site that I'm not going to chase after).

Alan
  • 716
  • 5
  • 15