Implementing PorterDuff modes - how to deal with alpha

669 views Asked by At

I'm in a situation when I need to use bitmaps blending in Android. And make it similar to iOS version of app.

iOS guys used PorterDuff modes, that are currently absent in Android's PorterDuff class out-of-the-box.

Exactly I need soft-light mode, but forget that - I decided to deal with whole idea of some modes lacking for Android.

Here are the links that I've found during research:

All of them include desired formula so that it seems quite easy to just write a method and implement every blending mode that I might need.

However, that's where troubles start.

The formulas in the links above only deal with color component, leaving alpha aside as if it was something for granted. But it's not.

So far I've ended up having this SO question's code. I'm thinking about re-writing overlay_byte(int sc, int dc, int sa, int da) method to implement softlight formula, but can't figure out what to do with sa and da variables.

P. S.: I'm aware that performance of bare java implementation of blending mode will be far from native android's PorterDuff class's, but that can be dealt with later...

ADDED LATER:

Since yesterday I've done some deeper research and found a way to implement soft-light blending mode on java. Here's the source (the same that works inside android when we use out-of-the-box modes like PorterDuff.Mode.LIGHTEN).

I took softlight_byte method from there and all that he uses and translated it to java (did the best I could here). The result works, but gives incorrect image: bottom part seems ok, but top one get's re-burned. Here's my code, re-written to java:

public Bitmap applyBlendMode(Bitmap srcBmp, Bitmap destBmp)
{
    int width = srcBmp.getWidth();
    int height = srcBmp.getHeight();
    int srcPixels[] = new int[width * height];;
    int destPixels[] = new int[width * height];
    int resultPixels[] = new int[width * height];
    int aS = 0, rS = 0, gS = 0, bS = 0;
    int rgbS = 0;
    int aD = 0, rD = 0, gD = 0, bD = 0;
    int rgbD = 0;

    try
    {
        srcBmp.getPixels(srcPixels, 0, width, 0, 0, width, height);
        destBmp.getPixels(destPixels, 0, width, 0, 0, width, height);
        srcBmp.recycle();
        destBmp.recycle();
    }
    catch(IllegalArgumentException e) {
    }
    catch(ArrayIndexOutOfBoundsException e) {
    }

    for(int y = 0; y < height; y++) {
        for(int x = 0; x < width; x++) {
            rgbS = srcPixels[y*width + x];
            aS = (rgbS >> 24) & 0xff;
            rS = (rgbS >> 16) & 0xff;
            gS = (rgbS >>  8) & 0xff;
            bS = (rgbS      ) & 0xff;

            rgbD = destPixels[y*width + x];
            aD = ((rgbD >> 24) & 0xff);
            rD = (rgbD >> 16) & 0xff;
            gD = (rgbD >>  8) & 0xff;
            bD = (rgbD      )  & 0xff;

            rS = softlight_byte(rD, rS, aS, aD);
            gS = softlight_byte(gD, gS, aS, aD);
            bS = softlight_byte(bD, bS, aS, aD);
            aS = aS + aD - Math.round((aS * aD)/255f);

            resultPixels[y*width + x] = ((int)aS << 24) | ((int)rS << 16) | ((int)gS << 8) | (int)bS;
        }
    }

    return Bitmap.createBitmap(resultPixels, width, height, srcBmp.getConfig());
}

int softlight_byte(int sc, int dc, int sa, int da) {

    int m = (da != 0) ? dc * 256 / da : 0;

    int rc;
    if (2 * sc <= sa) {
        rc = dc * (sa + ((2 * sc - sa) * (256 - m) >> 8));
    } else if (4 * dc <= da) {
        int tmp = (4 * m * (4 * m + 256) * (m - 256) >> 16) + 7 * m;
        rc = dc * sa + (da * (2 * sc - sa) * tmp >> 8);
    } else {
        int tmp = sqrtUnitByte(m) - m;
        rc = dc * sa + (da * (2 * sc - sa) * tmp >> 8);
    }
    return clampDiv255Round(rc + sc * (255 - da) + dc * (255 - sa));
}

/** returns 255 * sqrt(n/255) */
private int sqrtUnitByte(int n) {
    return skSqrtBits(n, 15 + 4);
}

/** Return the integer square root of value, with a bias of bitBias */
int skSqrtBits(int value, int bitBias) {

    if (value < 0) { Log.wtf(TAG, "ASSERTION!"); } // bitBias always 15+4, no check required
    int root = 0;
    int remHi = 0;
    int remLo = value;
    do {
        root <<= 1;
        remHi = (remHi<<2) | (remLo>>30);
        remLo <<= 2;
        int testDiv = (root << 1) + 1;
        if (remHi >= testDiv) {
            remHi -= testDiv;
            root++;
        }
    } while (--bitBias >= 0);
    return root;
}

int clampDiv255Round(int prod) {
    if (prod <= 0) {
        return 0;
    } else if (prod >= 255*255) {
        return 255;
    } else {
        return skDiv255Round(prod);
    }
}

/** Just the rounding step in SkDiv255Round: round(value / 255)
 */
private static int skDiv255Round(int prod) {
    prod += 128;
    return (prod + (prod >> 8)) >> 8;
}

Can someone check it for possible mistakes? This is really of big importance to me!

P. S.: the applyBlendMode was taken from SO question I mentioned above, and softlight_byte(rD, rS, aS, aD); realization - rewritten from C++.

0

There are 0 answers