編輯:關於Android編程
計算機圖形表示的原理 加載大圖出現OOM 縮放加載大的圖片資源 創建一個原圖的副本 圖形處理的常用的API 傻瓜版美圖秀秀 畫畫版
首先要明確的一點是,一張圖片的在內存中存儲所需的大小和圖片在屏幕設備上完整顯示所需的內存大小是有非常大的差異的。
如下圖,從中我們可以清晰的看出這張圖片,在硬盤上所占的存儲空間是303KB,也就是310272個字節。但是如果想把這樣圖片完整的展示到屏幕設備上,所需的內存空間遠遠不止這些。
有這樣一個計算公式:圖片展示所需內存 = 圖片的寬度像素 × 圖片的高度像素 × 每個像素的大小<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjwvcD4NCjxwPsTHw7SjrM/xy9i089Ch1PXDtNaqtcDE2KO/tbHO0sPHteO79828xqzB7bTmzqrKsaOsu+Gz9s/Wz8LNvKO6PGJyIC8+DQo8aW1nIGFsdD0="" fhzd_has_check="true" src="/uploadfile/Collfiles/20150319/2015031908453836.png" title="\" />
依次來介紹一下其中的含義。
單色位圖: 要麼黑、要麼白,只需要一bit就可以表示,需要1/8個字節來表示一個像素值。在這種情況下上面的圖片顯示到加載入內存中需要:1920 * 1200 * 1/8 / 1024 / 1024= 0.2M。 16位圖: 需要1/2一個字節來表示一個像素值。在這種情況下圖片加載入內存准備顯示需要:1920 * 1200 * 1 / 2 / 1024 / 1024 = 1.09M。 256位圖: 表示一個像素值需要用1個字節來表示,在這種情況下圖片加載入內存准備顯示需要:1920 * 1200 * 1 / 1024 / 1024 = 2.19M。 24位位圖: 表示一個像素值需要3個字節來表示,在這種情況下圖片加載入內存准備顯示需要:1920 * 1200 * 3 / 1024 / 1024 = 6.59M。 而在Android操作系統中,使用的是ARGB來表示一個像素值 其中A代表的是透明度;那麼在這種情況下,在這種情況下圖片加載入內存准備顯示需要:1920 * 1200 * 4 / 1024 / 1024 = 8.79M。如果在Andorid中,系統為應用默認提供的VM Heap是16M,在不對圖片進行壓縮處理的情況下,一定會出現OOM異常。
讓我們加載一張大圖試試吧,圖片資源如下,大小為1.7M。
創建一個工程,在布局中加一個ImageView
控件,並在MainActivity
中找到控件,設置圖片。
ImageView iv = (ImageView) findViewById(R.id.iv);
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg");
iv.setImageBitmap(bitmap );
代碼很簡單,當運行時就出現了錯誤,讓我們看看錯誤是什麼:
可以看到,應用程序向系統申請了30720012個字節,然後就直接出現了OutOfMemoryError錯誤。
在下一節中將講述如何解決加載大圖而不出現OOM的方法。
我們可以看到,這樣狗狗的圖片是2400*3200的,而我們的手機只是320*480。如果完全直接放上去,一來是會出現異常;二來是浪費資源。
Google工程師已經我們准備好了解決辦法,再使用BitmapFactory
去解析資源時,先獲取被加載圖片的寬高,並結合手機設備的屏幕寬高計算出縮放比例,然後再去使用這個縮放比例加載圖片資源到內存中。
代碼也比較簡單,請看:
ImageView iv = (ImageView) findViewById(R.id.iv);
// ★1. 使用窗口管理者,獲取手機屏幕的寬高
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
int screenHeight = wm.getDefaultDisplay().getHeight();
int screenWidth = wm.getDefaultDisplay().getWidth();
// ★2. 在不把圖片加載入內存的情況下,獲取圖片的屬性和配置
BitmapFactory.Options options = new Options();
// 此參數設置為true是,使用BitmapFactory解析資源並不會返回Bitmap,但是資源的相關配置卻會被設置:例如圖片的寬高
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg", options);
// 拿到圖片的寬高
int outHeight = options.outHeight;
int outWidth = options.outWidth;
// ★3. 根據屏幕寬高和圖片寬高計算出縮放比例
int scale = 0;
int scaleH = outHeight / screenHeight;
int scaleW = outWidth / screenWidth;
scale = scaleH > scaleW ? scaleH : scaleW;
// ★4. 根據縮放比去解析圖片資源,並返回Bitmap
options.inJustDecodeBounds = false;
options.inSampleSize = scale;
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg", options);
iv.setImageBitmap(bitmap);
在Android加載到內存的Bitmap是不允許修改的,只能夠在其副本上修改和作畫。那麼如何創建一個原圖的副本呢?
需要以下這些步驟: ①准備一個和原圖寬高及配置完全一樣的白紙 ②白紙放在畫布上 ③准備一支筆 ④准備一個矩陣代碼也比較簡單,其中涉及了Canvas
、Paint
、Matrix
等類,下面是簡單的拷貝原圖的代碼:
ImageView srcImageView = (ImageView) findViewById(R.id.iv_src);
ImageView copyImageView = (ImageView) findViewById(R.id.iv_copy);
// 原圖
Bitmap srcBitmap = BitmapFactory.decodeFile("/mnt/sdcard/meinv.jpg");
srcImageView.setImageBitmap(srcBitmap);
// 拷貝
// 1. 准備一個和原圖寬高完全一樣的白紙
Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
// 2. 把白紙放在畫布上
Canvas canvas = new Canvas(copyBitmap);
// 3. 准備一支筆
Paint paint = new Paint();
// 4. 准備一個矩陣
Matrix matrix = new Matrix();
// 使用指定的矩陣繪圖
canvas.drawBitmap(srcBitmap, matrix, paint);
copyImageView.setImageBitmap(copyBitmap);
測試結果如下:
就如在學動畫的時候,圖形的處理操作也分為以下種類,操作的關鍵步驟是使用矩陣進行變化;Google工程師已經幫我們把這些操作封裝的很完善了。
平移:代碼如下:
// 原圖
Bitmap srcBitmap = BitmapFactory.decodeFile("/mnt/sdcard/meinv.jpg");
srcImageView.setImageBitmap(srcBitmap);
// 拷貝
Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
Canvas canvas = new Canvas(copyBitmap);
Paint paint = new Paint();
Matrix matrix = new Matrix();
// ★使用矩陣平移圖形
matrix.setTranslate(150, 50);
canvas.drawBitmap(srcBitmap, matrix, paint);
copyImageView.setImageBitmap(copyBitmap);
測試圖如下:
縮放代碼如下:
// ★使用矩陣縮放圖形
// 以圖片中心點為原點,縮小0.5倍
matrix.setScale(0.5f, 0.5f, srcBitmap.getWidth() / 2,
srcBitmap.getHeight() / 2);
測試圖:
旋轉代碼如下:
// ★使用矩陣圖形
// 以圖片中心點為原點,旋轉30度
matrix.setRotate(30, srcBitmap.getWidth()/2,
srcBitmap.getHeight()/2);
測試圖:
鏡像代碼如下:
// ★使用矩陣把圖片鏡像
matrix.setScale(-1.0f, 1.0f);
matrix.postTranslate(srcBitmap.getWidth(), 0);
測試圖:
倒影:
倒影代碼如下:
// ★使用矩陣把圖片倒影
matrix.setScale(1.0f, -1.0f);
matrix.postTranslate(0 , srcBitmap.getHeight());
測試圖:
目的,使用使用顏色矩陣,修改圖片的色值(簡單+簡陋=練練手):
界面布局如下:
布局下方是三個
布局,用於修改圖片的色值。
代碼如下:
public class MainActivity extends Activity implements OnSeekBarChangeListener {
private Bitmap srcBitmap;
private SeekBar sb_red;
private SeekBar sb_green;
private SeekBar sb_blue;
private ImageView iv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
sb_red = (SeekBar) findViewById(R.id.sb_red);
sb_green = (SeekBar) findViewById(R.id.sb_green);
sb_blue = (SeekBar) findViewById(R.id.sb_blue);
iv = (ImageView) findViewById(R.id.iv);
srcBitmap = BitmapFactory.decodeFile("/mnt/sdcard/meinv.jpg");
iv.setImageBitmap(srcBitmap);
sb_blue.setOnSeekBarChangeListener(this);
sb_red.setOnSeekBarChangeListener(this);
sb_green.setOnSeekBarChangeListener(this);
}
// 停止滑動時
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int currentPosition = seekBar.getProgress();
Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
Canvas canvas = new Canvas(copyBitmap);
Paint paint = new Paint();
// ★使用顏色矩陣繪制繪制圖片
ColorMatrix colorMatrix = new ColorMatrix();
float rf = 0;
float gf = 0;
float bf = 0;
// 根據滑動的值修改ARGB的顏色值
switch (seekBar.getId()) {
case R.id.sb_red:
rf = currentPosition / 10f;
break;
case R.id.sb_blue:
bf = currentPosition / 10f;
break;
case R.id.sb_green:
gf = currentPosition / 10f;
break;
}
colorMatrix.set(new float[] { //
rf, 0, 0, 0, 0,//
0, gf, 0, 0, 0,//
0, 0, bf, 0, 0,//
0, 0, 0, 1, 0 });
ColorFilter filter = new ColorMatrixColorFilter(colorMatrix);
// ★為畫筆設置顏色矩陣過濾器
paint.setColorFilter(filter);
canvas.drawBitmap(srcBitmap, new Matrix(), paint);
iv.setImageBitmap(copyBitmap);
}
// 滑動的過程中
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
// 開始滑動時
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
}
測試結果:
沒什麼很重要的東西,知道一下顏色矩陣就行。
先看一些成型後什麼樣子吧:
自己做出來後還是挺逗的。
做畫畫板有以下這些步驟: ①准備一個背景圖 ②做一個背景圖的拷貝,以便在其上面進行繪畫 ③注冊一個ImageView
的一個觸摸事件;在按下和移動時,記錄觸摸點,並繪制圖畫和設置到Imageview
上。 ④增加“加粗畫筆”和“改變顏色的功能”
那麼下面就一步一步來吧
第一步,准備一個背景圖,並存入到SD卡中。
設置背景圖片的代碼:
iv = (ImageView) findViewById(R.id.iv);
srcBitmap = BitmapFactory.decodeFile("/mnt/sdcard/bk.png");
iv.setImageBitmap(srcBitmap);
第二步,做一個背景圖的拷貝,以便在其上面進行繪畫
// 獲取一個拷貝
copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
// 獲取畫布
canvas = new Canvas(copyBitmap);
// 獲取畫筆
paint = new Paint();
// 獲取一個矩陣
matrix = new Matrix();
canvas.drawBitmap(srcBitmap, matrix, paint);
// 畫一條直線
// canvas.drawLine(0, 0, 100, 100, paint);
iv.setImageBitmap(copyBitmap);
// 注冊觸摸事件回調
iv.setOnTouchListener(this);
第三步,注冊一個ImageView
的一個觸摸事件;在按下和移動時,記錄觸摸點,並繪制圖畫和設置到Imageview
上。
// 起始坐標點
private int startX;
private int startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
startY = (int) event.getY();
System.out.println("按下的坐標點(" + startX + "," + startY + ")");
break;
case MotionEvent.ACTION_MOVE:
int nowX = (int) event.getX();
int nowY = (int) event.getY();
System.out.println("移動起始的坐標點(" + startX + "," + startY + ")");
System.out.println("移動結束的坐標點(" + nowX + "," + nowY + ")");
canvas.drawLine(startX, startY, nowX, nowY, paint);
iv.setImageBitmap(copyBitmap);
startX = nowX;
startY = nowY;
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
// 返回值為true,事件被消耗掉,並且不在向下傳遞;
// 返回值為false,事件不被消耗掉,並且向下繼續傳遞;
return true;
}
第四步,增加“加粗畫筆”和“改變顏色的功能”
// 顏色內容
int[] colors = { Color.RED, Color.GREEN, Color.BLUE };
private int colorIndex;
// 修改顏色
public void changecolor(View v) {
paint.setColor(colors[colorIndex++ % 3]);
}
// 畫筆寬度
private int paintWidth;
// 加粗畫筆
public void overstriking(View v) {
paint.setStrokeWidth(++paintWidth);
}
本博客涉及的內容有:多線程並發的性能問題,介紹了AsyncTask,HandlerThread,IntentService與ThreadPool分別適合的使用場景以及各自
SwipeBackLayout其實github上已經有這個開源庫了,我是個菜鳥,我喜歡用開源庫,同時也非常好奇它的實現原理。很多大神寫的代碼注釋都特別少,可能是他們覺得很
寫在前面一家移動互聯網公司,說到底,要盈利總是需要付費用戶的,自己開發支付系統顯然是不明智的,國內已經有多家成熟的移動支付提供商,騰訊就是其中之一。梳理了下微信支付的接入
進入官網(http://opencv.org/)下載OpenCV for android並解壓,不知道什麼原因,嘗試過幾次下載的很慢,時常斷開,後來有一次突然就很順利的下