Related
I just found about Sikuli when I was looking for a library to find matches of a given image within a larger image (both loaded from files).
By default, Sikuli only supports loading the searched image from file, but relies on a proprietary class Screen to take screenshots to use as base for the search... And I'd like to have the ability to use a image file instead.
Looking for a solution has led me to this question, but the answer is a bit vague when you consider that I have no prior experience with Sikuli and the available documentation is not particularly helpful for my needs.
Does anyone have any examples on how to make a customized implementation of Screen, ScreenRegion, ImageScreen and ImageScreenLocation? Even a link to a more detailed documentation on these classes would be a big help.
All I want is to obtain the coordinates of an image match within another image file, so if there's another library that could help with this task I'd more than happy to learn about it!
You can implement it by yourself with something like this:
class MyImage{
private BufferedImage img;
private int imgWidth;
private int imgHeight;
public MyImage(String imagePath){
try{
img = ImageIO.read(getClass().getResource(imagePath));
}catch(IOException ioe){System.out.println("Unable to open file");}
init();
}
public MyImage(BufferedImage img){
this.img = img;
init();
}
private void init(){
imgWidth = img.getWidth;
imgHeight = img.getHeight();
}
public boolean equals(BufferedImage img){
//Your algorithm for image comparison (See below desc for your choices)
}
public boolean contains(BufferedImage subImage){
int subWidth = subImage.getWidth();
int subHeight = subImage.getHeight();
if(subWidth > imgWidth || subHeight > imgHeight)
throw new IllegalArgumentException("SubImage is larger than main image");
for(int x=0; x<(imgHeight-subHeight); x++)
for(int y=0; y<(imgWidth-subWidth); y++){
BufferedImage cmpImage = img.getSumbimage(x, y, subWidth, subHeight);
if(subImage.equals(cmpImage))
return true;
}
return false;
}
}
The contains method will grab a subimage from the main image and compare with the given subimage. If it is not the same, it will move on to the next pixel until it went through the entire image. There might be other more efficient ways than moving pixel by pixel, but this should work.
To compare 2 images for similarity
You have at least 2 options:
Scan pixel by pixel using a pair of nested loop to compare the RGB value of each pixel. (Just like how you compare two int 2D array for similarity)
It should be possible to generate a hash for the 2 images and just compare the hash value.
Aah... Sikuli has an answer for this too... You just didnt look close enough. :)
Answer : The FINDER Class
Pattern searchImage = new Pattern("abc.png").similar((float)0.9);
String ScreenImage = "xyz.png"; //In this case, the image you want to search
Finder objFinder = null;
Match objMatch = null;
objFinder = new Finder(ScreenImage);
objFinder.find(searchImage); //searchImage is the image you want to search within ScreenImage
int counter = 0;
while(objFinder.hasNext())
{
objMatch = objFinder.next(); //objMatch gives you the matching region.
counter++;
}
if(counter!=0)
System.out.println("Match Found!");
In the end I gave up on Sikuli and used pure OpenCV in my Android project: The Imgproc.matchTemplate() method did the trick, giving me a matrix of all pixels with "scores" for the likehood of that being the starting point of my subimage.
With Sikuli, you can check for the presence of an image inside another one.
In this example code, the pictures are loaded from files.
This code tell us if the second picture is a part of the first picture.
public static void main(String[] argv){
String img1Path = "/test/img1.png";
String img2Path = "/test/img2.png";
if ( findPictureRegion(img1Path, img2Path) == null )
System.out.println("Picture 2 was not found in picture 1");
else
System.out.println("Picture 2 is in picture 1");
}
public static ScreenRegion findPictureRegion(String refPictureName, String targetPictureName2){
Target target = new ImageTarget(new File(targetPictureName2));
target.setMinScore(0.5); // Precision of recognization from 0 to 1.
BufferedImage refPicture = loadPicture(refPictureName);
ScreenRegion screenRegion = new StaticImageScreenRegion(refPicture);
return screenRegion.find(target);
}
public static BufferedImage loadPicture(String pictureFullPath){
try {
return ImageIO.read(new File(pictureFullPath));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
To use Sikuli package, I added this dependency with Maven :
<!-- SIKULI libraries -->
<dependency>
<groupId>org.sikuli</groupId>
<artifactId>sikuli-api</artifactId>
<version>1.1.0</version>
</dependency>
Nearest neighbor scaling works: The entire picture stays intact when I use TYPE_NEAREST_NEIGHBOR.
Even though it is Scala code, all used libraries are standard Java libraries.
Functions:
def getBufferedImage(imageFile: java.io.File): BufferedImage = {
ImageIO.read(imageFile)
}
def scaleImage(image: BufferedImage, minSize: Double): BufferedImage = {
val before: BufferedImage = image
val w = before.getWidth()
val h = before.getHeight()
val affit = new AffineTransform()
var scale = 1.0
if(h < w) {
if(h > 0) {
scale = minSize / h
}
} else {
if(w > 0) {
scale = minSize / w
}
}
affit.scale(scale, scale)
val affitop = new AffineTransformOp(affit, AffineTransformOp.TYPE_BICUBIC)
affitop.filter(before, null)
}
def getImageJpegByteArray(image: BufferedImage): Array[Byte] = {
val baos = new java.io.ByteArrayOutputStream()
val mcios = new MemoryCacheImageOutputStream(baos)
ImageIO.write(image, "jpeg", mcios)
mcios.close()
baos.toByteArray
}
Calling code snippet:
val img = getBufferedImage(imageFile)
val scaledImg = scaleImage(img, 512)
val result = getImageJpegByteArray(scaledImg)
// result is written to SQLite database
result is written to an SQLite database. If I download it from the database and save it as JPEG file, the resulting JPEG is
as expected if I use AffineTransformOp.TYPE_NEAREST_NEIGHBOR
completely black if I use AffineTransformOp.TYPE_BILINEAR
completely black if I use AffineTransformOp.TYPE_BICUBIC
Consequently, I accuse AffineTransformOp of being buggy...
How can I solve this problem?
File magic number of result is always ff d8 ff as expected for JPEG.
Details
Java version: Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_71
Operating System: Apple, OS X 10.9.5
Test image: http://www.photos-public-domain.com/wp-content/uploads/2012/05/thundercloud-plum-blossoms.jpg
I was able to reproduce your issue on Java 1.7.0_71 on OS X 10.10.4 (I rewrote your code in Java, I can post the full code if you are interested).
In any case, the problem is not that AffineTransformOp is buggy in itself. In my test program I displayed the image using a minimal Swing JFrame and the scaled image looked all good there. This is likely why most people in the comments did not understand the problem.
Part of the issue is that the BufferedImage returned by AffineTransformOp when you don't provide a destination to the filter method (the second parameter, null in your case), it will create one for you. This image will get type BufferedImage.TYPE_INT_ARGB. Here is the relevant code from AffineTransformOp.createCompatibleDestImage() (lines 456-468, I kept the formatting, to make it easier to spot):
ColorModel cm = src.getColorModel();
if (interpolationType != TYPE_NEAREST_NEIGHBOR &&
(cm instanceof IndexColorModel ||
cm.getTransparency() == Transparency.OPAQUE)
{
image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
}
else {
image = new BufferedImage(cm,
src.getRaster().createCompatibleWritableRaster(w,h),
cm.isAlphaPremultiplied(), null);
}
Notice the special case for TYPE_NEAREST_NEIGHBOR, which explains why you'll get different behavior when using nearest neighbor algorithm.
Normally this is all good, however (as I said, the image displays just fine in a Swing component).
The problem arises when you try to store this image as a JPEG. During the years, there's been a lot of confusion and issues related to the ImageIO JPEG plugin and whether it will allow you to write images with alpha channel (like your TYPE_INT_ARGB image). It does allow that. But, most often ARGB JPEGs will get misinterpreted as CMYK JPEGs (as they are 4 channels, and storing ARGB data in JPEG is very exotic) and will be displayed in all funky colors. In your case though, it seems to be all black...
So, there are two possible solutions:
Either write your image in a file format that supports alpha channel, like PNG or TIFF (TIFF requires an extra plugin, so it might not be the best choice). Like this:
ImageIO.write(image, "PNG", mcios);
Or, make sure your BufferedImage is in a pixel format without alpha channel before storing as JPEG. You can do this after the scaling, but the easiest (and fastest) is to just provide the AffineTransformOp with an explicit destination image, like this:
Rectangle newSize = affitop.getBounds2D(before).getBounds();
return affitop.filter(before,
new BufferedImage(newSize.width, newSize.height, BufferedImage.TYPE_3BYTE_BGR));
Here is your image, scaled by the program, using JPEG format and the TYPE_3BYTE_BGR:
I'm sure you can rewrite my Java code back to Scala. :-)
I need to resize PNG, JPEG and GIF files. How can I do this using Java?
FWIW I just released (Apache 2, hosted on GitHub) a simple image-scaling library for Java called imgscalr (available on Maven central).
The library implements a few different approaches to image-scaling (including Chris Campbell's incremental approach with a few minor enhancements) and will either pick the most optimal approach for you if you ask it to, or give you the fastest or best looking (if you ask for that).
Usage is dead-simple, just a bunch of static methods. The simplest use-case is:
BufferedImage scaledImage = Scalr.resize(myImage, 200);
All operations maintain the image's original proportions, so in this case you are asking imgscalr to resize your image within a bounds of 200 pixels wide and 200 pixels tall and by default it will automatically select the best-looking and fastest approach for that since it wasn't specified.
I realize on the outset this looks like self-promotion (it is), but I spent my fair share of time googling this exact same subject and kept coming up with different results/approaches/thoughts/suggestions and decided to sit down and write a simple implementation that would address that 80-85% use-cases where you have an image and probably want a thumbnail for it -- either as fast as possible or as good-looking as possible (for those that have tried, you'll notice doing a Graphics.drawImage even with BICUBIC interpolation to a small enough image, it still looks like garbage).
After loading the image you can try:
BufferedImage createResizedCopy(Image originalImage,
int scaledWidth, int scaledHeight,
boolean preserveAlpha)
{
System.out.println("resizing...");
int imageType = preserveAlpha ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage scaledBI = new BufferedImage(scaledWidth, scaledHeight, imageType);
Graphics2D g = scaledBI.createGraphics();
if (preserveAlpha) {
g.setComposite(AlphaComposite.Src);
}
g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null);
g.dispose();
return scaledBI;
}
Thumbnailator is an open-source image resizing library for Java with a fluent interface, distributed under the MIT license.
I wrote this library because making high-quality thumbnails in Java can be surprisingly difficult, and the resulting code could be pretty messy. With Thumbnailator, it's possible to express fairly complicated tasks using a simple fluent API.
A simple example
For a simple example, taking a image and resizing it to 100 x 100 (preserving the aspect ratio of the original image), and saving it to an file can achieved in a single statement:
Thumbnails.of("path/to/image")
.size(100, 100)
.toFile("path/to/thumbnail");
An advanced example
Performing complex resizing tasks is simplified with Thumbnailator's fluent interface.
Let's suppose we want to do the following:
take the images in a directory and,
resize them to 100 x 100, with the aspect ratio of the original image,
save them all to JPEGs with quality settings of 0.85,
where the file names are taken from the original with thumbnail. appended to the beginning
Translated to Thumbnailator, we'd be able to perform the above with the following:
Thumbnails.of(new File("path/to/directory").listFiles())
.size(100, 100)
.outputFormat("JPEG")
.outputQuality(0.85)
.toFiles(Rename.PREFIX_DOT_THUMBNAIL);
A note about image quality and speed
This library also uses the progressive bilinear scaling method highlighted in Filthy Rich Clients by Chet Haase and Romain Guy in order to generate high-quality thumbnails while ensuring acceptable runtime performance.
You don't need a library to do this. You can do it with Java itself.
Chris Campbell has an excellent and detailed write-up on scaling images - see this article.
Chet Haase and Romain Guy also have a detailed and very informative write-up of image scaling in their book, Filthy Rich Clients.
Java Advanced Imaging is now open source, and provides the operations you need.
If you are dealing with large images or want a nice looking result it's not a trivial task in java. Simply doing it via a rescale op via Graphics2D will not create a high quality thumbnail. You can do it using JAI, but it requires more work than you would imagine to get something that looks good and JAI has a nasty habit of blowing our your JVM with OutOfMemory errors.
I suggest using ImageMagick as an external executable if you can get away with it. Its simple to use and it does the job right so that you don't have to.
If, having imagemagick installed on your maschine is an option, I recommend im4java. It is a very thin abstraction layer upon the command line interface, but does its job very well.
The Java API does not provide a standard scaling feature for images and downgrading image quality.
Because of this I tried to use cvResize from JavaCV but it seems to cause problems.
I found a good library for image scaling: simply add the dependency for "java-image-scaling" in your pom.xml.
<dependency>
<groupId>com.mortennobel</groupId>
<artifactId>java-image-scaling</artifactId>
<version>0.8.6</version>
</dependency>
In the maven repository you will get the recent version for this.
Ex. In your java program
ResampleOp resamOp = new ResampleOp(50, 40);
BufferedImage modifiedImage = resamOp.filter(originalBufferedImage, null);
You could try to use GraphicsMagick Image Processing System with im4java as a comand-line interface for Java.
There are a lot of advantages of GraphicsMagick, but one for all:
GM is used to process billions of
files at the world's largest photo
sites (e.g. Flickr and Etsy).
Image Magick has been mentioned. There is a JNI front end project called JMagick. It's not a particularly stable project (and Image Magick itself has been known to change a lot and even break compatibility). That said, we've had good experience using JMagick and a compatible version of Image Magick in a production environment to perform scaling at a high throughput, low latency rate. Speed was substantially better then with an all Java graphics library that we previously tried.
http://www.jmagick.org/index.html
Simply use Burkhard's answer but add this line after creating the graphics:
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
You could also set the value to BICUBIC, it will produce a better quality image but is a more expensive operation. There are other rendering hints you can set but I have found that interpolation produces the most notable effect.
Keep in mind if you want to zoom in in a lot, java code most likely will be very slow. I find larger images start to produce lag around 300% zoom even with all rendering hints set to optimize for speed over quality.
You can use Marvin (pure Java image processing framework) for this kind of operation:
http://marvinproject.sourceforge.net
Scale plug-in:
http://marvinproject.sourceforge.net/en/plugins/scale.html
It turns out that writing a performant scaler is not trivial. I did it once for an open source project: ImageScaler.
In principle 'java.awt.Image#getScaledInstance(int, int, int)' would do the job as well, but there is a nasty bug with this - refer to my link for details.
I have developed a solution with the freely available classes ( AnimatedGifEncoder, GifDecoder, and LWZEncoder) available for handling GIF Animation.
You can download the jgifcode jar and run the GifImageUtil class.
Link: http://www.jgifcode.com
you can use following popular product: thumbnailator
If you dont want to import imgScalr like #Riyad Kalla answer above which i tested too works fine, you can do this
taken from Peter Walser answer #Peter Walser on another issue though:
/**
* utility method to get an icon from the resources of this class
* #param name the name of the icon
* #return the icon, or null if the icon wasn't found.
*/
public Icon getIcon(String name) {
Icon icon = null;
URL url = null;
ImageIcon imgicon = null;
BufferedImage scaledImage = null;
try {
url = getClass().getResource(name);
icon = new ImageIcon(url);
if (icon == null) {
System.out.println("Couldn't find " + url);
}
BufferedImage bi = new BufferedImage(
icon.getIconWidth(),
icon.getIconHeight(),
BufferedImage.TYPE_INT_RGB);
Graphics g = bi.createGraphics();
// paint the Icon to the BufferedImage.
icon.paintIcon(null, g, 0,0);
g.dispose();
bi = resizeImage(bi,30,30);
scaledImage = bi;// or replace with this line Scalr.resize(bi, 30,30);
imgicon = new ImageIcon(scaledImage);
} catch (Exception e) {
System.out.println("Couldn't find " + getClass().getName() + "/" + name);
e.printStackTrace();
}
return imgicon;
}
public static BufferedImage resizeImage (BufferedImage image, int areaWidth, int areaHeight) {
float scaleX = (float) areaWidth / image.getWidth();
float scaleY = (float) areaHeight / image.getHeight();
float scale = Math.min(scaleX, scaleY);
int w = Math.round(image.getWidth() * scale);
int h = Math.round(image.getHeight() * scale);
int type = image.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
boolean scaleDown = scale < 1;
if (scaleDown) {
// multi-pass bilinear div 2
int currentW = image.getWidth();
int currentH = image.getHeight();
BufferedImage resized = image;
while (currentW > w || currentH > h) {
currentW = Math.max(w, currentW / 2);
currentH = Math.max(h, currentH / 2);
BufferedImage temp = new BufferedImage(currentW, currentH, type);
Graphics2D g2 = temp.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(resized, 0, 0, currentW, currentH, null);
g2.dispose();
resized = temp;
}
return resized;
} else {
Object hint = scale > 2 ? RenderingHints.VALUE_INTERPOLATION_BICUBIC : RenderingHints.VALUE_INTERPOLATION_BILINEAR;
BufferedImage resized = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = resized.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(image, 0, 0, w, h, null);
g2.dispose();
return resized;
}
}
Try this folowing method :
ImageIcon icon = new ImageIcon("image.png");
Image img = icon.getImage();
Image newImg = img.getScaledInstance(350, 350, java.evt.Image.SCALE_SMOOTH);
icon = new ImageIcon(img);
JOptionPane.showMessageDialog(null, "image on The frame", "Display Image", JOptionPane.INFORMATION_MESSAGE, icon);
you can also use
Process p = Runtime.getRuntime().exec("convert " + origPath + " -resize 75% -quality 70 " + largePath + "");
p.waitFor();
Design jLabel first:
JLabel label1 = new JLabel("");
label1.setHorizontalAlignment(SwingConstants.CENTER);
label1.setBounds(628, 28, 169, 125);
frame1.getContentPane().add(label1); //frame1 = "Jframe name"
Then you can code below code(add your own height and width):
ImageIcon imageIcon1 = new ImageIcon(new ImageIcon("add location url").getImage().getScaledInstance(100, 100, Image.SCALE_DEFAULT)); //100, 100 add your own size
label1.setIcon(imageIcon1);
I am using Xuggle to play interlaced video from ip-camera and really need in deinterlace option, which exists in FFMpeg (Xuggle is a java wrapper over FFMpeg library).
Unfortunately, deinterlace option ("-vf yadif", if i'm not mistake) is not exposed by Xuggle. So, i'm interested in either pure java solution of Java BufferedImage deinterlacing or some way to make Xuggle do that.
Now i simply duplicate odd lines and discard even. It is quick but quality of course is not very well.
// Interlaced image
BufferedImage img = (BufferedImage) event.getImage();
//duplicate odd lines
WritableRaster raster = img.getRaster();
for (int i = 0; i < raster.getHeight(); i += 2) {
int[] arr2 = null;
raster.setPixels(0, i + 1, raster.getWidth(), 1,
raster.getPixels(0, i, raster.getWidth(), 1,arr2));
}
//now img is deinterlaced
Could somebody help me in this regard?
I used FFMPEG's yadif filter through JNI. Java realisations were very slow.
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;
}
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.
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;
}
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.
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?
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.
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