咱們能夠稱之爲位圖,是一種存儲像素的數據結構,經過這個對象咱們能夠獲取到一系列和圖片相關的屬性, 而且能夠對圖像進行處理,好比切割,放大等等,相關操做。html
bitmap 在內存空間中所佔用的內存計算是這樣的:android
bitmap 的寬 x 高 x 每一個像素所佔的字節c++
其中每一個像素佔用的字節能夠經過Bitmap.Config動態配置算法
Config | 每一個像素佔用的字節 | 說明 |
---|---|---|
ALPHA_8 | 1 bytes | 每一個像素僅僅儲存透明度通道 |
RGB_565 | 2 bytes | 每一個像素的RGB通道會保存,透明度不會保存,紅色通道5位,有2^5 =32種表現形式,綠色通道6位,有2^6 =64種表現形式;藍色通道5位,有2^5=32種表現形式 |
ARGB_4444 | 2 bytes | 每一個像素的ARGB通道都會保存,透明度/紅色/綠色/藍色通道4位,有2^4=16種表現形式 |
ARGB_8888 | 4 bytes | 每一個像素的ARGB通道都會保存,透明度/紅色/綠色/藍色通道8位,有2^8=256種表現形式 |
有什麼區別呢?最簡單的,當一個顏色表現形式越多,那麼畫面總體的色彩就會更豐富,圖片質量就會越高,固然,圖片佔用的儲存空間也越大。canvas
1.文件形式(即以二進制形式存在於硬盤上)瀏覽器
2.流的形式(即以二進制形式存在於內存中)數據結構
3.Bitmap形式ide
這三種形式的區別: 文件形式和流的形式對圖片體積大小並無影響,也就是說,若是你手機SD卡上的若是是100K,那麼經過流的形式讀到內存中,也必定是佔100K的內存,注意是流的形式,不是Bitmap的形式,當圖片以Bitmap的形式存在時,其佔用的內存會瞬間變大, 我作分享的時候一個9.9M的圖片保存在手機相冊中顯示是238KB,80M的圖片在手機相冊中顯示是1.24M。函數
在Android的開發中,咱們常常回去處理一些圖片相關的問題,好比當加載圖片到內存中產生的OOM(OutOfMemory)異常、圖片太大壓縮形成失真,圖片不顯示,圖片壓縮以後出現黑色,分享圖片漸變色出現色塊等。工具
bitmap 的寬 x 高 x 每一個像素所佔的字節 從這個公式能夠看出想要壓縮圖片大小有兩種方式:
bitmap.compress(CompressFormat format, int quality, OutputStream stream);
圖片格式 | 說明 |
---|---|
PNG | 它是一種無損數據壓縮位圖圖形文件格式。這也就是說PNG 只支持無損壓縮。對於PNG 格式是有8 位、24位、32位的三種形式的。區別就是對透明度的支持。 |
JPG | 其實就是 JPEG的另外一種叫法 |
JPEG | 它是一種有損壓縮的圖片格式 |
WEBP | Google 開發出的一種支持alpha 通道的有損壓縮和無損壓縮。同等質量狀況下比 JPEG和PNG小 25%~45%.WebP格式圖像的編碼時間「比JPEG格式圖像長8倍」(佔用cpu,節省內存 |
GIF | 它是動態圖片的一種格式,和PNG 同樣是一種無損壓縮。 |
SVG | 是一種無損、矢量圖(放大不失真) |
圖片的格式有不少種,除了咱們熟知的 JPG、PNG、GIF,還有Webp,BMP,TIFF,CDR 等等幾十種,用於不一樣的場景或平臺。
這些格式能夠分爲兩大類:有損壓縮和無損壓縮。
1.有損壓縮:是對圖像數據進行處理,去掉那些圖像上會被人眼忽略的細節,而後使用附近的顏色經過漸變或其餘形式進行填充。這樣既能大大下降圖像信息的數據量,又不會影響圖像的還原效果。最典型的有損壓縮格式是 jpg。
2.無損壓縮:先判斷圖像上哪些區域的顏色是相同的,哪些是不一樣的,而後把這些相同的數據信息進行壓縮記錄,(例如一片藍色的天空之須要記錄起點和終點的位置就能夠了),而把不一樣的數據另外保存(例如天空上的白雲和漸變等數據)。常見的無損壓縮格式是 png,gif。
Android 原生支持的格式只有 JPEG、PNG、GIF、WEBP(android 4.0 加入)、BMP。而在android層代碼中咱們只能調用的編碼方式只有PNG、JPEG、和WEBP 三種。不過目前來講android 還不支持對GIF 這種的動態編碼。
注意 :咱們平常全部的 .png、.jpg、.jpeg 等等指的是圖像數據通過壓縮編碼後在媒體上的封存形式,是不能和PNG 、JPG、JEPG 混爲一談的。
經過此種方式,圖片的大小是沒有變的,由於質量壓縮不會減小圖片的像素,它是在保持像素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的,這也是爲何該方法叫質量壓縮方法。圖片的長,寬,像素都不變,那麼bitmap所佔內存大小是不會變的。
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; //inSampleSize 爲壓縮比 此處爲1/2
bm = BitmapFactory.decodeFile("/DCIM/Camera/test.jpg", options);
採樣前:
採樣後:
接着咱們來看看 inSampleSzie 的官方描述:
If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.
複製代碼
從官方的inSampleSzie描述看咱們能夠看到 x(x 爲 2 的倍數)個像素最後對應一個像素,因爲採樣率設置爲 1/2,因此是兩個像素生成一個像素。鄰近採樣的方式比較粗暴,直接選擇其中的一個像素做爲生成像素,另外一個像素直接拋棄,這樣就形成了圖片變成了純綠色,也就是紅色像素被拋棄。
1.Matrix matrix = new Matrix(); matrix.setScale(0.5f, 0.5f); bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),bit.getHeight(), matrix, true);
2.Bitmap.createScaledBitmap(bitmapOld, 150, 150, true);
採樣前:
採樣後:
能夠看處處理以後的圖片不是像鄰近採樣同樣純粹的一種顏色,而是兩種顏色的混合。雙線性採樣使用的是雙線性內插值算法,這個算法不像鄰近點插值算法同樣,直接粗暴的選擇一個像素,而是參考了源像素相應位置周圍 2x2 個點的值,根據相對位置取對應的權重,通過計算以後獲得目標圖像。
鄰近採樣的方式是最快的,由於它直接選擇其中一個像素做爲生成像素,可是生成的圖片可能會相對比較失真,產生比較明顯的鋸齒,最具備表明性的就是處理文字比較多的圖片在展現效果上的差異,對比:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; //將格式設置成RGB_565 bm = BitmapFactory.decodeFile( "/DCIM/Camera/test.jpg", options);
注意:因爲ARGB_4444的畫質慘不忍睹,通常假如對圖片沒有透明度要求的話,能夠改爲RGB_565,相比ARGB_8888將節省一半的內存開銷。
複製代碼
若是咱們要加載的圖片遠遠大於ImageView的大小,直接用ImageView去展現的話,就會帶來很差的視覺效果,也會佔用太多的內存和性能開銷。甚至這張圖片足夠大到致使程序oom崩潰
1.壓縮
2.局部展現
有時候咱們經過壓縮能夠取得很好的效果,但有時候效果就不那麼美好了,例如長圖像清明上河圖,像這類的長圖,若是咱們直接壓縮展現的話,這張圖徹底看不清,很影響體驗。這時咱們就能夠採用局部展現,而後滑動查看的方式去展現圖片。
public class LargeImageView extends View implements GestureDetector.OnGestureListener {
int bitmapLeft;
Paint paint;
private BitmapRegionDecoder mDecoder;
/**
* 繪製的區域
*/
public volatile Rect mRect = new Rect();
// private int mScaledTouchSlop;
// 分別記錄上次滑動的座標
private int mLastX = 0;
private int mLastY = 0;
/**
* 圖片的寬度和高度
*/
public int mImageWidth, mImageHeight;
private GestureDetector mGestureDetector;
private BitmapFactory.Options options;
public LargeImageView(Context context) {
this(context, null);
}
public LargeImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LargeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
// mScaledTouchSlop = ViewConfiguration.get(getContext())
// .getScaledTouchSlop();
//初始化手勢控制器
mGestureDetector = new GestureDetector(context, this);
paint = new Paint();
paint.setColor(Color.TRANSPARENT);
paint.setAntiAlias(true);
}
/**
* setInputStream裏面去得到圖片的真實的寬度和高度,以及初始化咱們的mDecoder。
*
* @param is
*/
public void setInputStream(InputStream is) {
try {
mDecoder = BitmapRegionDecoder.newInstance(is, false);
BitmapFactory.Options bfOptions = new BitmapFactory.Options();
//設置爲true後。不加載到內存就能獲取Bitmap尺寸大小。
bfOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, bfOptions);
mImageWidth = mDecoder.getWidth();
mImageHeight = mDecoder.getHeight();
requestLayout();
invalidate();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
//把觸摸事件交給手勢控制器處理
return mGestureDetector.onTouchEvent(ev);
}
@Override
public boolean onDown(MotionEvent e) {
mLastX = (int) e.getRawX();
mLastY = (int) e.getRawY();
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
int x = (int) e2.getRawX();
int y = (int) e2.getRawY();
move(x, y);
return true;
}
/**
* 移動的時候更新圖片顯示的區域
*
* @param x
* @param y
*/
private void move(int x, int y) {
boolean isInvalidate = false;
int deltaX = x - mLastX;
int deltaY = y - mLastY;
//若是圖片寬度大於屏幕寬度
if (mImageWidth > getWidth()) {
//移動rect區域
mRect.offset(-deltaX, 0);
//檢查是否到達圖片最右端
if (mRect.right > mImageWidth) {
mRect.right = mImageWidth;
mRect.left = mImageWidth - getWidth();
}
//檢查左端
if (mRect.left < 0) {
mRect.left = 0;
mRect.right = getWidth();
}
isInvalidate = true;
}
//若是圖片高度大於屏幕高度
if (mImageHeight > getHeight()) {
mRect.offset(0, -deltaY);
//是否到達最底部
if (mRect.bottom > mImageHeight) {
mRect.bottom = mImageHeight;
mRect.top = mImageHeight - getHeight();
}
if (mRect.top < 0) {
mRect.top = 0;
mRect.bottom = getHeight();
}
isInvalidate = true;
}
if (isInvalidate) {
invalidate();
}
mLastX = x;
mLastY = y;
}
@Override
public void onLongPress(MotionEvent e) {
mLastX = (int) e.getRawX();
mLastY = (int) e.getRawY();
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
int x = (int) e2.getRawX();
int y = (int) e2.getRawY();
move(x, y);
return true;
}
@SuppressLint("CheckResult")
@Override
protected void onDraw(final Canvas canvas) {
//顯示圖片
if (null != mDecoder) {
Bitmap bm = mDecoder.decodeRegion(mRect, options);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.drawBitmap(bm, bitmapLeft, 0, null);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
bitmapLeft = width / 2 - mImageWidth / 2;//圖片距離左邊大小
mRect.left = 0;
mRect.top = 0;
mRect.right = mImageWidth;
mRect.bottom = mRect.top + height;
}
}
複製代碼
根據上述源碼:
在setInputStream方法裏面初始BitmapRegionDecoder,獲取圖片的實際寬高; onMeasure方法裏面給Rect賦初始化值,控制開始顯示的圖片區域; onTouchEvent監聽用戶手勢,修改Rect參數來修改圖片展現區域,而且進行邊界檢測,最後invalidate; 在onDraw裏面根據Rect獲取Bitmap而且繪製。
Skia 是一個 Google 本身維護的 c++ 實現的圖像引擎,實現了各類圖像處理功能,而且普遍地應用於谷歌本身和其它公司的產品中(如:Chrome、Firefox、 Android等),基於它能夠很方便爲操做系統、瀏覽器等開發圖像處理功能。
Skia 在 Android 中提供了基本的畫圖和簡單的編解碼功能,能夠掛接其餘的第三方編碼解碼庫或者硬件編解碼庫,例如 libpng 和 libjpeg,libgif 等等。所以,這個函數調用bitmap.compress(Bitmap.CompressFormat.JPEG...),實際會調用 libjpeg.so 動態庫進行編碼壓縮。
最終 Android 編碼保存圖片的邏輯是 Java 層函數→Native 函數→Skia函數→對應第三庫函數(例如 libjpeg)。因此 skia 就像一個膠水層,用來連接各類第三方編解碼庫,不過 Android 也會對這些庫作一些修改,好比修改內存管理的方式等等。
Android 在以前從某種程度來講使用的算是 libjpeg 的功能閹割版,壓縮圖片默認使用的是 standard huffman,而不是 optimized huffman,也就是說使用的是默認的哈夫曼表,並無根據實際圖片去計算相對應的哈夫曼表,Google 在初期考慮到手機的性能瓶頸,計算圖片權重這個階段很是佔用 CPU 資源的同時也很是耗時,由於此時須要計算圖片全部像素 argb 的權重,這也是 Android 的圖片壓縮率對比 iOS 來講差了一些的緣由之一。