Monday, September 2, 2013

Detect touch and draw rect on bitmap

In last example "Detect touch and free draw on Bitmap", the points are drawn on the canvas (also the bitmap) directly when user touch.

Detect touch and draw rect on bitmap


In case of drawing square, the user touch on the screen to mark the start position, and move, and release on the end position. We have to keep displaying the updated square. But if we draw the rect on the canvasMaster, it will full of rect when user touch and move.

In this example, we create two overlay ImageViews, have same dimension. Also additional bitmap and canvas for the extra ImagewView. When ACTION_DOWN detected, we mark the starting position. When ACTION_MOVE, we draw rect on the extra canvasDrawingPane, not the canvasMaster. Such that we can clear and re-draw the canvas everytime. And finally when ACTION_UP, simple draw the extra bitmap on canvasMaster.



package com.example.androiddrawbitmap;

import java.io.FileNotFoundException;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends Activity {
 
 Button btnLoadImage;
 TextView textSource;
 ImageView imageResult, imageDrawingPane;
 
 final int RQS_IMAGE1 = 1;

 Uri source;
 Bitmap bitmapMaster;
 Canvas canvasMaster;
 Bitmap bitmapDrawingPane;
 Canvas canvasDrawingPane;
 projectPt startPt;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  btnLoadImage = (Button)findViewById(R.id.loadimage);
  textSource = (TextView)findViewById(R.id.sourceuri);
  imageResult = (ImageView)findViewById(R.id.result);
  imageDrawingPane = (ImageView)findViewById(R.id.drawingpane);
  
  btnLoadImage.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    Intent intent = new Intent(Intent.ACTION_PICK,
      android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(intent, RQS_IMAGE1);
   }});
  
  imageResult.setOnTouchListener(new OnTouchListener(){

   @Override
   public boolean onTouch(View v, MotionEvent event) {
    
    int action = event.getAction();
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch(action){
    case MotionEvent.ACTION_DOWN:
     textSource.setText("ACTION_DOWN- " + x + " : " + y);
     startPt = projectXY((ImageView)v, bitmapMaster, x, y);
     break;
    case MotionEvent.ACTION_MOVE:
     textSource.setText("ACTION_MOVE- " + x + " : " + y);
     drawOnRectProjectedBitMap((ImageView)v, bitmapMaster, x, y);
     break;
    case MotionEvent.ACTION_UP:
     textSource.setText("ACTION_UP- " + x + " : " + y);
     drawOnRectProjectedBitMap((ImageView)v, bitmapMaster, x, y);
     finalizeDrawing();
     break;
    }
    /*
     * Return 'true' to indicate that the event have been consumed.
     * If auto-generated 'false', your code can detect ACTION_DOWN only,
     * cannot detect ACTION_MOVE and ACTION_UP.
     */
    return true;
   }});
  
 }
 
 class projectPt{
  int x;
  int y;
  
  projectPt(int tx, int ty){
   x = tx;
   y = ty;
  }
 }
 
 private projectPt projectXY(ImageView iv, Bitmap bm, int x, int y){
  if(x<0 || y<0 || x > iv.getWidth() || y > iv.getHeight()){
   //outside ImageView
   return null;
  }else{
   int projectedX = (int)((double)x * ((double)bm.getWidth()/(double)iv.getWidth()));
   int projectedY = (int)((double)y * ((double)bm.getHeight()/(double)iv.getHeight()));

   return new projectPt(projectedX, projectedY);
  }
 }
 
 private void drawOnRectProjectedBitMap(ImageView iv, Bitmap bm, int x, int y){
  if(x<0 || y<0 || x > iv.getWidth() || y > iv.getHeight()){
   //outside ImageView
   return;
  }else{
   int projectedX = (int)((double)x * ((double)bm.getWidth()/(double)iv.getWidth()));
   int projectedY = (int)((double)y * ((double)bm.getHeight()/(double)iv.getHeight()));

   //clear canvasDrawingPane
   canvasDrawingPane.drawColor(Color.TRANSPARENT, Mode.CLEAR);
   
   Paint paint = new Paint();
            paint.setStyle(Paint.Style.STROKE);
            paint.setColor(Color.WHITE);
            paint.setStrokeWidth(3);
   canvasDrawingPane.drawRect(startPt.x, startPt.y, projectedX, projectedY, paint);
   imageDrawingPane.invalidate();
   
   
   textSource.setText(x + ":" + y + "/" + iv.getWidth() + " : " + iv.getHeight() + "\n" +
     projectedX + " : " + projectedY + "/" + bm.getWidth() + " : " + bm.getHeight()
     );
  }
 }
 
 private void finalizeDrawing(){
  canvasMaster.drawBitmap(bitmapDrawingPane, 0, 0, null);
 }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  
  Bitmap tempBitmap;
  
  if(resultCode == RESULT_OK){
   switch (requestCode){
   case RQS_IMAGE1:
    source = data.getData();
    textSource.setText(source.toString());
    
    try {
     //tempBitmap is Immutable bitmap,
     //cannot be passed to Canvas constructor
     tempBitmap = BitmapFactory.decodeStream(
       getContentResolver().openInputStream(source));
     
     Config config;
     if(tempBitmap.getConfig() != null){
      config = tempBitmap.getConfig();
     }else{
      config = Config.ARGB_8888;
     }
     
     //bitmapMaster is Mutable bitmap
     bitmapMaster = Bitmap.createBitmap(
       tempBitmap.getWidth(),
       tempBitmap.getHeight(),
       config);
     
     canvasMaster = new Canvas(bitmapMaster);
     canvasMaster.drawBitmap(tempBitmap, 0, 0, null);
     
     imageResult.setImageBitmap(bitmapMaster);
     
     //Create bitmap of same size for drawing
     bitmapDrawingPane = Bitmap.createBitmap(
       tempBitmap.getWidth(),
       tempBitmap.getHeight(),
       config);
     canvasDrawingPane = new Canvas(bitmapDrawingPane);
     imageDrawingPane.setImageBitmap(bitmapDrawingPane);
     
     
    } catch (FileNotFoundException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
    
    break;
   }
  }
 }

}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Button
        android:id="@+id/loadimage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load Image 1" />

    <TextView
        android:id="@+id/sourceuri"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <ImageView
            android:id="@+id/result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:background="@android:color/background_dark"
            android:scaleType="centerInside" />
        <ImageView 
            android:id="@+id/drawingpane"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:scaleType="centerInside"
            android:layout_alignLeft="@id/result"
            android:layout_alignTop="@id/result"
            android:layout_alignRight="@id/result"
            android:layout_alignBottom="@id/result"/>
    </RelativeLayout>

</LinearLayout>


download filesDownload the files.

Correction: To make sure bitmapDrawingPane have alpha channel, create it with Config.ARGB_8888.

     //Create bitmap of same size for drawing
     bitmapDrawingPane = Bitmap.createBitmap(
       tempBitmap.getWidth(),
       tempBitmap.getHeight(),
       Config.ARGB_8888);



more: Something about processing images in Android

2 comments:

Unknown said...

this only works for some images and it crashes for some images

Matheus said...

Please I need help, you can zoom and pan to the two ImagemViews the same time? Thank you