mobisocial.musubi.util.UriImage.java Source code

Java tutorial

Introduction

Here is the source code for mobisocial.musubi.util.UriImage.java

Source

/*
 * Copyright (C) 2008 Esmertec AG.
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package mobisocial.musubi.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.provider.MediaStore.Images;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
import android.webkit.MimeTypeMap;

public class UriImage {
    /**
     * The quality parameter which is used to compress JPEG images.
     */
    public static final int IMAGE_COMPRESSION_QUALITY = 80;
    /**
     * The minimum quality parameter which is used to compress JPEG images.
     */
    public static final int MINIMUM_IMAGE_COMPRESSION_QUALITY = 50;

    private static final String TAG = "Mms/image";
    private static final boolean DEBUG = true;
    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;

    private final Context mContext;
    private final Uri mUri;
    private String mContentType;
    private String mPath;
    private String mSrc;
    private int mWidth;
    private int mHeight;
    private float mRotation;
    private byte[] mByteCache;
    private boolean mDecodedBounds = false;

    public UriImage(Context context, Uri uri) {
        if ((null == context) || (null == uri)) {
            throw new IllegalArgumentException();
        }

        mRotation = PhotoTaker.rotationForImage(context, uri);
        String scheme = uri.getScheme();
        if (scheme.equals("content")) {
            try {
                initFromContentUri(context, uri);
            } catch (Exception e) {
                Log.w(TAG, "last-ditch image params");
                mPath = uri.getPath();
                mContentType = context.getContentResolver().getType(uri);
            }
        } else if (uri.getScheme().equals("file")) {
            initFromFile(context, uri);
        } else {
            mPath = uri.getPath();
        }

        mSrc = mPath.substring(mPath.lastIndexOf('/') + 1);

        if (mSrc.startsWith(".") && mSrc.length() > 1) {
            mSrc = mSrc.substring(1);
        }

        // Some MMSCs appear to have problems with filenames
        // containing a space.  So just replace them with
        // underscores in the name, which is typically not
        // visible to the user anyway.
        mSrc = mSrc.replace(' ', '_');

        mContext = context;
        mUri = uri;
    }

    private void initFromFile(Context context, Uri uri) {
        mPath = uri.getPath();
        MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
        String extension = MimeTypeMap.getFileExtensionFromUrl(mPath);
        if (TextUtils.isEmpty(extension)) {
            // getMimeTypeFromExtension() doesn't handle spaces in filenames nor can it handle
            // urlEncoded strings. Let's try one last time at finding the extension.
            int dotPos = mPath.lastIndexOf('.');
            if (0 <= dotPos) {
                extension = mPath.substring(dotPos + 1);
            }
        }
        mContentType = mimeTypeMap.getMimeTypeFromExtension(extension);
        // It's ok if mContentType is null. Eventually we'll show a toast telling the
        // user the picture couldn't be attached.
    }

    private void initFromContentUri(Context context, Uri uri) {
        Cursor c = context.getContentResolver().query(uri, null, null, null, null);

        if (c == null) {
            throw new IllegalArgumentException("Query on " + uri + " returns null result.");
        }

        try {
            if ((c.getCount() != 1) || !c.moveToFirst()) {
                throw new IllegalArgumentException("Query on " + uri + " returns 0 or multiple rows.");
            }

            String filePath = c.getString(c.getColumnIndexOrThrow(Images.Media.DATA));
            mContentType = c.getString(c.getColumnIndexOrThrow(Images.Media.MIME_TYPE));
            mPath = filePath;
        } finally {
            c.close();
        }
    }

    private void decodeBoundsInfo() throws IOException {
        InputStream input = null;
        try {
            input = openInputStream(mUri);
            BitmapFactory.Options opt = new BitmapFactory.Options();
            opt.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(input, null, opt);
            mWidth = opt.outWidth;
            mHeight = opt.outHeight;
        } catch (FileNotFoundException e) {
            // Ignore
            Log.e(TAG, "IOException caught while opening stream", e);
        } finally {
            if (null != input) {
                try {
                    input.close();
                } catch (IOException e) {
                    // Ignore
                    Log.e(TAG, "IOException caught while closing stream", e);
                }
            }
        }
    }

    public String getContentType() {
        return mContentType;
    }

    public String getSrc() {
        return mSrc;
    }

    public int getWidth() {
        return mWidth;
    }

    public int getHeight() {
        return mHeight;
    }

    private static final int NUMBER_OF_RESIZE_ATTEMPTS = 4;

    public byte[] getResizedImageData(int widthLimit, int heightLimit, int byteLimit) throws IOException {
        return getResizedImageData(widthLimit, heightLimit, byteLimit, false);
    }

    /**
     * Returns the bytes for this UriImage. If the uri for the image is remote,
     * then this code must not be run on the main thread.
     */
    public byte[] getResizedImageData(int widthLimit, int heightLimit, int byteLimit, boolean square)
            throws IOException {
        if (!mDecodedBounds) {
            decodeBoundsInfo();
            mDecodedBounds = true;
        }
        InputStream input = null;
        try {
            int inDensity = 0;
            int targetDensity = 0;
            BitmapFactory.Options read_options = new BitmapFactory.Options();
            read_options.inJustDecodeBounds = true;
            input = openInputStream(mUri);
            BitmapFactory.decodeStream(input, null, read_options);
            if (read_options.outWidth > widthLimit || read_options.outHeight > heightLimit) {
                //we need to scale
                if (read_options.outWidth / widthLimit > read_options.outHeight / heightLimit) {
                    //width is the large edge
                    if (read_options.outWidth * heightLimit > widthLimit * read_options.outHeight) {
                        //incoming image is wider than target
                        inDensity = read_options.outWidth;
                        targetDensity = widthLimit;
                    } else {
                        //incoming image is taller than target
                        inDensity = read_options.outHeight;
                        targetDensity = heightLimit;

                    }
                } else {
                    //height is the long edge, swap the limits
                    if (read_options.outWidth * widthLimit > heightLimit * read_options.outHeight) {
                        //incoming image is wider than target
                        inDensity = read_options.outWidth;
                        targetDensity = heightLimit;
                    } else {
                        //incoming image is taller than target
                        inDensity = read_options.outHeight;
                        targetDensity = widthLimit;

                    }
                }
            } else {
                //no scale
                if (read_options.outWidth > read_options.outHeight) {
                    inDensity = targetDensity = read_options.outWidth;
                } else {
                    inDensity = targetDensity = read_options.outHeight;
                }
            }

            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG,
                        "getResizedImageData: wlimit=" + widthLimit + ", hlimit=" + heightLimit + ", sizeLimit="
                                + byteLimit + ", mWidth=" + mWidth + ", mHeight=" + mHeight + ", initialRatio="
                                + targetDensity + "/" + inDensity);
            }

            ByteArrayOutputStream os = null;
            int attempts = 1;

            int lowMemoryReduce = 1;
            do {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inDensity = inDensity;
                options.inSampleSize = lowMemoryReduce;
                options.inScaled = lowMemoryReduce == 1;
                options.inTargetDensity = targetDensity;
                //no purgeable because we are only trying to resave this
                if (input != null)
                    input.close();
                input = openInputStream(mUri);
                int quality = IMAGE_COMPRESSION_QUALITY;
                try {
                    Bitmap b = BitmapFactory.decodeStream(input, null, options);
                    if (b == null) {
                        return null;
                    }
                    if (options.outWidth > widthLimit + 1 || options.outHeight > heightLimit + 1) {
                        // The decoder does not support the inSampleSize option.
                        // Scale the bitmap using Bitmap library.
                        int scaledWidth;
                        int scaledHeight;
                        scaledWidth = options.outWidth * targetDensity / inDensity;
                        scaledHeight = options.outHeight * targetDensity / inDensity;

                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
                            Log.v(TAG, "getResizedImageData: retry scaling using " + "Bitmap.createScaledBitmap: w="
                                    + scaledWidth + ", h=" + scaledHeight);
                        }

                        if (square) {
                            int w = b.getWidth();
                            int h = b.getHeight();
                            int dim = Math.min(w, h);
                            b = Bitmap.createBitmap(b, (w - dim) / 2, (h - dim) / 2, dim, dim);
                            scaledWidth = dim;
                            scaledHeight = dim;
                        }
                        Bitmap b2 = Bitmap.createScaledBitmap(b, scaledWidth, scaledHeight, false);
                        b.recycle();
                        b = b2;
                        if (b == null) {
                            return null;
                        }
                    }

                    Matrix matrix = new Matrix();
                    if (mRotation != 0f) {
                        matrix.preRotate(mRotation);
                    }

                    Bitmap old = b;
                    b = Bitmap.createBitmap(old, 0, 0, old.getWidth(), old.getHeight(), matrix, true);

                    // Compress the image into a JPG. Start with MessageUtils.IMAGE_COMPRESSION_QUALITY.
                    // In case that the image byte size is still too large reduce the quality in
                    // proportion to the desired byte size. Should the quality fall below
                    // MINIMUM_IMAGE_COMPRESSION_QUALITY skip a compression attempt and we will enter
                    // the next round with a smaller image to start with.
                    os = new ByteArrayOutputStream();
                    b.compress(CompressFormat.JPEG, quality, os);
                    int jpgFileSize = os.size();
                    if (jpgFileSize > byteLimit) {
                        int reducedQuality = quality * byteLimit / jpgFileSize;
                        //always try to squish it before computing the new size
                        if (reducedQuality < MINIMUM_IMAGE_COMPRESSION_QUALITY) {
                            reducedQuality = MINIMUM_IMAGE_COMPRESSION_QUALITY;
                        }
                        quality = reducedQuality;

                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
                            Log.v(TAG, "getResizedImageData: compress(2) w/ quality=" + quality);
                        }

                        os = new ByteArrayOutputStream();
                        b.compress(CompressFormat.JPEG, quality, os);
                    }
                    b.recycle(); // done with the bitmap, release the memory
                } catch (java.lang.OutOfMemoryError e) {
                    Log.w(TAG, "getResizedImageData - image too big (OutOfMemoryError), will try "
                            + " with smaller scale factor, cur scale factor", e);
                    lowMemoryReduce *= 2;
                    // fall through and keep trying with a smaller scale factor.
                }
                if (true || Log.isLoggable(TAG, Log.VERBOSE)) {
                    Log.v(TAG,
                            "attempt=" + attempts + " size=" + (os == null ? 0 : os.size()) + " width="
                                    + options.outWidth + " height=" + options.outHeight + " Ratio=" + targetDensity
                                    + "/" + inDensity + " quality=" + quality);
                }
                //move halfway to the target
                targetDensity = (os == null) ? (int) (targetDensity * .8)
                        : (targetDensity * byteLimit / os.size() + targetDensity) / 2;
                attempts++;
            } while ((os == null || os.size() > byteLimit) && attempts < NUMBER_OF_RESIZE_ATTEMPTS);

            return os == null ? null : os.toByteArray();
        } catch (Throwable t) {
            Log.e(TAG, t.getMessage(), t);
            return null;
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    Log.e(TAG, e.getMessage(), e);
                }
            }
        }
    }

    private InputStream openInputStream(Uri uri) throws IOException {
        String scheme = uri.getScheme();
        if ("content".equals(scheme)) {
            return mContext.getContentResolver().openInputStream(mUri);
        } else if (scheme.startsWith("http")) {
            if (mByteCache == null) {
                DefaultHttpClient c = new DefaultHttpClient();
                HttpGet get = new HttpGet(uri.toString());
                HttpResponse response = c.execute(get);
                mByteCache = IOUtils.toByteArray(response.getEntity().getContent());
            }
            return new ByteArrayInputStream(mByteCache);
        } else if (scheme.equals("file")) {
            return new FileInputStream(uri.getPath());
        } else {
            throw new IOException("Unmatched uri scheme " + scheme);
        }
    }
}