Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit be667040 authored by John Reck's avatar John Reck Committed by Android (Google) Code Review
Browse files

Merge "Copy Tiling changes from G+" into gb-ub-photos-carlsbad

parents f1412c43 f5ef4801
Loading
Loading
Loading
Loading
+112 −16
Original line number Diff line number Diff line
@@ -16,66 +16,131 @@

package com.android.photos;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.util.Log;

import com.android.gallery3d.glrenderer.BasicTexture;
import com.android.gallery3d.glrenderer.BitmapTexture;
import com.android.photos.views.TiledImageRenderer;

import java.io.IOException;

/**
 * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
 * {@link BitmapRegionDecoder} to wrap a local file
 */
public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {

    BitmapRegionDecoder mDecoder;
    private static final String TAG = "BitmapRegionTileSource";

    private static final boolean REUSE_BITMAP =
            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
    private static final int MAX_PREVIEW_SIZE = 1024;

    public BitmapRegionTileSource(String path) {
    BitmapRegionDecoder mDecoder;
    int mWidth;
    int mHeight;
    int mTileSize;
    private BasicTexture mPreview;
    private final int mRotation;

    // For use only by getTile
    private Rect mWantRegion = new Rect();
    private Rect mOverlapRegion = new Rect();
    private BitmapFactory.Options mOptions;
    private Canvas mCanvas;

    public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
        mTileSize = TiledImageRenderer.suggestedTileSize(context);
        mRotation = rotation;
        try {
            mDecoder = BitmapRegionDecoder.newInstance(path, true);
            mWidth = mDecoder.getWidth();
            mHeight = mDecoder.getHeight();
        } catch (IOException e) {
            Log.w("BitmapRegionTileSource", "ctor failed", e);
        }
        mOptions = new BitmapFactory.Options();
        mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
        mOptions.inPreferQualityOverSpeed = true;
        mOptions.inTempStorage = new byte[16 * 1024];
        if (previewSize != 0) {
            previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
            // Although this is the same size as the Bitmap that is likely already
            // loaded, the lifecycle is different and interactions are on a different
            // thread. Thus to simplify, this source will decode its own bitmap.
            int sampleSize = (int) Math.ceil(Math.max(
                    mWidth / (float) previewSize, mHeight / (float) previewSize));
            mOptions.inSampleSize = Math.max(sampleSize, 1);
            Bitmap preview = mDecoder.decodeRegion(
                    new Rect(0, 0, mWidth, mHeight), mOptions);
            if (preview.getWidth() <= MAX_PREVIEW_SIZE && preview.getHeight() <= MAX_PREVIEW_SIZE) {
                mPreview = new BitmapTexture(preview);
            } else {
                Log.w(TAG, String.format(
                        "Failed to create preview of apropriate size! "
                        + " in: %dx%d, sample: %d, out: %dx%d",
                        mWidth, mHeight, sampleSize,
                        preview.getWidth(), preview.getHeight()));
            }
        }
    }

    @Override
    public int getTileSize() {
        return 256;
        return mTileSize;
    }

    @Override
    public int getImageWidth() {
        return mDecoder.getWidth();
        return mWidth;
    }

    @Override
    public int getImageHeight() {
        return mDecoder.getHeight();
        return mHeight;
    }

    @Override
    public BasicTexture getPreview() {
        return mPreview;
    }

    @Override
    public int getRotation() {
        return mRotation;
    }

    @Override
    public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
        int tileSize = getTileSize();
        int t = tileSize << level;
        if (!REUSE_BITMAP) {
            return getTileWithoutReusingBitmap(level, x, y, tileSize);
        }

        Rect wantRegion = new Rect(x, y, x + t, y + t);
        int t = tileSize << level;
        mWantRegion.set(x, y, x + t, y + t);

        if (bitmap == null) {
            bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
        }

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPreferQualityOverSpeed = true;
        options.inSampleSize =  (1 << level);
        options.inBitmap = bitmap;
        mOptions.inSampleSize = (1 << level);
        mOptions.inBitmap = bitmap;

        try {
            // In CropImage, we may call the decodeRegion() concurrently.
            bitmap = mDecoder.decodeRegion(wantRegion, options);
            bitmap = mDecoder.decodeRegion(mWantRegion, mOptions);
        } finally {
            if (options.inBitmap != bitmap && options.inBitmap != null) {
                options.inBitmap = null;
            if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) {
                mOptions.inBitmap = null;
            }
        }

@@ -84,4 +149,35 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        }
        return bitmap;
    }

    private Bitmap getTileWithoutReusingBitmap(
            int level, int x, int y, int tileSize) {

        int t = tileSize << level;
        mWantRegion.set(x, y, x + t, y + t);

        mOverlapRegion.set(0, 0, mWidth, mHeight);

        mOptions.inSampleSize = (1 << level);
        Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions);

        if (bitmap == null) {
            Log.w(TAG, "fail in decoding region");
        }

        if (mWantRegion.equals(mOverlapRegion)) {
            return bitmap;
        }

        Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
        if (mCanvas == null) {
            mCanvas = new Canvas();
        }
        mCanvas.setBitmap(result);
        mCanvas.drawBitmap(bitmap,
                (mOverlapRegion.left - mWantRegion.left) >> level,
                (mOverlapRegion.top - mWantRegion.top) >> level, null);
        mCanvas.setBitmap(null);
        return result;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -31,7 +31,7 @@ public class FullscreenViewer extends Activity {

        String path = getIntent().getData().toString();
        mTextureView = new TiledImageView(this);
        mTextureView.setTileSource(new BitmapRegionTileSource(path));
        mTextureView.setTileSource(new BitmapRegionTileSource(this, path, 0, 0), null);
        setContentView(mTextureView);
    }

+22 −11
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.photos.views;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLSurfaceView.Renderer;
@@ -32,7 +31,9 @@ import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL10;


/**
 * A TextureView that supports blocking rendering for synchronous drawing
 */
public class BlockingGLTextureView extends TextureView
        implements SurfaceTextureListener {

@@ -90,7 +91,9 @@ public class BlockingGLTextureView extends TextureView
    protected void finalize() throws Throwable {
        try {
            destroy();
        } catch (Throwable t) {}
        } catch (Throwable t) {
            // Ignore
        }
        super.finalize();
    }

@@ -135,8 +138,8 @@ public class BlockingGLTextureView extends TextureView
        }

        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
            int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList);
        }

        /**
@@ -368,19 +371,24 @@ public class BlockingGLTextureView extends TextureView
            exec(FINISH);
            try {
                join();
            } catch (InterruptedException e) {}
            } catch (InterruptedException e) {
                // Ignore
            }
        }

        private void exec(int msgid) {
            synchronized (mLock) {
                if (mExecMsgId != INVALID) {
                    throw new IllegalArgumentException("Message already set - multithreaded access?");
                    throw new IllegalArgumentException(
                            "Message already set - multithreaded access?");
                }
                mExecMsgId = msgid;
                mLock.notify();
                try {
                    mLock.wait();
                } catch (InterruptedException e) {}
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
        }

@@ -415,12 +423,15 @@ public class BlockingGLTextureView extends TextureView
                    while (mExecMsgId == INVALID) {
                        try {
                            mLock.wait();
                        } catch (InterruptedException e) {}
                        } catch (InterruptedException e) {
                            // Ignore
                        }
                    }
                    handleMessageLocked(mExecMsgId);
                    mExecMsgId = INVALID;
                    mLock.notify();
                }
                mExecMsgId = FINISH;
            }
        }
    }
+138 −64
Original line number Diff line number Diff line
@@ -23,14 +23,19 @@ import android.graphics.RectF;
import android.support.v4.util.LongSparseArray;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pools.Pool;
import android.util.Pools.SynchronizedPool;
import android.view.View;
import android.view.WindowManager;

import com.android.gallery3d.common.Utils;
import com.android.gallery3d.glrenderer.BasicTexture;
import com.android.gallery3d.glrenderer.GLCanvas;
import com.android.gallery3d.glrenderer.UploadedTexture;
import com.android.photos.data.GalleryBitmapPool;

/**
 * Handles laying out, decoding, and drawing of tiles in GL
 */
public class TiledImageRenderer {
    public static final int SIZE_UNKNOWN = -1;

@@ -62,12 +67,13 @@ public class TiledImageRenderer {
    private static final int STATE_RECYCLING = 0x20;
    private static final int STATE_RECYCLED = 0x40;

    private static GalleryBitmapPool sTilePool = GalleryBitmapPool.getInstance();
    private static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64);

    // TILE_SIZE must be 2^N
    private int mTileSize;

    private TileSource mModel;
    private BasicTexture mPreview;
    protected int mLevelCount;  // cache the value of mScaledBitmaps.length

    // The mLevel variable indicates which level of bitmap we should use.
@@ -116,22 +122,39 @@ public class TiledImageRenderer {
    private int mViewWidth, mViewHeight;
    private View mParent;

    /**
     * Interface for providing tiles to a {@link TiledImageRenderer}
     */
    public static interface TileSource {

        /**
         * If the source does not care about the tile size, it should use
         * {@link TiledImageRenderer#suggestedTileSize(Context)}
         */
        public int getTileSize();
        public int getImageWidth();
        public int getImageHeight();
        public int getRotation();

        // The tile returned by this method can be specified this way: Assuming
        // the image size is (width, height), first take the intersection of (0,
        // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If
        // in extending the region, we found some part of the region is outside
        // the image, those pixels are filled with black.
        //
        // If level > 0, it does the same operation on a down-scaled version of
        // the original image (down-scaled by a factor of 2^level), but (x, y)
        // still refers to the coordinate on the original image.
        //
        // The method would be called by the decoder thread.
        /**
         * Return a Preview image if available. This will be used as the base layer
         * if higher res tiles are not yet available
         */
        public BasicTexture getPreview();

        /**
         * The tile returned by this method can be specified this way: Assuming
         * the image size is (width, height), first take the intersection of (0,
         * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If
         * in extending the region, we found some part of the region is outside
         * the image, those pixels are filled with black.
         *
         * If level > 0, it does the same operation on a down-scaled version of
         * the original image (down-scaled by a factor of 2^level), but (x, y)
         * still refers to the coordinate on the original image.
         *
         * The method would be called by the decoder thread.
         */
        public Bitmap getTile(int level, int x, int y, Bitmap reuse);
    }

@@ -173,19 +196,23 @@ public class TiledImageRenderer {
        if (mRotation != rotation) {
            mRotation = rotation;
            mLayoutTiles = true;
            invalidate();
        }
    }

    private static int calulateLevelCount(TileSource source) {
    private void calculateLevelCount() {
        if (mPreview != null) {
            mLevelCount = Math.max(0, Utils.ceilLog2(
                mImageWidth / (float) mPreview.getWidth()));
        } else {
            int levels = 1;
        int maxDim = Math.max(source.getImageWidth(), source.getImageHeight());
        int t = source.getTileSize();
            int maxDim = Math.max(mImageWidth, mImageHeight);
            int t = mTileSize;
            while (t < maxDim) {
                t <<= 1;
                levels++;
            }
        return levels;
            mLevelCount = levels;
        }
    }

    public void notifyModelInvalidated() {
@@ -194,14 +221,15 @@ public class TiledImageRenderer {
            mImageWidth = 0;
            mImageHeight = 0;
            mLevelCount = 0;
            mPreview = null;
        } else {
            mImageWidth = mModel.getImageWidth();
            mImageHeight = mModel.getImageHeight();
            mLevelCount = calulateLevelCount(mModel);
            mPreview = mModel.getPreview();
            mTileSize = mModel.getTileSize();
            calculateLevelCount();
        }
        mLayoutTiles = true;
        invalidate();
    }

    public void setViewSize(int width, int height) {
@@ -211,12 +239,13 @@ public class TiledImageRenderer {

    public void setPosition(int centerX, int centerY, float scale) {
        if (mCenterX == centerX && mCenterY == centerY
                && mScale == scale) return;
                && mScale == scale) {
            return;
        }
        mCenterX = centerX;
        mCenterY = centerY;
        mScale = scale;
        mLayoutTiles = true;
        invalidate();
    }

    // Prepare the tiles we want to use for display.
@@ -265,7 +294,9 @@ public class TiledImageRenderer {
        }

        // If rotation is transient, don't update the tile.
        if (mRotation % 90 != 0) return;
        if (mRotation % 90 != 0) {
            return;
        }

        synchronized (mQueueLock) {
            mDecodeQueue.clean();
@@ -305,7 +336,7 @@ public class TiledImageRenderer {
            mDecodeQueue.clean();
            mUploadQueue.clean();

            // TODO disable decoder
            // TODO(xx): disable decoder
            int n = mActiveTiles.size();
            for (int i = 0; i < n; i++) {
                Tile tile = mActiveTiles.valueAt(i);
@@ -357,6 +388,7 @@ public class TiledImageRenderer {
    public void freeTextures() {
        mLayoutTiles = true;

        mTileDecoder.finishAndWait();
        synchronized (mQueueLock) {
            mUploadQueue.clean();
            mDecodeQueue.clean();
@@ -375,10 +407,10 @@ public class TiledImageRenderer {
        mActiveTiles.clear();
        mTileRange.set(0, 0, 0, 0);

        if (sTilePool != null) sTilePool.clear();
        while (sTilePool.acquire() != null) {}
    }

    public void draw(GLCanvas canvas) {
    public boolean draw(GLCanvas canvas) {
        layoutTiles();
        uploadTiles(canvas);

@@ -388,7 +420,9 @@ public class TiledImageRenderer {
        int level = mLevel;
        int rotation = mRotation;
        int flags = 0;
        if (rotation != 0) flags |= GLCanvas.SAVE_FLAG_MATRIX;
        if (rotation != 0) {
            flags |= GLCanvas.SAVE_FLAG_MATRIX;
        }

        if (flags != 0) {
            canvas.save(flags);
@@ -412,9 +446,15 @@ public class TiledImageRenderer {
                        drawTile(canvas, tx, ty, level, x, y, length);
                    }
                }
            } else if (mPreview != null) {
                mPreview.draw(canvas, mOffsetX, mOffsetY,
                        Math.round(mImageWidth * mScale),
                        Math.round(mImageHeight * mScale));
            }
        } finally {
            if (flags != 0) canvas.restore();
            if (flags != 0) {
                canvas.restore();
            }
        }

        if (mRenderComplete) {
@@ -424,6 +464,7 @@ public class TiledImageRenderer {
        } else {
            invalidate();
        }
        return mRenderComplete || mPreview != null;
    }

    private void uploadBackgroundTiles(GLCanvas canvas) {
@@ -437,17 +478,6 @@ public class TiledImageRenderer {
        }
    }

    private void queueForUpload(Tile tile) {
        synchronized (mQueueLock) {
            mUploadQueue.push(tile);
        }
        invalidate();
        // TODO
//        if (mTileUploader.mActive.compareAndSet(false, true)) {
//            getGLRoot().addOnGLIdleListener(mTileUploader);
//        }
    }

   private void queueForDecode(Tile tile) {
       synchronized (mQueueLock) {
           if (tile.mTileState == STATE_ACTIVATED) {
@@ -459,9 +489,11 @@ public class TiledImageRenderer {
       }
    }

    private boolean decodeTile(Tile tile) {
    private void decodeTile(Tile tile) {
        synchronized (mQueueLock) {
            if (tile.mTileState != STATE_IN_QUEUE) return false;
            if (tile.mTileState != STATE_IN_QUEUE) {
                return;
            }
            tile.mTileState = STATE_DECODING;
        }
        boolean decodeComplete = tile.decode();
@@ -469,15 +501,19 @@ public class TiledImageRenderer {
            if (tile.mTileState == STATE_RECYCLING) {
                tile.mTileState = STATE_RECYCLED;
                if (tile.mDecodedTile != null) {
                    if (sTilePool != null) sTilePool.put(tile.mDecodedTile);
                    sTilePool.release(tile.mDecodedTile);
                    tile.mDecodedTile = null;
                }
                mRecycledQueue.push(tile);
                return false;
                return;
            }
            tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL;
            return decodeComplete;
            if (!decodeComplete) {
                return;
            }
            mUploadQueue.push(tile);
        }
        invalidate();
    }

    private Tile obtainTile(int x, int y, int level) {
@@ -500,7 +536,7 @@ public class TiledImageRenderer {
            }
            tile.mTileState = STATE_RECYCLED;
            if (tile.mDecodedTile != null) {
                if (sTilePool != null) sTilePool.put(tile.mDecodedTile);
                sTilePool.release(tile.mDecodedTile);
                tile.mDecodedTile = null;
            }
            mRecycledQueue.push(tile);
@@ -538,11 +574,16 @@ public class TiledImageRenderer {
            synchronized (mQueueLock) {
                tile = mUploadQueue.pop();
            }
            if (tile == null) break;
            if (tile == null) {
                break;
            }
            if (!tile.isContentValid()) {
                Utils.assertTrue(tile.mTileState == STATE_DECODED);
                if (tile.mTileState == STATE_DECODED) {
                    tile.updateContent(canvas);
                    --quota;
                } else {
                    Log.w(TAG, "Tile in upload queue has invalid state: " + tile.mTileState);
                }
            }
        }
        if (tile != null) {
@@ -574,7 +615,17 @@ public class TiledImageRenderer {
                    queueForDecode(tile);
                }
            }
            drawTile(tile, canvas, source, target);
            if (drawTile(tile, canvas, source, target)) {
                return;
            }
        }
        if (mPreview != null) {
            int size = mTileSize << level;
            float scaleX = (float) mPreview.getWidth() / mImageWidth;
            float scaleY = (float) mPreview.getHeight() / mImageHeight;
            source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX,
                    (ty + size) * scaleY);
            canvas.drawTexture(mPreview, source, target);
        }
    }

@@ -588,7 +639,9 @@ public class TiledImageRenderer {

            // Parent can be divided to four quads and tile is one of the four.
            Tile parent = tile.getParentTile();
            if (parent == null) return false;
            if (parent == null) {
                return false;
            }
            if (tile.mX == parent.mX) {
                source.left /= 2f;
                source.right /= 2f;
@@ -623,14 +676,17 @@ public class TiledImageRenderer {

        @Override
        protected void onFreeBitmap(Bitmap bitmap) {
            if (sTilePool != null) sTilePool.put(bitmap);
            sTilePool.release(bitmap);
        }

        boolean decode() {
            // Get a tile from the original image. The tile is down-scaled
            // by (1 << mTilelevel) from a region in the original image.
            try {
                Bitmap reuse = sTilePool.get(mTileSize, mTileSize);
                Bitmap reuse = sTilePool.acquire();
                if (reuse != null && reuse.getWidth() != mTileSize) {
                    reuse = null;
                }
                mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse);
            } catch (Throwable t) {
                Log.w(TAG, "fail to decode tile", t);
@@ -676,7 +732,9 @@ public class TiledImageRenderer {
        }

        public Tile getParentTile() {
            if (mTileLevel + 1 == mLevelCount) return null;
            if (mTileLevel + 1 == mLevelCount) {
                return null;
            }
            int size = mTileSize << (mTileLevel + 1);
            int x = size * (mX / size);
            int y = size * (mY / size);
@@ -695,17 +753,34 @@ public class TiledImageRenderer {

        public Tile pop() {
            Tile tile = mHead;
            if (tile != null) mHead = tile.mNext;
            if (tile != null) {
                mHead = tile.mNext;
            }
            return tile;
        }

        public boolean push(Tile tile) {
            if (contains(tile)) {
                Log.w(TAG, "Attempting to add a tile already in the queue!");
                return false;
            }
            boolean wasEmpty = mHead == null;
            tile.mNext = mHead;
            mHead = tile;
            return wasEmpty;
        }

        private boolean contains(Tile tile) {
            Tile other = mHead;
            while (other != null) {
                if (other == tile) {
                    return true;
                }
                other = other.mNext;
            }
            return false;
        }

        public void clean() {
            mHead = null;
        }
@@ -739,11 +814,10 @@ public class TiledImageRenderer {
            try {
                while (!isInterrupted()) {
                    Tile tile = waitForTile();
                    if (decodeTile(tile)) {
                        queueForUpload(tile);
                    }
                    decodeTile(tile);
                }
            } catch (InterruptedException ex) {
                // We were finished
            }
        }

+208 −95

File changed.

Preview size limit exceeded, changes collapsed.