| /* |
| * Copyright (C) 2012 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.gallery3d.filtershow.imageshow; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.RectF; |
| import android.graphics.drawable.Drawable; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| |
| import com.android.gallery3d.R; |
| import com.android.gallery3d.filtershow.CropExtras; |
| |
| public class ImageCrop extends ImageGeometry { |
| private static final boolean LOGV = false; |
| |
| // Sides |
| private static final int MOVE_LEFT = 1; |
| private static final int MOVE_TOP = 2; |
| private static final int MOVE_RIGHT = 4; |
| private static final int MOVE_BOTTOM = 8; |
| private static final int MOVE_BLOCK = 16; |
| |
| // Corners |
| private static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT; |
| private static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT; |
| private static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT; |
| private static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT; |
| |
| private static int mMinSideSize = 100; |
| private static int mTouchTolerance = 45; |
| |
| private boolean mFirstDraw = true; |
| private float mAspectWidth = 1; |
| private float mAspectHeight = 1; |
| private boolean mFixAspectRatio = false; |
| |
| private float mLastRot = 0; |
| |
| private BoundedRect mBounded = null; |
| private int movingEdges; |
| private final Drawable cropIndicator; |
| private final int indicatorSize; |
| private final int mBorderColor = Color.argb(128, 255, 255, 255); |
| |
| // Offset between crop center and photo center |
| private float[] mOffset = { |
| 0, 0 |
| }; |
| private CropExtras mCropExtras = null; |
| private boolean mDoingCropIntentAction = false; |
| |
| private static final String LOGTAG = "ImageCrop"; |
| |
| private String mAspect = ""; |
| private int mAspectTextSize = 24; |
| |
| public void setAspectTextSize(int textSize) { |
| mAspectTextSize = textSize; |
| } |
| |
| public void setAspectString(String a) { |
| mAspect = a; |
| } |
| |
| private static final Paint gPaint = new Paint(); |
| |
| public ImageCrop(Context context) { |
| super(context); |
| Resources resources = context.getResources(); |
| cropIndicator = resources.getDrawable(R.drawable.camera_crop); |
| indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); |
| } |
| |
| public ImageCrop(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| Resources resources = context.getResources(); |
| cropIndicator = resources.getDrawable(R.drawable.camera_crop); |
| indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); |
| } |
| |
| @Override |
| public String getName() { |
| return getContext().getString(R.string.crop); |
| } |
| |
| private void swapAspect() { |
| if (mDoingCropIntentAction) { |
| return; |
| } |
| float temp = mAspectWidth; |
| mAspectWidth = mAspectHeight; |
| mAspectHeight = temp; |
| } |
| |
| /** |
| * Set tolerance for crop marker selection (in pixels) |
| */ |
| public static void setTouchTolerance(int tolerance) { |
| mTouchTolerance = tolerance; |
| } |
| |
| /** |
| * Set minimum side length for crop box (in pixels) |
| */ |
| public static void setMinCropSize(int minHeightWidth) { |
| mMinSideSize = minHeightWidth; |
| } |
| |
| public void setExtras(CropExtras e) { |
| mCropExtras = e; |
| } |
| |
| public void setCropActionFlag(boolean f) { |
| mDoingCropIntentAction = f; |
| } |
| |
| public void apply(float w, float h) { |
| mFixAspectRatio = true; |
| mAspectWidth = w; |
| mAspectHeight = h; |
| setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), |
| getLocalStraighten())); |
| if (mVisibilityGained) { |
| cropSetup(); |
| } |
| saveAndSetPreset(); |
| invalidate(); |
| } |
| |
| public void applyOriginal() { |
| mFixAspectRatio = true; |
| RectF photobounds = getLocalPhotoBounds(); |
| float w = photobounds.width(); |
| float h = photobounds.height(); |
| float scale = Math.min(w, h); |
| mAspectWidth = w / scale; |
| mAspectHeight = h / scale; |
| setLocalCropBounds(getUntranslatedStraightenCropBounds(photobounds, |
| getLocalStraighten())); |
| if (mVisibilityGained) { |
| cropSetup(); |
| } |
| saveAndSetPreset(); |
| invalidate(); |
| } |
| |
| public void applyClear() { |
| mFixAspectRatio = false; |
| mAspectWidth = 1; |
| mAspectHeight = 1; |
| setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), |
| getLocalStraighten())); |
| if (mVisibilityGained) { |
| cropSetup(); |
| } |
| saveAndSetPreset(); |
| invalidate(); |
| } |
| |
| public void clear() { |
| if (mCropExtras != null) { |
| int x = mCropExtras.getAspectX(); |
| int y = mCropExtras.getAspectY(); |
| if (mDoingCropIntentAction && x > 0 && y > 0) { |
| apply(x, y); |
| } |
| } else { |
| applyClear(); |
| } |
| } |
| |
| private Matrix getPhotoBoundDisplayedMatrix() { |
| float[] displayCenter = new float[2]; |
| RectF scaledCrop = new RectF(); |
| RectF scaledPhoto = new RectF(); |
| float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); |
| Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop, |
| getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); |
| m.preScale(scale, scale); |
| return m; |
| } |
| |
| private Matrix getCropBoundDisplayedMatrix() { |
| float[] displayCenter = new float[2]; |
| RectF scaledCrop = new RectF(); |
| RectF scaledPhoto = new RectF(); |
| float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); |
| Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop, |
| getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); |
| m1.preScale(scale, scale); |
| return m1; |
| } |
| |
| /** |
| * Takes the rotated corners of a rectangle and returns the angle; sets |
| * unrotated to be the unrotated version of the rectangle. |
| */ |
| private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) { |
| float dy = rotatedRect[1] - rotatedRect[3]; |
| float dx = rotatedRect[0] - rotatedRect[2]; |
| float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI); |
| Matrix m = new Matrix(); |
| m.setRotate(-angle, center[0], center[1]); |
| float[] unrotatedRect = new float[rotatedRect.length]; |
| m.mapPoints(unrotatedRect, rotatedRect); |
| unrotated.set(CropMath.trapToRect(unrotatedRect)); |
| return angle; |
| } |
| |
| /** |
| * Sets cropped bounds; modifies the bounds if it's smaller than the allowed |
| * dimensions. |
| */ |
| public boolean setCropBounds(RectF bounds) { |
| RectF cbounds = new RectF(bounds); |
| Matrix mc = getCropBoundDisplayedMatrix(); |
| Matrix mcInv = new Matrix(); |
| mc.invert(mcInv); |
| mcInv.mapRect(cbounds); |
| // Avoid cropping smaller than minimum |
| float newWidth = cbounds.width(); |
| float newHeight = cbounds.height(); |
| float scale = getTransformState(null, null, null); |
| float minWidthHeight = mMinSideSize / scale; |
| RectF pbounds = getLocalPhotoBounds(); |
| |
| // if photo is smaller than minimum, refuse to set crop bounds |
| if (pbounds.width() < minWidthHeight || pbounds.height() < minWidthHeight) { |
| return false; |
| } |
| |
| // if incoming crop is smaller than minimum, refuse to set crop bounds |
| if (newWidth < minWidthHeight || newHeight < minWidthHeight) { |
| return false; |
| } |
| |
| float newX = bounds.centerX() - (getWidth() / 2f); |
| float newY = bounds.centerY() - (getHeight() / 2f); |
| mOffset[0] = newX; |
| mOffset[1] = newY; |
| |
| setLocalCropBounds(cbounds); |
| invalidate(); |
| return true; |
| } |
| |
| private BoundedRect getBoundedCrop(RectF crop) { |
| RectF photo = getLocalPhotoBounds(); |
| Matrix mp = getPhotoBoundDisplayedMatrix(); |
| float[] photoCorners = CropMath.getCornersFromRect(photo); |
| float[] photoCenter = { |
| photo.centerX(), photo.centerY() |
| }; |
| mp.mapPoints(photoCorners); |
| mp.mapPoints(photoCenter); |
| RectF scaledPhoto = new RectF(); |
| float angle = getUnrotated(photoCorners, photoCenter, scaledPhoto); |
| return new BoundedRect(angle, scaledPhoto, crop); |
| } |
| |
| private void detectMovingEdges(float x, float y) { |
| Matrix m = getCropBoundDisplayedMatrix(); |
| RectF cropped = getLocalCropBounds(); |
| m.mapRect(cropped); |
| mBounded = getBoundedCrop(cropped); |
| movingEdges = 0; |
| |
| float left = Math.abs(x - cropped.left); |
| float right = Math.abs(x - cropped.right); |
| float top = Math.abs(y - cropped.top); |
| float bottom = Math.abs(y - cropped.bottom); |
| |
| // Check left or right. |
| if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) |
| && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) { |
| movingEdges |= MOVE_LEFT; |
| } |
| else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) |
| && ((y - mTouchTolerance) <= cropped.bottom)) { |
| movingEdges |= MOVE_RIGHT; |
| } |
| |
| // Check top or bottom. |
| if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) |
| && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) { |
| movingEdges |= MOVE_TOP; |
| } |
| else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) |
| && ((x - mTouchTolerance) <= cropped.right)) { |
| movingEdges |= MOVE_BOTTOM; |
| } |
| if (movingEdges == 0) { |
| movingEdges = MOVE_BLOCK; |
| } |
| if (mFixAspectRatio && (movingEdges != MOVE_BLOCK)) { |
| movingEdges = fixEdgeToCorner(movingEdges); |
| } |
| invalidate(); |
| } |
| |
| private int fixEdgeToCorner(int moving_edges) { |
| if (moving_edges == MOVE_LEFT) { |
| moving_edges |= MOVE_TOP; |
| } |
| if (moving_edges == MOVE_TOP) { |
| moving_edges |= MOVE_LEFT; |
| } |
| if (moving_edges == MOVE_RIGHT) { |
| moving_edges |= MOVE_BOTTOM; |
| } |
| if (moving_edges == MOVE_BOTTOM) { |
| moving_edges |= MOVE_RIGHT; |
| } |
| return moving_edges; |
| } |
| |
| private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) { |
| RectF newCrop = null; |
| // Fix opposite corner in place and move sides |
| if (moving_corner == BOTTOM_RIGHT) { |
| newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height() |
| + dy); |
| } else if (moving_corner == BOTTOM_LEFT) { |
| newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height() |
| + dy); |
| } else if (moving_corner == TOP_LEFT) { |
| newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy, |
| r.right, r.bottom); |
| } else if (moving_corner == TOP_RIGHT) { |
| newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left |
| + r.width() + dx, r.bottom); |
| } |
| return newCrop; |
| } |
| |
| private void moveEdges(float dX, float dY) { |
| RectF crop = mBounded.getInner(); |
| |
| Matrix mc = getCropBoundDisplayedMatrix(); |
| |
| RectF photo = getLocalPhotoBounds(); |
| Matrix mp = getPhotoBoundDisplayedMatrix(); |
| float[] photoCorners = CropMath.getCornersFromRect(photo); |
| float[] photoCenter = { |
| photo.centerX(), photo.centerY() |
| }; |
| mp.mapPoints(photoCorners); |
| mp.mapPoints(photoCenter); |
| |
| float minWidthHeight = mMinSideSize; |
| |
| if (movingEdges == MOVE_BLOCK) { |
| mBounded.moveInner(-dX, -dY); |
| RectF r = mBounded.getInner(); |
| setCropBounds(r); |
| return; |
| } else { |
| float dx = 0; |
| float dy = 0; |
| |
| if ((movingEdges & MOVE_LEFT) != 0) { |
| dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left; |
| } |
| if ((movingEdges & MOVE_TOP) != 0) { |
| dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top; |
| } |
| if ((movingEdges & MOVE_RIGHT) != 0) { |
| dx = Math.max(crop.right + dX, crop.left + minWidthHeight) |
| - crop.right; |
| } |
| if ((movingEdges & MOVE_BOTTOM) != 0) { |
| dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight) |
| - crop.bottom; |
| } |
| |
| if (mFixAspectRatio) { |
| float[] l1 = { |
| crop.left, crop.bottom |
| }; |
| float[] l2 = { |
| crop.right, crop.top |
| }; |
| if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) { |
| l1[1] = crop.top; |
| l2[1] = crop.bottom; |
| } |
| float[] b = { |
| l1[0] - l2[0], l1[1] - l2[1] |
| }; |
| float[] disp = { |
| dx, dy |
| }; |
| float[] bUnit = GeometryMath.normalize(b); |
| float sp = GeometryMath.scalarProjection(disp, bUnit); |
| dx = sp * bUnit[0]; |
| dy = sp * bUnit[1]; |
| RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy); |
| |
| mBounded.fixedAspectResizeInner(newCrop); |
| newCrop = mBounded.getInner(); |
| setCropBounds(newCrop); |
| return; |
| } else { |
| if ((movingEdges & MOVE_LEFT) != 0) { |
| crop.left += dx; |
| } |
| if ((movingEdges & MOVE_TOP) != 0) { |
| crop.top += dy; |
| } |
| if ((movingEdges & MOVE_RIGHT) != 0) { |
| crop.right += dx; |
| } |
| if ((movingEdges & MOVE_BOTTOM) != 0) { |
| crop.bottom += dy; |
| } |
| } |
| } |
| mBounded.resizeInner(crop); |
| crop = mBounded.getInner(); |
| setCropBounds(crop); |
| } |
| |
| private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) { |
| int left = (int) centerX - indicatorSize / 2; |
| int top = (int) centerY - indicatorSize / 2; |
| indicator.setBounds(left, top, left + indicatorSize, top + indicatorSize); |
| indicator.draw(canvas); |
| } |
| |
| @Override |
| protected void setActionDown(float x, float y) { |
| super.setActionDown(x, y); |
| detectMovingEdges(x + mOffset[0], y + mOffset[1]); |
| |
| } |
| |
| @Override |
| protected void setActionUp() { |
| super.setActionUp(); |
| movingEdges = 0; |
| } |
| |
| @Override |
| protected void setActionMove(float x, float y) { |
| |
| if (movingEdges != 0) { |
| moveEdges(x - mCurrentX, y - mCurrentY); |
| } |
| super.setActionMove(x, y); |
| |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| setActionUp(); |
| cropSetup(); |
| invalidate(); |
| } |
| |
| private void cropSetup() { |
| RectF crop = getLocalCropBounds(); |
| Matrix m = getCropBoundDisplayedMatrix(); |
| m.mapRect(crop); |
| if (mFixAspectRatio) { |
| CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); |
| } |
| float dCentX = getWidth() / 2; |
| float dCentY = getHeight() / 2; |
| |
| BoundedRect r = getBoundedCrop(crop); |
| crop = r.getInner(); |
| if (!setCropBounds(crop)) { |
| float h = mMinSideSize / 2; |
| float wScale = 1; |
| float hScale = mAspectHeight / mAspectWidth; |
| if (hScale < 1) { |
| wScale = mAspectWidth / mAspectHeight; |
| hScale = 1; |
| } |
| crop.set(dCentX - h * wScale, dCentY - h * hScale, dCentX + h * wScale, dCentY + h |
| * hScale); |
| if (mFixAspectRatio) { |
| CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); |
| } |
| r.setInner(crop); |
| crop = r.getInner(); |
| if (!setCropBounds(crop)) { |
| crop.set(dCentX - h, dCentY - h, dCentX + h, dCentY + h); |
| r.setInner(crop); |
| crop = r.getInner(); |
| setCropBounds(crop); |
| } |
| } |
| } |
| |
| @Override |
| public void imageLoaded() { |
| super.imageLoaded(); |
| syncLocalToMasterGeometry(); |
| clear(); |
| invalidate(); |
| } |
| |
| @Override |
| protected void gainedVisibility() { |
| float rot = getLocalRotation(); |
| // if has changed orientation via rotate |
| if (((int) ((rot - mLastRot) / 90)) % 2 != 0) { |
| swapAspect(); |
| } |
| cropSetup(); |
| mFirstDraw = true; |
| } |
| |
| @Override |
| public void resetParameter() { |
| super.resetParameter(); |
| } |
| |
| @Override |
| protected void lostVisibility() { |
| mLastRot = getLocalRotation(); |
| } |
| |
| private void drawRuleOfThird(Canvas canvas, RectF bounds, Paint p) { |
| float stepX = bounds.width() / 3.0f; |
| float stepY = bounds.height() / 3.0f; |
| float x = bounds.left + stepX; |
| float y = bounds.top + stepY; |
| for (int i = 0; i < 2; i++) { |
| canvas.drawLine(x, bounds.top, x, bounds.bottom, p); |
| x += stepX; |
| } |
| for (int j = 0; j < 2; j++) { |
| canvas.drawLine(bounds.left, y, bounds.right, y, p); |
| y += stepY; |
| } |
| } |
| |
| @Override |
| protected void drawShape(Canvas canvas, Bitmap image) { |
| gPaint.setAntiAlias(true); |
| gPaint.setFilterBitmap(true); |
| gPaint.setDither(true); |
| gPaint.setARGB(255, 255, 255, 255); |
| |
| if (mFirstDraw) { |
| cropSetup(); |
| mFirstDraw = false; |
| } |
| |
| RectF crop = drawTransformed(canvas, image, gPaint, mOffset); |
| gPaint.setColor(mBorderColor); |
| gPaint.setStrokeWidth(3); |
| gPaint.setStyle(Paint.Style.STROKE); |
| |
| boolean doThirds = true; |
| |
| if (mFixAspectRatio) { |
| float spotlightX = 0; |
| float spotlightY = 0; |
| if (mCropExtras != null) { |
| spotlightX = mCropExtras.getSpotlightX(); |
| spotlightY = mCropExtras.getSpotlightY(); |
| } |
| if (mDoingCropIntentAction && spotlightX > 0 && spotlightY > 0) { |
| float sx = crop.width() * spotlightX; |
| float sy = crop.height() * spotlightY; |
| float cx = crop.centerX(); |
| float cy = crop.centerY(); |
| RectF r1 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2); |
| float temp = sx; |
| sx = sy; |
| sy = temp; |
| RectF r2 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2); |
| canvas.drawRect(r1, gPaint); |
| canvas.drawRect(r2, gPaint); |
| doThirds = false; |
| } else { |
| float w = crop.width(); |
| float h = crop.height(); |
| float diag = (float) Math.sqrt(w * w + h * h); |
| |
| float dash_len = 20; |
| int num_intervals = (int) (diag / dash_len); |
| float[] tl = { |
| crop.left, crop.top |
| }; |
| float centX = tl[0] + w / 2; |
| float centY = tl[1] + h / 2 + 5; |
| float[] br = { |
| crop.right, crop.bottom |
| }; |
| float[] vec = GeometryMath.getUnitVectorFromPoints(tl, br); |
| |
| float[] counter = tl; |
| for (int x = 0; x < num_intervals; x++) { |
| float tempX = counter[0] + vec[0] * dash_len; |
| float tempY = counter[1] + vec[1] * dash_len; |
| if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2) { |
| canvas.drawLine(counter[0], counter[1], tempX, tempY, gPaint); |
| } |
| counter[0] = tempX; |
| counter[1] = tempY; |
| } |
| |
| gPaint.setTextAlign(Paint.Align.CENTER); |
| gPaint.setTextSize(mAspectTextSize); |
| canvas.drawText(mAspect, centX, centY, gPaint); |
| } |
| } |
| |
| if (doThirds) { |
| drawRuleOfThird(canvas, crop, gPaint); |
| |
| } |
| |
| RectF scaledCrop = crop; |
| boolean notMoving = (movingEdges == 0); |
| if (mFixAspectRatio) { |
| if ((movingEdges == TOP_LEFT) || notMoving) { |
| drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.top); |
| } |
| if ((movingEdges == TOP_RIGHT) || notMoving) { |
| drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.top); |
| } |
| if ((movingEdges == BOTTOM_LEFT) || notMoving) { |
| drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.bottom); |
| } |
| if ((movingEdges == BOTTOM_RIGHT) || notMoving) { |
| drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.bottom); |
| } |
| } else { |
| if (((movingEdges & MOVE_TOP) != 0) || notMoving) { |
| drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); |
| } |
| if (((movingEdges & MOVE_BOTTOM) != 0) || notMoving) { |
| drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); |
| } |
| if (((movingEdges & MOVE_LEFT) != 0) || notMoving) { |
| drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); |
| } |
| if (((movingEdges & MOVE_RIGHT) != 0) || notMoving) { |
| drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); |
| } |
| } |
| } |
| |
| } |