I would assume that the downside of using the BitmapData.compare function is that it will not stop once it has found a single difference because, it assumes you want to know the location of all the differences between the images.
So the question is if the wasted time of checking the rest of the images after a difference has been found is made up for by the speed improvement over 'manually' checking.
Sounds like time for BENCHMARKS!
Basic test program:
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.utils.getTimer;
public class Test extends Sprite
{
private var one:BitmapData, two:BitmapData;
private const w:int = 1000, h:int = 1000;
private const numberOfRepeats:int = 1000;
public function Test()
{
if (stage != null)
{
init();
}
else
{
addEventListener(Event.ADDED_TO_STAGE, init);
}
}
private function init(e:Event = null):void
{
//make a large BitmapData filled with pretty colours.
one = new BitmapData(w, h, true, 0);
one.perlinNoise(10, 10, 3, 123, false, false);
//make a second BitmapData, and either..
//have it be identical:
two = one.clone();
//make it very slightly different:
//two = one.clone();
//two.setPixel32(w / 2, h / 2, 0);
//make it very different:
//two = new BitmapData(w, h, true, 0);
//two.perlinNoise(50, 50, 3, 456, false, false);
//..and because a .swf that doesn't display anything is boring:
addChild(new Bitmap(one));
//press any key to perform the comparison.
stage.addEventListener(KeyboardEvent.KEY_UP, doCompare);
}
private function doCompare(e:Event = null):void
{
var timeAtStart:int = getTimer();
var areSame:Boolean;
for (var repeat:int = 0; repeat < numberOfRepeats; repeat++)
{
/*
*
* Insert comparison code in here.
*
*/
}
var timeAtEnd:int = getTimer();
trace("result:", areSame);
trace("time:", ((timeAtEnd - timeAtStart) / repeat).toPrecision(3));
}
}
}
We vary what's in the doCompare function to try out different techniques.
As you can see it's testing two 1000x1000 BitmapData objects filled with Perlin noise. Test cases will be:
When they are identical
When they are completely different (both filled with different Perlin noise)
When they have a single pixel (right in the middle) different.
Each test is repeated 1000 times and the time given below is the mean execution time for a single test.
BitmapData.compareif (one.compare(two) as Number == 0)
{
areSame = true;
}
else
{
areSame = false;
}
(I added on the "as Number" for the sake of clarity)
With identical bitmapData objects:
4.03msWith slightly different:
5.38msWith totally different:
34.0msI suspect the increased time when there are differences (especially when there are large differences) is the time taken to create the new BitmapData object used to report what the differences are. Which is a shame, because our program isn't using that data.
BitmapData.getPixel32areSame = true;
checkSameness:
for (var bx:int = 0; bx < one.width; bx++)
{
for (var by:int = 0; by < one.height; by++)
{
if (one.getPixel32(bx, by) != two.getPixel32(bx, by))
{
areSame = false;
break checkSameness;
}
}
}
Using identical:
226msUsing slightly different:
116msUsing very different:
0.001msI had to limit the number of repeats for the first two conditions so as to avoid timeout errors. As you'd expect manually checking each of a million pixels does indeed take a while. Also unsurprising is that if you can end the check after just one pixel it gets very fast.
Custom ShaderThe PixelBender shader
<languageVersion : 1.0;>
kernel TestDifference
< namespace : "";
vendor : "";
version : 1;
description : "Compares two images. If the pixels matche, the result is coloured black and if they differ it is coloured white.";
>
{
input image4 one;
input image4 two;
output pixel4 dst;
void
evaluatePixel()
{
float2 here = outCoord();
float4 fromOne = sampleNearest(one, here);
float4 fromTwo = sampleNearest(two, here);
//(fromOne == fromTwo) works in PixelBender, but not when compiled
// and run through Flash. So I have to check each channel instead.
if (fromOne.r == fromTwo.r && fromOne.g == fromTwo.g && fromOne.b == fromOne.b && fromOne.a == fromTwo.a)
{
dst = pixel4(0.0,0.0,0.0,0.0);
}
else
{
dst = pixel4(1.0,1.0,1.0,1.0);
}
}
}
The comparison function:
var testDifference:Shader = new Shader(new MyEmbeddedShader() as ByteArray);
testDifference.data.one.input = one;
testDifference.data.two.input = two;
var result:Vector.<Number> = new Vector.<Number>();
var differenceJob:ShaderJob = new ShaderJob(testDifference, result, one.width, one.height);
differenceJob.start(true);
if (result.indexOf(1, 0) == -1)
{
areSame = true;
}
else
{
areSame = false;
}
Shaders are designed to operate on images. Here I am pushing the result of the shader into a vector instead. My hope is that the necessary search is faster on a Vector than on a BitmapData, mainly as Vector.IndexOf() can stop its search as soon as it finds a matching instance.
Using identical:
55.2msUsing slightly different:
39.5msUsing very different:
21.5msI had to again use fewer repeats for these tests to avoid running out of memory. Would have thought the garbage collector could collect some garbage between iterations, but I've never really studied Flash's garbage collection. The extra time taken in the cases of similar images must be due to the extra time the IndexOf() function takes to find evidence of difference. I tried repeating the test using LastIndexOf() and found just the same results.
Shader with analysis through BitmapDataSame shader as above, but new comparison function:
var testDifference:Shader = new Shader(new MyEmbeddedShader() as ByteArray);
testDifference.data.one.input = one;
testDifference.data.two.input = two;
var resultBmd:BitmapData = new BitmapData(one.width, one.height, true, 0);
var differenceJob:ShaderJob = new ShaderJob(testDifference, resultBmd);
differenceJob.start(true);
if (resultBmd.getColorBoundsRect(0xffffffff, 0xffffffff, true).isEmpty())
{
areSame = true;
}
else
{
areSame = false;
}
Just as above, the shader checks if each pixel is the same. When it finds a difference, it sets the corresponding pixel on resultBmd to be white. getColorBoundsRect is then used to find if there are any white pixels in resultBmd.
Using identical:
14.8msUsing slightly different:
13.4msUsing very different:
11.9msAs these times are less than even the fastest example of using a Vector to hold output from the shader it seems that forcing a shader to fill a Vector instead of a BitmapData slows it down. The wide variation in times for searching through the Vector shown in the previous test shows that the IndexOf() function is pretty slow, at least compared to BitmapData.getColourBoundsRect.
getColourBoundsRect is VERY fast it seems - skipping it only increases the execution time by around 1ms.
SummaryYeah, BitmapData.compare() is pretty good. It showed a dramatic slowdown when given completely different images but I am fairly sure that was due to the time taken to generate a new huge BitmapData object. It would be nice for BitmapData.compare() to take a parameter that told it not to bother making the BitmapData showing where the differences lie, and instead just return its number codes.
Choosing which areas of the image to compare first is nice smart thinking, but given that BitmapData.compare() can examine a million pixels in a handful of milliseconds it is unlikely to be a necessary optimisation (see also: root of all evil). Especially as I assume this process would only be performed once when the animations are first loaded, or at least wouldn't need to be repeated each frame.
It was interesting trying to do some of the work on a shader, but they're really just not designed for this kind of task. Like BitmapData.compare() the shader has to produce a new image full of information which isn't actually required by the task at hand. Custom shaders in Flash are also still freaky - the times above would likely at least double when running on a Mac for instance.
So there you go, a comprehensive answer to a question no-one asked!
I still don't see how a partial test can return an accurate result. Without examining the entire BitmapData, you've no way of knowing if there may be pixels astray.
I don't think he's suggesting that. Rather, you would
start your search at areas which you happen to know are likely to be different. If they are different then you can stop checking. If they're the same, then you go on to check everywhere else to be sure.
For example, an animation of a rocket ship - start your search for changes at the "engine flame" area which you know is very likely to be different between frames.
Unless you're comparing huge images very often such cleverness is probably just not going to be needed. It's a good technique that can be reused in other applications though!