/** * Creates a bitmap from encoded JPEG bytes. Supports a partial JPEG image. * * @param encodedImage the encoded image with reference to the encoded bytes * @param bitmapConfig the {@link android.graphics.Bitmap.Config} used to create the decoded * Bitmap * @param length the number of encoded bytes in the buffer * @return the bitmap * @exception java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ @Override public CloseableReference<Bitmap> decodeJPEGFromEncodedImage( EncodedImage encodedImage, Bitmap.Config bitmapConfig, int length) { boolean isJpegComplete = encodedImage.isCompleteAt(length); final BitmapFactory.Options options = getDecodeOptionsForStream(encodedImage, bitmapConfig); InputStream jpegDataStream = encodedImage.getInputStream(); // At this point the InputStream from the encoded image should not be null since in the // pipeline,this comes from a call stack where this was checked before. Also this method needs // the InputStream to decode the image so this can't be null. Preconditions.checkNotNull(jpegDataStream); if (encodedImage.getSize() > length) { jpegDataStream = new LimitedInputStream(jpegDataStream, length); } if (!isJpegComplete) { jpegDataStream = new TailAppendingInputStream(jpegDataStream, EOI_TAIL); } boolean retryOnFail = options.inPreferredConfig != Bitmap.Config.ARGB_8888; try { return decodeStaticImageFromStream(jpegDataStream, options); } catch (RuntimeException re) { if (retryOnFail) { return decodeFromEncodedImage(encodedImage, Bitmap.Config.ARGB_8888); } throw re; } }
/** Performs the decode synchronously. */ private void doDecode(EncodedImage encodedImage, boolean isLast) { if (isFinished() || !EncodedImage.isValid(encodedImage)) { return; } try { long queueTime = mJobScheduler.getQueuedTime(); int length = isLast ? encodedImage.getSize() : getIntermediateImageEndOffset(encodedImage); QualityInfo quality = isLast ? ImmutableQualityInfo.FULL_QUALITY : getQualityInfo(); mProducerListener.onProducerStart(mProducerContext.getId(), PRODUCER_NAME); CloseableImage image = null; try { image = mImageDecoder.decodeImage(encodedImage, length, quality, mImageDecodeOptions); } catch (Exception e) { Map<String, String> extraMap = getExtraMap(image, queueTime, quality, isLast); mProducerListener.onProducerFinishWithFailure( mProducerContext.getId(), PRODUCER_NAME, e, extraMap); handleError(e); return; } Map<String, String> extraMap = getExtraMap(image, queueTime, quality, isLast); mProducerListener.onProducerFinishWithSuccess( mProducerContext.getId(), PRODUCER_NAME, extraMap); handleResult(image, isLast); } finally { EncodedImage.closeSafely(encodedImage); } }
private boolean isResultGoodEnough(EncodedImage encodedimage, ImageRequest imagerequest) { while (encodedimage == null || encodedimage.getWidth() < imagerequest.getPreferredWidth() || encodedimage.getHeight() < imagerequest.getPreferredHeight()) { return false; } return true; }
@Test public void testEncodedMemoryCacheGetSuccessful() { setupEncodedMemoryCacheGetSuccess(); when(mProducerContext.getLowestPermittedRequestLevel()) .thenReturn(ImageRequest.RequestLevel.ENCODED_MEMORY_CACHE); mEncodedMemoryCacheProducer.produceResults(mConsumer, mProducerContext); ArgumentCaptor<EncodedImage> argumentCaptor = ArgumentCaptor.forClass(EncodedImage.class); verify(mConsumer).onNewResult(argumentCaptor.capture(), eq(true)); EncodedImage encodedImage = argumentCaptor.getValue(); Assert.assertSame( mFinalEncodedImage.getUnderlyingReferenceTestOnly(), encodedImage.getUnderlyingReferenceTestOnly()); verify(mProducerListener).onProducerStart(mRequestId, PRODUCER_NAME); Map<String, String> extraMap = ImmutableMap.of(EncodedMemoryCacheProducer.VALUE_FOUND, "true"); verify(mProducerListener).onProducerFinishWithSuccess(mRequestId, PRODUCER_NAME, extraMap); Assert.assertFalse(mFinalImageReference.isValid()); }
/** * Options returned by this method are configured with mDecodeBuffer which is GuardedBy("this") */ private static BitmapFactory.Options getDecodeOptionsForStream( EncodedImage encodedImage, Bitmap.Config bitmapConfig) { final BitmapFactory.Options options = new BitmapFactory.Options(); // Sample size should ONLY be different than 1 when downsampling is enabled in the pipeline options.inSampleSize = encodedImage.getSampleSize(); options.inJustDecodeBounds = true; // fill outWidth and outHeight BitmapFactory.decodeStream(encodedImage.getInputStream(), null, options); if (options.outWidth == -1 || options.outHeight == -1) { throw new IllegalArgumentException(); } options.inJustDecodeBounds = false; options.inDither = true; options.inPreferredConfig = bitmapConfig; options.inMutable = true; return options; }
@Override public void onNewResultImpl(EncodedImage newResult, boolean isLast) { if (isLast && !EncodedImage.isValid(newResult)) { handleError(new NullPointerException("Encoded image is not valid.")); return; } if (!updateDecodeJob(newResult, isLast)) { return; } if (isLast || mProducerContext.isIntermediateResultExpected()) { mJobScheduler.scheduleJob(); } }
/** * Creates a bitmap from encoded bytes. * * @param encodedImage the encoded image with a reference to the encoded bytes * @param bitmapConfig the {@link android.graphics.Bitmap.Config} used to create the decoded * Bitmap * @return the bitmap * @exception java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ @Override public CloseableReference<Bitmap> decodeFromEncodedImage( EncodedImage encodedImage, Bitmap.Config bitmapConfig) { final BitmapFactory.Options options = getDecodeOptionsForStream(encodedImage, bitmapConfig); boolean retryOnFail = options.inPreferredConfig != Bitmap.Config.ARGB_8888; try { return decodeStaticImageFromStream(encodedImage.getInputStream(), options); } catch (RuntimeException re) { if (retryOnFail) { return decodeFromEncodedImage(encodedImage, Bitmap.Config.ARGB_8888); } throw re; } }
@Override protected synchronized boolean updateDecodeJob(EncodedImage encodedImage, boolean isLast) { boolean ret = super.updateDecodeJob(encodedImage, isLast); if (!isLast && EncodedImage.isValid(encodedImage)) { if (!mProgressiveJpegParser.parseMoreData(encodedImage)) { return false; } int scanNum = mProgressiveJpegParser.getBestScanNumber(); if (scanNum <= mLastScheduledScanNumber || scanNum < mProgressiveJpegConfig.getNextScanNumberToDecode(mLastScheduledScanNumber)) { return false; } mLastScheduledScanNumber = scanNum; } return ret; }
@Test public void testEncodedMemoryCacheGetNotFoundNextProducerSuccess() { setupEncodedMemoryCacheGetNotFound(); setupNextProducerStreamingSuccess(); mEncodedMemoryCacheProducer.produceResults(mConsumer, mProducerContext); verify(mMemoryCache, never()).cache(mCacheKey, mIntermediateImageReference); ArgumentCaptor<CloseableReference> argumentCaptor = ArgumentCaptor.forClass(CloseableReference.class); verify(mMemoryCache).cache(eq(mCacheKey), argumentCaptor.capture()); CloseableReference<PooledByteBuffer> capturedRef = (CloseableReference<PooledByteBuffer>) argumentCaptor.getValue(); Assert.assertSame( mFinalImageReference.getUnderlyingReferenceTestOnly(), capturedRef.getUnderlyingReferenceTestOnly()); verify(mConsumer).onNewResult(mIntermediateEncodedImage, false); verify(mConsumer).onNewResult(mFinalEncodedImage, true); Assert.assertTrue(EncodedImage.isValid(mFinalEncodedImageClone)); verify(mProducerListener).onProducerStart(mRequestId, PRODUCER_NAME); Map<String, String> extraMap = ImmutableMap.of(EncodedMemoryCacheProducer.VALUE_FOUND, "false"); verify(mProducerListener).onProducerFinishWithSuccess(mRequestId, PRODUCER_NAME, extraMap); }
@Override protected int getIntermediateImageEndOffset(EncodedImage encodedImage) { return encodedImage.getSize(); }