/* * 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; } }