Java tutorial
/* * Copyright (C) 2007 The Android Open Source Project * Copyright (C) 2012,2013,2014,2015 Renard Wellnitz * * 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.renard.ocr.documents.creation.crop; import com.google.common.base.Optional; import com.googlecode.leptonica.android.Box; import com.googlecode.leptonica.android.Clip; import com.googlecode.leptonica.android.Convert; import com.googlecode.leptonica.android.Pix; import com.googlecode.leptonica.android.Projective; import com.googlecode.leptonica.android.Rotate; import com.googlecode.tesseract.android.OCR; import com.renard.ocr.HintDialog; import com.renard.ocr.MonitoredActivity; import com.renard.ocr.R; import com.renard.ocr.documents.viewing.grid.DocumentGridActivity; import com.renard.ocr.util.PreferencesUtils; import com.renard.ocr.util.Util; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.Toast; import android.widget.ViewSwitcher; import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; import de.greenrobot.event.EventBus; /** * The activity can crop specific region of interest from an image. */ public class CropImageActivity extends MonitoredActivity implements BlurWarningDialog.BlurDialogClickListener { public static final int RESULT_NEW_IMAGE = RESULT_FIRST_USER + 1; private static final int HINT_DIALOG_ID = 2; public static final String SCREEN_NAME = "Crop Image"; private final Handler mHandler = new Handler(); private int mRotation = 0; boolean mSaving; private Pix mPix; @Bind(R.id.toolbar) protected Toolbar mToolbar; @Bind(R.id.cropImageView) protected CropImageView mImageView; @Bind(R.id.crop_layout) protected ViewSwitcher mViewSwitcher; @Bind(R.id.item_rotate_left) protected ImageView mRotateLeft; @Bind(R.id.item_rotate_right) protected ImageView mRotateRight; @Bind(R.id.item_save) protected ImageView mSave; private CropHighlightView mCrop; private Optional<CropData> mCropData = Optional.absent(); private Optional<PreparePixForCropTask> mPrepareTask = Optional.absent(); @Override public String getScreenName() { return ""; } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); EventBus.getDefault().register(this); getWindow().setFormat(PixelFormat.RGBA_8888); setContentView(R.layout.activity_cropimage); ButterKnife.bind(this); initToolbar(); setToolbarMessage(R.string.crop_title); initNavigationAsUp(); startCropping(); } private void showCropOnBoarding(final CropData cropData) { PreferencesUtils.setFirstScan(this, false); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.crop_onboarding_title); builder.setMessage(R.string.crop_onboarding_message); builder.setPositiveButton(R.string.got_it, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { handleBlurResult(cropData); } }); builder.show(); } private boolean isFirstStart() { return PreferencesUtils.isFirstScan(this); } @OnClick(R.id.item_rotate_left) public void onRotateLeft() { onRotateClicked(-1); } @OnClick(R.id.item_rotate_right) public void onRotateRight() { onRotateClicked(1); } private void onRotateClicked(int delta) { if (mCropData.isPresent()) { if (delta < 0) { delta = -delta * 3; } mRotation += delta; mRotation = mRotation % 4; mImageView.setImageBitmapResetBase(mCropData.get().getBitmap(), false, mRotation * 90); showDefaultCroppingRectangle(mCropData.get().getBitmap()); } } private void startCropping() { mImageView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Bundle extras = getIntent().getExtras(); final long nativePix = extras.getLong(DocumentGridActivity.EXTRA_NATIVE_PIX); final float margin = getResources().getDimension(R.dimen.crop_margin); final int width = (int) (mViewSwitcher.getWidth() - 2 * margin); final int height = (int) (mViewSwitcher.getHeight() - 2 * margin); mPix = new Pix(nativePix); mRotation = 0; mPrepareTask = Optional.of(new PreparePixForCropTask(mPix, width, height)); mPrepareTask.get().execute(); mImageView.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } private void initNavigationAsUp() { final Drawable upArrow = getResources().getDrawable(R.drawable.ic_arrow_back_white); getSupportActionBar().setHomeAsUpIndicator(upArrow); getSupportActionBar().setDisplayHomeAsUpEnabled(true); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); } @Override protected int getHintDialogId() { return HINT_DIALOG_ID; } @SuppressWarnings("unused") public void onEventMainThread(final CropData cropData) { if (cropData.getBitmap() == null) { //should not happen. Scaling of the original document failed some how. Maybe out of memory? mAnalytics.sendCropError(); Toast.makeText(this, R.string.could_not_load_image, Toast.LENGTH_LONG).show(); onNewImageClicked(); return; } mAnalytics.sendBlurResult(cropData.getBlurriness()); mCropData = Optional.of(cropData); adjustOptionsMenu(); mViewSwitcher.setDisplayedChild(1); mImageView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mImageView.setImageBitmapResetBase(cropData.getBitmap(), true, mRotation * 90); if (isFirstStart()) { showCropOnBoarding(cropData); } else { handleBlurResult(cropData); } mImageView.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } private void handleBlurResult(CropData cropData) { switch (cropData.getBlurriness().getBlurriness()) { case NOT_BLURRED: mAnalytics.sendScreenView(SCREEN_NAME); showDefaultCroppingRectangle(cropData.getBitmap()); break; case MEDIUM_BLUR: case STRONG_BLUR: setTitle(R.string.image_is_blurred); BlurWarningDialog dialog = BlurWarningDialog .newInstance((float) cropData.getBlurriness().getBlurValue()); final FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); fragmentTransaction.add(dialog, BlurWarningDialog.TAG).commitAllowingStateLoss(); break; } } @Override protected Dialog onCreateDialog(int id, Bundle args) { switch (id) { case HINT_DIALOG_ID: return HintDialog.createDialog(this, R.string.crop_help_title, "file:///android_res/raw/crop_help.html"); } return super.onCreateDialog(id, args); } private void adjustOptionsMenu() { if (mCropData.isPresent()) { mRotateLeft.setVisibility(View.VISIBLE); mRotateRight.setVisibility(View.VISIBLE); mSave.setVisibility(View.VISIBLE); } else { mRotateLeft.setVisibility(View.GONE); mRotateRight.setVisibility(View.GONE); mSave.setVisibility(View.GONE); } } @OnClick(R.id.item_save) void onSaveClicked() { if (!mCropData.isPresent() || mSaving || (mCrop == null)) { return; } mSaving = true; Util.startBackgroundJob(this, null, getText(R.string.cropping_image).toString(), new Runnable() { public void run() { try { float scale = 1f / mCropData.get().getScaleResult().getScaleFactor(); Matrix scaleMatrix = new Matrix(); scaleMatrix.setScale(scale, scale); final float[] trapezoid = mCrop.getTrapezoid(); final RectF perspectiveCorrectedBoundingRect = new RectF( mCrop.getPerspectiveCorrectedBoundingRect()); scaleMatrix.mapRect(perspectiveCorrectedBoundingRect); Box bb = new Box((int) perspectiveCorrectedBoundingRect.left, (int) perspectiveCorrectedBoundingRect.top, (int) perspectiveCorrectedBoundingRect.width(), (int) perspectiveCorrectedBoundingRect.height()); Pix pix8 = Convert.convertTo8(mPix); mPix.recycle(); Pix croppedPix = Clip.clipRectangle2(pix8, bb); if (croppedPix == null) { throw new IllegalStateException(); } pix8.recycle(); scaleMatrix.postTranslate(-bb.getX(), -bb.getY()); scaleMatrix.mapPoints(trapezoid); final float[] dest = new float[] { 0, 0, bb.getWidth(), 0, bb.getWidth(), bb.getHeight(), 0, bb.getHeight() }; Pix bilinear = Projective.projectiveTransform(croppedPix, dest, trapezoid); if (bilinear == null) { bilinear = croppedPix; } else { croppedPix.recycle(); } if (mRotation != 0 && mRotation != 4) { Pix rotatedPix = Rotate.rotateOrth(bilinear, mRotation); bilinear.recycle(); bilinear = rotatedPix; } if (bilinear == null) { throw new IllegalStateException(); } Intent result = new Intent(); OCR.savePixToCacheDir(CropImageActivity.this, bilinear.copy()); result.putExtra(DocumentGridActivity.EXTRA_NATIVE_PIX, bilinear.getNativePix()); setResult(RESULT_OK, result); } catch (IllegalStateException e) { setResult(RESULT_CANCELED); } finally { finish(); } } }, mHandler); } @Override public void onBackPressed() { super.onBackPressed(); setResult(RESULT_CANCELED); mPix.recycle(); } @Override protected void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); unbindDrawables(findViewById(android.R.id.content)); mImageView.clear(); if (mPrepareTask.isPresent()) { mPrepareTask.get().cancel(true); mPrepareTask = Optional.absent(); } if (mCropData.isPresent()) { mCropData.get().recylce(); mCropData = Optional.absent(); } } private void unbindDrawables(View view) { if (view.getBackground() != null) { view.getBackground().setCallback(null); } if (view instanceof ViewGroup && !(view instanceof AdapterView)) { for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { unbindDrawables(((ViewGroup) view).getChildAt(i)); } ((ViewGroup) view).removeAllViews(); } } private void zoomToBlurredRegion(CropData data) { float width = data.getBlurriness().getPixBlur().getWidth(); float height = data.getBlurriness().getPixBlur().getHeight(); float widthScale = width / data.getBitmap().getWidth(); float heightScale = height / data.getBitmap().getHeight(); final Point c = data.getBlurriness().getMostBlurredRegion().getCenter(); c.set((int) (c.x / widthScale), (int) (c.y / heightScale)); float[] pts = { c.x, c.y }; mImageView.getImageMatrix().mapPoints(pts); /* int w = (Math.min(mBitmap.getWidth(), mBitmap.getHeight())) / 25; Rect focusArea = new Rect((int) (Math.max(c.x-w,0)*widthScale), (int) (Math.max(c.y-w,0)*heightScale), (int) (Math.min(c.x+w,mBitmap.getWidth())*widthScale), (int) (Math.min(c.y+w,mBitmap.getHeight())*heightScale)); //final int progressColor = getResources().getColor(R.color.progress_color); //final int edgeWidth = getResources().getDimensionPixelSize(R.dimen.crop_edge_width); Clip.clipRectangle2(); BlurHighLightView hv = new BlurHighLightView(focusArea,progressColor,edgeWidth, mImageView.getImageMatrix()); mImageView.add(hv); */ mImageView.setMaxZoom(3); mImageView.zoomTo(3, pts[0], pts[1], 2000); } private void showDefaultCroppingRectangle(Bitmap bitmap) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); Rect imageRect = new Rect(0, 0, width, height); // make the default size about 4/5 of the width or height int cropWidth = Math.min(width, height) * 4 / 5; int x = (width - cropWidth) / 2; int y = (height - cropWidth) / 2; RectF cropRect = new RectF(x, y, x + cropWidth, y + cropWidth); CropHighlightView hv = new CropHighlightView(mImageView, imageRect, cropRect); mImageView.resetMaxZoom(); mImageView.add(hv); mCrop = hv; mCrop.setFocus(true); mImageView.invalidate(); } @Override public void onContinueClicked() { if (mCropData.isPresent()) { mAnalytics.sendScreenView(SCREEN_NAME); showDefaultCroppingRectangle(mCropData.get().getBitmap()); setToolbarMessage(R.string.crop_title); mImageView.zoomTo(1, 500); } } @Override public void onNewImageClicked() { setResult(RESULT_NEW_IMAGE); mPix.recycle(); finish(); } }