com.lightbox.android.bitmap.BitmapUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.lightbox.android.bitmap.BitmapUtils.java

Source

/**
 * Copyright (c) 2012 Lightbox
 *
 * 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 com.lightbox.android.bitmap;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.FloatMath;
import android.util.Log;

import com.lightbox.android.io.RandomAccessFileOutputStream;
import com.lightbox.android.utils.debug.DebugLog;

/** 
 * BitmapUtils 
 * @author Fabien Devos & Nilesh Patel
 */
public final class BitmapUtils {
    /** Used to tag logs */
    //@SuppressWarnings("unused")
    private static final String TAG = "BitmapUtils";

    private static final int FULL_QUALITY = 100;
    public static final int HIGH_QUALITY = 90;

    public static final int MAX_PIXELS_LRG = 1024 * 1024 * 2; // 2 MegaPixels
    public static final int MAX_UPLOAD_SIZE = 720; // max width or height of 720 pixels

    //------------------------------------------------------
    // Private constructor for utility class
    private BitmapUtils() {
        throw new UnsupportedOperationException("Sorry, you cannot instantiate an utility class!");
    }
    //------------------------------------------------------

    /**
     * Read a bitmap from disk
     * @param absoluteFileName
     * @param config optional Bitmap.Config. If null, the default ARGB_8888 will be used.
     * @return
     */
    public static Bitmap readBitmapFromFile(String absoluteFileName, Bitmap.Config config) {
        return readBitmapFromFile(absoluteFileName, config, 1);
    }

    /**
     * Read a bitmap from disk
     * @param absoluteFileName
     * @param config optional Bitmap.Config. If null, the default ARGB_8888 will be used.
     * @return
     */
    public static Bitmap readBitmapFromFile(String absoluteFileName, Bitmap.Config config, int sampleSize) {
        Options opts = new Options();
        opts.inPreferredConfig = config == null ? Config.ARGB_8888 : config;
        opts.inDither = true;
        opts.inSampleSize = sampleSize;
        try {
            return BitmapFactory.decodeFile(absoluteFileName, opts);
        } catch (OutOfMemoryError e1) {
            try {
                opts.inPreferredConfig = Config.RGB_565;
                return BitmapFactory.decodeFile(absoluteFileName, opts);
            } catch (OutOfMemoryError e2) {
                throw e2;
            }
        }
    }

    /**
     * Get the size of a bitmap from disk
     * @param absoluteFileName
     */
    public static BitmapSize getBitmapSizeFromFile(String absoluteFileName) {
        Options opts = new Options();
        opts.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(absoluteFileName, opts);
        return new BitmapSize(opts.outWidth, opts.outHeight);
    }

    public static void writeBitmapInFile(String absoluteFileName, Bitmap bitmap) throws IOException {
        writeBitmapInFile(new File(absoluteFileName), bitmap);
    }

    public static void writeBitmapInFile(String absoluteFileName, Bitmap bitmap, CompressFormat compressFormat)
            throws IOException {
        writeBitmapInFile(new File(absoluteFileName), bitmap, compressFormat, null);
    }

    public static void writeBitmapInFile(File file, Bitmap bitmap) throws IOException {
        writeBitmapInFile(file, bitmap, CompressFormat.JPEG, null);
    }

    public static void writeBitmapInFile(String absoluteFileName, Bitmap bitmap, CompressFormat compressFormat,
            StringBuilder outMD5) throws IOException {
        writeBitmapInFile(new File(absoluteFileName), bitmap, compressFormat, outMD5);
    }

    public static void writeBitmapInFile(File file, Bitmap bitmap, CompressFormat compressFormat,
            StringBuilder outMD5) throws IOException {
        // Ensure that the directory exist
        file.getParentFile().mkdirs();

        OutputStream outputStream = null;
        try {
            if (outMD5 != null) {
                // We want a MD5: writing JPEG into a byte array
                outputStream = new ByteArrayOutputStream();
            } else {
                // Directly write to file
                try {
                    outputStream = new BufferedOutputStream(new RandomAccessFileOutputStream(file), 65536);
                } catch (OutOfMemoryError e) {
                    outputStream = new BufferedOutputStream(new RandomAccessFileOutputStream(file));
                }
            }
            boolean success = bitmap.compress(compressFormat, FULL_QUALITY, outputStream);
            if (!success) {
                throw new IOException(String.format("Unable to save bitmap as a %s file: %s",
                        (compressFormat == CompressFormat.JPEG) ? "jpeg" : "png",
                        file.getAbsoluteFile().toString()));
            } else {
                if (outMD5 != null) {
                    // Calculate MD5 and write the file to disk
                    long time = System.currentTimeMillis();
                    byte[] jpeg = ((ByteArrayOutputStream) outputStream).toByteArray();
                    if (outMD5 != null) {
                        outMD5.append(getMD5String(jpeg));
                    }
                    DebugLog.d(TAG, "Time to calculate MD5: " + (System.currentTimeMillis() - time));

                    time = System.currentTimeMillis();
                    RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rwd");
                    randomAccessFile.write(jpeg);
                    randomAccessFile.close();
                    DebugLog.d(TAG, "Time to write to file: " + (System.currentTimeMillis() - time));
                }
            }
        } finally {
            IOUtils.closeQuietly(outputStream);
        }
    }

    public static boolean writeBitmapInFileWithMD5Check(String filePath, Bitmap bitmap,
            CompressFormat compressFormat) throws IOException {
        return writeBitmapInFileWithMD5Check(new File(filePath), bitmap, compressFormat);
    }

    public static boolean writeBitmapInFileWithMD5Check(String filePath, Bitmap bitmap,
            CompressFormat compressFormat, StringBuffer md5Out) throws IOException {
        return writeBitmapInFileWithMD5Check(new File(filePath), bitmap, compressFormat, md5Out);
    }

    public static boolean writeBitmapInFileWithMD5Check(File file, Bitmap bitmap, CompressFormat compressFormat)
            throws IOException {
        return writeBitmapInFileWithMD5Check(file, bitmap, compressFormat, null);
    }

    public static boolean writeBitmapInFileWithMD5Check(File file, Bitmap bitmap, CompressFormat compressFormat,
            StringBuffer md5Out) throws IOException {
        // Ensure that the directory exist
        file.getParentFile().mkdirs();

        RandomAccessFileMD5OutputStream randomAccessFileOutputStream = new RandomAccessFileMD5OutputStream(file);
        BufferedOutputStream outputStream = null;
        try {
            outputStream = new BufferedOutputStream(randomAccessFileOutputStream, 65536);
            boolean compressSuccess = bitmap.compress(compressFormat, FULL_QUALITY, outputStream);

            if (!compressSuccess) {
                return false;
            }
        } finally {
            IOUtils.closeQuietly(outputStream);
        }

        String md5Data = randomAccessFileOutputStream.getMD5();
        DebugLog.d(TAG, "MD5 of data:" + md5Data);

        String md5File = getFileMD5(file);
        DebugLog.d(TAG, "MD5 of file:" + md5File);

        if (md5Data.equals(md5File)) {
            if (md5Out != null) {
                md5Out.append(md5Data);
            }
            return true;
        } else {
            //TrackHelper.trackException(TAG, new Exception("MD5 comparison failed."));
            FileUtils.deleteQuietly(file);
            return false;
        }
    }

    private static String getFileMD5(File file) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.reset();

            FileInputStream fileInputStream = null;
            try {
                fileInputStream = new FileInputStream(file);
                byte[] buffer = new byte[8192];
                int count;
                while ((count = fileInputStream.read(buffer)) != -1) {
                    md5.update(buffer, 0, count);
                }

                return md5ToString(md5.digest()).toString();
            } finally {
                IOUtils.closeQuietly(fileInputStream);
            }
        } catch (NoSuchAlgorithmException e) {
            Log.w(TAG, e);
        } catch (FileNotFoundException e) {
            Log.w(TAG, e);
        } catch (IOException e) {
            Log.w(TAG, e);
        }

        return "";
    }

    private static String md5ToString(byte[] md5Hash) {
        Formatter formatter = new Formatter();
        for (byte b : md5Hash) {
            formatter.format("%02x", b);
        }
        return formatter.toString();
    }

    private static String getMD5String(byte[] data) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            md5.reset();
            md5.update(data);
            byte[] md5Hash = md5.digest();
            Formatter formatter = new Formatter();
            for (byte b : md5Hash) {
                formatter.format("%02x", b);
            }
            return formatter.toString();
        } catch (NoSuchAlgorithmException e1) {
            Log.w(TAG, e1);
        }

        return "";
    }

    public static Bitmap createUserPhotoThumbnail(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();

        Bitmap scaledBitmap;
        //TODO get thumb size constant from somewhere, but where?
        if (width > height) {
            scaledBitmap = Bitmap.createScaledBitmap(bitmap,
                    (int) (BitmapSource.THM_SIZE_PX * ((float) width / height)), BitmapSource.THM_SIZE_PX, true);
        } else {
            scaledBitmap = Bitmap.createScaledBitmap(bitmap, BitmapSource.THM_SIZE_PX,
                    (int) (BitmapSource.THM_SIZE_PX * ((float) height / width)), true);
        }

        Bitmap thumb = Bitmap.createBitmap(scaledBitmap, (scaledBitmap.getWidth() - BitmapSource.THM_SIZE_PX) / 2,
                (scaledBitmap.getHeight() - BitmapSource.THM_SIZE_PX) / 2, BitmapSource.THM_SIZE_PX,
                BitmapSource.THM_SIZE_PX);

        scaledBitmap.recycle();

        return thumb;
    }

    public static Bitmap decodeFile(String pathName, int maxWidth, int maxHeight) {
        Options options = new Options();
        options.inJustDecodeBounds = true;
        int sampleSize = 1;
        options.inSampleSize = sampleSize;
        BitmapFactory.decodeFile(pathName, options);

        if (maxWidth < options.outWidth || maxHeight < options.outHeight) {
            int sampleSizeW = (int) FloatMath.ceil((float) options.outWidth / maxWidth);
            int sampleSizeH = (int) FloatMath.ceil((float) options.outHeight / maxHeight);
            sampleSize = Math.max(sampleSizeW, sampleSizeH);
            options.inSampleSize = sampleSize;
        }

        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeFile(pathName, options);
    }

    public static BitmapSize getScaledSize(int originalWidth, int originalHeight, int numPixels) {
        float ratio = (float) originalWidth / originalHeight;

        int scaledHeight = (int) FloatMath.sqrt((float) numPixels / ratio);
        int scaledWidth = (int) (ratio * FloatMath.sqrt((float) numPixels / ratio));

        return new BitmapSize(scaledWidth, scaledHeight);
    }

    private static Paint sScalePaint = new Paint(
            Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);

    /*
     * This method scales with less artifacts than the default Bitmap.createScaledBitmap() method
     */
    public static Bitmap createScaledBitmap(Bitmap src, int scaledWidth, int scaledHeight) {
        Bitmap scaledBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, src.getConfig());
        Canvas canvas = new Canvas(scaledBitmap);
        canvas.drawBitmap(src, new Rect(0, 0, src.getWidth(), src.getHeight()),
                new Rect(0, 0, scaledWidth, scaledHeight), sScalePaint);
        return scaledBitmap;
    }

    /******************************************************************************************
     * RandomAccessFileMD5OutputStream 
     */
    private static class RandomAccessFileMD5OutputStream extends RandomAccessFileOutputStream {
        MessageDigest mMD5 = null;
        byte[] digest = null;

        public RandomAccessFileMD5OutputStream(File file) throws FileNotFoundException {
            super(file);
            try {
                mMD5 = MessageDigest.getInstance("MD5");
                mMD5.reset();
            } catch (NoSuchAlgorithmException e) {
                Log.w(TAG, e);
            }
        }

        @Override
        public void write(int oneByte) throws IOException {
            super.write(oneByte);
            mMD5.update(new Integer(oneByte).byteValue());
        }

        @Override
        public void close() throws IOException {
            super.close();
            if (digest == null) {
                digest = mMD5.digest();
            }
        }

        @Override
        public void write(byte[] buffer, int offset, int count) throws IOException {
            super.write(buffer, offset, count);
            mMD5.update(buffer, offset, count);
        }

        @Override
        public void write(byte[] buffer) throws IOException {
            super.write(buffer);
            mMD5.update(buffer);
        }

        public String getMD5() {
            return md5ToString(digest).toString();
        }
    }
}