OSDN Git Service

69e0c0bf1ab6a30cbb8e767e67a5c839f8add090
[android-x86/packages-apps-Eleven.git] / src / com / cyanogenmod / eleven / utils / ImageUtils.java
1 /*
2 * Copyright (C) 2014 The CyanogenMod 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.cyanogenmod.eleven.utils;
17
18 import android.content.Context;
19 import android.graphics.Bitmap;
20 import android.graphics.BitmapFactory;
21 import android.widget.ImageView;
22
23 import com.cyanogenmod.eleven.cache.ImageCache;
24 import com.cyanogenmod.eleven.cache.ImageWorker;
25 import com.cyanogenmod.eleven.lastfm.ImageSize;
26 import com.cyanogenmod.eleven.lastfm.MusicEntry;
27
28 import java.io.BufferedInputStream;
29 import java.io.BufferedOutputStream;
30 import java.io.File;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.net.HttpURLConnection;
35 import java.net.URL;
36 import java.util.concurrent.atomic.AtomicInteger;
37
38 public class ImageUtils {
39     private static final String DEFAULT_HTTP_CACHE_DIR = "http"; //$NON-NLS-1$
40
41     public static final int IO_BUFFER_SIZE_BYTES = 1024;
42
43     private static final int DEFAULT_MAX_IMAGE_HEIGHT = 1024;
44
45     private static final int DEFAULT_MAX_IMAGE_WIDTH = 1024;
46
47     private static AtomicInteger sInteger = new AtomicInteger(0);
48
49     /**
50      * Gets the image url based on the imageType
51      * @param artistName The artist name param used in the Last.fm API.
52      * @param albumName The album name param used in the Last.fm API.
53      * @param imageType The type of image URL to fetch for.
54      * @return The image URL for an artist image or album image.
55      */
56     public static String processImageUrl(final Context context, final String artistName,
57                                          final String albumName, final ImageWorker.ImageType imageType) {
58         switch (imageType) {
59             case ARTIST:
60                 // Disable last.fm calls - TODO: Find an alternative artwork provider that has
61                 // the proper license rights for artwork
62                 /*if (!TextUtils.isEmpty(artistName)) {
63                     if (PreferenceUtils.getInstance(context).downloadMissingArtistImages()) {
64                         final Artist artist = Artist.getInfo(context, artistName);
65                         if (artist != null) {
66                             return getBestImage(artist);
67                         }
68                     }
69                 }*/
70                 break;
71             case ALBUM:
72                 // Disable last.fm calls - TODO: Find an alternative artwork provider that has
73                 // the proper license rights for artwork
74                 /*if (!TextUtils.isEmpty(artistName) && !TextUtils.isEmpty(albumName)) {
75                     if (PreferenceUtils.getInstance(context).downloadMissingArtwork()) {
76                         final Artist correction = Artist.getCorrection(context, artistName);
77                         if (correction != null) {
78                             final Album album = Album.getInfo(context, correction.getName(),
79                                     albumName);
80                             if (album != null) {
81                                 return getBestImage(album);
82                             }
83                         }
84                     }
85                 }*/
86                 break;
87             default:
88                 break;
89         }
90         return null;
91     }
92
93     /**
94      * Downloads the bitmap from the url and returns it after some processing
95      *
96      * @param key The key to identify which image to process, as provided by
97      *            {@link ImageWorker#loadImage(mKey, android.widget.ImageView)}
98      * @return The processed {@link Bitmap}.
99      */
100     public static Bitmap processBitmap(final Context context, final String url) {
101         if (url == null) {
102             return null;
103         }
104         final File file = downloadBitmapToFile(context, url, DEFAULT_HTTP_CACHE_DIR);
105         if (file != null) {
106             // Return a sampled down version
107             final Bitmap bitmap = decodeSampledBitmapFromFile(file.toString());
108             file.delete();
109             if (bitmap != null) {
110                 return bitmap;
111             }
112         }
113         return null;
114     }
115
116     /**
117      * Decode and sample down a {@link Bitmap} from a file to the requested
118      * width and height.
119      *
120      * @param filename The full path of the file to decode
121      * @param reqWidth The requested width of the resulting bitmap
122      * @param reqHeight The requested height of the resulting bitmap
123      * @return A {@link Bitmap} sampled down from the original with the same
124      *         aspect ratio and dimensions that are equal to or greater than the
125      *         requested width and height
126      */
127     public static Bitmap decodeSampledBitmapFromFile(final String filename) {
128
129         // First decode with inJustDecodeBounds=true to check dimensions
130         final BitmapFactory.Options options = new BitmapFactory.Options();
131         options.inJustDecodeBounds = true;
132         BitmapFactory.decodeFile(filename, options);
133
134         // Calculate inSampleSize
135         options.inSampleSize = calculateInSampleSize(options, DEFAULT_MAX_IMAGE_WIDTH,
136                 DEFAULT_MAX_IMAGE_HEIGHT);
137
138         // Decode bitmap with inSampleSize set
139         options.inJustDecodeBounds = false;
140         return BitmapFactory.decodeFile(filename, options);
141     }
142
143     /**
144      * Calculate an inSampleSize for use in a
145      * {@link android.graphics.BitmapFactory.Options} object when decoding
146      * bitmaps using the decode* methods from {@link BitmapFactory}. This
147      * implementation calculates the closest inSampleSize that will result in
148      * the final decoded bitmap having a width and height equal to or larger
149      * than the requested width and height. This implementation does not ensure
150      * a power of 2 is returned for inSampleSize which can be faster when
151      * decoding but results in a larger bitmap which isn't as useful for caching
152      * purposes.
153      *
154      * @param options An options object with out* params already populated (run
155      *            through a decode* method with inJustDecodeBounds==true
156      * @param reqWidth The requested width of the resulting bitmap
157      * @param reqHeight The requested height of the resulting bitmap
158      * @return The value to be used for inSampleSize
159      */
160     public static final int calculateInSampleSize(final BitmapFactory.Options options,
161                                                   final int reqWidth, final int reqHeight) {
162         /* Raw height and width of image */
163         final int height = options.outHeight;
164         final int width = options.outWidth;
165         int inSampleSize = 1;
166
167         if (height > reqHeight || width > reqWidth) {
168             if (width > height) {
169                 inSampleSize = Math.round((float)height / (float)reqHeight);
170             } else {
171                 inSampleSize = Math.round((float)width / (float)reqWidth);
172             }
173
174             // This offers some additional logic in case the image has a strange
175             // aspect ratio. For example, a panorama may have a much larger
176             // width than height. In these cases the total pixels might still
177             // end up being too large to fit comfortably in memory, so we should
178             // be more aggressive with sample down the image (=larger
179             // inSampleSize).
180
181             final float totalPixels = width * height;
182
183             /* More than 2x the requested pixels we'll sample down further */
184             final float totalReqPixelsCap = reqWidth * reqHeight * 2;
185
186             while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
187                 inSampleSize++;
188             }
189         }
190         return inSampleSize;
191     }
192
193     private static String getBestImage(MusicEntry e) {
194         final ImageSize[] QUALITY = {ImageSize.EXTRALARGE, ImageSize.LARGE, ImageSize.MEDIUM,
195                 ImageSize.SMALL, ImageSize.UNKNOWN};
196         for(ImageSize q : QUALITY) {
197             String url = e.getImageURL(q);
198             if (url != null) {
199                 return url;
200             }
201         }
202         return null;
203     }
204
205     /**
206      * Download a {@link Bitmap} from a URL, write it to a disk and return the
207      * File pointer. This implementation uses a simple disk cache.
208      *
209      * @param context The context to use
210      * @param urlString The URL to fetch
211      * @return A {@link File} pointing to the fetched bitmap
212      */
213     public static final File downloadBitmapToFile(final Context context, final String urlString,
214                                                   final String uniqueName) {
215         final File cacheDir = ImageCache.getDiskCacheDir(context, uniqueName);
216
217         if (!cacheDir.exists()) {
218             cacheDir.mkdir();
219         }
220
221         HttpURLConnection urlConnection = null;
222         BufferedOutputStream out = null;
223
224         try {
225             // increment the number to not collisions on the temp file name.  A collision can
226             // potentially cause up to 50s on the first creation of the temp file but not on
227             // subsequent ones for some reason.
228             int number = sInteger.getAndIncrement() % 10;
229             final File tempFile = File.createTempFile("bitmap" + number, null, cacheDir); //$NON-NLS-1$
230
231             final URL url = new URL(urlString);
232             urlConnection = (HttpURLConnection)url.openConnection();
233             if (urlConnection.getResponseCode() != HttpURLConnection.HTTP_OK) {
234                 return null;
235             }
236             int contentLength = urlConnection.getContentLength();
237             final InputStream in = new BufferedInputStream(urlConnection.getInputStream(),
238                     IO_BUFFER_SIZE_BYTES);
239             out = new BufferedOutputStream(new FileOutputStream(tempFile), IO_BUFFER_SIZE_BYTES);
240
241             final byte[] buffer = new byte[IO_BUFFER_SIZE_BYTES];
242             int numBytes;
243             while ((numBytes = in.read(buffer)) != -1) {
244                 out.write(buffer, 0, numBytes);
245                 contentLength -= numBytes;
246             }
247
248             // valid values for contentLength are either -ve (meaning it wasn't set) or 0
249             // if it is  > 0 that means we got a value but didn't fully download the content
250             if (contentLength > 0) {
251                 return null;
252             }
253
254             return tempFile;
255         } catch (final IOException ignored) {
256         } finally {
257             if (urlConnection != null) {
258                 urlConnection.disconnect();
259             }
260             if (out != null) {
261                 try {
262                     out.close();
263                 } catch (final IOException ignored) {
264                 }
265             }
266         }
267         return null;
268     }
269
270     /**
271      * Scale the bitmap to an image view. The bitmap will fill the image view bounds. The bitmap will be scaled
272      * while maintaining the aspect ratio and cropped if it exceeds the image-view bounds.
273      */
274     public static Bitmap scaleBitmapForImageView(Bitmap src, ImageView imageView) {
275         if (src == null || imageView == null) {
276             return src;
277         }
278         // get bitmap properties
279         int srcHeight = src.getHeight();
280         int srcWidth = src.getWidth();
281
282         // get image view bounds
283         int viewHeight = imageView.getHeight();
284         int viewWidth = imageView.getWidth();
285
286         int deltaWidth = viewWidth - srcWidth;
287         int deltaHeight = viewHeight - srcHeight;
288
289         if (deltaWidth <= 0 && deltaWidth <= 0)     // nothing to do if src bitmap is bigger than image-view
290             return src;
291
292         // scale bitmap along the dimension that is lacking the greatest
293         float scale = Math.max( ((float)viewWidth) / srcWidth, ((float)viewHeight) / srcHeight);
294
295         // calculate the new bitmap dimensions
296         int dstHeight = (int) Math.ceil(srcHeight * scale);
297         int dstWidth = (int) Math.ceil(srcWidth * scale);
298         Bitmap scaledBitmap =  Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);
299
300         return Bitmap.createBitmap(scaledBitmap, 0, 0, viewWidth, viewHeight);
301
302     }
303 }