/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.imagepipeline.producers; import javax.annotation.concurrent.GuardedBy; import java.util.Map; import java.util.concurrent.Executor; import android.graphics.Bitmap; import com.facebook.common.internal.ImmutableMap; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.references.CloseableReference; import com.facebook.imagepipeline.decoder.CloseableImageCopier; import com.facebook.imagepipeline.image.CloseableBitmap; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.request.Postprocessor; import com.facebook.imagepipeline.request.RepeatedPostprocessor; import com.facebook.imagepipeline.request.RepeatedPostprocessorRunner; /** * Runs a caller-supplied post-processor object. * * <p>Post-processors are only supported for static bitmaps. If the request is for an animated * image, the post-processor step will be skipped without warning. */ public class PostprocessorProducer implements Producer<CloseableReference<CloseableImage>> { @VisibleForTesting static final String NAME = "PostprocessorProducer"; @VisibleForTesting static final String BITMAP_COPIED_EVENT = "bitmap_copied"; @VisibleForTesting static final String POSTPROCESSOR = "Postprocessor"; private final Producer<CloseableReference<CloseableImage>> mNextProducer; private final CloseableImageCopier mCloseableImageCopier; private final Executor mExecutor; public PostprocessorProducer( Producer<CloseableReference<CloseableImage>> nextProducer, CloseableImageCopier closeableImageCopier, Executor executor) { mNextProducer = Preconditions.checkNotNull(nextProducer); mCloseableImageCopier = Preconditions.checkNotNull(closeableImageCopier); mExecutor = Preconditions.checkNotNull(executor); } @Override public void produceResults( final Consumer<CloseableReference<CloseableImage>> consumer, ProducerContext context) { final ProducerListener listener = context.getListener(); final Postprocessor postprocessor = context.getImageRequest().getPostprocessor(); Consumer<CloseableReference<CloseableImage>> postprocessorConsumer; if (postprocessor instanceof RepeatedPostprocessor) { postprocessorConsumer = new RepeatedPostprocessorConsumer( consumer, listener, context.getId(), (RepeatedPostprocessor) postprocessor, context); } else { postprocessorConsumer = new SingleUsePostprocessorConsumer(consumer, listener, context.getId(), postprocessor); } mNextProducer.produceResults(postprocessorConsumer, context); } private abstract class AbstractPostprocessorConsumer extends BaseConsumer<CloseableReference<CloseableImage>> { protected final Consumer<CloseableReference<CloseableImage>> mConsumer; private final ProducerListener mListener; private final String mRequestId; private final Postprocessor mPostprocessor; private AbstractPostprocessorConsumer( Consumer<CloseableReference<CloseableImage>> consumer, ProducerListener listener, String requestId, Postprocessor postprocessor) { mConsumer = consumer; mListener = listener; mRequestId = requestId; mPostprocessor = postprocessor; } @Override protected void onFailureImpl(Throwable t) { mConsumer.onFailure(t); } @Override protected void onCancellationImpl() { mConsumer.onCancellation(); } protected void copyAndPostprocessBitmap( CloseableReference<CloseableImage> sourceImageRef, boolean isLast) { mListener.onProducerStart(mRequestId, NAME); CloseableReference<CloseableImage> destRef = null; try { try { destRef = mCloseableImageCopier.copyCloseableImage(sourceImageRef); mListener.onProducerEvent(mRequestId, NAME, BITMAP_COPIED_EVENT); postprocessBitmap(destRef, mPostprocessor); } catch (Throwable t) { mListener.onProducerFinishWithFailure( mRequestId, NAME, t, getExtraMap(mListener, mRequestId, mPostprocessor)); mConsumer.onFailure(t); return; } mListener.onProducerFinishWithSuccess( mRequestId, NAME, getExtraMap(mListener, mRequestId, mPostprocessor)); mConsumer.onNewResult(destRef, isLast); } finally { CloseableReference.closeSafely(destRef); } } private void postprocessBitmap( CloseableReference<CloseableImage> destinationCloseableImageRef, Postprocessor postprocessor) { Bitmap destinationBitmap = ((CloseableBitmap) destinationCloseableImageRef.get()).getUnderlyingBitmap(); postprocessor.process(destinationBitmap); } private Map<String, String> getExtraMap( ProducerListener listener, String requestId, Postprocessor postprocessor) { if (!listener.requiresExtraMap(requestId)) { return null; } return ImmutableMap.of(POSTPROCESSOR, postprocessor.getName()); } } private class SingleUsePostprocessorConsumer extends AbstractPostprocessorConsumer { private SingleUsePostprocessorConsumer( Consumer<CloseableReference<CloseableImage>> consumer, ProducerListener listener, String requestId, Postprocessor postprocessor) { super(consumer, listener, requestId, postprocessor); } @Override protected void onNewResultImpl( final CloseableReference<CloseableImage> newResult, final boolean isLast) { if (!isLast) { return; } if (!mCloseableImageCopier.isCloseableImageCopyable(newResult)) { mConsumer.onNewResult(newResult, true); return; } final CloseableReference<CloseableImage> clonedResult = newResult.clone(); mExecutor.execute( new Runnable() { @Override public void run() { try { copyAndPostprocessBitmap(clonedResult, isLast); } finally { CloseableReference.closeSafely(clonedResult); } } }); } } private class RepeatedPostprocessorConsumer extends AbstractPostprocessorConsumer implements RepeatedPostprocessorRunner { @GuardedBy("this") private CloseableReference<CloseableImage> mOriginalImageRef; @GuardedBy("this") private boolean mIsDirty; @GuardedBy("this") private boolean mIsPostProcessingRunning; private RepeatedPostprocessorConsumer( Consumer<CloseableReference<CloseableImage>> consumer, ProducerListener listener, String requestId, RepeatedPostprocessor repeatedPostprocessor, ProducerContext context) { super(consumer, listener, requestId, repeatedPostprocessor); repeatedPostprocessor.setCallback(RepeatedPostprocessorConsumer.this); context.addCallbacks( new BaseProducerContextCallbacks() { @Override public void onCancellationRequested() { closeOriginalImage(); mConsumer.onCancellation(); } }); mIsDirty = false; mIsPostProcessingRunning = false; } @Override protected void onNewResultImpl( CloseableReference<CloseableImage> newResult, boolean isLast) { if (!isLast) { return; } if (!mCloseableImageCopier.isCloseableImageCopyable(newResult)) { mConsumer.onNewResult(newResult, true); return; } synchronized (RepeatedPostprocessorConsumer.this) { mOriginalImageRef = newResult.clone(); } maybeExecuteCopyAndPostprocessBitmap(); } @Override public synchronized void update() { maybeExecuteCopyAndPostprocessBitmap(); } private void maybeExecuteCopyAndPostprocessBitmap() { boolean shouldExecutePostProcessing = false; synchronized (RepeatedPostprocessorConsumer.this) { mIsDirty = true; if (!mIsPostProcessingRunning && CloseableReference.isValid(mOriginalImageRef)) { mIsPostProcessingRunning = true; shouldExecutePostProcessing = true; } } if (shouldExecutePostProcessing) { executeCopyAndPostprocessBitmap(); } } private void executeCopyAndPostprocessBitmap() { mExecutor.execute( new Runnable() { @Override public void run() { CloseableReference<CloseableImage> closeableImageRef = null; synchronized (RepeatedPostprocessorConsumer.this) { mIsDirty = false; if (CloseableReference.isValid(mOriginalImageRef)) { closeableImageRef = mOriginalImageRef.clone(); } } if (closeableImageRef != null) { try { copyAndPostprocessBitmap(closeableImageRef, /* isLast */false); } finally { CloseableReference.closeSafely(closeableImageRef); } } boolean shouldExecutePostprocessing; synchronized (RepeatedPostprocessorConsumer.this) { shouldExecutePostprocessing = mIsDirty; mIsPostProcessingRunning = mIsDirty; } if (shouldExecutePostprocessing) { executeCopyAndPostprocessBitmap(); } } }); } private synchronized void closeOriginalImage() { CloseableReference.closeSafely(mOriginalImageRef); mOriginalImageRef = null; } } }