1/*
2 * Copyright (C) 2012 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
17package com.android.mail.utils;
18import android.graphics.Bitmap;
19import android.graphics.BitmapFactory;
20import android.graphics.Matrix;
21
22/**
23 * Provides static functions to decode bitmaps at the optimal size
24 */
25public class BitmapUtil {
26
27    private static final String TAG = LogTag.getLogTag();
28    private static final boolean DEBUG = false;
29
30    private BitmapUtil() {
31    }
32
33    /**
34     * Decode an image into a Bitmap, using sub-sampling if the hinted dimensions call for it.
35     * Does not crop to fit the hinted dimensions.
36     *
37     * @param src an encoded image
38     * @param w hint width in px
39     * @param h hint height in px
40     * @return a decoded Bitmap that is not exactly sized to the hinted dimensions.
41     */
42    public static Bitmap decodeByteArray(byte[] src, int w, int h) {
43        try {
44            // calculate sample size based on w/h
45            final BitmapFactory.Options opts = new BitmapFactory.Options();
46            opts.inJustDecodeBounds = true;
47            BitmapFactory.decodeByteArray(src, 0, src.length, opts);
48            if (opts.mCancel || opts.outWidth == -1 || opts.outHeight == -1) {
49                return null;
50            }
51            opts.inSampleSize = Math.min(opts.outWidth / w, opts.outHeight / h);
52            opts.inJustDecodeBounds = false;
53            return BitmapFactory.decodeByteArray(src, 0, src.length, opts);
54        } catch (Throwable t) {
55            LogUtils.w(TAG, t, "BitmapUtils unable to decode image");
56            return null;
57        }
58    }
59
60    /**
61     * Decode an image into a Bitmap, using sub-sampling if the desired dimensions call for it.
62     * Also applies a center-crop a la {@link android.widget.ImageView.ScaleType#CENTER_CROP}.
63     *
64     * @param src an encoded image
65     * @param w desired width in px
66     * @param h desired height in px
67     * @return an exactly-sized decoded Bitmap that is center-cropped.
68     */
69    public static Bitmap decodeByteArrayWithCenterCrop(byte[] src, int w, int h) {
70        try {
71            final Bitmap decoded = decodeByteArray(src, w, h);
72            return centerCrop(decoded, w, h);
73
74        } catch (Throwable t) {
75            LogUtils.w(TAG, t, "BitmapUtils unable to crop image");
76            return null;
77        }
78    }
79
80    /**
81     * Returns a new Bitmap copy with a center-crop effect a la
82     * {@link android.widget.ImageView.ScaleType#CENTER_CROP}. May return the input bitmap if no
83     * scaling is necessary.
84     *
85     * @param src original bitmap of any size
86     * @param w desired width in px
87     * @param h desired height in px
88     * @return a copy of src conforming to the given width and height, or src itself if it already
89     *         matches the given width and height
90     */
91    public static Bitmap centerCrop(final Bitmap src, final int w, final int h) {
92        return crop(src, w, h, 0.5f, 0.5f);
93    }
94
95    /**
96     * Returns a new Bitmap copy with a crop effect depending on the crop anchor given. 0.5f is like
97     * {@link android.widget.ImageView.ScaleType#CENTER_CROP}. The crop anchor will be be nudged
98     * so the entire cropped bitmap will fit inside the src. May return the input bitmap if no
99     * scaling is necessary.
100     *
101     *
102     * Example of changing verticalCenterPercent:
103     *   _________            _________
104     *  |         |          |         |
105     *  |         |          |_________|
106     *  |         |          |         |/___0.3f
107     *  |---------|          |_________|\
108     *  |         |<---0.5f  |         |
109     *  |---------|          |         |
110     *  |         |          |         |
111     *  |         |          |         |
112     *  |_________|          |_________|
113     *
114     * @param src original bitmap of any size
115     * @param w desired width in px
116     * @param h desired height in px
117     * @param horizontalCenterPercent determines which part of the src to crop from. Range from 0
118     *                                .0f to 1.0f. The value determines which part of the src
119     *                                maps to the horizontal center of the resulting bitmap.
120     * @param verticalCenterPercent determines which part of the src to crop from. Range from 0
121     *                              .0f to 1.0f. The value determines which part of the src maps
122     *                              to the vertical center of the resulting bitmap.
123     * @return a copy of src conforming to the given width and height, or src itself if it already
124     *         matches the given width and height
125     */
126    public static Bitmap crop(final Bitmap src, final int w, final int h,
127            final float horizontalCenterPercent, final float verticalCenterPercent) {
128        if (horizontalCenterPercent < 0 || horizontalCenterPercent > 1 || verticalCenterPercent < 0
129                || verticalCenterPercent > 1) {
130            throw new IllegalArgumentException(
131                    "horizontalCenterPercent and verticalCenterPercent must be between 0.0f and "
132                            + "1.0f, inclusive.");
133        }
134        final int srcWidth = src.getWidth();
135        final int srcHeight = src.getHeight();
136
137        // exit early if no resize/crop needed
138        if (w == srcWidth && h == srcHeight) {
139            return src;
140        }
141
142        final Matrix m = new Matrix();
143        final float scale = Math.max(
144                (float) w / srcWidth,
145                (float) h / srcHeight);
146        m.setScale(scale, scale);
147
148        final int srcCroppedW, srcCroppedH;
149        int srcX, srcY;
150
151        srcCroppedW = Math.round(w / scale);
152        srcCroppedH = Math.round(h / scale);
153        srcX = (int) (srcWidth * horizontalCenterPercent - srcCroppedW / 2);
154        srcY = (int) (srcHeight * verticalCenterPercent - srcCroppedH / 2);
155
156        // Nudge srcX and srcY to be within the bounds of src
157        srcX = Math.max(Math.min(srcX, srcWidth - srcCroppedW), 0);
158        srcY = Math.max(Math.min(srcY, srcHeight - srcCroppedH), 0);
159
160        final Bitmap cropped = Bitmap.createBitmap(src, srcX, srcY, srcCroppedW, srcCroppedH, m,
161                true /* filter */);
162
163        if (DEBUG) LogUtils.i(TAG,
164                "BitmapUtils IN centerCrop, srcW/H=%s/%s desiredW/H=%s/%s srcX/Y=%s/%s" +
165                " innerW/H=%s/%s scale=%s resultW/H=%s/%s",
166                srcWidth, srcHeight, w, h, srcX, srcY, srcCroppedW, srcCroppedH, scale,
167                cropped.getWidth(), cropped.getHeight());
168        if (DEBUG && (w != cropped.getWidth() || h != cropped.getHeight())) {
169            LogUtils.e(TAG, new Error(), "BitmapUtils last center crop violated assumptions.");
170        }
171
172        return cropped;
173    }
174
175}
176