Sunday, November 21, 2010

Drawing with Canvas in Android, Saving your drawings

Continuing with our Drawing in Canvas series, let us see how to save your drawings
Update:
May 5, 2011 - If saving the image is not that fine, this could come in handy http://stackoverflow.com/questions/5243547/decode-byte-array-to-bitmap-that-has-been-compressed-in-java

Introduction
We draw and saw our drawings are good, now lets save them

Notes
• The files are uploaded in http://goo.gl/ecHpE
• The project was build in IntelliJ and it should be easy to import to Eclipse


What Do I Need

DrawingSurface
public class DrawingSurface extends SurfaceView implements SurfaceHolder.Callback {
  private Bitmap mBitmap;
  private ...
  class DrawThread extends  Thread{
    ...
    @Override
    public void run() {
      Canvas canvas = null;
      while (_run){
        try{
          canvas = mSurfaceHolder.lockCanvas(null);
          if(mBitmap == null){
            mBitmap =  Bitmap.createBitmap (1, 1, Bitmap.Config.ARGB_8888);;
          }
          final Canvas c = new Canvas (mBitmap);
          c.drawColor(0, PorterDuff.Mode.CLEAR);
          commandManager.executeAll(c);
          canvas.drawBitmap (mBitmap, 0,  0,null);
        } finally {
          mSurfaceHolder.unlockCanvasAndPost(canvas);
        }
      }
    }
  }

  public Bitmap getBitmap(){
    return mBitmap;
  }

  public void surfaceChanged(SurfaceHolder holder, int format, int width,  int height) {
    // TODO Auto-generated method stub
    mBitmap =  Bitmap.createBitmap (width, height, Bitmap.Config.ARGB_8888);;
  }
}

DrawingActivity
public class DrawingActivity extends Activity implements View.OnTouchListener{
  ...
  private static File APP_FILE_PATH = new File("/sdcard/TutorialForAndroidDrawings");

  public void onClick(View view){
    switch (view.getId()){
      ...
      case R.id.saveBtn:
        final Activity currentActivity  = this;
        Handler saveHandler = new Handler(){
          @Override
          public void handleMessage(Message msg) {
            final AlertDialog alertDialog = new AlertDialog.Builder(currentActivity).create();
            alertDialog.setTitle("Saved 1");
            alertDialog.setMessage("Your drawing had been saved :)");
            alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
              public void onClick(DialogInterface dialog, int which) {
                return;
              }
            });
            alertDialog.show();
          }
        } ;
         new ExportBitmapToFile(this,saveHandler, drawingSurface.getBitmap()).execute();
      break;
    }
  }


  private class ExportBitmapToFile extends AsyncTask {
    private Context mContext;
    private Handler mHandler;
    private Bitmap nBitmap;

    public ExportBitmapToFile(Context context,Handler handler,Bitmap bitmap) {
      mContext = context;
      nBitmap = bitmap;
      mHandler = handler;
    }

    @Override
    protected Boolean doInBackground(Intent... arg0) {
      try {
        if (!APP_FILE_PATH.exists()) {
          APP_FILE_PATH.mkdirs();
        }

        final FileOutputStream out = new FileOutputStream(new File(APP_FILE_PATH + "/myAwesomeDrawing.png"));
        nBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
        out.flush();
        out.close();
        return true;
      }catch (Exception e) {
        e.printStackTrace();
      }
      //mHandler.post(completeRunnable);
      return false;
    }

    @Override
    protected void onPostExecute(Boolean bool) {
      super.onPostExecute(bool);
      if ( bool ){
        mHandler.sendEmptyMessage(1);
      }
    }
  }
}


Quick Explanation
Ads from Amazon:
Explanation
if(mBitmap == null){
  mBitmap = Bitmap.createBitmap (1, 1, Bitmap.Config.ARGB_8888);;
}
final Canvas c = new Canvas (mBitmap);
c.drawColor(0, PorterDuff.Mode.CLEAR);
commandManager.executeAll(c);

This is the core of the saving part, in order for our drawing to be exported we have to create a canvas with bitmap to draw upon to, See Canvas Class for more info.


canvas.drawBitmap (mBitmap, 0, 0,null);
We put our bitmap to our original canvas (the surfaceView's canvas) so that we can see our drawings


public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  mBitmap = Bitmap.createBitmap (width, height, Bitmap.Config.ARGB_8888);;
}

We would have to recreate our bitmap when we change the orientation (The drawings will currently be removed)


final Activity currentActivity = this;
Handler saveHandler = new Handler(){ ... }

We cant call the UI tread from another thread so we pass a handler to our AsyncTask, see Using Handler in Android for more information on Handlers

private class ExportBitmapToFile extends AsyncTask {
  private Context mContext;
  private Handler mHandler;
  private Bitmap nBitmap;

  public ExportBitmapToFile(Context context,Handler handler,Bitmap bitmap) {
    mContext = context;
    nBitmap = bitmap;
    mHandler = handler;
  }

  @Override
  protected Boolean doInBackground(Intent... arg0) {
    try {
      if (!APP_FILE_PATH.exists()) {
        APP_FILE_PATH.mkdirs();
      }

      final FileOutputStream out = new FileOutputStream(new File(APP_FILE_PATH + "/myAwesomeDrawing.png"));
      nBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
      out.flush();
      out.close();
      return true;
    }catch (Exception e) {
      e.printStackTrace();
    }
    //mHandler.post(completeRunnable);
    return false;
  }


  @Override
  protected void onPostExecute(Boolean bool) {
    super.onPostExecute(bool);
    if ( bool ){
      mHandler.sendEmptyMessage(1);
    }
  }
}

In order for us to export the bitmap and not hard the UX of our app, we need to create a background thread for it, for more information on AsyncTask see the AsyncTask Class and Painless Threading

final FileOutputStream out = new FileOutputStream(new File(APP_FILE_PATH + "/myAwesomeDrawing.png"));
nBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
out.flush();
out.close();

This is default Java feature, if your now from Java then this just mean that you create a buffer where when we use that in mBitmap.compress the compressed bitmap would be buffered into that FileOutputStream (i hope i was clear in this. haha)


Reference
Most of the stuff here were back in 2009, most of them are in Google Groups.
Post a Comment