Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8158370

Text drawn with floating pointing scale can be shifted to one pixel

XMLWordPrintable

    • 2d

      OS: Windows 8.1
      JDK: 9 build 9-ea+112

      To draw a selected text Swing splits it on three parts: unselected text, selected text and unselected text.

      There is a suggested fix which uses font.getStringBounds() to calculate a substring width using floating point value: http://mail.openjdk.java.net/pipermail/swing-dev/2016-May/006009.html

      However, there is a problem that a string drawn from floating point position and with floating point scale can be shifted on one pixel. See the attached screenshots.

      The samples below draw a text on two lines.
      The upper text is just a drawn text
      The lower text is drawn the first "aa" string and the last "a" is shifted to width of the font.getStringBounds(text, 0, index, g.getFontMetrics().getFontRenderContext()) rectangle 2d.

      ------------------------
              g.drawString(TEXT, x, y); // The upper text
              y = 2 * y;
              Rectangle2D rect = font.getStringBounds(TEXT, 0, index, g.getFontMetrics().getFontRenderContext());
              float selectedTextPosition = (float) rect.getWidth();
              g.drawString(TEXT.substring(0, index), x, y); // The lower text
              g.drawString(TEXT.substring(index, TEXT.length()), x + selectedTextPosition, y); // The lower selected text
      ------------------------

      There are two cases.
      a) graphics scale is 1.5 and translation is 1.
      b) graphics scale is 2.25 without applied translation

      a)
      To reproduce the issue run the code below which requires a path to saved image as argument.
      The sample uses floating point scale 1.5 and translation 1.
      ---------------------------
      import java.awt.Color;
      import java.awt.Font;
      import java.awt.Frame;
      import java.awt.Graphics;
      import java.awt.Graphics2D;
      import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING;
      import static java.awt.RenderingHints.KEY_TEXT_LCD_CONTRAST;
      import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB;
      import java.awt.event.KeyAdapter;
      import java.awt.event.KeyEvent;
      import java.awt.event.WindowAdapter;
      import java.awt.event.WindowEvent;
      import java.awt.geom.Rectangle2D;
      import java.awt.image.BufferedImage;
      import java.io.File;
      import javax.imageio.ImageIO;

      public class ScaledTransformedGraphicsTextDrawTest {

          private static final String TEXT = "aaaaaaaaaaaaaaaaaaaaa";
          private static final double SCALE = 1.5;
          private static final Color TEXT_COLOR = Color.BLACK;
          private static final Color SELECTED_TEXT_COLOR = Color.RED;
          private static final int IMG_WIDTH = 230;
          private static final int IMG_HEIGHT = 40;

          public static void main(String[] args) throws Exception {

              if (args.length < 1) {
                  System.err.println("run > java FloatScaleDrawingTest <path-to-image>");
                  return;
              }

              String fileName = args[0];
              BufferedImage img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB);
              int index = TEXT.length() - 1;
              Graphics2D g = img.createGraphics();
              g.setColor(Color.WHITE);
              g.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT);
              g.setColor(Color.BLUE);
              g.drawRect(0, 0, IMG_WIDTH, IMG_HEIGHT);

              // Important note: scale and translate the graphics!!!
              g.scale(SCALE, SCALE);
              int translate = 1;
              g.translate(-translate, 0); // Issue is reproduced only with translated graphics!!!

              int x = 2;
              int y = 10;

              g.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_LCD_HRGB);
              g.setRenderingHint(KEY_TEXT_LCD_CONTRAST, 120);

              g.setColor(TEXT_COLOR);
              g.drawString(TEXT, x, y);

              y = 2 * y;

              Font font = g.getFont();
              Rectangle2D rect = font.getStringBounds(TEXT, 0, index, g.getFontMetrics().getFontRenderContext());
              float selectedTextPosition = (float) rect.getWidth();
              g.drawString(TEXT.substring(0, index), x, y);
              g.setColor(SELECTED_TEXT_COLOR);
              g.drawString(TEXT.substring(index, TEXT.length()), x + selectedTextPosition, y);

              g.dispose();

              ImageIO.write(img, "png", new File(fileName));
          }
      }
      ---------------------------

      b)

      To reproduce the issue run the code below which requires a path to saved image as argument.
      The sample uses floating point scale 2.25.
      ---------------------------
      import java.awt.Color;
      import java.awt.Font;
      import java.awt.Graphics2D;
      import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING;
      import static java.awt.RenderingHints.KEY_TEXT_LCD_CONTRAST;
      import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB;
      import java.awt.geom.Rectangle2D;
      import java.awt.image.BufferedImage;
      import java.io.File;
      import javax.imageio.ImageIO;

      public class FloatScaleDrawingTest {

          private static final double SCALE = 2.25;
          private static final int IMG_WIDTH = 50;
          private static final int IMG_HEIGHT = 50;

          public static void main(String[] args) throws Exception {

              if (args.length < 1) {
                  System.err.println("run > java FloatScaleDrawingTest <path-to-image>");
                  return;
              }

              String fileName = args[0];
              String text = "aa";
              int index = 1;
              BufferedImage img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB);
              Graphics2D g = img.createGraphics();

              int x = 2;
              int y = 10;

              g.setColor(Color.WHITE);
              g.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT);
              g.scale(SCALE, SCALE);

              g.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_LCD_HRGB);
              g.setRenderingHint(KEY_TEXT_LCD_CONTRAST, 120);

              g.setColor(Color.BLACK);
              g.drawString(text, x, y);

              y = 2 * y;

              Font font = g.getFont();
              Rectangle2D rect = font.getStringBounds(text, 0, index, g.getFontMetrics().getFontRenderContext());
              float selectedTextPosition = (float) rect.getWidth();

              g.drawString(text.substring(0, index), x, y);
              g.drawString(text.substring(index, text.length()), x + selectedTextPosition, y);

              g.setColor(Color.BLUE);
              int xx = x + (int) selectedTextPosition;
              g.drawLine(xx, 0, xx, y);

              g.dispose();

              ImageIO.write(img, "png", new File(fileName));
          }
      }
      ---------------------------

        1. screenshot.png
          0.5 kB
          Alexandr Scherbatiy
        2. scrennshot-a.png
          0.4 kB
          Alexandr Scherbatiy

            prr Philip Race
            alexsch Alexandr Scherbatiy
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: