/*
* Copyright (C) 2009 Muthu Ramadoss. All rights reserved.
*
* Modified from Romain Guy Shelves project to suit Books-Exchange requirements.
* Original source from Shelves - http://code.google.com/p/shelves/
*/
/*
* Copyright (C) 2008 Romain Guy
*
* 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.androidrocks.bex.util;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.LinearGradient;
import android.graphics.Shader;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.lang.ref.SoftReference;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.Header;
import org.apache.http.client.methods.HttpGet;
import com.androidrocks.bex.drawable.FastBitmapDrawable;
public class ImageUtilities {
private static final String LOG_TAG = "ImageUtilities";
private static final boolean FLAG_DECODE_BITMAP_WITH_SKIA = false;
private static final float EDGE_START = 0.0f;
private static final float EDGE_END = 4.0f;
private static final int EDGE_COLOR_START = 0x7F000000;
private static final int EDGE_COLOR_END = 0x00000000;
private static final Paint EDGE_PAINT = new Paint();
private static final int END_EDGE_COLOR_START = 0x00000000;
private static final int END_EDGE_COLOR_END = 0x4F000000;
private static final Paint END_EDGE_PAINT = new Paint();
private static final float FOLD_START = 5.0f;
private static final float FOLD_END = 13.0f;
private static final int FOLD_COLOR_START = 0x00000000;
private static final int FOLD_COLOR_END = 0x26000000;
private static final Paint FOLD_PAINT = new Paint();
private static final float SHADOW_RADIUS = 12.0f;
private static final int SHADOW_COLOR = 0x99000000;
private static final Paint SHADOW_PAINT = new Paint();
private static final Paint SCALE_PAINT = new Paint(Paint.ANTI_ALIAS_FLAG |
Paint.FILTER_BITMAP_FLAG);
private static final FastBitmapDrawable NULL_DRAWABLE = new FastBitmapDrawable(null);
// TODO: Use a concurrent HashMap to support multiple threads
private static final HashMap<String, SoftReference<FastBitmapDrawable>> sArtCache =
new HashMap<String, SoftReference<FastBitmapDrawable>>();
private static volatile Matrix sScaleMatrix;
private static SimpleDateFormat sLastModifiedFormat;
static {
Shader shader = new LinearGradient(EDGE_START, 0.0f, EDGE_END, 0.0f, EDGE_COLOR_START,
EDGE_COLOR_END, Shader.TileMode.CLAMP);
EDGE_PAINT.setShader(shader);
shader = new LinearGradient(EDGE_START, 0.0f, EDGE_END, 0.0f, END_EDGE_COLOR_START,
END_EDGE_COLOR_END, Shader.TileMode.CLAMP);
END_EDGE_PAINT.setShader(shader);
shader = new LinearGradient(FOLD_START, 0.0f, FOLD_END, 0.0f, new int[] {
FOLD_COLOR_START, FOLD_COLOR_END, FOLD_COLOR_START
}, new float[] { 0.0f, 0.5f, 1.0f }, Shader.TileMode.CLAMP);
FOLD_PAINT.setShader(shader);
SHADOW_PAINT.setShadowLayer(SHADOW_RADIUS / 2.0f, 0.0f, 0.0f, SHADOW_COLOR);
SHADOW_PAINT.setAntiAlias(true);
SHADOW_PAINT.setFilterBitmap(true);
SHADOW_PAINT.setColor(0xFF000000);
SHADOW_PAINT.setStyle(Paint.Style.FILL);
}
private ImageUtilities() {
}
/**
* A Bitmap associated with its last modification date. This can be used to check
* whether the book covers should be downloaded again.
*/
public static class ExpiringBitmap {
public Bitmap bitmap;
public Calendar lastModified;
}
/**
* Deletes the specified drawable from the cache. Calling this method will remove
* the drawable from the in-memory cache and delete the corresponding file from the
* external storage.
*
* @param id The id of the drawable to delete from the cache
*/
public static void deleteCachedCover(String id) {
new File(ImportUtilities.getCacheDirectory(), id).delete();
sArtCache.remove(id);
}
/**
* Retrieves a drawable from the book covers cache, identified by the specified id.
* If the drawable does not exist in the cache, it is loaded and added to the cache.
* If the drawable cannot be added to the cache, the specified default drwaable is
* returned.
*
* @param id The id of the drawable to retrieve
* @param defaultCover The default drawable returned if no drawable can be found that
* matches the id
*
* @return The drawable identified by id or defaultCover
*/
public static FastBitmapDrawable getCachedCover(String id, FastBitmapDrawable defaultCover) {
FastBitmapDrawable drawable = null;
SoftReference<FastBitmapDrawable> reference = sArtCache.get(id);
if (reference != null) {
drawable = reference.get();
}
if (drawable == null) {
final Bitmap bitmap = loadCover(id);
if (bitmap != null) {
drawable = new FastBitmapDrawable(bitmap);
} else {
drawable = NULL_DRAWABLE;
}
sArtCache.put(id, new SoftReference<FastBitmapDrawable>(drawable));
}
return drawable == NULL_DRAWABLE ? defaultCover : drawable;
}
/**
* Removes all the callbacks from the drawables stored in the memory cache. This
* method must be called from the onDestroy() method of any activity using the
* cached drawables. Failure to do so will result in the entire activity being
* leaked.
*/
public static void cleanupCache() {
for (SoftReference<FastBitmapDrawable> reference : sArtCache.values()) {
final FastBitmapDrawable drawable = reference.get();
if (drawable != null) drawable.setCallback(null);
}
}
/**
* Loads an image from the specified URL.
*
* @param url The URL of the image to load.
*
* @return The image at the specified URL or null if an error occured.
*/
public static ExpiringBitmap load(String url) {
return load(url, null);
}
/**
* Loads an image from the specified URL with the specified cookie.
*
* @param url The URL of the image to load.
* @param cookie The cookie to use to load the image.
*
* @return The image at the specified URL or null if an error occured.
*/
public static ExpiringBitmap load(String url, String cookie) {
ExpiringBitmap expiring = new ExpiringBitmap();
final HttpGet get = new HttpGet(url);
if (cookie != null) get.setHeader("cookie", cookie);
HttpEntity entity = null;
try {
final HttpResponse response = HttpManager.execute(get);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
setLastModified(expiring, response);
entity = response.getEntity();
InputStream in = null;
OutputStream out = null;
try {
in = entity.getContent();
if (FLAG_DECODE_BITMAP_WITH_SKIA) {
expiring.bitmap = BitmapFactory.decodeStream(in);
} else {
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
out = new BufferedOutputStream(dataStream, IOUtilities.IO_BUFFER_SIZE);
IOUtilities.copy(in, out);
out.flush();
final byte[] data = dataStream.toByteArray();
expiring.bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
}
} catch (IOException e) {
android.util.Log.e(LOG_TAG, "Could not load image from " + url, e);
} finally {
IOUtilities.closeStream(in);
IOUtilities.closeStream(out);
}
}
} catch (IOException e) {
android.util.Log.e(LOG_TAG, "Could not load image from " + url, e);
} finally {
if (entity != null) {
try {
entity.consumeContent();
} catch (IOException e) {
android.util.Log.e(LOG_TAG, "Could not load image from " + url, e);
}
}
}
return expiring;
}
private static void setLastModified(ExpiringBitmap expiring, HttpResponse response) {
expiring.lastModified = null;
final Header header = response.getFirstHeader("Last-Modified");
if (header == null) return;
if (sLastModifiedFormat == null) {
sLastModifiedFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
}
final Calendar calendar = GregorianCalendar.getInstance();
try {
calendar.setTime(sLastModifiedFormat.parse(header.getValue()));
expiring.lastModified = calendar;
} catch (ParseException e) {
// Ignore
}
}
/**
* Return the same image with a shadow, scaled by the specified amount..
*
* @param bitmap The bitmap to decor with a shadow
* @param width The target width of the decored bitmap
* @param height The target height of the decored bitmap
*
* @return A new Bitmap based on the original bitmap
*/
public static Bitmap createShadow(Bitmap bitmap, int width, int height) {
if (bitmap == null) return null;
final int bitmapWidth = bitmap.getWidth();
final int bitmapHeight = bitmap.getHeight();
final float scale = Math.min((float) width / (float) bitmapWidth,
(float) height / (float) bitmapHeight);
final int scaledWidth = (int) (bitmapWidth * scale);
final int scaledHeight = (int) (bitmapHeight * scale);
return createScaledBitmap(bitmap, scaledWidth, scaledHeight,
SHADOW_RADIUS, false, SHADOW_PAINT);
}
/**
* Create a book cover with the specified bitmap. This method applies several
* lighting effects to the original bitmap and returns a new decored bitmap.
*
* @param bitmap The bitmap to decor with lighting effects
* @param width The target width of the decored bitmap
* @param height The target height of the decored bitmap
*
* @return A new Bitmap based on the original bitmap
*/
public static Bitmap createBookCover(Bitmap bitmap, int width, int height) {
final int bitmapWidth = bitmap.getWidth();
final int bitmapHeight = bitmap.getHeight();
final float scale = Math.min((float) width / (float) bitmapWidth,
(float) height / (float) bitmapHeight);
final int scaledWidth = (int) (bitmapWidth * scale);
final int scaledHeight = (int) (bitmapHeight * scale);
final Bitmap decored = createScaledBitmap(bitmap, scaledWidth, scaledHeight,
SHADOW_RADIUS, true, SHADOW_PAINT);
final Canvas canvas = new Canvas(decored);
canvas.translate(SHADOW_RADIUS / 2.0f, SHADOW_RADIUS / 2.0f);
canvas.drawRect(EDGE_START, 0.0f, EDGE_END, scaledHeight, EDGE_PAINT);
canvas.drawRect(FOLD_START, 0.0f, FOLD_END, scaledHeight, FOLD_PAINT);
//noinspection PointlessArithmeticExpression
canvas.translate(scaledWidth - (EDGE_END - EDGE_START), 0.0f);
canvas.drawRect(EDGE_START, 0.0f, EDGE_END, scaledHeight, END_EDGE_PAINT);
return decored;
}
private static Bitmap loadCover(String id) {
final File file = new File(ImportUtilities.getCacheDirectory(), id);
if (file.exists()) {
InputStream stream = null;
try {
stream = new FileInputStream(file);
return BitmapFactory.decodeStream(stream, null, null);
} catch (FileNotFoundException e) {
// Ignore
} finally {
IOUtilities.closeStream(stream);
}
}
return null;
}
private static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight,
float offset, boolean clipShadow, Paint paint) {
Matrix m;
synchronized (Bitmap.class) {
m = sScaleMatrix;
sScaleMatrix = null;
}
if (m == null) {
m = new Matrix();
}
final int width = src.getWidth();
final int height = src.getHeight();
final float sx = dstWidth / (float) width;
final float sy = dstHeight / (float) height;
m.setScale(sx, sy);
Bitmap b = createBitmap(src, 0, 0, width, height, m, offset, clipShadow, paint);
synchronized (Bitmap.class) {
sScaleMatrix = m;
}
return b;
}
private static Bitmap createBitmap(Bitmap source, int x, int y, int width,
int height, Matrix m, float offset, boolean clipShadow, Paint paint) {
int scaledWidth = width;
int scaledHeight = height;
final Canvas canvas = new Canvas();
canvas.translate(offset / 2.0f, offset / 2.0f);
Bitmap bitmap;
final Rect from = new Rect(x, y, x + width, y + height);
final RectF to = new RectF(0, 0, width, height);
if (m == null || m.isIdentity()) {
bitmap = Bitmap.createBitmap(scaledWidth + (int) offset,
scaledHeight + (int) (clipShadow ? (offset / 2.0f) : offset),
Bitmap.Config.ARGB_8888);
paint = null;
} else {
RectF mapped = new RectF();
m.mapRect(mapped, to);
scaledWidth = Math.round(mapped.width());
scaledHeight = Math.round(mapped.height());
bitmap = Bitmap.createBitmap(scaledWidth + (int) offset,
scaledHeight + (int) (clipShadow ? (offset / 2.0f) : offset),
Bitmap.Config.ARGB_8888);
canvas.translate(-mapped.left, -mapped.top);
canvas.concat(m);
}
canvas.setBitmap(bitmap);
canvas.drawRect(0.0f, 0.0f, width, height, paint);
canvas.drawBitmap(source, from, to, SCALE_PAINT);
return bitmap;
}
}