האמת היא שאת המאמר הזה אני כותב בצל עסקת הענק של 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 היא קריטית וכבר הסברתי למה, חייבים לשחרר את המצלמה
public void surfaceDestroyed(SurfaceHolder holder) {
if (camera!=null){
camera.stopPreview();
//free the resources
camera.release();
}
}
כאשר משתנה ה SurfaceView מתבצעת הפונקציה של SurfaceChanged , לדוגמה כשהמסך מסתובב.
//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 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.
העבודה מתבצעת עם 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");
}
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();
}
}
};
<activity
android:label="@string/app_name"
android:name=".CameraTestActivity"
android:screenOrientation="portrait">
חשוב מאוד! לבקש הרשאות למצלמה ב Mainfest ממולץ לנטרל את ה Rotation של התוכנית על מנת שה SurfaceView לא ישתנה.
<uses-permission android:name="android.permission.CAMERA" />
android:label="@string/app_name"
android:name=".CameraTestActivity"
android:screenOrientation="portrait">
הורדת הפרויקט:
סיכום:
ראינו דרך מהירה ופשוטה לעבודה עם המצלמה ב Android ניתן לממש עוד מגוון רחב של פעולות על המצלמה כמו זיהוי פנים, הוספת גרפיקה על המצלמה , עבודה עם וידאו וכו' (למידע מקיף של Google).
תנו חיוך, מצלמים...
אין תגובות:
הוסף רשומת תגובה