1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.photos.data; 17 18 import android.content.Context; 19 import android.content.res.Resources; 20 import android.graphics.Bitmap; 21 import android.graphics.Bitmap.CompressFormat; 22 import android.graphics.BitmapFactory; 23 import android.util.Log; 24 import android.util.Pools.SimplePool; 25 import android.util.Pools.SynchronizedPool; 26 27 import com.android.gallery3d.R; 28 import com.android.gallery3d.common.BitmapUtils; 29 import com.android.gallery3d.common.Utils; 30 import com.android.gallery3d.data.DecodeUtils; 31 import com.android.gallery3d.data.MediaItem; 32 import com.android.gallery3d.util.ThreadPool.CancelListener; 33 import com.android.gallery3d.util.ThreadPool.JobContext; 34 import com.android.photos.data.MediaRetriever.MediaSize; 35 36 import java.io.File; 37 import java.io.FileOutputStream; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.io.OutputStream; 41 42 public class MediaCacheUtils { 43 private static final String TAG = MediaCacheUtils.class.getSimpleName(); 44 private static int QUALITY = 80; 45 private static final int BUFFER_SIZE = 4096; 46 private static final SimplePool<byte[]> mBufferPool = new SynchronizedPool<byte[]>(5); 47 48 private static final JobContext sJobStub = new JobContext() { 49 50 @Override 51 public boolean isCancelled() { 52 return false; 53 } 54 55 @Override 56 public void setCancelListener(CancelListener listener) { 57 } 58 59 @Override 60 public boolean setMode(int mode) { 61 return true; 62 } 63 }; 64 65 private static int mTargetThumbnailSize; 66 private static int mTargetPreviewSize; 67 68 public static void initialize(Context context) { 69 Resources resources = context.getResources(); 70 mTargetThumbnailSize = resources.getDimensionPixelSize(R.dimen.size_thumbnail); 71 mTargetPreviewSize = resources.getDimensionPixelSize(R.dimen.size_preview); 72 } 73 74 public static int getTargetSize(MediaSize size) { 75 return (size == MediaSize.Thumbnail) ? mTargetThumbnailSize : mTargetPreviewSize; 76 } 77 78 public static boolean downsample(File inBitmap, MediaSize targetSize, File outBitmap) { 79 if (MediaSize.Original == targetSize) { 80 return false; // MediaCache should use the local path for this. 81 } 82 int size = getTargetSize(targetSize); 83 BitmapFactory.Options options = new BitmapFactory.Options(); 84 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 85 // TODO: remove unnecessary job context from DecodeUtils. 86 Bitmap bitmap = DecodeUtils.decodeThumbnail(sJobStub, inBitmap.getPath(), options, size, 87 MediaItem.TYPE_THUMBNAIL); 88 boolean success = (bitmap != null); 89 if (success) { 90 success = writeAndRecycle(bitmap, outBitmap); 91 } 92 return success; 93 } 94 95 public static boolean downsample(Bitmap inBitmap, MediaSize size, File outBitmap) { 96 if (MediaSize.Original == size) { 97 return false; // MediaCache should use the local path for this. 98 } 99 int targetSize = getTargetSize(size); 100 boolean success; 101 if (!needsDownsample(inBitmap, size)) { 102 success = writeAndRecycle(inBitmap, outBitmap); 103 } else { 104 float maxDimension = Math.max(inBitmap.getWidth(), inBitmap.getHeight()); 105 float scale = targetSize / maxDimension; 106 int targetWidth = Math.round(scale * inBitmap.getWidth()); 107 int targetHeight = Math.round(scale * inBitmap.getHeight()); 108 Bitmap scaled = Bitmap.createScaledBitmap(inBitmap, targetWidth, targetHeight, false); 109 success = writeAndRecycle(scaled, outBitmap); 110 inBitmap.recycle(); 111 } 112 return success; 113 } 114 115 public static boolean extractImageFromVideo(File inVideo, File outBitmap) { 116 Bitmap bitmap = BitmapUtils.createVideoThumbnail(inVideo.getPath()); 117 return writeAndRecycle(bitmap, outBitmap); 118 } 119 120 public static boolean needsDownsample(Bitmap bitmap, MediaSize size) { 121 if (size == MediaSize.Original) { 122 return false; 123 } 124 int targetSize = getTargetSize(size); 125 int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); 126 return maxDimension > (targetSize * 4 / 3); 127 } 128 129 public static boolean writeAndRecycle(Bitmap bitmap, File outBitmap) { 130 boolean success = writeToFile(bitmap, outBitmap); 131 bitmap.recycle(); 132 return success; 133 } 134 135 public static boolean writeToFile(Bitmap bitmap, File outBitmap) { 136 boolean success = false; 137 try { 138 FileOutputStream out = new FileOutputStream(outBitmap); 139 success = bitmap.compress(CompressFormat.JPEG, QUALITY, out); 140 out.close(); 141 } catch (IOException e) { 142 Log.w(TAG, "Couldn't write bitmap to cache", e); 143 // success is already false 144 } 145 return success; 146 } 147 148 public static int copyStream(InputStream in, OutputStream out) throws IOException { 149 byte[] buffer = mBufferPool.acquire(); 150 if (buffer == null) { 151 buffer = new byte[BUFFER_SIZE]; 152 } 153 try { 154 int totalWritten = 0; 155 int bytesRead; 156 while ((bytesRead = in.read(buffer)) >= 0) { 157 out.write(buffer, 0, bytesRead); 158 totalWritten += bytesRead; 159 } 160 return totalWritten; 161 } finally { 162 Utils.closeSilently(in); 163 Utils.closeSilently(out); 164 mBufferPool.release(buffer); 165 } 166 } 167 } 168