1/*
2 * Copyright (C) 2010 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.camera.ui;
18
19import com.android.camera.Util;
20
21import android.app.Activity;
22import android.content.Context;
23import android.graphics.Color;
24import android.graphics.Matrix;
25import android.graphics.PixelFormat;
26import android.opengl.GLSurfaceView;
27import android.opengl.GLU;
28import android.os.SystemClock;
29import android.util.AttributeSet;
30import android.util.DisplayMetrics;
31import android.util.Log;
32import android.view.MotionEvent;
33import android.view.animation.Animation;
34import android.view.animation.Transformation;
35
36import java.nio.ByteBuffer;
37import java.nio.ByteOrder;
38import java.util.ArrayList;
39import java.util.Stack;
40
41import javax.microedition.khronos.egl.EGLConfig;
42import javax.microedition.khronos.opengles.GL10;
43import javax.microedition.khronos.opengles.GL11;
44import javax.microedition.khronos.opengles.GL11Ext;
45
46// The root component of all <code>GLView</code>s. The rendering is done in GL
47// thread while the event handling is done in the main thread.  To synchronize
48// the two threads, the entry points of this package need to synchronize on the
49// <code>GLRootView</code> instance unless it can be proved that the rendering
50// thread won't access the same thing as the method. The entry points include:
51// (1) The public methods of HeadUpDisplay
52// (2) The public methods of CameraHeadUpDisplay
53// (3) The overridden methods in GLRootView.
54public class GLRootView extends GLSurfaceView
55        implements GLSurfaceView.Renderer {
56    private static final String TAG = "GLRootView";
57
58    private final boolean ENABLE_FPS_TEST = false;
59    private int mFrameCount = 0;
60    private long mFrameCountingStart = 0;
61
62    // We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
63    private static final int VERTEX_BUFFER_SIZE = 16 * 2;
64
65    // We need 22 indices for a normal nine-patch image
66    private static final int INDEX_BUFFER_SIZE = 22;
67
68    private static final int FLAG_INITIALIZED = 1;
69    private static final int FLAG_NEED_LAYOUT = 2;
70
71    private static boolean mTexture2DEnabled;
72
73    private static float sPixelDensity = -1f;
74
75    private GL11 mGL;
76    private GLView mContentView;
77    private DisplayMetrics mDisplayMetrics;
78
79    private final ArrayList<Animation> mAnimations = new ArrayList<Animation>();
80
81    private final Stack<Transformation> mFreeTransform =
82            new Stack<Transformation>();
83
84    private final Transformation mTransformation = new Transformation();
85    private final Stack<Transformation> mTransformStack =
86            new Stack<Transformation>();
87
88    private float mLastAlpha = mTransformation.getAlpha();
89
90    private final float mMatrixValues[] = new float[16];
91
92    private final float mUvBuffer[] = new float[VERTEX_BUFFER_SIZE];
93    private final float mXyBuffer[] = new float[VERTEX_BUFFER_SIZE];
94    private final byte mIndexBuffer[] = new byte[INDEX_BUFFER_SIZE];
95
96    private int mNinePatchX[] = new int[4];
97    private int mNinePatchY[] = new int[4];
98    private float mNinePatchU[] = new float[4];
99    private float mNinePatchV[] = new float[4];
100
101    private ByteBuffer mXyPointer;
102    private ByteBuffer mUvPointer;
103    private ByteBuffer mIndexPointer;
104
105    private int mFlags = FLAG_NEED_LAYOUT;
106    private long mAnimationTime;
107
108    private CameraEGLConfigChooser mEglConfigChooser = new CameraEGLConfigChooser();
109
110    public GLRootView(Context context) {
111        this(context, null);
112    }
113
114    public GLRootView(Context context, AttributeSet attrs) {
115        super(context, attrs);
116        initialize();
117    }
118
119    void registerLaunchedAnimation(Animation animation) {
120        // Register the newly launched animation so that we can set the start
121        // time more precisely. (Usually, it takes much longer for the first
122        // rendering, so we set the animation start time as the time we
123        // complete rendering)
124        mAnimations.add(animation);
125    }
126
127    public long currentAnimationTimeMillis() {
128        return mAnimationTime;
129    }
130
131    public synchronized static float dpToPixel(Context context, float dp) {
132        if (sPixelDensity < 0) {
133            DisplayMetrics metrics = new DisplayMetrics();
134            ((Activity) context).getWindowManager()
135                    .getDefaultDisplay().getMetrics(metrics);
136            sPixelDensity =  metrics.density;
137        }
138        return sPixelDensity * dp;
139    }
140
141    public static int dpToPixel(Context context, int dp) {
142        return (int)(dpToPixel(context, (float) dp) + .5f);
143    }
144
145    public Transformation obtainTransformation() {
146        if (!mFreeTransform.isEmpty()) {
147            Transformation t = mFreeTransform.pop();
148            t.clear();
149            return t;
150        }
151        return new Transformation();
152    }
153
154    public void freeTransformation(Transformation freeTransformation) {
155        mFreeTransform.push(freeTransformation);
156    }
157
158    public Transformation getTransformation() {
159        return mTransformation;
160    }
161
162    public Transformation pushTransform() {
163        Transformation trans = obtainTransformation();
164        trans.set(mTransformation);
165        mTransformStack.push(trans);
166        return mTransformation;
167    }
168
169    public void popTransform() {
170        Transformation trans = mTransformStack.pop();
171        mTransformation.set(trans);
172        freeTransformation(trans);
173    }
174
175    public CameraEGLConfigChooser getEGLConfigChooser() {
176        return mEglConfigChooser;
177    }
178
179    private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
180        return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
181    }
182
183    private void initialize() {
184        mFlags |= FLAG_INITIALIZED;
185        setEGLConfigChooser(mEglConfigChooser);
186        getHolder().setFormat(PixelFormat.TRANSLUCENT);
187        setZOrderOnTop(true);
188
189        setRenderer(this);
190
191        int size = VERTEX_BUFFER_SIZE * Float.SIZE / Byte.SIZE;
192        mXyPointer = allocateDirectNativeOrderBuffer(size);
193        mUvPointer = allocateDirectNativeOrderBuffer(size);
194        mIndexPointer = allocateDirectNativeOrderBuffer(INDEX_BUFFER_SIZE);
195    }
196
197    public void setContentPane(GLView content) {
198        mContentView = content;
199        content.onAttachToRoot(this);
200
201        // no parent for the content pane
202        content.onAddToParent(null);
203        requestLayoutContentPane();
204    }
205
206    public GLView getContentPane() {
207        return mContentView;
208    }
209
210    void handleLowMemory() {
211        //TODO: delete texture from GL
212    }
213
214    public synchronized void requestLayoutContentPane() {
215        if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return;
216
217        // "View" system will invoke onLayout() for initialization(bug ?), we
218        // have to ignore it since the GLThread is not ready yet.
219        if ((mFlags & FLAG_INITIALIZED) == 0) return;
220
221        mFlags |= FLAG_NEED_LAYOUT;
222        requestRender();
223    }
224
225    private synchronized void layoutContentPane() {
226        mFlags &= ~FLAG_NEED_LAYOUT;
227        int width = getWidth();
228        int height = getHeight();
229        Log.v(TAG, "layout content pane " + width + "x" + height);
230        if (mContentView != null && width != 0 && height != 0) {
231            mContentView.layout(0, 0, width, height);
232        }
233    }
234
235    @Override
236    protected void onLayout(
237            boolean changed, int left, int top, int right, int bottom) {
238        if (changed) requestLayoutContentPane();
239    }
240
241    /**
242     * Called when the context is created, possibly after automatic destruction.
243     */
244    // This is a GLSurfaceView.Renderer callback
245    public void onSurfaceCreated(GL10 gl1, EGLConfig config) {
246        GL11 gl = (GL11) gl1;
247        if (mGL != null) {
248            // The GL Object has changed
249            Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl);
250        }
251        mGL = gl;
252
253        if (!ENABLE_FPS_TEST) {
254            setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
255        } else {
256            setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
257        }
258
259        // Disable unused state
260        gl.glDisable(GL11.GL_LIGHTING);
261
262        // Enable used features
263        gl.glEnable(GL11.GL_BLEND);
264        gl.glEnable(GL11.GL_SCISSOR_TEST);
265        gl.glEnable(GL11.GL_STENCIL_TEST);
266        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
267        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
268        gl.glEnable(GL11.GL_TEXTURE_2D);
269        mTexture2DEnabled = true;
270
271        gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
272                GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
273
274        // Set the background color
275        gl.glClearColor(0f, 0f, 0f, 0f);
276        gl.glClearStencil(0);
277
278        gl.glVertexPointer(2, GL11.GL_FLOAT, 0, mXyPointer);
279        gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, mUvPointer);
280
281    }
282
283    /**
284     * Called when the OpenGL surface is recreated without destroying the
285     * context.
286     */
287    // This is a GLSurfaceView.Renderer callback
288    public void onSurfaceChanged(GL10 gl1, int width, int height) {
289        Log.v(TAG, "onSurfaceChanged: " + width + "x" + height
290                + ", gl10: " + gl1.toString());
291        GL11 gl = (GL11) gl1;
292        mGL = gl;
293        gl.glViewport(0, 0, width, height);
294
295        gl.glMatrixMode(GL11.GL_PROJECTION);
296        gl.glLoadIdentity();
297
298        GLU.gluOrtho2D(gl, 0, width, 0, height);
299        Matrix matrix = mTransformation.getMatrix();
300        matrix.reset();
301        matrix.preTranslate(0, getHeight());
302        matrix.preScale(1, -1);
303    }
304
305    private void setAlphaValue(float alpha) {
306        if (mLastAlpha == alpha) return;
307
308        GL11 gl = mGL;
309        mLastAlpha = alpha;
310        if (alpha >= 0.95f) {
311            gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
312                    GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
313        } else {
314            gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
315                    GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
316            gl.glColor4f(alpha, alpha, alpha, alpha);
317        }
318    }
319
320    public void drawRect(int x, int y, int width, int height) {
321        float matrix[] = mMatrixValues;
322        mTransformation.getMatrix().getValues(matrix);
323        drawRect(x, y, width, height, matrix);
324    }
325
326    private static void putRectangle(float x, float y,
327            float width, float height, float[] buffer, ByteBuffer pointer) {
328        buffer[0] = x;
329        buffer[1] = y;
330        buffer[2] = x + width;
331        buffer[3] = y;
332        buffer[4] = x;
333        buffer[5] = y + height;
334        buffer[6] = x + width;
335        buffer[7] = y + height;
336        pointer.asFloatBuffer().put(buffer, 0, 8).position(0);
337    }
338
339    private void drawRect(
340            int x, int y, int width, int height, float matrix[]) {
341        GL11 gl = mGL;
342        gl.glPushMatrix();
343        gl.glMultMatrixf(toGLMatrix(matrix), 0);
344        putRectangle(x, y, width, height, mXyBuffer, mXyPointer);
345        gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, 0, 4);
346        gl.glPopMatrix();
347    }
348
349    public void drawNinePatch(
350            NinePatchTexture tex, int x, int y, int width, int height) {
351
352        NinePatchChunk chunk = tex.getNinePatchChunk();
353
354        // The code should be easily extended to handle the general cases by
355        // allocating more space for buffers. But let's just handle the only
356        // use case.
357        if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
358            throw new RuntimeException("unsupported nine patch");
359        }
360        if (!tex.bind(this, mGL)) {
361            throw new RuntimeException("cannot bind" + tex.toString());
362        }
363        if (width <= 0 || height <= 0) return ;
364
365        int divX[] = mNinePatchX;
366        int divY[] = mNinePatchY;
367        float divU[] = mNinePatchU;
368        float divV[] = mNinePatchV;
369
370        int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
371        int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
372
373        setAlphaValue(mTransformation.getAlpha());
374        Matrix matrix = mTransformation.getMatrix();
375        matrix.getValues(mMatrixValues);
376        GL11 gl = mGL;
377        gl.glPushMatrix();
378        gl.glMultMatrixf(toGLMatrix(mMatrixValues), 0);
379        gl.glTranslatef(x, y, 0);
380        drawMesh(divX, divY, divU, divV, nx, ny);
381        gl.glPopMatrix();
382    }
383
384    /**
385     * Stretches the texture according to the nine-patch rules. It will
386     * linearly distribute the strechy parts defined in the nine-patch chunk to
387     * the target area.
388     *
389     * <pre>
390     *                      source
391     *          /--------------^---------------\
392     *         u0    u1       u2  u3     u4   u5
393     * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
394     *          |    div0    div1 div2   div3  |
395     *          |     |       /   /      /    /
396     *          |     |      /   /     /    /
397     *          |     |     /   /    /    /
398     *          |fffff|ssss|fff|sss|ffff| ---> x
399     *         x0    x1   x2  x3  x4   x5
400     *          \----------v------------/
401     *                  target
402     *
403     * f: fixed segment
404     * s: stretchy segment
405     * </pre>
406     *
407     * @param div the stretch parts defined in nine-patch chunk
408     * @param source the length of the texture
409     * @param target the length on the drawing plan
410     * @param u output, the positions of these dividers in the texture
411     *        coordinate
412     * @param x output, the corresponding position of these dividers on the
413     *        drawing plan
414     * @return the number of these dividers.
415     */
416    private int stretch(
417            int x[], float u[], int div[], int source, int target) {
418        int textureSize = Util.nextPowerOf2(source);
419        float textureBound = (source - 0.5f) / textureSize;
420
421        int stretch = 0;
422        for (int i = 0, n = div.length; i < n; i += 2) {
423            stretch += div[i + 1] - div[i];
424        }
425
426        float remaining = target - source + stretch;
427
428        int lastX = 0;
429        int lastU = 0;
430
431        x[0] = 0;
432        u[0] = 0;
433        for (int i = 0, n = div.length; i < n; i += 2) {
434            // fixed segment
435            x[i + 1] = lastX + (div[i] - lastU);
436            u[i + 1] = Math.min((float) div[i] / textureSize, textureBound);
437
438            // stretchy segment
439            float partU = div[i + 1] - div[i];
440            int partX = (int)(remaining * partU / stretch + 0.5f);
441            remaining -= partX;
442            stretch -= partU;
443
444            lastX = x[i + 1] + partX;
445            lastU = div[i + 1];
446            x[i + 2] = lastX;
447            u[i + 2] = Math.min((float) lastU / textureSize, textureBound);
448        }
449        // the last fixed segment
450        x[div.length + 1] = target;
451        u[div.length + 1] = textureBound;
452
453        // remove segments with length 0.
454        int last = 0;
455        for (int i = 1, n = div.length + 2; i < n; ++i) {
456            if (x[last] == x[i]) continue;
457            x[++last] = x[i];
458            u[last] = u[i];
459        }
460        return last + 1;
461    }
462
463    private void drawMesh(
464            int x[], int y[], float u[], float v[], int nx, int ny) {
465        /*
466         * Given a 3x3 nine-patch image, the vertex order is defined as the
467         * following graph:
468         *
469         * (0) (1) (2) (3)
470         *  |  /|  /|  /|
471         *  | / | / | / |
472         * (4) (5) (6) (7)
473         *  | \ | \ | \ |
474         *  |  \|  \|  \|
475         * (8) (9) (A) (B)
476         *  |  /|  /|  /|
477         *  | / | / | / |
478         * (C) (D) (E) (F)
479         *
480         * And we draw the triangle strip in the following index order:
481         *
482         * index: 04152637B6A5948C9DAEBF
483         */
484        int pntCount = 0;
485        float xy[] = mXyBuffer;
486        float uv[] = mUvBuffer;
487        for (int j = 0; j < ny; ++j) {
488            for (int i = 0; i < nx; ++i) {
489                int xIndex = (pntCount++) << 1;
490                int yIndex = xIndex + 1;
491                xy[xIndex] = x[i];
492                xy[yIndex] = y[j];
493                uv[xIndex] = u[i];
494                uv[yIndex] = v[j];
495            }
496        }
497        mUvPointer.asFloatBuffer().put(uv, 0, pntCount << 1).position(0);
498        mXyPointer.asFloatBuffer().put(xy, 0, pntCount << 1).position(0);
499
500        int idxCount = 1;
501        byte index[] = mIndexBuffer;
502        for (int i = 0, bound = nx * (ny - 1); true;) {
503            // normal direction
504            --idxCount;
505            for (int j = 0; j < nx; ++j, ++i) {
506                index[idxCount++] = (byte) i;
507                index[idxCount++] = (byte) (i + nx);
508            }
509            if (i >= bound) break;
510
511            // reverse direction
512            int sum = i + i + nx - 1;
513            --idxCount;
514            for (int j = 0; j < nx; ++j, ++i) {
515                index[idxCount++] = (byte) (sum - i);
516                index[idxCount++] = (byte) (sum - i + nx);
517            }
518            if (i >= bound) break;
519        }
520        mIndexPointer.put(index, 0, idxCount).position(0);
521
522        mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP,
523                idxCount, GL11.GL_UNSIGNED_BYTE, mIndexPointer);
524    }
525
526    private float[] mapPoints(Matrix matrix, int x1, int y1, int x2, int y2) {
527        float[] point = mXyBuffer;
528        point[0] = x1; point[1] = y1; point[2] = x2; point[3] = y2;
529        matrix.mapPoints(point, 0, point, 0, 4);
530        return point;
531    }
532
533    public void clipRect(int x, int y, int width, int height) {
534        float point[] = mapPoints(
535                mTransformation.getMatrix(), x, y + height, x + width, y);
536
537        // mMatrix could be a rotation matrix. In this case, we need to find
538        // the boundaries after rotation. (only handle 90 * n degrees)
539        if (point[0] > point[2]) {
540            x = (int) point[2];
541            width = (int) point[0] - x;
542        } else {
543            x = (int) point[0];
544            width = (int) point[2] - x;
545        }
546        if (point[1] > point[3]) {
547            y = (int) point[3];
548            height = (int) point[1] - y;
549        } else {
550            y = (int) point[1];
551            height = (int) point[3] - y;
552        }
553        mGL.glScissor(x, y, width, height);
554    }
555
556    public void clearClip() {
557        mGL.glScissor(0, 0, getWidth(), getHeight());
558    }
559
560    private static float[] toGLMatrix(float v[]) {
561        v[15] = v[8]; v[13] = v[5]; v[5] = v[4]; v[4] = v[1];
562        v[12] = v[2]; v[1] = v[3]; v[3] = v[6];
563        v[2] = v[6] = v[8] = v[9] = 0;
564        v[10] = 1;
565        return v;
566    }
567
568    public void drawColor(int x, int y, int width, int height, int color) {
569        float alpha = mTransformation.getAlpha();
570        GL11 gl = mGL;
571        if (mTexture2DEnabled) {
572            // Set mLastAlpha to an invalid value, so that it will reset again
573            // in setAlphaValue(float) later.
574            mLastAlpha = -1.0f;
575            gl.glDisable(GL11.GL_TEXTURE_2D);
576            mTexture2DEnabled = false;
577        }
578        alpha /= 256.0f;
579        gl.glColor4f(Color.red(color) * alpha, Color.green(color) * alpha,
580                Color.blue(color) * alpha, Color.alpha(color) * alpha);
581        drawRect(x, y, width, height);
582    }
583
584    public void drawTexture(
585            BasicTexture texture, int x, int y, int width, int height) {
586        drawTexture(texture, x, y, width, height, mTransformation.getAlpha());
587    }
588
589    public void drawTexture(BasicTexture texture,
590            int x, int y, int width, int height, float alpha) {
591
592        if (!mTexture2DEnabled) {
593            mGL.glEnable(GL11.GL_TEXTURE_2D);
594            mTexture2DEnabled = true;
595        }
596
597        if (!texture.bind(this, mGL)) {
598            throw new RuntimeException("cannot bind" + texture.toString());
599        }
600        if (width <= 0 || height <= 0) return ;
601
602        Matrix matrix = mTransformation.getMatrix();
603        matrix.getValues(mMatrixValues);
604
605        // Test whether it has been rotated or flipped, if so, glDrawTexiOES
606        // won't work
607        if (isMatrixRotatedOrFlipped(mMatrixValues)) {
608            putRectangle(0, 0,
609                    (texture.mWidth - 0.5f) / texture.mTextureWidth,
610                    (texture.mHeight - 0.5f) / texture.mTextureHeight,
611                    mUvBuffer, mUvPointer);
612            setAlphaValue(alpha);
613            drawRect(x, y, width, height, mMatrixValues);
614        } else {
615            // draw the rect from bottom-left to top-right
616            float points[] = mapPoints(matrix, x, y + height, x + width, y);
617            x = (int) points[0];
618            y = (int) points[1];
619            width = (int) points[2] - x;
620            height = (int) points[3] - y;
621            if (width > 0 && height > 0) {
622                setAlphaValue(alpha);
623                ((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height);
624            }
625        }
626    }
627
628    private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
629        return matrix[Matrix.MSKEW_X] != 0 || matrix[Matrix.MSKEW_Y] != 0
630                || matrix[Matrix.MSCALE_X] < 0 || matrix[Matrix.MSCALE_Y] > 0;
631    }
632
633    public synchronized void onDrawFrame(GL10 gl) {
634        if (ENABLE_FPS_TEST) {
635            long now = System.nanoTime();
636            if (mFrameCountingStart == 0) {
637                mFrameCountingStart = now;
638            } else if ((now - mFrameCountingStart) > 1000000000) {
639                Log.v(TAG, "fps: " + (double) mFrameCount
640                        * 1000000000 / (now - mFrameCountingStart));
641                mFrameCountingStart = now;
642                mFrameCount = 0;
643            }
644            ++mFrameCount;
645        }
646
647        if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
648        clearClip();
649        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_STENCIL_BUFFER_BIT);
650        gl.glEnable(GL11.GL_BLEND);
651        gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
652
653        mAnimationTime = SystemClock.uptimeMillis();
654        if (mContentView != null) {
655            mContentView.render(GLRootView.this, (GL11) gl);
656        }
657        long now = SystemClock.uptimeMillis();
658        for (Animation animation : mAnimations) {
659            animation.setStartTime(now);
660        }
661        mAnimations.clear();
662    }
663
664    @Override
665    public synchronized boolean dispatchTouchEvent(MotionEvent event) {
666        // If this has been detached from root, we don't need to handle event
667        return mContentView != null
668                ? mContentView.dispatchTouchEvent(event)
669                : false;
670    }
671
672    public DisplayMetrics getDisplayMetrics() {
673        if (mDisplayMetrics == null) {
674            mDisplayMetrics = new DisplayMetrics();
675            ((Activity) getContext()).getWindowManager()
676                    .getDefaultDisplay().getMetrics(mDisplayMetrics);
677        }
678        return mDisplayMetrics;
679    }
680
681    public void copyTexture2D(
682            RawTexture texture, int x, int y, int width, int height)
683            throws GLOutOfMemoryException {
684        Matrix matrix = mTransformation.getMatrix();
685        matrix.getValues(mMatrixValues);
686
687        if (isMatrixRotatedOrFlipped(mMatrixValues)) {
688            throw new IllegalArgumentException("cannot support rotated matrix");
689        }
690        float points[] = mapPoints(matrix, x, y + height, x + width, y);
691        x = (int) points[0];
692        y = (int) points[1];
693        width = (int) points[2] - x;
694        height = (int) points[3] - y;
695
696        GL11 gl = mGL;
697        int newWidth = Util.nextPowerOf2(width);
698        int newHeight = Util.nextPowerOf2(height);
699        int glError = GL11.GL_NO_ERROR;
700
701        gl.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId());
702
703        int[] cropRect = {0,  0, width, height};
704        gl.glTexParameteriv(GL11.GL_TEXTURE_2D,
705                GL11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect, 0);
706        gl.glTexParameteri(GL11.GL_TEXTURE_2D,
707                GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
708        gl.glTexParameteri(GL11.GL_TEXTURE_2D,
709                GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
710        gl.glTexParameterf(GL11.GL_TEXTURE_2D,
711                GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
712        gl.glTexParameterf(GL11.GL_TEXTURE_2D,
713                GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
714        gl.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0,
715                GL11.GL_RGBA, x, y, newWidth, newHeight, 0);
716        glError = gl.glGetError();
717
718        if (glError == GL11.GL_OUT_OF_MEMORY) {
719            throw new GLOutOfMemoryException();
720        }
721
722        if (glError != GL11.GL_NO_ERROR) {
723            throw new RuntimeException(
724                    "Texture copy fail, glError " + glError);
725        }
726
727        texture.setSize(width, height);
728        texture.setTextureSize(newWidth, newHeight);
729    }
730
731}
732