/* * Materialize - Materialize all those not material * Copyright (C) 2015 XiNGRZ <xxx@oxo.ooo> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ooo.oxo.apps.materialize.graphics; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.support.annotation.DimenRes; import android.support.annotation.DrawableRes; import android.support.annotation.Nullable; import ooo.oxo.apps.materialize.R; public class CompositeDrawable extends Drawable { private static final int FLAG_SCALES = Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG; private static final int SCORE_COLOR = Color.argb(0x10, 0, 0, 0); private final Resources resources; private final RectF foregroundBounds = new RectF(); private final Rect backgroundBounds = new Rect(); private final Rect scoreBounds = new Rect(); private final Paint paint = new Paint(); @Nullable private Bitmap source; private Shape shape; private float padding = 0; private Drawable background; private Bitmap back; private Bitmap mask; private Bitmap fore; public CompositeDrawable(Resources resources) { this.resources = resources; setShape(Shape.SQUARE); } public void setSource(@Nullable Bitmap source) { this.source = source; invalidateSelf(); } public void setShape(Shape shape) { this.shape = shape; invalidateForegroundBounds(); invalidateBackgroundBounds(); invalidateBitmaps(); invalidateSelf(); } public void setPadding(float padding) { this.padding = padding; invalidateForegroundBounds(); invalidateSelf(); } public void setBackground(Drawable background) { this.background = background; invalidateSelf(); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); invalidateForegroundBounds(); invalidateBackgroundBounds(); invalidateScoreBounds(); } private void invalidateForegroundBounds() { foregroundBounds.set(getBounds()); applyForegroundBounds(foregroundBounds); } private void applyForegroundBounds(RectF foregroundBounds) { float finalPadding = padding + shape.getPadding(resources); foregroundBounds.inset(finalPadding, finalPadding); } private void invalidateBackgroundBounds() { backgroundBounds.set(getBounds()); applyBackgroundBounds(backgroundBounds); } private void applyBackgroundBounds(Rect backgroundBounds) { int offset = resources.getDimensionPixelOffset(shape.padding); backgroundBounds.inset(offset, offset); } private void invalidateScoreBounds() { scoreBounds.set(getBounds()); applyScoreBounds(scoreBounds); } private void applyScoreBounds(Rect scoreBounds) { scoreBounds.bottom = scoreBounds.centerY(); } private void invalidateBitmaps() { if (back != null) { back.recycle(); } back = shape.getBackBitmap(resources); if (mask != null) { mask.recycle(); } mask = shape.getMaskBitmap(resources); if (fore != null) { fore.recycle(); } fore = shape.getForeBitmap(resources); } @Override public void draw(Canvas canvas) { drawInternal(canvas, true, getBounds(), foregroundBounds, backgroundBounds, scoreBounds); } public void drawTo(Canvas canvas, boolean antiAliasing) { Rect bounds = new Rect(0, 0, canvas.getWidth(), canvas.getHeight()); RectF foregroundBounds = new RectF(bounds); applyForegroundBounds(foregroundBounds); Rect backgroundBounds = new Rect(bounds); applyBackgroundBounds(backgroundBounds); Rect scoreBounds = new Rect(bounds); applyScoreBounds(scoreBounds); drawInternal(canvas, antiAliasing, bounds, foregroundBounds, backgroundBounds, scoreBounds); } private void drawInternal(Canvas canvas, boolean antiAliasing, Rect bounds, RectF foregroundBounds, Rect backgroundBounds, Rect scoreBounds) { paint.setFlags(antiAliasing ? FLAG_SCALES : 0); canvas.drawBitmap(back, null, bounds, paint); canvas.saveLayer( bounds.left, bounds.top, bounds.right, bounds.bottom, null, Canvas.ALL_SAVE_FLAG); if (background != null) { background.setBounds(backgroundBounds); background.draw(canvas); } // always anti-aliasing on source bitmap because we scaled it if (source != null) { paint.setFlags(FLAG_SCALES); canvas.drawBitmap(source, null, foregroundBounds, paint); } paint.setFlags(antiAliasing ? FLAG_SCALES : 0); if (shape.score) { paint.setColor(SCORE_COLOR); canvas.drawRect(scoreBounds, paint); paint.setColor(Color.WHITE); } paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(mask, null, bounds, paint); paint.setXfermode(null); canvas.drawBitmap(fore, null, bounds, paint); canvas.restore(); } @Override public void setAlpha(int alpha) { // not support } @Override public void setColorFilter(ColorFilter colorFilter) { // not support } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } public enum Shape { SQUARE( R.dimen.default_padding_square, R.drawable.stencil_square_back, R.drawable.stencil_square_mask, R.drawable.stencil_square_fore, false ), SQUARE_SCORE( R.dimen.default_padding_square, R.drawable.stencil_square_back, R.drawable.stencil_square_mask, R.drawable.stencil_square_fore, true ), SQUARE_DOGEAR( R.dimen.default_padding_square, R.drawable.stencil_square_dogear_back, R.drawable.stencil_square_dogear_mask, R.drawable.stencil_square_dogear_fore, false ), ROUND( R.dimen.default_padding_round, R.drawable.stencil_round_back, R.drawable.stencil_round_mask, R.drawable.stencil_round_fore, false ), ROUND_SCORE( R.dimen.default_padding_round, R.drawable.stencil_round_back, R.drawable.stencil_round_mask, R.drawable.stencil_round_fore, true ); @DimenRes public final int padding; @DrawableRes public final int back; @DrawableRes public final int mask; @DrawableRes public final int fore; public final boolean score; Shape(int padding, @DrawableRes int back, @DrawableRes int mask, @DrawableRes int fore, boolean score) { this.padding = padding; this.back = back; this.mask = mask; this.fore = fore; this.score = score; } public float getPadding(Resources resources) { return resources.getDimension(padding); } public Bitmap getBitmap(Resources resources, @DrawableRes int drawable) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inScaled = false; return BitmapFactory.decodeResource(resources, drawable, options); } public Bitmap getBackBitmap(Resources resources) { return getBitmap(resources, back); } public Bitmap getMaskBitmap(Resources resources) { return getBitmap(resources, mask); } public Bitmap getForeBitmap(Resources resources) { return getBitmap(resources, fore); } } }