Initial checkin of AOSP Messaging app.
b/23110861 Change-Id: I9aa980d7569247d6b2ca78f5dcb4502e1eaadb8a
This commit is contained in:
@@ -0,0 +1,589 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.messaging.ui.mediapicker.camerafocus;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.hardware.Camera.Area;
|
||||
import android.hardware.Camera.Parameters;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/* A class that handles everything about focus in still picture mode.
|
||||
* This also handles the metering area because it is the same as focus area.
|
||||
*
|
||||
* The test cases:
|
||||
* (1) The camera has continuous autofocus. Move the camera. Take a picture when
|
||||
* CAF is not in progress.
|
||||
* (2) The camera has continuous autofocus. Move the camera. Take a picture when
|
||||
* CAF is in progress.
|
||||
* (3) The camera has face detection. Point the camera at some faces. Hold the
|
||||
* shutter. Release to take a picture.
|
||||
* (4) The camera has face detection. Point the camera at some faces. Single tap
|
||||
* the shutter to take a picture.
|
||||
* (5) The camera has autofocus. Single tap the shutter to take a picture.
|
||||
* (6) The camera has autofocus. Hold the shutter. Release to take a picture.
|
||||
* (7) The camera has no autofocus. Single tap the shutter and take a picture.
|
||||
* (8) The camera has autofocus and supports focus area. Touch the screen to
|
||||
* trigger autofocus. Take a picture.
|
||||
* (9) The camera has autofocus and supports focus area. Touch the screen to
|
||||
* trigger autofocus. Wait until it times out.
|
||||
* (10) The camera has no autofocus and supports metering area. Touch the screen
|
||||
* to change metering area.
|
||||
*/
|
||||
public class FocusOverlayManager {
|
||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||
private static final String TRUE = "true";
|
||||
private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
|
||||
private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED =
|
||||
"auto-whitebalance-lock-supported";
|
||||
|
||||
private static final int RESET_TOUCH_FOCUS = 0;
|
||||
private static final int RESET_TOUCH_FOCUS_DELAY = 3000;
|
||||
|
||||
private int mState = STATE_IDLE;
|
||||
private static final int STATE_IDLE = 0; // Focus is not active.
|
||||
private static final int STATE_FOCUSING = 1; // Focus is in progress.
|
||||
// Focus is in progress and the camera should take a picture after focus finishes.
|
||||
private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2;
|
||||
private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds.
|
||||
private static final int STATE_FAIL = 4; // Focus finishes and fails.
|
||||
|
||||
private boolean mInitialized;
|
||||
private boolean mFocusAreaSupported;
|
||||
private boolean mMeteringAreaSupported;
|
||||
private boolean mLockAeAwbNeeded;
|
||||
private boolean mAeAwbLock;
|
||||
private Matrix mMatrix;
|
||||
|
||||
private PieRenderer mPieRenderer;
|
||||
|
||||
private int mPreviewWidth; // The width of the preview frame layout.
|
||||
private int mPreviewHeight; // The height of the preview frame layout.
|
||||
private boolean mMirror; // true if the camera is front-facing.
|
||||
private int mDisplayOrientation;
|
||||
private List<Object> mFocusArea; // focus area in driver format
|
||||
private List<Object> mMeteringArea; // metering area in driver format
|
||||
private String mFocusMode;
|
||||
private String mOverrideFocusMode;
|
||||
private Parameters mParameters;
|
||||
private Handler mHandler;
|
||||
Listener mListener;
|
||||
|
||||
public interface Listener {
|
||||
public void autoFocus();
|
||||
public void cancelAutoFocus();
|
||||
public boolean capture();
|
||||
public void setFocusParameters();
|
||||
}
|
||||
|
||||
private class MainHandler extends Handler {
|
||||
public MainHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case RESET_TOUCH_FOCUS: {
|
||||
cancelAutoFocus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public FocusOverlayManager(Listener listener, Looper looper) {
|
||||
mHandler = new MainHandler(looper);
|
||||
mMatrix = new Matrix();
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void setFocusRenderer(PieRenderer renderer) {
|
||||
mPieRenderer = renderer;
|
||||
mInitialized = (mMatrix != null);
|
||||
}
|
||||
|
||||
public void setParameters(Parameters parameters) {
|
||||
// parameters can only be null when onConfigurationChanged is called
|
||||
// before camera is open. We will just return in this case, because
|
||||
// parameters will be set again later with the right parameters after
|
||||
// camera is open.
|
||||
if (parameters == null) {
|
||||
return;
|
||||
}
|
||||
mParameters = parameters;
|
||||
mFocusAreaSupported = isFocusAreaSupported(parameters);
|
||||
mMeteringAreaSupported = isMeteringAreaSupported(parameters);
|
||||
mLockAeAwbNeeded = (isAutoExposureLockSupported(mParameters) ||
|
||||
isAutoWhiteBalanceLockSupported(mParameters));
|
||||
}
|
||||
|
||||
public void setPreviewSize(int previewWidth, int previewHeight) {
|
||||
if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) {
|
||||
mPreviewWidth = previewWidth;
|
||||
mPreviewHeight = previewHeight;
|
||||
setMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMirror(boolean mirror) {
|
||||
mMirror = mirror;
|
||||
setMatrix();
|
||||
}
|
||||
|
||||
public void setDisplayOrientation(int displayOrientation) {
|
||||
mDisplayOrientation = displayOrientation;
|
||||
setMatrix();
|
||||
}
|
||||
|
||||
private void setMatrix() {
|
||||
if (mPreviewWidth != 0 && mPreviewHeight != 0) {
|
||||
Matrix matrix = new Matrix();
|
||||
prepareMatrix(matrix, mMirror, mDisplayOrientation,
|
||||
mPreviewWidth, mPreviewHeight);
|
||||
// In face detection, the matrix converts the driver coordinates to UI
|
||||
// coordinates. In tap focus, the inverted matrix converts the UI
|
||||
// coordinates to driver coordinates.
|
||||
matrix.invert(mMatrix);
|
||||
mInitialized = (mPieRenderer != null);
|
||||
}
|
||||
}
|
||||
|
||||
private void lockAeAwbIfNeeded() {
|
||||
if (mLockAeAwbNeeded && !mAeAwbLock) {
|
||||
mAeAwbLock = true;
|
||||
mListener.setFocusParameters();
|
||||
}
|
||||
}
|
||||
|
||||
private void unlockAeAwbIfNeeded() {
|
||||
if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) {
|
||||
mAeAwbLock = false;
|
||||
mListener.setFocusParameters();
|
||||
}
|
||||
}
|
||||
|
||||
public void onShutterDown() {
|
||||
if (!mInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean autoFocusCalled = false;
|
||||
if (needAutoFocusCall()) {
|
||||
// Do not focus if touch focus has been triggered.
|
||||
if (mState != STATE_SUCCESS && mState != STATE_FAIL) {
|
||||
autoFocus();
|
||||
autoFocusCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!autoFocusCalled) {
|
||||
lockAeAwbIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
public void onShutterUp() {
|
||||
if (!mInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (needAutoFocusCall()) {
|
||||
// User releases half-pressed focus key.
|
||||
if (mState == STATE_FOCUSING || mState == STATE_SUCCESS
|
||||
|| mState == STATE_FAIL) {
|
||||
cancelAutoFocus();
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock AE and AWB after cancelAutoFocus. Camera API does not
|
||||
// guarantee setParameters can be called during autofocus.
|
||||
unlockAeAwbIfNeeded();
|
||||
}
|
||||
|
||||
public void doSnap() {
|
||||
if (!mInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user has half-pressed the shutter and focus is completed, we
|
||||
// can take the photo right away. If the focus mode is infinity, we can
|
||||
// also take the photo.
|
||||
if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) {
|
||||
capture();
|
||||
} else if (mState == STATE_FOCUSING) {
|
||||
// Half pressing the shutter (i.e. the focus button event) will
|
||||
// already have requested AF for us, so just request capture on
|
||||
// focus here.
|
||||
mState = STATE_FOCUSING_SNAP_ON_FINISH;
|
||||
} else if (mState == STATE_IDLE) {
|
||||
// We didn't do focus. This can happen if the user press focus key
|
||||
// while the snapshot is still in progress. The user probably wants
|
||||
// the next snapshot as soon as possible, so we just do a snapshot
|
||||
// without focusing again.
|
||||
capture();
|
||||
}
|
||||
}
|
||||
|
||||
public void onAutoFocus(boolean focused, boolean shutterButtonPressed) {
|
||||
if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {
|
||||
// Take the picture no matter focus succeeds or fails. No need
|
||||
// to play the AF sound if we're about to play the shutter
|
||||
// sound.
|
||||
if (focused) {
|
||||
mState = STATE_SUCCESS;
|
||||
} else {
|
||||
mState = STATE_FAIL;
|
||||
}
|
||||
updateFocusUI();
|
||||
capture();
|
||||
} else if (mState == STATE_FOCUSING) {
|
||||
// This happens when (1) user is half-pressing the focus key or
|
||||
// (2) touch focus is triggered. Play the focus tone. Do not
|
||||
// take the picture now.
|
||||
if (focused) {
|
||||
mState = STATE_SUCCESS;
|
||||
} else {
|
||||
mState = STATE_FAIL;
|
||||
}
|
||||
updateFocusUI();
|
||||
// If this is triggered by touch focus, cancel focus after a
|
||||
// while.
|
||||
if (mFocusArea != null) {
|
||||
mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
|
||||
}
|
||||
if (shutterButtonPressed) {
|
||||
// Lock AE & AWB so users can half-press shutter and recompose.
|
||||
lockAeAwbIfNeeded();
|
||||
}
|
||||
} else if (mState == STATE_IDLE) {
|
||||
// User has released the focus key before focus completes.
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
public void onAutoFocusMoving(boolean moving) {
|
||||
if (!mInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore if we have requested autofocus. This method only handles
|
||||
// continuous autofocus.
|
||||
if (mState != STATE_IDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (moving) {
|
||||
mPieRenderer.showStart();
|
||||
} else {
|
||||
mPieRenderer.showSuccess(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeFocusAreas(int focusWidth, int focusHeight,
|
||||
int x, int y, int previewWidth, int previewHeight) {
|
||||
if (mFocusArea == null) {
|
||||
mFocusArea = new ArrayList<Object>();
|
||||
mFocusArea.add(new Area(new Rect(), 1));
|
||||
}
|
||||
|
||||
// Convert the coordinates to driver format.
|
||||
calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight,
|
||||
((Area) mFocusArea.get(0)).rect);
|
||||
}
|
||||
|
||||
private void initializeMeteringAreas(int focusWidth, int focusHeight,
|
||||
int x, int y, int previewWidth, int previewHeight) {
|
||||
if (mMeteringArea == null) {
|
||||
mMeteringArea = new ArrayList<Object>();
|
||||
mMeteringArea.add(new Area(new Rect(), 1));
|
||||
}
|
||||
|
||||
// Convert the coordinates to driver format.
|
||||
// AE area is bigger because exposure is sensitive and
|
||||
// easy to over- or underexposure if area is too small.
|
||||
calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight,
|
||||
((Area) mMeteringArea.get(0)).rect);
|
||||
}
|
||||
|
||||
public void onSingleTapUp(int x, int y) {
|
||||
if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let users be able to cancel previous touch focus.
|
||||
if ((mFocusArea != null) && (mState == STATE_FOCUSING ||
|
||||
mState == STATE_SUCCESS || mState == STATE_FAIL)) {
|
||||
cancelAutoFocus();
|
||||
}
|
||||
// Initialize variables.
|
||||
int focusWidth = mPieRenderer.getSize();
|
||||
int focusHeight = mPieRenderer.getSize();
|
||||
if (focusWidth == 0 || mPieRenderer.getWidth() == 0 || mPieRenderer.getHeight() == 0) {
|
||||
return;
|
||||
}
|
||||
int previewWidth = mPreviewWidth;
|
||||
int previewHeight = mPreviewHeight;
|
||||
// Initialize mFocusArea.
|
||||
if (mFocusAreaSupported) {
|
||||
initializeFocusAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
|
||||
}
|
||||
// Initialize mMeteringArea.
|
||||
if (mMeteringAreaSupported) {
|
||||
initializeMeteringAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
|
||||
}
|
||||
|
||||
// Use margin to set the focus indicator to the touched area.
|
||||
mPieRenderer.setFocus(x, y);
|
||||
|
||||
// Set the focus area and metering area.
|
||||
mListener.setFocusParameters();
|
||||
if (mFocusAreaSupported) {
|
||||
autoFocus();
|
||||
} else { // Just show the indicator in all other cases.
|
||||
updateFocusUI();
|
||||
// Reset the metering area in 3 seconds.
|
||||
mHandler.removeMessages(RESET_TOUCH_FOCUS);
|
||||
mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
public void onPreviewStarted() {
|
||||
mState = STATE_IDLE;
|
||||
}
|
||||
|
||||
public void onPreviewStopped() {
|
||||
// If auto focus was in progress, it would have been stopped.
|
||||
mState = STATE_IDLE;
|
||||
resetTouchFocus();
|
||||
updateFocusUI();
|
||||
}
|
||||
|
||||
public void onCameraReleased() {
|
||||
onPreviewStopped();
|
||||
}
|
||||
|
||||
private void autoFocus() {
|
||||
LogUtil.v(TAG, "Start autofocus.");
|
||||
mListener.autoFocus();
|
||||
mState = STATE_FOCUSING;
|
||||
updateFocusUI();
|
||||
mHandler.removeMessages(RESET_TOUCH_FOCUS);
|
||||
}
|
||||
|
||||
private void cancelAutoFocus() {
|
||||
LogUtil.v(TAG, "Cancel autofocus.");
|
||||
|
||||
// Reset the tap area before calling mListener.cancelAutofocus.
|
||||
// Otherwise, focus mode stays at auto and the tap area passed to the
|
||||
// driver is not reset.
|
||||
resetTouchFocus();
|
||||
mListener.cancelAutoFocus();
|
||||
mState = STATE_IDLE;
|
||||
updateFocusUI();
|
||||
mHandler.removeMessages(RESET_TOUCH_FOCUS);
|
||||
}
|
||||
|
||||
private void capture() {
|
||||
if (mListener.capture()) {
|
||||
mState = STATE_IDLE;
|
||||
mHandler.removeMessages(RESET_TOUCH_FOCUS);
|
||||
}
|
||||
}
|
||||
|
||||
public String getFocusMode() {
|
||||
if (mOverrideFocusMode != null) {
|
||||
return mOverrideFocusMode;
|
||||
}
|
||||
List<String> supportedFocusModes = mParameters.getSupportedFocusModes();
|
||||
|
||||
if (mFocusAreaSupported && mFocusArea != null) {
|
||||
// Always use autofocus in tap-to-focus.
|
||||
mFocusMode = Parameters.FOCUS_MODE_AUTO;
|
||||
} else {
|
||||
mFocusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
|
||||
}
|
||||
|
||||
if (!isSupported(mFocusMode, supportedFocusModes)) {
|
||||
// For some reasons, the driver does not support the current
|
||||
// focus mode. Fall back to auto.
|
||||
if (isSupported(Parameters.FOCUS_MODE_AUTO,
|
||||
mParameters.getSupportedFocusModes())) {
|
||||
mFocusMode = Parameters.FOCUS_MODE_AUTO;
|
||||
} else {
|
||||
mFocusMode = mParameters.getFocusMode();
|
||||
}
|
||||
}
|
||||
return mFocusMode;
|
||||
}
|
||||
|
||||
public List getFocusAreas() {
|
||||
return mFocusArea;
|
||||
}
|
||||
|
||||
public List getMeteringAreas() {
|
||||
return mMeteringArea;
|
||||
}
|
||||
|
||||
public void updateFocusUI() {
|
||||
if (!mInitialized) {
|
||||
return;
|
||||
}
|
||||
FocusIndicator focusIndicator = mPieRenderer;
|
||||
|
||||
if (mState == STATE_IDLE) {
|
||||
if (mFocusArea == null) {
|
||||
focusIndicator.clear();
|
||||
} else {
|
||||
// Users touch on the preview and the indicator represents the
|
||||
// metering area. Either focus area is not supported or
|
||||
// autoFocus call is not required.
|
||||
focusIndicator.showStart();
|
||||
}
|
||||
} else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
|
||||
focusIndicator.showStart();
|
||||
} else {
|
||||
if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) {
|
||||
// TODO: check HAL behavior and decide if this can be removed.
|
||||
focusIndicator.showSuccess(false);
|
||||
} else if (mState == STATE_SUCCESS) {
|
||||
focusIndicator.showSuccess(false);
|
||||
} else if (mState == STATE_FAIL) {
|
||||
focusIndicator.showFail(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTouchFocus() {
|
||||
if (!mInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Put focus indicator to the center. clear reset position
|
||||
mPieRenderer.clear();
|
||||
|
||||
mFocusArea = null;
|
||||
mMeteringArea = null;
|
||||
}
|
||||
|
||||
private void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple,
|
||||
int x, int y, int previewWidth, int previewHeight, Rect rect) {
|
||||
int areaWidth = (int) (focusWidth * areaMultiple);
|
||||
int areaHeight = (int) (focusHeight * areaMultiple);
|
||||
int left = clamp(x - areaWidth / 2, 0, previewWidth - areaWidth);
|
||||
int top = clamp(y - areaHeight / 2, 0, previewHeight - areaHeight);
|
||||
|
||||
RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight);
|
||||
mMatrix.mapRect(rectF);
|
||||
rectFToRect(rectF, rect);
|
||||
}
|
||||
|
||||
/* package */ int getFocusState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
public boolean isFocusCompleted() {
|
||||
return mState == STATE_SUCCESS || mState == STATE_FAIL;
|
||||
}
|
||||
|
||||
public boolean isFocusingSnapOnFinish() {
|
||||
return mState == STATE_FOCUSING_SNAP_ON_FINISH;
|
||||
}
|
||||
|
||||
public void removeMessages() {
|
||||
mHandler.removeMessages(RESET_TOUCH_FOCUS);
|
||||
}
|
||||
|
||||
public void overrideFocusMode(String focusMode) {
|
||||
mOverrideFocusMode = focusMode;
|
||||
}
|
||||
|
||||
public void setAeAwbLock(boolean lock) {
|
||||
mAeAwbLock = lock;
|
||||
}
|
||||
|
||||
public boolean getAeAwbLock() {
|
||||
return mAeAwbLock;
|
||||
}
|
||||
|
||||
private boolean needAutoFocusCall() {
|
||||
String focusMode = getFocusMode();
|
||||
return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY)
|
||||
|| focusMode.equals(Parameters.FOCUS_MODE_FIXED)
|
||||
|| focusMode.equals(Parameters.FOCUS_MODE_EDOF));
|
||||
}
|
||||
|
||||
public static boolean isAutoExposureLockSupported(Parameters params) {
|
||||
return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
|
||||
}
|
||||
|
||||
public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
|
||||
return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
|
||||
}
|
||||
|
||||
public static boolean isSupported(String value, List<String> supported) {
|
||||
return supported != null && supported.indexOf(value) >= 0;
|
||||
}
|
||||
|
||||
public static boolean isMeteringAreaSupported(Parameters params) {
|
||||
return params.getMaxNumMeteringAreas() > 0;
|
||||
}
|
||||
|
||||
public static boolean isFocusAreaSupported(Parameters params) {
|
||||
return (params.getMaxNumFocusAreas() > 0
|
||||
&& isSupported(Parameters.FOCUS_MODE_AUTO,
|
||||
params.getSupportedFocusModes()));
|
||||
}
|
||||
|
||||
public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
|
||||
int viewWidth, int viewHeight) {
|
||||
// Need mirror for front camera.
|
||||
matrix.setScale(mirror ? -1 : 1, 1);
|
||||
// This is the value for android.hardware.Camera.setDisplayOrientation.
|
||||
matrix.postRotate(displayOrientation);
|
||||
// Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
|
||||
// UI coordinates range from (0, 0) to (width, height).
|
||||
matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
|
||||
matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
|
||||
}
|
||||
|
||||
public static int clamp(int x, int min, int max) {
|
||||
Assert.isTrue(max >= min);
|
||||
if (x > max) {
|
||||
return max;
|
||||
}
|
||||
if (x < min) {
|
||||
return min;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
public static void rectFToRect(RectF rectF, Rect rect) {
|
||||
rect.left = Math.round(rectF.left);
|
||||
rect.top = Math.round(rectF.top);
|
||||
rect.right = Math.round(rectF.right);
|
||||
rect.bottom = Math.round(rectF.bottom);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user