10

I have a method converting BufferedImages who's type is TYPE_CUSTOM to TYPE_INT_RGB. I am using the following code, however I would really like to find a faster way of doing this.

BufferedImage newImg = new BufferedImage(
    src.getWidth(), 
    src.getHeight(), 
    BufferedImage.TYPE_INT_RGB);

ColorConvertOp op = new ColorConvertOp(null);
op.filter(src, newImg);

It works fine, however it's quite slow and I am wondering if there is a faster way to do this conversion.

ColorModel Before Conversion:

ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@1c92586f transparency = 1 has alpha = false isAlphaPre = false

ColorModel After Conversion:

DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0

Thanks!


Update:

Turns out working with the raw pixel data was the best way. Since the TYPE_CUSTOM was actually RGB converting it manually is simple and is about 95% faster than ColorConvertOp.

public static BufferedImage makeCompatible(BufferedImage img) throws IOException {
    // Allocate the new image
    BufferedImage dstImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);

    // Check if the ColorSpace is RGB and the TransferType is BYTE. 
    // Otherwise this fast method does not work as expected
    ColorModel cm = img.getColorModel();
    if ( cm.getColorSpace().getType() == ColorSpace.TYPE_RGB && img.getRaster().getTransferType() == DataBuffer.TYPE_BYTE ) {
        //Allocate arrays
        int len = img.getWidth()*img.getHeight();
        byte[] src = new byte[len*3];
        int[] dst = new int[len];

        // Read the src image data into the array
        img.getRaster().getDataElements(0, 0, img.getWidth(), img.getHeight(), src);

        // Convert to INT_RGB
        int j = 0;
        for ( int i=0; i<len; i++ ) {
            dst[i] = (((int)src[j++] & 0xFF) << 16) | 
                     (((int)src[j++] & 0xFF) << 8) | 
                     (((int)src[j++] & 0xFF));
        }

        // Set the dst image data
        dstImage.getRaster().setDataElements(0, 0, img.getWidth(), img.getHeight(), dst);

        return dstImage;
    }

    ColorConvertOp op = new ColorConvertOp(null);
    op.filter(img, dstImage);

    return dstImage;
}
Matt MacLean
  • 19,410
  • 7
  • 50
  • 53

6 Answers6

7

BufferedImages are painfully slow. I got a solution but I'm not sure you will like it. The fastest way to process and convert buffered images is to extract the raw data array from inside the BufferedImage. You do that by calling buffImg.getRaster() and converting it into the specific raster. Then call raster.getDataStorage(). Once you have access to the raw data it is possible to write fast image processing code without all the abstraction in BufferedImages slowing it down. This technique also requires an in depth understanding of image formats and some reverse engineering on your part. This is the only way I have been able to get image processing code to run fast enough for my applications.

Example:

ByteInterleavedRaster srcRaster = (ByteInterleavedRaster)src.getRaster();
byte srcData[] = srcRaster.getDataStorage();

IntegerInterleavedRaster dstRaster = (IntegerInterleavedRaster)dst.getRaster();
int dstData[] = dstRaster.getDataStorage();

dstData[0] = srcData[0] << 16 | srcData[1] << 8 | srcData[2];

or something like that. Expect compiler errors warning you not to access low level rasters like that. The only place I have had issues with this technique is inside of applets where an access violation will occur.

lessthanoptimal
  • 2,722
  • 2
  • 23
  • 25
  • Beautiful! Working with the raw data cut the processing time by 95%!! See edited post for exactly how I ended up doing this. – Matt MacLean Jan 16 '12 at 06:50
  • I didn't end up using the ByteInterleavedRaster and IntegerInterleavedRaster classes because for some reason I don't have the sun packages. – Matt MacLean Jan 16 '12 at 08:14
  • 1
    Holy crap I just went from processing a 100MB+ psd file for 10 minutes to 8 seconds. Thanks! – EdgeCaseBerg Feb 26 '16 at 16:44
3

I've found rendering using Graphics.drawImage() instead of ColorConvertOp 50 times faster. I can only assume that drawImage() is GPU accelerated.

i.e this is really slow, like 50ms a go for 100x200 rectangles

public void BufferdImage convert(BufferedImage input) {
   BufferedImage output= new BufferedImage(input.getWidht(), input.getHeight(), BufferedImage.TYPE_BYTE_BINARY, CUSTOM_PALETTE);

   ColorConvertOp op = new ColorConvertOp(input.getColorModel().getColorSpace(), 
                                          output.getColorModel().getColorSpace());

   op.filter(input, output);
   return output;
}

i.e however this registers < 1ms for same inputs

public void BufferdImage convert(BufferedImage input) {
   BufferedImage output= new BufferedImage(input.getWidht(), input.getHeight(), BufferedImage.TYPE_BYTE_BINARY, CUSTOM_PALETTE);

   Graphics graphics = output.getGraphics();
   graphics.drawImage(input, 0, 0, null);
   graphics.dispose();
   return output;
}
Adam
  • 35,919
  • 9
  • 100
  • 137
0

Have you tried supplying any RenderingHints? No guarantees, but using

ColorConvertOp op = new ColorConvertOp(new RenderingHints(
    RenderingHints.KEY_COLOR_RENDERING, 
    RenderingHints.VALUE_COLOR_RENDER_SPEED));

rather than the null in your code snippet might speed it up somewhat.

serg10
  • 31,923
  • 16
  • 73
  • 94
0

I suspect the problem might be that ColorConvertOp() works pixel-by-pixel (guaranteed to be "slow").

Q: Is it possible for you to use gc.createCompatibleImage()?

Q: Is your original bitmap true color, or does it use a colormap?

Q: Failing all else, would you be agreeable to writing a JNI interface? Either to your own, custom C code, or to an external library such as ImageMagick?

paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • Also - have you looked at any of the new ImageIO calls in Java 6++? http://docs.oracle.com/javase/6/docs/api/javax/imageio/ImageIO.html – paulsm4 Jan 09 '12 at 19:21
  • gc.createCompatibleImage() will not work (headless machine), bitmap is true color, I'm not opposed to using JNI or ImageMagick pending I can get the same results as using ColorConvertOp. – Matt MacLean Jan 09 '12 at 19:36
  • I am using ImageIO with the native acceleration to open the JPG. – Matt MacLean Jan 09 '12 at 19:37
  • Q: What format *is* your original bitmap? Q: I presume you've definitely narrowed the problem down to ColorConvertOp.filter(), correct? Q: How long does it take to convert an image? Is it because the image is large (what's the size?), because there are so many images (how many are you converting at a time?) or because this method is inherently slow? – paulsm4 Jan 09 '12 at 20:01
  • When you get a chance, please do answer my questions. I'm curious :) But in the meanwhile, here's a link to "JavaMagick": http://code.google.com/p/javamagick/ – paulsm4 Jan 09 '12 at 20:03
  • The original file is an 8-bit RGB JPEG (according to photoshop), I assume I need the ColorConvertOp because the byte order is different than the System's native order. The problem is def the filter method. It takes anywhere from 1s to 5s to do the operation on a 3000x4000 image which does not seem like much but once it's converted I can run a pixel-by-pixel operation on the image in <25ms. I am only ever processing 1 image at a time. – Matt MacLean Jan 09 '12 at 20:15
  • Hi - 1) JPEG stores data in big-endian (Motorola): byte order is not an issue. 2) Try the simple ImageIo functions to read/convert your data (you *might* be able to do the convert in *one* API call) and see what happens! – paulsm4 Jan 09 '12 at 20:26
  • I am using ImageIO.read(stream) to load and decode the JPEG, but the BufferedImage it returns it still TYPE_CUSTOM. Is there and ImageIO method to tell it to decode to TYPE_INT_RGB? – Matt MacLean Jan 09 '12 at 20:53
  • Yes - see ImageTypeSpecifier() http://docs.oracle.com/javase/6/docs/api/javax/imageio/ImageTypeSpecifier.html. See also http://stackoverflow.com/questions/429337/resizing-type-custom-bufferedimages – paulsm4 Jan 09 '12 at 21:34
0

If you have JAI installed then you might try uninstalling it, if you can, or otherwise look for some way to disable codecLib when loading JPEG. In a past life I had similar issues (http://www.java.net/node/660804) and ColorConvertOp was the fastest at the time.

As I recall the fundamental problem is that Java2D is not at all optimized for TYPE_CUSTOM images in general. When you install JAI it comes with codecLib which has a decoder that returns TYPE_CUSTOM and gets used instead of the default. The JAI list may be able to provide more help, it's been several years.

Ken Blair
  • 337
  • 3
  • 4
-1

maybe try this:

Bitmap source = Bitmap.create(width, height, RGB_565);//don't remember exactly...
Canvas c = new Canvas(source);
// then 
c.draw(bitmap, 0, 0);

Then the source bitmap will be modified.

Later you can do:

onDraw(Canvas canvas){
canvas.draw(source, rectSrs,rectDestination, op);
}

if you can manage always reuse the bitmap so you be able to get better performance. As well you can use other canvas functions to draw your bitmap

Ilya Gazman
  • 31,250
  • 24
  • 137
  • 216