Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2015 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 
     17 package com.android.camera.data;
     18 
     19 import android.content.Context;
     20 import android.graphics.Bitmap;
     21 import android.net.Uri;
     22 import android.opengl.EGL14;
     23 import android.opengl.EGLConfig;
     24 import android.opengl.EGLContext;
     25 import android.opengl.EGLDisplay;
     26 import android.opengl.EGLSurface;
     27 import android.opengl.GLES20;
     28 
     29 import com.android.camera.debug.Log;
     30 import com.android.camera.debug.Log.Tag;
     31 import com.android.camera.util.Size;
     32 import com.android.camera2.R;
     33 import com.bumptech.glide.DrawableRequestBuilder;
     34 import com.bumptech.glide.GenericRequestBuilder;
     35 import com.bumptech.glide.Glide;
     36 import com.bumptech.glide.RequestManager;
     37 import com.bumptech.glide.load.Key;
     38 import com.bumptech.glide.load.resource.bitmap.BitmapEncoder;
     39 import com.bumptech.glide.load.resource.drawable.GlideDrawable;
     40 import com.bumptech.glide.load.resource.gif.GifResourceEncoder;
     41 import com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceEncoder;
     42 import com.bumptech.glide.load.resource.transcode.BitmapToGlideDrawableTranscoder;
     43 
     44 /**
     45  * Manage common glide image requests for the camera filmstrip.
     46  */
     47 public final class GlideFilmstripManager {
     48     private static final Tag TAG = new Tag("GlideFlmMgr");
     49 
     50     /** Default placeholder to display while images load */
     51     public static final int DEFAULT_PLACEHOLDER_RESOURCE = R.color.photo_placeholder;
     52 
     53     // This is the default GL texture size for K and below, it may be bigger,
     54     // it should not be smaller than this.
     55     private static final int DEFAULT_MAX_IMAGE_DISPLAY_SIZE = 2048;
     56 
     57     // Some phones have massive GL_Texture sizes. Prevent images from doing
     58     // overly large allocations by capping the texture size.
     59     private static final int MAX_GL_TEXTURE_SIZE = 4096;
     60     private static Size MAX_IMAGE_DISPLAY_SIZE;
     61     public static Size getMaxImageDisplaySize() {
     62         if (MAX_IMAGE_DISPLAY_SIZE == null) {
     63             Integer size = computeEglMaxTextureSize();
     64             if (size == null) {
     65                 // Fallback to the default 2048 if a size is not found.
     66                 MAX_IMAGE_DISPLAY_SIZE = new Size(DEFAULT_MAX_IMAGE_DISPLAY_SIZE,
     67                       DEFAULT_MAX_IMAGE_DISPLAY_SIZE);
     68             } else if (size > MAX_GL_TEXTURE_SIZE) {
     69                 // Cap the display size to prevent Out of memory problems during
     70                 // pre-allocation of huge bitmaps.
     71                 MAX_IMAGE_DISPLAY_SIZE = new Size(MAX_GL_TEXTURE_SIZE, MAX_GL_TEXTURE_SIZE);
     72             } else {
     73                 MAX_IMAGE_DISPLAY_SIZE = new Size(size, size);
     74             }
     75         }
     76 
     77         return MAX_IMAGE_DISPLAY_SIZE;
     78     }
     79 
     80     public static final Size MEDIASTORE_THUMB_SIZE = new Size(512, 384);
     81     public static final Size TINY_THUMB_SIZE = new Size(256, 256);
     82 
     83     // Estimated memory bandwidth for N5 and N6 is about 500MB/s
     84     // 500MBs * 1000000(Bytes per MB) / 4 (RGBA pixel) / 1000 (milli per S)
     85     // Give a 20% margin for error and real conditions.
     86     private static final int EST_PIXELS_PER_MILLI = 100000;
     87 
     88     // Estimated number of bytes that can be used to usually display a thumbnail
     89     // in under a frame at 60fps (16ms).
     90     public static final int MAXIMUM_SMOOTH_PIXELS = EST_PIXELS_PER_MILLI * 10 /* millis */;
     91 
     92     // Estimated number of bytes that can be used to generate a large thumbnail in under
     93     // (about) 3 frames at 60fps (16ms).
     94     public static final int MAXIMUM_FULL_RES_PIXELS = EST_PIXELS_PER_MILLI * 45 /* millis */;
     95     public static final int JPEG_COMPRESS_QUALITY = 90;
     96 
     97     private final GenericRequestBuilder<Uri, ?, ?, GlideDrawable> mTinyImageBuilder;
     98     private final DrawableRequestBuilder<Uri> mLargeImageBuilder;
     99 
    100     public GlideFilmstripManager(Context context) {
    101         Glide glide = Glide.get(context);
    102         BitmapEncoder bitmapEncoder = new BitmapEncoder(Bitmap.CompressFormat.JPEG,
    103               JPEG_COMPRESS_QUALITY);
    104         GifBitmapWrapperResourceEncoder drawableEncoder = new GifBitmapWrapperResourceEncoder(
    105               bitmapEncoder,
    106               new GifResourceEncoder(glide.getBitmapPool()));
    107         RequestManager request = Glide.with(context);
    108 
    109         mTinyImageBuilder = request
    110               .fromMediaStore()
    111               .asBitmap() // This prevents gifs from animating at tiny sizes.
    112               .transcode(new BitmapToGlideDrawableTranscoder(context), GlideDrawable.class)
    113               .fitCenter()
    114               .placeholder(DEFAULT_PLACEHOLDER_RESOURCE)
    115               .dontAnimate();
    116 
    117         mLargeImageBuilder = request
    118               .fromMediaStore()
    119               .encoder(drawableEncoder)
    120               .fitCenter()
    121               .placeholder(DEFAULT_PLACEHOLDER_RESOURCE)
    122               .dontAnimate();
    123     }
    124 
    125     /**
    126      * Create a full size drawable request for a given width and height that is
    127      * as large as we can reasonably load into a view without causing massive
    128      * jank problems or blank frames due to overly large textures.
    129      */
    130     public final DrawableRequestBuilder<Uri> loadFull(Uri uri, Key key, Size original) {
    131         Size size = clampSize(original, MAXIMUM_FULL_RES_PIXELS, getMaxImageDisplaySize());
    132 
    133         return mLargeImageBuilder
    134               .clone()
    135               .load(uri)
    136               .signature(key)
    137               .override(size.width(), size.height());
    138     }
    139 
    140     /**
    141      * Create a full size drawable request for a given width and height that is
    142      * smaller than loadFull, but is intended be large enough to fill the screen
    143      * pixels.
    144      */
    145     public DrawableRequestBuilder<Uri> loadScreen(Uri uri, Key key, Size original) {
    146         Size size = clampSize(original, MAXIMUM_SMOOTH_PIXELS, getMaxImageDisplaySize());
    147         return mLargeImageBuilder
    148               .clone()
    149               .load(uri)
    150               .signature(key)
    151               .override(size.width(), size.height());
    152     }
    153 
    154     /**
    155      * Create a small thumbnail sized image that has the same bounds as the
    156      * media store thumbnail images.
    157      *
    158      * If the Uri points at an animated gif, the gif will not play.
    159      */
    160     public GenericRequestBuilder<Uri, ?, ?, GlideDrawable> loadMediaStoreThumb(Uri uri, Key key) {
    161         Size size = clampSize(MEDIASTORE_THUMB_SIZE, MAXIMUM_SMOOTH_PIXELS, getMaxImageDisplaySize());
    162         return mTinyImageBuilder
    163               .clone()
    164               .load(uri)
    165               .signature(key)
    166                     // This attempts to ensure we load the cached media store version.
    167               .override(size.width(), size.height());
    168     }
    169 
    170     /**
    171      * Create very tiny thumbnail request that should complete as fast
    172      * as possible.
    173      *
    174      * If the Uri points at an animated gif, the gif will not play.
    175      */
    176     public GenericRequestBuilder<Uri, ?, ?, GlideDrawable> loadTinyThumb(Uri uri, Key key) {
    177         Size size = clampSize(TINY_THUMB_SIZE, MAXIMUM_SMOOTH_PIXELS,  getMaxImageDisplaySize());
    178         return mTinyImageBuilder
    179               .clone()
    180               .load(uri)
    181               .signature(key)
    182               .override(size.width(), size.height());
    183     }
    184 
    185     /**
    186      * Given a size, compute a value such that it will downscale the original size
    187      * to fit within the maxSize bounding box and to be less than the provided area.
    188      *
    189      * This will never upscale sizes.
    190      */
    191     private static Size clampSize(Size original, double maxArea, Size maxSize) {
    192         if (original.getWidth() * original.getHeight() < maxArea &&
    193               original.getWidth() < maxSize.getWidth() &&
    194               original.getHeight() < maxSize.getHeight()) {
    195             // In several cases, the size is smaller than the max, and the area is
    196             // smaller than the max area.
    197             return original;
    198         }
    199 
    200         // Compute a ratio that will keep the number of pixels in the image (hence,
    201         // the number of bytes that can be copied into memory) under the maxArea.
    202         double ratio = Math.min(Math.sqrt(maxArea / original.area()), 1.0f);
    203         int width = (int) Math.round(original.width() * ratio);
    204         int height = (int) Math.round(original.height() * ratio);
    205 
    206         // If that ratio results in an image where the edge length is still too large,
    207         // constrain based on max edge length instead.
    208         if (width > maxSize.width() || height > maxSize.height()) {
    209             return computeFitWithinSize(original, maxSize);
    210         }
    211 
    212         return new Size(width, height);
    213     }
    214 
    215     private static Size computeFitWithinSize(Size original, Size maxSize) {
    216         double widthRatio = (double) maxSize.width() / original.width();
    217         double heightRatio = (double) maxSize.height() / original.height();
    218 
    219         double ratio = widthRatio > heightRatio ? heightRatio : widthRatio;
    220 
    221         // This rounds and ensures that (even with rounding and int conversion)
    222         // that the returned size is never larger than maxSize.
    223         return new Size(
    224               Math.min((int) Math.round(original.width() * ratio), maxSize.width()),
    225               Math.min((int) Math.round(original.height() * ratio), maxSize.height()));
    226     }
    227 
    228     /**
    229      * Ridiculous way to read the devices maximum texture size because no other
    230      * way is provided.
    231      */
    232     private static Integer computeEglMaxTextureSize() {
    233         EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
    234         int[] majorMinor = new int[2];
    235         EGL14.eglInitialize(eglDisplay, majorMinor, 0, majorMinor, 1);
    236 
    237         int[] configAttr = {
    238               EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER,
    239               EGL14.EGL_LEVEL, 0,
    240               EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
    241               EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
    242               EGL14.EGL_NONE
    243         };
    244         EGLConfig[] eglConfigs = new EGLConfig[1];
    245         int[] configCount = new int[1];
    246         EGL14.eglChooseConfig(eglDisplay, configAttr, 0,
    247               eglConfigs, 0, 1, configCount, 0);
    248 
    249         if (configCount[0] == 0) {
    250             Log.w(TAG, "No EGL configurations found!");
    251             return null;
    252         }
    253         EGLConfig eglConfig = eglConfigs[0];
    254 
    255         // Create a tiny surface
    256         int[] eglSurfaceAttributes = {
    257               EGL14.EGL_WIDTH, 64,
    258               EGL14.EGL_HEIGHT, 64,
    259               EGL14.EGL_NONE
    260         };
    261         //
    262         EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig,
    263               eglSurfaceAttributes, 0);
    264 
    265         int[] eglContextAttributes = {
    266               EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
    267               EGL14.EGL_NONE
    268         };
    269 
    270         // Create an EGL context.
    271         EGLContext eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT,
    272               eglContextAttributes, 0);
    273         EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
    274 
    275         // Actually read the Gl_MAX_TEXTURE_SIZE into the array.
    276         int[] maxSize = new int[1];
    277         GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0);
    278         int result = maxSize[0];
    279 
    280         // Tear down the surface, context, and display.
    281         EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
    282               EGL14.EGL_NO_CONTEXT);
    283         EGL14.eglDestroySurface(eglDisplay, eglSurface);
    284         EGL14.eglDestroyContext(eglDisplay, eglContext);
    285         EGL14.eglTerminate(eglDisplay);
    286 
    287         // Return the computed max size.
    288         return result;
    289     }
    290 }
    291