在Flash编译目标中有效地XOR两个图像

时间:2014-09-04 00:25:31

标签: actionscript-3 flash shader haxe pixel-bender

我需要将两个BitmapData个对象混合在一起。

我正在使用flash.*库和AS3编译目标在Haxe上写作。

我已经调查了HxSL和PixelBender,似乎没有人有一个按位XOR运算符,也没有任何其他可用于创建XOR的按位运算符(但是我错过了一些明显的东西吗?我&#39 ; d接受任何答案,它只使用HxSL或PixelBlender中可用的整数/浮点运算符和函数来进行按位异或。)

我找不到Flash中的预定义过滤器或着色器似乎都无法对两个图像进行异或(但是,我是否遗漏了一些明显的东西?可以使用其他过滤器的组合来完成XOR。)< / p>

我找不到像XOR drawmode那样把东西画到其他东西上(但这并不意味着它不存在!如果它存在的话也会有效!)

目前我能找到的唯一方法是在图像上逐像素化循环,但即使在快速机器上,每个图像也需要几秒钟,而不是过滤器,我用于其他图像处理操作,快了大约一百倍。

是否有任何更快的方法?

2 个答案:

答案 0 :(得分:3)

编辑:

更多地使用它我发现在循环中删除条件和额外的Vector访问速度可以在我的机器上加速大约100ms。

这是之前的XOR循环:

// 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;
    }
}

以下是Vector解决方案的更新XOR循环:

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]);
}

答案:

以下是我在Flash中对两个图像进行异或测试的解决方案。

我发现PixelBender解决方案比直接使用ActionScript要快6到10个

我不知道是不是因为我的算法很慢,或者只是在PixelBender中尝试伪造按位操作的极限。

<强>结果:

  • PixelBender:~6500ms
  • BitmapData.getVector(): ~480-500ms
  • BitmapData.getPixel32():〜1200ms
  • BitmapData.getPixels():〜1200ms

明显的赢家是使用BitmapData.getVector(),然后对两个像素数据流进行异或。


1。 PixelBender解决方案

这是我在PixelBender中实现按位XOR的方式,基于维基百科上给出的公式:http://en.wikipedia.org/wiki/Bitwise_operation#Mathematical_equivalents

以下是最终PBK的要点:https://gist.github.com/Coridyn/67a0ff75afaa0163f673

在我的机器上运行两个3200x1400图像的XOR时,这需要 6500-6700ms

我首先将公式转换为JavaScript以检查它是否正确:

// 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);
}

确认它是正确的:

// Test the manual XOR is correct.
XOR(255, 85);   // 170
XOR(170, 85);   // 255
XOR(170, 170);  // 0

然后我通过使用一系列宏展开循环将JavaScript转换为PixelBender:

// 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

evaluatePixel函数中当前像素的每个通道的XOR:

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解决方案

2。 BitmapData.getVector()

我发现最快的解决方案是从两个图像中提取Vector像素并在ActionScript中执行XOR。

对于相同的两个3200x1400,这需要 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()

您目前使用BitmapData.getPixel32()循环使用BitmapData的方法提供了类似于 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()

我最后的测试是尝试迭代两个ByteArray像素数据(非常类似于上面的Vector解决方案)。此实施还需要 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;
}

答案 1 :(得分:1)

根据您想要实现的目标,有一些可能的选项(例如,每个通道的XOR或者只是非黑色的任何像素?)。

  1. BitmapData.compare() method可以为您提供有关这两个位图的大量信息。在比较之前,您可以BitmapData.threshold()输入数据。

  2. 另一种选择是使用draw methodBlendMode.DIFFERENCE blend mode将两个图片绘制到同一个BitmapData实例中。这将显示两个图像之间的差异(相当于Photoshop中的差异混合模式)。

  3. 如果您需要检查是否有任何像素是非黑色的,那么您可以先尝试运行BitmapData.threshold然后使用差异混合模式绘制结果,如上图所示。

  4. 您是在进行图像处理还是其他类似的像素点击检测?

    首先,我要查看BitmapData并查看可以使用的内容。