За что особенно люблю Andriod - так это за обилие аппаратных "плюшек". Тут тебе и микрофон/динамик и gps и wifi, тут и фото/видео камера и так далее... В результате способов применений у этого маленького девайса, а значит простора для программиста, который под него пишет - очень много.
Я уже писал о работе с микрофоном в Android-приложениях, сегодня удостоим внимания камеру. Примеров того, как реализовать простое приложения для фотографирования достаточно много, поэтому в этой статье я покажу кроме всего прочего, как работать с полученной фотографией, в частности как её обрезать. Наше простое приложение может не только сфотографировать вашего знакомого, но и выделить его лицо из кадра, например для сохранения в списке контактов.
Готовим камеру и управляем предварительным просмотром
Для работы с камерой в Android нам понадобится создать объект класса Camera. Одновременно с устройством может работать только одно приложение, поэтому камеру нужно захватить как можно позже (в методе onResume нашего Activity) и отдать как можно раньше (в onPause). Инициализация камеры выполняется методом camera = Camera.open(), освобождение - методом camera.release().
Чтобы навести камеру на "цель" нам потребуется включить предпросмотр методом camera.startPreview(), а чтобы было где его показывать нам потребуется SurfaceView. Чтобы направить результат предварительного просмотра камеры в наш SurfaceView, мы получаем из него SurfaceHolder и "отдаём" его камере: camera.setPreviewDisplay(holder). Делать это лучше в callback-методе surfaceCreated, который буде вызван, когда наш SurfaceView будет готов принимать картинку. Чтобы иметь возможность "поймать" событие surfaceCreated, реализуем в нашем Activity интерфейс SurfaceHolder.Callback. В этом же методе подгоняем размеры изображения с камеры под размеры нашего экрана и включаем предварительный просмотр методом camera.startPreview(). Останавливаем просмотр перед освобождением камеры. Также просмотр нужно повторно включить после фотографирования.
Получаем снимок
Чтобы получить фото с камеры самый простой способ - вызвать метод camera.takePicture(null, null, null, null). Последний параметр можно сделать не null, а передать туда реализацию интерфейса Camera.PictureCallback, и в методе onPictureTaken обработать сохранение полученной фотографии.
Это можно сделать по нажатию кнопки, которую мы тоже должны разместить на экране. Но можно поступить и хитрее. Большинство Android-устройств имеют сейчас камеру, которая может выполнять автофокусировку. Поэтому мы по нажатию кнопки выполняем camera.autoFocus, передавая в этот метод inner-класс, реализующий Camera.AutoFocusCallback. А в методе onAutoFocus этого класса, когда автофокус выполнился, уже, собственно, фотографируем.
Выбираем область для обрезки
Теперь о интересном: из вот этих исходников я вытащил (и немного подправил) реализацию выбора области для обрезки фотографии. Принцип тут следующий: используя FrameLayout, размещаем над нашим SurfaceView объект класса ViewfinderView, наследующий View. В этом классе мы переопредляем метод onDraw, чтобы нарисовать полупрозрачный фон и прозрачный прямоугольник по заданным координатам углов. Вот код этого класса:
import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Point; import android.graphics.Rect; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; public final class ViewfinderView extends View implements View.OnTouchListener { private final Paint paint; private final int maskColor; private final int frameColor; private final int cornerColor; private Rect frame; private Point screenResolution; private int lastX, lastY; private static final int MIN_FRAME_WIDTH = 50; // originally 240 private static final int MIN_FRAME_HEIGHT = 20; // originally 240 private static final int MAX_FRAME_WIDTH = 800; // originally 480 private static final int MAX_FRAME_HEIGHT = 600; // originally 360 public ViewfinderView(Context context) { super(context); paint = new Paint(Paint.ANTI_ALIAS_FLAG); maskColor = Color.argb(200, 100, 100, 100); frameColor = Color.LTGRAY; cornerColor = Color.WHITE; WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = manager.getDefaultDisplay(); int width = display.getWidth(); int height = display.getHeight(); screenResolution = new Point(width, height); calcFramingRect(); } public Rect getFramingRect() { return frame; } private void adjustFramingRect(int deltaWidth, int deltaHeight, Point screenResolution) { // Set maximum and minimum sizes if ((frame.width() + deltaWidth > screenResolution.x - 4) || (frame.width() + deltaWidth < 50)) { deltaWidth = 0; } if ((frame.height() + deltaHeight > screenResolution.y - 4) || (frame.height() + deltaHeight < 50)) { deltaHeight = 0; } int newWidth = frame.width() + deltaWidth; int newHeight = frame.height() + deltaHeight; int leftOffset = (screenResolution.x - newWidth) / 2; int topOffset = (screenResolution.y - newHeight) / 2; frame = new Rect(leftOffset, topOffset, leftOffset + newWidth, topOffset + newHeight); } @Override public void onDraw(Canvas canvas) { if (frame == null) { return; } int width = canvas.getWidth(); int height = canvas.getHeight(); // Draw the exterior (i.e. outside the framing rect) darkened paint.setColor(maskColor); canvas.drawRect(0, 0, width, frame.top, paint); canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint); canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint); canvas.drawRect(0, frame.bottom + 1, width, height, paint); // Draw a two pixel solid border inside the framing rect paint.setAlpha(0); paint.setStyle(Style.FILL); paint.setColor(frameColor); canvas.drawRect(frame.left, frame.top, frame.right + 1, frame.top + 2, paint); canvas.drawRect(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1, paint); canvas.drawRect(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1, paint); canvas.drawRect(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1, paint); // Draw the framing rect corner UI elements paint.setColor(cornerColor); canvas.drawRect(frame.left - 15, frame.top - 15, frame.left + 15, frame.top, paint); canvas.drawRect(frame.left - 15, frame.top, frame.left, frame.top + 15, paint); canvas.drawRect(frame.right - 15, frame.top - 15, frame.right + 15, frame.top, paint); canvas.drawRect(frame.right, frame.top - 15, frame.right + 15, frame.top + 15, paint); canvas.drawRect(frame.left - 15, frame.bottom, frame.left + 15, frame.bottom + 15, paint); canvas.drawRect(frame.left - 15, frame.bottom - 15, frame.left, frame.bottom, paint); canvas.drawRect(frame.right - 15, frame.bottom, frame.right + 15, frame.bottom + 15, paint); canvas.drawRect(frame.right, frame.bottom - 15, frame.right + 15, frame.bottom + 15, paint); } private void calcFramingRect() { if (frame == null) { int width = screenResolution.x * 3 / 5; if (width < MIN_FRAME_WIDTH) { width = MIN_FRAME_WIDTH; } else if (width > MAX_FRAME_WIDTH) { width = MAX_FRAME_WIDTH; } int height = screenResolution.y * 1 / 5; if (height < MIN_FRAME_HEIGHT) { height = MIN_FRAME_HEIGHT; } else if (height > MAX_FRAME_HEIGHT) { height = MAX_FRAME_HEIGHT; } int leftOffset = (screenResolution.x - width) / 2; int topOffset = (screenResolution.y - height) / 2; frame = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); } } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = -1; lastY = -1; return true; case MotionEvent.ACTION_MOVE: int currentX = (int) event.getX(); int currentY = (int) event.getY(); try { Rect rect = getFramingRect(); final int BUFFER = 50; final int BIG_BUFFER = 60; if (lastX >= 0) { if (((currentX >= rect.left - BIG_BUFFER && currentX <= rect.left + BIG_BUFFER) || (lastX >= rect.left - BIG_BUFFER && lastX <= rect.left + BIG_BUFFER)) && ((currentY <= rect.top + BIG_BUFFER && currentY >= rect.top - BIG_BUFFER) || (lastY <= rect.top + BIG_BUFFER && lastY >= rect.top - BIG_BUFFER))) { adjustFramingRect(2 * (lastX - currentX), 2 * (lastY - currentY), screenResolution); } else if (((currentX >= rect.right - BIG_BUFFER && currentX <= rect.right + BIG_BUFFER) || (lastX >= rect.right - BIG_BUFFER && lastX <= rect.right + BIG_BUFFER)) && ((currentY <= rect.top + BIG_BUFFER && currentY >= rect.top - BIG_BUFFER) || (lastY <= rect.top + BIG_BUFFER && lastY >= rect.top - BIG_BUFFER))) { adjustFramingRect(2 * (currentX - lastX), 2 * (lastY - currentY), screenResolution); } else if (((currentX >= rect.left - BIG_BUFFER && currentX <= rect.left + BIG_BUFFER) || (lastX >= rect.left - BIG_BUFFER && lastX <= rect.left + BIG_BUFFER)) && ((currentY <= rect.bottom + BIG_BUFFER && currentY >= rect.bottom - BIG_BUFFER) || (lastY <= rect.bottom + BIG_BUFFER && lastY >= rect.bottom - BIG_BUFFER))) { adjustFramingRect(2 * (lastX - currentX), 2 * (currentY - lastY), screenResolution); } else if (((currentX >= rect.right - BIG_BUFFER && currentX <= rect.right + BIG_BUFFER) || (lastX >= rect.right - BIG_BUFFER && lastX <= rect.right + BIG_BUFFER)) && ((currentY <= rect.bottom + BIG_BUFFER && currentY >= rect.bottom - BIG_BUFFER) || (lastY <= rect.bottom + BIG_BUFFER && lastY >= rect.bottom - BIG_BUFFER))) { adjustFramingRect(2 * (currentX - lastX), 2 * (currentY - lastY), screenResolution); } else if (((currentX >= rect.left - BUFFER && currentX <= rect.left + BUFFER) || (lastX >= rect.left - BUFFER && lastX <= rect.left + BUFFER)) && ((currentY <= rect.bottom && currentY >= rect.top) || (lastY <= rect.bottom && lastY >= rect.top))) { adjustFramingRect(2 * (lastX - currentX), 0, screenResolution); } else if (((currentX >= rect.right - BUFFER && currentX <= rect.right + BUFFER) || (lastX >= rect.right - BUFFER && lastX <= rect.right + BUFFER)) && ((currentY <= rect.bottom && currentY >= rect.top) || (lastY <= rect.bottom && lastY >= rect.top))) { adjustFramingRect(2 * (currentX - lastX), 0, screenResolution); } else if (((currentY <= rect.top + BUFFER && currentY >= rect.top - BUFFER) || (lastY <= rect.top + BUFFER && lastY >= rect.top - BUFFER)) && ((currentX <= rect.right && currentX >= rect.left) || (lastX <= rect.right && lastX >= rect.left))) { adjustFramingRect(0, 2 * (lastY - currentY), screenResolution); } else if (((currentY <= rect.bottom + BUFFER && currentY >= rect.bottom - BUFFER) || (lastY <= rect.bottom + BUFFER && lastY >= rect.bottom - BUFFER)) && ((currentX <= rect.right && currentX >= rect.left) || (lastX <= rect.right && lastX >= rect.left))) { adjustFramingRect(0, 2 * (currentY - lastY), screenResolution); } } } catch (NullPointerException e) { e.printStackTrace(); } v.invalidate(); lastX = currentX; lastY = currentY; return true; case MotionEvent.ACTION_UP: lastX = -1; lastY = -1; return true; } return false; } }
Как видим, метод adjustFramingRect устанавливает границы прозрачной области. Мы должны вызвать этот метод при "перетаскивании" этих границ пальцем, т.е. ViewfinderView должен реализовать ещё и интерфейс View.OnTouchListener. Получаем границы для обрезки методом getFramingRect.
Обрезаем снимок
В оригинальном исходнике обрезалось изображение, которое даёт предварительный просмотр. Мы тоже могли бы использовать его, если бы реализовали интерфейс Camera.PreviewCallback и передали его камере методом camera.setPreviewCallback. В этом случае в методе onPreviewFrame можно манипулировать с изображением препросмотра (само собой, отдавая его в отдельный поток, чтобы не подвесить этот самый предпросмотр). Нам, в принципе, это не нужно, поэтому обрезать будем уже полученный снимок. Делаем это в методе onPictureTaken, как я уже указывал ранее. Принцип тут очень прост: сохраняем изображение во временный файл, потом читаем его в Bitmap, указывая размеры исходя из размеров выбранной области, а затем сохраняем полученный Bitmap окончательно. Это всё реализовано в нашем методе cropFile.
Итого, получаем код главного Activity, котоый вместе с вышеприведённым кодом и составляет весь наш небольшой проект:
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import android.app.Activity; import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Rect; import android.hardware.Camera; import android.hardware.Camera.Size; import android.os.Bundle; import android.os.Environment; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageButton; public class MainActivity extends Activity implements SurfaceHolder.Callback, Camera.PictureCallback { private Camera camera; private SurfaceView preview; private ViewfinderView vfw; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); FrameLayout fl = new FrameLayout(this); preview = new SurfaceView(this); SurfaceHolder surfaceHolder = preview.getHolder(); surfaceHolder.addCallback(this); surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); fl.addView(preview, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); vfw = new ViewfinderView(this); vfw.setBackgroundColor(Color.TRANSPARENT); vfw.setOnTouchListener(vfw); fl.addView(vfw, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); ImageButton shotBtn = new ImageButton(this); shotBtn.setImageResource(android.R.drawable.ic_menu_camera); shotBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { camera.autoFocus(new Camera.AutoFocusCallback() { @Override public void onAutoFocus(boolean success, Camera camera) { camera.takePicture(null, null, null, MainActivity.this); } }); } }); fl.addView(shotBtn, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); setContentView(fl); } @Override protected void onResume() { super.onResume(); camera = Camera.open(); } @Override protected void onPause() { super.onPause(); if (camera != null) { camera.setPreviewCallback(null); camera.stopPreview(); camera.release(); camera = null; } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { try { camera.setPreviewDisplay(holder); } catch (IOException e) { e.printStackTrace(); } Size previewSize = camera.getParameters().getPreviewSize(); float aspect = (float) previewSize.width / previewSize.height; int previewSurfaceWidth = preview.getWidth(); LayoutParams lp = preview.getLayoutParams(); // здесь корректируем размер отображаемого preview, чтобы не было // искажений camera.setDisplayOrientation(0); lp.width = previewSurfaceWidth; lp.height = (int) (previewSurfaceWidth / aspect); preview.setLayoutParams(lp); camera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } @Override public void onPictureTaken(byte[] paramArrayOfByte, Camera camera) { try { String base = Environment.getExternalStorageDirectory()+"/img/"; File saveDir = new File(base); if (!saveDir.exists()) { saveDir.mkdirs(); } String tmpFile = base+"tmp.jpg"; FileOutputStream tos = new FileOutputStream(tmpFile); tos.write(paramArrayOfByte); tos.flush(); tos.close(); cropFile(new File(tmpFile), new File(base+String.valueOf(System.currentTimeMillis())+".jpg"), vfw.getFramingRect()); } catch (Exception e) { e.printStackTrace(); } camera.startPreview(); } private void cropFile(File in, File out, Rect rect){ try { BitmapFactory.Options o = new BitmapFactory.Options(); Bitmap inb = BitmapFactory.decodeStream(new FileInputStream(in),null,o); Bitmap outb = Bitmap.createBitmap(inb,rect.bottom, rect.top, rect.width(), rect.height()); FileOutputStream os = new FileOutputStream(out); outb.compress(Bitmap.CompressFormat.JPEG, 85, os); os.flush(); os.close(); in.delete(); } catch (Exception e) { e.printStackTrace(); } } }
Вот, собственно и всё. Не забудьте добавить в AndroidManifest.xml запрос разрешений android.permission.CAMERA и android.permission.WRITE_EXTERNAL_STORAGE
Здравствуйте, у Вас есть рецепт, как по нажатию кнопки сделать фотографию и сохранить ее в галерею бес вызова предварительного просмотра? Иначе говоря забрать изображения из матрице камеры в фоновом режиме. Очень заинтересовал это вопрос но негде не могу найти как ... Неделю назад начал изучать Android не судите строго.
ОтветитьУдалитьу вас ошибка в конце: не rect.bottom, а rect.left
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьЭтот комментарий был удален автором.
УдалитьНе пойму.
ОтветитьУдалитьRendering Problems Custom view ViewfinderView is not using the 2- or 3-argument View constructors; XML attributes will not work
Какие аргументы нужно добавить?