4

How to efficiently find out if a PNG only contains white and transparent pixels? My input pictures are largely transparent with a bit of handwritten notes on them. Recognition has to be very reliable. My current code loops all pixels once the bitmap is loaded, but it takes too long to execute. I'm looking for a better/faster solution.

public static boolean isEmpty(Bitmap bmp)
{
    int[] pixels = new int[bmp.getWidth() * bmp.getHeight()];
    bmp.getPixels(pixels, 0, bmp.getWidth(), 0, 0, bmp.getWidth(), bmp.getHeight());
    for (int i : pixels)
    {
        if (!(Color.alpha(i) == 0 || (Color.blue(i) == 255 && Color.red(i) == 255 && Color.green(i) == 255)))
            return false;
    }
    return true;
}
Raffaele
  • 20,627
  • 6
  • 47
  • 86
Diemex
  • 821
  • 2
  • 12
  • 19

4 Answers4

2

Another idea could be:

1 - scale the picture down to a very small size (say 100 by 100 px).
2 - analyze each pixel color in a nested for loop (for y including for x - or vice versa).
3 - if all these 10000 px have an alpha of 0 (just use a counter you increment every time the examined pixel's alpha is 0), you can affirm with good approximation that the image is completely transparent.

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
  • Not a solution: to scale an image you must read **and** process the pixels, so I don't think this is gonna be faster than reading and comparing against known values – Raffaele May 23 '14 at 16:16
  • I think it answers the OP question: `Fastest way to check if an image is all white or all transparent` (previously, `Check if a png on disk is empty - fully transparent`) – Phantômaxx May 23 '14 at 17:08
  • 1
    Scaling the image to 100x100 brought the biggest performance boost. That's 10k iterations vs 4 million for my test images. The nested for loop with getPixel(x, y) was slower than looping all pixels like in my code snippet. – Diemex May 23 '14 at 23:18
  • @Diemex obviously the loop takes less time, but what about the resizing? – Raffaele May 24 '14 at 07:48
  • 2
    I benchmarked the whole method with resizing and it still was faster on my device. – Diemex May 29 '14 at 22:04
1

An image with all alpha channels set to 0 isn't empty. It's as large as the resolution of the image, regardless of the contents of each pixel.

With than in mind, if you want to try and check if an image is "invisible" without checking everything, your best bet is to do a random sampling of pixels and check them. This does have the potential to give you a false positive or false negative, but it is reliable enough to use consistently. I'll give an example that tests 100 random pixels, but you could raise or lower that as you feel comfortable.

public static boolean isEmpty(Bitmap bmp)
{
    Random rand = new Random();
    int currentPixel;
    for(int i = 0; i < 100; i++)
    {
        currentPixel = bmp.getPixel(rand.getNextInt(bmp.getWidth(), rand.getNextInt(bmp.getHeight());
        if(Color.alpha(currentPixel) == 0)
        {
            return false;
        }
    }
    return true;
}

Seeing as I don't know the nature of the images you are testing, a metric you could have is to check a 5% random sample of pixels of an image.

Andrew Schuster
  • 3,229
  • 2
  • 21
  • 32
  • 1
    The idea is not bad, but I think it would be much more reliable to test both diagonals of pixels across the whole image. This scales automatically to the size of the image and seems reliable. – qwertzguy May 23 '14 at 15:22
1

I think the fastest way ever is analyzing the PNG source directly, without even decompressing it to build the bitmap. I tried images of different sizes, each with only one color (for example all green, all red, all black, all transparent), and looking at the bytes with a hexadecimal editor I found the output PNG to be really really small (quite obviously, given the very little entropy), and the IDAT section containing a repeating pattern (which grows with the size of the image).

I'm working on a way to understand the IDAT section and the bit pattern to give you a code sample, but I think this approach may be viable.

On the other side, by sampling pixels on the bitmap, you'd only have a statistical answer, I mean something like "99% it's empty", or "85% it's empty": you won't be able to definitely tell yes/no by not examining all of the pixels (which, of course! may be exactly what you are looking for)

Raffaele
  • 20,627
  • 6
  • 47
  • 86
  • Looking at the actual compressed image data on the disk is probably a lot faster than looping the pixels once the picture is loaded. I'm not sure how to do this though. – Diemex May 23 '14 at 15:46
  • @Diemex not sure about how to analyze the compressed bytes, you mean? – Raffaele May 23 '14 at 16:09
  • Yes exactly. I guess I would have to read up on the specification of png. Work > Reward – Diemex May 23 '14 at 18:05
  • That sounds insanely useful, some example code / or just a small writeup somewhere would be awesome. – Stuart Axon May 05 '16 at 10:43
1

A way to optimize the loop (even if without a benchmark we can't say how much) at the language level (avoiding function calls, assuming the runtime doesn't already do this):

for (int pixel : pixels) {
  if ((pixel & 0xFF000000) != 0 || (pixel & 0xFFFFFF) != 0xFFFFFF) return false;
}
Raffaele
  • 20,627
  • 6
  • 47
  • 86