• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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 
17 package com.android.videoeditor.util;
18 
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.lang.Math;
24 
25 import android.content.Context;
26 import android.graphics.Bitmap;
27 import android.graphics.Bitmap.CompressFormat;
28 import android.graphics.BitmapFactory;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.drawable.Drawable;
32 import android.graphics.Matrix;
33 import android.graphics.Paint;
34 import android.graphics.Rect;
35 import android.graphics.Typeface;
36 import android.media.ExifInterface;
37 import android.util.Log;
38 
39 import com.android.videoeditor.R;
40 import com.android.videoeditor.service.MovieOverlay;
41 
42 /**
43  * Image utility methods
44  */
45 public class ImageUtils {
46     /**
47      *  Logging
48      */
49     private static final String TAG = "ImageUtils";
50 
51     // The resize paint
52     private static final Paint sResizePaint = new Paint(Paint.FILTER_BITMAP_FLAG);
53 
54     // The match aspect ratio mode for scaleImage
55     public static int MATCH_SMALLER_DIMENSION = 1;
56     public static int MATCH_LARGER_DIMENSION = 2;
57 
58     /**
59      * It is not possible to instantiate this class
60      */
ImageUtils()61     private ImageUtils() {
62     }
63 
64     /**
65      * Resize a bitmap to the specified width and height.
66      *
67      * @param filename The filename
68      * @param width The thumbnail width
69      * @param height The thumbnail height
70      * @param match MATCH_SMALLER_DIMENSION or MATCH_LARGER_DIMMENSION
71      *
72      * @return The resized bitmap
73      */
scaleImage(String filename, int width, int height, int match)74     public static Bitmap scaleImage(String filename, int width, int height, int match)
75             throws IOException {
76         final BitmapFactory.Options dbo = new BitmapFactory.Options();
77         dbo.inJustDecodeBounds = true;
78         BitmapFactory.decodeFile(filename, dbo);
79 
80         final int nativeWidth = dbo.outWidth;
81         final int nativeHeight = dbo.outHeight;
82 
83         final Bitmap srcBitmap;
84         float scaledWidth, scaledHeight;
85         final BitmapFactory.Options options = new BitmapFactory.Options();
86         if (nativeWidth > width || nativeHeight > height) {
87             float dx = ((float) nativeWidth) / ((float) width);
88             float dy = ((float) nativeHeight) / ((float) height);
89             float scale = (match == MATCH_SMALLER_DIMENSION) ? Math.max(dx,dy) : Math.min(dx,dy);
90             scaledWidth = nativeWidth / scale;
91             scaledHeight = nativeHeight / scale;
92             // Create the bitmap from file.
93             options.inSampleSize = (scale > 1.0f) ? ((int) scale) : 1;
94        } else {
95             scaledWidth = width;
96             scaledHeight = height;
97             options.inSampleSize = 1;
98        }
99 
100        srcBitmap = BitmapFactory.decodeFile(filename, options);
101        if (srcBitmap == null) {
102          throw new IOException("Cannot decode file: " + filename);
103        }
104 
105        // Create the canvas bitmap.
106        final Bitmap bitmap = Bitmap.createBitmap(Math.round(scaledWidth),
107                Math.round(scaledHeight),
108                Bitmap.Config.ARGB_8888);
109        final Canvas canvas = new Canvas(bitmap);
110        canvas.drawBitmap(srcBitmap,
111                new Rect(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight()),
112                new Rect(0, 0, Math.round(scaledWidth), Math.round(scaledHeight)),
113                sResizePaint);
114 
115        // Release the source bitmap
116        srcBitmap.recycle();
117        return bitmap;
118     }
119 
120     /**
121      * Rotate a JPEG according to the EXIF data
122      *
123      * @param inputFilename The name of the input file (must be a JPEG filename)
124      * @param outputFile The rotated file
125      *
126      * @return true if the image was rotated
127      */
transformJpeg(String inputFilename, File outputFile)128     public static boolean transformJpeg(String inputFilename, File outputFile)
129             throws IOException {
130         final ExifInterface exif = new ExifInterface(inputFilename);
131         final int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
132                 ExifInterface.ORIENTATION_UNDEFINED);
133 
134         if (Log.isLoggable(TAG, Log.DEBUG)) {
135             Log.d(TAG, "Exif orientation: " + orientation);
136         }
137 
138         // Degrees by which we rotate the image.
139         int degrees = 0;
140         switch (orientation) {
141             case ExifInterface.ORIENTATION_ROTATE_90: {
142                 degrees = 90;
143                 break;
144             }
145 
146             case ExifInterface.ORIENTATION_ROTATE_180: {
147                 degrees = 180;
148                 break;
149             }
150 
151             case ExifInterface.ORIENTATION_ROTATE_270: {
152                 degrees = 270;
153                 break;
154             }
155         }
156         rotateAndScaleImage(inputFilename, degrees, outputFile);
157         return degrees != 0;
158     }
159 
160     /**
161      * Rotates an image according to the specified {@code orientation}.
162      * We limit the number of pixels of the scaled image. Thus the image
163      * will typically be downsampled.
164      *
165      * @param inputFilename The input filename
166      * @param orientation The rotation angle
167      * @param outputFile The output file
168      */
rotateAndScaleImage(String inputFilename, int orientation, File outputFile)169     private static void rotateAndScaleImage(String inputFilename, int orientation, File outputFile)
170             throws FileNotFoundException, IOException {
171         // In order to avoid OutOfMemoryError when rotating the image, we scale down the size of the
172         // input image. We set the maxmimum number of allowed pixels to 2M and scale down the image
173         // accordingly.
174 
175         // Determine width and height of the original bitmap without allocating memory for it,
176         BitmapFactory.Options opt = new BitmapFactory.Options();
177         opt.inJustDecodeBounds = true;
178         BitmapFactory.decodeFile(inputFilename, opt);
179 
180         // Determine the scale factor based on the ratio of pixel count over max allowed pixels.
181         final int width = opt.outWidth;
182         final int height = opt.outHeight;
183         final int pixelCount = width * height;
184         final int MAX_PIXELS_FOR_SCALED_IMAGE = 2000000;
185         double scale = Math.sqrt( (double) pixelCount / MAX_PIXELS_FOR_SCALED_IMAGE);
186         if (scale <= 1) {
187           scale = 1;
188         } else {
189           // Make the scale factor a power of 2 for faster processing. Also the resulting bitmap may
190           // have different dimensions than what has been requested if the scale factor is not a
191           // power of 2.
192           scale = nextPowerOf2((int) Math.ceil(scale));
193         }
194 
195         // Load the scaled image.
196         BitmapFactory.Options opt2 = new BitmapFactory.Options();
197         opt2.inSampleSize = (int) scale;
198         final Bitmap scaledBmp = BitmapFactory.decodeFile(inputFilename, opt2);
199 
200         // Rotation matrix used to rotate the image.
201         final Matrix mtx = new Matrix();
202         mtx.postRotate(orientation);
203 
204         final Bitmap rotatedBmp = Bitmap.createBitmap(scaledBmp, 0, 0,
205                 scaledBmp.getWidth(), scaledBmp.getHeight(), mtx, true);
206         scaledBmp.recycle();
207 
208         // Save the rotated image to a file in the current project folder
209         final FileOutputStream fos = new FileOutputStream(outputFile);
210         rotatedBmp.compress(CompressFormat.JPEG, 100, fos);
211         fos.close();
212 
213         rotatedBmp.recycle();
214     }
215 
216     /**
217      * Returns the next power of two.
218      * Returns the input if it is already power of 2.
219      * Throws IllegalArgumentException if the input is <= 0 or the answer overflows.
220      */
nextPowerOf2(int n)221     private static int nextPowerOf2(int n) {
222         if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException();
223         n -= 1;
224         n |= n >> 16;
225         n |= n >> 8;
226         n |= n >> 4;
227         n |= n >> 2;
228         n |= n >> 1;
229         return n + 1;
230     }
231 
232     /**
233      * Build an overlay image
234      *
235      * @param context The context
236      * @param inputBitmap If the bitmap is provided no not create a new one
237      * @param overlayType The overlay type
238      * @param title The title
239      * @param subTitle The subtitle
240      * @param width The width
241      * @param height The height
242      *
243      * @return The bitmap
244      */
buildOverlayBitmap(Context context, Bitmap inputBitmap, int overlayType, String title, String subTitle, int width, int height)245     public static Bitmap buildOverlayBitmap(Context context, Bitmap inputBitmap, int overlayType,
246             String title, String subTitle, int width, int height) {
247         final Bitmap overlayBitmap;
248         if (inputBitmap == null) {
249             overlayBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
250         } else {
251             overlayBitmap = inputBitmap;
252         }
253 
254         overlayBitmap.eraseColor(Color.TRANSPARENT);
255         final Canvas canvas = new Canvas(overlayBitmap);
256 
257         switch (overlayType) {
258             case MovieOverlay.OVERLAY_TYPE_CENTER_1: {
259                 drawCenterOverlay(context, canvas, R.drawable.overlay_background_1,
260                         Color.WHITE, title, subTitle, width, height);
261                 break;
262             }
263 
264             case MovieOverlay.OVERLAY_TYPE_BOTTOM_1: {
265                 drawBottomOverlay(context, canvas, R.drawable.overlay_background_1,
266                         Color.WHITE, title, subTitle, width, height);
267                 break;
268             }
269 
270             case MovieOverlay.OVERLAY_TYPE_CENTER_2: {
271                 drawCenterOverlay(context, canvas, R.drawable.overlay_background_2,
272                         Color.BLACK, title, subTitle, width, height);
273                 break;
274             }
275 
276             case MovieOverlay.OVERLAY_TYPE_BOTTOM_2: {
277                 drawBottomOverlay(context, canvas, R.drawable.overlay_background_2,
278                         Color.BLACK, title, subTitle, width, height);
279                 break;
280             }
281 
282             default: {
283                 throw new IllegalArgumentException("Unsupported overlay type: " + overlayType);
284             }
285         }
286 
287         return overlayBitmap;
288     }
289 
290     /**
291      * Build an overlay image in the center third of the image
292      *
293      * @param context The context
294      * @param canvas The canvas
295      * @param drawableId The overlay background drawable if
296      * @param textColor The text color
297      * @param title The title
298      * @param subTitle The subtitle
299      * @param width The width
300      * @param height The height
301      */
drawCenterOverlay(Context context, Canvas canvas, int drawableId, int textColor, String title, String subTitle, int width, int height)302     private static void drawCenterOverlay(Context context, Canvas canvas, int drawableId,
303             int textColor, String title, String subTitle, int width, int height) {
304         final int INSET = width / 72;
305         final int startHeight = (height / 3) + INSET;
306         final Drawable background = context.getResources().getDrawable(drawableId);
307         background.setBounds(INSET, startHeight, width - INSET,
308                 ((2 * height) / 3) - INSET);
309         background.draw(canvas);
310 
311         final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
312         p.setTypeface(Typeface.DEFAULT_BOLD);
313         p.setColor(textColor);
314 
315         final int titleFontSize = height / 12;
316         final int maxWidth = width - (2 * INSET) - (2 * titleFontSize);
317         final int startYOffset = startHeight + (height / 6);
318         if (title != null) {
319             p.setTextSize(titleFontSize);
320             title = StringUtils.trimText(title, p, maxWidth);
321             canvas.drawText(title, (width - (2 * INSET) - p.measureText(title)) / 2,
322                     startYOffset - p.descent(), p);
323         }
324 
325         if (subTitle != null) {
326             p.setTextSize(titleFontSize - 6);
327             subTitle = StringUtils.trimText(subTitle, p, maxWidth);
328             canvas.drawText(subTitle, (width - (2 * INSET) - p.measureText(subTitle)) / 2,
329                     startYOffset - p.ascent(), p);
330         }
331     }
332 
333     /**
334      * Build an overlay image in the lower third of the image
335      *
336      * @param context The context
337      * @param canvas The canvas
338      * @param drawableId The overlay background drawable if
339      * @param textColor The text color
340      * @param title The title
341      * @param subTitle The subtitle
342      * @param width The width
343      * @param height The height
344      */
drawBottomOverlay(Context context, Canvas canvas, int drawableId, int textColor, String title, String subTitle, int width, int height)345     private static void drawBottomOverlay(Context context, Canvas canvas, int drawableId,
346             int textColor, String title, String subTitle, int width, int height) {
347         final int INSET = width / 72;
348         final int startHeight = ((2 * height) / 3) + INSET;
349         final Drawable background = context.getResources().getDrawable(drawableId);
350         background.setBounds(INSET, startHeight, width - INSET, height - INSET);
351         background.draw(canvas);
352 
353         final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
354         p.setTypeface(Typeface.DEFAULT_BOLD);
355         p.setColor(textColor);
356 
357         final int titleFontSize = height / 12;
358         final int maxWidth = width - (2 * INSET) - (2 * titleFontSize);
359         final int startYOffset = startHeight + (height / 6);
360         if (title != null) {
361             p.setTextSize(titleFontSize);
362             title = StringUtils.trimText(title, p, maxWidth);
363             canvas.drawText(title, (width - (2 * INSET) - p.measureText(title)) / 2,
364                     startYOffset - p.descent(), p);
365         }
366 
367         if (subTitle != null) {
368             p.setTextSize(titleFontSize - 6);
369             subTitle = StringUtils.trimText(subTitle, p, maxWidth);
370             canvas.drawText(subTitle, (width - (2 * INSET) - p.measureText(subTitle)) / 2,
371                     startYOffset - p.ascent(), p);
372         }
373     }
374 
375     /**
376      * Build an overlay preview image
377      *
378      * @param context The context
379      * @param canvas The canvas
380      * @param overlayType The overlay type
381      * @param title The title
382      * @param subTitle The subtitle
383      * @param startX The start horizontal position
384      * @param startY The start vertical position
385      * @param width The width
386      * @param height The height
387      */
buildOverlayPreview(Context context, Canvas canvas, int overlayType, String title, String subTitle, int startX, int startY, int width, int height)388     public static void buildOverlayPreview(Context context, Canvas canvas, int overlayType,
389             String title, String subTitle, int startX, int startY, int width, int height) {
390         switch (overlayType) {
391             case MovieOverlay.OVERLAY_TYPE_CENTER_1:
392             case MovieOverlay.OVERLAY_TYPE_BOTTOM_1: {
393                 drawOverlayPreview(context, canvas, R.drawable.overlay_background_1,
394                         Color.WHITE, title, subTitle, startX, startY, width, height);
395                 break;
396             }
397 
398             case MovieOverlay.OVERLAY_TYPE_CENTER_2:
399             case MovieOverlay.OVERLAY_TYPE_BOTTOM_2: {
400                 drawOverlayPreview(context, canvas, R.drawable.overlay_background_2,
401                         Color.BLACK, title, subTitle, startX, startY, width, height);
402                 break;
403             }
404 
405             default: {
406                 throw new IllegalArgumentException("Unsupported overlay type: " + overlayType);
407             }
408         }
409     }
410 
411     /**
412      * Build an overlay image in the lower third of the image
413      *
414      * @param context The context
415      * @param canvas The canvas
416      * @param drawableId The overlay background drawable if
417      * @param title The title
418      * @param subTitle The subtitle
419      * @param width The width
420      * @param height The height
421      */
drawOverlayPreview(Context context, Canvas canvas, int drawableId, int textColor, String title, String subTitle, int startX, int startY, int width, int height)422     private static void drawOverlayPreview(Context context, Canvas canvas, int drawableId,
423             int textColor, String title, String subTitle, int startX, int startY, int width,
424             int height) {
425         final int INSET = 0;
426         final int startHeight = startY + INSET;
427         final Drawable background = context.getResources().getDrawable(drawableId);
428         background.setBounds(startX + INSET, startHeight, startX + width - INSET,
429                 height - INSET + startY);
430         background.draw(canvas);
431 
432         final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
433         p.setTypeface(Typeface.DEFAULT_BOLD);
434         p.setColor(textColor);
435 
436         final int titleFontSize = height / 4;
437         final int maxWidth = width - (2 * INSET) - (2 * titleFontSize);
438         final int startYOffset = startHeight + (height / 2);
439         if (title != null) {
440             p.setTextSize(titleFontSize);
441             title = StringUtils.trimText(title, p, maxWidth);
442             canvas.drawText(title, (width - (2 * INSET) - p.measureText(title)) / 2,
443                     startYOffset - p.descent(), p);
444         }
445 
446         if (subTitle != null) {
447             p.setTextSize(titleFontSize - 6);
448             subTitle = StringUtils.trimText(subTitle, p, maxWidth);
449             canvas.drawText(subTitle, (width - (2 * INSET) - p.measureText(subTitle)) / 2,
450                     startYOffset - p.ascent(), p);
451         }
452     }
453 }
454