יום שבת, 12 במאי 2012

Android Camera Guide



האמת היא שאת המאמר הזה אני כותב בצל עסקת הענק של Instagram שנמכרה ל Facebook בסכום דמיוני של מיליארד דולר וחשבתי לעצמי נושא הצילום נהפך לכלי רציני בפלאפונים במיוחד בעידן ה SmartPhone שהכל נהפך לאפשרי, אם חשבתם שפלאפון הוא רק לדיבורים אז אתם חיים בסרט ואני לא יוסיף יותר, בסדרת המאמרים הקרובה נתעסק עם החומרה של הפלאפונים שלנו אז בראשית נתחיל עם המצלמה כי היא בהחלט נהפכה למשהו מאוד איכותי בשנים האחרונות ומאפשרת לנו להפיק תמונות באיכות גבוהה.

קצת תאוריה:

ב Framework של Android נוכל למצוא מחלקות מגוונות עבור רכיבי החומרה, לשימוש במצלמה ב Android יש להשתמש במחלקות המוגדרות כ Multimedia, דרכן תתבצע התקשורת עם החומרה.
שימו לב! המצלמה יכולה להיות משוייכת לתוכנית אחת בלבד לכן חשוב לשחרר את משאבי המערכת של המצלמה על מנת שתוכניות אחרות יוכלו להשתמש בה.

SurfaceView

להצגת הפלט של המצלמה יש לעבוד עם מחלקת Surface שזה בעצם סוג של Buffer ,  הבעיה שלא משנה איפה נמקם אותו הוא יעלם על ידי החלון של התוכנית שלנו כי הוא מוגדר ב Z index הנמוך ביותר בההרכיה של ה Gui  ב Android לכן יש להשתמש ב SurfaceView שמכיל בתוכו Surface והיתרון הגדול שהוא בעצם עושה "חור" בחלון התוכנית ומאפשר לנו לראות את הפלט המצלמה.

יש לממש מספר פונקציות של ה SurfaceView:

//creating the surface, and connecting the camera display buffer
public void surfaceCreated(SurfaceHolder holder) {}

//destroy the surface in this point you must to release all camera resources
public void surfaceDestroyed(SurfaceHolder holder) {}

//occur when the surface size change 
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {}


Camera

המצלמה היא אובייקט סטטי ולא ניתן לייצר ממנו Instance , תחילה ננסה לבקש את המצלמה ממערכת ההפעלה וננסה לחבר אותה ל Surface כאשר הפונקציה SurfaceCreated מתממשת ב SurfaceView.

public void surfaceCreated(SurfaceHolder holder) {

               try {

                //set the camera to open in this point our application
//taking control of the camera and blocking others applications
  camera = Camera.open();
//set the camera display to the holder
camera.setPreviewDisplay(holder);
                        //start preview the camera output
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}


מימוש SurfaceDestroyed היא קריטית וכבר הסברתי למה, חייבים לשחרר את המצלמה

//happen when we destroyed the surface
public void surfaceDestroyed(SurfaceHolder holder) {
if (camera!=null){
              camera.stopPreview();
              //free the resources
              camera.release();
}
}


כאשר משתנה ה SurfaceView מתבצעת הפונקציה של SurfaceChanged , לדוגמה כשהמסך מסתובב.

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
  //when the surface size changed, first happen SurfaceDestroyed
//so we need to reconnect to the camera to the surface
//getting the parameters from the camera and 
//change the width and the height of the camera
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(w, h);
camera.setParameters(parameters);
try {
 camera.setPreviewDisplay(mHolder);
 camera.startPreview();
} catch (IOException e) {
 // TODO Auto-generated catch block
 e.printStackTrace();
}
}

נארוז את הכל ב Class אחד שמוגדר כ Preview  שמציג לנו את המצלמה, בעצם יצרנו שכבה שמשלבת גם את המצלמה וגם את ה Surface.

import java.io.IOException;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

class Preview extends SurfaceView implements SurfaceHolder.Callback {

//interface to the display surface 
SurfaceHolder mHolder;
//static instance for the camera
public Camera camera;


Preview(Context context) {
super(context);

// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
 
//creation the surface 
//and connecting to the camera
public void surfaceCreated(SurfaceHolder holder) {

   //set the camera to open
//in this point our application
//taking control of the camera and 
//blocking others
  camera = Camera.open();

 //set the camera rotation to portrait
   camera.setDisplayOrientation(90);

try {

//set the camera display to the holder
camera.setPreviewDisplay(holder);
camera.startPreview();


} catch (IOException e) {
e.printStackTrace();
}
}

//happen when we destroyed the surface
public void surfaceDestroyed(SurfaceHolder holder) {
 if (camera!=null){
              camera.stopPreview();
              //free the resources
              camera.release();
  }
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
 //when the surface size changed, first happen SurfaceDestroyed
//so we need to reconnect to the camera to the surface
//getting the parameters from the camera and 
//change the width and the height of the camera

Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(w, h);
camera.setParameters(parameters);
try {
camera.setPreviewDisplay(mHolder);
camera.startPreview();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}



Activity

הפרויקט מחולק ל 2 חלקים הראשון הוא בעצם ה Activity שלנו (האפליקציה עצמה) והחלק השני הוא ה Preview שאותו נחבר לאפליקציה, קיים חלק נוסף שמתממש ב Activity והוא שמירת התמונה כקבוץ ע"ג המכשיר שלנו.

העבודה מתבצעת עם 3 פונקציות callback שחוזרות לנו מהמצלמה שנמצאת ב Preview שלנו, הראשונה נקראת shutterCallback שמתבצעת ברגע לקיחת תמונה מהמצלמה, השניה rawCallback כאשר מתמלא Buffer של התמונה, והשלישית jpegCallback כאשר מתמלא Buffer של התמונה לאחר Compress.



 public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
setContentView(R.layout.main);

//create new instance to the surface
preview = new Preview(this);

//set the surface to the xml in the surface
((FrameLayout) findViewById(R.id.preview)).addView(preview);

//set button listener.
buttonClick = (Button) findViewById(R.id.buttonClick);
buttonClick.setOnClickListener(new OnClickListener() {
public void onClick(View v) {

//set the camera inside the surface to
//take a picture, the function have 3
//calls back  to the activity
//* shutterCallback the actual event 
//of the capturing happen
//* RawCallBack where the buffer was fill
//by the camera image
//* jpegCallback happen where compress 
//image created
preview.camera.takePicture(shutterCallback, rawCallback,
jpegCallback);
}
});

Log.d(TAG, "Created");
    }

//happen when picture was taken
ShutterCallback shutterCallback = new ShutterCallback() {
   public void onShutter() {
Log.d(TAG, "Shutter");
   }
};

//happen when the image fill buffer
PictureCallback rawCallback = new PictureCallback() {
    public void onPictureTaken(byte[] data, Camera camera) {
Log.d(TAG, "onPictureTaken - raw");
    }
};



בקיצור הפונקציה שעושה את רוב העבודה היא jpegCallback ששומרת לנו את הקובץ ע"ג המכשיר.

//happen when the jepg buffer fill.
PictureCallback jpegCallback = new PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
  try {
           
                             //crate the metadata of the image
           ContentValues image = new ContentValues();
           
           // add metadata tags:
           image.put(Media.DISPLAY_NAME, "proxytype.blog image");
           image.put(Media.MIME_TYPE, "image/jpg");
           image.put(Media.TITLE, "this is camera test");
           image.put(Media.DESCRIPTION, "enjoy the photo");

           // write the rotation information of the image
           switch (mOrientation) {
               case ORIENTATION_PORTRAIT_NORMAL:
                   image.put(Media.ORIENTATION, 90);
                   break;
               case ORIENTATION_LANDSCAPE_NORMAL:
                   image.put(Media.ORIENTATION, 0);
                   break;
               case ORIENTATION_PORTRAIT_INVERTED:
                   image.put(Media.ORIENTATION, 270);
                   break;
               case ORIENTATION_LANDSCAPE_INVERTED:
                   image.put(Media.ORIENTATION, 180);
                   break;
           }
           
           //set the image location
           Uri uriTarget = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, image);
           
           //set the stream for the image
           OutputStream imageFileOS;
           
           try {
           
           
            imageFileOS = getContentResolver().openOutputStream(uriTarget);
            //write the data buffer of the image
            //to the file
            imageFileOS.write(data);
             
            //close the stream
            imageFileOS.flush();
            imageFileOS.close();
             
            Toast.makeText(CameraTestActivity.this,
                "Image saved: " + uriTarget.toString(),
                Toast.LENGTH_LONG).show();
             
           } catch (Exception e) {
               e.printStackTrace();
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
};

חשוב מאוד! לבקש הרשאות למצלמה ב Mainfest ממולץ לנטרל את ה Rotation של התוכנית על מנת שה SurfaceView לא ישתנה.
<uses-permission android:name="android.permission.CAMERA" />

<activity
            android:label="@string/app_name"
            android:name=".CameraTestActivity"
            android:screenOrientation="portrait">




הורדת הפרויקט:


סיכום:
ראינו דרך מהירה ופשוטה לעבודה עם המצלמה ב Android ניתן לממש עוד מגוון רחב של פעולות על המצלמה כמו זיהוי פנים, הוספת גרפיקה על המצלמה , עבודה עם וידאו וכו' (למידע מקיף של Google).

תנו חיוך, מצלמים...

אין תגובות:

הוסף רשומת תגובה