blob: af4199c91eeab514b8c26feac9ea9476761d5f8e [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.photos.views;
18
19import android.annotation.SuppressLint;
20import android.annotation.TargetApi;
21import android.content.Context;
22import android.graphics.Bitmap;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.Matrix;
26import android.graphics.Paint;
27import android.graphics.Paint.Align;
28import android.graphics.RectF;
29import android.opengl.GLSurfaceView;
30import android.opengl.GLSurfaceView.Renderer;
31import android.os.Build;
32import android.util.AttributeSet;
33import android.view.Choreographer;
34import android.view.Choreographer.FrameCallback;
35import android.view.View;
36import android.widget.FrameLayout;
37
38import com.android.gallery3d.glrenderer.BasicTexture;
39import com.android.gallery3d.glrenderer.GLES20Canvas;
40import com.android.photos.views.TiledImageRenderer.TileSource;
41
42import javax.microedition.khronos.egl.EGLConfig;
43import javax.microedition.khronos.opengles.GL10;
44
45/**
46 * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}
47 * or {@link BlockingGLTextureView}.
48 */
49public class TiledImageView extends FrameLayout {
50
51 private static final boolean USE_TEXTURE_VIEW = false;
52 private static final boolean IS_SUPPORTED =
53 Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
54 private static final boolean USE_CHOREOGRAPHER =
55 Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
56
57 private BlockingGLTextureView mTextureView;
58 private GLSurfaceView mGLSurfaceView;
59 private boolean mInvalPending = false;
60 private FrameCallback mFrameCallback;
61
62 protected static class ImageRendererWrapper {
63 // Guarded by locks
64 public float scale;
65 public int centerX, centerY;
66 public int rotation;
67 public TileSource source;
68 Runnable isReadyCallback;
69
70 // GL thread only
71 TiledImageRenderer image;
72 }
73
74 private float[] mValues = new float[9];
75
76 // -------------------------
77 // Guarded by mLock
78 // -------------------------
79 protected Object mLock = new Object();
80 protected ImageRendererWrapper mRenderer;
81
82 public static boolean isTilingSupported() {
83 return IS_SUPPORTED;
84 }
85
86 public TiledImageView(Context context) {
87 this(context, null);
88 }
89
90 public TiledImageView(Context context, AttributeSet attrs) {
91 super(context, attrs);
92 if (!IS_SUPPORTED) {
93 return;
94 }
95
96 mRenderer = new ImageRendererWrapper();
97 mRenderer.image = new TiledImageRenderer(this);
98 View view;
99 if (USE_TEXTURE_VIEW) {
100 mTextureView = new BlockingGLTextureView(context);
101 mTextureView.setRenderer(new TileRenderer());
102 view = mTextureView;
103 } else {
104 mGLSurfaceView = new GLSurfaceView(context);
105 mGLSurfaceView.setEGLContextClientVersion(2);
106 mGLSurfaceView.setRenderer(new TileRenderer());
107 mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
108 view = mGLSurfaceView;
109 }
110 addView(view, new LayoutParams(
111 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
112 //setTileSource(new ColoredTiles());
113 }
114
115 public void destroy() {
116 if (!IS_SUPPORTED) {
117 return;
118 }
119 if (USE_TEXTURE_VIEW) {
120 mTextureView.destroy();
121 } else {
122 mGLSurfaceView.queueEvent(mFreeTextures);
123 }
124 }
125
126 private Runnable mFreeTextures = new Runnable() {
127
128 @Override
129 public void run() {
130 mRenderer.image.freeTextures();
131 }
132 };
133
134 public void onPause() {
135 if (!IS_SUPPORTED) {
136 return;
137 }
138 if (!USE_TEXTURE_VIEW) {
139 mGLSurfaceView.onPause();
140 }
141 }
142
143 public void onResume() {
144 if (!IS_SUPPORTED) {
145 return;
146 }
147 if (!USE_TEXTURE_VIEW) {
148 mGLSurfaceView.onResume();
149 }
150 }
151
152 public void setTileSource(TileSource source, Runnable isReadyCallback) {
153 if (!IS_SUPPORTED) {
154 return;
155 }
156 synchronized (mLock) {
157 mRenderer.source = source;
158 mRenderer.isReadyCallback = isReadyCallback;
159 mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
160 mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
161 mRenderer.rotation = source != null ? source.getRotation() : 0;
162 mRenderer.scale = 0;
163 updateScaleIfNecessaryLocked(mRenderer);
164 }
165 invalidate();
166 }
167
168 @Override
169 protected void onLayout(boolean changed, int left, int top, int right,
170 int bottom) {
171 super.onLayout(changed, left, top, right, bottom);
172 if (!IS_SUPPORTED) {
173 return;
174 }
175 synchronized (mLock) {
176 updateScaleIfNecessaryLocked(mRenderer);
177 }
178 }
179
180 private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
181 if (renderer == null || renderer.source == null
182 || renderer.scale > 0 || getWidth() == 0) {
183 return;
184 }
185 renderer.scale = Math.min(
186 (float) getWidth() / (float) renderer.source.getImageWidth(),
187 (float) getHeight() / (float) renderer.source.getImageHeight());
188 }
189
190 @Override
191 protected void dispatchDraw(Canvas canvas) {
192 if (!IS_SUPPORTED) {
193 return;
194 }
195 if (USE_TEXTURE_VIEW) {
196 mTextureView.render();
197 }
198 super.dispatchDraw(canvas);
199 }
200
201 @SuppressLint("NewApi")
202 @Override
203 public void setTranslationX(float translationX) {
204 if (!IS_SUPPORTED) {
205 return;
206 }
207 super.setTranslationX(translationX);
208 }
209
210 @Override
211 public void invalidate() {
212 if (!IS_SUPPORTED) {
213 return;
214 }
215 if (USE_TEXTURE_VIEW) {
216 super.invalidate();
217 mTextureView.invalidate();
218 } else {
219 if (USE_CHOREOGRAPHER) {
220 invalOnVsync();
221 } else {
222 mGLSurfaceView.requestRender();
223 }
224 }
225 }
226
227 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
228 private void invalOnVsync() {
229 if (!mInvalPending) {
230 mInvalPending = true;
231 if (mFrameCallback == null) {
232 mFrameCallback = new FrameCallback() {
233 @Override
234 public void doFrame(long frameTimeNanos) {
235 mInvalPending = false;
236 mGLSurfaceView.requestRender();
237 }
238 };
239 }
240 Choreographer.getInstance().postFrameCallback(mFrameCallback);
241 }
242 }
243
244 private RectF mTempRectF = new RectF();
245 public void positionFromMatrix(Matrix matrix) {
246 if (!IS_SUPPORTED) {
247 return;
248 }
249 if (mRenderer.source != null) {
250 final int rotation = mRenderer.source.getRotation();
251 final boolean swap = !(rotation % 180 == 0);
252 final int width = swap ? mRenderer.source.getImageHeight()
253 : mRenderer.source.getImageWidth();
254 final int height = swap ? mRenderer.source.getImageWidth()
255 : mRenderer.source.getImageHeight();
256 mTempRectF.set(0, 0, width, height);
257 matrix.mapRect(mTempRectF);
258 matrix.getValues(mValues);
259 int cx = width / 2;
260 int cy = height / 2;
261 float scale = mValues[Matrix.MSCALE_X];
262 int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
263 int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
264 if (rotation == 90 || rotation == 180) {
265 cx += (mTempRectF.left / scale) - xoffset;
266 } else {
267 cx -= (mTempRectF.left / scale) - xoffset;
268 }
269 if (rotation == 180 || rotation == 270) {
270 cy += (mTempRectF.top / scale) - yoffset;
271 } else {
272 cy -= (mTempRectF.top / scale) - yoffset;
273 }
274 mRenderer.scale = scale;
275 mRenderer.centerX = swap ? cy : cx;
276 mRenderer.centerY = swap ? cx : cy;
277 invalidate();
278 }
279 }
280
281 private class TileRenderer implements Renderer {
282
283 private GLES20Canvas mCanvas;
284
285 @Override
286 public void onSurfaceCreated(GL10 gl, EGLConfig config) {
287 mCanvas = new GLES20Canvas();
288 BasicTexture.invalidateAllTextures();
289 mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
290 }
291
292 @Override
293 public void onSurfaceChanged(GL10 gl, int width, int height) {
294 mCanvas.setSize(width, height);
295 mRenderer.image.setViewSize(width, height);
296 }
297
298 @Override
299 public void onDrawFrame(GL10 gl) {
300 mCanvas.clearBuffer();
301 Runnable readyCallback;
302 synchronized (mLock) {
303 readyCallback = mRenderer.isReadyCallback;
304 mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
305 mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
306 mRenderer.scale);
307 }
308 boolean complete = mRenderer.image.draw(mCanvas);
309 if (complete && readyCallback != null) {
310 synchronized (mLock) {
311 // Make sure we don't trample on a newly set callback/source
312 // if it changed while we were rendering
313 if (mRenderer.isReadyCallback == readyCallback) {
314 mRenderer.isReadyCallback = null;
315 }
316 }
317 if (readyCallback != null) {
318 post(readyCallback);
319 }
320 }
321 }
322
323 }
324
325 @SuppressWarnings("unused")
326 private static class ColoredTiles implements TileSource {
327 private static final int[] COLORS = new int[] {
328 Color.RED,
329 Color.BLUE,
330 Color.YELLOW,
331 Color.GREEN,
332 Color.CYAN,
333 Color.MAGENTA,
334 Color.WHITE,
335 };
336
337 private Paint mPaint = new Paint();
338 private Canvas mCanvas = new Canvas();
339
340 @Override
341 public int getTileSize() {
342 return 256;
343 }
344
345 @Override
346 public int getImageWidth() {
347 return 16384;
348 }
349
350 @Override
351 public int getImageHeight() {
352 return 8192;
353 }
354
355 @Override
356 public int getRotation() {
357 return 0;
358 }
359
360 @Override
361 public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
362 int tileSize = getTileSize();
363 if (bitmap == null) {
364 bitmap = Bitmap.createBitmap(tileSize, tileSize,
365 Bitmap.Config.ARGB_8888);
366 }
367 mCanvas.setBitmap(bitmap);
368 mCanvas.drawColor(COLORS[level]);
369 mPaint.setColor(Color.BLACK);
370 mPaint.setTextSize(20);
371 mPaint.setTextAlign(Align.CENTER);
372 mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
373 tileSize <<= level;
374 x /= tileSize;
375 y /= tileSize;
376 mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
377 mCanvas.setBitmap(null);
378 return bitmap;
379 }
380
381 @Override
382 public BasicTexture getPreview() {
383 return null;
384 }
385 }
386}