1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License
15 */
16package com.android.providers.contacts;
17
18import android.graphics.Bitmap;
19import android.graphics.BitmapFactory;
20import android.graphics.Matrix;
21
22import java.io.ByteArrayOutputStream;
23import java.io.IOException;
24
25/**
26 * Class that converts a bitmap (or byte array representing a bitmap) into a display
27 * photo and a thumbnail photo.
28 */
29/* package-protected */ final class PhotoProcessor {
30
31    private final int mMaxDisplayPhotoDim;
32    private final int mMaxThumbnailPhotoDim;
33    private final boolean mForceCropToSquare;
34    private final Bitmap mOriginal;
35    private Bitmap mDisplayPhoto;
36    private Bitmap mThumbnailPhoto;
37
38    /**
39     * Initializes a photo processor for the given bitmap.
40     * @param original The bitmap to process.
41     * @param maxDisplayPhotoDim The maximum height and width for the display photo.
42     * @param maxThumbnailPhotoDim The maximum height and width for the thumbnail photo.
43     * @throws IOException If bitmap decoding or scaling fails.
44     */
45    public PhotoProcessor(Bitmap original, int maxDisplayPhotoDim, int maxThumbnailPhotoDim)
46            throws IOException {
47        this(original, maxDisplayPhotoDim, maxThumbnailPhotoDim, false);
48    }
49
50    /**
51     * Initializes a photo processor for the given bitmap.
52     * @param originalBytes A byte array to decode into a bitmap to process.
53     * @param maxDisplayPhotoDim The maximum height and width for the display photo.
54     * @param maxThumbnailPhotoDim The maximum height and width for the thumbnail photo.
55     * @throws IOException If bitmap decoding or scaling fails.
56     */
57    public PhotoProcessor(byte[] originalBytes, int maxDisplayPhotoDim, int maxThumbnailPhotoDim)
58            throws IOException {
59        this(BitmapFactory.decodeByteArray(originalBytes, 0, originalBytes.length),
60                maxDisplayPhotoDim, maxThumbnailPhotoDim, false);
61    }
62
63    /**
64     * Initializes a photo processor for the given bitmap.
65     * @param original The bitmap to process.
66     * @param maxDisplayPhotoDim The maximum height and width for the display photo.
67     * @param maxThumbnailPhotoDim The maximum height and width for the thumbnail photo.
68     * @param forceCropToSquare Whether to force the processed images to be square.  If the source
69     *     photo is not square, this will crop to the square at the center of the image's rectangle.
70     *     If this is not set to true, the image will simply be downscaled to fit in the given
71     *     dimensions, retaining its original aspect ratio.
72     * @throws IOException If bitmap decoding or scaling fails.
73     */
74    public PhotoProcessor(Bitmap original, int maxDisplayPhotoDim, int maxThumbnailPhotoDim,
75            boolean forceCropToSquare) throws IOException {
76        mOriginal = original;
77        mMaxDisplayPhotoDim = maxDisplayPhotoDim;
78        mMaxThumbnailPhotoDim = maxThumbnailPhotoDim;
79        mForceCropToSquare = forceCropToSquare;
80        process();
81    }
82
83    /**
84     * Initializes a photo processor for the given bitmap.
85     * @param originalBytes A byte array to decode into a bitmap to process.
86     * @param maxDisplayPhotoDim The maximum height and width for the display photo.
87     * @param maxThumbnailPhotoDim The maximum height and width for the thumbnail photo.
88     * @param forceCropToSquare Whether to force the processed images to be square.  If the source
89     *     photo is not square, this will crop to the square at the center of the image's rectangle.
90     *     If this is not set to true, the image will simply be downscaled to fit in the given
91     *     dimensions, retaining its original aspect ratio.
92     * @throws IOException If bitmap decoding or scaling fails.
93     */
94    public PhotoProcessor(byte[] originalBytes, int maxDisplayPhotoDim, int maxThumbnailPhotoDim,
95            boolean forceCropToSquare) throws IOException {
96        this(BitmapFactory.decodeByteArray(originalBytes, 0, originalBytes.length),
97                maxDisplayPhotoDim, maxThumbnailPhotoDim, forceCropToSquare);
98    }
99
100    /**
101     * Processes the original image, producing a scaled-down display photo and thumbnail photo.
102     * @throws IOException If bitmap decoding or scaling fails.
103     */
104    private void process() throws IOException {
105        if (mOriginal == null) {
106            throw new IOException("Invalid image file");
107        }
108        mDisplayPhoto = getScaledBitmap(mMaxDisplayPhotoDim);
109        mThumbnailPhoto = getScaledBitmap(mMaxThumbnailPhotoDim);
110    }
111
112    /**
113     * Scales down the original bitmap to fit within the given maximum width and height.
114     * If the bitmap already fits in those dimensions, the original bitmap will be
115     * returned unmodified unless the photo processor is set up to crop it to a square.
116     * @param maxDim Maximum width and height (in pixels) for the image.
117     * @return A bitmap that fits the maximum dimensions.
118     */
119    @SuppressWarnings({"SuspiciousNameCombination"})
120    private Bitmap getScaledBitmap(int maxDim) {
121        Bitmap scaledBitmap = mOriginal;
122        int width = mOriginal.getWidth();
123        int height = mOriginal.getHeight();
124        int cropLeft = 0;
125        int cropTop = 0;
126        if (mForceCropToSquare && width != height) {
127            // Crop the image to the square at its center.
128            if (height > width) {
129                cropTop = (height - width) / 2;
130                height = width;
131            } else {
132                cropLeft = (width - height) / 2;
133                width = height;
134            }
135        }
136        float scaleFactor = ((float) maxDim) / Math.max(width, height);
137        if (scaleFactor < 1.0 || cropLeft != 0 || cropTop != 0) {
138            // Need to scale or crop the photo.
139            Matrix matrix = new Matrix();
140            matrix.setScale(scaleFactor, scaleFactor);
141            scaledBitmap = Bitmap.createBitmap(
142                    mOriginal, cropLeft, cropTop, width, height, matrix, false);
143        }
144        return scaledBitmap;
145    }
146
147    /**
148     * Helper method to compress the given bitmap as a JPEG and return the resulting byte array.
149     */
150    private byte[] getCompressedBytes(Bitmap b) throws IOException {
151        ByteArrayOutputStream baos = new ByteArrayOutputStream();
152        boolean compressed = b.compress(Bitmap.CompressFormat.JPEG, 95, baos);
153        if (!compressed) {
154            throw new IOException("Unable to compress image");
155        }
156        baos.flush();
157        baos.close();
158        return baos.toByteArray();
159    }
160
161    /**
162     * Retrieves the uncompressed display photo.
163     */
164    public Bitmap getDisplayPhoto() {
165        return mDisplayPhoto;
166    }
167
168    /**
169     * Retrieves the uncompressed thumbnail photo.
170     */
171    public Bitmap getThumbnailPhoto() {
172        return mThumbnailPhoto;
173    }
174
175    /**
176     * Retrieves the compressed display photo as a byte array.
177     */
178    public byte[] getDisplayPhotoBytes() throws IOException {
179        return getCompressedBytes(mDisplayPhoto);
180    }
181
182    /**
183     * Retrieves the compressed thumbnail photo as a byte array.
184     */
185    public byte[] getThumbnailPhotoBytes() throws IOException {
186        return getCompressedBytes(mThumbnailPhoto);
187    }
188
189    /**
190     * Retrieves the maximum width or height (in pixels) of the display photo.
191     */
192    public int getMaxDisplayPhotoDim() {
193        return mMaxDisplayPhotoDim;
194    }
195
196    /**
197     * Retrieves the maximum width or height (in pixels) of the thumbnail.
198     */
199    public int getMaxThumbnailPhotoDim() {
200        return mMaxThumbnailPhotoDim;
201    }
202}
203