Java-Gaming.org    
Featured games (78)
games approved by the League of Dukes
Games in Showcase (426)
Games in Android Showcase (89)
games submitted by our members
Games in WIP (466)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  fastest image copying?  (Read 3842 times)
0 Members and 1 Guest are viewing this topic.
Offline counterp

Senior Member


Medals: 11



« Posted 2011-07-15 19:34:40 »

what is the fastest method of copying an Image/BufferedImage (is there a speed difference between using each one?) to a new Image object (so that they contain the same data, but are not the same reference, same thing as cloning an object).

I could make a new image object, and draw the desired image to the new image object's graphics, but I have found that this is not as fast as I want. I also tried this (but it's also slow):

1  
2  
3  
4  
5  
6  
   public BufferedImage copy(BufferedImage image) {
      boolean isAlphaPremultiplied = image.isAlphaPremultiplied();
      ColorModel colorModel = image.getColorModel();
      WritableRaster writableRaster = image.copyData(null);
      return new BufferedImage(colorModel, writableRaster, isAlphaPremultiplied, null);
   }


Help?

EDIT: Also, the reason I need to copy images is because in my application you can draw things. So after each drawing action is perform (like using the pen tool), it saves the image to an Array of images (the drawing history) so when you undo an action, it goes to the array to get the last image submitted to it. Is there a better solution?
Offline avm1979
« Reply #1 - Posted 2011-07-15 21:19:13 »

Drawing is indeed extremely slow. I wouldn't use this approach at all, though - for reasonably-sized images, you'll run out of memory very quickly. As an example, a 1024x1024 image takes up 1024x1024x4 = 4MB.

I don't know what the standard way of doing something like this would be, but at the very least you could just save the dirty area and not the whole image - for something like the pen tool, that would mean just saving what the area you drew on looked like beforehand.

Hmm... just thinking about it now, maybe this would work - what if you saved a "checkpoint" image and a current one, and then the sequence of operations it took to get from the checkpoint to current?

So the checkpoint is an old image, and you know that you did - fill(color, x, y), pen_draw(color, x, y), etc to get to current. Then if you need to step back, you would take the checkpoint image and apply all the operations to it again, up to (but not including) the operation you want to undo.

You could then set an undo buffer depth and, once it's reached, apply the first operation in the sequence to the checkpoint image and then discard the operation. So when you draw, you'd be applying two operations - one to draw on the current image, and one to update the checkpoint to be <undo buffer depth> operations away from current.

Online Riven
Showcase Moderator

JGO Overlord


Medals: 611
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #2 - Posted 2011-07-15 21:30:07 »

Drawing is indeed extremely slow. I wouldn't use this approach at all, though - for reasonably-sized images, you'll run out of memory very quickly. As an example, a 1024x1024 image takes up 1024x1024x32 = 32MB.

1024x1024x4 = 4MB (4 bytes per pixel)

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline avm1979
« Reply #3 - Posted 2011-07-15 21:31:50 »

Drawing is indeed extremely slow. I wouldn't use this approach at all, though - for reasonably-sized images, you'll run out of memory very quickly. As an example, a 1024x1024 image takes up 1024x1024x32 = 32MB.

1024x1024x4 = 4MB

I meant megabits, not megabytes Grin

Well, that's what I ended up calculating, anyway. Thank you for spotting that.

Offline Z-Man
« Reply #4 - Posted 2011-07-15 21:38:44 »

Out of curiosity I did a test to see how long it would take to copy a BufferedImage to another BufferedImage by just drawing the first image onto the second. On average it took about 6.6 milliseconds (6603092 nanoseconds) to copy a 1024x1024pixel BufferedImage that is filled with random colored pixels. That seems fast enough to me, but I don't know how your programs structured so I can't really vouch for that time being acceptable. Just thought I'd share.

EDIT: Now I'm getting averages of around 5.4 milliseconds.
Offline avm1979
« Reply #5 - Posted 2011-07-15 22:08:52 »

That's slow for what should be just copying a bit of data. But to your point, that might be good enough for what the app needs to do, and what image sizes it works with.

The other problem here is it's actually not going to give you the same image you started with. AFAIK the draw call does some anti-aliasing (not sure if you can turn that off, maybe you can), but as-is you'll end up with a copy that's subtly different from the source image. Not entirely sure if it always happens - might be it'll only show up if you have transparency in the image, but it does happen.

Offline Z-Man
« Reply #6 - Posted 2011-07-15 22:14:26 »

Yah like I said, I have no idea if that timing is acceptable for the OP's needs. I had no idea that the draw method did anti-aliasing though, thanks for the info on that. Of course I really wouldn't notice it in my test program since it's really just random colors.
Offline ra4king

JGO Kernel


Medals: 322
Projects: 2
Exp: 4 years


I'm the King!


« Reply #7 - Posted 2011-07-15 22:37:20 »

AFAIK, Anti-Aliasing is not applied to images. Try turning on anti-aliasing and rotate an image: you'll notice the image will have jagged edges Wink

Offline avm1979
« Reply #8 - Posted 2011-07-15 22:53:59 »

It's definitely doing some interpolation between neighboring pixel values Edit#2 - or not, as it still happens in the case of a 1x1 image.

The case where I really noticed it was an image with transparent pixels that were (255,255,255,0). Ended up with very pronounced white areas around the edge of the sprite when it was re-drawn.

Then started looking at other sprites and realized they were also affected, just not to a degree that was immediately noticeable.

Edit:
Here's some code to show what I mean.
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;

public class ImageTest {
   public static void putPixel(WritableRaster data, int x, int y, Color c) {
      int[] i = new int[4];
      i[0] = c.getRed();
      i[1] = c.getGreen();
      i[2] = c.getBlue();
      i[3] = c.getAlpha();
      data.setPixel(x, y, i);
   }
   public static BufferedImage createBlankImage(int size) {
      BufferedImage b = new BufferedImage(size, size,BufferedImage.TYPE_4BYTE_ABGR);
      WritableRaster data = b.getRaster();
      for (int i = 0; i < b.getWidth(); i++) {
         for (int j = 0; j < b.getHeight(); j++) {
            putPixel(data, i, j, new Color(0,0,0,0));
         }
      }
      return b;
   }
   
   public static void main(String[] args) {
      BufferedImage src = createBlankImage(1);
      BufferedImage dest = createBlankImage(1);
     
      WritableRaster srcRaster = src.getRaster();
      putPixel(srcRaster, 0, 0, new Color(155,255,55,55));
     
      int r = srcRaster.getSample(0, 0, 0);
      int g = srcRaster.getSample(0, 0, 1);
      int b = srcRaster.getSample(0, 0, 2);
      int a = srcRaster.getSample(0, 0, 3);
      System.out.println(String.format("Before: %d,%d,%d,%d", r, g, b, a));
     
      Graphics2D graphics = dest.createGraphics();
      graphics.drawImage(src, 0, 0, null);
     
      WritableRaster destRaster = dest.getRaster();
      r = destRaster.getSample(0, 0, 0);
      g = destRaster.getSample(0, 0, 1);
      b = destRaster.getSample(0, 0, 2);
      a = destRaster.getSample(0, 0, 3);
      System.out.println(String.format("After: %d,%d,%d,%d", r, g, b, a));
   }
}


The output I get from it is this:
1  
2  
Before: 155,255,55,55
After: 153,255,56,55


Which seems to indicate there's more than a straight copy of the data going on... unless I'm doing something silly here. I'm not an expert on Java2D by any means.

Offline counterp

Senior Member


Medals: 11



« Reply #9 - Posted 2011-07-16 01:36:37 »

It would be fine if the copying speed did not jump up to 300+ ms sometimes for one image (usually it's only around 2 ms for the size of the images I'm copying) and I'm using the method posted in first post.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline ra4king

JGO Kernel


Medals: 322
Projects: 2
Exp: 4 years


I'm the King!


« Reply #10 - Posted 2011-07-16 03:56:36 »

@avm1979
Off-topic: you know there is a System.out.printf(String,Object...) method? Wink

Offline avm1979
« Reply #11 - Posted 2011-07-16 04:10:46 »

@avm1979
Off-topic: you know there is a System.out.printf(String,Object...) method? Wink

Thanks, did not know that! Had "sop" set up as a template in Eclipse for println (so useful...), now also added "sof" to expand into the printf.

I think I've been conditioned not to expect the standard Java APIs to provide anything conveniently short Wink

Offline ra4king

JGO Kernel


Medals: 322
Projects: 2
Exp: 4 years


I'm the King!


« Reply #12 - Posted 2011-07-16 09:54:04 »

@avm1979
Off-topic: you know there is a System.out.printf(String,Object...) method? Wink

Thanks, did not know that! Had "sop" set up as a template in Eclipse for println (so useful...), now also added "sof" to expand into the printf.

I think I've been conditioned not to expect the standard Java APIs to provide anything conveniently short Wink
Whoa...you can set shorcuts to methods in Eclipse?? O_O
Medal for you too sir!

Offline Z-Man
« Reply #13 - Posted 2011-07-19 05:59:05 »

Whoa...you can set shorcuts to methods in Eclipse?? O_O
Medal for you too sir!
Off-Topic: If only Eclipse did it like NetBeans, I seem to remeber being able to type sout, hit tab, and have it expand to System.out.println(""); with the cursor between the quotes. There are a few features I miss from NetBeans... not enough to use it though  Grin

@counterp If you care I did testing with your copy method, it's about 3 milliseconds for a 1024x1024 image.
Online Riven
Showcase Moderator

JGO Overlord


Medals: 611
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #14 - Posted 2011-07-19 09:37:51 »

Off-Topic: If only Eclipse did it like NetBeans, I seem to remeber being able to type sout, hit tab, and have it expand to System.out.println(""); with the cursor between the quotes. There are a few features I miss
syso CTRL+SPACE

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline Z-Man
« Reply #15 - Posted 2011-07-19 10:25:59 »

Shocked Thank you Riven
Offline BoBear2681

JGO Coder


Medals: 18



« Reply #16 - Posted 2011-07-19 13:58:22 »

There's a whole slew of templates like this, and you can also define your own.

Window -> Preferences -> Java -> Editor -> Templates
Offline Dx4

Junior Member


Medals: 5



« Reply #17 - Posted 2011-07-19 17:13:41 »

in my drawing application, I use a software renderer that is able to save it's old state.

so for example, I divide the whole canvas up into 32x32 tiles and store them in a hashmap.

1  
2  
3  
   class UndoData {
      int [] pixelData = new int[32 * 32];
   }


the above class stores the undo information for a single tile. in each level of undo, I store a map of these objects, in the form Map<Point, UndoData>

now, whenever the user uses the pen, it calls a method with the following signature:

1  
public void drawBrush(int x, int y, int size, int alpha, int color, int [] pixels, int width, int height, Map<Point, UndoData> undoData)


and then when a pixel is modified, it is placed into the undoData map (creating a UndoData object if one doesnt already exist for the 32x32 area im currently drawing in)

as for the undo queue, I have a structure like the following:

1  
Deque<Map<Point,UndoData>> data = new ArrayDeque() (or LinkedList, whatever)


append the current map to the queue when the user releases the mouse:

1  
2  
3  
4  
public void onPointerRelease(PointerEvent pEvt) {
 ...
 data.append(currentUndoState);
}


and when the user presses CTRL+Z, all I do is

1  
2  
3  
4  
5  
Map<Point, UndoData> shit = data.pop();
for (Entry<Point,UndoData> entry:shit.entrySet()) {
 Point x = entry.getKey();
 recoverTile(x.x<<5,x.y<<5,entry.getValue());
}


the recoverTile method would look something like the following:

1  
2  
3  
4  
void recoverTile(int x, int y, UndoData shit) {
 Tile t = getTile(x,y);
 copyPixels(shit.pixelData, t.pixelData); // replace
}


HTH

ps: if you're interested, check out my drawing application: http://www.java-gaming.org/topics/uxpaint-new-collaborative-paint-program-in-development/24478/view.html (inb4 shameless advertising)

if you have any more questions, pls ask


EDIT: Here is my full class, feel free to use it.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
package com.uxpaint.internal;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

/**
 *
 * @author David
 */

public class UndoManager {
   static class UndoData {
      int [] pixelData = new int[32 * 32];
   }

   private Deque<UndoState> stateQueue = new LinkedList<UndoState>();
   private UndoState currentState = new UndoState();

   public UndoState popState() {
      return stateQueue.pollLast();
   }

   public void pushState() {
      stateQueue.addLast(currentState);
      currentState = new UndoState();
   }

   public static class UndoState extends HashMap<Point, UndoData> {
      public UndoState() {

      }

      public void undo(BufferedImage image) {
         final int
               width = image.getWidth(),
               height = image.getHeight();
         final int [] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
         for (Iterator<Map.Entry<Point, UndoData> > i = this.entrySet().iterator(); i.hasNext();) {
            Map.Entry<Point, UndoData> e = i.next();
            Point p = e.getKey();
            final int startOffset = ((p.y<<5) * width) + (p.x<<5);
            UndoData undoData = e.getValue();
            for (int x = 0; x < 32; x++) {
               for (int y = 0; y < 32; y++) {
                  final int pos = (x<<5) + y;
                  if (undoData.pixelData[pos] != 0) {
                     pixels[startOffset + width * y + x] = undoData.pixelData[pos];
                  }
               }
            }
         }
         clear();
      }

      public void putPixel(int pixel, int x, int y) {
         Point pt = new Point(x >> 5, y >> 5);
         UndoData data = get(pt);
         if (data == null) {
            data = new UndoData();
            put(pt, data);
         }
         int xpos = x-(pt.x<<5);
         int ypos = y-(pt.y<<5);
         data.pixelData[xpos+(ypos<<5)] = pixel;
      }

      public int calculateMemorySize() {
         return size() * 32 * 32 * 4;
      }
   }
}


when this above class is used compared to recovering the whole image, it uses roughly ~20% of how much memory would be required if you saved the whole canvas every step.

it is also about ~1000% faster when you need to undo a step.
Offline counterp

Senior Member


Medals: 11



« Reply #18 - Posted 2011-07-21 15:07:18 »

Hey, a post that's on-topic Cheesy

Thank you very much, I will check out your application and test out this class when I have enough free time!
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

xsi3rr4x (73 views)
2014-04-15 18:08:23

BurntPizza (68 views)
2014-04-15 03:46:01

UprightPath (79 views)
2014-04-14 17:39:50

UprightPath (65 views)
2014-04-14 17:35:47

Porlus (80 views)
2014-04-14 15:48:38

tom_mai78101 (104 views)
2014-04-10 04:04:31

BurntPizza (164 views)
2014-04-08 23:06:04

tom_mai78101 (260 views)
2014-04-05 13:34:39

trollwarrior1 (210 views)
2014-04-04 12:06:45

CJLetsGame (220 views)
2014-04-01 02:16:10
List of Learning Resources
by SHC
2014-04-18 03:17:39

List of Learning Resources
by Longarmx
2014-04-08 03:14:44

Good Examples
by matheus23
2014-04-05 13:51:37

Good Examples
by Grunnt
2014-04-03 15:48:46

Good Examples
by Grunnt
2014-04-03 15:48:37

Good Examples
by matheus23
2014-04-01 18:40:51

Good Examples
by matheus23
2014-04-01 18:40:34

Anonymous/Local/Inner class gotchas
by Roquen
2014-03-11 15:22:30
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!