/* * 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 java.util.concurrent.Executor; import android.util.Pair; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.references.CloseableReference; import com.facebook.common.util.TriState; import com.facebook.imagepipeline.common.ResizeOptions; import com.facebook.imagepipeline.memory.PooledByteBuffer; import com.facebook.imagepipeline.memory.PooledByteBufferFactory; import com.facebook.imagepipeline.memory.PooledByteBufferOutputStream; import com.facebook.imagepipeline.nativecode.JpegTranscoder; import com.facebook.imagepipeline.request.ImageRequest; /** * Resizes and rotates jpeg image according to exif orientation data. * * <p> If image is not jpeg then no transformation is applied. */ public class ResizeAndRotateProducer extends ImageTransformProducer< Pair<CloseableReference<PooledByteBuffer>, ImageTransformMetaData>, ImageTransformMetaData> { private static final String PRODUCER_NAME = "ResizeAndRotateProducer"; @VisibleForTesting static final int DEFAULT_JPEG_QUALITY = 85; @VisibleForTesting static final int MAX_JPEG_SCALE_NUMERATOR = JpegTranscoder.SCALE_DENOMINATOR; public ResizeAndRotateProducer( Executor executor, PooledByteBufferFactory pooledByteBufferFactory, Producer<Pair<CloseableReference<PooledByteBuffer>, ImageTransformMetaData>> nextProducer) { super(executor, pooledByteBufferFactory, nextProducer); } @Override protected TriState shouldTransform( final Pair<CloseableReference<PooledByteBuffer>, ImageTransformMetaData> input, final ImageRequest imageRequest, boolean isLast) { ImageTransformMetaData metaData = input.second; switch (metaData.getImageFormat()) { case JPEG: return isLast ? TriState.valueOf( getRotationAngle(imageRequest, metaData) != 0 || getScaleNumerator(imageRequest, metaData) != JpegTranscoder.SCALE_DENOMINATOR) : TriState.UNSET; case UNKNOWN: return isLast ? TriState.NO : TriState.UNSET; default: return TriState.NO; } } @Override protected void transform( final CloseableReference<PooledByteBuffer> imageRef, final PooledByteBufferOutputStream outputStream, final ImageRequest imageRequest, final ImageTransformMetaData metaData) throws Exception { JpegTranscoder.transcodeJpeg( imageRef.get().getStream(), outputStream, getRotationAngle(imageRequest, metaData), getScaleNumerator(imageRequest, metaData), DEFAULT_JPEG_QUALITY); } @Override protected CloseableReference<PooledByteBuffer> getImageCopy( Pair<CloseableReference<PooledByteBuffer>, ImageTransformMetaData> originalResult) { return originalResult.first.clone(); } @Override protected ImageTransformMetaData getExtraInformation( Pair<CloseableReference<PooledByteBuffer>, ImageTransformMetaData> originalResult) { return originalResult.second; } @Override protected Pair<CloseableReference<PooledByteBuffer>, ImageTransformMetaData> createReturnValue( PooledByteBuffer transformedBytes, ImageTransformMetaData metaData) { return Pair.create(CloseableReference.of(transformedBytes), metaData); } @Override protected void closeReturnValue( Pair<CloseableReference<PooledByteBuffer>, ImageTransformMetaData> returnValue) { CloseableReference.closeSafely(returnValue.first); } @Override protected String getProducerName() { return PRODUCER_NAME; } @Override protected boolean shouldAllowCancellation() { return true; } private static int getScaleNumerator( final ImageRequest imageRequest, final ImageTransformMetaData metaData) { final ResizeOptions resizeOptions = imageRequest.getResizeOptions(); if (resizeOptions == null) { return JpegTranscoder.SCALE_DENOMINATOR; } final int rotationAngle = getRotationAngle(imageRequest, metaData); final boolean swapDimensions = rotationAngle == 90 || rotationAngle == 270; final int widthAfterRotation = swapDimensions ? metaData.getHeight() : metaData.getWidth(); final int heightAfterRotation = swapDimensions ? metaData.getWidth() : metaData.getHeight(); final float widthRatio = ((float) resizeOptions.width) / widthAfterRotation; final float heightRatio = ((float) resizeOptions.height) / heightAfterRotation; final int numerator = (int) Math.ceil(Math.max(widthRatio, heightRatio) * JpegTranscoder.SCALE_DENOMINATOR); if (numerator > MAX_JPEG_SCALE_NUMERATOR) { return JpegTranscoder.SCALE_DENOMINATOR; } if (numerator < 1) { return 1; } return numerator; } private static int getRotationAngle( final ImageRequest imageRequest, final ImageTransformMetaData metaData) { if (!imageRequest.getAutoRotateEnabled()) { return 0; } int rotationAngle = metaData.getRotationAngle(); Preconditions.checkArgument( rotationAngle == 0 || rotationAngle == 90 || rotationAngle == 180 || rotationAngle == 270); return rotationAngle; } }