5
\$\begingroup\$

For what I'm doing, I need to be able to draw a buffered image directly into a larger buffered image. I've searched around a little and still have failed to find a better way, but my current way is extremely inefficient and I would like a better way if it's possible. Any improvements onto this design would also be great, even if it's not a totally separate way.

public static final void drawOntoBI(final BufferedImage on, final BufferedImage draw, final int x, final int y)
{
    // Calls method instead of needing to call them every loop
    final int width = draw.getWidth(), height = draw.getHeight(), onwidth = on.getWidth(), onheight = on.getHeight();
    // Stores temporary variables so they wont be recalculated
    int x3, y3;
    // Goes through all of the pixels in the drawn image to copy them
    for(int x2 = 0; x2 < width; x2++) 
    {
        x3 = x + x2; // Prevents having to do the same math 3 times
        for(int y2 = 0; y2 < height; y2++)
        {
            y3 = y + y2;
            // Makes sure the image to be drawn is not out of bounds, if it is, go to the next location
            if(x3 > onwidth - 1 || x3 < 1 || y3 > onheight - 1 || y3 < 1) break;
            // Draws the pixel on the image where it needs to be from the x and y looped in the image
            on.setRGB(x3, y3, draw.getRGB(x2, y2));
        }
    }
}

This is the second biggest bottleneck on my program, so it really is important to be improved. It's called all the time, and is rather slow. Memory isn't an issue, so memory-using methods can be considered.

\$\endgroup\$
4
  • \$\begingroup\$ I'm assuming that you tried the option java offers itself, and it is slower than your approach? \$\endgroup\$
    – tim
    Commented Jul 25, 2014 at 17:25
  • \$\begingroup\$ also, your bound checks seem slightly off. Can y3 be smaller than 0? only depending on y, what should happen then? and x3 should only be checked when x2 changes, not on every iteration of y2. this would save some computing. \$\endgroup\$
    – tim
    Commented Jul 25, 2014 at 17:51
  • \$\begingroup\$ Yes, y3 can be smaller than 0. And for the x3 thing, I'll fix that. I also saw that question, but it wasn't applicable (it was specific to jframes and such). Thanks for the info. \$\endgroup\$ Commented Jul 25, 2014 at 19:09
  • \$\begingroup\$ you can ignore the jframes code from that question. But I also posted the code as an answer. \$\endgroup\$
    – tim
    Commented Jul 27, 2014 at 10:10

3 Answers 3

4
\$\begingroup\$

Java Native Solution

The solution Java offers for adding two BufferedImages should be faster than anything that you will program yourself:

 /**
  * prints the contents of buff2 on buff1 with the given opaque value.
  */
 private void addImage(BufferedImage buff1, BufferedImage buff2,
         float opaque, int x, int y) {
     Graphics2D g2d = buff1.createGraphics();
     g2d.setComposite(
             AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opaque));
     g2d.drawImage(buff2, x, y, null);
     g2d.dispose();
 }

Your Code

As I said, if you remove the unnecessary x3 checks, you would at least save two comparisons for each loop of y2 (and those can really add up).

You can also move the onheight - 1 and onwidth - 1 computations outside the loop.

If most of the time the smaller image does fit into the larger image, you can also remove the bounds check completely. Create two new methods. One for when the image fits - no bound checks necessary - and one for when it does not fit (here you obviously still need the check). Then in your main method, you check once if the image would fit and use the quicker method without bounds check. If it does not fit, you use the method with bounds check.

If the smaller image does not fit most of the time, you could also first crop the smaller image (if it does not fit), so that it does always fit. I doubt that it will be faster, but it is worth a try.

\$\endgroup\$
2
\$\begingroup\$

You'll need to obtain the the backing source of a BufferedImage, such that you can apply operations in constant time.

You can obtain a both readable and writable backing source via:

BufferedImage bufferedImage = obtainSourceImage();
Raster raster = bufferedImage.getRaster();
\$\endgroup\$
3
  • \$\begingroup\$ Can you link to an example of how to use this? \$\endgroup\$ Commented Jul 25, 2014 at 19:25
  • \$\begingroup\$ @Zeusoflightning125 You can find the API here: docs.oracle.com/javase/8/docs/api/java/awt/image/… \$\endgroup\$
    – skiwi
    Commented Jul 25, 2014 at 19:31
  • \$\begingroup\$ I made an implementation of that (although I didn't make it work), and it wasn't any faster than my previous version. pastebin.com/jxhWAQxb Did I just mess the implementation up (aside from the fact it didn't work) to make it slower? Also, link to the java 7 docs, not the java 8 ones (replace the 8 in that link with the 7). Thanks. \$\endgroup\$ Commented Jul 25, 2014 at 19:52
2
\$\begingroup\$

If you need to draw image #1 on image #2 at coords (x, y):

public void drawImage
    (BufferedImage smaller, BufferedImage larger, int x, int y) {
    larger.getGraphics().drawImage(smaller, x, y, null);
}
  • Method gets the Graphics object from larger image
  • Using this object draws smaller image on larger
\$\endgroup\$
1
  • 1
    \$\begingroup\$ You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process. \$\endgroup\$
    – Ludisposed
    Commented Aug 26, 2018 at 20:40

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.