package com.datdo.mobilib.imageinput;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.Button;
import com.datdo.mobilib.util.MblUtils;
import com.datdo.mobilib.widget.MblTouchImageView;
/**
* Activity to take image by camera. Also support cropping.
*/
public class MblTakeImageActivity extends MblDataInputActivity {
private static final String TAG = MblUtils.getTag(MblTakeImageActivity.class);
private static final int REQUEST_CODE = 268;
private static final String EXTRA_INPUT_IMAGE_PATH = "input_image_path";
private static final String EXTRA_CROP_SIZE_WIDTH_IN_PX = "crop_size_width_in_px";
private static final String EXTRA_CROP_SIZE_HEIGHT_IN_PX = "crop_size_height_in_px";
private static final String EXTRA_NATIVE_CAMERA_RETURN_DATA = "return-data";
private MblTouchImageView mPreviewImageView;
private Uri mTakenPhotoUri;
private String mInputImagePath;
private int mCropSizeWidthInPx;
private int mCropSizeHeightInPx;
private View mCropFrame;
private View mCropFrameMid;
private boolean mStartTakePhotoOnResume;
private boolean mIsPortrait;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mbl_photo_preview_layout);
// get data from extra
if (getIntent().getExtras() != null) {
mInputImagePath = getIntent().getExtras().getString(EXTRA_INPUT_IMAGE_PATH);
mCropSizeWidthInPx = getIntent().getExtras().getInt(EXTRA_CROP_SIZE_WIDTH_IN_PX);
mCropSizeHeightInPx = getIntent().getExtras().getInt(EXTRA_CROP_SIZE_HEIGHT_IN_PX);
}
// init UI
mPreviewImageView = (MblTouchImageView) findViewById(R.id.image);
Button leftButton = (Button) findViewById(R.id.left_button);
mCropFrame = findViewById(R.id.crop_frame);
mCropFrameMid = mCropFrame.findViewById(R.id.mid);
if (needCrop()) {
// set sizes for transparent area in middle of crop frame
ViewGroup.LayoutParams lpOfMid = mCropFrameMid.getLayoutParams();
lpOfMid.width = mCropSizeWidthInPx;
lpOfMid.height = mCropSizeHeightInPx;
mCropFrameMid.setLayoutParams(lpOfMid);
// set sizes frame surrounding middle view of crop frame
View frameOfMid = mCropFrame.findViewById(R.id.frame);
ViewGroup.LayoutParams lpOfMidFrame = frameOfMid.getLayoutParams();
lpOfMidFrame.width = mCropSizeWidthInPx + MblUtils.pxFromDp(2);
lpOfMidFrame.height = mCropSizeHeightInPx + MblUtils.pxFromDp(2);
frameOfMid.setLayoutParams(lpOfMidFrame);
}
if (mInputImagePath == null) { // take photo
// left button
leftButton.setText(R.string.mbl_retake_photo);
leftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
takePhoto();
}
});
// show native camera right away
mStartTakePhotoOnResume = true;
} else { // load photo from external storage
// left button
leftButton.setText(R.string.mbl_cancel);
leftButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
cancelInput();
}
});
// show crop frame
mCropFrame.setVisibility(View.VISIBLE);
// load photo from storage
loadPhotoFromExternal(mInputImagePath);
}
// right button
final Button rightButton = (Button) findViewById(R.id.right_button);
rightButton.setText(R.string.mbl_use_photo);
rightButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
rightButton.setEnabled(false);
if (needCrop()) {
cropPhoto();
} else {
usePhoto();
}
}
});
// store orientation
mIsPortrait = MblUtils.isPortraitDisplay();
}
@Override
protected void finishInput(Object... outputData) {
deallocatePreviewImageView();
super.finishInput(outputData);
}
@Override
protected void cancelInput() {
deallocatePreviewImageView();
super.cancelInput();
}
@Override
protected void onResume() {
super.onResume();
if (mStartTakePhotoOnResume) {
takePhoto();
mStartTakePhotoOnResume = false;
}
}
private File getTempFile(String name) {
// get external storage folder to store image
File externalDir = new File(MblImageInput.sFolderToSaveTakenImages);
if (!externalDir.exists()) {
externalDir.mkdirs();
}
// create file to store image
File f = new File(externalDir, name);
if (f.exists()) {
f.delete();
}
try {
f.createNewFile();
} catch (IOException e) {
Log.e(TAG, "Can not create temp file at: " + f.getAbsolutePath(), e);
return null;
}
return f;
}
private void takePhoto() {
deallocatePreviewImageView();
File tempFile = getTempFile(UUID.randomUUID().toString() + ".jpg");
if (tempFile != null) {
mTakenPhotoUri = Uri.fromFile(tempFile);
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, mTakenPhotoUri);
try {
cameraIntent.putExtra(EXTRA_NATIVE_CAMERA_RETURN_DATA, true);
startActivityForResult(cameraIntent, REQUEST_CODE);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Camera is not available", e);
// TODO: show alert
}
} else {
// TODO: show alert
}
}
private void usePhoto() {
if (mTakenPhotoUri != null) {
finishInput(mTakenPhotoUri.getPath());
}
}
private void cropPhoto() {
try {
// get image being manipulated
Bitmap photo = MblUtils.extractBitmap(mPreviewImageView);
if (photo == null || photo.isRecycled()) {
cancelInput();
return;
}
// calculate crop area x,y,w,h
float[] matrixValues = mPreviewImageView.getMatrixValues();
float transX = matrixValues[Matrix.MTRANS_X];
float transY = matrixValues[Matrix.MTRANS_Y];
float scaleX = matrixValues[Matrix.MSCALE_X];
float scaleY = matrixValues[Matrix.MSCALE_Y];
int cropAreaX = Math.round((mCropFrameMid.getLeft() - transX) / scaleX);
int cropAreaY = Math.round((mCropFrameMid.getTop() - transY) / scaleY);
int cropAreaWidth = Math.round(mCropFrameMid.getWidth() / scaleX);
int cropAreaHeight = Math.round(mCropFrameMid.getHeight() / scaleY);
// make crop area be inside photo
cropAreaWidth = Math.min(cropAreaWidth, photo.getWidth());
cropAreaHeight = Math.min(cropAreaHeight, photo.getHeight());
cropAreaX = Math.min(Math.max(0, cropAreaX), photo.getWidth() - cropAreaWidth);
cropAreaY = Math.min(Math.max(0, cropAreaY), photo.getHeight() - cropAreaHeight);
// calculate crop matrix
Matrix matrix = new Matrix();
matrix.postScale(1.0f * mCropSizeWidthInPx / cropAreaWidth, 1.0f * mCropSizeHeightInPx / cropAreaHeight);
// crop bitmap
Bitmap croppedPhoto = Bitmap.createBitmap(photo, cropAreaX, cropAreaY, cropAreaWidth, cropAreaHeight, matrix, true);
// save bitmap to cache folder
String cacheImagePath = MblUtils.getCacheAsbPath(UUID.randomUUID().toString() + ".jpg");
OutputStream os = new FileOutputStream(cacheImagePath);
croppedPhoto.compress(CompressFormat.JPEG, 100, os);
os.flush();
os.close();
finishInput(cacheImagePath);
} catch (Exception e) {
Log.e(TAG, "Can not crop photo", e);
cancelInput();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == REQUEST_CODE) {
resetDefaultMaxAllowedTrasitionBetweenActivity();
if (resultCode == RESULT_OK) {
loadPhotoFromExternal(mTakenPhotoUri.getPath());
if (needCrop()) {
mCropFrame.setVisibility(View.VISIBLE);
}
} else if (resultCode == RESULT_CANCELED) {
// delete temp file
new File(mTakenPhotoUri.getPath()).delete();
// cancel input
cancelInput();
}
}
}
private boolean needCrop() {
return mCropSizeWidthInPx > 0 && mCropSizeHeightInPx > 0;
}
private void loadPhotoFromExternal(final String imagePath) {
if (mPreviewImageView.getWidth() == 0 || mPreviewImageView.getHeight() == 0 || mIsPortrait != MblUtils.isPortraitDisplay()) {
mPreviewImageView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
MblUtils.removeOnGlobalLayoutListener(mPreviewImageView, this);
loadPhotoFromExternal(imagePath);
}
});
return;
}
MblUtils.executeOnAsyncThread(new Runnable() {
@Override
public void run() {
Bitmap temp = null;
try {
temp = MblUtils.loadBitmapMatchSpecifiedSize(-1, -1, imagePath);
} catch (OutOfMemoryError e) {
try {
Log.e(TAG, "Image too big -> scale to match ImageView size", e);
temp = MblUtils.loadBitmapMatchSpecifiedSize(
Math.round(mPreviewImageView.getWidth() * 1.5f),
Math.round(mPreviewImageView.getHeight() * 1.5f),
imagePath);
} catch (OutOfMemoryError e2) {
Log.e(TAG, "Still too big --> cancel", e);
}
}
final Bitmap bm = temp;
if (bm == null) {
cancelInput();
return;
}
MblUtils.executeOnMainThread(new Runnable() {
@Override
public void run() {
// display bitmap
mPreviewImageView.setImageBitmap(bm);
// set min and max for zoom
float minZoom = MblImageInput.sCropMinZoom;
float maxZoom = MblImageInput.sCropMaxZoom;
if (needCrop()) {
float minBmWidth = minZoom * bm.getWidth();
float minBmHeight = minZoom * bm.getHeight();
boolean needJustify = false;
if (minBmWidth < mCropSizeWidthInPx) {
minBmWidth = mCropSizeWidthInPx;
needJustify = true;
}
if (minBmHeight < mCropSizeHeightInPx) {
minBmHeight = mCropSizeHeightInPx;
needJustify = true;
}
if (needJustify) {
float maxPerMin = maxZoom / minZoom;
minZoom = Math.max(
minBmWidth / bm.getWidth(),
minBmHeight / bm.getHeight());
maxZoom = maxPerMin * minZoom;
}
}
if (needCrop()) {
mPreviewImageView.setOptions(
minZoom, maxZoom, -1,
mCropFrame.findViewById(R.id.left).getWidth(),
mCropFrame.findViewById(R.id.top).getHeight(),
mCropFrame.findViewById(R.id.right).getWidth(),
mCropFrame.findViewById(R.id.bottom).getHeight());
} else {
mPreviewImageView.setOptions(
minZoom, maxZoom, -1,
0, 0, 0, 0);
}
}
});
}
});
}
@Override
protected void onDestroy() {
deallocatePreviewImageView();
super.onDestroy();
}
/**
* <pre>
* Start activity to take image by camera.
* Support cropping by passing cropSizeWidthInPx > 0, cropSizeWidthInPx > 0.
* Also support cropping a specific image (via inputImagePath) without capturing image by camera
* </pre>
* @param inputImagePath
* @param cropSizeWidthInPx crop image to specific width (in pixel). Pass -1 if you don't want to crop
* @param cropSizeHeightInPx crop image to specific height (in pixel). Pass -1 if you don't want to crop
* @param callback callback to receive result
*/
public static void start(
String inputImagePath,
int cropSizeWidthInPx,
int cropSizeHeightInPx,
final MblTakeImageCallback callback) {
Intent intent = createIntent(MblTakeImageActivity.class, new CmDataInputActivityCallback() {
@Override
public void onFinish(Object... outputData) {
if (callback != null) {
callback.onFinish((String)outputData[0]);
}
}
@Override
public void onCancel() {
if (callback != null) {
callback.onCancel();
}
}
}, null);
intent.putExtra(EXTRA_INPUT_IMAGE_PATH, inputImagePath);
intent.putExtra(EXTRA_CROP_SIZE_WIDTH_IN_PX, cropSizeWidthInPx);
intent.putExtra(EXTRA_CROP_SIZE_HEIGHT_IN_PX, cropSizeHeightInPx);
MblUtils.getCurrentContext().startActivity(intent);
}
public static interface MblTakeImageCallback {
public void onFinish(String path);
public void onCancel();
}
private void deallocatePreviewImageView() {
MblUtils.executeOnMainThread(new Runnable() {
@Override
public void run() {
MblUtils.recycleImageView(mPreviewImageView);
}
});
}
}