MAIN EN typewriter

older-tomato

Drawing heart in console

Geometric figures • Text image • Font rendering 08.03.2023

Let’s write two versions of the algorithm in Java to output a heart to the console in the form of a text image — let’s congratulate women on the eighth of March. Let’s draw a graph of the function in the form of a heart and, in addition, draw the symbol heart in the form of a picture, and output the picture as text — console congratulations on the eighth of March.

Heart shaped graph #

Let’s draw two half-circles and one half-rhombus, filled inside and outside. In the previous example, we output a function graph to console — we take the formulas for the circle and for the rhombus from it, and in this example, we add the filling of the figure inside and outside — instead of the equal sign in the formulas, we substitute less than or greater than signs. There will be many conditions, unlike the previous example.

Let’s draw a picture for clarity.

Heart shaped graph — is two half-circles and one half-rhombus
Two half-circles and one half-rhombus

We output the upper part of the figure, the lower part of the figure, paint over in a checkerboard pattern and output the coordinate axes. We get several text images that look like this.

Radius: 5, in/out/axes: true/true/true.
  ·   ·   ·   ·   ·   ↑y  ·   ·   ·   ·   ·   
·   ·   o o o o o   · ¦ ·   o o o o o   ·   · 
  ·   o   *   *   o   ·   o   *   *   o   ·   
·   o   *   *   *   o ¦ o   *   *   *   o   · 
  o   *   *   *   *   o   *   *   *   *   o   
· o *   *   *   *   * o *   *   *   *   * o · 
--o --* --* --* --* --+---* --* --* --* --o >x
·   o   *   *   *   * ¦ *   *   *   *   o   · 
  ·   o   *   *   *   *   *   *   *   o   ·   
·   ·   o   *   *   * ¦ *   *   *   o   ·   · 
  ·   ·   o   *   *   *   *   *   o   ·   ·   
·   ·   ·   o   *   * ¦ *   *   o   ·   ·   · 
  ·   ·   ·   o   *   *   *   o   ·   ·   ·   
·   ·   ·   ·   o   * ¦ *   o   ·   ·   ·   · 
  ·   ·   ·   ·   o   *   o   ·   ·   ·   ·   
·   ·   ·   ·   ·   o ¦ o   ·   ·   ·   ·   · 
  ·   ·   ·   ·   ·   o   ·   ·   ·   ·   ·   
·   ·   ·   ·   ·   · ¦ ·   ·   ·   ·   ·   · 
Full output
Radius: 4, in/out/axes: false/true/false.
·   ·   ·   ·   ·   ·   ·   ·   ·   · 
  ·   o o o o o   ·   o o o o o   ·   
·   o o       o o   o o       o o   · 
  o o           o o o           o o   
· o               o               o · 
  o                               o   
·   o                           o   · 
  ·   o                       o   ·   
·   ·   o                   o   ·   · 
  ·   ·   o               o   ·   ·   
·   ·   ·   o           o   ·   ·   · 
  ·   ·   ·   o       o   ·   ·   ·   
·   ·   ·   ·   o   o   ·   ·   ·   · 
  ·   ·   ·   ·   o   ·   ·   ·   ·   
·   ·   ·   ·   ·   ·   ·   ·   ·   · 
Radius: 3, in/out/axes: true/false/false.
    o o o       o o o     
  o *   * o   o *   * o   
o *   *   * o *   *   * o 
o   *   *   *   *   *   o 
  o   *   *   *   *   o   
    o   *   *   *   o     
      o   *   *   o       
        o   *   o         
          o   o           
            o             
Radius: 2, in/out/axes: false/false/false.
  o o o   o o o   
o       o       o 
o               o 
  o           o   
    o       o     
      o   o       
        o         

We bypass the coordinate range with two nested for loops: first along the y axis and then along the x axis. We check each point for compliance with the conditions and output it. In the upper part we draw two half-circles and optionally paint over them inside and outside. In the lower part we draw a half-rhombus and also optionally paint over inside and outside.

/**
 * @param r    radius
 * @param gap  offset
 * @param in   filling inside
 * @param out  filling outside
 * @param axes coordinate axes
 */
public static void printHeartGraph(
    int r, int gap, boolean in, boolean out, boolean axes) {
  // boundaries of the text image
  int xMax = 2*r+gap, xMin = -xMax;
  int yMax = r+gap, yMin = -r-yMax;
  System.out.println( // header with parameters
      "Radius: "+r+", in/out/axes: "+in+"/"+out+"/"+axes+".");
  // output to the console line by line from left to right from top to bottom
  for (int y = yMax; y >= yMin; y--) {
    for (int x = xMin; x <= xMax; x++) {
      double[] circle = { // two circles left/right
          Math.round(Math.sqrt(Math.pow(x+r,2)+Math.pow(y,2))), // left
          Math.round(Math.sqrt(Math.pow(x-r,2)+Math.pow(y,2)))}; // right
      int rhombus = Math.abs(x)+Math.abs(y); // rhombus
      boolean inCh = in && (x+y)%2 == 0; // checkerboard pattern inside
      boolean outCh = out && (x+y)%2 == 0; // checkerboard pattern outside
      // check each point for compliance with the conditions and output it
      if (axes && y == 0 && x == 0)
        System.out.print("+-"); // origin of coordinates
      else if (axes && y == 0 && x == xMax)
        System.out.print(">x"); // maximum of abscissa axis (x)
      else if (axes && x == 0 && y == yMax)
        System.out.print("↑y"); // maximum of ordinate axis (y)
      else if (y > 0 && (circle[0] == r || circle[1] == r))
        System.out.print("o "); // two half circles, top
      else if (y > 0 && inCh && (circle[0] < r || circle[1] < r))
        System.out.print("* "); // top inside
      else if (y > 0 && outCh && (circle[0] > r && circle[1] > r))
        System.out.print("· "); // top outside
      else if (y <= 0 && rhombus == 2*r)
        System.out.print("o "); // half rhombus, bottom
      else if (y <= 0 && inCh && rhombus < 2*r)
        System.out.print("* "); // bottom inside
      else if (y <= 0 && outCh && rhombus > 2*r)
        System.out.print("· "); // bottom outside
      else if (axes && y == 0)
        System.out.print("--"); // abscissa axis (x)
      else if (axes && x == 0)
        System.out.print("¦ "); // ordinate axis (y)
      else
        System.out.print("  "); // empty space
    } // transition to a new line
    System.out.println();
  }
}
// execute the program and output the result
public static void main(String[] args) {
  printHeartGraph(5, 1, true, true, true);
  printHeartGraph(4, 1, false, true, false);
  printHeartGraph(3, 0, true, false, false);
  printHeartGraph(2, 0, false, false, false);
}

Text as picture and picture as text #

In the previous example we drew a simple captcha — we take the font rendering algorithm from it, only this time we draw a binary black-and-white image in a monospaced font, we do not use anti-aliasing. The symbol heart in the form of a picture looks like this.

Symbol heart, monospaced font, plain, 22

Since the character is in the middle of the line, half of the pixels in the resulting image are empty. We bypass the pixels line by line and output only non-empty lines, that is the central part of the image.

Monospaced.plain, 22, symbols: ♡
      o           o o     
  o o   o o   o o     o   
o         o   o         o 
o           o           o 
o           o           o 
o                       o 
o                     o   
  o                   o   
    o               o     
      o           o       
        o       o         
        o     o           
          o   o           
            o             
Full output
Monospaced.plain, 28, symbols: ♡
        o               o         
    o o   o o       o o   o o     
  o           o   o           o   
o             o o             o   
o               o               o 
o               o               o 
o               o             o   
o                             o   
  o                           o   
    o                       o     
      o                   o       
        o               o         
        o             o           
          o         o             
            o       o             
              o   o               
                o                 
Monospaced.plain, 36, symbols: ♡
        o o o               o o o o         
    o o       o o         o         o o     
  o               o     o             o     
  o               o   o                 o   
o                   o o                 o   
o                   o                   o   
o                   o                   o   
o                                       o   
  o                                     o   
  o                                     o   
    o                                 o     
    o                               o       
      o                           o         
        o                         o         
          o                     o           
            o                 o             
              o             o               
              o           o                 
                o       o                   
                  o     o                   
                    o o                     

We draw a line of text as a black-and-white image, then iterate over the pixels of this image and print them as text to the console line by line. We draw only the central part of the image with text, do not display empty lines of pixels.

// draw a text in the form of a picture and a picture in the form of a text
public static void printTextImage(String str, Font font) {
  FontRenderContext ctx = // font rendering context
      new FontRenderContext(font.getTransform(), false, false);
  // get the dimensions of the picture with text when rendering
  Rectangle bnd = font.getStringBounds(str, ctx).getBounds();
  // create a new binary black-and-white image
  BufferedImage image = new BufferedImage(
      bnd.width, bnd.height, BufferedImage.TYPE_BYTE_BINARY);
  // turn on the editing mode of the new image
  Graphics2D graphics = image.createGraphics();
  // font for rendering, do not use anti-aliasing
  graphics.setFont(font);
  // draw a picture with text
  graphics.drawString(str, bnd.x, -bnd.y);
  // disable the editing mode
  graphics.dispose();
  // output the header
  System.out.println(
      font.getFontName()+", "+font.getSize()+", symbols: "+str);
  // bypass the pixels line by line and output non-empty lines
  for (int y = 0; y < bnd.height; y++) {
    StringBuilder line = new StringBuilder();
    for (int x = 0; x < bnd.width; x++)
      line.append(image.getRGB(x, y) == -1 ? "o " : "  ");
    // draw only non-empty lines
    if (line.indexOf("o") != -1) System.out.println(line);
  }
}
// execute the program and output the result
public static void main(String[] args) {
  printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 22));
  printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 28));
  printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 36));
}

The last example uses the Java AWT library.

Required imports
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.image.BufferedImage;

© Golovin G.G., Code with comments, translation from Russian, 2023