1. 程式人生 > >一張圖片佔多大記憶體的計算-android

一張圖片佔多大記憶體的計算-android

DisplayMetrics 的兩個變數,摘錄官方文件的解釋:

    density:The logical density of the display. This is a scaling factor for the Density Independent Pixel unit, where one DIP is one pixel on an approximately 160 dpi screen (for example a 240x320, 1.5”x2” screen), providing the baseline of the system’s display. Thus on a 160dpi screen this density value will be 1; on a 120 dpi screen it would be .75; etc.This value does not exactly follow the real screen size (as given by xdpi and ydpi, but rather is used to scale the size of the overall UI in steps based on gross changes in the display dpi. For example, a 240x320 screen will have a density of 1 even if its width is 1.8”, 1.3”, etc. However, if the screen resolution is increased to 320x480 but the screen size remained 1.5”x2” then the density would be increased (probably to 1.5).
    densityDpi:The screen density expressed as dots-per-inch.

可以理解為 density 的數值是 1dp=density px;densityDpi 是螢幕每英寸對應多少個點(不是畫素點),在 DisplayMetrics 當中,這兩個的關係是線性的:

density 1 1.5 2 3 3.5 4
densityDpi 160 240 320 480 560 640

 >>> Android中有四種,分別是:
ALPHA_8:  每個畫素佔用1byte記憶體
ARGB_4444:每個畫素佔用2byte記憶體
ARGB_8888:每個畫素佔用4byte記憶體
RGB_565:     每個畫素佔用2byte記憶體

注:ARGB指的是一種色彩模式,裡面A代表Alpha,R表示red,G表示green,B表示blue,其實所有的可見色都是紅綠藍組成的,所以紅綠藍又稱為三原色。

簡單點說

 佔用記憶體的計算方向

 一張100*100的圖片佔用記憶體的大小

 ALPHA_8

 圖片長度*圖片寬度

 100*100=10000位元組

 ARGB_4444

 圖片長度*圖片寬度*2

 100*100*2=20000位元組

 ARGB_8888

 圖片長度*圖片寬度*4

 100*100*4=40000位元組

 RGB_565 

 圖片長度*圖片寬度*2

 100*100*2=20000位元組

Bitmap.Config  ARGB_4444,ARGB  分別佔四位
Bitmap.Config  ARGB_8888,ARGB  分別佔八位
Bitmap.Config  RGB_565  ,沒有透明度(A)   R佔5位   G 佔6位   B佔5位 。

> Bitmap 在記憶體當中佔用的大小其實取決於:
 1.色彩格式,前面我們已經提到,如果是 ARGB8888 那麼就是一個畫素4個位元組,如果是 RGB565 那就是2個位元組
 2.原始檔案存放的資源目錄(是 hdpi 還是 xxhdpi 可不能傻傻分不清楚哈)
 3.目標螢幕的密度(所以同等條件下,紅米在資源方面消耗的記憶體肯定是要小於三星S6的)

 jpg 是一種有失真壓縮的圖片儲存格式,而 png 則是 無失真壓縮的圖片儲存格式,顯而易見,jpg 會比 png 小,代價也是顯而易見的。
 拋開 Android 這個平臺不談,從出圖的角度來看的話,jpg 格式的圖片大小也不一定比 png 的小,這要取決於影象資訊的內容:
JPG 不適用於所含顏色很少、具有大塊顏色相近的區域或亮度差異十分明顯的較簡單的圖片。對於需要高保真的較複雜的影象,PNG 雖然能無失真壓縮,但圖片檔案較大。

 如果僅僅是為了 Bitmap 讀到記憶體中的大小而考慮的話,jpg 也好 png 也好,沒有什麼實質的差別;二者的差別主要體現在:
  1.alpha 你是否真的需要?如果需要 alpha 通道,那麼沒有別的選擇,用 png。
  2.你的圖色值豐富還是單調?就像剛才提到的,如果色值豐富,那麼用jpg,如果作為按鈕的背景,請用 png。
  3.對安裝包大小的要求是否非常嚴格?如果你的 app 資源很少,安裝包大小問題不是很凸顯,看情況選擇 jpg 或者 png(不過,我想現在對資原始檔沒有苛求的應用會很少吧。。)
  4.目標使用者的 cpu 是否強勁?jpg 的影象壓縮演算法比 png 耗時。這方面還是要酌情選擇,前幾年做了一段時間 Cocos2dx,由於資源非常多,專案組要求統一使用 png,可能就是出於這方面的考慮。

 -- 在我們的例子中

scaledWidth = int( 522 * 640 / 480f + 0.5) = int(696.5) = 696

scaledHeight = int( 686 * 640 / 480f + 0.5) = int(915.16666…) = 915

下面就是見證奇蹟的時刻:
915 * 696 * 4 = 2547360 bit

ARGB8888格式的圖片,每畫素佔用 4 Byte,而 RGB565則是 2 Byte。我們先看下有多少種格式可選:

格式 描述
ALPHA_8 只有一個alpha通道
ARGB_4444 這個從API 13開始不建議使用,因為質量太差
ARGB_8888 ARGB四個通道,每個通道8bit
RGB_565 每個畫素佔2Byte,其中紅色佔5bit,綠色佔6bit,藍色佔5bit

--- 獲取圖片佔用記憶體的大小:原始碼跟蹤,java-->JNI --> C++

public final int getByteCount() {
    // int result permits bitmaps up to 46,340 x 46,340
    return getRowBytes() * getHeight();
}

public final int getrowBytes() {
   if (mRecycled) {
          Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
   }
   return nativeRowBytes(mFinalizer.mNativeBitmap);
}

static jint Bitmap_rowBytes(JNIEnv* env, jobject, jlong bitmapHandle) {
     SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle)
     return static_cast<jint>(bitmap->rowBytes());
}
size_t SkBitmap::ComputeRowBytes(Config c, int width) {
    return SkColorTypeMinRowBytes(SkBitmapConfigToColorType(c), width);
}


SkImageInfo.h
static int SkColorTypeBytesPerPixel(SkColorType ct) {
   static const uint8_t gSize[] = {
    0,  // Unknown
    1,  // Alpha_8
    2,  // RGB_565
    2,  // ARGB_4444
    4,  // RGBA_8888
    4,  // BGRA_8888
    1,  // kIndex_8

  };
  SK_COMPILE_ASSERT(SK_ARRAY_COUNT(gSize) == (size_t)(kLastEnum_SkColorType + 1),
                size_mismatch_with_SkColorType_enum);
 
   SkASSERT((size_t)ct < SK_ARRAY_COUNT(gSize));
   return gSize[ct];
}
 
static inline size_t SkColorTypeMinRowBytes(SkColorType ct, int width) {
    return width * SkColorTypeBytesPerPixel(ct);
}

跟蹤到這裡,我們發現 ARGB_8888(也就是我們最常用的 Bitmap 的格式)的一個畫素佔用 4byte,那麼 rowBytes 實際上就是 4*width bytes。結論出來了,一張 ARGB_8888 的 Bitmap 佔用記憶體的計算公式.

我們讀取的是 drawable 目錄下面的圖片,用的是 decodeResource 方法,該方法本質上就兩步:
  1.讀取原始資源,這個呼叫了 Resource.openRawResource 方法,這個方法呼叫完成之後會對 TypedValue 進行賦值,其中包含了原始資源的 density 等資訊;
  2.呼叫 decodeResourceStream 對原始資源進行解碼和適配。這個過程實際上就是原始資源的 density 到螢幕 density 的一個對映。

BitmapFactory.java
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
    InputStream is, Rect pad, Options opts) {
 
//實際上,我們這裡的opts是null的,所以在這裡初始化。
if (opts == null) {
    opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
    final int density = value.density;
    if (density == TypedValue.DENSITY_DEFAULT) {
        opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
    } else if (density != TypedValue.DENSITY_NONE) {
        opts.inDensity = density; //這裡density的值如果對應資源目錄為hdpi的話,就是240
    }
}
if (opts.inTargetDensity == 0 && res != null) {
//請注意,inTargetDensity就是當前的顯示密度,比如三星s6時就是640
    opts.inTargetDensity = res.getDisplayMetrics().densityDpi;

return decodeStream(is, pad, opts);
}

我們看到 opts 這個值被初始化,而它的構造居然如此簡單:
public Options() {
   inDither = false;
   inScaled = true;
   inPremultiplied = true;
}
---------------------------------------------------
BitmapFactory.cpp
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
......
    if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
        const int density = env->GetIntField(options, gOptions_densityFieldID);//對應hdpi的時候,是240
        const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);//三星s6的為640
        const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
        if (density != 0 && targetDensity != 0 && density != screenDensity) {
            scale = (float) targetDensity / density;
        }
    }
}
 
const bool willScale = scale != 1.0f;
......
SkBitmap decodingBitmap;
if (!decoder->decode(stream, &decodingBitmap, prefColorType,decodeMode)) {
   return nullObjectReturn("decoder->decode returned false");
}
//這裡這個deodingBitmap就是解碼出來的bitmap,大小是圖片原始的大小
int scaledWidth = decodingBitmap.width();
int scaledHeight = decodingBitmap.height();
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}
if (willScale) {
    const float sx = scaledWidth / float(decodingBitmap.width());
    const float sy = scaledHeight / float(decodingBitmap.height());
 
    // TODO: avoid copying when scaled size equals decodingBitmap size
    SkColorType colorType = colorTypeForScaledOutput(decodingBitmap.colorType());
    // FIXME: If the alphaType is kUnpremul and the image has alpha, the
    // colors may not be correct, since Skia does not yet support drawing
    // to/from unpremultiplied bitmaps.
    outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
            colorType, decodingBitmap.alphaType()));
    if (!outputBitmap->allocPixels(outputAllocator, NULL)) {
        return nullObjectReturn("allocation failed for scaled bitmap");
    }
 
    // If outputBitmap's pixels are newly allocated by Java, there is no need
    // to erase to 0, since the pixels were initialized to 0.
    if (outputAllocator != &javaAllocator) {
        outputBitmap->eraseColor(0);
    }
 
    SkPaint paint;
    paint.setFilterLevel(SkPaint::kLow_FilterLevel);
 
    SkCanvas canvas(*outputBitmap);
    canvas.scale(sx, sy);
    canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
}
......
}
  注意到其中有個 density 和 targetDensity,前者是 decodingBitmap 的 density,這個值跟這張圖片的放置的目錄有關(比如 hdpi 是240,xxhdpi 是480),這部分程式碼我跟了一下,太長了,就不列出來了;targetDensity 實際上是我們載入圖片的目標 density,這個值的來源我們已經在前面給出了,就是 DisplayMetrics 的 densityDpi,如果是三星s6那麼這個數值就是640。sx 和sy 實際上是約等於 scale 的,因為 scaledWidth 和 scaledHeight 是由 width 和 height 乘以 scale 得到的。我們看到 Canvas 放大了 scale 倍,然後又把讀到記憶體的這張 bitmap 畫上去,相當於把這張 bitmap 放大了 scale 倍。

outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
            colorType, decodingBitmap.alphaType()));

我們看到最終輸出的 outputBitmap 的大小是scaledWidth*scaledHeight,我們把這兩個變數計算的片段拿出來給大家一看就明白了:
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
    scaledWidth = int(scaledWidth * scale + 0.5f);
    scaledHeight = int(scaledHeight * scale + 0.5f);
}

-- 想辦法減少 Bitmap 記憶體佔用: Jpg 和 Png;使用 inSampleSize;使用矩陣Matrix;合理選擇Bitmap的畫素格式;高能:索引點陣圖(Indexed Bitmap);
public enum Config {
    // these native values must match up with the enum in SkBitmap.h
 
    ALPHA_8     (2),
    RGB_565     (4),
    ARGB_4444   (5),
    ARGB_8888   (6);
 
    final int nativeInt;
}


研究一下 Skia 引擎
bool SkPNGImageDecoder::getBitmapColorType(png_structp png_ptr, png_infop info_ptr,
                                       SkColorType* colorTypep,
                                       bool* hasAlphap,
                                       SkPMColor* SK_RESTRICT theTranspColorp) {
png_uint_32 origWidth, origHeight;
int bitDepth, colorType;
png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
             &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
 
#ifdef PNG_sBIT_SUPPORTED
  // check for sBIT chunk data, in case we should disable dithering because
  // our data is not truely 8bits per component
  png_color_8p sig_bit;
  if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
#if 0
    SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
             sig_bit->blue, sig_bit->alpha);
#endif
    // 0 seems to indicate no information available
    if (pos_le(sig_bit->red, SK_R16_BITS) &&
        pos_le(sig_bit->green, SK_G16_BITS) &&
        pos_le(sig_bit->blue, SK_B16_BITS)) {
        this->setDitherImage(false);
    }
}
#endif
 
 
if (colorType == PNG_COLOR_TYPE_PALETTE) {
    bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
    *colorTypep = this->getPrefColorType(kIndex_SrcDepth, paletteHasAlpha);
    // now see if we can upscale to their requested colortype
    //這段程式碼,如果返回false,那麼colorType就被置為索引了,那麼我們看看如何返回false
    if (!canUpscalePaletteToConfig(*colorTypep, paletteHasAlpha)) {
        *colorTypep = kIndex_8_SkColorType;
    }
} else {
...... 
}
return true;

轉載地址:http://blog.csdn.net/theone10211024/article/details/50801099