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