7af6960f66d820ff5d7efc05c29d20573e0a97d9
[pub/Android/ownCloud.git] / src / com / owncloud / android / utils / BitmapUtils.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author David A. Velasco
5 * Copyright (C) 2015 ownCloud Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2,
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20 package com.owncloud.android.utils;
21
22 import com.owncloud.android.lib.common.utils.Log_OC;
23
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.graphics.Matrix;
27 import android.graphics.BitmapFactory.Options;
28 import android.media.ExifInterface;
29 import android.net.Uri;
30 import android.webkit.MimeTypeMap;
31
32 import java.io.File;
33
34 /**
35 * Utility class with methods for decoding Bitmaps.
36 */
37 public class BitmapUtils {
38
39
40 /**
41 * Decodes a bitmap from a file containing it minimizing the memory use, known that the bitmap
42 * will be drawn in a surface of reqWidth x reqHeight
43 *
44 * @param srcPath Absolute path to the file containing the image.
45 * @param reqWidth Width of the surface where the Bitmap will be drawn on, in pixels.
46 * @param reqHeight Height of the surface where the Bitmap will be drawn on, in pixels.
47 * @return
48 */
49 public static Bitmap decodeSampledBitmapFromFile(String srcPath, int reqWidth, int reqHeight) {
50
51 // set desired options that will affect the size of the bitmap
52 final Options options = new Options();
53 options.inScaled = true;
54 options.inPurgeable = true;
55 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
56 options.inPreferQualityOverSpeed = false;
57 }
58 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
59 options.inMutable = false;
60 }
61
62 // make a false load of the bitmap to get its dimensions
63 options.inJustDecodeBounds = true;
64
65 BitmapFactory.decodeFile(srcPath, options);
66
67 // calculate factor to subsample the bitmap
68 options.inSampleSize = calculateSampleFactor(options, reqWidth, reqHeight);
69
70 // decode bitmap with inSampleSize set
71 options.inJustDecodeBounds = false;
72 return BitmapFactory.decodeFile(srcPath, options);
73
74 }
75
76
77 /**
78 * Calculates a proper value for options.inSampleSize in order to decode a Bitmap minimizing
79 * the memory overload and covering a target surface of reqWidth x reqHeight if the original
80 * image is big enough.
81 *
82 * @param options Bitmap decoding options; options.outHeight and options.inHeight should
83 * be set.
84 * @param reqWidth Width of the surface where the Bitmap will be drawn on, in pixels.
85 * @param reqHeight Height of the surface where the Bitmap will be drawn on, in pixels.
86 * @return The largest inSampleSize value that is a power of 2 and keeps both
87 * height and width larger than reqWidth and reqHeight.
88 */
89 private static int calculateSampleFactor(Options options, int reqWidth, int reqHeight) {
90
91 final int height = options.outHeight;
92 final int width = options.outWidth;
93 int inSampleSize = 1;
94
95 if (height > reqHeight || width > reqWidth) {
96 final int halfHeight = height / 2;
97 final int halfWidth = width / 2;
98
99 // calculates the largest inSampleSize value (for smallest sample) that is a power of 2 and keeps both
100 // height and width **larger** than the requested height and width.
101 while ((halfHeight / inSampleSize) > reqHeight
102 && (halfWidth / inSampleSize) > reqWidth) {
103 inSampleSize *= 2;
104 }
105 }
106
107 return inSampleSize;
108 }
109
110 /**
111 * Rotate bitmap according to EXIF orientation.
112 * Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/
113 * @param bitmap Bitmap to be rotated
114 * @param storagePath Path to source file of bitmap. Needed for EXIF information.
115 * @return correctly EXIF-rotated bitmap
116 */
117 public static Bitmap rotateImage(Bitmap bitmap, String storagePath){
118 Bitmap resultBitmap = bitmap;
119
120 try
121 {
122 ExifInterface exifInterface = new ExifInterface(storagePath);
123 int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
124
125 Matrix matrix = new Matrix();
126
127 // 1: nothing to do
128
129 // 2
130 if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL)
131 {
132 matrix.postScale(-1.0f, 1.0f);
133 }
134 // 3
135 else if (orientation == ExifInterface.ORIENTATION_ROTATE_180)
136 {
137 matrix.postRotate(180);
138 }
139 // 4
140 else if (orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL)
141 {
142 matrix.postScale(1.0f, -1.0f);
143 }
144 // 5
145 else if (orientation == ExifInterface.ORIENTATION_TRANSPOSE)
146 {
147 matrix.postRotate(-90);
148 matrix.postScale(1.0f, -1.0f);
149 }
150 // 6
151 else if (orientation == ExifInterface.ORIENTATION_ROTATE_90)
152 {
153 matrix.postRotate(90);
154 }
155 // 7
156 else if (orientation == ExifInterface.ORIENTATION_TRANSVERSE)
157 {
158 matrix.postRotate(90);
159 matrix.postScale(1.0f, -1.0f);
160 }
161 // 8
162 else if (orientation == ExifInterface.ORIENTATION_ROTATE_270)
163 {
164 matrix.postRotate(270);
165 }
166
167 // Rotate the bitmap
168 resultBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
169 if (resultBitmap != bitmap) {
170 bitmap.recycle();
171 }
172 }
173 catch (Exception exception)
174 {
175 Log_OC.e("BitmapUtil", "Could not rotate the image: " + storagePath);
176 }
177 return resultBitmap;
178 }
179
180 /**
181 * Converts an HSL color value to RGB. Conversion formula
182 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
183 * Assumes h, s, and l are contained in the set [0, 1] and
184 * returns r, g, and b in the set [0, 255].
185 * from: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
186 *
187 * @param integer h The hue
188 * @param Integer s The saturation
189 * @param Integer l The lightness
190 * @return Array The RGB representation
191 */
192 public static int[] hslToRgb(Double h, Double s, Double l){
193 Double r, g, b;
194
195 if(s == 0){
196 r = g = b = l; // achromatic
197 } else {
198 Double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
199 Double p = 2 * l - q;
200 r = hue2rgb(p, q, h + 1/3) * 255;
201 g = hue2rgb(p, q, h) * 255;
202 b = hue2rgb(p, q, h - 1/3) * 255;
203 }
204
205
206 int[] array = {r.intValue(), g.intValue(), b.intValue()};
207 return array;
208 }
209
210 private static Double hue2rgb(Double p, Double q, Double t){
211 if(t < 0) t += 1;
212 if(t > 1) t -= 1;
213 if(t < 1/6) return p + (q - p) * 6 * t;
214 if(t < 1/2) return q;
215 if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
216 return p;
217 }
218
219
220 /**
221 * Checks if file passed is an image
222 * @param file
223 * @return true/false
224 */
225 public static boolean isImage(File file) {
226 Uri selectedUri = Uri.fromFile(file);
227 String fileExtension = MimeTypeMap.getFileExtensionFromUrl(selectedUri.toString().toLowerCase());
228 String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
229
230 return (mimeType != null && mimeType.startsWith("image/"));
231 }
232
233 }