如何爲你的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手機上運行的結果。

相關文章
相關標籤/搜索