blob: 37a6808a469fb49930ad8650908261716ac1c692 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.videoeditor;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.widget.FrameLayout;
import android.widget.RadioGroup;
import android.widget.Toast;
import com.android.videoeditor.widgets.ImageViewTouchBase;
/**
* Activity for setting the begin and end Ken Burns viewing rectangles
*/
public class KenBurnsActivity extends Activity {
// Logging
private static final String TAG = "KenBurnsActivity";
// State keys
private static final String STATE_WHICH_RECTANGLE_ID = "which";
private static final String STATE_START_RECTANGLE = "start";
private static final String STATE_END_RECTANGLE = "end";
// Intent extras
public static final String PARAM_WIDTH = "width";
public static final String PARAM_HEIGHT = "height";
public static final String PARAM_FILENAME = "filename";
public static final String PARAM_MEDIA_ITEM_ID = "media_item_id";
public static final String PARAM_START_RECT = "start_rect";
public static final String PARAM_END_RECT = "end_rect";
private static final int MAX_HW_BITMAP_WIDTH = 2048;
private static final int MAX_HW_BITMAP_HEIGHT = 2048;
private static final int MAX_WIDTH = 1296;
private static final int MAX_HEIGHT = 720;
private static final int MAX_PAN = 3;
// Instance variables
private final Rect mStartRect = new Rect(0, 0, 0, 0);
private final Rect mEndRect = new Rect(0, 0, 0, 0);
private final RectF mMatrixRect = new RectF(0, 0, 0, 0);
private RadioGroup mRadioGroup;
private ImageViewTouchBase mImageView;
private View mDoneButton;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private boolean mPaused = true;
private int mMediaItemWidth, mMediaItemHeight;
private float mImageViewScale;
private int mImageSubsample;
private Bitmap mBitmap;
/**
* The simple gestures listener
*/
private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (mImageView.getScale() > 1F) {
mImageView.postTranslateCenter(-distanceX, -distanceY);
saveBitmapRectangle();
}
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
// Switch between the original scale and 3x scale.
if (mImageView.getScale() > 2F) {
mImageView.zoomTo(1F);
} else {
mImageView.zoomTo(3F, e.getX(), e.getY());
}
saveBitmapRectangle();
return true;
}
}
/**
* Scale gesture listener
*/
private class MyScaleGestureListener implements OnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
final float relativeScaleFactor = detector.getScaleFactor();
final float newAbsoluteScale = relativeScaleFactor * mImageView.getScale();
if (newAbsoluteScale < 1.0F) {
return false;
}
mImageView.zoomTo(newAbsoluteScale, detector.getFocusX(), detector.getFocusY());
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
saveBitmapRectangle();
}
}
/**
* Image loader class
*/
private class ImageLoaderAsyncTask extends AsyncTask<Void, Void, Bitmap> {
// Instance variables
private final String mFilename;
/**
* Constructor
*
* @param filename The filename
*/
public ImageLoaderAsyncTask(String filename) {
mFilename = filename;
showProgress(true);
}
@Override
protected Bitmap doInBackground(Void... zzz) {
if (mPaused) {
return null;
}
// Wait for the layout to complete
while (mImageView.getWidth() <= 0) {
try {
Thread.sleep(30);
} catch (InterruptedException ex) {
}
}
if (mBitmap != null) {
return mBitmap;
} else {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = mImageSubsample;
return BitmapFactory.decodeFile(mFilename, options);
}
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap == null) {
if (!mPaused) {
finish();
}
return;
}
if (!mPaused) {
showProgress(false);
mRadioGroup.setEnabled(true);
mImageView.setImageBitmapResetBase(bitmap, true);
mBitmap = bitmap;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Bitmap size: " + bitmap.getWidth() + "x" + bitmap.getHeight()
+ ", bytes: " + (bitmap.getRowBytes() * bitmap.getHeight()));
}
showBitmapRectangle();
}
}
}
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.ken_burns_layout);
setFinishOnTouchOutside(true);
mMediaItemWidth = getIntent().getIntExtra(PARAM_WIDTH, 0);
mMediaItemHeight = getIntent().getIntExtra(PARAM_HEIGHT, 0);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Media item size: " + mMediaItemWidth + "x" + mMediaItemHeight);
}
// Setup the image view
mImageView = (ImageViewTouchBase)findViewById(R.id.ken_burns_image);
// Set the width and height of the image view
final FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams)mImageView.getLayoutParams();
if (mMediaItemWidth >= mMediaItemHeight) {
lp.width = Math.min(mMediaItemWidth, MAX_WIDTH) / MAX_PAN;
// Compute the height by preserving the aspect ratio
lp.height = (lp.width * mMediaItemHeight) / mMediaItemWidth;
mImageSubsample = mMediaItemWidth / (lp.width * MAX_PAN);
} else {
lp.height = Math.min(mMediaItemHeight, MAX_HEIGHT) / MAX_PAN;
// Compute the width by preserving the aspect ratio
lp.width = (lp.height * mMediaItemWidth) / mMediaItemHeight;
mImageSubsample = mMediaItemHeight / (lp.height * MAX_PAN);
}
// Ensure that the size of the bitmap will not exceed the size supported
// by HW vendors
while ((mMediaItemWidth / mImageSubsample > MAX_HW_BITMAP_WIDTH) ||
(mMediaItemHeight / mImageSubsample > MAX_HW_BITMAP_HEIGHT)) {
mImageSubsample++;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "View size: " + lp.width + "x" + lp.height
+ ", subsample: " + mImageSubsample);
}
// If the image is too small the image view may be too small to pinch
if (lp.width < 120 || lp.height < 120) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Image is too small: " + lp.width + "x" + lp.height);
}
Toast.makeText(this, getString(R.string.pan_zoom_small_image_error),
Toast.LENGTH_LONG).show();
finish();
return;
}
mImageView.setLayoutParams(lp);
mImageViewScale = ((float)lp.width) / ((float)mMediaItemWidth);
mGestureDetector = new GestureDetector(this, new MyGestureListener());
mScaleGestureDetector = new ScaleGestureDetector(this, new MyScaleGestureListener());
mRadioGroup = (RadioGroup)findViewById(R.id.which_rectangle);
if (state != null) {
mRadioGroup.check(state.getInt(STATE_WHICH_RECTANGLE_ID));
mStartRect.set((Rect)state.getParcelable(STATE_START_RECTANGLE));
mEndRect.set((Rect)state.getParcelable(STATE_END_RECTANGLE));
} else {
mRadioGroup.check(R.id.start_rectangle);
final Rect startRect = (Rect)getIntent().getParcelableExtra(PARAM_START_RECT);
if (startRect != null) {
mStartRect.set(startRect);
} else {
mStartRect.set(0, 0, mMediaItemWidth, mMediaItemHeight);
}
final Rect endRect = (Rect)getIntent().getParcelableExtra(PARAM_END_RECT);
if (endRect != null) {
mEndRect.set(endRect);
} else {
mEndRect.set(0, 0, mMediaItemWidth, mMediaItemHeight);
}
}
mDoneButton = findViewById(R.id.done);
enableDoneButton();
// Disable the ratio buttons until we load the image
mRadioGroup.setEnabled(false);
mRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch (checkedId) {
case R.id.start_rectangle: {
showBitmapRectangle();
break;
}
case R.id.end_rectangle: {
showBitmapRectangle();
break;
}
case R.id.done: {
final Intent extra = new Intent();
extra.putExtra(PARAM_MEDIA_ITEM_ID,
getIntent().getStringExtra(PARAM_MEDIA_ITEM_ID));
extra.putExtra(PARAM_START_RECT, mStartRect);
extra.putExtra(PARAM_END_RECT, mEndRect);
setResult(RESULT_OK, extra);
finish();
break;
}
default: {
break;
}
}
}
});
mBitmap = (Bitmap) getLastNonConfigurationInstance();
mImageView.setEventListener(new ImageViewTouchBase.ImageTouchEventListener() {
@Override
public boolean onImageTouchEvent(MotionEvent ev) {
mScaleGestureDetector.onTouchEvent(ev);
mGestureDetector.onTouchEvent(ev);
return true;
}
});
}
@Override
protected void onResume() {
super.onResume();
mPaused = false;
// Load the image
new ImageLoaderAsyncTask(getIntent().getStringExtra(PARAM_FILENAME)).execute();
}
@Override
protected void onPause() {
super.onPause();
mPaused = true;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (!isChangingConfigurations()) {
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
System.gc();
}
}
@Override
public Object onRetainNonConfigurationInstance() {
return mBitmap;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
final RadioGroup radioGroup = (RadioGroup)findViewById(R.id.which_rectangle);
outState.putInt(STATE_WHICH_RECTANGLE_ID, radioGroup.getCheckedRadioButtonId());
outState.putParcelable(STATE_START_RECTANGLE, mStartRect);
outState.putParcelable(STATE_END_RECTANGLE, mEndRect);
}
public void onClickHandler(View target) {
switch (target.getId()) {
case R.id.done: {
final Intent extra = new Intent();
extra.putExtra(PARAM_MEDIA_ITEM_ID,
getIntent().getStringExtra(PARAM_MEDIA_ITEM_ID));
extra.putExtra(PARAM_START_RECT, mStartRect);
extra.putExtra(PARAM_END_RECT, mEndRect);
setResult(RESULT_OK, extra);
finish();
break;
}
default: {
break;
}
}
}
/**
* Show/hide the progress bar
*
* @param show true to show the progress
*/
private void showProgress(boolean show) {
if (show) {
findViewById(R.id.image_loading).setVisibility(View.VISIBLE);
} else {
findViewById(R.id.image_loading).setVisibility(View.GONE);
}
}
/**
* Enable the "Done" button if both rectangles are set
*/
private void enableDoneButton() {
mDoneButton.setEnabled(!mStartRect.isEmpty() && !mEndRect.isEmpty());
}
/**
* Show the bitmap rectangle
*/
private void showBitmapRectangle() {
final int checkedRect = mRadioGroup.getCheckedRadioButtonId();
switch (checkedRect) {
case R.id.start_rectangle: {
if (!mStartRect.isEmpty()) {
mImageView.reset();
final float scale = ((float)mMediaItemWidth)
/ ((float)(mStartRect.right - mStartRect.left));
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "showBitmapRectangle START: " + scale + " "
+ mStartRect.left + ", " + mStartRect.top + ", "
+ mStartRect.right + ", " + mStartRect.bottom);
}
if (scale > 1F) {
mImageView.zoomToOffset(scale, mStartRect.left * scale * mImageViewScale,
mStartRect.top * scale * mImageViewScale);
}
}
break;
}
case R.id.end_rectangle: {
if (!mEndRect.isEmpty()) {
mImageView.reset();
final float scale = ((float)mMediaItemWidth)
/ ((float)(mEndRect.right - mEndRect.left));
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "showBitmapRectangle END: " + scale + " "
+ mEndRect.left + ", " + mEndRect.top + ", "
+ mEndRect.right + ", " + mEndRect.bottom);
}
if (scale > 1F) {
mImageView.zoomToOffset(scale, mEndRect.left * scale * mImageViewScale,
mEndRect.top * scale * mImageViewScale);
}
}
break;
}
default: {
break;
}
}
}
/**
* Show the bitmap rectangle
*/
private void saveBitmapRectangle() {
final int checkedRect = mRadioGroup.getCheckedRadioButtonId();
final FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams)mImageView.getLayoutParams();
switch (checkedRect) {
case R.id.start_rectangle: {
mMatrixRect.set(0, 0, lp.width, lp.height);
mImageView.mapRect(mMatrixRect);
final float scale = mImageView.getScale();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "START RAW: " + scale + ", rect: " + mMatrixRect.left
+ ", " + mMatrixRect.top + ", " + mMatrixRect.right
+ ", " + mMatrixRect.bottom);
}
final int left = (int)((-mMatrixRect.left/scale) / mImageViewScale);
final int top = (int)((-mMatrixRect.top/scale) / mImageViewScale);
final int right = (int)(((-mMatrixRect.left + lp.width)/scale) / mImageViewScale);
final int bottom = (int)(((-mMatrixRect.top + lp.height)/scale) / mImageViewScale);
mStartRect.set(left, top, right, bottom);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "START: " + mStartRect.left + ", " + mStartRect.top + ", "
+ mStartRect.right + ", " + mStartRect.bottom);
}
enableDoneButton();
break;
}
case R.id.end_rectangle: {
mMatrixRect.set(0, 0, lp.width, lp.height);
mImageView.mapRect(mMatrixRect);
final float scale = mImageView.getScale();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "END RAW: " + scale + ", rect: " + mMatrixRect.left
+ ", " + mMatrixRect.top + ", " + mMatrixRect.right
+ ", " + mMatrixRect.bottom);
}
final int left = (int)((-mMatrixRect.left/scale) / mImageViewScale);
final int top = (int)((-mMatrixRect.top/scale) / mImageViewScale);
final int right = (int)(((-mMatrixRect.left + lp.width)/scale) / mImageViewScale);
final int bottom = (int)(((-mMatrixRect.top + lp.height)/scale) / mImageViewScale);
mEndRect.set(left, top, right, bottom);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "END: " + mEndRect.left + ", " + mEndRect.top + ", "
+ mEndRect.right + ", " + mEndRect.bottom);
}
enableDoneButton();
break;
}
default: {
break;
}
}
}
}