/* * Copyright (C) 2013 Peng fei Pan <sky@xiaopan.me> * * 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 me.xiaopan.sketch.decode; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.graphics.BitmapRegionDecoder; import android.graphics.Rect; import android.os.Build; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.LinkedList; import java.util.List; import me.xiaopan.sketch.SLog; import me.xiaopan.sketch.SLogType; import me.xiaopan.sketch.cache.DiskCache; import me.xiaopan.sketch.feature.ImageOrientationCorrector; import me.xiaopan.sketch.feature.ImageSizeCalculator; import me.xiaopan.sketch.request.LoadRequest; import me.xiaopan.sketch.request.MaxSize; import me.xiaopan.sketch.util.SketchUtils; /** * 图片解码器 */ public class DefaultImageDecoder implements ImageDecoder { protected String logName = "DefaultImageDecoder"; private DecodeTimeAnalyze timeAnalyze = new DecodeTimeAnalyze(); private List<DecodeHelper> decodeHelperList; private List<ResultProcessor> resultProcessorList; public DefaultImageDecoder() { decodeHelperList = new LinkedList<>(); resultProcessorList = new LinkedList<>(); decodeHelperList.add(new ProcessedCacheDecodeHelper()); decodeHelperList.add(new GifDecodeHelper()); decodeHelperList.add(new ThumbnailModeDecodeHelper()); decodeHelperList.add(new NormalDecodeHelper()); resultProcessorList.add(new CorrectOrientationResultProcessor()); resultProcessorList.add(new ProcessImageResultProcessor()); resultProcessorList.add(new ProcessedCacheResultProcessor()); } public static Bitmap decodeBitmap(DataSource dataSource, BitmapFactory.Options options) throws IOException { InputStream inputStream = null; Bitmap bitmap = null; try { inputStream = dataSource.getInputStream(); bitmap = BitmapFactory.decodeStream(inputStream, null, options); } finally { SketchUtils.close(inputStream); } return bitmap; } public static Bitmap decodeRegionBitmap(DataSource dataSource, Rect srcRect, BitmapFactory.Options options) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD_MR1) { return null; } InputStream inputStream; try { inputStream = dataSource.getInputStream(); } catch (IOException e) { e.printStackTrace(); return null; } BitmapRegionDecoder regionDecoder; try { regionDecoder = BitmapRegionDecoder.newInstance(inputStream, false); } catch (IOException e) { e.printStackTrace(); return null; } finally { SketchUtils.close(inputStream); } Bitmap bitmap = regionDecoder.decodeRegion(srcRect, options); regionDecoder.recycle(); SketchUtils.close(inputStream); return bitmap; } static void decodeSuccess(Bitmap bitmap, int outWidth, int outHeight, int inSampleSize, LoadRequest loadRequest, String logName) { if (SLogType.REQUEST.isEnabled()) { if (bitmap != null && loadRequest.getOptions().getMaxSize() != null) { MaxSize maxSize = loadRequest.getOptions().getMaxSize(); ImageSizeCalculator sizeCalculator = loadRequest.getConfiguration().getImageSizeCalculator(); SLog.d(SLogType.REQUEST, logName, "decodeSuccess. originalSize=%dx%d, targetSize=%dx%d, " + "targetSizeScale=%s, inSampleSize=%d, finalSize=%dx%d. %s", outWidth, outHeight, maxSize.getWidth(), maxSize.getHeight(), sizeCalculator.getTargetSizeScale(), inSampleSize, bitmap.getWidth(), bitmap.getHeight(), loadRequest.getKey()); } else { SLog.d(SLogType.REQUEST, logName, "decodeSuccess. unchanged. %s", loadRequest.getKey()); } } } static void decodeError(LoadRequest loadRequest, DataSource dataSource, String logName) { if (dataSource instanceof CacheFileDataSource) { DiskCache.Entry diskCacheEntry = ((CacheFileDataSource) dataSource).getDiskCacheEntry(); if (SLogType.REQUEST.isEnabled()) { SLog.e(SLogType.REQUEST, logName, "decode failed. diskCacheKey=%s. %s", diskCacheEntry.getUri(), loadRequest.getKey()); } if (!diskCacheEntry.delete()) { if (SLogType.REQUEST.isEnabled()) { SLog.e(SLogType.REQUEST, logName, "delete image disk cache file failed. diskCacheKey=%s. %s", diskCacheEntry.getUri(), loadRequest.getKey()); } } } if (dataSource instanceof FileDataSource) { File file = ((FileDataSource) dataSource).getFile(); if (SLogType.REQUEST.isEnabled()) { SLog.e(SLogType.REQUEST, logName, "decode failed. filePath=%s, fileLength=%d", file.getPath(), file.exists() ? file.length() : 0); } } else { if (SLogType.REQUEST.isEnabled()) { SLog.e(SLogType.REQUEST, logName, "decode failed. %s", String.valueOf(loadRequest.getUri())); } } } @Override public DecodeResult decode(LoadRequest request) throws DecodeException { long startTime = 0; if (SLogType.TIME.isEnabled()) { startTime = timeAnalyze.decodeStart(); } DecodeResult result = null; try { result = doDecode(request); } catch (DecodeException e) { throw e; } catch (Throwable e) { e.printStackTrace(); } if (SLogType.TIME.isEnabled()) { timeAnalyze.decodeEnd(startTime, logName, request.getKey()); } if (result != null) { try { doProcess(request, result); } catch (DecodeException e) { result.recycle(request.getConfiguration().getBitmapPool()); throw e; } catch (Throwable e) { e.printStackTrace(); result.recycle(request.getConfiguration().getBitmapPool()); result = null; } } return result; } private DecodeResult doDecode(LoadRequest request) throws DecodeException { // Make date source DataSource dataSource = DataSourceFactory.makeDataSourceByRequest(request, false, logName); // Decode bounds and mime info Options boundOptions = new Options(); boundOptions.inJustDecodeBounds = true; try { decodeBitmap(dataSource, boundOptions); } catch (IOException e) { e.printStackTrace(); SLog.e(SLogType.REQUEST, logName, "decode bounds failed %s", request.getKey()); decodeError(request, dataSource, logName); return null; } // Exclude images with a width of less than or equal to 1 if (boundOptions.outWidth <= 1 || boundOptions.outHeight <= 1) { SLog.e(SLogType.REQUEST, logName, "image width or height less than or equal to 1px. imageSize: %dx%d. %s", boundOptions.outWidth, boundOptions.outHeight, request.getKey()); decodeError(request, dataSource, logName); return null; } // Read image orientation int imageOrientation = 0; if (request.getOptions().isCorrectImageOrientation()) { ImageOrientationCorrector imageOrientationCorrector = request.getConfiguration().getImageOrientationCorrector(); imageOrientation = imageOrientationCorrector.readImageRotateDegrees(boundOptions.outMimeType, dataSource); } ImageType imageType = ImageType.valueOfMimeType(boundOptions.outMimeType); // Set whether priority is given to quality or speed Options decodeOptions = new Options(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1 && request.getOptions().isInPreferQualityOverSpeed()) { decodeOptions.inPreferQualityOverSpeed = true; } // Setup preferred bitmap config Bitmap.Config newConfig = request.getOptions().getBitmapConfig(); if (newConfig == null && imageType != null) { newConfig = imageType.getConfig(request.getOptions().isLowQualityImage()); } if (newConfig != null) { decodeOptions.inPreferredConfig = newConfig; } DecodeResult decodeResult = null; for (DecodeHelper decodeHelper : decodeHelperList) { if (decodeHelper.match(request, dataSource, imageType, boundOptions)) { decodeResult = decodeHelper.decode(request, dataSource, imageType, boundOptions, decodeOptions, imageOrientation); break; } } if (decodeResult != null) { decodeResult.setImageFrom(dataSource.getImageFrom()); } return decodeResult; } private void doProcess(LoadRequest request, DecodeResult result) throws DecodeException { for (ResultProcessor resultProcessor : resultProcessorList) { resultProcessor.process(request, result); } } @Override public String getKey() { return logName; } }