如何为你的Android应用缩放图片[原创翻译]

很难为你的应用程序获得正确的图像缩放吗?是你的图片过大,形成内存问题?仍是图片不正确缩放形成不良用户体验的结果?为了寻求一个好的解决方案,咱们咨询了Andreas Agvard(索尼爱立信软件部门),让他分享一些关于这方面的经验。css

注意:本文没有完整显示出代码示例。你能够下载本文的PDF,来看完整的代码示例。java

在索尼爱立信软件部门工做,我常常遇到须要图片缩放的应用,例如:当处理别人或者网络上提供的图片。缩放是必要的,由于一般状况下的图片不是你想要呈现的那样。android

典型的例子,若是你正在为你的应用开发一个LiveView™扩展。大多数人开发应用利用LiveView™和其余第二屏幕设备,可能须要从新调整图片,重要的是要保持适当的缩放比例和图像质量。固然,在不少状况下,改变图片尺寸是一个有点困难,可是颇有效的途径。canvas

ImageView解决了许多的图片缩放问题,首先,至少你在设置完一个图片源后,不用去解码或缩放图片。但有时须要你本身去解码控制,这是本教程的用武之地。随着本教程,我写了一个代码示例,下载图片缩放代码示例。在文本中呈现的效果,能够经过编译和运行该项目来看到。网络

孤立的问题
我作这个教程,是由于我已经有一些实用方法来实现图片的缩放,为了不最多见的图片缩放问题。以下面的例子:性能

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

那么在上面的代码中,什么是正确的,什么是错的?让咱们来看看在不一样的代码行。.net

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

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

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

  • 源图像的尺寸和想要的图像尺寸在长宽比上多是不同的。这将致使图像的拉伸

左边的图片:原始图像。右边的图片:缩放后图片。能够看出明显的失真问题,如原图的眼睛很是的鲜明,缩放后就没有了。高度出现拉伸。blog

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

替换行2
在这一部分,咱们将缩放位图到咱们所须要的。这一步很必要,由于以前的解码能力是有限的。此外,在这一步为了不拉伸,咱们可能要从新调整图片到想要的大小。

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

左边的图片:图像经过fit方法缩放。图片已被缩小到适合的尺寸和高度,结果是小于想要的高度。右边的图像:图像crop方法缩放。图像已被缩放到适应至少想要的尺寸。所以原图已被裁剪,切割了成左边和右边二部分。

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

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);

左边的图像:原始解决方案,解码消耗6693 KB的内存和1/4秒左右。结果被拉长失真。中间的图像:同比缩放解决方案,解码消耗418 KB的内存和1/10秒左右。右边的图像:裁剪解决方案,解码消耗418 KB的内存和1/10秒左右。

想要了解更多信息,请下载咱们的代码示例。有了这个源码项目,你能够看到你的Android手机上运行的结果。

相关文章
相关标签/搜索