如何在不创建新位图的情况下获得圆形中心裁剪的imageView?

时间:2016-02-18 12:42:43

标签: android bitmap geometry android-imageview aspect-ratio

注意:我知道有很多关于此的问题和存储库,但似乎没有一个符合我的目标。

背景

给定任何宽高比的位图,我希望将其设置为ImageView的内容(仅使用drawable,而不扩展ImageView),以便内容将被中心裁剪,但形状一圈。

所有这一切,内存使用量最少,因为有时图像可能非常大。我不想为此创建一个全新的Bitmap。内容已经存在......

问题

我发现的所有解决方案都缺少我写过的内容之一:有些不是中心裁剪,有些人认为图像是方形的,有些则是根据给定的位图创建新的位图。

我尝试了什么

除了尝试各种存储库之外,我已尝试过this tutorial,我试图针对非方形宽高比进行修复,但我已经失败了。

以下是其代码,以防网站关闭:

public class RoundImage extends Drawable {
      private final Bitmap mBitmap;
      private final Paint mPaint;
      private final RectF mRectF;
      private final int mBitmapWidth;
      private final int mBitmapHeight;

      public RoundImage(Bitmap bitmap) {
            mBitmap = bitmap;
            mRectF = new RectF();
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setDither(true);
            final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            mPaint.setShader(shader);

            mBitmapWidth = mBitmap.getWidth();
            mBitmapHeight = mBitmap.getHeight();
      }

      @Override
      public void draw(Canvas canvas) {
            canvas.drawOval(mRectF, mPaint);
      }

      @Override
      protected void onBoundsChange(Rect bounds) {
            super.onBoundsChange(bounds);
            mRectF.set(bounds);
      }

      @Override
      public void setAlpha(int alpha) {
            if (mPaint.getAlpha() != alpha) {
                  mPaint.setAlpha(alpha);
                  invalidateSelf();
            }
      }

      @Override
      public void setColorFilter(ColorFilter cf) {
            mPaint.setColorFilter(cf);
      }

      @Override
      public int getOpacity() {
            return PixelFormat.TRANSLUCENT;
      }

      @Override
      public int getIntrinsicWidth() {
            return mBitmapWidth;
      }

      @Override
      public int getIntrinsicHeight() {
            return mBitmapHeight;
      }

      public void setAntiAlias(boolean aa) {
            mPaint.setAntiAlias(aa);
            invalidateSelf();
      }

      @Override
      public void setFilterBitmap(boolean filter) {
            mPaint.setFilterBitmap(filter);
            invalidateSelf();
      }

      @Override
      public void setDither(boolean dither) {
            mPaint.setDither(dither);
            invalidateSelf();
      }

      public Bitmap getBitmap() {
            return mBitmap;
      }

}

我发现的一个非常好的解决方案(here)正是我所需要的,除了它在ImageView本身中使用它,而不是创建一个drawable。这意味着我无法将其设置为例如视图的背景。

问题

我怎样才能做到这一点?

编辑:这是当前的代码,因为我想添加边框,它也有这个代码:

public class SimpleRoundedDrawable extends BitmapDrawable {
    private final Path p = new Path();
    private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public SimpleRoundedDrawable(final Resources res, final Bitmap bitmap) {
        super(res, bitmap);
        mBorderPaint.setStyle(Paint.Style.STROKE);
    }

    public SimpleRoundedDrawable setBorder(float borderWidth, @ColorInt int borderColor) {
        mBorderPaint.setStrokeWidth(borderWidth);
        mBorderPaint.setColor(borderColor);
        invalidateSelf();
        return this;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        p.rewind();
        p.addCircle(bounds.width() / 2,
                bounds.height() / 2,
                Math.min(bounds.width(), bounds.height()) / 2,
                Path.Direction.CW);
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.clipPath(p);
        super.draw(canvas);
        final float width = getBounds().width(), height = getBounds().height();
        canvas.drawCircle(width / 2, height / 2, Math.min(width, height) / 2, mBorderPaint);
    }
}

我希望这就是事情应该如何发挥作用。

编辑:似乎该解决方案仅适用于特定的Android版本,因为它不适用于Android 4.2.2。相反,它显示了平方图像。

编辑:似乎上述解决方案的效率远低于使用BitmapShader(链接here)。知道如何在drawable中使用它而不是在自定义的ImageView中使用它真的很棒

- 这是以下解决方案的当前修改版本。我希望这对某些人来说会很方便:

public class SimpleRoundedDrawable extends Drawable {
    final Paint mMaskPaint = new Paint(Paint.ANTI_ALIAS_FLAG), mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Bitmap mBitmap;
    int mSide;
    float mRadius;

    public SimpleRoundedDrawable() {
        this(null);
    }

    public SimpleRoundedDrawable(Bitmap bitmap) {
        this(bitmap, 0, 0);
    }

    public SimpleRoundedDrawable(Bitmap bitmap, float width, @ColorInt int color) {
        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBitmap = bitmap;
        mSide = mBitmap == null ? 0 : Math.min(bitmap.getWidth(), bitmap.getHeight());
        mBorderPaint.setStrokeWidth(width);
        mBorderPaint.setColor(color);
    }

    public SimpleRoundedDrawable setBitmap(final Bitmap bitmap) {
        mBitmap = bitmap;
        mSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
        invalidateSelf();
        return this;
    }

    public SimpleRoundedDrawable setBorder(float width, @ColorInt int color) {
        mBorderPaint.setStrokeWidth(width);
        mBorderPaint.setColor(color);
        invalidateSelf();
        return this;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        if (mBitmap == null)
            return;
        Matrix matrix = new Matrix();
        RectF src = new RectF(0, 0, mSide, mSide);
        src.offset((mBitmap.getWidth() - mSide) / 2f, (mBitmap.getHeight() - mSide) / 2f);
        RectF dst = new RectF(bounds);
        final float strokeWidth = mBorderPaint.getStrokeWidth();
        if (strokeWidth > 0)
            dst.inset(strokeWidth, strokeWidth);
        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
        Shader shader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        shader.setLocalMatrix(matrix);
        mMaskPaint.setShader(shader);
        matrix.mapRect(src);
        mRadius = src.width() / 2f;
    }

    @Override
    public void draw(Canvas canvas) {
        Rect b = getBounds();
        if (mBitmap != null)
            canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), mRadius, mMaskPaint);
        final float strokeWidth = mBorderPaint.getStrokeWidth();
        if (strokeWidth > 0)
            canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), mRadius + strokeWidth / 2, mBorderPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        mMaskPaint.setAlpha(alpha);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mMaskPaint.setColorFilter(cf);
        invalidateSelf();
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }
}

5 个答案:

答案 0 :(得分:3)

如果我正确地关注您,您的Drawable课程就是这样:

public class CroppedDrawable extends BitmapDrawable {
    private Path p = new Path();

    public CroppedDrawable(Bitmap b) {
        super(b);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);

        p.rewind();
        p.addCircle(bounds.width() / 2,
                    bounds.height() / 2,
                    Math.min(bounds.width(), bounds.height()) / 2,
                    Path.Direction.CW);
    } 

    @Override
    public void draw(Canvas canvas) {
        canvas.clipPath(p);
        super.draw(canvas);
    }
}

示例用法是:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mila);
CroppedDrawable cd = new CroppedDrawable(bitmap);
imageView.setImageDrawable(cd);

使用之前的示例图像,可以得到如下内容:

screenshot

答案 1 :(得分:3)

尝试这种极简主义自定义Drawable并对其进行修改以满足您的需求:

class D extends Drawable {
    Bitmap bitmap;
    Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    int side;
    float radius;

    public D(Bitmap wrappedBitmap) {
        bitmap = wrappedBitmap;
        borderPaint.setStyle(Paint.Style.STROKE);
        borderPaint.setStrokeWidth(16);
        borderPaint.setColor(0xcc220088);
        side = Math.min(bitmap.getWidth(), bitmap.getHeight());
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        Matrix matrix = new Matrix();
        RectF src = new RectF(0, 0, side, side);
        src.offset((bitmap.getWidth() - side) / 2f, (bitmap.getHeight() - side) / 2f);
        RectF dst = new RectF(bounds);
        dst.inset(borderPaint.getStrokeWidth(), borderPaint.getStrokeWidth());
        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);

        Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        shader.setLocalMatrix(matrix);
        maskPaint.setShader(shader);
        matrix.mapRect(src);
        radius = src.width() / 2f;
    }

    @Override
    public void draw(Canvas canvas) {
        Rect b = getBounds();
        canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), radius, maskPaint);
        canvas.drawCircle(b.exactCenterX(), b.exactCenterY(), radius + borderPaint.getStrokeWidth() / 2, borderPaint);
    }

    @Override public void setAlpha(int alpha) {}
    @Override public void setColorFilter(ColorFilter cf) {}
    @Override public int getOpacity() {return PixelFormat.TRANSLUCENT;}
}

答案 2 :(得分:2)

基于我的CircleImageView库的CircleImageDrawable的快速草稿。这不会创建新的Bitmap,使用BitmapShader来实现所需的效果并对图像进行中心裁剪。

public class CircleImageDrawable extends Drawable {

    private final RectF mBounds = new RectF();
    private final RectF mDrawableRect = new RectF();
    private final RectF mBorderRect = new RectF();

    private final Matrix mShaderMatrix = new Matrix();
    private final Paint mBitmapPaint = new Paint();
    private final Paint mBorderPaint = new Paint();

    private int mBorderColor = Color.BLACK;
    private int mBorderWidth = 0;

    private Bitmap mBitmap;
    private BitmapShader mBitmapShader;
    private int mBitmapWidth;
    private int mBitmapHeight;

    private float mDrawableRadius;
    private float mBorderRadius;

    public CircleImageDrawable(Bitmap bitmap) {
        mBitmap = bitmap;
        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawCircle(mBounds.width() / 2.0f, mBounds.height() / 2.0f, mDrawableRadius, mBitmapPaint);
        if (mBorderWidth != 0) {
            canvas.drawCircle(mBounds.width() / 2.0f, mBounds.height() / 2.0f, mBorderRadius, mBorderPaint);
        }
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        mBounds.set(bounds);
        setup();
    }

    private void setup() {
        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader);

        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);

        mBorderRect.set(mBounds);
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);

        mDrawableRect.set(mBorderRect);
        mDrawableRect.inset(mBorderWidth, mBorderWidth);
        mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);

        updateShaderMatrix();
        invalidateSelf();
    }

    private void updateShaderMatrix() {
        float scale;
        float dx = 0;
        float dy = 0;

        mShaderMatrix.set(null);

        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
            scale = mDrawableRect.height() / (float) mBitmapHeight;
            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
        } else {
            scale = mDrawableRect.width() / (float) mBitmapWidth;
            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
        }

        mShaderMatrix.setScale(scale, scale);
        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);

        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

    @Override
    public void setAlpha(int alpha) {
        mBitmapPaint.setAlpha(alpha);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mBitmapPaint.setColorFilter(colorFilter);
        invalidateSelf();
    }

    @Override
    public int getOpacity() {
        return 0;
    }

}

答案 3 :(得分:1)

看起来,使用“clipPath”效率不高,需要在4.3及以下禁用硬件加速。

更好的解决方案是使用BitmapShader:

在此库中使用类似的东西

https://github.com/hdodenhof/CircleImageView

基于:

http://www.curious-creature.com/2012/12/11/android-recipe-1-image-with-rounded-corners/

相关代码是:

BitmapShader shader;
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);

RectF rect = new RectF(0.0f, 0.0f, width, height);

// rect contains the bounds of the shape
// radius is the radius in pixels of the rounded corners
// paint contains the shader that will texture the shape
canvas.drawRoundRect(rect, radius, radius, paint);

如果输入是位图,我仍然想知道如何在drawable中完成所有操作。

答案 4 :(得分:0)

已经有一种内置方法可以实现这一点,它有一行代码(ThumbnailUtils.extractThumbnail())

int dimension = getSquareCropDimensionForBitmap(bitmap);
bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension); 

... 

//I added this method because people keep asking how  
//to calculate the dimensions of the bitmap...see comments below 
public int getSquareCropDimensionForBitmap(Bitmap bitmap)
{ 
    //If the bitmap is wider than it is tall 
    //use the height as the square crop dimension 
    if (bitmap.getWidth() >= bitmap.getHeight())
    { 
        dimension = bitmap.getHeight();
    } 
    //If the bitmap is taller than it is wide 
    //use the width as the square crop dimension 
    else 
    { 
        dimension = bitmap.getWidth();
    }  
} 

如果您希望回收位图对象,可以传递使其成为的选项:

bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);

以下是文档的链接: ThumbnailUtils Documentation