From 960647247705f2c3e375061d234424b71c286e4d Mon Sep 17 00:00:00 2001 From: Yuli Huang Date: Thu, 20 Oct 2011 18:29:05 +0800 Subject: Fix b/5401109. 1. Add FlipView similar to existing RotateView. 2. Add flipPhoto() similar to existing rotatePhoto() in PhotoView, and add setRenderToFlip() in RendererUtils. 3. Make FlipAction use FlipView/PhotoView similar to how RotateAction uses RotateView/PhotoView. Change-Id: I5642266adbc248c0b8eda48ddc29558ae9cbd21e --- .../android/gallery3d/photoeditor/PhotoView.java | 25 +++- .../gallery3d/photoeditor/RendererUtils.java | 79 ++++++++++- .../photoeditor/actions/EffectToolFactory.java | 4 + .../gallery3d/photoeditor/actions/FlipAction.java | 98 ++++++++++---- .../gallery3d/photoeditor/actions/FlipView.java | 147 +++++++++++++++++++++ .../photoeditor/actions/RotateAction.java | 36 +++-- .../gallery3d/photoeditor/actions/RotateView.java | 2 +- .../photoeditor/actions/StraightenAction.java | 4 +- 8 files changed, 338 insertions(+), 57 deletions(-) create mode 100644 src/com/android/gallery3d/photoeditor/actions/FlipView.java (limited to 'src') diff --git a/src/com/android/gallery3d/photoeditor/PhotoView.java b/src/com/android/gallery3d/photoeditor/PhotoView.java index edb36242e..0d4caa862 100644 --- a/src/com/android/gallery3d/photoeditor/PhotoView.java +++ b/src/com/android/gallery3d/photoeditor/PhotoView.java @@ -87,6 +87,13 @@ public class PhotoView extends GLSurfaceView { renderer.rotatePhoto(degrees); } + /** + * Flips displayed photo; this method must be queued for GL thread. + */ + public void flipPhoto(float horizontalDegrees, float verticalDegrees) { + renderer.flipPhoto(horizontalDegrees, verticalDegrees); + } + /** * Renderer that renders the GL surface-view and only be called from the GL thread. */ @@ -99,6 +106,8 @@ public class PhotoView extends GLSurfaceView { int viewWidth; int viewHeight; float rotatedDegrees; + float flippedHorizontalDegrees; + float flippedVerticalDegrees; void setPhoto(Photo photo, boolean clearTransform) { int width = (photo != null) ? photo.width() : 0; @@ -115,18 +124,23 @@ public class PhotoView extends GLSurfaceView { } void updateSurface(boolean clearTransform, boolean sizeChanged) { - boolean transformed = (rotatedDegrees != 0); + boolean flipped = (flippedHorizontalDegrees != 0) || (flippedVerticalDegrees != 0); + boolean transformed = (rotatedDegrees != 0) || flipped; if ((clearTransform && transformed) || (sizeChanged && !transformed)) { // Fit photo when clearing existing transforms or changing surface/photo sizes. if (photo != null) { RendererUtils.setRenderToFit(renderContext, photo.width(), photo.height(), viewWidth, viewHeight); rotatedDegrees = 0; + flippedHorizontalDegrees = 0; + flippedVerticalDegrees = 0; } } else { // Restore existing transformations for orientation changes or awaking from sleep. if (rotatedDegrees != 0) { rotatePhoto(rotatedDegrees); + } else if (flipped) { + flipPhoto(flippedHorizontalDegrees, flippedVerticalDegrees); } } } @@ -139,6 +153,15 @@ public class PhotoView extends GLSurfaceView { } } + void flipPhoto(float horizontalDegrees, float verticalDegrees) { + if (photo != null) { + RendererUtils.setRenderToFlip(renderContext, photo.width(), photo.height(), + viewWidth, viewHeight, horizontalDegrees, verticalDegrees); + flippedHorizontalDegrees = horizontalDegrees; + flippedVerticalDegrees = verticalDegrees; + } + } + @Override public void onDrawFrame(GL10 gl) { Runnable r = null; diff --git a/src/com/android/gallery3d/photoeditor/RendererUtils.java b/src/com/android/gallery3d/photoeditor/RendererUtils.java index ff593d4ef..b92907df0 100644 --- a/src/com/android/gallery3d/photoeditor/RendererUtils.java +++ b/src/com/android/gallery3d/photoeditor/RendererUtils.java @@ -121,13 +121,13 @@ public class RendererUtils { checkGlError("glDeleteTextures"); } - public static void setRenderToFit(RenderContext context, int srcWidth, int srcHeight, - int dstWidth, int dstHeight) { + private static float[] getFitVertices(int srcWidth, int srcHeight, int dstWidth, + int dstHeight) { float srcAspectRatio = ((float) srcWidth) / srcHeight; float dstAspectRatio = ((float) dstWidth) / dstHeight; float relativeAspectRatio = dstAspectRatio / srcAspectRatio; - float vertices[] = new float[8]; + float[] vertices = new float[8]; System.arraycopy(POS_VERTICES, 0, vertices, 0, vertices.length); if (relativeAspectRatio > 1.0f) { // Screen is wider than the camera, scale down X @@ -141,13 +141,20 @@ public class RendererUtils { vertices[5] *= relativeAspectRatio; vertices[7] *= relativeAspectRatio; } - context.posVertices = createVerticesBuffer(vertices); + return vertices; + } + + public static void setRenderToFit(RenderContext context, int srcWidth, int srcHeight, + int dstWidth, int dstHeight) { + context.posVertices = createVerticesBuffer( + getFitVertices(srcWidth, srcHeight, dstWidth, dstHeight)); } public static void setRenderToRotate(RenderContext context, int srcWidth, int srcHeight, int dstWidth, int dstHeight, float degrees) { - float cosTheta = (float) Math.cos(-degrees * DEGREE_TO_RADIAN); - float sinTheta = (float) Math.sin(-degrees * DEGREE_TO_RADIAN); + float radian = -degrees * DEGREE_TO_RADIAN; + float cosTheta = (float) Math.cos(radian); + float sinTheta = (float) Math.sin(radian); float cosWidth = cosTheta * srcWidth; float sinWidth = sinTheta * srcWidth; float cosHeight = cosTheta * srcHeight; @@ -174,6 +181,66 @@ public class RendererUtils { context.posVertices = createVerticesBuffer(vertices); } + public static void setRenderToFlip(RenderContext context, int srcWidth, int srcHeight, + int dstWidth, int dstHeight, float horizontalDegrees, float verticalDegrees) { + // Calculate the base flip coordinates. + float[] base = getFitVertices(srcWidth, srcHeight, dstWidth, dstHeight); + int horizontalRounds = (int) horizontalDegrees / 180; + if (horizontalRounds % 2 != 0) { + base[0] = -base[0]; + base[4] = base[0]; + base[2] = -base[2]; + base[6] = base[2]; + } + int verticalRounds = (int) verticalDegrees / 180; + if (verticalRounds % 2 != 0) { + base[1] = -base[1]; + base[3] = base[1]; + base[5] = -base[5]; + base[7] = base[5]; + } + + float length = 5; + float[] vertices = new float[8]; + System.arraycopy(base, 0, vertices, 0, vertices.length); + if (horizontalDegrees % 180f != 0) { + float radian = (horizontalDegrees - horizontalRounds * 180) * DEGREE_TO_RADIAN; + float cosTheta = (float) Math.cos(radian); + float sinTheta = (float) Math.sin(radian); + + float scale = length / (length + sinTheta * base[0]); + vertices[0] = cosTheta * base[0] * scale; + vertices[1] = base[1] * scale; + vertices[4] = vertices[0]; + vertices[5] = base[5] * scale; + + scale = length / (length + sinTheta * base[2]); + vertices[2] = cosTheta * base[2] * scale; + vertices[3] = base[3] * scale; + vertices[6] = vertices[2]; + vertices[7] = base[7] * scale; + } + + if (verticalDegrees % 180f != 0) { + float radian = (verticalDegrees - verticalRounds * 180) * DEGREE_TO_RADIAN; + float cosTheta = (float) Math.cos(radian); + float sinTheta = (float) Math.sin(radian); + + float scale = length / (length + sinTheta * base[1]); + vertices[0] = base[0] * scale; + vertices[1] = cosTheta * base[1] * scale; + vertices[2] = base[2] * scale; + vertices[3] = vertices[1]; + + scale = length / (length + sinTheta * base[5]); + vertices[4] = base[4] * scale; + vertices[5] = cosTheta * base[5] * scale; + vertices[6] = base[6] * scale; + vertices[7] = vertices[5]; + } + context.posVertices = createVerticesBuffer(vertices); + } + public static void renderBackground() { GLES20.glClearColor(0, 0, 0, 1); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); diff --git a/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java b/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java index 2c697351d..3641828a6 100644 --- a/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java +++ b/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java @@ -90,6 +90,10 @@ public class EffectToolFactory { return (TouchView) createFullscreenTool(R.layout.photoeditor_touch_view); } + public FlipView createFlipView() { + return (FlipView) createFullscreenTool(R.layout.photoeditor_flip_view); + } + public RotateView createRotateView() { return (RotateView) createFullscreenTool(R.layout.photoeditor_rotate_view); } diff --git a/src/com/android/gallery3d/photoeditor/actions/FlipAction.java b/src/com/android/gallery3d/photoeditor/actions/FlipAction.java index 00abc60a0..da238ba98 100644 --- a/src/com/android/gallery3d/photoeditor/actions/FlipAction.java +++ b/src/com/android/gallery3d/photoeditor/actions/FlipAction.java @@ -19,6 +19,8 @@ package com.android.gallery3d.photoeditor.actions; import android.content.Context; import android.util.AttributeSet; +import com.android.gallery3d.R; +import com.android.gallery3d.photoeditor.PhotoView; import com.android.gallery3d.photoeditor.filters.FlipFilter; /** @@ -26,9 +28,14 @@ import com.android.gallery3d.photoeditor.filters.FlipFilter; */ public class FlipAction extends EffectAction { - private boolean flipHorizontal; - private boolean flipVertical; - private TouchView touchView; + private static final float DEFAULT_ANGLE = 0.0f; + private static final float DEFAULT_FLIP_SPAN = 180.0f; + + private FlipFilter filter; + private float horizontalFlipDegrees; + private float verticalFlipDegrees; + private Runnable queuedFlipChange; + private FlipView flipView; public FlipAction(Context context, AttributeSet attrs) { super(context, attrs); @@ -36,51 +43,86 @@ public class FlipAction extends EffectAction { @Override public void doBegin() { - final FlipFilter filter = new FlipFilter(); + filter = new FlipFilter(); + + flipView = factory.createFlipView(); + flipView.setOnFlipChangeListener(new FlipView.OnFlipChangeListener() { - touchView = factory.createTouchView(); - touchView.setSwipeListener(new TouchView.SwipeListener() { + // Directly transform photo-view because running the flip filter isn't fast enough. + PhotoView photoView = (PhotoView) flipView.getRootView().findViewById( + R.id.photo_view); @Override - public void onSwipeDown() { - flipFilterVertically(filter); + public void onAngleChanged(float horizontalDegrees, float verticalDegrees, + boolean fromUser) { + if (fromUser) { + horizontalFlipDegrees = horizontalDegrees; + verticalFlipDegrees = verticalDegrees; + updateFlipFilter(false); + transformPhotoView(horizontalDegrees, verticalDegrees); + } } @Override - public void onSwipeLeft() { - flipFilterHorizontally(filter); + public void onStartTrackingTouch() { + // no-op } @Override - public void onSwipeRight() { - flipFilterHorizontally(filter); + public void onStopTrackingTouch() { + roundFlipDegrees(); + updateFlipFilter(false); + transformPhotoView(horizontalFlipDegrees, verticalFlipDegrees); + flipView.setFlippedAngles(horizontalFlipDegrees, verticalFlipDegrees); } - @Override - public void onSwipeUp() { - flipFilterVertically(filter); + private void transformPhotoView(final float horizontalDegrees, + final float verticalDegrees) { + // Remove the outdated flip change before queuing a new one. + if (queuedFlipChange != null) { + photoView.remove(queuedFlipChange); + } + queuedFlipChange = new Runnable() { + + @Override + public void run() { + photoView.flipPhoto(horizontalDegrees, verticalDegrees); + } + }; + photoView.queue(queuedFlipChange); } }); - - flipHorizontal = false; - flipVertical = false; - flipFilterHorizontally(filter); + flipView.setFlippedAngles(DEFAULT_ANGLE, DEFAULT_ANGLE); + flipView.setFlipSpan(DEFAULT_FLIP_SPAN); + horizontalFlipDegrees = 0; + verticalFlipDegrees = 0; + queuedFlipChange = null; } @Override public void doEnd() { - touchView.setSwipeListener(null); + flipView.setOnFlipChangeListener(null); + // Round the current flip degrees in case flip tracking has not stopped yet. + roundFlipDegrees(); + updateFlipFilter(true); } - private void flipFilterHorizontally(final FlipFilter filter) { - flipHorizontal = !flipHorizontal; - filter.setFlip(flipHorizontal, flipVertical); - notifyFilterChanged(filter, true); + /** + * Rounds flip degrees to multiples of 180 degrees. + */ + private void roundFlipDegrees() { + if (horizontalFlipDegrees % 180 != 0) { + horizontalFlipDegrees = Math.round(horizontalFlipDegrees / 180) * 180; + } + if (verticalFlipDegrees % 180 != 0) { + verticalFlipDegrees = Math.round(verticalFlipDegrees / 180) * 180; + } } - private void flipFilterVertically(final FlipFilter filter) { - flipVertical = !flipVertical; - filter.setFlip(flipHorizontal, flipVertical); - notifyFilterChanged(filter, true); + private void updateFlipFilter(boolean outputFilter) { + // Flip the filter if the flipped degrees are at the opposite directions. + filter.setFlip(((int) horizontalFlipDegrees / 180) % 2 != 0, + ((int) verticalFlipDegrees / 180) % 2 != 0); + notifyFilterChanged(filter, outputFilter); } } diff --git a/src/com/android/gallery3d/photoeditor/actions/FlipView.java b/src/com/android/gallery3d/photoeditor/actions/FlipView.java new file mode 100644 index 000000000..17c4343ad --- /dev/null +++ b/src/com/android/gallery3d/photoeditor/actions/FlipView.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2010 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.photoeditor.actions; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +/** + * View that handles touch-events to track flipping directions and angles. + */ +class FlipView extends FullscreenToolView { + + /** + * Listens to flip changes. + */ + public interface OnFlipChangeListener { + + void onAngleChanged(float horizontalDegrees, float verticalDegrees, boolean fromUser); + + void onStartTrackingTouch(); + + void onStopTrackingTouch(); + } + + private static final float FIXED_DIRECTION_THRESHOLD = 20; + + private OnFlipChangeListener listener; + private float maxFlipSpan; + private float touchStartX; + private float touchStartY; + private float currentHorizontalDegrees; + private float currentVerticalDegrees; + private float lastHorizontalDegrees; + private float lastVerticalDegrees; + private boolean fixedDirection; + private boolean fixedDirectionHorizontal; + + public FlipView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setOnFlipChangeListener(OnFlipChangeListener listener) { + this.listener = listener; + } + + public void setFlippedAngles(float horizontalDegrees, float verticalDegrees) { + refreshAngle(horizontalDegrees, verticalDegrees, false); + } + + /** + * Sets allowed degrees for every flip before flipping the view. + */ + public void setFlipSpan(float degrees) { + // Flip-span limits allowed flipping degrees of every flip for usability purpose; the max. + // flipped angles could be accumulated and larger than allowed flip-span. + maxFlipSpan = degrees; + } + + private float calculateAngle(boolean flipHorizontal, float x, float y) { + // Use partial length along the moving direction to calculate the flip angle. + float maxDistance = (flipHorizontal ? getWidth() : getHeight()) * 0.35f; + float moveDistance = flipHorizontal ? (x - touchStartX) : (touchStartY - y); + + if (Math.abs(moveDistance) > maxDistance) { + moveDistance = (moveDistance > 0) ? maxDistance : -maxDistance; + + if (flipHorizontal) { + touchStartX = x - moveDistance; + } else { + touchStartY = moveDistance + y; + } + } + return (moveDistance / maxDistance) * maxFlipSpan; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + super.onTouchEvent(ev); + + if (isEnabled()) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + fixedDirection = false; + lastHorizontalDegrees = currentHorizontalDegrees; + lastVerticalDegrees = currentVerticalDegrees; + touchStartX = ev.getX(); + touchStartY = ev.getY(); + + if (listener != null) { + listener.onStartTrackingTouch(); + } + break; + + case MotionEvent.ACTION_MOVE: + // Allow only one direction for flipping during movements, and make the + // direction fixed once it exceeds threshold. + float x = ev.getX(); + float y = ev.getY(); + boolean flipHorizontal = fixedDirection ? fixedDirectionHorizontal + : (Math.abs(x - touchStartX) >= Math.abs(y - touchStartY)); + float degrees = calculateAngle(flipHorizontal, x, y); + if (!fixedDirection && (Math.abs(degrees) > FIXED_DIRECTION_THRESHOLD)) { + fixedDirection = true; + fixedDirectionHorizontal = flipHorizontal; + } + + if (flipHorizontal) { + refreshAngle(lastHorizontalDegrees + degrees, lastVerticalDegrees, true); + } else { + refreshAngle(lastHorizontalDegrees, lastVerticalDegrees + degrees, true); + } + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (listener != null) { + listener.onStopTrackingTouch(); + } + break; + } + } + return true; + } + + private void refreshAngle(float horizontalDegrees, float verticalDegrees, boolean fromUser) { + currentHorizontalDegrees = horizontalDegrees; + currentVerticalDegrees = verticalDegrees; + if (listener != null) { + listener.onAngleChanged(horizontalDegrees, verticalDegrees, fromUser); + } + } +} diff --git a/src/com/android/gallery3d/photoeditor/actions/RotateAction.java b/src/com/android/gallery3d/photoeditor/actions/RotateAction.java index cc1903886..36a09d96f 100644 --- a/src/com/android/gallery3d/photoeditor/actions/RotateAction.java +++ b/src/com/android/gallery3d/photoeditor/actions/RotateAction.java @@ -45,9 +45,9 @@ public class RotateAction extends EffectAction { filter = new RotateFilter(); rotateView = factory.createRotateView(); - rotateView.setOnAngleChangeListener(new RotateView.OnRotateChangeListener() { + rotateView.setOnRotateChangeListener(new RotateView.OnRotateChangeListener() { - // Directly transform photo-view because running the rotation filter isn't fast enough. + // Directly transform photo-view because running the rotate filter isn't fast enough. PhotoView photoView = (PhotoView) rotateView.getRootView().findViewById( R.id.photo_view); @@ -55,8 +55,7 @@ public class RotateAction extends EffectAction { public void onAngleChanged(float degrees, boolean fromUser){ if (fromUser) { rotateDegrees = degrees; - filter.setAngle(degrees); - notifyFilterChanged(filter, false); + updateRotateFilter(false); transformPhotoView(degrees); } } @@ -68,11 +67,10 @@ public class RotateAction extends EffectAction { @Override public void onStopTrackingTouch() { - if (roundFilterRotationDegrees()) { - notifyFilterChanged(filter, false); - transformPhotoView(rotateDegrees); - rotateView.setRotatedAngle(rotateDegrees); - } + roundRotateDegrees(); + updateRotateFilter(false); + transformPhotoView(rotateDegrees); + rotateView.setRotatedAngle(rotateDegrees); } private void transformPhotoView(final float degrees) { @@ -98,23 +96,23 @@ public class RotateAction extends EffectAction { @Override public void doEnd() { - rotateView.setOnAngleChangeListener(null); + rotateView.setOnRotateChangeListener(null); // Round the current rotation degrees in case rotation tracking has not stopped yet. - roundFilterRotationDegrees(); - notifyFilterChanged(filter, true); + roundRotateDegrees(); + updateRotateFilter(true); } /** - * Rounds filter rotation degrees to multiples of 90 degrees. - * - * @return true if the rotation degrees has been changed. + * Rounds rotate degrees to multiples of 90 degrees. */ - private boolean roundFilterRotationDegrees() { + private void roundRotateDegrees() { if (rotateDegrees % 90 != 0) { rotateDegrees = Math.round(rotateDegrees / 90) * 90; - filter.setAngle(rotateDegrees); - return true; } - return false; + } + + private void updateRotateFilter(boolean outputFilter) { + filter.setAngle(rotateDegrees); + notifyFilterChanged(filter, outputFilter); } } diff --git a/src/com/android/gallery3d/photoeditor/actions/RotateView.java b/src/com/android/gallery3d/photoeditor/actions/RotateView.java index 3598e32b8..b4f63f08f 100644 --- a/src/com/android/gallery3d/photoeditor/actions/RotateView.java +++ b/src/com/android/gallery3d/photoeditor/actions/RotateView.java @@ -92,7 +92,7 @@ class RotateView extends FullscreenToolView { minRotatedAngle = -maxRotatedAngle; } - public void setOnAngleChangeListener(OnRotateChangeListener listener) { + public void setOnRotateChangeListener(OnRotateChangeListener listener) { this.listener = listener; } diff --git a/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java b/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java index 2a8c549f6..42b384dc6 100644 --- a/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java +++ b/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java @@ -40,7 +40,7 @@ public class StraightenAction extends EffectAction { final StraightenFilter filter = new StraightenFilter(); rotateView = factory.createRotateView(); - rotateView.setOnAngleChangeListener(new RotateView.OnRotateChangeListener() { + rotateView.setOnRotateChangeListener(new RotateView.OnRotateChangeListener() { @Override public void onAngleChanged(float degrees, boolean fromUser){ @@ -67,6 +67,6 @@ public class StraightenAction extends EffectAction { @Override public void doEnd() { - rotateView.setOnAngleChangeListener(null); + rotateView.setOnRotateChangeListener(null); } } -- cgit v1.2.3