Add stuff from Gallery2
This commit is contained in:
1 parent
a54f00808a
commit
ee9d23dd60
42 files changed
+7038
-1
No files matched your search
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.anim;
|
||||
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class AlphaAnimation extends CanvasAnimation {
|
||||
private final float mStartAlpha;
|
||||
private final float mEndAlpha;
|
||||
private float mCurrentAlpha;
|
||||
|
||||
public AlphaAnimation(float from, float to) {
|
||||
mStartAlpha = from;
|
||||
mEndAlpha = to;
|
||||
mCurrentAlpha = from;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(GLCanvas canvas) {
|
||||
canvas.multiplyAlpha(mCurrentAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCanvasSaveFlags() {
|
||||
return GLCanvas.SAVE_FLAG_ALPHA;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
mCurrentAlpha = MathUtils.clamp(mStartAlpha +
|
||||
(mEndAlpha - mStartAlpha) * progress, 0f, 1f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.anim;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
// Animation calculates a value according to the current input time.
|
||||
//
|
||||
// 1. First we need to use setDuration(int) to set the duration of the
|
||||
// animation. The duration is in milliseconds.
|
||||
// 2. Then we should call start(). The actual start time is the first value
|
||||
// passed to calculate(long).
|
||||
// 3. Each time we want to get an animation value, we call
|
||||
// calculate(long currentTimeMillis) to ask the Animation to calculate it.
|
||||
// The parameter passed to calculate(long) should be nonnegative.
|
||||
// 4. Use get() to get that value.
|
||||
//
|
||||
// In step 3, onCalculate(float progress) is called so subclasses can calculate
|
||||
// the value according to progress (progress is a value in [0,1]).
|
||||
//
|
||||
// Before onCalculate(float) is called, There is an optional interpolator which
|
||||
// can change the progress value. The interpolator can be set by
|
||||
// setInterpolator(Interpolator). If the interpolator is used, the value passed
|
||||
// to onCalculate may be (for example, the overshoot effect).
|
||||
//
|
||||
// The isActive() method returns true after the animation start() is called and
|
||||
// before calculate is passed a value which reaches the duration of the
|
||||
// animation.
|
||||
//
|
||||
// The start() method can be called again to restart the Animation.
|
||||
//
|
||||
abstract public class Animation {
|
||||
private static final long ANIMATION_START = -1;
|
||||
private static final long NO_ANIMATION = -2;
|
||||
|
||||
private long mStartTime = NO_ANIMATION;
|
||||
private int mDuration;
|
||||
private Interpolator mInterpolator;
|
||||
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
public void setDuration(int duration) {
|
||||
mDuration = duration;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
mStartTime = ANIMATION_START;
|
||||
}
|
||||
|
||||
public void setStartTime(long time) {
|
||||
mStartTime = time;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return mStartTime != NO_ANIMATION;
|
||||
}
|
||||
|
||||
public void forceStop() {
|
||||
mStartTime = NO_ANIMATION;
|
||||
}
|
||||
|
||||
public boolean calculate(long currentTimeMillis) {
|
||||
if (mStartTime == NO_ANIMATION) return false;
|
||||
if (mStartTime == ANIMATION_START) mStartTime = currentTimeMillis;
|
||||
int elapse = (int) (currentTimeMillis - mStartTime);
|
||||
float x = MathUtils.clamp((float) elapse / mDuration, 0f, 1f);
|
||||
Interpolator i = mInterpolator;
|
||||
onCalculate(i != null ? i.getInterpolation(x) : x);
|
||||
if (elapse >= mDuration) mStartTime = NO_ANIMATION;
|
||||
return mStartTime != NO_ANIMATION;
|
||||
}
|
||||
|
||||
abstract protected void onCalculate(float progress);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.anim;
|
||||
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
|
||||
public abstract class CanvasAnimation extends Animation {
|
||||
|
||||
public abstract int getCanvasSaveFlags();
|
||||
public abstract void apply(GLCanvas canvas);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.anim;
|
||||
|
||||
public class FloatAnimation extends Animation {
|
||||
|
||||
private final float mFrom;
|
||||
private final float mTo;
|
||||
private float mCurrent;
|
||||
|
||||
public FloatAnimation(float from, float to, int duration) {
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
mCurrent = from;
|
||||
setDuration(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
mCurrent = mFrom + (mTo - mFrom) * progress;
|
||||
}
|
||||
|
||||
public float get() {
|
||||
return mCurrent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.anim;
|
||||
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.RawTexture;
|
||||
import com.hippo.ehviewer.gallery.ui.GLView;
|
||||
|
||||
public class StateTransitionAnimation extends Animation {
|
||||
|
||||
public static class Spec {
|
||||
public static final Spec OUTGOING;
|
||||
public static final Spec INCOMING;
|
||||
public static final Spec PHOTO_INCOMING;
|
||||
|
||||
private static final Interpolator DEFAULT_INTERPOLATOR =
|
||||
new DecelerateInterpolator();
|
||||
|
||||
public int duration = 330;
|
||||
public float backgroundAlphaFrom = 0;
|
||||
public float backgroundAlphaTo = 0;
|
||||
public float backgroundScaleFrom = 0;
|
||||
public float backgroundScaleTo = 0;
|
||||
public float contentAlphaFrom = 1;
|
||||
public float contentAlphaTo = 1;
|
||||
public float contentScaleFrom = 1;
|
||||
public float contentScaleTo = 1;
|
||||
public float overlayAlphaFrom = 0;
|
||||
public float overlayAlphaTo = 0;
|
||||
public float overlayScaleFrom = 0;
|
||||
public float overlayScaleTo = 0;
|
||||
public Interpolator interpolator = DEFAULT_INTERPOLATOR;
|
||||
|
||||
static {
|
||||
OUTGOING = new Spec();
|
||||
OUTGOING.backgroundAlphaFrom = 0.5f;
|
||||
OUTGOING.backgroundAlphaTo = 0f;
|
||||
OUTGOING.backgroundScaleFrom = 1f;
|
||||
OUTGOING.backgroundScaleTo = 0f;
|
||||
OUTGOING.contentAlphaFrom = 0.5f;
|
||||
OUTGOING.contentAlphaTo = 1f;
|
||||
OUTGOING.contentScaleFrom = 3f;
|
||||
OUTGOING.contentScaleTo = 1f;
|
||||
|
||||
INCOMING = new Spec();
|
||||
INCOMING.overlayAlphaFrom = 1f;
|
||||
INCOMING.overlayAlphaTo = 0f;
|
||||
INCOMING.overlayScaleFrom = 1f;
|
||||
INCOMING.overlayScaleTo = 3f;
|
||||
INCOMING.contentAlphaFrom = 0f;
|
||||
INCOMING.contentAlphaTo = 1f;
|
||||
INCOMING.contentScaleFrom = 0.25f;
|
||||
INCOMING.contentScaleTo = 1f;
|
||||
|
||||
PHOTO_INCOMING = INCOMING;
|
||||
}
|
||||
|
||||
private static Spec specForTransition(Transition t) {
|
||||
switch (t) {
|
||||
case Outgoing:
|
||||
return Spec.OUTGOING;
|
||||
case Incoming:
|
||||
return Spec.INCOMING;
|
||||
case PhotoIncoming:
|
||||
return Spec.PHOTO_INCOMING;
|
||||
case None:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Transition { None, Outgoing, Incoming, PhotoIncoming }
|
||||
|
||||
private final Spec mTransitionSpec;
|
||||
private float mCurrentContentScale;
|
||||
private float mCurrentContentAlpha;
|
||||
private float mCurrentBackgroundScale;
|
||||
private float mCurrentBackgroundAlpha;
|
||||
private float mCurrentOverlayScale;
|
||||
private float mCurrentOverlayAlpha;
|
||||
private RawTexture mOldScreenTexture;
|
||||
|
||||
public StateTransitionAnimation(Transition t, RawTexture oldScreen) {
|
||||
this(Spec.specForTransition(t), oldScreen);
|
||||
}
|
||||
|
||||
public StateTransitionAnimation(Spec spec, RawTexture oldScreen) {
|
||||
mTransitionSpec = spec != null ? spec : Spec.OUTGOING;
|
||||
setDuration(mTransitionSpec.duration);
|
||||
setInterpolator(mTransitionSpec.interpolator);
|
||||
mOldScreenTexture = oldScreen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean calculate(long currentTimeMillis) {
|
||||
boolean retval = super.calculate(currentTimeMillis);
|
||||
if (!isActive()) {
|
||||
if (mOldScreenTexture != null) {
|
||||
mOldScreenTexture.recycle();
|
||||
mOldScreenTexture = null;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
mCurrentContentScale = mTransitionSpec.contentScaleFrom
|
||||
+ (mTransitionSpec.contentScaleTo - mTransitionSpec.contentScaleFrom) * progress;
|
||||
mCurrentContentAlpha = mTransitionSpec.contentAlphaFrom
|
||||
+ (mTransitionSpec.contentAlphaTo - mTransitionSpec.contentAlphaFrom) * progress;
|
||||
mCurrentBackgroundAlpha = mTransitionSpec.backgroundAlphaFrom
|
||||
+ (mTransitionSpec.backgroundAlphaTo - mTransitionSpec.backgroundAlphaFrom)
|
||||
* progress;
|
||||
mCurrentBackgroundScale = mTransitionSpec.backgroundScaleFrom
|
||||
+ (mTransitionSpec.backgroundScaleTo - mTransitionSpec.backgroundScaleFrom)
|
||||
* progress;
|
||||
mCurrentOverlayScale = mTransitionSpec.overlayScaleFrom
|
||||
+ (mTransitionSpec.overlayScaleTo - mTransitionSpec.overlayScaleFrom) * progress;
|
||||
mCurrentOverlayAlpha = mTransitionSpec.overlayAlphaFrom
|
||||
+ (mTransitionSpec.overlayAlphaTo - mTransitionSpec.overlayAlphaFrom) * progress;
|
||||
}
|
||||
|
||||
private void applyOldTexture(GLView view, GLCanvas canvas, float alpha, float scale, boolean clear) {
|
||||
if (mOldScreenTexture == null)
|
||||
return;
|
||||
if (clear) canvas.clearBuffer(view.getBackgroundColor());
|
||||
canvas.save();
|
||||
canvas.setAlpha(alpha);
|
||||
int xOffset = view.getWidth() / 2;
|
||||
int yOffset = view.getHeight() / 2;
|
||||
canvas.translate(xOffset, yOffset);
|
||||
canvas.scale(scale, scale, 1);
|
||||
mOldScreenTexture.draw(canvas, -xOffset, -yOffset);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public void applyBackground(GLView view, GLCanvas canvas) {
|
||||
if (mCurrentBackgroundAlpha > 0f) {
|
||||
applyOldTexture(view, canvas, mCurrentBackgroundAlpha, mCurrentBackgroundScale, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyContentTransform(GLView view, GLCanvas canvas) {
|
||||
int xOffset = view.getWidth() / 2;
|
||||
int yOffset = view.getHeight() / 2;
|
||||
canvas.translate(xOffset, yOffset);
|
||||
canvas.scale(mCurrentContentScale, mCurrentContentScale, 1);
|
||||
canvas.translate(-xOffset, -yOffset);
|
||||
canvas.setAlpha(mCurrentContentAlpha);
|
||||
}
|
||||
|
||||
public void applyOverlay(GLView view, GLCanvas canvas) {
|
||||
if (mCurrentOverlayAlpha > 0f) {
|
||||
applyOldTexture(view, canvas, mCurrentOverlayAlpha, mCurrentOverlayScale, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
// BasicTexture is a Texture corresponds to a real GL texture.
|
||||
// The state of a BasicTexture indicates whether its data is loaded to GL memory.
|
||||
// If a BasicTexture is loaded into GL memory, it has a GL texture id.
|
||||
public abstract class BasicTexture implements Texture {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "BasicTexture";
|
||||
protected static final int UNSPECIFIED = -1;
|
||||
|
||||
protected static final int STATE_UNLOADED = 0;
|
||||
protected static final int STATE_LOADED = 1;
|
||||
protected static final int STATE_ERROR = -1;
|
||||
|
||||
// Log a warning if a texture is larger along a dimension
|
||||
private static final int MAX_TEXTURE_SIZE = 4096;
|
||||
|
||||
protected int mId = -1;
|
||||
protected int mState;
|
||||
|
||||
protected int mWidth = UNSPECIFIED;
|
||||
protected int mHeight = UNSPECIFIED;
|
||||
|
||||
protected int mTextureWidth;
|
||||
protected int mTextureHeight;
|
||||
|
||||
private boolean mHasBorder;
|
||||
|
||||
protected GLCanvas mCanvasRef = null;
|
||||
private final static WeakHashMap<BasicTexture, Object> sAllTextures
|
||||
= new WeakHashMap<BasicTexture, Object>();
|
||||
private static ThreadLocal sInFinalizer = new ThreadLocal();
|
||||
|
||||
protected BasicTexture(GLCanvas canvas, int id, int state) {
|
||||
setAssociatedCanvas(canvas);
|
||||
mId = id;
|
||||
mState = state;
|
||||
synchronized (sAllTextures) {
|
||||
sAllTextures.put(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
protected BasicTexture() {
|
||||
this(null, 0, STATE_UNLOADED);
|
||||
}
|
||||
|
||||
protected void setAssociatedCanvas(GLCanvas canvas) {
|
||||
mCanvasRef = canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content size of this texture. In OpenGL, the actual texture
|
||||
* size must be of power of 2, the size of the content may be smaller.
|
||||
*/
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mTextureWidth = width > 0 ? MathUtils.nextPowerOf2(width) : 0;
|
||||
mTextureHeight = height > 0 ? MathUtils.nextPowerOf2(height) : 0;
|
||||
if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
|
||||
Log.w(TAG, String.format("texture is too large: %d x %d",
|
||||
mTextureWidth, mTextureHeight), new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFlippedVertically() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
// Returns the width rounded to the next power of 2.
|
||||
public int getTextureWidth() {
|
||||
return mTextureWidth;
|
||||
}
|
||||
|
||||
// Returns the height rounded to the next power of 2.
|
||||
public int getTextureHeight() {
|
||||
return mTextureHeight;
|
||||
}
|
||||
|
||||
// Returns true if the texture has one pixel transparent border around the
|
||||
// actual content. This is used to avoid jigged edges.
|
||||
//
|
||||
// The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
|
||||
// mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
|
||||
// covered by the texture will use the color of the edge texel. If we add
|
||||
// the transparent border, the color of the edge texel will be mixed with
|
||||
// appropriate amount of transparent.
|
||||
//
|
||||
// Currently our background is black, so we can draw the thumbnails without
|
||||
// enabling blending.
|
||||
public boolean hasBorder() {
|
||||
return mHasBorder;
|
||||
}
|
||||
|
||||
protected void setBorder(boolean hasBorder) {
|
||||
mHasBorder = hasBorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
canvas.drawTexture(this, x, y, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
canvas.drawTexture(this, x, y, w, h);
|
||||
}
|
||||
|
||||
// onBind is called before GLCanvas binds this texture.
|
||||
// It should make sure the data is uploaded to GL memory.
|
||||
abstract protected boolean onBind(GLCanvas canvas);
|
||||
|
||||
// Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
|
||||
abstract protected int getTarget();
|
||||
|
||||
public boolean isLoaded() {
|
||||
return mState == STATE_LOADED;
|
||||
}
|
||||
|
||||
// recycle() is called when the texture will never be used again,
|
||||
// so it can free all resources.
|
||||
public void recycle() {
|
||||
freeResource();
|
||||
}
|
||||
|
||||
// yield() is called when the texture will not be used temporarily,
|
||||
// so it can free some resources.
|
||||
// The default implementation unloads the texture from GL memory, so
|
||||
// the subclass should make sure it can reload the texture to GL memory
|
||||
// later, or it will have to override this method.
|
||||
public void yield() {
|
||||
freeResource();
|
||||
}
|
||||
|
||||
private void freeResource() {
|
||||
GLCanvas canvas = mCanvasRef;
|
||||
if (canvas != null && mId != -1) {
|
||||
canvas.unloadTexture(this);
|
||||
mId = -1; // Don't free it again.
|
||||
}
|
||||
mState = STATE_UNLOADED;
|
||||
setAssociatedCanvas(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() {
|
||||
sInFinalizer.set(BasicTexture.class);
|
||||
recycle();
|
||||
sInFinalizer.set(null);
|
||||
}
|
||||
|
||||
// This is for deciding if we can call Bitmap's recycle().
|
||||
// We cannot call Bitmap's recycle() in finalizer because at that point
|
||||
// the finalizer of Bitmap may already be called so recycle() will crash.
|
||||
public static boolean inFinalizer() {
|
||||
return sInFinalizer.get() != null;
|
||||
}
|
||||
|
||||
public static void yieldAllTextures() {
|
||||
synchronized (sAllTextures) {
|
||||
for (BasicTexture t : sAllTextures.keySet()) {
|
||||
t.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void invalidateAllTextures() {
|
||||
synchronized (sAllTextures) {
|
||||
for (BasicTexture t : sAllTextures.keySet()) {
|
||||
t.mState = STATE_UNLOADED;
|
||||
t.setAssociatedCanvas(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
|
||||
//
|
||||
// The texture does not own the Bitmap. The user should make sure the Bitmap
|
||||
// is valid during the texture's lifetime. When the texture is recycled, it
|
||||
// does not free the Bitmap.
|
||||
public class BitmapTexture extends UploadedTexture {
|
||||
protected Bitmap mContentBitmap;
|
||||
|
||||
public BitmapTexture(Bitmap bitmap) {
|
||||
this(bitmap, false);
|
||||
}
|
||||
|
||||
public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
|
||||
super(hasBorder);
|
||||
Assert.assertTrue(bitmap != null && !bitmap.isRecycled());
|
||||
mContentBitmap = bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
return mContentBitmap;
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
return mContentBitmap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
|
||||
// CanvasTexture is a texture whose content is the drawing on a Canvas.
|
||||
// The subclasses should override onDraw() to draw on the bitmap.
|
||||
// By default CanvasTexture is not opaque.
|
||||
abstract class CanvasTexture extends UploadedTexture {
|
||||
protected Canvas mCanvas;
|
||||
private final Config mConfig;
|
||||
|
||||
public CanvasTexture(int width, int height) {
|
||||
mConfig = Config.ARGB_8888;
|
||||
setSize(width, height);
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig);
|
||||
mCanvas = new Canvas(bitmap);
|
||||
onDraw(mCanvas, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
if (!inFinalizer()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected void onDraw(Canvas canvas, Bitmap backing);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import com.hippo.yorozuya.ColorUtils;
|
||||
|
||||
// ColorTexture is a texture which fills the rectangle with the specified color.
|
||||
public class ColorTexture implements Texture {
|
||||
|
||||
private final int mColor;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
|
||||
public ColorTexture(int color) {
|
||||
mColor = color;
|
||||
mWidth = 1;
|
||||
mHeight = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
canvas.fillRect(x, y, w, h, mColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return ColorUtils.isOpaque(mColor);
|
||||
}
|
||||
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
// ExtTexture is a texture whose content comes from a external texture.
|
||||
// Before drawing, setSize() should be called.
|
||||
public class ExtTexture extends BasicTexture {
|
||||
|
||||
private int mTarget;
|
||||
|
||||
public ExtTexture(GLCanvas canvas, int target) {
|
||||
GLId glId = canvas.getGLId();
|
||||
mId = glId.generateTexture();
|
||||
mTarget = target;
|
||||
}
|
||||
|
||||
private void uploadToCanvas(GLCanvas canvas) {
|
||||
canvas.setTextureParameters(this);
|
||||
setAssociatedCanvas(canvas);
|
||||
mState = STATE_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
if (!isLoaded()) {
|
||||
uploadToCanvas(canvas);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTarget() {
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void yield() {
|
||||
// we cannot free the texture because we have no backup.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
|
||||
// FadeInTexture is a texture which begins with a color, then gradually animates
|
||||
// into a given texture.
|
||||
public class FadeInTexture extends FadeTexture implements Texture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "FadeInTexture";
|
||||
|
||||
private final int mColor;
|
||||
private final TiledTexture mTexture;
|
||||
|
||||
public FadeInTexture(int color, TiledTexture texture) {
|
||||
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
|
||||
mColor = color;
|
||||
mTexture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (isAnimating()) {
|
||||
mTexture.drawMixed(canvas, mColor, getRatio(), x, y, w, h);
|
||||
} else {
|
||||
mTexture.draw(canvas, x, y, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
|
||||
// FadeOutTexture is a texture which begins with a given texture, then gradually animates
|
||||
// into fading out totally.
|
||||
public class FadeOutTexture extends FadeTexture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "FadeOutTexture";
|
||||
|
||||
private final BasicTexture mTexture;
|
||||
|
||||
public FadeOutTexture(BasicTexture texture) {
|
||||
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
|
||||
mTexture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (isAnimating()) {
|
||||
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
|
||||
canvas.setAlpha(getRatio());
|
||||
mTexture.draw(canvas, x, y, w, h);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import com.hippo.ehviewer.gallery.ui.AnimationTime;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
// FadeTexture is a texture which fades the given texture along the time.
|
||||
public abstract class FadeTexture implements Texture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "FadeTexture";
|
||||
|
||||
// The duration of the fading animation in milliseconds
|
||||
public static final int DURATION = 180;
|
||||
|
||||
private final long mStartTime;
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
private final boolean mIsOpaque;
|
||||
private boolean mIsAnimating;
|
||||
|
||||
public FadeTexture(int width, int height, boolean opaque) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mIsOpaque = opaque;
|
||||
mStartTime = now();
|
||||
mIsAnimating = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mIsOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public boolean isAnimating() {
|
||||
if (mIsAnimating) {
|
||||
if (now() - mStartTime >= DURATION) {
|
||||
mIsAnimating = false;
|
||||
}
|
||||
}
|
||||
return mIsAnimating;
|
||||
}
|
||||
|
||||
protected float getRatio() {
|
||||
float r = (float)(now() - mStartTime) / DURATION;
|
||||
return MathUtils.clamp(1.0f - r, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
private long now() {
|
||||
return AnimationTime.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
//
|
||||
// GLCanvas gives a convenient interface to draw using OpenGL.
|
||||
//
|
||||
// When a rectangle is specified in this interface, it means the region
|
||||
// [x, x+width) * [y, y+height)
|
||||
//
|
||||
public interface GLCanvas {
|
||||
|
||||
GLId getGLId();
|
||||
|
||||
// Tells GLCanvas the size of the underlying GL surface. This should be
|
||||
// called before first drawing and when the size of GL surface is changed.
|
||||
// This is called by GLRoot and should not be called by the clients
|
||||
// who only want to draw on the GLCanvas. Both width and height must be
|
||||
// nonnegative.
|
||||
void setSize(int width, int height);
|
||||
|
||||
// Clear the drawing buffers. This should only be used by GLRoot.
|
||||
void clearBuffer();
|
||||
|
||||
void clearBuffer(float[] argb);
|
||||
|
||||
// Sets and gets the current alpha, alpha must be in [0, 1].
|
||||
void setAlpha(float alpha);
|
||||
|
||||
float getAlpha();
|
||||
|
||||
// (current alpha) = (current alpha) * alpha
|
||||
void multiplyAlpha(float alpha);
|
||||
|
||||
// Change the current transform matrix.
|
||||
void translate(float x, float y, float z);
|
||||
|
||||
void translate(float x, float y);
|
||||
|
||||
void scale(float sx, float sy, float sz);
|
||||
|
||||
void rotate(float angle, float x, float y, float z);
|
||||
|
||||
void multiplyMatrix(float[] mMatrix, int offset);
|
||||
|
||||
// Pushes the configuration state (matrix, and alpha) onto
|
||||
// a private stack.
|
||||
void save();
|
||||
|
||||
// Same as save(), but only save those specified in saveFlags.
|
||||
void save(int saveFlags);
|
||||
|
||||
int SAVE_FLAG_ALL = 0xFFFFFFFF;
|
||||
int SAVE_FLAG_ALPHA = 0x01;
|
||||
int SAVE_FLAG_MATRIX = 0x02;
|
||||
|
||||
// Pops from the top of the stack as current configuration state (matrix,
|
||||
// alpha, and clip). This call balances a previous call to save(), and is
|
||||
// used to remove all modifications to the configuration state since the
|
||||
// last save call.
|
||||
void restore();
|
||||
|
||||
// Draws a line using the specified paint from (x1, y1) to (x2, y2).
|
||||
// (Both end points are included).
|
||||
void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
|
||||
|
||||
// Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
|
||||
// (Both end points are included).
|
||||
void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
|
||||
|
||||
// Fills the specified rectangle with the specified color.
|
||||
void fillRect(float x, float y, float width, float height, int color);
|
||||
|
||||
// Draws a texture to the specified rectangle.
|
||||
void drawTexture(BasicTexture texture, int x, int y, int width, int height);
|
||||
|
||||
void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, int uvBuffer, int indexBuffer, int indexCount);
|
||||
|
||||
// Draws the source rectangle part of the texture to the target rectangle.
|
||||
void drawTexture(BasicTexture texture, RectF source, RectF target);
|
||||
|
||||
// Draw a texture with a specified texture transform.
|
||||
void drawTexture(BasicTexture texture, float[] mTextureTransform, int x, int y, int w, int h);
|
||||
|
||||
// Draw two textures to the specified rectangle. The actual texture used is
|
||||
// from * (1 - ratio) + to * ratio
|
||||
// The two textures must have the same size.
|
||||
void drawMixed(BasicTexture from, int toColor, float ratio, int x, int y, int w, int h);
|
||||
|
||||
// Draw a region of a texture and a specified color to the specified
|
||||
// rectangle. The actual color used is from * (1 - ratio) + to * ratio.
|
||||
// The region of the texture is defined by parameter "src". The target
|
||||
// rectangle is specified by parameter "target".
|
||||
void drawMixed(BasicTexture from, int toColor, float ratio, RectF src, RectF target);
|
||||
|
||||
// Unloads the specified texture from the canvas. The resource allocated
|
||||
// to draw the texture will be released. The specified texture will return
|
||||
// to the unloaded state. This function should be called only from
|
||||
// BasicTexture or its descendant
|
||||
boolean unloadTexture(BasicTexture texture);
|
||||
|
||||
// Delete the specified buffer object, similar to unloadTexture.
|
||||
void deleteBuffer(int bufferId);
|
||||
|
||||
// Delete the textures and buffers in GL side. This function should only be
|
||||
// called in the GL thread.
|
||||
void deleteRecycledResources();
|
||||
|
||||
// Dump statistics information and clear the counters. For debug only.
|
||||
void dumpStatisticsAndClear();
|
||||
|
||||
void beginRenderTarget(RawTexture texture);
|
||||
|
||||
void endRenderTarget();
|
||||
|
||||
/**
|
||||
* Sets texture parameters to use GL_CLAMP_TO_EDGE for both
|
||||
* GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
|
||||
* GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
|
||||
* bindTexture() must be called prior to this.
|
||||
*
|
||||
* @param texture The texture to set parameters on.
|
||||
*/
|
||||
void setTextureParameters(BasicTexture texture);
|
||||
|
||||
/**
|
||||
* Initializes the texture to a size by calling texImage2D on it.
|
||||
*
|
||||
* @param texture The texture to initialize the size.
|
||||
* @param format The texture format (e.g. GL_RGBA)
|
||||
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
|
||||
*/
|
||||
void initializeTextureSize(BasicTexture texture, int format, int type);
|
||||
|
||||
/**
|
||||
* Initializes the texture to a size by calling texImage2D on it.
|
||||
*
|
||||
* @param texture The texture to initialize the size.
|
||||
* @param bitmap The bitmap to initialize the bitmap with.
|
||||
*/
|
||||
void initializeTexture(BasicTexture texture, Bitmap bitmap);
|
||||
|
||||
/**
|
||||
* Calls glTexSubImage2D to upload a bitmap to the texture.
|
||||
*
|
||||
* @param texture The target texture to write to.
|
||||
* @param xOffset Specifies a texel offset in the x direction within the
|
||||
* texture array.
|
||||
* @param yOffset Specifies a texel offset in the y direction within the
|
||||
* texture array.
|
||||
* @param format The texture format (e.g. GL_RGBA)
|
||||
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
|
||||
*/
|
||||
void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, int format, int type);
|
||||
|
||||
/**
|
||||
* Generates buffers and uploads the buffer data.
|
||||
*
|
||||
* @param buffer The buffer to upload
|
||||
* @return The buffer ID that was generated.
|
||||
*/
|
||||
int uploadBuffer(java.nio.FloatBuffer buffer);
|
||||
|
||||
/**
|
||||
* Generates buffers and uploads the element array buffer data.
|
||||
*
|
||||
* @param buffer The buffer to upload
|
||||
* @return The buffer ID that was generated.
|
||||
*/
|
||||
int uploadBuffer(java.nio.ByteBuffer buffer);
|
||||
|
||||
/**
|
||||
* After LightCycle makes GL calls, this method is called to restore the GL
|
||||
* configuration to the one expected by GLCanvas.
|
||||
*/
|
||||
void recoverFromLightCycle();
|
||||
|
||||
/**
|
||||
* Gets the bounds given by x, y, width, and height as well as the internal
|
||||
* matrix state. There is no special handling for non-90-degree rotations.
|
||||
* It only considers the lower-left and upper-right corners as the bounds.
|
||||
*
|
||||
* @param bounds The output bounds to write to.
|
||||
* @param x The left side of the input rectangle.
|
||||
* @param y The bottom of the input rectangle.
|
||||
* @param width The width of the input rectangle.
|
||||
* @param height The height of the input rectangle.
|
||||
*/
|
||||
void getBounds(Rect bounds, int x, int y, int width, int height);
|
||||
}
|
||||
@@ -0,0 +1,997 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.opengl.GLU;
|
||||
import android.opengl.GLUtils;
|
||||
import android.opengl.Matrix;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.gallery.util.IntArray;
|
||||
import com.hippo.yorozuya.ColorUtils;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11Ext;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
public class GLES11Canvas implements GLCanvas {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "GLCanvasImp";
|
||||
|
||||
private static final float OPAQUE_ALPHA = 0.95f;
|
||||
|
||||
private static final int OFFSET_FILL_RECT = 0;
|
||||
private static final int OFFSET_DRAW_LINE = 4;
|
||||
private static final int OFFSET_DRAW_RECT = 6;
|
||||
private static final float[] BOX_COORDINATES = {
|
||||
0, 0, 1, 0, 0, 1, 1, 1, // used for filling a rectangle
|
||||
0, 0, 1, 1, // used for drawing a line
|
||||
0, 0, 0, 1, 1, 1, 1, 0}; // used for drawing the outline of a rectangle
|
||||
|
||||
private GL11 mGL;
|
||||
|
||||
private final float mMatrixValues[] = new float[16];
|
||||
private final float mTextureMatrixValues[] = new float[16];
|
||||
|
||||
// The results of mapPoints are stored in this buffer, and the order is
|
||||
// x1, y1, x2, y2.
|
||||
private final float mMapPointsBuffer[] = new float[4];
|
||||
|
||||
private final float mTextureColor[] = new float[4];
|
||||
|
||||
private int mBoxCoords;
|
||||
|
||||
private GLState mGLState;
|
||||
private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
|
||||
|
||||
private float mAlpha;
|
||||
private final ArrayList<ConfigState> mRestoreStack = new ArrayList<ConfigState>();
|
||||
private ConfigState mRecycledRestoreAction;
|
||||
|
||||
private final RectF mDrawTextureSourceRect = new RectF();
|
||||
private final RectF mDrawTextureTargetRect = new RectF();
|
||||
private final float[] mTempMatrix = new float[32];
|
||||
private final IntArray mUnboundTextures = new IntArray();
|
||||
private final IntArray mDeleteBuffers = new IntArray();
|
||||
private int mScreenWidth;
|
||||
private int mScreenHeight;
|
||||
private boolean mBlendEnabled = true;
|
||||
private int mFrameBuffer[] = new int[1];
|
||||
private static float[] sCropRect = new float[4];
|
||||
|
||||
private RawTexture mTargetTexture;
|
||||
|
||||
// Drawing statistics
|
||||
int mCountDrawLine;
|
||||
int mCountFillRect;
|
||||
int mCountDrawMesh;
|
||||
int mCountTextureRect;
|
||||
int mCountTextureOES;
|
||||
|
||||
private static GLId mGLId = new GLES11IdImpl();
|
||||
|
||||
public GLES11Canvas(GL11 gl) {
|
||||
mGL = gl;
|
||||
mGLState = new GLState(gl);
|
||||
// First create an nio buffer, then create a VBO from it.
|
||||
int size = BOX_COORDINATES.length * Float.SIZE / Byte.SIZE;
|
||||
FloatBuffer xyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
|
||||
xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0);
|
||||
|
||||
int[] name = new int[1];
|
||||
mGLId.glGenBuffers(1, name, 0);
|
||||
mBoxCoords = name[0];
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords);
|
||||
gl.glBufferData(GL11.GL_ARRAY_BUFFER, xyBuffer.capacity() * (Float.SIZE / Byte.SIZE),
|
||||
xyBuffer, GL11.GL_STATIC_DRAW);
|
||||
|
||||
gl.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
|
||||
// Enable the texture coordinate array for Texture 1
|
||||
gl.glClientActiveTexture(GL11.GL_TEXTURE1);
|
||||
gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
gl.glClientActiveTexture(GL11.GL_TEXTURE0);
|
||||
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
// mMatrixValues and mAlpha will be initialized in setSize()
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSize(int width, int height) {
|
||||
Assert.assertTrue(width >= 0 && height >= 0);
|
||||
|
||||
if (mTargetTexture == null) {
|
||||
mScreenWidth = width;
|
||||
mScreenHeight = height;
|
||||
}
|
||||
mAlpha = 1.0f;
|
||||
|
||||
GL11 gl = mGL;
|
||||
gl.glViewport(0, 0, width, height);
|
||||
gl.glMatrixMode(GL11.GL_PROJECTION);
|
||||
gl.glLoadIdentity();
|
||||
GLU.gluOrtho2D(gl, 0, width, 0, height);
|
||||
|
||||
gl.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
gl.glLoadIdentity();
|
||||
|
||||
float matrix[] = mMatrixValues;
|
||||
Matrix.setIdentityM(matrix, 0);
|
||||
// to match the graphic coordinate system in android, we flip it vertically.
|
||||
if (mTargetTexture == null) {
|
||||
Matrix.translateM(matrix, 0, 0, height, 0);
|
||||
Matrix.scaleM(matrix, 0, 1, -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(float alpha) {
|
||||
Assert.assertTrue(alpha >= 0 && alpha <= 1);
|
||||
mAlpha = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAlpha() {
|
||||
return mAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multiplyAlpha(float alpha) {
|
||||
Assert.assertTrue(alpha >= 0 && alpha <= 1);
|
||||
mAlpha *= alpha;
|
||||
}
|
||||
|
||||
private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
|
||||
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawRect(float x, float y, float width, float height, GLPaint paint) {
|
||||
GL11 gl = mGL;
|
||||
|
||||
mGLState.setColorMode(paint.getColor(), mAlpha);
|
||||
mGLState.setLineWidth(paint.getLineWidth());
|
||||
|
||||
saveTransform();
|
||||
translate(x, y);
|
||||
scale(width, height, 1);
|
||||
|
||||
gl.glLoadMatrixf(mMatrixValues, 0);
|
||||
gl.glDrawArrays(GL11.GL_LINE_LOOP, OFFSET_DRAW_RECT, 4);
|
||||
|
||||
restoreTransform();
|
||||
mCountDrawLine++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {
|
||||
GL11 gl = mGL;
|
||||
|
||||
mGLState.setColorMode(paint.getColor(), mAlpha);
|
||||
mGLState.setLineWidth(paint.getLineWidth());
|
||||
|
||||
saveTransform();
|
||||
translate(x1, y1);
|
||||
scale(x2 - x1, y2 - y1, 1);
|
||||
|
||||
gl.glLoadMatrixf(mMatrixValues, 0);
|
||||
gl.glDrawArrays(GL11.GL_LINE_STRIP, OFFSET_DRAW_LINE, 2);
|
||||
|
||||
restoreTransform();
|
||||
mCountDrawLine++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillRect(float x, float y, float width, float height, int color) {
|
||||
mGLState.setColorMode(color, mAlpha);
|
||||
GL11 gl = mGL;
|
||||
|
||||
saveTransform();
|
||||
translate(x, y);
|
||||
scale(width, height, 1);
|
||||
|
||||
gl.glLoadMatrixf(mMatrixValues, 0);
|
||||
gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
|
||||
|
||||
restoreTransform();
|
||||
mCountFillRect++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translate(float x, float y, float z) {
|
||||
Matrix.translateM(mMatrixValues, 0, x, y, z);
|
||||
}
|
||||
|
||||
// This is a faster version of translate(x, y, z) because
|
||||
// (1) we knows z = 0, (2) we inline the Matrix.translateM call,
|
||||
// (3) we unroll the loop
|
||||
@Override
|
||||
public void translate(float x, float y) {
|
||||
float[] m = mMatrixValues;
|
||||
m[12] += m[0] * x + m[4] * y;
|
||||
m[13] += m[1] * x + m[5] * y;
|
||||
m[14] += m[2] * x + m[6] * y;
|
||||
m[15] += m[3] * x + m[7] * y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale(float sx, float sy, float sz) {
|
||||
Matrix.scaleM(mMatrixValues, 0, sx, sy, sz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rotate(float angle, float x, float y, float z) {
|
||||
if (angle == 0) return;
|
||||
float[] temp = mTempMatrix;
|
||||
Matrix.setRotateM(temp, 0, angle, x, y, z);
|
||||
Matrix.multiplyMM(temp, 16, mMatrixValues, 0, temp, 0);
|
||||
System.arraycopy(temp, 16, mMatrixValues, 0, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multiplyMatrix(float matrix[], int offset) {
|
||||
float[] temp = mTempMatrix;
|
||||
Matrix.multiplyMM(temp, 0, mMatrixValues, 0, matrix, offset);
|
||||
System.arraycopy(temp, 0, mMatrixValues, 0, 16);
|
||||
}
|
||||
|
||||
private void textureRect(float x, float y, float width, float height) {
|
||||
GL11 gl = mGL;
|
||||
|
||||
saveTransform();
|
||||
translate(x, y);
|
||||
scale(width, height, 1);
|
||||
|
||||
gl.glLoadMatrixf(mMatrixValues, 0);
|
||||
gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
|
||||
|
||||
restoreTransform();
|
||||
mCountTextureRect++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
|
||||
int uvBuffer, int indexBuffer, int indexCount) {
|
||||
float alpha = mAlpha;
|
||||
if (!bindTexture(tex)) return;
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled
|
||||
&& (!tex.isOpaque() || alpha < OPAQUE_ALPHA));
|
||||
mGLState.setTextureAlpha(alpha);
|
||||
|
||||
// Reset the texture matrix. We will set our own texture coordinates
|
||||
// below.
|
||||
setTextureCoords(0, 0, 1, 1);
|
||||
|
||||
saveTransform();
|
||||
translate(x, y);
|
||||
|
||||
mGL.glLoadMatrixf(mMatrixValues, 0);
|
||||
|
||||
mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, xyBuffer);
|
||||
mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
|
||||
mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, uvBuffer);
|
||||
mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
|
||||
mGL.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||
mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP,
|
||||
indexCount, GL11.GL_UNSIGNED_BYTE, 0);
|
||||
|
||||
mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords);
|
||||
mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
|
||||
restoreTransform();
|
||||
mCountDrawMesh++;
|
||||
}
|
||||
|
||||
// Transforms two points by the given matrix m. The result
|
||||
// {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned.
|
||||
private float[] mapPoints(float m[], int x1, int y1, int x2, int y2) {
|
||||
float[] r = mMapPointsBuffer;
|
||||
|
||||
// Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused.
|
||||
float x3 = m[0] * x1 + m[4] * y1 + m[12];
|
||||
float y3 = m[1] * x1 + m[5] * y1 + m[13];
|
||||
float w3 = m[3] * x1 + m[7] * y1 + m[15];
|
||||
r[0] = x3 / w3;
|
||||
r[1] = y3 / w3;
|
||||
|
||||
// Same for x2 y2.
|
||||
float x4 = m[0] * x2 + m[4] * y2 + m[12];
|
||||
float y4 = m[1] * x2 + m[5] * y2 + m[13];
|
||||
float w4 = m[3] * x2 + m[7] * y2 + m[15];
|
||||
r[2] = x4 / w4;
|
||||
r[3] = y4 / w4;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
private void drawBoundTexture(
|
||||
BasicTexture texture, int x, int y, int width, int height) {
|
||||
// Test whether it has been rotated or flipped, if so, glDrawTexiOES
|
||||
// won't work
|
||||
if (isMatrixRotatedOrFlipped(mMatrixValues)) {
|
||||
if (texture.hasBorder()) {
|
||||
setTextureCoords(
|
||||
1.0f / texture.getTextureWidth(),
|
||||
1.0f / texture.getTextureHeight(),
|
||||
(texture.getWidth() - 1.0f) / texture.getTextureWidth(),
|
||||
(texture.getHeight() - 1.0f) / texture.getTextureHeight());
|
||||
} else {
|
||||
setTextureCoords(0, 0,
|
||||
(float) texture.getWidth() / texture.getTextureWidth(),
|
||||
(float) texture.getHeight() / texture.getTextureHeight());
|
||||
}
|
||||
textureRect(x, y, width, height);
|
||||
} else {
|
||||
// draw the rect from bottom-left to top-right
|
||||
float points[] = mapPoints(
|
||||
mMatrixValues, x, y + height, x + width, y);
|
||||
x = (int) (points[0] + 0.5f);
|
||||
y = (int) (points[1] + 0.5f);
|
||||
width = (int) (points[2] + 0.5f) - x;
|
||||
height = (int) (points[3] + 0.5f) - y;
|
||||
if (width > 0 && height > 0) {
|
||||
((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height);
|
||||
mCountTextureOES++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTexture(
|
||||
BasicTexture texture, int x, int y, int width, int height) {
|
||||
drawTexture(texture, x, y, width, height, mAlpha);
|
||||
}
|
||||
|
||||
private void drawTexture(BasicTexture texture,
|
||||
int x, int y, int width, int height, float alpha) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled
|
||||
&& (!texture.isOpaque() || alpha < OPAQUE_ALPHA));
|
||||
if (!bindTexture(texture)) return;
|
||||
mGLState.setTextureAlpha(alpha);
|
||||
drawBoundTexture(texture, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTexture(BasicTexture texture, RectF source, RectF target) {
|
||||
if (target.width() <= 0 || target.height() <= 0) return;
|
||||
|
||||
// Copy the input to avoid changing it.
|
||||
mDrawTextureSourceRect.set(source);
|
||||
mDrawTextureTargetRect.set(target);
|
||||
source = mDrawTextureSourceRect;
|
||||
target = mDrawTextureTargetRect;
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled
|
||||
&& (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
|
||||
if (!bindTexture(texture)) return;
|
||||
convertCoordinate(source, target, texture);
|
||||
setTextureCoords(source);
|
||||
mGLState.setTextureAlpha(mAlpha);
|
||||
textureRect(target.left, target.top, target.width(), target.height());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTexture(BasicTexture texture, float[] mTextureTransform,
|
||||
int x, int y, int w, int h) {
|
||||
mGLState.setBlendEnabled(mBlendEnabled
|
||||
&& (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
|
||||
if (!bindTexture(texture)) return;
|
||||
setTextureCoords(mTextureTransform);
|
||||
mGLState.setTextureAlpha(mAlpha);
|
||||
textureRect(x, y, w, h);
|
||||
}
|
||||
|
||||
// This function changes the source coordinate to the texture coordinates.
|
||||
// It also clips the source and target coordinates if it is beyond the
|
||||
// bound of the texture.
|
||||
private static void convertCoordinate(RectF source, RectF target,
|
||||
BasicTexture texture) {
|
||||
|
||||
int width = texture.getWidth();
|
||||
int height = texture.getHeight();
|
||||
int texWidth = texture.getTextureWidth();
|
||||
int texHeight = texture.getTextureHeight();
|
||||
// Convert to texture coordinates
|
||||
source.left /= texWidth;
|
||||
source.right /= texWidth;
|
||||
source.top /= texHeight;
|
||||
source.bottom /= texHeight;
|
||||
|
||||
// Clip if the rendering range is beyond the bound of the texture.
|
||||
float xBound = (float) width / texWidth;
|
||||
if (source.right > xBound) {
|
||||
target.right = target.left + target.width() *
|
||||
(xBound - source.left) / source.width();
|
||||
source.right = xBound;
|
||||
}
|
||||
float yBound = (float) height / texHeight;
|
||||
if (source.bottom > yBound) {
|
||||
target.bottom = target.top + target.height() *
|
||||
(yBound - source.top) / source.height();
|
||||
source.bottom = yBound;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawMixed(BasicTexture from,
|
||||
int toColor, float ratio, int x, int y, int w, int h) {
|
||||
drawMixed(from, toColor, ratio, x, y, w, h, mAlpha);
|
||||
}
|
||||
|
||||
private boolean bindTexture(BasicTexture texture) {
|
||||
if (!texture.onBind(this)) return false;
|
||||
int target = texture.getTarget();
|
||||
mGLState.setTextureTarget(target);
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setTextureColor(float r, float g, float b, float alpha) {
|
||||
float[] color = mTextureColor;
|
||||
color[0] = r;
|
||||
color[1] = g;
|
||||
color[2] = b;
|
||||
color[3] = alpha;
|
||||
}
|
||||
|
||||
private void setMixedColor(int toColor, float ratio, float alpha) {
|
||||
//
|
||||
// The formula we want:
|
||||
// alpha * ((1 - ratio) * from + ratio * to)
|
||||
//
|
||||
// The formula that GL supports is in the form of:
|
||||
// combo * from + (1 - combo) * to * scale
|
||||
//
|
||||
// So, we have combo = alpha * (1 - ratio)
|
||||
// and scale = alpha * ratio / (1 - combo)
|
||||
//
|
||||
float combo = alpha * (1 - ratio);
|
||||
float scale = alpha * ratio / (1 - combo);
|
||||
|
||||
// Specify the interpolation factor via the alpha component of
|
||||
// GL_TEXTURE_ENV_COLORs.
|
||||
// RGB component are get from toColor and will used as SRC1
|
||||
float colorScale = scale * (toColor >>> 24) / (0xff * 0xff);
|
||||
setTextureColor(((toColor >>> 16) & 0xff) * colorScale,
|
||||
((toColor >>> 8) & 0xff) * colorScale,
|
||||
(toColor & 0xff) * colorScale, combo);
|
||||
GL11 gl = mGL;
|
||||
gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0);
|
||||
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_RGB, GL11.GL_CONSTANT);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_ALPHA, GL11.GL_CONSTANT);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
|
||||
// Wire up the interpolation factor for RGB.
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA);
|
||||
|
||||
// Wire up the interpolation factor for alpha.
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawMixed(BasicTexture from, int toColor, float ratio,
|
||||
RectF source, RectF target) {
|
||||
if (target.width() <= 0 || target.height() <= 0) return;
|
||||
|
||||
if (ratio <= 0.01f) {
|
||||
drawTexture(from, source, target);
|
||||
return;
|
||||
} else if (ratio >= 1) {
|
||||
fillRect(target.left, target.top, target.width(), target.height(), toColor);
|
||||
return;
|
||||
}
|
||||
|
||||
float alpha = mAlpha;
|
||||
|
||||
// Copy the input to avoid changing it.
|
||||
mDrawTextureSourceRect.set(source);
|
||||
mDrawTextureTargetRect.set(target);
|
||||
source = mDrawTextureSourceRect;
|
||||
target = mDrawTextureTargetRect;
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
|
||||
|| !ColorUtils.isOpaque(toColor) || alpha < OPAQUE_ALPHA));
|
||||
|
||||
if (!bindTexture(from)) return;
|
||||
|
||||
// Interpolate the RGB and alpha values between both textures.
|
||||
mGLState.setTexEnvMode(GL11.GL_COMBINE);
|
||||
setMixedColor(toColor, ratio, alpha);
|
||||
convertCoordinate(source, target, from);
|
||||
setTextureCoords(source);
|
||||
textureRect(target.left, target.top, target.width(), target.height());
|
||||
mGLState.setTexEnvMode(GL11.GL_REPLACE);
|
||||
}
|
||||
|
||||
private void drawMixed(BasicTexture from, int toColor,
|
||||
float ratio, int x, int y, int width, int height, float alpha) {
|
||||
// change from 0 to 0.01f to prevent getting divided by zero below
|
||||
if (ratio <= 0.01f) {
|
||||
drawTexture(from, x, y, width, height, alpha);
|
||||
return;
|
||||
} else if (ratio >= 1) {
|
||||
fillRect(x, y, width, height, toColor);
|
||||
return;
|
||||
}
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
|
||||
|| !ColorUtils.isOpaque(toColor) || alpha < OPAQUE_ALPHA));
|
||||
|
||||
final GL11 gl = mGL;
|
||||
if (!bindTexture(from)) return;
|
||||
|
||||
// Interpolate the RGB and alpha values between both textures.
|
||||
mGLState.setTexEnvMode(GL11.GL_COMBINE);
|
||||
setMixedColor(toColor, ratio, alpha);
|
||||
|
||||
drawBoundTexture(from, x, y, width, height);
|
||||
mGLState.setTexEnvMode(GL11.GL_REPLACE);
|
||||
}
|
||||
|
||||
// TODO: the code only work for 2D should get fixed for 3D or removed
|
||||
private static final int MSKEW_X = 4;
|
||||
private static final int MSKEW_Y = 1;
|
||||
private static final int MSCALE_X = 0;
|
||||
private static final int MSCALE_Y = 5;
|
||||
|
||||
private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
|
||||
final float eps = 1e-5f;
|
||||
return Math.abs(matrix[MSKEW_X]) > eps
|
||||
|| Math.abs(matrix[MSKEW_Y]) > eps
|
||||
|| matrix[MSCALE_X] < -eps
|
||||
|| matrix[MSCALE_Y] > eps;
|
||||
}
|
||||
|
||||
private static class GLState {
|
||||
|
||||
private final GL11 mGL;
|
||||
|
||||
private int mTexEnvMode = GL11.GL_REPLACE;
|
||||
private float mTextureAlpha = 1.0f;
|
||||
private int mTextureTarget = GL11.GL_TEXTURE_2D;
|
||||
private boolean mBlendEnabled = true;
|
||||
private float mLineWidth = 1.0f;
|
||||
private boolean mLineSmooth = false;
|
||||
|
||||
public GLState(GL11 gl) {
|
||||
mGL = gl;
|
||||
|
||||
// Disable unused state
|
||||
gl.glDisable(GL11.GL_LIGHTING);
|
||||
|
||||
// Enable used features
|
||||
gl.glEnable(GL11.GL_DITHER);
|
||||
|
||||
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
|
||||
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
|
||||
gl.glEnable(GL11.GL_TEXTURE_2D);
|
||||
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
|
||||
GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
|
||||
|
||||
// Set the background color
|
||||
gl.glClearColor(0f, 0f, 0f, 0f);
|
||||
|
||||
gl.glEnable(GL11.GL_BLEND);
|
||||
gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// We use 565 or 8888 format, so set the alignment to 2 bytes/pixel.
|
||||
gl.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2);
|
||||
}
|
||||
|
||||
public void setTexEnvMode(int mode) {
|
||||
if (mTexEnvMode == mode) return;
|
||||
mTexEnvMode = mode;
|
||||
mGL.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, mode);
|
||||
}
|
||||
|
||||
public void setLineWidth(float width) {
|
||||
if (mLineWidth == width) return;
|
||||
mLineWidth = width;
|
||||
mGL.glLineWidth(width);
|
||||
}
|
||||
|
||||
public void setTextureAlpha(float alpha) {
|
||||
if (mTextureAlpha == alpha) return;
|
||||
mTextureAlpha = alpha;
|
||||
if (alpha >= OPAQUE_ALPHA) {
|
||||
// The alpha is need for those texture without alpha channel
|
||||
mGL.glColor4f(1, 1, 1, 1);
|
||||
setTexEnvMode(GL11.GL_REPLACE);
|
||||
} else {
|
||||
mGL.glColor4f(alpha, alpha, alpha, alpha);
|
||||
setTexEnvMode(GL11.GL_MODULATE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setColorMode(int color, float alpha) {
|
||||
setBlendEnabled(!ColorUtils.isOpaque(color) || alpha < OPAQUE_ALPHA);
|
||||
|
||||
// Set mTextureAlpha to an invalid value, so that it will reset
|
||||
// again in setTextureAlpha(float) later.
|
||||
mTextureAlpha = -1.0f;
|
||||
|
||||
setTextureTarget(0);
|
||||
|
||||
float prealpha = (color >>> 24) * alpha * 65535f / 255f / 255f;
|
||||
mGL.glColor4x(
|
||||
Math.round(((color >> 16) & 0xFF) * prealpha),
|
||||
Math.round(((color >> 8) & 0xFF) * prealpha),
|
||||
Math.round((color & 0xFF) * prealpha),
|
||||
Math.round(255 * prealpha));
|
||||
}
|
||||
|
||||
// target is a value like GL_TEXTURE_2D. If target = 0, texturing is disabled.
|
||||
public void setTextureTarget(int target) {
|
||||
if (mTextureTarget == target) return;
|
||||
if (mTextureTarget != 0) {
|
||||
mGL.glDisable(mTextureTarget);
|
||||
}
|
||||
mTextureTarget = target;
|
||||
if (mTextureTarget != 0) {
|
||||
mGL.glEnable(mTextureTarget);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlendEnabled(boolean enabled) {
|
||||
if (mBlendEnabled == enabled) return;
|
||||
mBlendEnabled = enabled;
|
||||
if (enabled) {
|
||||
mGL.glEnable(GL11.GL_BLEND);
|
||||
} else {
|
||||
mGL.glDisable(GL11.GL_BLEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBuffer(float[] argb) {
|
||||
if(argb != null && argb.length == 4) {
|
||||
mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]);
|
||||
} else {
|
||||
mGL.glClearColor(0, 0, 0, 1);
|
||||
}
|
||||
mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBuffer() {
|
||||
clearBuffer(null);
|
||||
}
|
||||
|
||||
private void setTextureCoords(RectF source) {
|
||||
setTextureCoords(source.left, source.top, source.right, source.bottom);
|
||||
}
|
||||
|
||||
private void setTextureCoords(float left, float top,
|
||||
float right, float bottom) {
|
||||
mGL.glMatrixMode(GL11.GL_TEXTURE);
|
||||
mTextureMatrixValues[0] = right - left;
|
||||
mTextureMatrixValues[5] = bottom - top;
|
||||
mTextureMatrixValues[10] = 1;
|
||||
mTextureMatrixValues[12] = left;
|
||||
mTextureMatrixValues[13] = top;
|
||||
mTextureMatrixValues[15] = 1;
|
||||
mGL.glLoadMatrixf(mTextureMatrixValues, 0);
|
||||
mGL.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
private void setTextureCoords(float[] mTextureTransform) {
|
||||
mGL.glMatrixMode(GL11.GL_TEXTURE);
|
||||
mGL.glLoadMatrixf(mTextureTransform, 0);
|
||||
mGL.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
// unloadTexture and deleteBuffer can be called from the finalizer thread,
|
||||
// so we synchronized on the mUnboundTextures object.
|
||||
@Override
|
||||
public boolean unloadTexture(BasicTexture t) {
|
||||
synchronized (mUnboundTextures) {
|
||||
if (!t.isLoaded()) return false;
|
||||
mUnboundTextures.add(t.mId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBuffer(int bufferId) {
|
||||
synchronized (mUnboundTextures) {
|
||||
mDeleteBuffers.add(bufferId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRecycledResources() {
|
||||
synchronized (mUnboundTextures) {
|
||||
IntArray ids = mUnboundTextures;
|
||||
if (ids.size() > 0) {
|
||||
mGLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0);
|
||||
ids.clear();
|
||||
}
|
||||
|
||||
ids = mDeleteBuffers;
|
||||
if (ids.size() > 0) {
|
||||
mGLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0);
|
||||
ids.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
save(SAVE_FLAG_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(int saveFlags) {
|
||||
ConfigState config = obtainRestoreConfig();
|
||||
|
||||
if ((saveFlags & SAVE_FLAG_ALPHA) != 0) {
|
||||
config.mAlpha = mAlpha;
|
||||
} else {
|
||||
config.mAlpha = -1;
|
||||
}
|
||||
|
||||
if ((saveFlags & SAVE_FLAG_MATRIX) != 0) {
|
||||
System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16);
|
||||
} else {
|
||||
config.mMatrix[0] = Float.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
mRestoreStack.add(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restore() {
|
||||
if (mRestoreStack.isEmpty()) throw new IllegalStateException();
|
||||
ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1);
|
||||
config.restore(this);
|
||||
freeRestoreConfig(config);
|
||||
}
|
||||
|
||||
private void freeRestoreConfig(ConfigState action) {
|
||||
action.mNextFree = mRecycledRestoreAction;
|
||||
mRecycledRestoreAction = action;
|
||||
}
|
||||
|
||||
private ConfigState obtainRestoreConfig() {
|
||||
if (mRecycledRestoreAction != null) {
|
||||
ConfigState result = mRecycledRestoreAction;
|
||||
mRecycledRestoreAction = result.mNextFree;
|
||||
return result;
|
||||
}
|
||||
return new ConfigState();
|
||||
}
|
||||
|
||||
private static class ConfigState {
|
||||
float mAlpha;
|
||||
float mMatrix[] = new float[16];
|
||||
ConfigState mNextFree;
|
||||
|
||||
public void restore(GLES11Canvas canvas) {
|
||||
if (mAlpha >= 0) canvas.setAlpha(mAlpha);
|
||||
if (mMatrix[0] != Float.NEGATIVE_INFINITY) {
|
||||
System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpStatisticsAndClear() {
|
||||
String line = String.format(
|
||||
"MESH:%d, TEX_OES:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d",
|
||||
mCountDrawMesh, mCountTextureRect, mCountTextureOES,
|
||||
mCountFillRect, mCountDrawLine);
|
||||
mCountDrawMesh = 0;
|
||||
mCountTextureRect = 0;
|
||||
mCountTextureOES = 0;
|
||||
mCountFillRect = 0;
|
||||
mCountDrawLine = 0;
|
||||
Log.d(TAG, line);
|
||||
}
|
||||
|
||||
private void saveTransform() {
|
||||
System.arraycopy(mMatrixValues, 0, mTempMatrix, 0, 16);
|
||||
}
|
||||
|
||||
private void restoreTransform() {
|
||||
System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16);
|
||||
}
|
||||
|
||||
private void setRenderTarget(RawTexture texture) {
|
||||
GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL;
|
||||
|
||||
if (mTargetTexture == null && texture != null) {
|
||||
mGLId.glGenBuffers(1, mFrameBuffer, 0);
|
||||
gl11ep.glBindFramebufferOES(
|
||||
GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]);
|
||||
}
|
||||
if (mTargetTexture != null && texture == null) {
|
||||
gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0);
|
||||
gl11ep.glDeleteFramebuffersOES(1, mFrameBuffer, 0);
|
||||
}
|
||||
|
||||
mTargetTexture = texture;
|
||||
if (texture == null) {
|
||||
setSize(mScreenWidth, mScreenHeight);
|
||||
} else {
|
||||
setSize(texture.getWidth(), texture.getHeight());
|
||||
|
||||
if (!texture.isLoaded()) texture.prepare(this);
|
||||
|
||||
gl11ep.glFramebufferTexture2DOES(
|
||||
GL11ExtensionPack.GL_FRAMEBUFFER_OES,
|
||||
GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES,
|
||||
GL11.GL_TEXTURE_2D, texture.getId(), 0);
|
||||
|
||||
checkFramebufferStatus(gl11ep);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endRenderTarget() {
|
||||
RawTexture texture = mTargetStack.remove(mTargetStack.size() - 1);
|
||||
setRenderTarget(texture);
|
||||
restore(); // restore matrix and alpha
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginRenderTarget(RawTexture texture) {
|
||||
save(); // save matrix and alpha
|
||||
mTargetStack.add(mTargetTexture);
|
||||
setRenderTarget(texture);
|
||||
}
|
||||
|
||||
private static void checkFramebufferStatus(GL11ExtensionPack gl11ep) {
|
||||
int status = gl11ep.glCheckFramebufferStatusOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES);
|
||||
if (status != GL11ExtensionPack.GL_FRAMEBUFFER_COMPLETE_OES) {
|
||||
String msg = "";
|
||||
switch (status) {
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
|
||||
msg = "FRAMEBUFFER_FORMATS";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
|
||||
msg = "FRAMEBUFFER_ATTACHMENT";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
|
||||
msg = "FRAMEBUFFER_MISSING_ATTACHMENT";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_OES:
|
||||
msg = "FRAMEBUFFER_DRAW_BUFFER";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_OES:
|
||||
msg = "FRAMEBUFFER_READ_BUFFER";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_UNSUPPORTED_OES:
|
||||
msg = "FRAMEBUFFER_UNSUPPORTED";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
|
||||
msg = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
|
||||
break;
|
||||
}
|
||||
throw new RuntimeException(msg + ":" + Integer.toHexString(status));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextureParameters(BasicTexture texture) {
|
||||
int width = texture.getWidth();
|
||||
int height = texture.getHeight();
|
||||
// Define a vertically flipped crop rectangle for OES_draw_texture.
|
||||
// The four values in sCropRect are: left, bottom, width, and
|
||||
// height. Negative value of width or height means flip.
|
||||
sCropRect[0] = 0;
|
||||
sCropRect[1] = height;
|
||||
sCropRect[2] = width;
|
||||
sCropRect[3] = -height;
|
||||
|
||||
// Set texture parameters.
|
||||
int target = texture.getTarget();
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
mGL.glTexParameterfv(target, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
|
||||
mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
|
||||
mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
|
||||
mGL.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
mGL.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTextureSize(BasicTexture texture, int format, int type) {
|
||||
int target = texture.getTarget();
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
int width = texture.getTextureWidth();
|
||||
int height = texture.getTextureHeight();
|
||||
mGL.glTexImage2D(target, 0, format, width, height, 0, format, type, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
|
||||
int target = texture.getTarget();
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
GLUtils.texImage2D(target, 0, bitmap, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
|
||||
int format, int type) {
|
||||
int target = texture.getTarget();
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int uploadBuffer(FloatBuffer buf) {
|
||||
return uploadBuffer(buf, Float.SIZE / Byte.SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int uploadBuffer(ByteBuffer buf) {
|
||||
return uploadBuffer(buf, 1);
|
||||
}
|
||||
|
||||
private int uploadBuffer(Buffer buf, int elementSize) {
|
||||
int[] bufferIds = new int[1];
|
||||
mGLId.glGenBuffers(bufferIds.length, bufferIds, 0);
|
||||
int bufferId = bufferIds[0];
|
||||
mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId);
|
||||
mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf,
|
||||
GL11.GL_STATIC_DRAW);
|
||||
return bufferId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recoverFromLightCycle() {
|
||||
// This is only required for GLES20
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getBounds(Rect bounds, int x, int y, int width, int height) {
|
||||
// This is only required for GLES20
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLId getGLId() {
|
||||
return mGLId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
/**
|
||||
* Open GL ES 1.1 implementation for generating and destroying texture IDs and
|
||||
* buffer IDs
|
||||
*/
|
||||
public class GLES11IdImpl implements GLId {
|
||||
private static int sNextId = 1;
|
||||
// Mutex for sNextId
|
||||
private static final Object sLock = new Object();
|
||||
|
||||
@Override
|
||||
public int generateTexture() {
|
||||
synchronized (sLock) {
|
||||
return sNextId++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenBuffers(int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
while (n-- > 0) {
|
||||
buffers[offset + n] = sNextId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl.glDeleteTextures(n, textures, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl.glDeleteBuffers(n, buffers, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl11ep.glDeleteFramebuffersOES(n, buffers, offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,42 @@
|
||||
package com.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.opengl.GLES20;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
public class GLES20IdImpl implements GLId {
|
||||
private final int[] mTempIntArray = new int[1];
|
||||
|
||||
@Override
|
||||
public int generateTexture() {
|
||||
GLES20.glGenTextures(1, mTempIntArray, 0);
|
||||
GLES20Canvas.checkError();
|
||||
return mTempIntArray[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenBuffers(int n, int[] buffers, int offset) {
|
||||
GLES20.glGenBuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
|
||||
GLES20.glDeleteTextures(n, textures, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
|
||||
GLES20.glDeleteBuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
|
||||
GLES20.glDeleteFramebuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
// This mimics corresponding GL functions.
|
||||
public interface GLId {
|
||||
int generateTexture();
|
||||
|
||||
void glGenBuffers(int n, int[] buffers, int offset);
|
||||
|
||||
void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
|
||||
|
||||
void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
|
||||
|
||||
void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
public class GLPaint {
|
||||
private float mLineWidth = 1f;
|
||||
private int mColor = 0;
|
||||
|
||||
public void setColor(int color) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
public void setLineWidth(float width) {
|
||||
Assert.assertTrue(width >= 0);
|
||||
mLineWidth = width;
|
||||
}
|
||||
|
||||
public float getLineWidth() {
|
||||
return mLineWidth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
|
||||
|
||||
// MultiLineTexture is a texture shows the content of a specified String.
|
||||
//
|
||||
// To create a MultiLineTexture, use the newInstance() method and specify
|
||||
// the String, the font size, and the color.
|
||||
class MultiLineTexture extends CanvasTexture {
|
||||
private final Layout mLayout;
|
||||
|
||||
private MultiLineTexture(Layout layout) {
|
||||
super(layout.getWidth(), layout.getHeight());
|
||||
mLayout = layout;
|
||||
}
|
||||
|
||||
public static MultiLineTexture newInstance(
|
||||
String text, int maxWidth, float textSize, int color,
|
||||
Layout.Alignment alignment) {
|
||||
TextPaint paint = StringTexture.getDefaultPaint(textSize, color);
|
||||
Layout layout = new StaticLayout(text, 0, text.length(), paint,
|
||||
maxWidth, alignment, 1, 0, true, null, 0);
|
||||
|
||||
return new MultiLineTexture(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas, Bitmap backing) {
|
||||
mLayout.draw(canvas);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
// See "frameworks/base/include/utils/ResourceTypes.h" for the format of
|
||||
// NinePatch chunk.
|
||||
class NinePatchChunk {
|
||||
|
||||
public static final int NO_COLOR = 0x00000001;
|
||||
public static final int TRANSPARENT_COLOR = 0x00000000;
|
||||
|
||||
public Rect mPaddings = new Rect();
|
||||
|
||||
public int mDivX[];
|
||||
public int mDivY[];
|
||||
public int mColor[];
|
||||
|
||||
private static void readIntArray(int[] data, ByteBuffer buffer) {
|
||||
for (int i = 0, n = data.length; i < n; ++i) {
|
||||
data[i] = buffer.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDivCount(int length) {
|
||||
if (length == 0 || (length & 0x01) != 0) {
|
||||
throw new RuntimeException("invalid nine-patch: " + length);
|
||||
}
|
||||
}
|
||||
|
||||
public static NinePatchChunk deserialize(byte[] data) {
|
||||
ByteBuffer byteBuffer =
|
||||
ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
|
||||
byte wasSerialized = byteBuffer.get();
|
||||
if (wasSerialized == 0) return null;
|
||||
|
||||
NinePatchChunk chunk = new NinePatchChunk();
|
||||
chunk.mDivX = new int[byteBuffer.get()];
|
||||
chunk.mDivY = new int[byteBuffer.get()];
|
||||
chunk.mColor = new int[byteBuffer.get()];
|
||||
|
||||
checkDivCount(chunk.mDivX.length);
|
||||
checkDivCount(chunk.mDivY.length);
|
||||
|
||||
// skip 8 bytes
|
||||
byteBuffer.getInt();
|
||||
byteBuffer.getInt();
|
||||
|
||||
chunk.mPaddings.left = byteBuffer.getInt();
|
||||
chunk.mPaddings.right = byteBuffer.getInt();
|
||||
chunk.mPaddings.top = byteBuffer.getInt();
|
||||
chunk.mPaddings.bottom = byteBuffer.getInt();
|
||||
|
||||
// skip 4 bytes
|
||||
byteBuffer.getInt();
|
||||
|
||||
readIntArray(chunk.mDivX, byteBuffer);
|
||||
readIntArray(chunk.mDivY, byteBuffer);
|
||||
readIntArray(chunk.mColor, byteBuffer);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
// NinePatchTexture is a texture backed by a NinePatch resource.
|
||||
//
|
||||
// getPaddings() returns paddings specified in the NinePatch.
|
||||
// getNinePatchChunk() returns the layout data specified in the NinePatch.
|
||||
//
|
||||
public class NinePatchTexture extends ResourceTexture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "NinePatchTexture";
|
||||
private NinePatchChunk mChunk;
|
||||
private SmallCache<NinePatchInstance> mInstanceCache
|
||||
= new SmallCache<NinePatchInstance>();
|
||||
|
||||
public NinePatchTexture(Context context, int resId) {
|
||||
super(context, resId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
if (mBitmap != null) return mBitmap;
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||
Bitmap bitmap = BitmapFactory.decodeResource(
|
||||
mContext.getResources(), mResId, options);
|
||||
mBitmap = bitmap;
|
||||
setSize(bitmap.getWidth(), bitmap.getHeight());
|
||||
byte[] chunkData = bitmap.getNinePatchChunk();
|
||||
mChunk = chunkData == null
|
||||
? null
|
||||
: NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
|
||||
if (mChunk == null) {
|
||||
throw new RuntimeException("invalid nine-patch image: " + mResId);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public Rect getPaddings() {
|
||||
// get the paddings from nine patch
|
||||
if (mChunk == null) onGetBitmap();
|
||||
return mChunk.mPaddings;
|
||||
}
|
||||
|
||||
public NinePatchChunk getNinePatchChunk() {
|
||||
if (mChunk == null) onGetBitmap();
|
||||
return mChunk;
|
||||
}
|
||||
|
||||
// This is a simple cache for a small number of things. Linear search
|
||||
// is used because the cache is small. It also tries to remove less used
|
||||
// item when the cache is full by moving the often-used items to the front.
|
||||
private static class SmallCache<V> {
|
||||
private static final int CACHE_SIZE = 16;
|
||||
private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
|
||||
private int[] mKey = new int[CACHE_SIZE];
|
||||
private V[] mValue = (V[]) new Object[CACHE_SIZE];
|
||||
private int mCount; // number of items in this cache
|
||||
|
||||
// Puts a value into the cache. If the cache is full, also returns
|
||||
// a less used item, otherwise returns null.
|
||||
public V put(int key, V value) {
|
||||
if (mCount == CACHE_SIZE) {
|
||||
V old = mValue[CACHE_SIZE - 1]; // remove the last item
|
||||
mKey[CACHE_SIZE - 1] = key;
|
||||
mValue[CACHE_SIZE - 1] = value;
|
||||
return old;
|
||||
} else {
|
||||
mKey[mCount] = key;
|
||||
mValue[mCount] = value;
|
||||
mCount++;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public V get(int key) {
|
||||
for (int i = 0; i < mCount; i++) {
|
||||
if (mKey[i] == key) {
|
||||
// Move the accessed item one position to the front, so it
|
||||
// will less likely to be removed when cache is full. Only
|
||||
// do this if the cache is starting to get full.
|
||||
if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
|
||||
int tmpKey = mKey[i];
|
||||
mKey[i] = mKey[i - 1];
|
||||
mKey[i - 1] = tmpKey;
|
||||
|
||||
V tmpValue = mValue[i];
|
||||
mValue[i] = mValue[i - 1];
|
||||
mValue[i - 1] = tmpValue;
|
||||
}
|
||||
return mValue[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (int i = 0; i < mCount; i++) {
|
||||
mValue[i] = null; // make sure it's can be garbage-collected.
|
||||
}
|
||||
mCount = 0;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
public V valueAt(int i) {
|
||||
return mValue[i];
|
||||
}
|
||||
}
|
||||
|
||||
private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
|
||||
int key = w;
|
||||
key = (key << 16) | h;
|
||||
NinePatchInstance instance = mInstanceCache.get(key);
|
||||
|
||||
if (instance == null) {
|
||||
instance = new NinePatchInstance(this, w, h);
|
||||
NinePatchInstance removed = mInstanceCache.put(key, instance);
|
||||
if (removed != null) {
|
||||
removed.recycle(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (!isLoaded()) {
|
||||
mInstanceCache.clear();
|
||||
}
|
||||
|
||||
if (w != 0 && h != 0) {
|
||||
findInstance(canvas, w, h).draw(canvas, this, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
super.recycle();
|
||||
GLCanvas canvas = mCanvasRef;
|
||||
if (canvas == null) return;
|
||||
int n = mInstanceCache.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
NinePatchInstance instance = mInstanceCache.valueAt(i);
|
||||
instance.recycle(canvas);
|
||||
}
|
||||
mInstanceCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// This keeps data for a specialization of NinePatchTexture with the size
|
||||
// (width, height). We pre-compute the coordinates for efficiency.
|
||||
class NinePatchInstance {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "NinePatchInstance";
|
||||
|
||||
// We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
|
||||
private static final int VERTEX_BUFFER_SIZE = 16 * 2;
|
||||
|
||||
// We need 22 indices for a normal nine-patch image, plus 2 for each
|
||||
// transparent region. Current there are at most 1 transparent region.
|
||||
private static final int INDEX_BUFFER_SIZE = 22 + 2;
|
||||
|
||||
private FloatBuffer mXyBuffer;
|
||||
private FloatBuffer mUvBuffer;
|
||||
private ByteBuffer mIndexBuffer;
|
||||
|
||||
// Names for buffer names: xy, uv, index.
|
||||
private int mXyBufferName = -1;
|
||||
private int mUvBufferName;
|
||||
private int mIndexBufferName;
|
||||
|
||||
private int mIdxCount;
|
||||
|
||||
public NinePatchInstance(NinePatchTexture tex, int width, int height) {
|
||||
NinePatchChunk chunk = tex.getNinePatchChunk();
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw new RuntimeException("invalid dimension");
|
||||
}
|
||||
|
||||
// The code should be easily extended to handle the general cases by
|
||||
// allocating more space for buffers. But let's just handle the only
|
||||
// use case.
|
||||
if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
|
||||
throw new RuntimeException("unsupported nine patch");
|
||||
}
|
||||
|
||||
float divX[] = new float[4];
|
||||
float divY[] = new float[4];
|
||||
float divU[] = new float[4];
|
||||
float divV[] = new float[4];
|
||||
|
||||
int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
|
||||
int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
|
||||
|
||||
prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stretches the texture according to the nine-patch rules. It will
|
||||
* linearly distribute the strechy parts defined in the nine-patch chunk to
|
||||
* the target area.
|
||||
*
|
||||
* <pre>
|
||||
* source
|
||||
* /--------------^---------------\
|
||||
* u0 u1 u2 u3 u4 u5
|
||||
* div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
|
||||
* | div0 div1 div2 div3 |
|
||||
* | | / / / /
|
||||
* | | / / / /
|
||||
* | | / / / /
|
||||
* |fffff|ssss|fff|sss|ffff| ---> x
|
||||
* x0 x1 x2 x3 x4 x5
|
||||
* \----------v------------/
|
||||
* target
|
||||
*
|
||||
* f: fixed segment
|
||||
* s: stretchy segment
|
||||
* </pre>
|
||||
*
|
||||
* @param div the stretch parts defined in nine-patch chunk
|
||||
* @param source the length of the texture
|
||||
* @param target the length on the drawing plan
|
||||
* @param u output, the positions of these dividers in the texture
|
||||
* coordinate
|
||||
* @param x output, the corresponding position of these dividers on the
|
||||
* drawing plan
|
||||
* @return the number of these dividers.
|
||||
*/
|
||||
private static int stretch(
|
||||
float x[], float u[], int div[], int source, int target) {
|
||||
int textureSize = MathUtils.nextPowerOf2(source);
|
||||
float textureBound = (float) source / textureSize;
|
||||
|
||||
float stretch = 0;
|
||||
for (int i = 0, n = div.length; i < n; i += 2) {
|
||||
stretch += div[i + 1] - div[i];
|
||||
}
|
||||
|
||||
float remaining = target - source + stretch;
|
||||
|
||||
float lastX = 0;
|
||||
float lastU = 0;
|
||||
|
||||
x[0] = 0;
|
||||
u[0] = 0;
|
||||
for (int i = 0, n = div.length; i < n; i += 2) {
|
||||
// Make the stretchy segment a little smaller to prevent sampling
|
||||
// on neighboring fixed segments.
|
||||
// fixed segment
|
||||
x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
|
||||
u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
|
||||
|
||||
// stretchy segment
|
||||
float partU = div[i + 1] - div[i];
|
||||
float partX = remaining * partU / stretch;
|
||||
remaining -= partX;
|
||||
stretch -= partU;
|
||||
|
||||
lastX = x[i + 1] + partX;
|
||||
lastU = div[i + 1];
|
||||
x[i + 2] = lastX - 0.5f;
|
||||
u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound);
|
||||
}
|
||||
// the last fixed segment
|
||||
x[div.length + 1] = target;
|
||||
u[div.length + 1] = textureBound;
|
||||
|
||||
// remove segments with length 0.
|
||||
int last = 0;
|
||||
for (int i = 1, n = div.length + 2; i < n; ++i) {
|
||||
if ((x[i] - x[last]) < 1f) continue;
|
||||
x[++last] = x[i];
|
||||
u[last] = u[i];
|
||||
}
|
||||
return last + 1;
|
||||
}
|
||||
|
||||
private void prepareVertexData(float x[], float y[], float u[], float v[],
|
||||
int nx, int ny, int[] color) {
|
||||
/*
|
||||
* Given a 3x3 nine-patch image, the vertex order is defined as the
|
||||
* following graph:
|
||||
*
|
||||
* (0) (1) (2) (3)
|
||||
* | /| /| /|
|
||||
* | / | / | / |
|
||||
* (4) (5) (6) (7)
|
||||
* | \ | \ | \ |
|
||||
* | \| \| \|
|
||||
* (8) (9) (A) (B)
|
||||
* | /| /| /|
|
||||
* | / | / | / |
|
||||
* (C) (D) (E) (F)
|
||||
*
|
||||
* And we draw the triangle strip in the following index order:
|
||||
*
|
||||
* index: 04152637B6A5948C9DAEBF
|
||||
*/
|
||||
int pntCount = 0;
|
||||
float xy[] = new float[VERTEX_BUFFER_SIZE];
|
||||
float uv[] = new float[VERTEX_BUFFER_SIZE];
|
||||
for (int j = 0; j < ny; ++j) {
|
||||
for (int i = 0; i < nx; ++i) {
|
||||
int xIndex = (pntCount++) << 1;
|
||||
int yIndex = xIndex + 1;
|
||||
xy[xIndex] = x[i];
|
||||
xy[yIndex] = y[j];
|
||||
uv[xIndex] = u[i];
|
||||
uv[yIndex] = v[j];
|
||||
}
|
||||
}
|
||||
|
||||
int idxCount = 1;
|
||||
boolean isForward = false;
|
||||
byte index[] = new byte[INDEX_BUFFER_SIZE];
|
||||
for (int row = 0; row < ny - 1; row++) {
|
||||
--idxCount;
|
||||
isForward = !isForward;
|
||||
|
||||
int start, end, inc;
|
||||
if (isForward) {
|
||||
start = 0;
|
||||
end = nx;
|
||||
inc = 1;
|
||||
} else {
|
||||
start = nx - 1;
|
||||
end = -1;
|
||||
inc = -1;
|
||||
}
|
||||
|
||||
for (int col = start; col != end; col += inc) {
|
||||
int k = row * nx + col;
|
||||
if (col != start) {
|
||||
int colorIdx = row * (nx - 1) + col;
|
||||
if (isForward) colorIdx--;
|
||||
if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
|
||||
index[idxCount] = index[idxCount - 1];
|
||||
++idxCount;
|
||||
index[idxCount++] = (byte) k;
|
||||
}
|
||||
}
|
||||
|
||||
index[idxCount++] = (byte) k;
|
||||
index[idxCount++] = (byte) (k + nx);
|
||||
}
|
||||
}
|
||||
|
||||
mIdxCount = idxCount;
|
||||
|
||||
int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
|
||||
mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
|
||||
mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
|
||||
mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
|
||||
|
||||
mXyBuffer.put(xy, 0, pntCount * 2).position(0);
|
||||
mUvBuffer.put(uv, 0, pntCount * 2).position(0);
|
||||
mIndexBuffer.put(index, 0, idxCount).position(0);
|
||||
}
|
||||
|
||||
private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
|
||||
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
private void prepareBuffers(GLCanvas canvas) {
|
||||
mXyBufferName = canvas.uploadBuffer(mXyBuffer);
|
||||
mUvBufferName = canvas.uploadBuffer(mUvBuffer);
|
||||
mIndexBufferName = canvas.uploadBuffer(mIndexBuffer);
|
||||
|
||||
// These buffers are never used again.
|
||||
mXyBuffer = null;
|
||||
mUvBuffer = null;
|
||||
mIndexBuffer = null;
|
||||
}
|
||||
|
||||
public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
|
||||
if (mXyBufferName == -1) {
|
||||
prepareBuffers(canvas);
|
||||
}
|
||||
canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount);
|
||||
}
|
||||
|
||||
public void recycle(GLCanvas canvas) {
|
||||
if (mXyBuffer == null) {
|
||||
canvas.deleteBuffer(mXyBufferName);
|
||||
canvas.deleteBuffer(mUvBufferName);
|
||||
canvas.deleteBuffer(mIndexBufferName);
|
||||
mXyBufferName = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
public class RawTexture extends BasicTexture {
|
||||
private static final String TAG = "RawTexture";
|
||||
|
||||
private final boolean mOpaque;
|
||||
private boolean mIsFlipped;
|
||||
|
||||
public RawTexture(int width, int height, boolean opaque) {
|
||||
mOpaque = opaque;
|
||||
setSize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFlippedVertically() {
|
||||
return mIsFlipped;
|
||||
}
|
||||
|
||||
public void setIsFlippedVertically(boolean isFlipped) {
|
||||
mIsFlipped = isFlipped;
|
||||
}
|
||||
|
||||
protected void prepare(GLCanvas canvas) {
|
||||
GLId glId = canvas.getGLId();
|
||||
mId = glId.generateTexture();
|
||||
canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
|
||||
canvas.setTextureParameters(this);
|
||||
mState = STATE_LOADED;
|
||||
setAssociatedCanvas(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
if (isLoaded()) return true;
|
||||
Log.w(TAG, "lost the content due to context change");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void yield() {
|
||||
// we cannot free the texture because we have no backup.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTarget() {
|
||||
return GL11.GL_TEXTURE_2D;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
// ResourceTexture is a texture whose Bitmap is decoded from a resource.
|
||||
// By default ResourceTexture is not opaque.
|
||||
public class ResourceTexture extends UploadedTexture {
|
||||
|
||||
protected final Context mContext;
|
||||
protected final int mResId;
|
||||
|
||||
public ResourceTexture(Context context, int resId) {
|
||||
Assert.assertNotNull(context);
|
||||
mContext = context;
|
||||
mResId = resId;
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||
return BitmapFactory.decodeResource(
|
||||
mContext.getResources(), mResId, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
if (!inFinalizer()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint.FontMetricsInt;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.util.FloatMath;
|
||||
|
||||
// StringTexture is a texture shows the content of a specified String.
|
||||
//
|
||||
// To create a StringTexture, use the newInstance() method and specify
|
||||
// the String, the font size, and the color.
|
||||
public class StringTexture extends CanvasTexture {
|
||||
private final String mText;
|
||||
private final TextPaint mPaint;
|
||||
private final FontMetricsInt mMetrics;
|
||||
|
||||
private StringTexture(String text, TextPaint paint,
|
||||
FontMetricsInt metrics, int width, int height) {
|
||||
super(width, height);
|
||||
mText = text;
|
||||
mPaint = paint;
|
||||
mMetrics = metrics;
|
||||
}
|
||||
|
||||
public static TextPaint getDefaultPaint(float textSize, int color) {
|
||||
TextPaint paint = new TextPaint();
|
||||
paint.setTextSize(textSize);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setColor(color);
|
||||
paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
|
||||
return paint;
|
||||
}
|
||||
|
||||
public static StringTexture newInstance(
|
||||
String text, float textSize, int color) {
|
||||
return newInstance(text, getDefaultPaint(textSize, color));
|
||||
}
|
||||
|
||||
public static StringTexture newInstance(
|
||||
String text, float textSize, int color,
|
||||
float lengthLimit, boolean isBold) {
|
||||
TextPaint paint = getDefaultPaint(textSize, color);
|
||||
if (isBold) {
|
||||
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
|
||||
}
|
||||
if (lengthLimit > 0) {
|
||||
text = TextUtils.ellipsize(
|
||||
text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();
|
||||
}
|
||||
return newInstance(text, paint);
|
||||
}
|
||||
|
||||
private static StringTexture newInstance(String text, TextPaint paint) {
|
||||
FontMetricsInt metrics = paint.getFontMetricsInt();
|
||||
int width = (int) FloatMath.ceil(paint.measureText(text));
|
||||
int height = metrics.bottom - metrics.top;
|
||||
// The texture size needs to be at least 1x1.
|
||||
if (width <= 0) width = 1;
|
||||
if (height <= 0) height = 1;
|
||||
return new StringTexture(text, paint, metrics, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas, Bitmap backing) {
|
||||
canvas.translate(0, -mMetrics.ascent);
|
||||
canvas.drawText(mText, 0, 0, mPaint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
|
||||
// Texture is a rectangular image which can be drawn on GLCanvas.
|
||||
// The isOpaque() function gives a hint about whether the texture is opaque,
|
||||
// so the drawing can be done faster.
|
||||
//
|
||||
// This is the current texture hierarchy:
|
||||
//
|
||||
// Texture
|
||||
// -- ColorTexture
|
||||
// -- FadeInTexture
|
||||
// -- BasicTexture
|
||||
// -- UploadedTexture
|
||||
// -- BitmapTexture
|
||||
// -- Tile
|
||||
// -- ResourceTexture
|
||||
// -- NinePatchTexture
|
||||
// -- CanvasTexture
|
||||
// -- StringTexture
|
||||
//
|
||||
public interface Texture {
|
||||
int getWidth();
|
||||
int getHeight();
|
||||
void draw(GLCanvas canvas, int x, int y);
|
||||
void draw(GLCanvas canvas, int x, int y, int w, int h);
|
||||
boolean isOpaque();
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import com.hippo.ehviewer.gallery.ui.GLRoot;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
public class TextureUploader implements GLRoot.OnGLIdleListener {
|
||||
private static final int INIT_CAPACITY = 64;
|
||||
private static final int QUOTA_PER_FRAME = 1;
|
||||
|
||||
private final ArrayDeque<UploadedTexture> mFgTextures =
|
||||
new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
|
||||
private final ArrayDeque<UploadedTexture> mBgTextures =
|
||||
new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
|
||||
private final GLRoot mGLRoot;
|
||||
private volatile boolean mIsQueued = false;
|
||||
|
||||
public TextureUploader(GLRoot root) {
|
||||
mGLRoot = root;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
while (!mFgTextures.isEmpty()) {
|
||||
mFgTextures.pop().setIsUploading(false);
|
||||
}
|
||||
while (!mBgTextures.isEmpty()) {
|
||||
mBgTextures.pop().setIsUploading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// caller should hold synchronized on "this"
|
||||
private void queueSelfIfNeed() {
|
||||
if (mIsQueued) return;
|
||||
mIsQueued = true;
|
||||
mGLRoot.addOnGLIdleListener(this);
|
||||
}
|
||||
|
||||
public synchronized void addBgTexture(UploadedTexture t) {
|
||||
if (t.isContentValid()) return;
|
||||
mBgTextures.addLast(t);
|
||||
t.setIsUploading(true);
|
||||
queueSelfIfNeed();
|
||||
}
|
||||
|
||||
public synchronized void addFgTexture(UploadedTexture t) {
|
||||
if (t.isContentValid()) return;
|
||||
mFgTextures.addLast(t);
|
||||
t.setIsUploading(true);
|
||||
queueSelfIfNeed();
|
||||
}
|
||||
|
||||
private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
|
||||
int uploadQuota, boolean isBackground) {
|
||||
while (uploadQuota > 0) {
|
||||
UploadedTexture t;
|
||||
synchronized (this) {
|
||||
if (deque.isEmpty()) break;
|
||||
t = deque.removeFirst();
|
||||
t.setIsUploading(false);
|
||||
if (t.isContentValid()) continue;
|
||||
|
||||
// this has to be protected by the synchronized block
|
||||
// to prevent the inner bitmap get recycled
|
||||
t.updateContent(canvas);
|
||||
}
|
||||
|
||||
// It will took some more time for a texture to be drawn for
|
||||
// the first time.
|
||||
// Thus, when scrolling, if a new column appears on screen,
|
||||
// it may cause a UI jank even these textures are uploaded.
|
||||
if (isBackground) t.draw(canvas, 0, 0);
|
||||
--uploadQuota;
|
||||
}
|
||||
return uploadQuota;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
|
||||
int uploadQuota = QUOTA_PER_FRAME;
|
||||
uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
|
||||
if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
|
||||
upload(canvas, mBgTextures, uploadQuota, true);
|
||||
synchronized (this) {
|
||||
mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
|
||||
return mIsQueued;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff.Mode;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.hippo.ehviewer.gallery.ui.GLRoot;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
|
||||
// This class is similar to BitmapTexture, except the bitmap is
|
||||
// split into tiles. By doing so, we may increase the time required to
|
||||
// upload the whole bitmap but we reduce the time of uploading each tile
|
||||
// so it make the animation more smooth and prevents jank.
|
||||
public class TiledTexture implements Texture {
|
||||
private static final int CONTENT_SIZE = 254;
|
||||
private static final int BORDER_SIZE = 1;
|
||||
private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
|
||||
private static final int INIT_CAPACITY = 8;
|
||||
|
||||
// We are targeting at 60fps, so we have 16ms for each frame.
|
||||
// In this 16ms, we use about 4~8 ms to upload tiles.
|
||||
private static final long UPLOAD_TILE_LIMIT = 4; // ms
|
||||
|
||||
private static Tile sFreeTileHead = null;
|
||||
private static final Object sFreeTileLock = new Object();
|
||||
|
||||
private static Bitmap sUploadBitmap;
|
||||
private static Canvas sCanvas;
|
||||
private static Paint sBitmapPaint;
|
||||
private static Paint sPaint;
|
||||
|
||||
private int mUploadIndex = 0;
|
||||
|
||||
private final Tile[] mTiles; // Can be modified in different threads.
|
||||
// Should be protected by "synchronized."
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
private final RectF mSrcRect = new RectF();
|
||||
private final RectF mDestRect = new RectF();
|
||||
|
||||
public static class Uploader implements GLRoot.OnGLIdleListener {
|
||||
private final ArrayDeque<TiledTexture> mTextures =
|
||||
new ArrayDeque<>(INIT_CAPACITY);
|
||||
|
||||
private final GLRoot mGlRoot;
|
||||
private boolean mIsQueued = false;
|
||||
|
||||
public Uploader(GLRoot glRoot) {
|
||||
mGlRoot = glRoot;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
mTextures.clear();
|
||||
}
|
||||
|
||||
public synchronized void addTexture(TiledTexture t) {
|
||||
if (t.isReady()) return;
|
||||
mTextures.addLast(t);
|
||||
|
||||
if (mIsQueued) return;
|
||||
mIsQueued = true;
|
||||
mGlRoot.addOnGLIdleListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
|
||||
ArrayDeque<TiledTexture> deque = mTextures;
|
||||
synchronized (this) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
long dueTime = now + UPLOAD_TILE_LIMIT;
|
||||
while (now < dueTime && !deque.isEmpty()) {
|
||||
TiledTexture t = deque.peekFirst();
|
||||
if (t.uploadNextTile(canvas)) {
|
||||
deque.removeFirst();
|
||||
mGlRoot.requestRender();
|
||||
}
|
||||
now = SystemClock.uptimeMillis();
|
||||
}
|
||||
mIsQueued = !mTextures.isEmpty();
|
||||
|
||||
// return true to keep this listener in the queue
|
||||
return mIsQueued;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Tile extends UploadedTexture {
|
||||
public int offsetX;
|
||||
public int offsetY;
|
||||
public Bitmap bitmap;
|
||||
public Tile nextFreeTile;
|
||||
public int contentWidth;
|
||||
public int contentHeight;
|
||||
|
||||
@Override
|
||||
public void setSize(int width, int height) {
|
||||
contentWidth = width;
|
||||
contentHeight = height;
|
||||
mWidth = width + 2 * BORDER_SIZE;
|
||||
mHeight = height + 2 * BORDER_SIZE;
|
||||
mTextureWidth = TILE_SIZE;
|
||||
mTextureHeight = TILE_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
// make a local copy of the reference to the bitmap,
|
||||
// since it might be null'd in a different thread. b/8694871
|
||||
Bitmap localBitmapRef = bitmap;
|
||||
bitmap = null;
|
||||
|
||||
if (localBitmapRef != null) {
|
||||
int x = BORDER_SIZE - offsetX;
|
||||
int y = BORDER_SIZE - offsetY;
|
||||
int r = localBitmapRef.getWidth() + x;
|
||||
int b = localBitmapRef.getHeight() + y;
|
||||
sCanvas.drawBitmap(localBitmapRef, x, y, sBitmapPaint);
|
||||
localBitmapRef = null;
|
||||
|
||||
// draw borders if need
|
||||
if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
|
||||
if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
|
||||
if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
|
||||
if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
|
||||
}
|
||||
|
||||
return sUploadBitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private static void freeTile(Tile tile) {
|
||||
tile.invalidateContent();
|
||||
tile.bitmap = null;
|
||||
synchronized (sFreeTileLock) {
|
||||
tile.nextFreeTile = sFreeTileHead;
|
||||
sFreeTileHead = tile;
|
||||
}
|
||||
}
|
||||
|
||||
private static Tile obtainTile() {
|
||||
synchronized (sFreeTileLock) {
|
||||
Tile result = sFreeTileHead;
|
||||
if (result == null) return new Tile();
|
||||
sFreeTileHead = result.nextFreeTile;
|
||||
result.nextFreeTile = null;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean uploadNextTile(GLCanvas canvas) {
|
||||
if (mUploadIndex == mTiles.length) return true;
|
||||
|
||||
synchronized (mTiles) {
|
||||
Tile next = mTiles[mUploadIndex++];
|
||||
|
||||
// Make sure tile has not already been recycled by the time
|
||||
// this is called (race condition in onGLIdle)
|
||||
if (next.bitmap != null) {
|
||||
boolean hasBeenLoad = next.isLoaded();
|
||||
next.updateContent(canvas);
|
||||
|
||||
// It will take some time for a texture to be drawn for the first
|
||||
// time. When scrolling, we need to draw several tiles on the screen
|
||||
// at the same time. It may cause a UI jank even these textures has
|
||||
// been uploaded.
|
||||
if (!hasBeenLoad) next.draw(canvas, 0, 0);
|
||||
}
|
||||
}
|
||||
return mUploadIndex == mTiles.length;
|
||||
}
|
||||
|
||||
public TiledTexture(Bitmap bitmap) {
|
||||
mWidth = bitmap.getWidth();
|
||||
mHeight = bitmap.getHeight();
|
||||
ArrayList<Tile> list = new ArrayList<Tile>();
|
||||
|
||||
for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
|
||||
for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
|
||||
Tile tile = obtainTile();
|
||||
tile.offsetX = x;
|
||||
tile.offsetY = y;
|
||||
tile.bitmap = bitmap;
|
||||
tile.setSize(
|
||||
Math.min(CONTENT_SIZE, mWidth - x),
|
||||
Math.min(CONTENT_SIZE, mHeight - y));
|
||||
list.add(tile);
|
||||
}
|
||||
}
|
||||
mTiles = list.toArray(new Tile[list.size()]);
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return mUploadIndex == mTiles.length;
|
||||
}
|
||||
|
||||
// Can be called in UI thread.
|
||||
public void recycle() {
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
freeTile(mTiles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void freeResources() {
|
||||
sUploadBitmap = null;
|
||||
sCanvas = null;
|
||||
sBitmapPaint = null;
|
||||
sPaint = null;
|
||||
}
|
||||
|
||||
public static void prepareResources() {
|
||||
sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
|
||||
sCanvas = new Canvas(sUploadBitmap);
|
||||
sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
|
||||
sPaint = new Paint();
|
||||
sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
|
||||
sPaint.setColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
// We want to draw the "source" on the "target".
|
||||
// This method is to find the "output" rectangle which is
|
||||
// the corresponding area of the "src".
|
||||
// (x,y) target
|
||||
// (x0,y0) source +---------------+
|
||||
// +----------+ | |
|
||||
// | src | | output |
|
||||
// | +--+ | linear map | +----+ |
|
||||
// | +--+ | ----------> | | | |
|
||||
// | | by (scaleX, scaleY) | +----+ |
|
||||
// +----------+ | |
|
||||
// Texture +---------------+
|
||||
// Canvas
|
||||
private static void mapRect(RectF output,
|
||||
RectF src, float x0, float y0, float x, float y, float scaleX,
|
||||
float scaleY) {
|
||||
output.set(x + (src.left - x0) * scaleX,
|
||||
y + (src.top - y0) * scaleY,
|
||||
x + (src.right - x0) * scaleX,
|
||||
y + (src.bottom - y0) * scaleY);
|
||||
}
|
||||
|
||||
// Draws a mixed color of this texture and a specified color onto the
|
||||
// a rectangle. The used color is: from * (1 - ratio) + to * ratio.
|
||||
public void drawMixed(GLCanvas canvas, int color, float ratio,
|
||||
int x, int y, int width, int height) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float scaleX = (float) width / mWidth;
|
||||
float scaleY = (float) height / mHeight;
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws the texture on to the specified rectangle.
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int width, int height) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float scaleX = (float) width / mWidth;
|
||||
float scaleY = (float) height / mHeight;
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawTexture(t, mSrcRect, mDestRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a sub region of this texture on to the specified rectangle.
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float x0 = source.left;
|
||||
float y0 = source.top;
|
||||
float x = target.left;
|
||||
float y = target.top;
|
||||
float scaleX = target.width() / source.width();
|
||||
float scaleY = target.height() / source.height();
|
||||
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
if (!src.intersect(source)) continue;
|
||||
mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawTexture(t, src, dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.opengl.GLUtils;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
// UploadedTextures use a Bitmap for the content of the texture.
|
||||
//
|
||||
// Subclasses should implement onGetBitmap() to provide the Bitmap and
|
||||
// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
|
||||
// is not needed anymore.
|
||||
//
|
||||
// isContentValid() is meaningful only when the isLoaded() returns true.
|
||||
// It means whether the content needs to be updated.
|
||||
//
|
||||
// The user of this class should call recycle() when the texture is not
|
||||
// needed anymore.
|
||||
//
|
||||
// By default an UploadedTexture is opaque (so it can be drawn faster without
|
||||
// blending). The user or subclass can override it using setOpaque().
|
||||
public abstract class UploadedTexture extends BasicTexture {
|
||||
|
||||
// To prevent keeping allocation the borders, we store those used borders here.
|
||||
// Since the length will be power of two, it won't use too much memory.
|
||||
private static HashMap<BorderKey, Bitmap> sBorderLines =
|
||||
new HashMap<>();
|
||||
private static BorderKey sBorderKey = new BorderKey();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "Texture";
|
||||
private boolean mContentValid = true;
|
||||
|
||||
// indicate this textures is being uploaded in background
|
||||
private boolean mIsUploading = false;
|
||||
private boolean mOpaque = true;
|
||||
private boolean mThrottled = false;
|
||||
private static int sUploadedCount;
|
||||
private static final int UPLOAD_LIMIT = 100;
|
||||
|
||||
protected Bitmap mBitmap;
|
||||
private int mBorder;
|
||||
|
||||
protected UploadedTexture() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
protected UploadedTexture(boolean hasBorder) {
|
||||
super(null, 0, STATE_UNLOADED);
|
||||
if (hasBorder) {
|
||||
setBorder(true);
|
||||
mBorder = 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setIsUploading(boolean uploading) {
|
||||
mIsUploading = uploading;
|
||||
}
|
||||
|
||||
public boolean isUploading() {
|
||||
return mIsUploading;
|
||||
}
|
||||
|
||||
private static class BorderKey implements Cloneable {
|
||||
public boolean vertical;
|
||||
public Config config;
|
||||
public int length;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int x = config.hashCode() ^ length;
|
||||
return vertical ? x : -x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (!(object instanceof BorderKey)) return false;
|
||||
BorderKey o = (BorderKey) object;
|
||||
return vertical == o.vertical
|
||||
&& config == o.config && length == o.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BorderKey clone() {
|
||||
try {
|
||||
return (BorderKey) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setThrottled(boolean throttled) {
|
||||
mThrottled = throttled;
|
||||
}
|
||||
|
||||
private static Bitmap getBorderLine(
|
||||
boolean vertical, Config config, int length) {
|
||||
BorderKey key = sBorderKey;
|
||||
key.vertical = vertical;
|
||||
key.config = config;
|
||||
key.length = length;
|
||||
Bitmap bitmap = sBorderLines.get(key);
|
||||
if (bitmap == null) {
|
||||
bitmap = vertical
|
||||
? Bitmap.createBitmap(1, length, config)
|
||||
: Bitmap.createBitmap(length, 1, config);
|
||||
sBorderLines.put(key.clone(), bitmap);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private Bitmap getBitmap() {
|
||||
if (mBitmap == null) {
|
||||
mBitmap = onGetBitmap();
|
||||
int w = mBitmap.getWidth() + mBorder * 2;
|
||||
int h = mBitmap.getHeight() + mBorder * 2;
|
||||
if (mWidth == UNSPECIFIED) {
|
||||
setSize(w, h);
|
||||
}
|
||||
}
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
private void freeBitmap() {
|
||||
Assert.assertTrue(mBitmap != null);
|
||||
onFreeBitmap(mBitmap);
|
||||
mBitmap = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
if (mWidth == UNSPECIFIED) getBitmap();
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
if (mWidth == UNSPECIFIED) getBitmap();
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
protected abstract Bitmap onGetBitmap();
|
||||
|
||||
protected abstract void onFreeBitmap(Bitmap bitmap);
|
||||
|
||||
protected void invalidateContent() {
|
||||
if (mBitmap != null) freeBitmap();
|
||||
mContentValid = false;
|
||||
mWidth = UNSPECIFIED;
|
||||
mHeight = UNSPECIFIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the content on GPU is valid.
|
||||
*/
|
||||
public boolean isContentValid() {
|
||||
return isLoaded() && mContentValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the content on GPU's memory.
|
||||
* @param canvas
|
||||
*/
|
||||
public void updateContent(GLCanvas canvas) {
|
||||
if (!isLoaded()) {
|
||||
if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
|
||||
return;
|
||||
}
|
||||
uploadToCanvas(canvas);
|
||||
} else if (!mContentValid) {
|
||||
Bitmap bitmap = getBitmap();
|
||||
int format = GLUtils.getInternalFormat(bitmap);
|
||||
int type = GLUtils.getType(bitmap);
|
||||
canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
|
||||
freeBitmap();
|
||||
mContentValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetUploadLimit() {
|
||||
sUploadedCount = 0;
|
||||
}
|
||||
|
||||
public static boolean uploadLimitReached() {
|
||||
return sUploadedCount > UPLOAD_LIMIT;
|
||||
}
|
||||
|
||||
private void uploadToCanvas(GLCanvas canvas) {
|
||||
|
||||
Bitmap bitmap = getBitmap();
|
||||
if (bitmap != null) {
|
||||
try {
|
||||
int bWidth = bitmap.getWidth();
|
||||
int bHeight = bitmap.getHeight();
|
||||
int texWidth = getTextureWidth();
|
||||
int texHeight = getTextureHeight();
|
||||
|
||||
Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
|
||||
|
||||
// Upload the bitmap to a new texture.
|
||||
mId = canvas.getGLId().generateTexture();
|
||||
canvas.setTextureParameters(this);
|
||||
|
||||
if (bWidth == texWidth && bHeight == texHeight) {
|
||||
canvas.initializeTexture(this, bitmap);
|
||||
} else {
|
||||
int format = GLUtils.getInternalFormat(bitmap);
|
||||
int type = GLUtils.getType(bitmap);
|
||||
Config config = bitmap.getConfig();
|
||||
|
||||
canvas.initializeTextureSize(this, format, type);
|
||||
canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
|
||||
|
||||
if (mBorder > 0) {
|
||||
// Left border
|
||||
Bitmap line = getBorderLine(true, config, texHeight);
|
||||
canvas.texSubImage2D(this, 0, 0, line, format, type);
|
||||
|
||||
// Top border
|
||||
line = getBorderLine(false, config, texWidth);
|
||||
canvas.texSubImage2D(this, 0, 0, line, format, type);
|
||||
}
|
||||
|
||||
// Right border
|
||||
if (mBorder + bWidth < texWidth) {
|
||||
Bitmap line = getBorderLine(true, config, texHeight);
|
||||
canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
|
||||
}
|
||||
|
||||
// Bottom border
|
||||
if (mBorder + bHeight < texHeight) {
|
||||
Bitmap line = getBorderLine(false, config, texWidth);
|
||||
canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
freeBitmap();
|
||||
}
|
||||
// Update texture state.
|
||||
setAssociatedCanvas(canvas);
|
||||
mState = STATE_LOADED;
|
||||
mContentValid = true;
|
||||
} else {
|
||||
mState = STATE_ERROR;
|
||||
throw new RuntimeException("Texture load fail, no bitmap");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
updateContent(canvas);
|
||||
return isContentValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTarget() {
|
||||
return GL11.GL_TEXTURE_2D;
|
||||
}
|
||||
|
||||
public void setOpaque(boolean isOpaque) {
|
||||
mOpaque = isOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
super.recycle();
|
||||
if (mBitmap != null) freeBitmap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
//
|
||||
// The animation time should ideally be the vsync time the frame will be
|
||||
// displayed, but that is an unknown time in the future. So we use the system
|
||||
// time just after eglSwapBuffers (when GLSurfaceView.onDrawFrame is called)
|
||||
// as a approximation.
|
||||
//
|
||||
public class AnimationTime {
|
||||
private static volatile long sTime;
|
||||
|
||||
// Sets current time as the animation time.
|
||||
public static void update() {
|
||||
sTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
// Returns the animation time.
|
||||
public static long get() {
|
||||
return sTime;
|
||||
}
|
||||
|
||||
public static long startTime() {
|
||||
sTime = SystemClock.uptimeMillis();
|
||||
return sTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Matrix;
|
||||
|
||||
import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
|
||||
public interface GLRoot {
|
||||
|
||||
// Listener will be called when GL is idle AND before each frame.
|
||||
// Mainly used for uploading textures.
|
||||
interface OnGLIdleListener {
|
||||
boolean onGLIdle(GLCanvas canvas, boolean renderRequested);
|
||||
}
|
||||
|
||||
void addOnGLIdleListener(OnGLIdleListener listener);
|
||||
void registerLaunchedAnimation(CanvasAnimation animation);
|
||||
void requestRenderForced();
|
||||
void requestRender();
|
||||
void requestLayoutContentPane();
|
||||
|
||||
void lockRenderThread();
|
||||
void unlockRenderThread();
|
||||
|
||||
void setContentPane(GLView content);
|
||||
void setOrientationSource(OrientationSource source);
|
||||
int getDisplayRotation();
|
||||
int getCompensation();
|
||||
Matrix getCompensationMatrix();
|
||||
void freeze();
|
||||
void unfreeze();
|
||||
void setLightsOutMode(boolean enabled);
|
||||
|
||||
Context getContext();
|
||||
}
|
||||
@@ -0,0 +1,639 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ConfigurationInfo;
|
||||
import android.graphics.Matrix;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.BasicTexture;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLES11Canvas;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLES20Canvas;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.UploadedTexture;
|
||||
import com.hippo.ehviewer.gallery.util.ApiHelper;
|
||||
import com.hippo.ehviewer.gallery.util.GalleryUtils;
|
||||
import com.hippo.ehviewer.gallery.util.MotionEventHelper;
|
||||
import com.hippo.ehviewer.gallery.util.Profile;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
// The root component of all <code>GLView</code>s. The rendering is done in GL
|
||||
// thread while the event handling is done in the main thread. To synchronize
|
||||
// the two threads, the entry points of this package need to synchronize on the
|
||||
// <code>GLRootView</code> instance unless it can be proved that the rendering
|
||||
// thread won't access the same thing as the method. The entry points include:
|
||||
// (1) The public methods of HeadUpDisplay
|
||||
// (2) The public methods of CameraHeadUpDisplay
|
||||
// (3) The overridden methods in GLRootView.
|
||||
public class GLRootView extends GLSurfaceView
|
||||
implements GLSurfaceView.Renderer, GLRoot {
|
||||
private static final String TAG = "GLRootView";
|
||||
|
||||
private static final boolean DEBUG_FPS = false;
|
||||
private int mFrameCount = 0;
|
||||
private long mFrameCountingStart = 0;
|
||||
|
||||
private static final boolean DEBUG_INVALIDATE = false;
|
||||
private int mInvalidateColor = 0;
|
||||
|
||||
private static final boolean DEBUG_DRAWING_STAT = false;
|
||||
|
||||
private static final boolean DEBUG_PROFILE = false;
|
||||
private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
|
||||
|
||||
private static final int FLAG_INITIALIZED = 1;
|
||||
private static final int FLAG_NEED_LAYOUT = 2;
|
||||
|
||||
private GL11 mGL;
|
||||
private GLCanvas mCanvas;
|
||||
private GLView mContentView;
|
||||
|
||||
private OrientationSource mOrientationSource;
|
||||
// mCompensation is the difference between the UI orientation on GLCanvas
|
||||
// and the framework orientation. See OrientationManager for details.
|
||||
private int mCompensation;
|
||||
// mCompensationMatrix maps the coordinates of touch events. It is kept sync
|
||||
// with mCompensation.
|
||||
private Matrix mCompensationMatrix = new Matrix();
|
||||
private int mDisplayRotation;
|
||||
|
||||
private int mFlags = FLAG_NEED_LAYOUT;
|
||||
private volatile boolean mRenderRequested = false;
|
||||
|
||||
private final ArrayList<CanvasAnimation> mAnimations =
|
||||
new ArrayList<>();
|
||||
|
||||
private final ArrayDeque<OnGLIdleListener> mIdleListeners =
|
||||
new ArrayDeque<>();
|
||||
|
||||
private final IdleRunner mIdleRunner = new IdleRunner();
|
||||
|
||||
private final ReentrantLock mRenderLock = new ReentrantLock();
|
||||
private final Condition mFreezeCondition =
|
||||
mRenderLock.newCondition();
|
||||
private boolean mFreeze;
|
||||
|
||||
private long mLastDrawFinishTime;
|
||||
private boolean mInDownState = false;
|
||||
//private boolean mFirstDraw = true;
|
||||
|
||||
public static boolean checkGLES20Support(Context context) {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
ConfigurationInfo info = am.getDeviceConfigurationInfo();
|
||||
return info.reqGlEsVersion >= 0x20000;
|
||||
}
|
||||
|
||||
public GLRootView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public GLRootView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mFlags |= FLAG_INITIALIZED;
|
||||
setBackgroundDrawable(null);
|
||||
setEGLContextClientVersion(checkGLES20Support(context) ? 2 : 1);
|
||||
|
||||
// We can't detect 888_PIXEL_FORMAT by Build.VERSION.SDK_INT,
|
||||
// for example CyanogenMod use ro.opengles.surface.rgb565,
|
||||
// so just use default EGLConfigChooser.
|
||||
// And we do not need depth buffer
|
||||
setEGLConfigChooser(false);
|
||||
setRenderer(this);
|
||||
// setFormat is done by SurfaceView in SDK 2.3 and newer.
|
||||
//if (ApiHelper.USE_888_PIXEL_FORMAT) {
|
||||
// getHolder().setFormat(PixelFormat.RGB_888);
|
||||
//} else {
|
||||
// getHolder().setFormat(PixelFormat.RGB_565);
|
||||
//}
|
||||
|
||||
// Uncomment this to enable gl error check.
|
||||
// setDebugFlags(DEBUG_CHECK_GL_ERROR);
|
||||
|
||||
// Uncomment this to enable gl error check.
|
||||
// setDebugFlags(DEBUG_CHECK_GL_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerLaunchedAnimation(CanvasAnimation animation) {
|
||||
// Register the newly launched animation so that we can set the start
|
||||
// time more precisely. (Usually, it takes much longer for first
|
||||
// rendering, so we set the animation start time as the time we
|
||||
// complete rendering)
|
||||
mAnimations.add(animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addOnGLIdleListener(OnGLIdleListener listener) {
|
||||
synchronized (mIdleListeners) {
|
||||
mIdleListeners.addLast(listener);
|
||||
mIdleRunner.enable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentPane(GLView content) {
|
||||
if (mContentView == content) return;
|
||||
if (mContentView != null) {
|
||||
if (mInDownState) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(
|
||||
now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
|
||||
mContentView.dispatchTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
mInDownState = false;
|
||||
}
|
||||
mContentView.detachFromRoot();
|
||||
BasicTexture.yieldAllTextures();
|
||||
}
|
||||
mContentView = content;
|
||||
if (content != null) {
|
||||
content.attachToRoot(this);
|
||||
requestLayoutContentPane();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestRenderForced() {
|
||||
superRequestRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestRender() {
|
||||
if (DEBUG_INVALIDATE) {
|
||||
StackTraceElement e = Thread.currentThread().getStackTrace()[4];
|
||||
String caller = e.getFileName() + ":" + e.getLineNumber() + " ";
|
||||
Log.d(TAG, "invalidate: " + caller);
|
||||
}
|
||||
if (mRenderRequested) return;
|
||||
mRenderRequested = true;
|
||||
if (ApiHelper.HAS_POST_ON_ANIMATION) {
|
||||
postOnAnimation(mRequestRenderOnAnimationFrame);
|
||||
} else {
|
||||
super.requestRender();
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable mRequestRenderOnAnimationFrame = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
superRequestRender();
|
||||
}
|
||||
};
|
||||
|
||||
private void superRequestRender() {
|
||||
super.requestRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayoutContentPane() {
|
||||
mRenderLock.lock();
|
||||
try {
|
||||
if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
|
||||
|
||||
// "View" system will invoke onLayout() for initialization(bug ?), we
|
||||
// have to ignore it since the GLThread is not ready yet.
|
||||
if ((mFlags & FLAG_INITIALIZED) == 0) return;
|
||||
|
||||
mFlags |= FLAG_NEED_LAYOUT;
|
||||
requestRender();
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void layoutContentPane() {
|
||||
mFlags &= ~FLAG_NEED_LAYOUT;
|
||||
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
int displayRotation = 0;
|
||||
int compensation = 0;
|
||||
|
||||
// Get the new orientation values
|
||||
if (mOrientationSource != null) {
|
||||
displayRotation = mOrientationSource.getDisplayRotation();
|
||||
compensation = mOrientationSource.getCompensation();
|
||||
} else {
|
||||
displayRotation = 0;
|
||||
compensation = 0;
|
||||
}
|
||||
|
||||
if (mCompensation != compensation) {
|
||||
mCompensation = compensation;
|
||||
if (mCompensation % 180 != 0) {
|
||||
mCompensationMatrix.setRotate(mCompensation);
|
||||
// move center to origin before rotation
|
||||
mCompensationMatrix.preTranslate(-w / 2, -h / 2);
|
||||
// align with the new origin after rotation
|
||||
mCompensationMatrix.postTranslate(h / 2, w / 2);
|
||||
} else {
|
||||
mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
|
||||
}
|
||||
}
|
||||
mDisplayRotation = displayRotation;
|
||||
|
||||
// Do the actual layout.
|
||||
if (mCompensation % 180 != 0) {
|
||||
int tmp = w;
|
||||
w = h;
|
||||
h = tmp;
|
||||
}
|
||||
Log.i(TAG, "layout content pane " + w + "x" + h + " (compensation " + mCompensation + ")");
|
||||
if (mContentView != null && w != 0 && h != 0) {
|
||||
mContentView.layout(0, 0, w, h);
|
||||
}
|
||||
// Uncomment this to dump the view hierarchy.
|
||||
//mContentView.dumpTree("");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(
|
||||
boolean changed, int left, int top, int right, int bottom) {
|
||||
if (changed) requestLayoutContentPane();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the context is created, possibly after automatic destruction.
|
||||
*/
|
||||
// This is a GLSurfaceView.Renderer callback
|
||||
@Override
|
||||
public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
|
||||
GL11 gl = (GL11) gl1;
|
||||
if (mGL != null) {
|
||||
// The GL Object has changed
|
||||
Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
|
||||
}
|
||||
mRenderLock.lock();
|
||||
try {
|
||||
mGL = gl;
|
||||
mCanvas = ApiHelper.HAS_GLES20_REQUIRED ? new GLES20Canvas() : new GLES11Canvas(gl);
|
||||
BasicTexture.invalidateAllTextures();
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
if (DEBUG_FPS || DEBUG_PROFILE) {
|
||||
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
|
||||
} else {
|
||||
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the OpenGL surface is recreated without destroying the
|
||||
* context.
|
||||
*/
|
||||
// This is a GLSurfaceView.Renderer callback
|
||||
@Override
|
||||
public void onSurfaceChanged(GL10 gl1, int width, int height) {
|
||||
Log.i(TAG, "onSurfaceChanged: " + width + "x" + height + ", gl10: " + gl1.toString());
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
|
||||
GalleryUtils.setRenderThread();
|
||||
if (DEBUG_PROFILE) {
|
||||
Log.d(TAG, "Start profiling");
|
||||
Profile.enable(20); // take a sample every 20ms
|
||||
}
|
||||
GL11 gl = (GL11) gl1;
|
||||
AssertUtils.assertTrue(mGL == gl);
|
||||
|
||||
mCanvas.setSize(width, height);
|
||||
}
|
||||
|
||||
private void outputFps() {
|
||||
long now = System.nanoTime();
|
||||
if (mFrameCountingStart == 0) {
|
||||
mFrameCountingStart = now;
|
||||
} else if ((now - mFrameCountingStart) > 1000000000) {
|
||||
Log.d(TAG, "fps: " + (double) mFrameCount
|
||||
* 1000000000 / (now - mFrameCountingStart));
|
||||
mFrameCountingStart = now;
|
||||
mFrameCount = 0;
|
||||
}
|
||||
++mFrameCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl) {
|
||||
AnimationTime.update();
|
||||
long t0;
|
||||
if (DEBUG_PROFILE_SLOW_ONLY) {
|
||||
Profile.hold();
|
||||
t0 = System.nanoTime();
|
||||
}
|
||||
mRenderLock.lock();
|
||||
|
||||
while (mFreeze) {
|
||||
mFreezeCondition.awaitUninterruptibly();
|
||||
}
|
||||
|
||||
try {
|
||||
onDrawFrameLocked(gl);
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
// We put a black cover View in front of the SurfaceView and hide it
|
||||
// after the first draw. This prevents the SurfaceView being transparent
|
||||
// before the first draw.
|
||||
//if (mFirstDraw) {
|
||||
// mFirstDraw = false;
|
||||
// post(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// View root = getRootView();
|
||||
// View cover = root.findViewById(R.id.gl_root_cover);
|
||||
// cover.setVisibility(GONE);
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
|
||||
if (DEBUG_PROFILE_SLOW_ONLY) {
|
||||
long t = System.nanoTime();
|
||||
long durationInMs = (t - mLastDrawFinishTime) / 1000000;
|
||||
long durationDrawInMs = (t - t0) / 1000000;
|
||||
mLastDrawFinishTime = t;
|
||||
|
||||
if (durationInMs > 34) { // 34ms -> we skipped at least 2 frames
|
||||
Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
|
||||
durationInMs + ") -----");
|
||||
Profile.commit();
|
||||
} else {
|
||||
Profile.drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onDrawFrameLocked(GL10 gl) {
|
||||
if (DEBUG_FPS) outputFps();
|
||||
|
||||
// release the unbound textures and deleted buffers.
|
||||
mCanvas.deleteRecycledResources();
|
||||
|
||||
// reset texture upload limit
|
||||
UploadedTexture.resetUploadLimit();
|
||||
|
||||
mRenderRequested = false;
|
||||
|
||||
if ((mOrientationSource != null
|
||||
&& mDisplayRotation != mOrientationSource.getDisplayRotation())
|
||||
|| (mFlags & FLAG_NEED_LAYOUT) != 0) {
|
||||
layoutContentPane();
|
||||
}
|
||||
|
||||
mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
|
||||
rotateCanvas(-mCompensation);
|
||||
if (mContentView != null) {
|
||||
mContentView.render(mCanvas);
|
||||
} else {
|
||||
// Make sure we always draw something to prevent displaying garbage
|
||||
mCanvas.clearBuffer();
|
||||
}
|
||||
mCanvas.restore();
|
||||
|
||||
if (!mAnimations.isEmpty()) {
|
||||
long now = AnimationTime.get();
|
||||
for (int i = 0, n = mAnimations.size(); i < n; i++) {
|
||||
mAnimations.get(i).setStartTime(now);
|
||||
}
|
||||
mAnimations.clear();
|
||||
}
|
||||
|
||||
if (UploadedTexture.uploadLimitReached()) {
|
||||
requestRender();
|
||||
}
|
||||
|
||||
synchronized (mIdleListeners) {
|
||||
if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
|
||||
}
|
||||
|
||||
if (DEBUG_INVALIDATE) {
|
||||
mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor);
|
||||
mInvalidateColor = ~mInvalidateColor;
|
||||
}
|
||||
|
||||
if (DEBUG_DRAWING_STAT) {
|
||||
mCanvas.dumpStatisticsAndClear();
|
||||
}
|
||||
}
|
||||
|
||||
private void rotateCanvas(int degrees) {
|
||||
if (degrees == 0) return;
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
int cx = w / 2;
|
||||
int cy = h / 2;
|
||||
mCanvas.translate(cx, cy);
|
||||
mCanvas.rotate(degrees, 0, 0, 1);
|
||||
if (degrees % 180 != 0) {
|
||||
mCanvas.translate(-cy, -cx);
|
||||
} else {
|
||||
mCanvas.translate(-cx, -cy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||
if (!isEnabled()) return false;
|
||||
|
||||
int action = event.getAction();
|
||||
if (action == MotionEvent.ACTION_CANCEL
|
||||
|| action == MotionEvent.ACTION_UP) {
|
||||
mInDownState = false;
|
||||
} else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mCompensation != 0) {
|
||||
event = MotionEventHelper.transformEvent(event, mCompensationMatrix);
|
||||
}
|
||||
|
||||
mRenderLock.lock();
|
||||
try {
|
||||
// If this has been detached from root, we don't need to handle event
|
||||
boolean handled = mContentView != null
|
||||
&& mContentView.dispatchTouchEvent(event);
|
||||
if (action == MotionEvent.ACTION_DOWN && handled) {
|
||||
mInDownState = true;
|
||||
}
|
||||
return handled;
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private class IdleRunner implements Runnable {
|
||||
// true if the idle runner is in the queue
|
||||
private boolean mActive = false;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
OnGLIdleListener listener;
|
||||
synchronized (mIdleListeners) {
|
||||
mActive = false;
|
||||
if (mIdleListeners.isEmpty()) return;
|
||||
listener = mIdleListeners.removeFirst();
|
||||
}
|
||||
mRenderLock.lock();
|
||||
boolean keepInQueue;
|
||||
try {
|
||||
keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested);
|
||||
} finally {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
synchronized (mIdleListeners) {
|
||||
if (keepInQueue) mIdleListeners.addLast(listener);
|
||||
if (!mRenderRequested && !mIdleListeners.isEmpty()) enable();
|
||||
}
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
// Who gets the flag can add it to the queue
|
||||
if (mActive) return;
|
||||
mActive = true;
|
||||
queueEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockRenderThread() {
|
||||
mRenderLock.lock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlockRenderThread() {
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
unfreeze();
|
||||
super.onPause();
|
||||
if (DEBUG_PROFILE) {
|
||||
Log.d(TAG, "Stop profiling");
|
||||
Profile.disableAll();
|
||||
Profile.dumpToFile("/sdcard/gallery.prof");
|
||||
Profile.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrientationSource(OrientationSource source) {
|
||||
mOrientationSource = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDisplayRotation() {
|
||||
return mDisplayRotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCompensation() {
|
||||
return mCompensation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matrix getCompensationMatrix() {
|
||||
return mCompensationMatrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void freeze() {
|
||||
mRenderLock.lock();
|
||||
mFreeze = true;
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unfreeze() {
|
||||
mRenderLock.lock();
|
||||
mFreeze = false;
|
||||
mFreezeCondition.signalAll();
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public void setLightsOutMode(boolean enabled) {
|
||||
if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
|
||||
|
||||
int flags = 0;
|
||||
if (enabled) {
|
||||
flags = STATUS_BAR_HIDDEN;
|
||||
if (ApiHelper.HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE) {
|
||||
flags |= (SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
}
|
||||
}
|
||||
setSystemUiVisibility(flags);
|
||||
}
|
||||
|
||||
// We need to unfreeze in the following methods and in onPause().
|
||||
// These methods will wait on GLThread. If we have freezed the GLRootView,
|
||||
// the GLThread will wait on main thread to call unfreeze and cause dead
|
||||
// lock.
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
|
||||
unfreeze();
|
||||
super.surfaceChanged(holder, format, w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
unfreeze();
|
||||
super.surfaceCreated(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
unfreeze();
|
||||
super.surfaceDestroyed(holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
unfreeze();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
unfreeze();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import com.hippo.ehviewer.gallery.anim.CanvasAnimation;
|
||||
import com.hippo.ehviewer.gallery.anim.StateTransitionAnimation;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// GLView is a UI component. It can render to a GLCanvas and accept touch
|
||||
// events. A GLView may have zero or more child GLView and they form a tree
|
||||
// structure. The rendering and event handling will pass through the tree
|
||||
// structure.
|
||||
//
|
||||
// A GLView tree should be attached to a GLRoot before event dispatching and
|
||||
// rendering happens. GLView asks GLRoot to re-render or re-layout the
|
||||
// GLView hierarchy using requestRender() and requestLayoutContentPane().
|
||||
//
|
||||
// The render() method is called in a separate thread. Before calling
|
||||
// dispatchTouchEvent() and layout(), GLRoot acquires a lock to avoid the
|
||||
// rendering thread running at the same time. If there are other entry points
|
||||
// from main thread (like a Handler) in your GLView, you need to call
|
||||
// lockRendering() if the rendering thread should not run at the same time.
|
||||
//
|
||||
public class GLView {
|
||||
private static final String TAG = "GLView";
|
||||
|
||||
public static final int VISIBLE = 0;
|
||||
public static final int INVISIBLE = 1;
|
||||
|
||||
private static final int FLAG_INVISIBLE = 1;
|
||||
private static final int FLAG_SET_MEASURED_SIZE = 2;
|
||||
private static final int FLAG_LAYOUT_REQUESTED = 4;
|
||||
|
||||
public interface OnClickListener {
|
||||
void onClick(GLView v);
|
||||
}
|
||||
|
||||
protected final Rect mBounds = new Rect();
|
||||
protected final Rect mPaddings = new Rect();
|
||||
|
||||
private GLRoot mRoot;
|
||||
protected GLView mParent;
|
||||
private ArrayList<GLView> mComponents;
|
||||
private GLView mMotionTarget;
|
||||
|
||||
private CanvasAnimation mAnimation;
|
||||
|
||||
private int mViewFlags = 0;
|
||||
|
||||
protected int mMeasuredWidth = 0;
|
||||
protected int mMeasuredHeight = 0;
|
||||
|
||||
private int mLastWidthSpec = -1;
|
||||
private int mLastHeightSpec = -1;
|
||||
|
||||
protected int mScrollY = 0;
|
||||
protected int mScrollX = 0;
|
||||
protected int mScrollHeight = 0;
|
||||
protected int mScrollWidth = 0;
|
||||
|
||||
private float [] mBackgroundColor;
|
||||
private StateTransitionAnimation mTransition;
|
||||
|
||||
public void startAnimation(CanvasAnimation animation) {
|
||||
GLRoot root = getGLRoot();
|
||||
if (root == null) throw new IllegalStateException();
|
||||
mAnimation = animation;
|
||||
if (mAnimation != null) {
|
||||
mAnimation.start();
|
||||
root.registerLaunchedAnimation(mAnimation);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// Sets the visiblity of this GLView (either GLView.VISIBLE or
|
||||
// GLView.INVISIBLE).
|
||||
public void setVisibility(int visibility) {
|
||||
if (visibility == getVisibility()) return;
|
||||
if (visibility == VISIBLE) {
|
||||
mViewFlags &= ~FLAG_INVISIBLE;
|
||||
} else {
|
||||
mViewFlags |= FLAG_INVISIBLE;
|
||||
}
|
||||
onVisibilityChanged(visibility);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// Returns GLView.VISIBLE or GLView.INVISIBLE
|
||||
public int getVisibility() {
|
||||
return (mViewFlags & FLAG_INVISIBLE) == 0 ? VISIBLE : INVISIBLE;
|
||||
}
|
||||
|
||||
// This should only be called on the content pane (the topmost GLView).
|
||||
public void attachToRoot(GLRoot root) {
|
||||
AssertUtils.assertTrue(mParent == null && mRoot == null);
|
||||
onAttachToRoot(root);
|
||||
}
|
||||
|
||||
// This should only be called on the content pane (the topmost GLView).
|
||||
public void detachFromRoot() {
|
||||
AssertUtils.assertTrue(mParent == null && mRoot != null);
|
||||
onDetachFromRoot();
|
||||
}
|
||||
|
||||
// Returns the number of children of the GLView.
|
||||
public int getComponentCount() {
|
||||
return mComponents == null ? 0 : mComponents.size();
|
||||
}
|
||||
|
||||
// Returns the children for the given index.
|
||||
public GLView getComponent(int index) {
|
||||
if (mComponents == null) {
|
||||
throw new ArrayIndexOutOfBoundsException(index);
|
||||
}
|
||||
return mComponents.get(index);
|
||||
}
|
||||
|
||||
// Adds a child to this GLView.
|
||||
public void addComponent(GLView component) {
|
||||
// Make sure the component doesn't have a parent currently.
|
||||
if (component.mParent != null) throw new IllegalStateException();
|
||||
|
||||
// Build parent-child links
|
||||
if (mComponents == null) {
|
||||
mComponents = new ArrayList<GLView>();
|
||||
}
|
||||
mComponents.add(component);
|
||||
component.mParent = this;
|
||||
|
||||
// If this is added after we have a root, tell the component.
|
||||
if (mRoot != null) {
|
||||
component.onAttachToRoot(mRoot);
|
||||
}
|
||||
}
|
||||
|
||||
// Removes a child from this GLView.
|
||||
public boolean removeComponent(GLView component) {
|
||||
if (mComponents == null) return false;
|
||||
if (mComponents.remove(component)) {
|
||||
removeOneComponent(component);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Removes all children of this GLView.
|
||||
public void removeAllComponents() {
|
||||
for (int i = 0, n = mComponents.size(); i < n; ++i) {
|
||||
removeOneComponent(mComponents.get(i));
|
||||
}
|
||||
mComponents.clear();
|
||||
}
|
||||
|
||||
private void removeOneComponent(GLView component) {
|
||||
if (mMotionTarget == component) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(
|
||||
now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
|
||||
dispatchTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
}
|
||||
component.onDetachFromRoot();
|
||||
component.mParent = null;
|
||||
}
|
||||
|
||||
public Rect bounds() {
|
||||
return mBounds;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return mBounds.right - mBounds.left;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return mBounds.bottom - mBounds.top;
|
||||
}
|
||||
|
||||
public GLRoot getGLRoot() {
|
||||
return mRoot;
|
||||
}
|
||||
|
||||
// Request re-rendering of the view hierarchy.
|
||||
// This is used for animation or when the contents changed.
|
||||
public void invalidate() {
|
||||
GLRoot root = getGLRoot();
|
||||
if (root != null) root.requestRender();
|
||||
}
|
||||
|
||||
// Request re-layout of the view hierarchy.
|
||||
public void requestLayout() {
|
||||
mViewFlags |= FLAG_LAYOUT_REQUESTED;
|
||||
mLastHeightSpec = -1;
|
||||
mLastWidthSpec = -1;
|
||||
if (mParent != null) {
|
||||
mParent.requestLayout();
|
||||
} else {
|
||||
// Is this a content pane ?
|
||||
GLRoot root = getGLRoot();
|
||||
if (root != null) root.requestLayoutContentPane();
|
||||
}
|
||||
}
|
||||
|
||||
protected void render(GLCanvas canvas) {
|
||||
boolean transitionActive = false;
|
||||
if (mTransition != null && mTransition.calculate(AnimationTime.get())) {
|
||||
invalidate();
|
||||
transitionActive = mTransition.isActive();
|
||||
}
|
||||
renderBackground(canvas);
|
||||
canvas.save();
|
||||
if (transitionActive) {
|
||||
mTransition.applyContentTransform(this, canvas);
|
||||
}
|
||||
for (int i = 0, n = getComponentCount(); i < n; ++i) {
|
||||
renderChild(canvas, getComponent(i));
|
||||
}
|
||||
canvas.restore();
|
||||
if (transitionActive) {
|
||||
mTransition.applyOverlay(this, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public void setIntroAnimation(StateTransitionAnimation intro) {
|
||||
mTransition = intro;
|
||||
if (mTransition != null) mTransition.start();
|
||||
}
|
||||
|
||||
public float [] getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
public void setBackgroundColor(float [] color) {
|
||||
mBackgroundColor = color;
|
||||
}
|
||||
|
||||
protected void renderBackground(GLCanvas view) {
|
||||
if (mBackgroundColor != null) {
|
||||
view.clearBuffer(mBackgroundColor);
|
||||
}
|
||||
if (mTransition != null && mTransition.isActive()) {
|
||||
mTransition.applyBackground(this, view);
|
||||
}
|
||||
}
|
||||
|
||||
protected void renderChild(GLCanvas canvas, GLView component) {
|
||||
if (component.getVisibility() != GLView.VISIBLE
|
||||
&& component.mAnimation == null) return;
|
||||
|
||||
int xoffset = component.mBounds.left - mScrollX;
|
||||
int yoffset = component.mBounds.top - mScrollY;
|
||||
|
||||
canvas.translate(xoffset, yoffset);
|
||||
|
||||
CanvasAnimation anim = component.mAnimation;
|
||||
if (anim != null) {
|
||||
canvas.save(anim.getCanvasSaveFlags());
|
||||
if (anim.calculate(AnimationTime.get())) {
|
||||
invalidate();
|
||||
} else {
|
||||
component.mAnimation = null;
|
||||
}
|
||||
anim.apply(canvas);
|
||||
}
|
||||
component.render(canvas);
|
||||
if (anim != null) canvas.restore();
|
||||
canvas.translate(-xoffset, -yoffset);
|
||||
}
|
||||
|
||||
protected boolean onTouch(MotionEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean dispatchTouchEvent(MotionEvent event,
|
||||
int x, int y, GLView component, boolean checkBounds) {
|
||||
Rect rect = component.mBounds;
|
||||
int left = rect.left;
|
||||
int top = rect.top;
|
||||
if (!checkBounds || rect.contains(x, y)) {
|
||||
event.offsetLocation(-left, -top);
|
||||
if (component.dispatchTouchEvent(event)) {
|
||||
event.offsetLocation(left, top);
|
||||
return true;
|
||||
}
|
||||
event.offsetLocation(left, top);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean dispatchTouchEvent(MotionEvent event) {
|
||||
int x = (int) event.getX();
|
||||
int y = (int) event.getY();
|
||||
int action = event.getAction();
|
||||
if (mMotionTarget != null) {
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
MotionEvent cancel = MotionEvent.obtain(event);
|
||||
cancel.setAction(MotionEvent.ACTION_CANCEL);
|
||||
dispatchTouchEvent(cancel, x, y, mMotionTarget, false);
|
||||
mMotionTarget = null;
|
||||
} else {
|
||||
dispatchTouchEvent(event, x, y, mMotionTarget, false);
|
||||
if (action == MotionEvent.ACTION_CANCEL
|
||||
|| action == MotionEvent.ACTION_UP) {
|
||||
mMotionTarget = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
// in the reverse rendering order
|
||||
for (int i = getComponentCount() - 1; i >= 0; --i) {
|
||||
GLView component = getComponent(i);
|
||||
if (component.getVisibility() != GLView.VISIBLE) continue;
|
||||
if (dispatchTouchEvent(event, x, y, component, true)) {
|
||||
mMotionTarget = component;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return onTouch(event);
|
||||
}
|
||||
|
||||
public Rect getPaddings() {
|
||||
return mPaddings;
|
||||
}
|
||||
|
||||
public void layout(int left, int top, int right, int bottom) {
|
||||
boolean sizeChanged = setBounds(left, top, right, bottom);
|
||||
mViewFlags &= ~FLAG_LAYOUT_REQUESTED;
|
||||
// We call onLayout no matter sizeChanged is true or not because the
|
||||
// orientation may change without changing the size of the View (for
|
||||
// example, rotate the device by 180 degrees), and we want to handle
|
||||
// orientation change in onLayout.
|
||||
onLayout(sizeChanged, left, top, right, bottom);
|
||||
}
|
||||
|
||||
private boolean setBounds(int left, int top, int right, int bottom) {
|
||||
boolean sizeChanged = (right - left) != (mBounds.right - mBounds.left)
|
||||
|| (bottom - top) != (mBounds.bottom - mBounds.top);
|
||||
mBounds.set(left, top, right, bottom);
|
||||
return sizeChanged;
|
||||
}
|
||||
|
||||
public void measure(int widthSpec, int heightSpec) {
|
||||
if (widthSpec == mLastWidthSpec && heightSpec == mLastHeightSpec
|
||||
&& (mViewFlags & FLAG_LAYOUT_REQUESTED) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mLastWidthSpec = widthSpec;
|
||||
mLastHeightSpec = heightSpec;
|
||||
|
||||
mViewFlags &= ~FLAG_SET_MEASURED_SIZE;
|
||||
onMeasure(widthSpec, heightSpec);
|
||||
if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) {
|
||||
throw new IllegalStateException(getClass().getName()
|
||||
+ " should call setMeasuredSize() in onMeasure()");
|
||||
}
|
||||
}
|
||||
|
||||
protected void onMeasure(int widthSpec, int heightSpec) {
|
||||
}
|
||||
|
||||
protected void setMeasuredSize(int width, int height) {
|
||||
mViewFlags |= FLAG_SET_MEASURED_SIZE;
|
||||
mMeasuredWidth = width;
|
||||
mMeasuredHeight = height;
|
||||
}
|
||||
|
||||
public int getMeasuredWidth() {
|
||||
return mMeasuredWidth;
|
||||
}
|
||||
|
||||
public int getMeasuredHeight() {
|
||||
return mMeasuredHeight;
|
||||
}
|
||||
|
||||
protected void onLayout(
|
||||
boolean changeSize, int left, int top, int right, int bottom) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bounds of the given descendant that relative to this view.
|
||||
*/
|
||||
public boolean getBoundsOf(GLView descendant, Rect out) {
|
||||
int xoffset = 0;
|
||||
int yoffset = 0;
|
||||
GLView view = descendant;
|
||||
while (view != this) {
|
||||
if (view == null) return false;
|
||||
Rect bounds = view.mBounds;
|
||||
xoffset += bounds.left;
|
||||
yoffset += bounds.top;
|
||||
view = view.mParent;
|
||||
}
|
||||
out.set(xoffset, yoffset, xoffset + descendant.getWidth(),
|
||||
yoffset + descendant.getHeight());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void onVisibilityChanged(int visibility) {
|
||||
for (int i = 0, n = getComponentCount(); i < n; ++i) {
|
||||
GLView child = getComponent(i);
|
||||
if (child.getVisibility() == GLView.VISIBLE) {
|
||||
child.onVisibilityChanged(visibility);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onAttachToRoot(GLRoot root) {
|
||||
mRoot = root;
|
||||
for (int i = 0, n = getComponentCount(); i < n; ++i) {
|
||||
getComponent(i).onAttachToRoot(root);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onDetachFromRoot() {
|
||||
for (int i = 0, n = getComponentCount(); i < n; ++i) {
|
||||
getComponent(i).onDetachFromRoot();
|
||||
}
|
||||
mRoot = null;
|
||||
}
|
||||
|
||||
public void lockRendering() {
|
||||
if (mRoot != null) {
|
||||
mRoot.lockRenderThread();
|
||||
}
|
||||
}
|
||||
|
||||
public void unlockRendering() {
|
||||
if (mRoot != null) {
|
||||
mRoot.unlockRenderThread();
|
||||
}
|
||||
}
|
||||
|
||||
// This is for debugging only.
|
||||
// Dump the view hierarchy into log.
|
||||
void dumpTree(String prefix) {
|
||||
Log.d(TAG, prefix + getClass().getSimpleName());
|
||||
for (int i = 0, n = getComponentCount(); i < n; ++i) {
|
||||
getComponent(i).dumpTree(prefix + "....");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.ui;
|
||||
|
||||
public interface OrientationSource {
|
||||
int getDisplayRotation();
|
||||
int getCompensation();
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.util;
|
||||
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.ComponentName;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore.MediaColumns;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ApiHelper {
|
||||
public static interface VERSION_CODES {
|
||||
// These value are copied from Build.VERSION_CODES
|
||||
public static final int GINGERBREAD_MR1 = 10;
|
||||
public static final int HONEYCOMB = 11;
|
||||
public static final int HONEYCOMB_MR1 = 12;
|
||||
public static final int HONEYCOMB_MR2 = 13;
|
||||
public static final int ICE_CREAM_SANDWICH = 14;
|
||||
public static final int ICE_CREAM_SANDWICH_MR1 = 15;
|
||||
public static final int JELLY_BEAN = 16;
|
||||
public static final int JELLY_BEAN_MR1 = 17;
|
||||
public static final int JELLY_BEAN_MR2 = 18;
|
||||
}
|
||||
|
||||
public static final boolean AT_LEAST_16 = Build.VERSION.SDK_INT >= 16;
|
||||
|
||||
public static final boolean USE_888_PIXEL_FORMAT =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
|
||||
public static final boolean ENABLE_PHOTO_EDITOR =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
|
||||
public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_LAYOUT_STABLE =
|
||||
hasField(View.class, "SYSTEM_UI_FLAG_LAYOUT_STABLE");
|
||||
|
||||
public static final boolean HAS_VIEW_SYSTEM_UI_FLAG_HIDE_NAVIGATION =
|
||||
hasField(View.class, "SYSTEM_UI_FLAG_HIDE_NAVIGATION");
|
||||
|
||||
public static final boolean HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT =
|
||||
hasField(MediaColumns.class, "WIDTH");
|
||||
|
||||
public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
|
||||
public static final boolean HAS_REUSING_BITMAP_IN_BITMAP_FACTORY =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_SET_BEAM_PUSH_URIS =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
|
||||
public static final boolean HAS_SET_DEFALT_BUFFER_SIZE = hasMethod(
|
||||
"android.graphics.SurfaceTexture", "setDefaultBufferSize",
|
||||
int.class, int.class);
|
||||
|
||||
public static final boolean HAS_RELEASE_SURFACE_TEXTURE = hasMethod(
|
||||
"android.graphics.SurfaceTexture", "release");
|
||||
|
||||
public static final boolean HAS_SURFACE_TEXTURE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_MTP =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
|
||||
|
||||
public static final boolean HAS_AUTO_FOCUS_MOVE_CALLBACK =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
|
||||
public static final boolean HAS_REMOTE_VIEWS_SERVICE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_INTENT_EXTRA_LOCAL_ONLY =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_SET_SYSTEM_UI_VISIBILITY =
|
||||
hasMethod(View.class, "setSystemUiVisibility", int.class);
|
||||
|
||||
public static final boolean HAS_FACE_DETECTION;
|
||||
static {
|
||||
boolean hasFaceDetection = false;
|
||||
try {
|
||||
Class<?> listenerClass = Class.forName(
|
||||
"android.hardware.Camera$FaceDetectionListener");
|
||||
hasFaceDetection =
|
||||
hasMethod(Camera.class, "setFaceDetectionListener", listenerClass) &&
|
||||
hasMethod(Camera.class, "startFaceDetection") &&
|
||||
hasMethod(Camera.class, "stopFaceDetection") &&
|
||||
hasMethod(Camera.Parameters.class, "getMaxNumDetectedFaces");
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
HAS_FACE_DETECTION = hasFaceDetection;
|
||||
}
|
||||
|
||||
public static final boolean HAS_GET_CAMERA_DISABLED =
|
||||
hasMethod(DevicePolicyManager.class, "getCameraDisabled", ComponentName.class);
|
||||
|
||||
public static final boolean HAS_MEDIA_ACTION_SOUND =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
|
||||
public static final boolean HAS_TIME_LAPSE_RECORDING =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_ZOOM_WHEN_RECORDING =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
|
||||
public static final boolean HAS_CAMERA_FOCUS_AREA =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
|
||||
public static final boolean HAS_CAMERA_METERING_AREA =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
|
||||
public static final boolean HAS_MOTION_EVENT_TRANSFORM =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_EFFECTS_RECORDING = false;
|
||||
|
||||
// "Background" filter does not have "context" input port in jelly bean.
|
||||
public static final boolean HAS_EFFECTS_RECORDING_CONTEXT_INPUT =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1;
|
||||
|
||||
public static final boolean HAS_GET_SUPPORTED_VIDEO_SIZE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_SET_ICON_ATTRIBUTE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_MEDIA_PROVIDER_FILES_TABLE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_SURFACE_TEXTURE_RECORDING =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
|
||||
public static final boolean HAS_ACTION_BAR =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
// Ex: View.setTranslationX.
|
||||
public static final boolean HAS_VIEW_TRANSFORM_PROPERTIES =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_CAMERA_HDR =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1;
|
||||
|
||||
public static final boolean HAS_OPTIONS_IN_MUTABLE =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean CAN_START_PREVIEW_IN_JPEG_CALLBACK =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH;
|
||||
|
||||
public static final boolean HAS_VIEW_PROPERTY_ANIMATOR =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1;
|
||||
|
||||
public static final boolean HAS_POST_ON_ANIMATION =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
|
||||
public static final boolean HAS_ANNOUNCE_FOR_ACCESSIBILITY =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
|
||||
public static final boolean HAS_OBJECT_ANIMATION =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_GLES20_REQUIRED =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
|
||||
|
||||
public static final boolean HAS_ROTATION_ANIMATION =
|
||||
hasField(WindowManager.LayoutParams.class, "rotationAnimation");
|
||||
|
||||
public static final boolean HAS_ORIENTATION_LOCK =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2;
|
||||
|
||||
public static final boolean HAS_CANCELLATION_SIGNAL =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
|
||||
|
||||
public static final boolean HAS_MEDIA_MUXER =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2;
|
||||
|
||||
public static final boolean HAS_DISPLAY_LISTENER =
|
||||
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1;
|
||||
|
||||
public static int getIntFieldIfExists(Class<?> klass, String fieldName,
|
||||
Class<?> obj, int defaultVal) {
|
||||
try {
|
||||
Field f = klass.getDeclaredField(fieldName);
|
||||
return f.getInt(obj);
|
||||
} catch (Exception e) {
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasField(Class<?> klass, String fieldName) {
|
||||
try {
|
||||
klass.getDeclaredField(fieldName);
|
||||
return true;
|
||||
} catch (NoSuchFieldException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasMethod(String className, String methodName,
|
||||
Class<?>... parameterTypes) {
|
||||
try {
|
||||
Class<?> klass = Class.forName(className);
|
||||
klass.getDeclaredMethod(methodName, parameterTypes);
|
||||
return true;
|
||||
} catch (Throwable th) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasMethod(
|
||||
Class<?> klass, String methodName, Class<?> ... paramTypes) {
|
||||
try {
|
||||
klass.getDeclaredMethod(methodName, paramTypes);
|
||||
return true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.util;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.util.Log;
|
||||
|
||||
public class GalleryUtils {
|
||||
|
||||
private static final String TAG = "GalleryUtils";
|
||||
|
||||
public static float[] intColorToFloatARGBArray(int from) {
|
||||
return new float[] {
|
||||
Color.alpha(from) / 255f,
|
||||
Color.red(from) / 255f,
|
||||
Color.green(from) / 255f,
|
||||
Color.blue(from) / 255f
|
||||
};
|
||||
}
|
||||
|
||||
// Below are used the detect using database in the render thread. It only
|
||||
// works most of the time, but that's ok because it's for debugging only.
|
||||
|
||||
private static volatile Thread sCurrentThread;
|
||||
private static volatile boolean sWarned;
|
||||
|
||||
public static void setRenderThread() {
|
||||
sCurrentThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
public static void assertNotInRenderThread() {
|
||||
if (!sWarned) {
|
||||
if (Thread.currentThread() == sCurrentThread) {
|
||||
sWarned = true;
|
||||
Log.w(TAG, new Throwable("Should not do this in render thread"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.util;
|
||||
|
||||
public class IntArray {
|
||||
private static final int INIT_CAPACITY = 8;
|
||||
|
||||
private int mData[] = new int[INIT_CAPACITY];
|
||||
private int mSize = 0;
|
||||
|
||||
public void add(int value) {
|
||||
if (mData.length == mSize) {
|
||||
int temp[] = new int[mSize + mSize];
|
||||
System.arraycopy(mData, 0, temp, 0, mSize);
|
||||
mData = temp;
|
||||
}
|
||||
mData[mSize++] = value;
|
||||
}
|
||||
|
||||
public int removeLast() {
|
||||
mSize--;
|
||||
return mData[mSize];
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
// For testing only
|
||||
public int[] toArray(int[] result) {
|
||||
if (result == null || result.length < mSize) {
|
||||
result = new int[mSize];
|
||||
}
|
||||
System.arraycopy(mData, 0, result, 0, mSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] getInternalArray() {
|
||||
return mData;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mSize = 0;
|
||||
if (mData.length != INIT_CAPACITY) mData = new int[INIT_CAPACITY];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.graphics.Matrix;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.MotionEvent.PointerCoords;
|
||||
|
||||
public final class MotionEventHelper {
|
||||
private MotionEventHelper() {}
|
||||
|
||||
public static MotionEvent transformEvent(MotionEvent e, Matrix m) {
|
||||
// We try to use the new transform method if possible because it uses
|
||||
// less memory.
|
||||
if (ApiHelper.HAS_MOTION_EVENT_TRANSFORM) {
|
||||
return transformEventNew(e, m);
|
||||
} else {
|
||||
return transformEventOld(e, m);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
|
||||
private static MotionEvent transformEventNew(MotionEvent e, Matrix m) {
|
||||
MotionEvent newEvent = MotionEvent.obtain(e);
|
||||
newEvent.transform(m);
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
// This is copied from Input.cpp in the android framework.
|
||||
private static MotionEvent transformEventOld(MotionEvent e, Matrix m) {
|
||||
long downTime = e.getDownTime();
|
||||
long eventTime = e.getEventTime();
|
||||
int action = e.getAction();
|
||||
int pointerCount = e.getPointerCount();
|
||||
int[] pointerIds = getPointerIds(e);
|
||||
PointerCoords[] pointerCoords = getPointerCoords(e);
|
||||
int metaState = e.getMetaState();
|
||||
float xPrecision = e.getXPrecision();
|
||||
float yPrecision = e.getYPrecision();
|
||||
int deviceId = e.getDeviceId();
|
||||
int edgeFlags = e.getEdgeFlags();
|
||||
int source = e.getSource();
|
||||
int flags = e.getFlags();
|
||||
|
||||
// Copy the x and y coordinates into an array, map them, and copy back.
|
||||
float[] xy = new float[pointerCoords.length * 2];
|
||||
for (int i = 0; i < pointerCount;i++) {
|
||||
xy[2 * i] = pointerCoords[i].x;
|
||||
xy[2 * i + 1] = pointerCoords[i].y;
|
||||
}
|
||||
m.mapPoints(xy);
|
||||
for (int i = 0; i < pointerCount;i++) {
|
||||
pointerCoords[i].x = xy[2 * i];
|
||||
pointerCoords[i].y = xy[2 * i + 1];
|
||||
pointerCoords[i].orientation = transformAngle(
|
||||
m, pointerCoords[i].orientation);
|
||||
}
|
||||
|
||||
MotionEvent n = MotionEvent.obtain(downTime, eventTime, action,
|
||||
pointerCount, pointerIds, pointerCoords, metaState, xPrecision,
|
||||
yPrecision, deviceId, edgeFlags, source, flags);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
private static int[] getPointerIds(MotionEvent e) {
|
||||
int n = e.getPointerCount();
|
||||
int[] r = new int[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = e.getPointerId(i);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private static PointerCoords[] getPointerCoords(MotionEvent e) {
|
||||
int n = e.getPointerCount();
|
||||
PointerCoords[] r = new PointerCoords[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
r[i] = new PointerCoords();
|
||||
e.getPointerCoords(i, r[i]);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private static float transformAngle(Matrix m, float angleRadians) {
|
||||
// Construct and transform a vector oriented at the specified clockwise
|
||||
// angle from vertical. Coordinate system: down is increasing Y, right is
|
||||
// increasing X.
|
||||
float[] v = new float[2];
|
||||
v[0] = (float) Math.sin(angleRadians);
|
||||
v[1] = (float) -Math.cos(angleRadians);
|
||||
m.mapVectors(v);
|
||||
|
||||
// Derive the transformed vector's clockwise angle from vertical.
|
||||
float result = (float) Math.atan2(v[0], -v[1]);
|
||||
if (result < -Math.PI / 2) {
|
||||
result += Math.PI;
|
||||
} else if (result > Math.PI / 2) {
|
||||
result -= Math.PI;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.util;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
// The Profile class is used to collect profiling information for a thread. It
|
||||
// samples stack traces for a thread periodically. enable() and disable() is
|
||||
// used to enable and disable profiling for the calling thread. The profiling
|
||||
// information can then be dumped to a file using the dumpToFile() method.
|
||||
//
|
||||
// The disableAll() method can be used to disable profiling for all threads and
|
||||
// can be called in onPause() to ensure all profiling is disabled when an
|
||||
// activity is paused.
|
||||
public class Profile {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "Profile";
|
||||
private static final int NS_PER_MS = 1000000;
|
||||
|
||||
// This is a watchdog entry for one thread.
|
||||
// For every cycleTime period, we dump the stack of the thread.
|
||||
private static class WatchEntry {
|
||||
Thread thread;
|
||||
|
||||
// Both are in milliseconds
|
||||
int cycleTime;
|
||||
int wakeTime;
|
||||
|
||||
boolean isHolding;
|
||||
ArrayList<String[]> holdingStacks = new ArrayList<>();
|
||||
}
|
||||
|
||||
// This is a watchdog thread which dumps stacks of other threads periodically.
|
||||
private static Watchdog sWatchdog = new Watchdog();
|
||||
|
||||
private static class Watchdog {
|
||||
private ArrayList<WatchEntry> mList = new ArrayList<>();
|
||||
private HandlerThread mHandlerThread;
|
||||
private Handler mHandler;
|
||||
private Runnable mProcessRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (Watchdog.this) {
|
||||
processList();
|
||||
}
|
||||
}
|
||||
};
|
||||
private Random mRandom = new Random();
|
||||
private ProfileData mProfileData = new ProfileData();
|
||||
|
||||
public Watchdog() {
|
||||
mHandlerThread = new HandlerThread("Watchdog Handler",
|
||||
Process.THREAD_PRIORITY_FOREGROUND);
|
||||
mHandlerThread.start();
|
||||
mHandler = new Handler(mHandlerThread.getLooper());
|
||||
}
|
||||
|
||||
public synchronized void addWatchEntry(Thread thread, int cycleTime) {
|
||||
WatchEntry e = new WatchEntry();
|
||||
e.thread = thread;
|
||||
e.cycleTime = cycleTime;
|
||||
int firstDelay = 1 + mRandom.nextInt(cycleTime);
|
||||
e.wakeTime = (int) (System.nanoTime() / NS_PER_MS) + firstDelay;
|
||||
mList.add(e);
|
||||
processList();
|
||||
}
|
||||
|
||||
public synchronized void removeWatchEntry(Thread thread) {
|
||||
for (int i = 0; i < mList.size(); i++) {
|
||||
if (mList.get(i).thread == thread) {
|
||||
mList.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
processList();
|
||||
}
|
||||
|
||||
public synchronized void removeAllWatchEntries() {
|
||||
mList.clear();
|
||||
processList();
|
||||
}
|
||||
|
||||
private void processList() {
|
||||
mHandler.removeCallbacks(mProcessRunnable);
|
||||
if (mList.size() == 0) return;
|
||||
|
||||
int currentTime = (int) (System.nanoTime() / NS_PER_MS);
|
||||
int nextWakeTime = 0;
|
||||
|
||||
for (WatchEntry entry : mList) {
|
||||
if (currentTime > entry.wakeTime) {
|
||||
entry.wakeTime += entry.cycleTime;
|
||||
sampleStack(entry);
|
||||
}
|
||||
|
||||
if (entry.wakeTime > nextWakeTime) {
|
||||
nextWakeTime = entry.wakeTime;
|
||||
}
|
||||
}
|
||||
|
||||
long delay = nextWakeTime - currentTime;
|
||||
mHandler.postDelayed(mProcessRunnable, delay);
|
||||
}
|
||||
|
||||
private void sampleStack(WatchEntry entry) {
|
||||
Thread thread = entry.thread;
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
String[] lines = new String[stack.length];
|
||||
for (int i = 0; i < stack.length; i++) {
|
||||
lines[i] = stack[i].toString();
|
||||
}
|
||||
if (entry.isHolding) {
|
||||
entry.holdingStacks.add(lines);
|
||||
} else {
|
||||
mProfileData.addSample(lines);
|
||||
}
|
||||
}
|
||||
|
||||
private WatchEntry findEntry(Thread thread) {
|
||||
for (int i = 0; i < mList.size(); i++) {
|
||||
WatchEntry entry = mList.get(i);
|
||||
if (entry.thread == thread) return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized void dumpToFile(String filename) {
|
||||
mProfileData.dumpToFile(filename);
|
||||
}
|
||||
|
||||
public synchronized void reset() {
|
||||
mProfileData.reset();
|
||||
}
|
||||
|
||||
public synchronized void hold(Thread t) {
|
||||
WatchEntry entry = findEntry(t);
|
||||
|
||||
// This can happen if the profiling is disabled (probably from
|
||||
// another thread). Same check is applied in commit() and drop()
|
||||
// below.
|
||||
if (entry == null) return;
|
||||
|
||||
entry.isHolding = true;
|
||||
}
|
||||
|
||||
public synchronized void commit(Thread t) {
|
||||
WatchEntry entry = findEntry(t);
|
||||
if (entry == null) return;
|
||||
ArrayList<String[]> stacks = entry.holdingStacks;
|
||||
for (int i = 0; i < stacks.size(); i++) {
|
||||
mProfileData.addSample(stacks.get(i));
|
||||
}
|
||||
entry.isHolding = false;
|
||||
entry.holdingStacks.clear();
|
||||
}
|
||||
|
||||
public synchronized void drop(Thread t) {
|
||||
WatchEntry entry = findEntry(t);
|
||||
if (entry == null) return;
|
||||
entry.isHolding = false;
|
||||
entry.holdingStacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Enable profiling for the calling thread. Periodically (every cycleTimeInMs
|
||||
// milliseconds) sample the stack trace of the calling thread.
|
||||
public static void enable(int cycleTimeInMs) {
|
||||
Thread t = Thread.currentThread();
|
||||
sWatchdog.addWatchEntry(t, cycleTimeInMs);
|
||||
}
|
||||
|
||||
// Disable profiling for the calling thread.
|
||||
public static void disable() {
|
||||
sWatchdog.removeWatchEntry(Thread.currentThread());
|
||||
}
|
||||
|
||||
// Disable profiling for all threads.
|
||||
public static void disableAll() {
|
||||
sWatchdog.removeAllWatchEntries();
|
||||
}
|
||||
|
||||
// Dump the profiling data to a file.
|
||||
public static void dumpToFile(String filename) {
|
||||
sWatchdog.dumpToFile(filename);
|
||||
}
|
||||
|
||||
// Reset the collected profiling data.
|
||||
public static void reset() {
|
||||
sWatchdog.reset();
|
||||
}
|
||||
|
||||
// Hold the future samples coming from current thread until commit() or
|
||||
// drop() is called, and those samples are recorded or ignored as a result.
|
||||
// This must called after enable() to be effective.
|
||||
public static void hold() {
|
||||
sWatchdog.hold(Thread.currentThread());
|
||||
}
|
||||
|
||||
public static void commit() {
|
||||
sWatchdog.commit(Thread.currentThread());
|
||||
}
|
||||
|
||||
public static void drop() {
|
||||
sWatchdog.drop(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.hippo.ehviewer.gallery.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
// ProfileData keeps profiling samples in a tree structure.
|
||||
// The addSample() method adds a sample. The dumpToFile() method saves the data
|
||||
// to a file. The reset() method clears all samples.
|
||||
public class ProfileData {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "ProfileData";
|
||||
|
||||
private static class Node {
|
||||
public int id; // this is the name of this node, mapped from mNameToId
|
||||
public Node parent;
|
||||
public int sampleCount;
|
||||
public ArrayList<Node> children;
|
||||
public Node(Node parent, int id) {
|
||||
this.parent = parent;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
private Node mRoot;
|
||||
private int mNextId;
|
||||
private HashMap<String, Integer> mNameToId;
|
||||
private DataOutputStream mOut;
|
||||
private byte mScratch[] = new byte[4]; // scratch space for writeInt()
|
||||
|
||||
public ProfileData() {
|
||||
mRoot = new Node(null, -1); // The id of the root node is unused.
|
||||
mNameToId = new HashMap<>();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mRoot = new Node(null, -1);
|
||||
mNameToId.clear();
|
||||
mNextId = 0;
|
||||
}
|
||||
|
||||
private int nameToId(String name) {
|
||||
Integer id = mNameToId.get(name);
|
||||
if (id == null) {
|
||||
id = ++mNextId; // The tool doesn't want id=0, so we start from 1.
|
||||
mNameToId.put(name, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public void addSample(String[] stack) {
|
||||
int[] ids = new int[stack.length];
|
||||
for (int i = 0; i < stack.length; i++) {
|
||||
ids[i] = nameToId(stack[i]);
|
||||
}
|
||||
|
||||
Node node = mRoot;
|
||||
for (int i = stack.length - 1; i >= 0; i--) {
|
||||
if (node.children == null) {
|
||||
node.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
int id = ids[i];
|
||||
ArrayList<Node> children = node.children;
|
||||
int j;
|
||||
for (j = 0; j < children.size(); j++) {
|
||||
if (children.get(j).id == id) break;
|
||||
}
|
||||
if (j == children.size()) {
|
||||
children.add(new Node(node, id));
|
||||
}
|
||||
|
||||
node = children.get(j);
|
||||
}
|
||||
|
||||
node.sampleCount++;
|
||||
}
|
||||
|
||||
public void dumpToFile(String filename) {
|
||||
try {
|
||||
mOut = new DataOutputStream(new FileOutputStream(filename));
|
||||
// Start record
|
||||
writeInt(0);
|
||||
writeInt(3);
|
||||
writeInt(1);
|
||||
writeInt(20000); // Sampling period: 20ms
|
||||
writeInt(0);
|
||||
|
||||
// Samples
|
||||
writeAllStacks(mRoot, 0);
|
||||
|
||||
// End record
|
||||
writeInt(0);
|
||||
writeInt(1);
|
||||
writeInt(0);
|
||||
writeAllSymbols();
|
||||
} catch (IOException ex) {
|
||||
Log.w("Failed to dump to file", ex);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(mOut);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes out one stack, consisting of N+2 words:
|
||||
// first word: sample count
|
||||
// second word: depth of the stack (N)
|
||||
// N words: each word is the id of one address in the stack
|
||||
private void writeOneStack(Node node, int depth) throws IOException {
|
||||
writeInt(node.sampleCount);
|
||||
writeInt(depth);
|
||||
while (depth-- > 0) {
|
||||
writeInt(node.id);
|
||||
node = node.parent;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAllStacks(Node node, int depth) throws IOException {
|
||||
if (node.sampleCount > 0) {
|
||||
writeOneStack(node, depth);
|
||||
}
|
||||
|
||||
ArrayList<Node> children = node.children;
|
||||
if (children != null) {
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
writeAllStacks(children.get(i), depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writes out the symbol table. Each line is like:
|
||||
// 0x17e java.util.ArrayList.isEmpty(ArrayList.java:319)
|
||||
private void writeAllSymbols() throws IOException {
|
||||
for (Entry<String, Integer> entry : mNameToId.entrySet()) {
|
||||
mOut.writeBytes(String.format("0x%x %s\n", entry.getValue(), entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeInt(int v) throws IOException {
|
||||
mScratch[0] = (byte) v;
|
||||
mScratch[1] = (byte) (v >> 8);
|
||||
mScratch[2] = (byte) (v >> 16);
|
||||
mScratch[3] = (byte) (v >> 24);
|
||||
mOut.write(mScratch);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user