/* * 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.android.gallery3d.ui; 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 com.android.gallery3d.ui.GLRoot.OnGLIdleListener; 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 { 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; private static Tile sFreeTileHead = null; private static final Object sFreeTileLock = new Object(); private static Bitmap sUploadBitmap; private static Canvas sCanvas; private static Paint sPaint; private int mUploadIndex = 0; private final Tile[] mTiles; 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 OnGLIdleListener { private final ArrayDeque 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 deque = mTextures; synchronized (this) { if (!deque.isEmpty()) { TiledTexture t = deque.peekFirst(); if (t.uploadNextTile(canvas)) { deque.removeFirst(); mGlRoot.requestRender(); } } 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() { int x = BORDER_SIZE - offsetX; int y = BORDER_SIZE - offsetY; int r = bitmap.getWidth() - x; int b = bitmap.getHeight() - y ; sCanvas.drawBitmap(bitmap, x, y, null); bitmap = 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; Tile next = mTiles[mUploadIndex++]; 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 list = new ArrayList(); 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; } public void recycle() { for (int i = 0, n = mTiles.length; i < n; ++i) { freeTile(mTiles[i]); } } public static void freeResources() { sUploadBitmap = null; sCanvas = null; sPaint = null; } public static void prepareResources() { sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888); sCanvas = new Canvas(sUploadBitmap); sPaint = new Paint(Paint.FILTER_BITMAP_FLAG); sPaint.setColor(Color.TRANSPARENT); sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); } // 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; 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. 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; 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(); 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); } } }