@Override protected String getUrl(Api.GifResult model, int width, int height, Options options) { Api.GifImage fixedHeight = model.images.fixed_height; int fixedHeightDifference = getDifference(fixedHeight, width, height); Api.GifImage fixedWidth = model.images.fixed_width; int fixedWidthDifference = getDifference(fixedWidth, width, height); if (fixedHeightDifference < fixedWidthDifference && !TextUtils.isEmpty(fixedHeight.url)) { return fixedHeight.url; } else if (!TextUtils.isEmpty(fixedWidth.url)) { return fixedWidth.url; } else if (!TextUtils.isEmpty(model.images.original.url)) { return model.images.original.url; } else { return null; } }
/** * Returns a copy of this request builder with all of the options put so far on this builder. * * <p> This method returns a "deep" copy in that all non-immutable arguments are copied such that * changes to one builder will not affect the other builder. However, in addition to immutable * arguments, the current model is not copied copied so changes to the model will affect both * builders. </p> * * <p> Even if this object was locked, the cloned object returned from this method will not be * locked. </p> */ @SuppressWarnings("unchecked") @Override public RequestOptions clone() { try { RequestOptions result = (RequestOptions) super.clone(); result.options = new Options(); result.options.putAll(options); result.transformations = new HashMap<>(); result.transformations.putAll(transformations); result.isLocked = false; result.isAutoCloneEnabled = false; return (RequestOptions) result; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } }
ResourceCacheKey( ArrayPool arrayPool, Key sourceKey, Key signature, int width, int height, Transformation<?> appliedTransformation, Class<?> decodedResourceClass, Options options) { this.arrayPool = arrayPool; this.sourceKey = sourceKey; this.signature = signature; this.width = width; this.height = height; this.transformation = appliedTransformation; this.decodedResourceClass = decodedResourceClass; this.options = options; }
@Override public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height, Options options) { // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time // spent parsing urls. GlideUrl url = model; if (modelCache != null) { url = modelCache.get(model, 0, 0); if (url == null) { modelCache.put(model, 0, 0, model); url = model; } } int timeout = options.get(TIMEOUT); return new LoadData<>(url, new HttpUrlFetcher(url, timeout)); }
@Override public LoadData<Data> buildLoadData(Model model, int width, int height, Options options) { Key sourceKey = null; int size = modelLoaders.size(); List<DataFetcher<Data>> fetchers = new ArrayList<>(size); for (int i = 0; i < size; i++) { ModelLoader<Model, Data> modelLoader = modelLoaders.get(i); if (modelLoader.handles(model)) { LoadData<Data> loadData = modelLoader.buildLoadData(model, width, height, options); if (loadData != null) { sourceKey = loadData.sourceKey; fetchers.add(loadData.fetcher); } } } return !fetchers.isEmpty() ? new LoadData<>(sourceKey, new MultiFetcher<>(fetchers, exceptionListPool)) : null; }
@Test public void testCanReRunCancelledRequests() { doAnswer(new CallSizeReady(100, 100)).when(harness.target) .getSize(any(SizeReadyCallback.class)); when(harness.engine .load(eq(harness.glideContext), eq(harness.model), eq(harness.signature), eq(100), eq(100), eq(Object.class), eq(List.class), any(Priority.class), any(DiskCacheStrategy.class), eq(harness.transformations), anyBoolean(), any(Options.class), anyBoolean(), anyBoolean(), anyBoolean(), any(ResourceCallback.class))) .thenAnswer(new CallResourceCallback(harness.resource)); SingleRequest<List> request = harness.getRequest(); request.begin(); request.cancel(); request.begin(); verify(harness.target, times(2)).onResourceReady(eq(harness.result), anyTransition()); }
@Before public void setUp() { MockitoAnnotations.initMocks(this); gifHeader = Mockito.spy(new GifHeader()); when(parser.parseHeader()).thenReturn(gifHeader); when(parserPool.obtain(isA(ByteBuffer.class))).thenReturn(parser); when(decoderFactory.build(isA(GifDecoder.BitmapProvider.class), eq(gifHeader), isA(ByteBuffer.class), anyInt())) .thenReturn(gifDecoder); List<ImageHeaderParser> parsers = new ArrayList<ImageHeaderParser>(); parsers.add(new DefaultImageHeaderParser()); options = new Options(); decoder = new ByteBufferGifDecoder( RuntimeEnvironment.application, parsers, bitmapPool, new LruArrayPool(ARRAY_POOL_SIZE_BYTES), parserPool, decoderFactory); }
@SuppressWarnings("unchecked") @Before public void setUp() { MockitoAnnotations.initMocks(this); Application context = RuntimeEnvironment.application; ReEncodingGifResourceEncoder.Factory factory = mock(ReEncodingGifResourceEncoder.Factory.class); when(decoder.getNextFrame()).thenReturn(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)); when(factory.buildDecoder(any(GifDecoder.BitmapProvider.class))).thenReturn(decoder); when(factory.buildParser()).thenReturn(parser); when(factory.buildEncoder()).thenReturn(gifEncoder); when(factory.buildFrameResource(any(Bitmap.class), any(BitmapPool.class))) .thenReturn(frameResource); // TODO Util.anyResource once Util is moved to testutil module (remove unchecked above!) when(frameTransformation.transform(anyContext(), any(Resource.class), anyInt(), anyInt())) .thenReturn(frameResource); when(gifDrawable.getFrameTransformation()).thenReturn(frameTransformation); when(gifDrawable.getBuffer()).thenReturn(ByteBuffer.allocate(0)); when(resource.get()).thenReturn(gifDrawable); encoder = new ReEncodingGifResourceEncoder(context, mock(BitmapPool.class), factory); options = new Options(); options.set(ReEncodingGifResourceEncoder.ENCODE_TRANSFORMATION, true); file = new File(context.getCacheDir(), "test"); }
@SuppressWarnings("unchecked") @Before public void setUp() { MockitoAnnotations.initMocks(this); Application context = RuntimeEnvironment.application; ReEncodingGifResourceEncoder.Factory factory = mock(ReEncodingGifResourceEncoder.Factory.class); when(factory.buildDecoder(any(GifDecoder.BitmapProvider.class))).thenReturn(decoder); when(factory.buildParser()).thenReturn(parser); when(factory.buildEncoder()).thenReturn(gifEncoder); when(factory.buildFrameResource(any(Bitmap.class), any(BitmapPool.class))) .thenReturn(frameResource); // TODO Util.anyResource once Util is moved to testutil module (remove unchecked above!) when(frameTransformation.transform(anyContext(), any(Resource.class), anyInt(), anyInt())) .thenReturn(frameResource); when(gifDrawable.getFrameTransformation()).thenReturn(frameTransformation); when(gifDrawable.getBuffer()).thenReturn(ByteBuffer.allocate(0)); when(resource.get()).thenReturn(gifDrawable); encoder = new ReEncodingGifResourceEncoder(context, mock(BitmapPool.class), factory); options = new Options(); options.set(ReEncodingGifResourceEncoder.ENCODE_TRANSFORMATION, true); file = new File(context.getCacheDir(), "test"); }
private static synchronized BitmapFactory.Options getDefaultOptions() { BitmapFactory.Options decodeBitmapOptions; synchronized (OPTIONS_QUEUE) { decodeBitmapOptions = OPTIONS_QUEUE.poll(); } if (decodeBitmapOptions == null) { decodeBitmapOptions = new BitmapFactory.Options(); resetOptions(decodeBitmapOptions); } return decodeBitmapOptions; }
public Resource<SVG> decode(InputStream source, int width, int height, Options options) throws IOException { try { SVG svg = SVG.getFromInputStream(source); return new SimpleResource<SVG>(svg); } catch (SVGParseException ex) { throw new IOException("Cannot load SVG from stream", ex); } }
@Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); DataUrlLoader.StreamFactory factory = new DataUrlLoader.StreamFactory(); options = new Options(); dataUrlLoader = (DataUrlLoader<InputStream>) factory.build(multiFactory); fetcher = dataUrlLoader.buildLoadData(VALID_PNG, -1, -1, options).fetcher; }
@Override public GifDrawableResource decode(ByteBuffer source, int width, int height, Options options) { final GifHeaderParser parser = parserPool.obtain(source); try { return decode(source, width, height, parser, options); } finally { parserPool.release(parser); } }
@Test public void testCallsSourceUnlimitedExecutorEngineIfOptionsIsSet() { doAnswer(new CallSizeReady(100, 100)).when(builder.target) .getSize(any(SizeReadyCallback.class)); SingleRequest<List> request = builder .setUseUnlimitedSourceGeneratorsPool(true) .build(); request.begin(); verify(builder.engine) .load( eq(builder.glideContext), eq(builder.model), eq(builder.signature), anyInt(), anyInt(), eq(Object.class), eq(List.class), any(Priority.class), any(DiskCacheStrategy.class), eq(builder.transformations), anyBoolean(), anyBoolean(), any(Options.class), anyBoolean(), eq(true), /*useAnimationPool=*/ anyBoolean(), anyBoolean(), any(ResourceCallback.class)); }
@Override public Resource<GifDrawable> decode(InputStream source, int width, int height, Options options) throws IOException { byte[] data = inputStreamToBytes(source); if (data == null) { return null; } ByteBuffer byteBuffer = ByteBuffer.wrap(data); return byteBufferDecoder.decode(byteBuffer, width, height, options); }
@Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); options = new Options(); loader = new UriLoader<>(factory); }
@Override public boolean encode(Resource<GifDrawable> data, File file, Options options) { GifDrawable drawable = data.get(); boolean success = false; try { ByteBufferUtil.toFile(drawable.getBuffer(), file); success = true; } catch (IOException e) { if (Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "Failed to encode GIF drawable data", e); } } return success; }
@Test public void testCanReRunCancelledRequests() { doAnswer(new CallSizeReady(100, 100)).when(builder.target) .getSize(any(SizeReadyCallback.class)); when(builder.engine .load( eq(builder.glideContext), eq(builder.model), eq(builder.signature), eq(100), eq(100), eq(Object.class), eq(List.class), any(Priority.class), any(DiskCacheStrategy.class), eq(builder.transformations), anyBoolean(), anyBoolean(), any(Options.class), anyBoolean(), anyBoolean(), /*useAnimationPool=*/ anyBoolean(), anyBoolean(), any(ResourceCallback.class))) .thenAnswer(new CallResourceCallback(builder.resource)); SingleRequest<List> request = builder.build(); request.begin(); request.cancel(); request.begin(); verify(builder.target, times(2)).onResourceReady(eq(builder.result), anyTransition()); }
private Resource<ResourceType> decodeResourceWithList(DataRewinder<DataType> rewinder, int width, int height, Options options, List<Throwable> exceptions) throws GlideException { Resource<ResourceType> result = null; for (int i = 0, size = decoders.size(); i < size; i++) { ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i); try { DataType data = rewinder.rewindAndGet(); if (decoder.handles(data, options)) { data = rewinder.rewindAndGet(); result = decoder.decode(data, width, height, options); } // Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but // instead log and continue. See #2406 for an example. } catch (IOException | RuntimeException | OutOfMemoryError e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Failed to decode data for " + decoder, e); } exceptions.add(e); } if (result != null) { break; } } if (result == null) { throw new GlideException(failureMessage, new ArrayList<>(exceptions)); } return result; }
private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource, LoadPath<Data, ResourceType, R> path) throws GlideException { Options options = getOptionsWithHardwareConfig(dataSource); DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data); try { // ResourceType in DecodeCallback below is required for compilation to work with gradle. return path.load( rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource)); } finally { rewinder.cleanup(); } }
private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options, DecodeCallbacks callbacks) throws IOException { if (options.inJustDecodeBounds) { is.mark(MARK_POSITION); } else { // Once we've read the image header, we no longer need to allow the buffer to expand in // size. To avoid unnecessary allocations reading image data, we fix the mark limit so that it // is no larger than our current buffer size here. We need to do so immediately before // decoding the full image to avoid having our mark limit overridden by other calls to // markand reset. See issue #225. callbacks.onObtainBounds(); } // BitmapFactory.Options out* variables are reset by most calls to decodeStream, successful or // otherwise, so capture here in case we log below. int sourceWidth = options.outWidth; int sourceHeight = options.outHeight; String outMimeType = options.outMimeType; final Bitmap result; TransformationUtils.getBitmapDrawableLock().lock(); try { result = BitmapFactory.decodeStream(is, null, options); } catch (IllegalArgumentException e) { throw newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options); } finally { TransformationUtils.getBitmapDrawableLock().unlock(); } if (options.inJustDecodeBounds) { is.reset(); } return result; }
@Test public void testIgnoresOnSizeReadyIfNotWaitingForSize() { SingleRequest<List> request = builder.build(); request.begin(); request.onSizeReady(100, 100); request.onSizeReady(100, 100); verify(builder.engine, times(1)) .load( eq(builder.glideContext), eq(builder.model), eq(builder.signature), eq(100), eq(100), eq(Object.class), eq(List.class), any(Priority.class), any(DiskCacheStrategy.class), eq(builder.transformations), anyBoolean(), anyBoolean(), any(Options.class), anyBoolean(), anyBoolean(), /*useAnimationPool=*/ anyBoolean(), anyBoolean(), any(ResourceCallback.class)); }
@Test public void testCallsEngineWithOverrideWidthAndHeightIfSet() { SingleRequest<List> request = builder .setOverrideWidth(1) .setOverrideHeight(2) .build(); request.begin(); verify(builder.engine) .load( eq(builder.glideContext), eq(builder.model), eq(builder.signature), anyInt(), anyInt(), eq(Object.class), eq(List.class), any(Priority.class), any(DiskCacheStrategy.class), eq(builder.transformations), anyBoolean(), anyBoolean(), any(Options.class), anyBoolean(), anyBoolean(), /*useAnimationPool=*/ anyBoolean(), anyBoolean(), any(ResourceCallback.class)); }
EngineKey(Object model, Key signature, int width, int height, Map<Class<?>, Transformation<?>> transformations, Class<?> resourceClass, Class<?> transcodeClass, Options options) { this.model = Preconditions.checkNotNull(model); this.signature = Preconditions.checkNotNull(signature, "Signature must not be null"); this.width = width; this.height = height; this.transformations = Preconditions.checkNotNull(transformations); this.resourceClass = Preconditions.checkNotNull(resourceClass, "Resource class must not be null"); this.transcodeClass = Preconditions.checkNotNull(transcodeClass, "Transcode class must not be null"); this.options = Preconditions.checkNotNull(options); }
@NonNull @Override public Resource<Drawable> decode(Uri source, int width, int height, Options options) throws IOException { @DrawableRes int resId = loadResourceIdFromUri(source); String packageName = source.getAuthority(); Context toUse = packageName.equals(context.getPackageName()) ? context : getContextForPackage(source, packageName); // We can't get a theme from another application. Drawable drawable = DrawableDecoderCompat.getDrawable(toUse, resId); return NonOwnedDrawableResource.newInstance(drawable); }
private static void logDecode(int sourceWidth, int sourceHeight, String outMimeType, BitmapFactory.Options options, Bitmap result, int requestedWidth, int requestedHeight, long startTime) { Log.v(TAG, "Decoded " + getBitmapString(result) + " from [" + sourceWidth + "x" + sourceHeight + "] " + outMimeType + " with inBitmap " + getInBitmapString(options) + " for [" + requestedWidth + "x" + requestedHeight + "]" + ", sample size: " + options.inSampleSize + ", density: " + options.inDensity + ", target density: " + options.inTargetDensity + ", thread: " + Thread.currentThread().getName() + ", duration: " + LogTime.getElapsedMillis(startTime)); }
private Bitmap.CompressFormat getFormat(Bitmap bitmap, Options options) { Bitmap.CompressFormat format = options.get(COMPRESSION_FORMAT); if (format != null) { return format; } else if (bitmap.hasAlpha()) { return Bitmap.CompressFormat.PNG; } else { return Bitmap.CompressFormat.JPEG; } }
@Test public void testCanHandleId() { int id = android.R.drawable.star_off; Uri contentUri = Uri.parse("android.resource://android/drawable/star_off"); when(uriLoader.buildLoadData(eq(contentUri), anyInt(), anyInt(), any(Options.class))) .thenReturn(new ModelLoader.LoadData<>(key, fetcher)); assertTrue(loader.handles(id)); assertEquals( fetcher, Preconditions.checkNotNull(loader.buildLoadData(id, 100, 100, new Options())).fetcher); }
@SuppressWarnings("unchecked") <R> DecodeHelper<R> init( GlideContext glideContext, Object model, Key signature, int width, int height, DiskCacheStrategy diskCacheStrategy, Class<?> resourceClass, Class<R> transcodeClass, Priority priority, Options options, Map<Class<?>, Transformation<?>> transformations, boolean isTransformationRequired, DecodeJob.DiskCacheProvider diskCacheProvider) { this.glideContext = glideContext; this.model = model; this.signature = signature; this.width = width; this.height = height; this.diskCacheStrategy = diskCacheStrategy; this.resourceClass = resourceClass; this.diskCacheProvider = diskCacheProvider; this.transcodeClass = (Class<Transcode>) transcodeClass; this.priority = priority; this.options = options; this.transformations = transformations; this.isTransformationRequired = isTransformationRequired; return (DecodeHelper<R>) this; }
public ResourceCacheKey(Key sourceKey, Key signature, int width, int height, Transformation<?> appliedTransformation, Class<?> decodedResourceClass, Options options) { this.sourceKey = sourceKey; this.signature = signature; this.width = width; this.height = height; this.transformation = appliedTransformation; this.decodedResourceClass = decodedResourceClass; this.options = options; }
@Before public void setup() { MockitoAnnotations.initMocks(this); when(factory.build()).thenReturn(retriever); decoder = new VideoBitmapDecoder(bitmapPool, factory); options = new Options(); }
private Resource<ResourceType> decodeResourceWithList(DataRewinder<DataType> rewinder, int width, int height, Options options, List<Exception> exceptions) throws GlideException { Resource<ResourceType> result = null; for (int i = 0, size = decoders.size(); i < size; i++) { ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i); try { DataType data = rewinder.rewindAndGet(); if (decoder.handles(data, options)) { data = rewinder.rewindAndGet(); result = decoder.decode(data, width, height, options); } } catch (IOException e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Failed to decode data for " + decoder, e); } exceptions.add(e); } if (result != null) { break; } } if (result == null) { throw new GlideException(failureMessage, new ArrayList<>(exceptions)); } return result; }
void encode(DiskCacheProvider diskCacheProvider, Options options) { try { diskCacheProvider.getDiskCache().put(key, new DataCacheWriter<>(encoder, toEncode, options)); } finally { toEncode.unlock(); } }
@Override public Resource<Bitmap> decode(ParcelFileDescriptor resource, int outWidth, int outHeight, Options options) throws IOException { long frameTimeMicros = options.get(TARGET_FRAME); if (frameTimeMicros < 0 && frameTimeMicros != DEFAULT_FRAME) { throw new IllegalArgumentException( "Requested frame must be non-negative, or DEFAULT_FRAME, given: " + frameTimeMicros); } Integer frameOption = options.get(FRAME_OPTION); final Bitmap result; MediaMetadataRetriever mediaMetadataRetriever = factory.build(); try { mediaMetadataRetriever.setDataSource(resource.getFileDescriptor()); if (frameTimeMicros == DEFAULT_FRAME) { result = mediaMetadataRetriever.getFrameAtTime(); } else if (frameOption == null) { result = mediaMetadataRetriever.getFrameAtTime(frameTimeMicros); } else { result = mediaMetadataRetriever.getFrameAtTime(frameTimeMicros, frameOption); } } catch (RuntimeException e) { // MediaMetadataRetriever APIs throw generic runtime exceptions when given invalid data. throw new IOException(e); } finally { mediaMetadataRetriever.release(); } resource.close(); return BitmapResource.obtain(result, bitmapPool); }
public Resource<Transcode> load(DataRewinder<Data> rewinder, Options options, int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException { List<Exception> exceptions = listPool.acquire(); try { return loadWithExceptionList(rewinder, options, width, height, decodeCallback, exceptions); } finally { listPool.release(exceptions); } }
@Test public void testWritesDataFromInputStreamToOutputStream() throws IOException { String fakeData = "SomeRandomFakeData"; ByteArrayInputStream is = new ByteArrayInputStream(fakeData.getBytes()); encoder.encode(is, file, new Options()); byte[] data = ByteBufferUtil.toBytes(ByteBufferUtil.fromFile(file)); assertEquals(fakeData, new String(data)); }
/** * Returns a Bitmap decoded from the given {@link InputStream} that is rotated to match any EXIF * data present in the stream and that is downsampled according to the given dimensions and any * provided {@link com.bumptech.glide.load.resource.bitmap.DownsampleStrategy} option. * * <p> If a Bitmap is present in the * {@link com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool} whose dimensions exactly match * those of the image for the given InputStream is available, the operation is much less expensive * in terms of memory. </p> * * <p> The provided {@link java.io.InputStream} must return <code>true</code> from * {@link java.io.InputStream#markSupported()} and is expected to support a reasonably large * mark limit to accommodate reading large image headers (~5MB). </p> * * @param is An {@link InputStream} to the data for the image. * @param requestedWidth The width the final image should be close to. * @param requestedHeight The height the final image should be close to. * @param options A set of options that may contain one or more supported options that influence * how a Bitmap will be decoded from the given stream. * @param callbacks A set of callbacks allowing callers to optionally respond to various * significant events during the decode process. * @return A new bitmap containing the image from the given InputStream, or recycle if recycle is * not null. */ @SuppressWarnings({"resource", "deprecation"}) public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight, Options options, DecodeCallbacks callbacks) throws IOException { Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports" + " mark()"); byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class); BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions(); bitmapFactoryOptions.inTempStorage = bytesForOptions; DecodeFormat decodeFormat = options.get(DECODE_FORMAT); DownsampleStrategy downsampleStrategy = options.get(DOWNSAMPLE_STRATEGY); boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS); boolean isHardwareConfigAllowed = options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG); if (decodeFormat == DecodeFormat.PREFER_ARGB_8888_DISALLOW_HARDWARE) { isHardwareConfigAllowed = false; } try { Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions, downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth, requestedHeight, fixBitmapToRequestedDimensions, callbacks); return BitmapResource.obtain(result, bitmapPool); } finally { releaseOptions(bitmapFactoryOptions); byteArrayPool.put(bytesForOptions); } }
@Test public void testDiffersIfOptionsDiffer() throws NoSuchAlgorithmException { EngineKey first = harness.build(); harness.options = new Options(); harness.options.set(Option.memory("fakeKey"), "someValue"); EngineKey second = harness.build(); KeyAssertions.assertDifferent(first, second, false /*checkDiskCacheKey*/); }