系列第一篇,從簡單的開始,一步一步完成這個小項目。
顏色獲取就是通過分析圖片中的每個像素的顏色,來分析整個圖片的主調顏色,有了主調顏色,我們可以用於圖片所在卡片的背景或者標題顏色,這樣整體感更加強烈。
有興趣的可以學習下使用谷歌提供的Palette,也是做同樣的工作,博客地址:http://www.cnblogs.com/Fndroid/p/5201236.html
先看效果圖:
分析原理比較簡單,就是獲取圖片的所有像素的顏色,然后統計,把統計的數目排序,然后返回給用戶。
但是這里要先注意幾個問題:
① 獲取顏色的過程會不會導致UI線程卡頓;
② 怎么實現排序(如何優化后面研究);
因為考慮到當圖片像素較多的時候,分析可能不會馬上完成,所以分析過程應該在子線程中完成,避免阻塞UI線程。其次,統計和排序都可以通過Java提供的數據結構來簡單實現,暫且不考慮性能因素,實現功能為先。
① 在Android中,圖片一般會用Bitmap來表示,而Bitmap中有一個叫做getPixels的方法:
public void getPixels(@ColorInt int[] pixels, int offset, int stride, int x, int y, int width, int height)
參數分別是:
pixiels:存放識別出顏色的數組
offset:數組的起始下標
stride:每行的數據量,比如一下張200*200的圖片,在stride設置為200的時候,pixels[200]為第二行的第一個像素顏色,當stride設置為400的時候,pixels[200]為額外的信息,不包含顏色,而第二行的第一個像素的顏色應該在pixels[400]
x、y:開始的x和y
width、height:需要獲取顏色的寬度和高度,和x、y構成一個矩形
② 獲取完顏色之后,對顏色進行統計,因為得到了一個數組,所以對數組進行統計,如果有相同的要加一,得出每個顏色出現的次數,算法簡單如下
HashMap<Integer, Integer> colors = new HashMap<>(); for (int pixel : pixels) { Integer num = colors.get(pixel); if (num == null) { colors.put(pixel, 1); } else { num += 1; colors.put(pixel, num); } }
③ 排序,因為需要返回一個有序的數組,這里方便的可以用TreeMap直接排,要注意TreeMap是對key排序的,而且默認是升序
TreeMap<Integer, Integer> sortedColors = new TreeMap<>(); for (Map.Entry<Integer, Integer> entry : colors.entrySet()) { sortedColors.put(entry.getValue(), entry.getKey()); }
④ 返回一個有序數組,這里簡單的遍歷一下存到ArrayList中即可
ArrayList<Integer> result = new ArrayList<>(); for (Map.Entry<Integer, Integer> entry : sortedColors.entrySet()) { result.add(entry.getValue()); }
⑤ 如果直接調用函數求,會可能阻塞UI,所以要用子線程來做這個工作,簡單的話直接new一個Thread
⑥ 在子線程中如果任務執行完畢,通過Handler發送消息,通知UI更新
下面給出整個類:
public class ColorCaptureUtil { private static final String TAG = "ColorCaptureUtil"; private Handler mHandler; public static final int SUCCESS = 1; /** * Construct a ColorCaptureUtil for analysing the color of the bitmap * @param handler When the analysing done, it would be used to send back the result and call for update */ public ColorCaptureUtil(Handler handler) { mHandler = handler; } /** * Capture the bitmap's colors by counting every pixels, use the constructor's Handler to send back * message, the ArrayList which contains the colors and sorted by the color quantity would be send * back with the message in the Message.obj * @param bitmap The Bitmap for analysing */ public void getBitmapColors(Bitmap bitmap){ getBitmapColors(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight()); } /** * Capture the bitmap's colors by counting every pixels, use the constructor's Handler to send back * message, the ArrayList which contains the colors and sorted by the color quantity would be send * back with the message in the Message.obj * @param bitmap The Bitmap for analysing * @param fromX The start X in the bitmap, can not be negative * @param fromY The start Y in the bitmap, can not be negative * @param toX The end X in the bitmap, can not less than fromX * @param toY The end Y in the bitmap, can not less than fromY */ public void getBitmapColors(Bitmap bitmap, int fromX, int fromY, int toX, int toY) { new Thread(new MyRunnable(bitmap,fromX,fromY,toX,toY,mHandler)).start(); } private class MyRunnable implements Runnable{ private Bitmap bitmap; private int fromX,fromY,toX,toY; private Handler mHandler; public MyRunnable(Bitmap bitmap, int fromX, int fromY, int toX, int toY, Handler handler) { this.bitmap = bitmap; this.fromX = fromX; this.fromY = fromY; this.toX = toX; this.toY = toY; this.mHandler = handler; } @Override public void run() { int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; HashMap<Integer, Integer> colors = new HashMap<>(); TreeMap<Integer, Integer> sortedColors = new TreeMap<>(); ArrayList<Integer> result = new ArrayList<>(); bitmap.getPixels(pixels, 0, bitmap.getWidth(), fromX, fromY, toX - fromX, toY - fromY); for (int pixel : pixels) { Integer num = colors.get(pixel); if (num == null) { colors.put(pixel, 1); } else { num += 1; colors.put(pixel, num); } } for (Map.Entry<Integer, Integer> entry : colors.entrySet()) { sortedColors.put(entry.getValue(), entry.getKey()); } for (Map.Entry<Integer, Integer> entry : sortedColors.entrySet()) { result.add(entry.getValue()); Log.d(TAG, "run: color:"+entry.getValue()+",count:"+entry.getKey()); } Message msg = new Message(); msg.obj = result; msg.what = SUCCESS; mHandler.sendMessage(msg); } } }
可以看到,在構造的時候需要一個Handler,當我們處理完圖片顏色數據的時候,就是通過這個Handler來通知UI線程,然后把統計的數據存到Message.obj中,在主線程中直接取出即可得到顏色,數據是按升序排列的,如果想得到最多的顏色,需要取出size-1下標的值
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == ColorCaptureUtil.SUCCESS) { ArrayList<Integer> colors = (ArrayList<Integer>) msg.obj; color.setBackgroundColor(colors.get(colors.size() - 1)); } } };
第一部分的內容比較簡單,正是因為比較簡單,所以接下來還是有一些問題需要解決的
① 解析速度問題,考慮是否要使用其他排序算法?是否需要處理全部的像素點?能否進行局部采樣?
② 當圖片像效果圖中的第四張(如下)一樣的時候,統計出現最多的像素顏色是否合理?
③ 直接使用new Thread是否合理?當有大量圖片需要處理的時候會不會出現問題?
盡快更新下篇,歡迎指正交流。