0

My approach is the following:

I have three TextViews (nested in a LinearLayout) and I want them to have a skewing effect, so that it looks like they are at a wall (look at the image below). Since the user can change the background (different wall), I am using the DrawingCache from the LinearLayout, skew it, and finally render it in a transparent image which is placed above the background image..

enter image description here

To ensure that the text is at the right position at every device, I came up with the solution to have a background image (720x1022) and render the text to a transparent image and place it at the right coordinates in a graphic with the same size (720x1022). In the app, pictures are scaled and placed the same way. Maybe it's not the best idea, because sometimes (let's say 1 time out of 10) I am running into an OutOfMemory Error.

java.lang.OutOfMemoryError
at android.graphics.Bitmap.nativeCreate(Native Method)
at android.graphics.Bitmap.createBitmap(Bitmap.java:689)
at android.graphics.Bitmap.createBitmap(Bitmap.java:666)
at android.graphics.Bitmap.createBitmap(Bitmap.java:633)
//or

java.lang.OutOfMemoryError
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:502)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:355)
at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:785)
at android.content.res.Resources.loadDrawable(Resources.java:1965)
at android.content.res.TypedArray.getDrawable(TypedArray.java:601)

and the heap is growing suddenly.

  Grow heap (frag case) to 73.520MB for 11773456-byte allocation

It doesn't crash every time (let's say 1 out of 10), and not at the same position. Here's my implementation:

First, we get the user input from the TextViews which are nested in a LinearLayout and scale it to a specific height and width (#2) to perform the skewing matrix (#3).

//#1 get Text from TextViews
View test = findViewById(R.id.signFakeContainer);
test.setDrawingCacheEnabled(true);
Bitmap mOfferText = test.getDrawingCache();

//#2 scale Text to specific width & height to perform skew effect
Bitmap mScaledBitmap = Bitmap.createScaledBitmap(mOfferText, 560, 720, false);

int mTextViewWidth = mScaledBitmap.getWidth();
int mTextViewHeight = mScaledBitmap.getHeight();

//#3 create skewing matrix
Matrix mMatrix2 = new Matrix();
float[] mStartSrc = new float[] { 0, 0, mTextViewWidth, 0, mTextViewWidth, mTextViewHeight, 0, mTextViewHeight };
float[] mDestSrc = new float[] { 0, 4f, 520f, 0f, 552f, 704f, 22f, 720f };
mMatrix2.setPolyToPoly(mStartSrc, 0, mDestSrc, 0, 4);

//#4 perform skewing
Bitmap mStrechtedTextView = Bitmap.createBitmap(mScaledBitmap, 0, 0, mTextViewWidth, mTextViewHeight, mMatrix2, false);

//#5 place 560x720 image in 720x1022 image at specific coordinates, to ensure its right position 
Bitmap bmp = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.transparent_dummy), 720, 1022, false);
Canvas comboImage = new Canvas(bmp);
comboImage.drawBitmap(mStrechtedTextView, 98, 110, null);

//#6 set rendered textview to image
ImageView mOfferImageView = (ImageView) findViewById(R.id.choose_sign_offer_image);
mOfferImageView.setImageBitmap(bmp);

I found this really good article: Strange out of memory issue while loading an image to a Bitmap object but still it doesn't solve my issue. I guess I still have too many createBitmaps. Maybe there's a better way to do this, so I'm happy for every suggestion!

Community
  • 1
  • 1
longi
  • 11,104
  • 10
  • 55
  • 89

2 Answers2

1

Our final solution for this case, was the use of an AsyncTask for rendering the Image. We pass a reference to the LinearLayout which included the TextViews (this is what we are rendering), a reference for the ImageView where we insert the rendered picture and a reference to ProgressBar which is hided after the Image is rendered

public class RenderBitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
  private final WeakReference<ImageView> imageViewReference;
  private final LinearLayout signViewReference;
  private final ProgressBar progressBar;

  public RenderBitmapWorkerTask(ImageView imageView, LinearLayout signView, ProgressBar mProgressBar) {
      // Use a WeakReference to ensure the ImageView can be garbage collected
      imageViewReference = new WeakReference<ImageView>(imageView);
      signViewReference = signView;
      progressBar = mProgressBar;
  }

  // Decode image in background.
  @Override
  protected Bitmap doInBackground(Integer... params) {
    signViewReference.setDrawingCacheEnabled(true);

    Bitmap mOfferText = signViewReference.getDrawingCache();

    int mTextViewWidth = 560;
        int mTextViewHeight = 720;

        mOfferText = Bitmap.createScaledBitmap(mOfferText, mTextViewWidth, mTextViewHeight, false);

        Matrix mMatrix2 = new Matrix();
        float[] mStartSrc = new float[] { 0, 0, mTextViewWidth, 0, mTextViewWidth, mTextViewHeight, 0, mTextViewHeight };
        float[] mDestSrc = new float[] { 0, 4f, 520f, 0f, 552f, 704f, 22f, 720f };
        mMatrix2.setPolyToPoly(mStartSrc, 0, mDestSrc, 0, 4);
        mOfferText = Bitmap.createBitmap(mOfferText, 0, 0, mTextViewWidth, mTextViewHeight, mMatrix2, false);

      return mOfferText;
  }

  // Once complete, see if ImageView is still around and set bitmap.
  @Override
  protected void onPostExecute(Bitmap bitmap) {
      if (imageViewReference != null && bitmap != null) {
          final ImageView imageView = imageViewReference.get();
          if (imageView != null) {

                progressBar.setVisibility(View.GONE);

              imageView.setImageBitmap(bitmap);
          }
      }
  }
}
longi
  • 11,104
  • 10
  • 55
  • 89
0

I see at least 4 Bitmaps in de code snippet you posted. Make sure to read about proper bitmap recycling at this android developer training link or on this stackoverflow question.

Recycle

When adding recycle() calls make sure not to display a recycled bitmap or you'll get another error. Before assigning new bitmap information perform a recycle (if not null) and onDestroy of the view (or onPause if you're careful enough to recreate the bitmap onResume)

A quick example:

// you see how I moved this up to class scope instead?
private Bitmap mScaledBitmap = null;

void someMethod(){
    // some code here
    // ...
    if(mScaledBitmap != null)
        mScaledBitmap.recycle();
    mScaledBitmap = Bitmap.createScaledBitmap(mOfferText, 560, 720, false);
    // more code here
    // ...
}

public void onDestroy(){
    // always call super
    super.onDestroy();
    // free memory from bitmap here
    if( mScaledBitmap != null ) {
        mScaledBitmap.recycle();
        mScaledBitmap = null;
    }
}

sampleSize

If even with proper recycling this code still gives out of memory exceptions you can try using the sampleSize options. Make sure to use powers of 2 then.

Another quick example for this:

Options opts = new Options();
opts.inSampleSize = 4;
// insert recycle here with proper checks
mScaledBitmap = BitmapFactory.decodeFile(imagePath, opts);
Community
  • 1
  • 1
hcpl
  • 17,382
  • 7
  • 72
  • 73
  • thanks for comment (whoever gave you a downvote?!), it doesn't really fit my needs, since you cannot give the 'Options' to 'Bitmap.createScaledBitmap'. I realized, that the 'createBitmap's are the critical ones, but I don't see any optimizing in the createImage -> scale -> skew -> render ... But maybe the whole idea of how to create that effect is stupid?! – longi Jun 05 '13 at 13:45