Tuesday, October 4, 2011

AIR extensions in Java -- FREBitmapData

The following function takes an FREBitmapData as an argument, creates a new copy and returns it. It isn't very useful, except how to demonstrate passing bitmap data back and forth.

Pay attention to the acquire() and release() calls on FREBitmapData objects. When you have called it on one object, you CANNOT call any methods that are backed by an ActionScript object of ANY object. This includes creating new FREObjects (including FREBitmapData, FREByteArray, and FREArray), getProperty(), setProperty(), or callMethod(). As the words in all caps indicate, I found this rather surprising. You might too. So acquire, do it, then release. If you fail to release, say you hit an exception, then the extension gets into an invalid state. Hence, I release the bitmaps in a finally block. I imagine that this can be inconvenient in more complex functions.



package com.example;



import java.nio.ByteBuffer;
import com.adobe.fre.FREBitmapData;
import com.adobe.fre.FREContext;
import com.adobe.fre.FREFunction;
import com.adobe.fre.FREObject;

import android.os.Debug;
import android.util.Log;

public class InvertBitmapDataFunction implements FREFunction {
    public static final String NAME = "invertBitmapData";
    private String tag;
   
    public FREObject call(FREContext arg0, FREObject[] arg1) {
        Debug.waitForDebugger();

        DataExchangeContext ctx = (DataExchangeContext) arg0;
        tag = ctx.getIdentifier() + "." + NAME;
        Log.i( tag, "Invoked " + NAME );
        FREBitmapData returnValue = null;
        FREBitmapData inputValue = null;
       
        try {
           
            inputValue = (FREBitmapData)arg1[0];
            inputValue.acquire();
                int srcWidth = inputValue.getWidth();
                int srcHeight = inputValue.getHeight();
                boolean srcAlpha = inputValue.hasAlpha();
            inputValue.release();
           
            Byte[] fillColor = {(byte)0xff,(byte)0x00,(byte)0x00,(byte)0xff}; //Range:[-128..127]
            returnValue = FREBitmapData.newBitmapData( srcWidth, srcHeight, srcAlpha, fillColor);
           
            inputValue.acquire();
            returnValue.acquire();
           
            ByteBuffer inPixels = inputValue.getBits();
            ByteBuffer outPixels = returnValue.getBits();
           
            while( inPixels.hasRemaining() )
            {
                outPixels.put( inPixels.get() );
            }       
           
           
        } catch (Exception e) {
            Log.d(tag, "Exception: " + e.toString());
            e.printStackTrace();
        }
        catch (Error e){
            Log.d(tag, "Error: " + e.toString());
            e.printStackTrace();           
        }
        finally{
            try {
               
                inputValue.release();
                returnValue.release();
               
            } catch (Exception e) {
                Log.d(tag, "Exception: " + e.toString());
                e.printStackTrace();
            } catch (Error e) {
                Log.d(tag, "Error: " + e.toString());
                e.printStackTrace();
            }                       
        }
       
        return returnValue;
    }

}

[edit to remove extra newBitmap() call that I had put in to debug fillColor]

16 comments:

  1. Thanks for sharing!

    I am having some problems recreating the bitmap in Java when the bitmapData passed from actionScript contains transparency.
    Pretty sure its got something to do with the bitmap config I am using.

    So in actionScript I am creating some bitmapData:
    var bmd:BitmapData = new BitmapData(200, 200, true, 0xFF0000FF);

    and then on the Java side:
    bm = Bitmap.createBitmap(srcWidth, srcHeight, Config.ARGB_8888);
    bm.copyPixelsFromBuffer( inputValue.getBits() );

    // This results in a red square, instead of the expected blue square

    Not sure what I am doing wrong.

    ReplyDelete
  2. @Brad, 2 things I can think of. One is that your new image isn't the same size, which knocks the component sequence out of whack. The other possiblity is that it is an endian issue.

    ReplyDelete
  3. Cool, thanks for the info. Will let you know if I get it working.

    ReplyDelete
  4. I just tried it and it worked for me. I copied inputValue to an Android Bitmap object and then to my returnValue object.

    I did have a stray createBitmapData() call in my example (from hacking up another test). Hopefully that isn't screwing you up. (I've removed it from the code above.)

    ReplyDelete
  5. That's weird...

    I'm not actually passing anything back to actionScript (I'm not using that returnValue FREBitmapData object at all)

    I'm displaying the bitmap as an Android ImageView:
    bm = Bitmap.createBitmap(200, 200, Config.ARGB_8888);
    bm.copyPixelsFromBuffer( inputValue.getBits() );

    ImageView iv = new ImageView(context.getActivity());
    iv.setImageBitmap(bm);
    context.getActivity().addContentView(iv, new LayoutParams(200, 200));

    ReplyDelete
  6. I see. Yep, I get the same results doing that. It sure looks like the red and the blue components are swapped. ff0000ff gives a red color, but ffff0000 gives blue. ff00ff00 gives green, as it should. I'm investigating why.

    ReplyDelete
  7. I still havent been able to figure out why the bitmapData is inverted. Have you had any luck with fixing the issue?

    ReplyDelete
  8. It looks like either a bug or a mismatch in the internal color format between Android and ActionScript. I've filed the issue, and Adobe is looking into it.

    ReplyDelete
  9. just a tip, but in c# windows forms the color bytes are stored just the opposite way one would expect: bgr and bgra instead of rgb and argb
    When I'v passed raw bytes from outside to build an image I had to swap the channels first.
    So maybe this isn't a bug, just something one should pay attention to.

    ReplyDelete
  10. @samedakira that is what it is looking like. However, both claim to be argb. I'm still trying to clarification.

    ReplyDelete
  11. I'm still stuck on this problem...

    according to this:
    http://help.adobe.com/en_US/air/extensions/WS11d1def534ea1be0-67da1176132a8869db6-7ffe.html
    FREBitmapData uses BGRA

    ReplyDelete
  12. AIR intentionally swizzles the red and blue channels. Before I left Adobe, I wasn't able to get clarification on whether the image format really is supposed to be ARGB on the ActionScript side of the house. It doesn't look like this is going to change any time soon, though. So you will have to swap red and blue when passing raw color data between ActionScript and Android Java code. (This might be fixed in the future.)

    ReplyDelete
    Replies
    1. Thank you so much for this tutorial!
      A lot of the explanations regarding FREBitmapData don't quite explain just how to do what your tutorial has explained.

      I've gotten the tutorial working, but I can't seem to make a basic Bitmap (with the BitmapData transferred *from* AS3) to even appear on Android.
      I've tried using what @brad has suggested - but I'm just a little stuck with regards to which context one is to send the view/canvas? (I'm still very new to pure Android development). Could someone point me in the right direction?

      Delete
    2. OK, nevermind about drawing the Bitmap, I read up on Android development... coupled that with your tutorial and the comments above, et voila. It works :-)
      Thanks again for the great tutorial!

      Delete
  13. This comment has been removed by the author.

    ReplyDelete
  14. I simply convert channels of BitmapData in AS3 side. It works.
    Exchange red for blue by BitmapData.copyChannel.

    ReplyDelete

Note: Only a member of this blog may post a comment.