安卓裁剪位图中心

162

我有一些位图,它们是正方形或矩形。我会选择最短的边,然后做类似以下的操作:

int value = 0;
if (bitmap.getHeight() <= bitmap.getWidth()) {
    value = bitmap.getHeight();
} else {
    value = bitmap.getWidth();
}

Bitmap finalBitmap = null;
finalBitmap = Bitmap.createBitmap(bitmap, 0, 0, value, value);

然后我使用以下方式将其缩放为 144 x 144 的位图:

Bitmap lastBitmap = null;
lastBitmap = Bitmap.createScaledBitmap(finalBitmap, 144, 144, true);

问题是它裁剪了原始位图的左上角,有人有裁剪位图中心的代码吗?

10个回答

373

enter image description here

可以通过以下方式实现:Bitmap.createBitmap(source, x, y, width, height)

if (srcBmp.getWidth() >= srcBmp.getHeight()){

  dstBmp = Bitmap.createBitmap(
     srcBmp, 
     srcBmp.getWidth()/2 - srcBmp.getHeight()/2,
     0,
     srcBmp.getHeight(), 
     srcBmp.getHeight()
     );

}else{

  dstBmp = Bitmap.createBitmap(
     srcBmp,
     0, 
     srcBmp.getHeight()/2 - srcBmp.getWidth()/2,
     srcBmp.getWidth(),
     srcBmp.getWidth() 
     );
}

1
编辑了回答,以便实际的目标位图是一个正方形。 - Joram van den Boezem
1
@Lumis:你为什么回滚第三个版本?它似乎是有效的正确的。你当前的版本创建了正确的起点,但是包括了太长边缘的其余部分。例如,给定一个100x1000的图像,你会得到一个100x550的图像。 - Guvante
12
请看我关于使用内置的ThumbnailUtils.extractThumbnail()方法的回答。为什么要重复发明轮子??? https://dev59.com/Cmw05IYBdhLWcg3w_Guw#17733530 - DiscDev
记得回收 srcBitmap :) - Dori
1
这个解决方案比创建画布并将可绘制对象绘制到其中的方法更短。它还比ThumbnailUtils解决方案处理更少(后者需要进行样本大小计算以确定如何缩放)。 - yincrash
显示剩余8条评论

344

虽然上面的大部分答案都提供了一种方法来实现这一点,但是已经有一种内置的方法可以完成这个任务,而且只需要1行代码(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)
{
    //use the smallest dimension of the image to crop to
    return Math.min(bitmap.getWidth(), bitmap.getHeight());
}

如果您希望回收位图对象,则可以传递使其实现这一点的选项:

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

来自:ThumbnailUtils文档

public static Bitmap extractThumbnail (Bitmap source, int width, int height)

API level 8中添加的功能,创建所需大小的居中位图。

参数:source 原始位图来源,width 目标宽度,height 目标高度

使用被接受的答案时,有时会出现内存不足错误,使用ThumbnailUtils解决了这些问题。此外,这样做更加清晰且可重用。


8
+1 我认为您需要改进这段代码,而不是使用400像素,请传递最短的bmp大小,以提供上面原始帖子的替代方案。但是感谢您引起我们的注意,这似乎是一个非常有用的功能。可惜我之前没有看到它... - Lumis
1
+1好发现-不知道这个存在-简单-一次裁剪和调整大小! - Dori
4
@DiscDev - 这可能是整个网站中最有用的答案之一。真心话。在 Android 问题上,你经常需要搜索两个小时才能找到简单明显的答案。不知道该如何感谢你。悬赏已发! - Fattie
2
你甚至可以使用一个稍微不同的函数,来回收旧位图:= ThumbnailUtils.extractThumbnail(bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT) - android developer
1
这是我迄今为止看到针对Android问题最强大的答案。我已经看过了20多种不同的解决方案,如何在Android上缩放/裁剪/缩小/调整大小位图等。所有答案都不同。而这个答案只需一行代码即可按预期工作。谢谢您。 - Alex Sorokoletov
显示剩余8条评论

13

你是否考虑过在 layout.xml 文件中完成此操作?你可以将你的 ImageView 设置 ScaleTypeandroid:scaleType="centerCrop",并在 layout.xml 文件中设置该图片在 ImageView 中的尺寸。


我尝试使用以下OpenGLRenderer错误的想法: “位图过大,无法上传到纹理中(2432x4320,最大=4096x4096)”。因此,我猜测无法处理4320高度。 - GregM
1
当然,这是一个正确的答案,完美地回答了问题!优化大图像的图像质量/大小...好吧,这是另一个问题! - ichalos
@ichalos 或许你找到了你想要的,但这并没有回答原来的问题。原来的问题是关于手动在画布上渲染的...实际上,我不确定任何人第一次尝试裁剪照片时会手动在画布上渲染,但是,很高兴你在这里找到了解决方案 :) - milosmns
@milosmns 或许你是正确的。或许这就是用户尝试手动裁剪到中心的问题解决方式。所有一切都取决于原始用户的确切需求。 - ichalos

11

迄今为止可能是最简单的解决方案:

public static Bitmap cropCenter(Bitmap bmp) {
    int dimension = Math.min(bmp.getWidth(), bmp.getHeight());
    return ThumbnailUtils.extractThumbnail(bmp, dimension, dimension);
}

导入:

import android.media.ThumbnailUtils;
import java.lang.Math;
import android.graphics.Bitmap;

9
您可以使用以下代码来解决您的问题。
Matrix matrix = new Matrix();
matrix.postScale(0.5f, 0.5f);
Bitmap croppedBitmap = Bitmap.createBitmap(bitmapOriginal, 100, 100,100, 100, matrix, true);

上述方法是在裁剪之前对图片进行了后缩放,这样您可以在没有发生OOM错误的情况下获得最佳裁剪图像的结果。
如需更多详细信息,请参阅此博客

1
E/AndroidRuntime(30010): 由于4次100像素,导致java.lang.IllegalArgumentException: x + width必须<= bitmap.width()。 - Stan

9
这里有一个更完整的代码片段,可以裁剪任意尺寸的位图中心,并将结果缩放到所需的[IMAGE_SIZE]。因此,您将始终获得一个[croppedBitmap]缩放的图像中心正方形,具有固定大小,非常适合制作缩略图等用途。这是其他解决方案的更完整组合。
final int IMAGE_SIZE = 255;
boolean landscape = bitmap.getWidth() > bitmap.getHeight();

float scale_factor;
if (landscape) scale_factor = (float)IMAGE_SIZE / bitmap.getHeight();
else scale_factor = (float)IMAGE_SIZE / bitmap.getWidth();
Matrix matrix = new Matrix();
matrix.postScale(scale_factor, scale_factor);

Bitmap croppedBitmap;
if (landscape){
    int start = (tempBitmap.getWidth() - tempBitmap.getHeight()) / 2;
    croppedBitmap = Bitmap.createBitmap(tempBitmap, start, 0, tempBitmap.getHeight(), tempBitmap.getHeight(), matrix, true);
} else {
    int start = (tempBitmap.getHeight() - tempBitmap.getWidth()) / 2;
    croppedBitmap = Bitmap.createBitmap(tempBitmap, 0, start, tempBitmap.getWidth(), tempBitmap.getWidth(), matrix, true);
}

3
为了更正 @willsteel 的解决方案:
if (landscape){
                int start = (tempBitmap.getWidth() - tempBitmap.getHeight()) / 2;
                croppedBitmap = Bitmap.createBitmap(tempBitmap, start, 0, tempBitmap.getHeight(), tempBitmap.getHeight(), matrix, true);
            } else {
                int start = (tempBitmap.getHeight() - tempBitmap.getWidth()) / 2;
                croppedBitmap = Bitmap.createBitmap(tempBitmap, 0, start, tempBitmap.getWidth(), tempBitmap.getWidth(), matrix, true);
            }

这是对WillSteel解决方案的修复。在这种情况下,tempBitmap只是原始(未更改)位图的副本,或者是它本身。 - Yman

3
public Bitmap getResizedBitmap(Bitmap bm) {
    int width = bm.getWidth();
    int height = bm.getHeight();

    int narrowSize = Math.min(width, height);
    int differ = (int)Math.abs((bm.getHeight() - bm.getWidth())/2.0f);
    width  = (width  == narrowSize) ? 0 : differ;
    height = (width == 0) ? differ : 0;

    Bitmap resizedBitmap = Bitmap.createBitmap(bm, width, height, narrowSize, narrowSize);
    bm.recycle();
    return resizedBitmap;
}

1
public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
    int w = bitmap.getWidth();
    int h = bitmap.getHeight();
    if (w == size && h == size) return bitmap;
    // scale the image so that the shorter side equals to the target;
    // the longer side will be center-cropped.
    float scale = (float) size / Math.min(w,  h);
    Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
    int width = Math.round(scale * bitmap.getWidth());
    int height = Math.round(scale * bitmap.getHeight());
    Canvas canvas = new Canvas(target);
    canvas.translate((size - width) / 2f, (size - height) / 2f);
    canvas.scale(scale, scale);
    Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
    canvas.drawBitmap(bitmap, 0, 0, paint);
    if (recycle) bitmap.recycle();
    return target;
}

private static Bitmap.Config getConfig(Bitmap bitmap) {
    Bitmap.Config config = bitmap.getConfig();
    if (config == null) {
        config = Bitmap.Config.ARGB_8888;
    }
    return config;
}

1
    val sourceWidth = source.width
    val sourceHeight = source.height
    val xScale = newWidth.toFloat() / sourceWidth
    val yScale = newHeight.toFloat() / sourceHeight
    val scale = xScale.coerceAtLeast(yScale)

    val scaledWidth = scale * sourceWidth
    val scaledHeight = scale * sourceHeight
    val left = (newWidth - scaledWidth) / 2
    val top = (newHeight - scaledHeight) / 2
    val targetRect = RectF(
        left, top, left + scaledWidth, top
                + scaledHeight
    )
    val dest = Bitmap.createBitmap(
        newWidth, newHeight,
        source.config
    )
    val mutableDest = dest.copy(source.config, true)
    val canvas = Canvas(mutableDest)
    canvas.drawBitmap(source, null, targetRect, null)
    binding.imgView.setImageBitmap(mutableDest)

1
请不要只发布代码,而是要稍微解释一下它的运作原理。 - Black Lotus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接