/* * Copyright 2014 Vinay S Shenoy * * 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.vinaysshenoy.okulus; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.widget.ImageView; /** * Custom drawable class that takes care of the actual drawing */ class OkulusDrawable extends Drawable { private static final String TAG = "OkulusDrawable"; private final RectF mRect = new RectF(); /** * Rect used for drawing the border */ private RectF mBorderRect; /** * Rect used for drawing the shadow */ private RectF mShadowRect; /** * Rect used for drawing the actual image */ private RectF mImageRect; private BitmapShader mBitmapShader; private final Paint mPaint; private float mBorderSize; private int mBorderColor; private boolean mFullCircle; private float mCornerRadius; private float mShadowSize; private int mShadowColor; private int mBitmapWidth; private int mBitmapHeight; private int mTouchSelectorColor; private Matrix mShaderMatrix; private ImageView.ScaleType mScaleType; private BlurMaskFilter mShadowMaskFilter; public OkulusDrawable(Bitmap bitmap, float cornerRadius, boolean fullCircle, float borderWidth, int borderColor, float shadowSize, int shadowColor, int touchSelectorColor, ImageView.ScaleType scaleType) { mCornerRadius = cornerRadius; mBorderSize = borderWidth; mBorderColor = borderColor; mFullCircle = fullCircle; mShadowColor = shadowColor; mShadowSize = shadowSize; mTouchSelectorColor = touchSelectorColor; if (ImageView.ScaleType.FIT_XY != scaleType && ImageView.ScaleType.CENTER_CROP != scaleType) { android.util.Log.w(TAG, "Only FIT_XY and CENTER_CROP scale types are supported"); } else { mScaleType = scaleType; } mBorderRect = new RectF(); mImageRect = new RectF(); mShadowRect = new RectF(); mShaderMatrix = new Matrix(); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); updateBitmap(bitmap); } /** * Updates the touch selector color * * @param touchSelectorColor The color to use as the touch selector */ public void setTouchSelectorColor(final int touchSelectorColor) { mTouchSelectorColor = touchSelectorColor; } /** * Creates a bitmap shader with a bitmap */ private BitmapShader getShaderForBitmap(Bitmap bitmap) { return new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); } /** * Updates the drawable with a Bitmap. {@link OkulusImageView#invalidate()} must be called by * the caller after this method returns * * @param bitmap The Bitmap to set, or <code>null</code> to clear the bitmap being drawn */ public void updateBitmap(Bitmap bitmap) { if (bitmap == null) { mBitmapShader = null; mBitmapWidth = 0; mBitmapHeight = 0; } else { mBitmapWidth = bitmap.getWidth(); mBitmapHeight = bitmap.getHeight(); mBitmapShader = getShaderForBitmap(bitmap); mBitmapShader.setLocalMatrix(mShaderMatrix); } } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); mRect.set(bounds); mShadowRect.set(mRect); mShadowRect.inset(mShadowSize, mShadowSize); if (mFullCircle) { mCornerRadius = Math.abs(mRect.left - mRect.right) / 2; } if (mBorderSize > 0) { initRectsWithBorders(); } else { initRectsWithoutBorders(); } if(mShadowSize > 0) { mShadowMaskFilter = new BlurMaskFilter(mShadowSize * 0.95F, BlurMaskFilter.Blur.SOLID); } updateShaderMatrix(); } /** * Initializes the rects without borders, taking shadows into account */ private void initRectsWithoutBorders() { mImageRect.set(mRect); mImageRect.bottom -= (mShadowSize * 1.5F); } /** * Initialize the rects with borders, taking shadows into account */ private void initRectsWithBorders() { mBorderRect.set(mRect); mBorderRect.inset(mBorderSize, mBorderSize); mBorderRect.left += Math.min(mBorderSize, mShadowSize) * 0.5F; mBorderRect.right -= Math.min(mBorderSize, mShadowSize) * 0.5F; mBorderRect.top += Math.min(mBorderSize, mShadowSize) * 0.5F; mBorderRect.bottom -= (mShadowSize + mBorderSize * 0.5F); // * 1.25F; mImageRect.set(mBorderRect); } /** * Updates the bitmap shader matrix to take the scale type into account */ private void updateShaderMatrix() { final float viewWidth = Math.abs(mRect.left - mRect.right); final float viewHeight = Math.abs(mRect.top - mRect.bottom); mShaderMatrix.reset(); if (mBitmapWidth == 0 && mBitmapHeight == 0) { return; } final float widthScale = viewWidth / (float) mBitmapWidth; final float heightScale = viewHeight / (float) mBitmapHeight; if (mScaleType == ImageView.ScaleType.CENTER_CROP) { float scale = Math.max(widthScale, heightScale); mShaderMatrix.postScale(scale, scale); mShaderMatrix.postTranslate((viewWidth - mBitmapWidth * scale) / 2F, (viewHeight - mBitmapHeight * scale) / 2F); } else if (mScaleType == ImageView.ScaleType.FIT_XY) { final RectF tempSrc = new RectF(0, 0, mBitmapWidth, mBitmapHeight); mShaderMatrix.setRectToRect(tempSrc, mRect, Matrix.ScaleToFit.FILL); float scale = Math.min(1.0f, Math.min(widthScale, heightScale)); mShaderMatrix.postScale(scale, scale); mShaderMatrix.postTranslate((viewWidth - mBitmapWidth * scale) / 2F, (viewHeight - mBitmapHeight * scale) / 2F); } if (mBitmapShader != null) { mBitmapShader.setLocalMatrix(mShaderMatrix); } } @Override public void draw(Canvas canvas) { if (mBitmapShader != null) { if (mShadowSize > 0) { drawShadows(canvas); } drawImage(canvas); drawBorders(canvas); if (mTouchSelectorColor != Color.TRANSPARENT) { drawTouchSelector(canvas); } } } /** * Draws drop shadows */ private void drawShadows(Canvas canvas) { mPaint.setShader(null); mPaint.setStrokeWidth(mShadowSize); mPaint.setColor(mShadowColor); mPaint.setStyle(Paint.Style.STROKE); mPaint.setMaskFilter(mShadowMaskFilter); if (mFullCircle) { canvas.drawCircle(mShadowRect.centerX(), mShadowRect.centerY(), mShadowRect.width() / 2.0F, mPaint); } else { canvas.drawRoundRect(mShadowRect, mCornerRadius, mCornerRadius, mPaint); } mPaint.setMaskFilter(null); } /** * Draws the touch selector on the canvas based on the View attributes * * @param canvas The canvas to draw the touch selector on */ private void drawTouchSelector(final Canvas canvas) { final int prevColor = mPaint.getColor(); mPaint.setShader(null); mPaint.setColor(mTouchSelectorColor); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); if (mBorderSize > 0) { canvas.drawRoundRect(mBorderRect, mCornerRadius, mCornerRadius, mPaint); } else { canvas.drawRoundRect(mImageRect, mCornerRadius, mCornerRadius, mPaint); } mPaint.setColor(prevColor); } /** * Draw the image on the canvas based on the View attributes * * @param canvas The canvas to draw the image on */ private void drawImage(final Canvas canvas) { mPaint.setColor(Color.WHITE); mPaint.setShader(mBitmapShader); mPaint.setStyle(Paint.Style.FILL); if (mFullCircle) { canvas.drawCircle(mImageRect.centerX(), mImageRect.centerY(), mImageRect.width() / 2F, mPaint); } else { canvas.drawRoundRect(mImageRect, mCornerRadius, mCornerRadius, mPaint); } } /** * Draw the borders & shadows on the canvas based on the view attributes * * @param canvas The canvas to draw the borders on */ private void drawBorders(final Canvas canvas) { if (mBorderSize > 0) { mPaint.setShader(null); mPaint.setColor(mBorderColor); mPaint.setStrokeWidth(mBorderSize); mPaint.setStyle(Paint.Style.STROKE); if (mFullCircle) { canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRect.width() / 2.0F, mPaint); } else { canvas.drawRoundRect(mBorderRect, mCornerRadius, mCornerRadius, mPaint); } } } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } }