2

I'm trying to write a program that just creates an image out of text (e.g. write "hello" on a white square and store the image), which sounds simple but it must be done quickly. I tried the Java2D library but drawing onto a BufferedImage takes ~2 seconds to just draw the image, not even save or display it. I also tried Java-based CAPTCHA generators but they take much too long (5 seconds).

This seems like simple enough task to just draw text, but I'm frustrated that I can't do this faster than 2 seconds.

Is there a way I can do this faster on my machine by some command line options (e.g. allocating more memory or priority)? Is there a particular Java library I ought to use, or some weird quirk to Java2D I should be aware of to make things faster?

This is my entire program. I run this in Eclipse:

import java.awt.*;
import java.awt.image.BufferedImage;

public class SimpleGraphics {
    public static void main(String[] args) {
        long time = System.currentTimeMillis();

        String message = "Hello world";
        int width = 100;
        int height = 100;
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);

        Graphics2D graphics = img.createGraphics();
        graphics.setColor(Color.black);
        graphics.setFont(new Font("TimesRoman", Font.BOLD, 12));

        FontMetrics fontMetrics = graphics.getFontMetrics();
        int stringWidth = fontMetrics.stringWidth(message);
        int stringHeight = fontMetrics.getAscent();

        graphics.drawString(message, (width - stringWidth) / 2, height / 2 + stringHeight / 4);
        System.out.println(System.currentTimeMillis() - time); //consistently ~2 seconds
    }
}
Booley
  • 819
  • 1
  • 9
  • 25
  • 2
    It does not take 2 seconds to do any basic Graphic painting or file I/O. Post your [SSCCE](http://sscce.org/) that demonstrates the problem so we can see exactly what you are doing. I don't see anything wrong with the basic code you posted, but the context of how you use the code is important. – camickr Nov 26 '15 at 16:50
  • That code is literally everything inside my main method. I am running this in Eclipse. – Booley Nov 26 '15 at 16:53
  • 1
    Well then literally copy all the code an post it to the forum. For example how do you calculate the "2 seconds"? If we can't run the code we don't know if the problem is your code or your machine. The 2 seconds may the loading of the JVM not the actually painting time. – camickr Nov 26 '15 at 16:57
  • Edited to include the entire program. I timed the inner code with System.currentTimeMillis() calls to verify it's the execution of the code itself, not the JVM. – Booley Nov 26 '15 at 17:03
  • `I timed the inner code with System.currentTimeMillis() calls to verify it's the execution of the code` - how? I don't see that code. Again how can we compare results if we don't have the code you are executing? – camickr Nov 26 '15 at 17:05
  • I have the same problem, I'm using Eclipse with Java version 1.8.0_121 on Macbook 3,1 GHz Intel Core i7, that's crazy! Did you find a solution for that? – navy1978 Sep 17 '19 at 19:19

2 Answers2

2

I run my code from the command line (using JDK8 on Windows 7) and I get around 300ms.

I modified your code to the following:

import java.awt.*;
import java.awt.image.BufferedImage;

public class SimpleGraphics {
    public static void main(String[] args) {
        long time = System.currentTimeMillis();

        for (int i = 0; i < 100; i++)
            createImage();

        System.out.println(System.currentTimeMillis() - time); //consistently ~2 seconds
    }

    public static void createImage()
    {
        String message = "Hello world";
        int width = 100;
        int height = 100;
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);

        Graphics2D graphics = img.createGraphics();
        graphics.setColor(Color.black);
        graphics.setFont(new Font("TimesRoman", Font.BOLD, 12));

        FontMetrics fontMetrics = graphics.getFontMetrics();
        int stringWidth = fontMetrics.stringWidth(message);
        int stringHeight = fontMetrics.getAscent();

        graphics.drawString(message, (width - stringWidth) / 2, height / 2 + stringHeight / 4);
    }
}

I still get around 300ms.

So the problem is not with the painting code.

I don't know why you get 2 seconds, but there is obviously some overhead to loading the class. So all I can suggest is to do your image creation in batches to minimize the time.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • I notice that when I run in batches the first image takes 2s to load, but everything else is < 10ms; it's nice to know that it's the class loading that's the issue and not the code itself, but it's still not practical to do batches. Is something wrong with my java/machine? – Booley Nov 26 '15 at 17:25
  • Don't know if it is your machine the IDE, platform or Java version. – camickr Nov 26 '15 at 17:30
  • May I ask what IDE/version you're using? (version of IDE, that is) – Booley Nov 26 '15 at 17:31
  • I stated that in my answer. I'll add its JDK8_45. You should be posting your information with your question so people using your configuration can run the code and compare. Again, give the information if the question if you want a good answer. We should not have to keep asking you to provide information. – camickr Nov 26 '15 at 17:35
  • 1
    @Booley My immediate suspect for eating the majority of runtime spent would be the "new Font()" specifying a disk-based font. You might also try other image types than BYTE_GRAY, java2d performance can vary considerably depending on target image. (Confirmed: Moving font creation out of the testing, painting time is too short to measure on my system). – Durandal Nov 26 '15 at 18:26
0

Timing each line individually gives a big jump at fontMetrics - so there is your culprit. I dont know if other measuring classes (lineMetrics) will give you shorter time

import java.awt.*;
import java.awt.image.BufferedImage;

public class SimpleGraphics {
public static void main(String[] args) {
    long time = System.currentTimeMillis();

    String message = "Hello world";
    int width = 100;
    int height = 100;
    BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
    System.out.println(System.currentTimeMillis() - time); //consistently ~2 seconds

    Graphics2D graphics = img.createGraphics();
    graphics.setColor(Color.black);
    graphics.setFont(new Font("TimesRoman", Font.BOLD, 12));
    System.out.println("1 "+(System.currentTimeMillis() - time)); //consistently ~2 seconds

    FontMetrics fontMetrics = graphics.getFontMetrics();
    System.out.println("2 "+(System.currentTimeMillis() - time)); //consistently ~2 seconds
    int stringWidth = fontMetrics.stringWidth(message);
    System.out.println("3 "+(System.currentTimeMillis() - time)); //consistently ~2 seconds
    int stringHeight = fontMetrics.getAscent();
    System.out.println(System.currentTimeMillis() - time); //consistently ~2 seconds

    graphics.drawString(message, (width - stringWidth) / 2, height / 2 + stringHeight / 4);
    System.out.println(System.currentTimeMillis() - time); //consistently ~2 seconds
}
}
gpasch
  • 2,672
  • 3
  • 10
  • 12