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