Home | History | Annotate | Download | only in util
      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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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