1 /*
2  * Copyright (C) 2015 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.messaging.util;
18 
19 import android.content.Context;
20 import android.text.format.Formatter;
21 
22 import com.google.common.base.Stopwatch;
23 
24 import java.io.File;
25 import java.util.concurrent.TimeUnit;
26 
27 /**
28  * Compresses a GIF so it can be sent via MMS.
29  * <p>
30  * The entry point lives in its own class, we can defer loading the native GIF transcoding library
31  * into memory until we actually need it.
32  */
33 public class GifTranscoder {
34     private static final String TAG = LogUtil.BUGLE_TAG;
35 
36     private static int MIN_HEIGHT = 100;
37     private static int MIN_WIDTH = 100;
38 
39     static {
40         System.loadLibrary("giftranscode");
41     }
42 
transcode(Context context, String filePath, String outFilePath)43     public static boolean transcode(Context context, String filePath, String outFilePath) {
44         if (!isEnabled()) {
45             return false;
46         }
47         final long inputSize = new File(filePath).length();
48         Stopwatch stopwatch = Stopwatch.createStarted();
49         final boolean success = transcodeInternal(filePath, outFilePath);
50         stopwatch.stop();
51         final long elapsedMs = stopwatch.elapsed(TimeUnit.MILLISECONDS);
52         final long outputSize = new File(outFilePath).length();
53         final float compression = (inputSize > 0) ? ((float) outputSize / inputSize) : 0;
54 
55         if (success) {
56             LogUtil.i(TAG, String.format("Resized GIF (%s) in %d ms, %s => %s (%.0f%%)",
57                     LogUtil.sanitizePII(filePath),
58                     elapsedMs,
59                     Formatter.formatShortFileSize(context, inputSize),
60                     Formatter.formatShortFileSize(context, outputSize),
61                     compression * 100.0f));
62         }
63         return success;
64     }
65 
transcodeInternal(String filePath, String outFilePath)66     private static native boolean transcodeInternal(String filePath, String outFilePath);
67 
68     /**
69      * Estimates the size of a GIF transcoded from a GIF with the specified size.
70      */
estimateFileSizeAfterTranscode(long fileSize)71     public static long estimateFileSizeAfterTranscode(long fileSize) {
72         // I tested transcoding on ~70 GIFs and found that the transcoded files are in general
73         // about 25-35% the size of the original. This compression ratio is very consistent for the
74         // class of GIFs we care about most: those converted from video clips and 1-3 MB in size.
75         return (long) (fileSize * 0.35f);
76     }
77 
canBeTranscoded(int width, int height)78     public static boolean canBeTranscoded(int width, int height) {
79         if (!isEnabled()) {
80             return false;
81         }
82         return width >= MIN_WIDTH && height >= MIN_HEIGHT;
83     }
84 
isEnabled()85     private static boolean isEnabled() {
86         final boolean enabled = BugleGservices.get().getBoolean(
87                 BugleGservicesKeys.ENABLE_GIF_TRANSCODING,
88                 BugleGservicesKeys.ENABLE_GIF_TRANSCODING_DEFAULT);
89         if (!enabled) {
90             LogUtil.w(TAG, "GIF transcoding is disabled");
91         }
92         return enabled;
93     }
94 }
95