Efficiently XOR two images in Flash compile target - actionscript-3
I need to XOR two BitmapData objects together.
I'm writing in Haxe, using the flash.* libraries and the AS3 compile target.
I've investigated HxSL and PixelBender, and neither one seems to have a bitwise XOR operator, nor do they have any other bitwise operators that could be used to create XOR (but am I missing something obvious? I'd accept any answer which gives a way to do a bitwise XOR using only the integer/float operators and functions available in HxSL or PixelBlender).
None of the predefined filters or shaders in Flash that I can find seem to be able to do a XOR of two images (but again, am I missing something obvious? Can XOR be done with a combination of other filters).
I can find nothing like a XOR drawmode for drawing things onto other things (but that doesn't mean it doesn't exist! That would work too, if it exists!)
The only way I can find at the moment is a pixel-by-pixel loop over the image, but this takes a couple of seconds per image even on a fast machine, as opposed to filters, which I use for my other image processing operations, which are about a hundred times faster.
Is there any faster method?
Edit:
Playing around with this a bit more I found that removing the conditional and extra Vector access in the loop speeds it up by about 100ms on my machine.
Here's the previous XOR loop:
// Original Vector XOR code:
for (var i: int = 0; i < len; i++) {
// XOR.
result[i] = vec1[i] ^ vec2[i];
if (ignoreAlpha) {
// Force alpha of FF so we can see the result.
result[i] |= 0xFF000000;
}
}
Here is the updated XOR loop for the Vector solution:
if (ignoreAlpha) {
// Force alpha of FF so we can see the result.
alphaMask = 0xFF000000;
}
// Fewer Vector accessors makes it quicker:
for (var i: int = 0; i < len; i++) {
// XOR.
result[i] = alphaMask | (vec1[i] ^ vec2[i]);
}
Answer:
Here are the solutions that I've tested to XOR two images in Flash.
I found that the PixelBender solution is about 6-10 slower than doing it in straight ActionScript.
I don't know if it's because I have a slow algorithm or it's just the limits of trying to fake bitwise operations in PixelBender.
Results:
PixelBender: ~6500ms
BitmapData.getVector(): ~480-500ms
BitmapData.getPixel32(): ~1200ms
BitmapData.getPixels(): ~1200ms
The clear winner is use BitmapData.getVector() and then XOR the two streams of pixel data.
1. PixelBender solution
This is how I implemented the bitwise XOR in PixelBender, based on the formula given on Wikipedia: http://en.wikipedia.org/wiki/Bitwise_operation#Mathematical_equivalents
Here is a Gist of the final PBK: https://gist.github.com/Coridyn/67a0ff75afaa0163f673
On my machine running an XOR on two 3200x1400 images this takes about 6500-6700ms.
I first converted the formula to JavaScript to check that it was correct:
// Do it for each RGBA channel.
// Each channel is assumed to be 8bits.
function XOR(x, y){
var result = 0;
var bitCount = 8; // log2(x) + 1
for (var n = 0; n < bitCount; n++) {
var pow2 = pow(2, n);
var x1 = mod(floor(x / pow2), 2);
var y1 = mod(floor(y / pow2), 2);
var z1 = mod(x1 + y1, 2);
result += pow2 * z1;
}
console.log('XOR(%s, %s) = %s', x, y, result);
console.log('%s ^ %s = %s', x, y, (x ^ y));
return result;
}
// Split out these functions so it's
// easier to convert to PixelBender.
function mod(x, y){
return x % y;
}
function pow(x, y){
return Math.pow(x, y);
}
function floor(x){
return Math.floor(x);
}
Confirm that it's correct:
// Test the manual XOR is correct.
XOR(255, 85); // 170
XOR(170, 85); // 255
XOR(170, 170); // 0
Then I converted the JavaScript to PixelBender by unrolling the loop using a series of macros:
// Bitwise algorithm was adapted from the "mathematical equivalents" formula on Wikipedia:
// http://en.wikipedia.org/wiki/Bitwise_operation#Mathematical_equivalents
// Macro for 2^n (it needs to be done a lot).
#define POW2(n) pow(2.0, n)
// Slight optimisation for the zeroth case - 2^0 = 1 is redundant so remove it.
#define XOR_i_0(x, y) ( mod( mod(floor(x), 2.0) + mod(floor(y), 2.0), 2.0 ) )
// Calculations for a given "iteration".
#define XOR_i(x, y, i) ( POW2(i) * ( mod( mod(floor(x / POW2(i)), 2.0) + mod(floor(y / POW2(i)), 2.0), 2.0 ) ) )
// Flash doesn't support loops.
// Unroll the loop by defining macros that call the next macro in the sequence.
// Adapted from: http://www.simppa.fi/blog/category/pixelbender/
// http://www.simppa.fi/source/LoopMacros2.pbk
#define XOR_0(x, y) XOR_i_0(x, y)
#define XOR_1(x, y) XOR_i(x, y, 1.0) + XOR_0(x, y)
#define XOR_2(x, y) XOR_i(x, y, 2.0) + XOR_1(x, y)
#define XOR_3(x, y) XOR_i(x, y, 3.0) + XOR_2(x, y)
#define XOR_4(x, y) XOR_i(x, y, 4.0) + XOR_3(x, y)
#define XOR_5(x, y) XOR_i(x, y, 5.0) + XOR_4(x, y)
#define XOR_6(x, y) XOR_i(x, y, 6.0) + XOR_5(x, y)
#define XOR_7(x, y) XOR_i(x, y, 7.0) + XOR_6(x, y)
// Entry point for XOR function.
// This will calculate the XOR the current pixels.
#define XOR(x, y) XOR_7(x, y)
// PixelBender uses floats from 0.0 to 1.0 to represent 0 to 255
// but the bitwise operations above work on ints.
// These macros convert between float and int values.
#define FLOAT_TO_INT(x) float(x) * 255.0
#define INT_TO_FLOAT(x) float(x) / 255.0
XOR for each channel of the current pixel in the evaluatePixel function:
void evaluatePixel()
{
// Acquire the pixel values from both images at the current location.
float4 frontPixel = sampleNearest(inputImage, outCoord());
float4 backPixel = sampleNearest(diffImage, outCoord());
// Set up the output variable - RGBA.
pixel4 result = pixel4(0.0, 0.0, 0.0, 1.0);
// XOR each channel.
result.r = INT_TO_FLOAT ( XOR(FLOAT_TO_INT(frontPixel.r), FLOAT_TO_INT(backPixel.r)) );
result.g = INT_TO_FLOAT ( XOR(FLOAT_TO_INT(frontPixel.g), FLOAT_TO_INT(backPixel.g)) );
result.b = INT_TO_FLOAT ( XOR(FLOAT_TO_INT(frontPixel.b), FLOAT_TO_INT(backPixel.b)) );
// Return the result for this pixel.
dst = result;
}
ActionScript Solutions
2. BitmapData.getVector()
I found the fastest solution is to extract a Vector of pixels from the two images and perform the XOR in ActionScript.
For the same two 3200x1400 this takes about 480-500ms.
package diff
{
import flash.display.Bitmap;
import flash.display.DisplayObject;
import flash.display.IBitmapDrawable;
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.utils.ByteArray;
/**
* #author Coridyn
*/
public class BitDiff
{
/**
* Perform a binary diff between two images.
*
* Return the result as a Vector of uints (as used by BitmapData).
*
* #param image1
* #param image2
* #param ignoreAlpha
* #return
*/
public static function diffImages(image1: DisplayObject,
image2: DisplayObject,
ignoreAlpha: Boolean = true): Vector.<uint> {
// For simplicity get the smallest common width and height of the two images
// to perform the XOR.
var w: Number = Math.min(image1.width, image2.width);
var h: Number = Math.min(image1.height, image2.height);
var rect: Rectangle = new Rectangle(0, 0, w, h);
var vec1: Vector.<uint> = BitDiff.getVector(image1, rect);
var vec2: Vector.<uint> = BitDiff.getVector(image2, rect);
var resultVec: Vector.<uint> = BitDiff.diffVectors(vec1, vec2, ignoreAlpha);
return resultVec;
}
/**
* Extract a portion of an image as a Vector of uints.
*
* #param drawable
* #param rect
* #return
*/
public static function getVector(drawable: DisplayObject, rect: Rectangle): Vector.<uint> {
var data: BitmapData = BitDiff.getBitmapData(drawable);
var vec: Vector.<uint> = data.getVector(rect);
data.dispose();
return vec;
}
/**
* Perform a binary diff between two streams of pixel data.
*
* If `ignoreAlpha` is false then will not normalise the
* alpha to make sure the pixels are opaque.
*
* #param vec1
* #param vec2
* #param ignoreAlpha
* #return
*/
public static function diffVectors(vec1: Vector.<uint>,
vec2: Vector.<uint>,
ignoreAlpha: Boolean): Vector.<uint> {
var larger: Vector.<uint> = vec1;
if (vec1.length < vec2.length) {
larger = vec2;
}
var len: Number = Math.min(vec1.length, vec2.length),
result: Vector.<uint> = new Vector.<uint>(len, true);
var alphaMask = 0;
if (ignoreAlpha) {
// Force alpha of FF so we can see the result.
alphaMask = 0xFF000000;
}
// Assume same length.
for (var i: int = 0; i < len; i++) {
// XOR.
result[i] = alphaMask | (vec1[i] ^ vec2[i]);
}
if (vec1.length != vec2.length) {
// Splice the remaining items.
result = result.concat(larger.slice(len));
}
return result;
}
}
}
3. BitmapData.getPixel32()
Your current approach of looping over the BitmapData with BitmapData.getPixel32() gave a similar speed of about 1200ms:
for (var y: int = 0; y < h; y++) {
for (var x: int = 0; x < w; x++) {
sourcePixel = bd1.getPixel32(x, y);
resultPixel = sourcePixel ^ bd2.getPixel(x, y);
result.setPixel32(x, y, resultPixel);
}
}
4. BitmapData.getPixels()
My final test was to try iterating over two ByteArrays of pixel data (very similar to the Vector solution above). This implementation also took about 1200ms:
/**
* Extract a portion of an image as a Vector of uints.
*
* #param drawable
* #param rect
* #return
*/
public static function getByteArray(drawable: DisplayObject, rect: Rectangle): ByteArray {
var data: BitmapData = BitDiff.getBitmapData(drawable);
var pixels: ByteArray = data.getPixels(rect);
data.dispose();
return pixels;
}
/**
* Perform a binary diff between two streams of pixel data.
*
* If `ignoreAlpha` is false then will not normalise the
* alpha to make sure the pixels are opaque.
*
* #param ba1
* #param ba2
* #param ignoreAlpha
* #return
*/
public static function diffByteArrays(ba1: ByteArray,
ba2: ByteArray,
ignoreAlpha: Boolean): ByteArray {
// Reset position to start of array.
ba1.position = 0;
ba2.position = 0;
var larger: ByteArray = ba1;
if (ba1.bytesAvailable < ba2.bytesAvailable) {
larger = ba2;
}
var len: Number = Math.min(ba1.length / 4, ba2.length / 4),
result: ByteArray = new ByteArray();
// Assume same length.
var resultPixel:uint;
for (var i: uint = 0; i < len; i++) {
// XOR.
resultPixel = ba1.readUnsignedInt() ^ ba2.readUnsignedInt();
if (ignoreAlpha) {
// Force alpha of FF so we can see the result.
resultPixel |= 0xFF000000;
}
result.writeUnsignedInt(resultPixel);
}
// Seek back to the start.
result.position = 0;
return result;
}
There are a few possible options depending on what you want to achieve (e.g. is the XOR per channel or is it just any pixel that is non-black?).
There is the BitmapData.compare() method which can give you a lot of information about the two bitmaps. You could BitmapData.threshold() the input data before comparing.
Another option would be to use the draw method with the BlendMode.DIFFERENCE blend mode to draw your two images into the same BitmapData instance. That will show you the difference between the two images (equivalent to the Difference blending mode in Photoshop).
If you need to check if any pixel is non-black then you can try running a BitmapData.threshold first and then draw the result with the difference blend mode as above for the two images.
Are you doing this for image processing or something else like per-pixel hit detection?
To start with I'd have a look at BitmapData and see what is available to play with.
Related
Applying a Color Transformation to very Pixel of a texture (Libgdx)
A game I am currently developing uses a 5x5 matrix to change the colors of the image on a per pixel basis. I was wondering if anyone has developed an extremely fast algorithm for something like this. For every Pixel(setPixel(sourcePixel * Matrix)) I have built my own algorithm for this by getting and setting pixels on pixmap then drawing a new pixmap from this through iterating every pixel with set/get pixel. I have found a reasonably fast algorithm for this (150 million pixels ~3 seconds), but I was thinking of another idea rather than using the pixmap but I am unsure of how to implement this. Libgdx provides a FileHandle.readBytes() method that reads image files (in my case PNG) to byte arrays. My thought was rather than creating a pixmap, read the byte array while iterating the pixels. While iterating I would be drawing a new pixmap meaning their really is no point for me to make one for the base pixmap in the first place. With tests I found that with my current algorithm, 70% of the time it takes is from the method (PixMap.getPixel(x, y), and I could bypass this by straight reading the byte array. I have looked into PNG readers for byte array's online but to no avail. Note I am unable to use ImageIO due it being an android based game. Would it make it faster by reading the byte array data while iterating/ is it possible to do this? In the code below, JList is basically a HashMap in this context private static JList<Integer, Pixmap> colorShiftImage(Pixmap p, JList<Integer, float[][]> cms){ JList<float[][], Pixmap> tempList = new JList<>(); for(int i = cms.size() - 1; i > -1; --i){ tempList.add(cms.getInt(i), new Pixmap(p.getWidth(), p.getHeight(), Pixmap.Format.RGBA8888)); } for(int y = p.getHeight() - 1; y > -1; --y){ for(int x = p.getWidth() - 1; x > -1; --x){ int v = p.getPixel(x, y); if(v != 0) { r = ((v & 0xff000000) >>> 24); g = ((v & 0x00ff0000) >>> 16); b = ((v & 0x0000ff00) >>> 8); a = ((v & 0x000000ff)); for(int i = tempList.size() - 1; i > -1; --i) { float[][] c = tempList.getIDList().get(i); tempList.getInt(i).drawPixel(x, y, (((l((r * c[0][0]) + (c[1][0] * g) + (c[2][0] * b) + (c[3][0] * a) + c[4][0])) << 24) | ((l((r * c[0][1]) + (c[1][1] * g) + (c[2][1] * b) + (c[3][1] * a) + c[4][1])) << 16) | ((l((r * c[0][2]) + (c[1][2] * g) + (c[2][2] * b) + (c[3][2] * a) + c[4][2])) << 8) | ((l((r * c[0][3]) + (c[1][3] * g) + (c[2][3] * b) + (c[3][3] * a) + c[4][3]))))); } } } } JList<Integer, Pixmap> returnL = new JList<>(); for(int i = tempList.size() - 1; i > - 1; --i){ returnL.add(cms.getIDList().get(i), tempList.getInt(i)); } return returnL; } public static int l(float v){ if(v < 0)return 0; else if(v > 255)return 255; return (int) v; }
Java 2D Polygon outside another
I'd like to know if there is a java way to, given a polygon, draw another one at a given distance and with the same center. I tried AffineTransform but don't really know how it Works. Thank you.
You need to translate your polygon by half its centroid width and height. I have included the code that comes from http://paulbourke.net/geometry/polygonmesh/PolygonUtilities.java to calculate the centroid of a polygon. public void drawPolygon(){ Graphics2D g2 = bufferedImage.createGraphics(); Polygon poly=new Polygon(); poly.addPoint(100, 100); poly.addPoint(200, 100); poly.addPoint(200, 200); poly.addPoint(150, 250); poly.addPoint(100, 200); poly.addPoint(100, 100); g2.setColor(Color.blue); g2.fillPolygon(poly); g2.setColor(Color.red); Point2D.Double []pts=new Point2D.Double[poly.npoints]; for (int i=0;i<poly.npoints;i++){ pts[i]=new Point2D.Double(poly.xpoints[i],poly.ypoints[i]); } Point2D centroid=centerOfMass(pts); g2.translate(-centroid.getX(), -centroid.getY()); g2.scale(2, 2); g2.drawPolygon(poly); } public static double area(Point2D[] polyPoints) { int i, j, n = polyPoints.length; double area = 0; for (i = 0; i < n; i++) { j = (i + 1) % n; area += polyPoints[i].getX() * polyPoints[j].getY(); area -= polyPoints[j].getX() * polyPoints[i].getY(); } area /= 2.0; return (area); } /** * Function to calculate the center of mass for a given polygon, according * to the algorithm defined at * http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/ * * #param polyPoints * array of points in the polygon * #return point that is the center of mass */ public static Point2D centerOfMass(Point2D[] polyPoints) { double cx = 0, cy = 0; double area = area(polyPoints); // could change this to Point2D.Float if you want to use less memory Point2D res = new Point2D.Double(); int i, j, n = polyPoints.length; double factor = 0; for (i = 0; i < n; i++) { j = (i + 1) % n; factor = (polyPoints[i].getX() * polyPoints[j].getY() - polyPoints[j].getX() * polyPoints[i].getY()); cx += (polyPoints[i].getX() + polyPoints[j].getX()) * factor; cy += (polyPoints[i].getY() + polyPoints[j].getY()) * factor; } area *= 6.0f; factor = 1 / area; cx *= factor; cy *= factor; res.setLocation(cx, cy); return res; } Another way of doing this, common in the GIS world, is to buffer a polygon. There is a library called Java Topology Suite that will provide this functionality, although it might be harder to figure out what the scale factor is. There are some very interesting discussions about polygon growing in this post: An algorithm for inflating/deflating (offsetting, buffering) polygons
Trouble creating a spectrogram
I know it was asked a thousand times before, but I still can't find a solution. Searching SO, I indeed found the algorithm for it, but lacking the mathematical knowledge required to truly understand it, I am helplessly lost! To start with the beginning, my goal is to compute an entire spectrogram and save it to an image in order to use it for a visualizer. I tried using Sound.computeSpectrum, but this requires to play the sound and wait for it to end, I want to compute the spectrogram in a way shorter time than that will require to listen all the song. And I have 2 hours long mp3s. What I am doing now is to read the bytes from a Sound object, the separate into two Vectors(.); Then using a timer, at each 100 ms I call a function (step1) where I have the implementation of the algorithm, as follows: for each vector (each for a channel) I apply the hann function to the elements; for each vector I nullify the imaginary part (I have a secondary vector for that) for each vector I apply FFT for each vector I find the magnitude for the first N / 2 elements for each vector I convert squared magnitude to dB scale end. But I get only negative values, and only 30 percent of the results might be useful (in the way that the rest are identical) I will post the code for only one channel to get rid off the "for each vector" part. private var N:Number = 512; private function step1() : void { var xReLeft:Vector.<Number> = new Vector.<Number>(N); var xImLeft:Vector.<Number> = new Vector.<Number>(N); var leftA:Vector.<Number> = new Vector.<Number>(N); // getting sample range leftA = this.channels.left.slice(step * N, step * (N) + (N)); if (leftA.length < N) { stepper.removeEventListener(TimerEvent.TIMER, getFreq100ms); return; } else if (leftA.length == 0) { stepper.removeEventListener(TimerEvent.TIMER, getFreq100ms); return; } var i:int; // hann window function init m_win = new Vector.<Number>(N); for ( var i:int = 0; i < N; i++ ) m_win[i] = (4.0 / N) * 0.5 * (1 - Math.cos(2 * Math.PI * i / N)); // applying hann window function for ( i = 0; i < N; i++ ) { xReLeft[i] = m_win[i]*leftA[i]; //xReRight[i] = m_win[i]*rightA[i]; } // nullify the imaginary part for ( i = 0; i < N; i++ ) { xImLeft[i] = 0.0; //xImRight[i] = 0.0; } var magnitutel:Vector.<Number> = new Vector.<Number>(N); fftl.run( xReLeft, xImLeft ); current = xReLeft; currf = xImLeft; for ( i = 0; i < N / 2; i++ ) { var re:Number = xReLeft[i]; var im:Number = xImLeft[i]; magnitutel[i] = Math.sqrt(re * re + im * im); } const SCALE:Number = 20 / Math.LN10; var l:uint = this.total.length; for ( i = 0; i < N / 2; i++ ) { magnitutel[i] = SCALE * Math.log( magnitutel[i] + Number.MIN_VALUE ); } var bufferl:Vector.<Number> = new Vector.<Number>(); for (i = 0; i < N / 2 ; i++) { bufferl[i] = magnitutel[i]; } var complete:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>(); complete[0] = bufferl; this.total[step] = complete; this.step++; } This function is executed in the event dispatched by the timer (stepper). Obviously I do something wrong, as I said I have only negative values and further more values range between 1 and 7000 (at least). I want to thank you in advance for any help. With respect, Paul
Negative dB values are OK. Just add a constant (representing your volume control) until the number of points you want to color become positive. The remaining values that stay negative are usually just displayed or colored as black in a spectrogram. No matter how negative (as they might just be the FFT's numerical noise, which can be a huge negative dB number or even NaN or -Inf for log(0)).
ideal lowpass filter with fftw
again I am still trying to get my lowpass filter running, but I am at a point where I do not know why this is still not running. I oriented my code according to FFT Filters and my previous question FFT Question in order to apply an ideal low pass filter to the image. The code below just makes the image darker and places some white pixels in the resulting image. // forward fft the result is in freqBuffer fftw_execute(forward); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { uint gid = y * w + x; // shifting coordinates normalized to [-0.5 ... 0.5] double xN = (x - (w / 2)) / (double)w; double yN = (y - (h / 2)) / (double)h; // max radius double maxR = sqrt(0.5f * 0.5f + 0.5f * 0.5f); // current radius normalized to [0 .. 1] double r = sqrt(xN * xN + yN * yN) / maxR ; // filter response double filter = r > 0.7f ? 0.0f : 1.0f; // applying filter response freqBuffer[gid][0] *= filter; freqBuffer[gid][1] *= filter; } } // normlization (see fftw scaling) for (uint i = 0; i < size; i++) { freqBuffer[i][0] /= (float)size; freqBuffer[i][1] /= (float)size; } // backward fft fftw_execute(backward); Some help would be appreciated. Wolf
If you have a filter with a step response in the frequency domain then you will see significant sin(x)/x ringing in the spatial domain. This is known as the Gibbs Phenomenon. You need to apply a window function to the desired frequency response to mitigate this.
Debugging a crashing Flash application
What is the best way to debug a CRASHING flash app ? (no exception, my application just crash) I am actualy facing a big problem: my app (full-flash website) was working fine with the flashplayer 9 but crash with the flashplayer 10... Here is the BAD method who crash my app with FP10. After removing the call to this method everything was working properly with FP10. public static function drawWedgeCrown(g : Graphics,a : Number,r : Number,r2 : Number, n : Number, c : Number, t : Number) : void { var x : Number ; var y : Number; g.beginFill(c, t); g.moveTo(r, 0); g.lineTo(r, 0); var teta : Number = 0; var dteta : Number = 2 * Math.PI / n; while(teta < a) { x = r * Math.cos(teta); y = -r * Math.sin(teta); g.lineTo(x, y); teta += dteta; } x = r * Math.cos(a); y = -r * Math.sin(a); g.lineTo(x, y); x = r2 * Math.cos(a); y = -r2 * Math.sin(a); g.lineTo(x, y); teta = a; dteta = 2 * Math.PI / n; var cpt : int = 0; while(teta > 0) { cpt++; x = r2 * Math.cos(teta); y = -r2 * Math.sin(teta); g.lineTo(x, y); teta -= dteta; } x = r2 * Math.cos(0); y = -r2 * Math.sin(0); g.lineTo(x, y); g.lineTo(r, 0); g.endFill(); } OK, i finaly found the real PROBLEM... it was not the method in it self. I was passing NaN for the "A" argument causing an infinite loop...
Have you tried running it with the debugger? Set a breakpoint at the entry of your app and then step through it until it crashes. This way you can see which line of code is responsible and the state of the variables. Of course the actual problem might be something that happens prior but at least you have narrowed down your search and can trace backwards. Also another way is to put some trace() statements in your code and see if the section ever gets hit. Then you can tell if its happening before or after and repeat until you find the problem area.