/* * Copyright (C) 2013 Square, Inc. * * 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.squareup.picasso; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.SystemClock; import android.widget.ImageView; import static android.graphics.Color.WHITE; import static com.squareup.picasso.Request.LoadedFrom; import static com.squareup.picasso.Request.LoadedFrom.MEMORY; final class PicassoDrawable extends Drawable { // Only accessed from main thread. private static final Paint DEBUG_PAINT = new Paint(); private static final float FADE_DURATION = 200f; //ms /** * Create or update the drawable on the target {@link ImageView} to display the supplied bitmap * image. */ static void setBitmap(ImageView target, Context context, Bitmap bitmap, LoadedFrom loadedFrom, boolean noFade, boolean debugging) { PicassoDrawable picassoDrawable = extractPicassoDrawable(target); if (picassoDrawable != null) { picassoDrawable.setBitmap(bitmap, loadedFrom, noFade); } else { target.setImageDrawable(new PicassoDrawable(context, bitmap, loadedFrom, noFade, debugging)); } } /** * Create or update the drawable on the target {@link ImageView} to display the supplied * placeholder image. */ static void setPlaceholder(ImageView target, Context context, int placeholderResId, Drawable placeholderDrawable, boolean debugging) { PicassoDrawable picassoDrawable = extractPicassoDrawable(target); if (picassoDrawable != null) { picassoDrawable.setPlaceholder(placeholderResId, placeholderDrawable); } else { target.setImageDrawable( new PicassoDrawable(context, placeholderResId, placeholderDrawable, debugging)); } } /** * Check for an existing instance of picasso drawable to save allocations if we need to set a * placeholder or were able to find the bitmap in the memory cache. */ private static PicassoDrawable extractPicassoDrawable(ImageView target) { Drawable targetDrawable = target.getDrawable(); if (targetDrawable instanceof PicassoDrawable) { return (PicassoDrawable) targetDrawable; } return null; } private final Context context; private final boolean debugging; private final float density; int placeholderResId; Drawable placeHolderDrawable; BitmapDrawable bitmapDrawable; private LoadedFrom loadedFrom; private int alpha; private long startTimeMillis; boolean animating; /** * Construct a drawable with the given placeholder (drawable or resource id). The actual bitmap * will be set later via * {@link #setBitmap(android.graphics.Bitmap, com.squareup.picasso.Request.LoadedFrom, boolean)}). * <p/> * This drawable may be re-used with view recycling by a call to * {@link #setBitmap(android.graphics.Bitmap, com.squareup.picasso.Request.LoadedFrom, boolean)} * or {@link #setPlaceholder(int, android.graphics.drawable.Drawable)}. */ PicassoDrawable(Context context, int placeholderResId, Drawable placeholderDrawable, boolean debugging) { Resources resources = context.getResources(); this.context = context.getApplicationContext(); this.density = resources.getDisplayMetrics().density; this.placeholderResId = placeholderResId; if (placeholderResId != 0) { placeholderDrawable = resources.getDrawable(placeholderResId); } this.placeHolderDrawable = placeholderDrawable; this.debugging = debugging; } /** * Construct a drawable with the actual bitmap for immediate display. * <p/> * This drawable may be re-used with view recycling by a call to * {@link #setBitmap(android.graphics.Bitmap, com.squareup.picasso.Request.LoadedFrom, boolean)} * or {@link #setPlaceholder(int, android.graphics.drawable.Drawable)}. */ PicassoDrawable(Context context, Bitmap bitmap, LoadedFrom loadedFrom, boolean noFade, boolean debugging) { Resources resources = context.getResources(); this.context = context.getApplicationContext(); this.loadedFrom = loadedFrom; this.density = resources.getDisplayMetrics().density; // TODO remove. draw ourselves. this.bitmapDrawable = new BitmapDrawable(resources, bitmap); this.debugging = debugging; if (loadedFrom != MEMORY && !noFade) { startTimeMillis = 0; animating = true; } } @Override public void draw(Canvas canvas) { // If no bitmap has been set, quickly draw the placeholder which must be present and return. if (bitmapDrawable == null) { placeHolderDrawable.draw(canvas); return; } boolean done = true; if (animating) { if (startTimeMillis == 0) { startTimeMillis = SystemClock.uptimeMillis(); done = false; alpha = 0; } else { float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION; done = normalized >= 1.0f; normalized = Math.min(normalized, 1.0f); alpha = (int) (0xFF * normalized); } } if (done) { bitmapDrawable.draw(canvas); } else { if (placeHolderDrawable != null) { placeHolderDrawable.draw(canvas); } if (alpha > 0) { bitmapDrawable.setAlpha(alpha); bitmapDrawable.draw(canvas); bitmapDrawable.setAlpha(0xFF); } invalidateSelf(); } if (debugging) { drawDebugIndicator(canvas); } } @Override public int getIntrinsicWidth() { if (bitmapDrawable != null) { return bitmapDrawable.getIntrinsicWidth(); } return -1; } @Override public int getIntrinsicHeight() { if (bitmapDrawable != null) { return bitmapDrawable.getIntrinsicHeight(); } return -1; } @Override public void setAlpha(int alpha) { // No-op } @Override public void setColorFilter(ColorFilter cf) { // No-op } @Override public int getOpacity() { return PixelFormat.OPAQUE; } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); if (bitmapDrawable != null) { setBounds(this.bitmapDrawable); } if (placeHolderDrawable != null) { this.placeHolderDrawable.setBounds(getBounds()); } } /** * Reset to displaying the specified placeholder (drawable or resource id). This will be called * when a view is recycled to avoid creating a new drawable. */ void setPlaceholder(int placeholderResId, Drawable placeHolderDrawable) { bitmapDrawable = null; loadedFrom = null; if (placeholderResId != 0) { if (this.placeholderResId != placeholderResId) { this.placeHolderDrawable = context.getResources().getDrawable(placeholderResId); this.placeHolderDrawable.setBounds(getBounds()); } } else if (this.placeHolderDrawable != placeHolderDrawable) { this.placeHolderDrawable = placeHolderDrawable; this.placeHolderDrawable.setBounds(getBounds()); } invalidateSelf(); } /** * Set the actual bitmap that we should be displaying. If we already have an image and the source * of the new image was not the memory cache then perform a cross-fade. */ void setBitmap(Bitmap bitmap, LoadedFrom loadedFrom, boolean noFade) { boolean fade = loadedFrom != MEMORY && !noFade; if (bitmapDrawable != null && fade) { placeHolderDrawable = bitmapDrawable; } bitmapDrawable = new BitmapDrawable(context.getResources(), bitmap); setBounds(bitmapDrawable); this.loadedFrom = loadedFrom; startTimeMillis = 0; animating = fade; invalidateSelf(); } private void setBounds(Drawable drawable) { Rect bounds = getBounds(); final int width = bounds.width(); final int height = bounds.height(); final float ratio = (float) width / height; final int drawableWidth = drawable.getIntrinsicWidth(); final int drawableHeight = drawable.getIntrinsicHeight(); final float drawableRatio = (float) drawableWidth / drawableHeight; if (drawableRatio < ratio) { final float scale = (float) height / drawableHeight; final int scaledDrawableWidth = (int) (drawableWidth * scale); final int drawableLeft = bounds.left - (scaledDrawableWidth - width) / 2; final int drawableRight = drawableLeft + scaledDrawableWidth; drawable.setBounds(drawableLeft, bounds.top, drawableRight, bounds.bottom); } else { final float scale = (float) width / drawableWidth; final int scaledDrawableHeight = (int) (drawableHeight * scale); final int drawableTop = bounds.top - (scaledDrawableHeight - height) / 2; final int drawableBottom = drawableTop + scaledDrawableHeight; drawable.setBounds(bounds.left, drawableTop, bounds.right, drawableBottom); } } private void drawDebugIndicator(Canvas canvas) { canvas.save(); canvas.rotate(45); // Draw a white square for the indicator border. DEBUG_PAINT.setColor(WHITE); canvas.drawRect(0, -10 * density, 7.5f * density, 10 * density, DEBUG_PAINT); // Draw a slightly smaller square for the indicator color. DEBUG_PAINT.setColor(loadedFrom.debugColor); canvas.drawRect(0, -9 * density, 6.5f * density, 9 * density, DEBUG_PAINT); canvas.restore(); } }