为了避免最常见的图片缩放问题。如下面的例子:

Bitmap unscaledBitmap = BitmapFactory.decodeResource(getResources(), mSourceId); 
Bitmap scaledBitmap = Bitmap.createScaledBitmap(unscaledBitmap, wantedWidth, wantedHeight, true);

那么在上面的代码中,什么是正确的,什么是错的?让我们来看看在不同的代码行。

1:整个源图像解码到一个位图。

  • 这可能会导致内存不足的错误,如果图片太大的话。
  • 这可能会导致在一个高分辨率上解码图像。这可能会很慢,但智能解码器可为解码提高性能。
  • 缩放图片很多时候是,高分辨率位图缩放到低分辨率,会导致锯齿的问题。使用位图过滤(例如,通过传送`true`参数到Bitmap.createScaledBitmap(...))减少了锯齿,但是还是不够。

2:解码的位图缩放到想要的大小。

  • 源图像的尺寸和想要的图像尺寸在长宽比上可能是不一样的。这将导致图像的拉伸

创建一个解决方案

我们的解决方案,将有一个结构类似上述代码,其中的一部分将取代行1,这样为缩放做准备。另一部分将取代行2,做最后的缩放。我们将开始替换行2的部分代码,引入两个新的概念,裁剪合适

替换行2

在这一部分,我们将缩放位图到我们所需要的。这一步很必要,因为之前的解码能力是有限的。此外,在这一步为了避免拉伸,我们可能要重新调整图片到想要的大小。

有两种可能性可以避免拉伸。不管是那种,我们都要调整尺寸,以确保他们有相同的宽高比;即缩放图像作为源图像,直到它适合想要的尺寸,或裁剪具有相同的宽高比的源图像为想要的尺寸。

为了缩放这样的效果,我们的实现代码如下:

public static Bitmap createScaledBitmap(Bitmap unscaledBitmap, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { 
  Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic); 
  Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic); 
  Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(), Config.ARGB_8888); 
  Canvas canvas = new Canvas(scaledBitmap); 
  canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG));return scaledBitmap; 
  }

在上面的代码,我们使用canvas.drawBitmap(...)做缩放。这种方法的裁剪区域是从源图像的规模面积定义画布的矩形为指定的目标矩形区域。为了避免拉伸,这两个矩形需要有相同的长宽比。我们还调用了两个实用的方法,一个为创建源矩形和另一个为创建目标矩形。方法如下:

public static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { 
  if (scalingLogic == ScalingLogic.CROP) { 
    final float srcAspect = (float)srcWidth / (float)srcHeight; 
    final float dstAspect = (float)dstWidth / (float)dstHeight; 
    if (srcAspect > dstAspect) { 
      final int srcRectWidth = (int)(srcHeight * dstAspect); 
      final int srcRectLeft = (srcWidth - srcRectWidth) / 2; 
      return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight); 
    } else { 
      final int srcRectHeight = (int)(srcWidth / dstAspect); 
      final int scrRectTop = (int)(srcHeight - srcRectHeight) / 2; 
      return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight); 
    } 
  } else { 
    return new Rect(0, 0, srcWidth, srcHeight); 
  } 
} 
public static Rect calculateDstRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { 
  if (scalingLogic == ScalingLogic.FIT) { 
    final float srcAspect = (float)srcWidth / (float)srcHeight; 
    final float dstAspect = (float)dstWidth / (float)dstHeight; 
    if (srcAspect > dstAspect) { 
      return new Rect(0, 0, dstWidth, (int)(dstWidth / srcAspect)); 
    } else { 
      return new Rect(0, 0, (int)(dstHeight * srcAspect), dstHeight); 
    } 
  } else { 
    return new Rect(0, 0, dstWidth, dstHeight); 
  } 
}

在刚好合适的情况下源矩形会包含整个源尺寸。在需要裁剪的情况下,它会计算好具有相同宽高比的目标图像,来裁剪源图像的宽度或高度,以达到你想要的尺寸。而刚好在合适的情况下,将有相同宽高比的源图像,调整成你想要的尺寸的宽度或高度。

 

替换行1
解码器很智能,特别是用于JPEG和PNG的格式。这些解码器在图片解码时可以进行缩放,并且性能也有所改善,这样锯齿问题也可以避免。此外,由于图片解码后变小了,需要的内存也会较少。

缩放解码的时候,只要简单设置上BitmapFactory.Options对象的inSampleSize参数,并把它传递给BitmapFactory。样本大小指定一个缩放图像大小的抽象因素,例如2是640×480图像在320×240图像上解码的因素。样本大小设置时,你不能保证严格按照这个数字,图像将被缩减,但至少它不会更小。例如,3倍640×480的图像可能会导致在一个320×240图像不支持值。通常情况下,至少2的一次方支持[1,2,4,8,...]。

下一步是指定一个合适的样本大小。合适的样本大小将产生最大的缩放,但仍然是大于等于你想要的图像尺寸。如下面代码:

public static Bitmap decodeFile(String pathName, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { 
  Options options = new Options(); 
  options.inJustDecodeBounds = true; 
  BitmapFactory.decodeFile(pathName, options); 
  options.inJustDecodeBounds = false; 
  options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, dstWidth, dstHeight, scalingLogic); 
  Bitmap unscaledBitmap = BitmapFactory.decodeFile(pathName, options); 
  return unscaledBitmap; 
} 
public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) { 
  if (scalingLogic == ScalingLogic.FIT) { 
    final float srcAspect = (float)srcWidth / (float)srcHeight; 
    final float dstAspect = (float)dstWidth / (float)dstHeight; 
    if (srcAspect > dstAspect) { 
      return srcWidth / dstWidth; 
    } else { 
      return srcHeight / dstHeight; 
    } 
  } else { 
    final float srcAspect = (float)srcWidth / (float)srcHeight; 
    final float dstAspect = (float)dstWidth / (float)dstHeight; 
    if (srcAspect > dstAspect) { 
      return srcHeight / dstHeight; 
    } else { 
      return srcWidth / dstWidth; 
    } 
  } 
}

在decodeFile(...)方法中,我们解码一个文件进行了最终缩放尺度。这是首先要通过解码源图片尺寸,然后使用calculateSampleSize(...)计算最佳样本大小,最后使用此样本的大小解码图像。如果你有兴趣的话,你可以更深入了解calculateSampleSize(...)方法,但以上方法基本可确保图片进行缩放。

全部放在一起
根据上面我们指定的方法的,现在可以执行替换最初的代码行:

Bitmap unscaledBitmap = decodeFile(pathname, dstWidth, dstHeight, scalingLogic); Bitmap scaledBitmap = createScaledBitmap(unscaledBitmap, dstWidth, dstHeight, scalingLogic);

 

发布评论

分享到:

IT源码网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

Drawable转换为Bitmap两种方法讲解
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。