1. 程式人生 > >Android圖片特效處理(畫素處理)

Android圖片特效處理(畫素處理)

這篇部落格將會通過對畫素的RGB分量做一個處理,然後達到一些特效。並沒有很高階大氣的程式碼。也沒用使用ImageFilter等一些庫。多數參考了別人,大神勿噴。
首先看一下今天的效果圖。
這裡寫圖片描述
由於上傳大小限制的關係,只有一小部分。當然,功能中除了光暈,其他都是實現了的。如果可以的話,我回上傳到github上gif圖。程式碼請在文末下載。那麼接下來,我們就來看下如何實現這些。
再次宣告,絕大多數是操作畫素實現的。速度上可能會很慢。不過不要緊,要的是思想。

由於程式碼太多的原因,下面只會給出關鍵性程式碼,更多程式碼請前往github。

  • 圖片灰度化
    灰度化原理:當前畫素值=0.3r+0.59g+0.11b
    灰度化我在這裡用2中方法實現的。一種是操作ColorMatrix,另一種是顏色分量處理。程式碼如下
public Bitmap doPro(Bitmap src) {
        int width,height;
        height = src.getHeight();
        width = src.getWidth();
        Bitmap bitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint();
        ColorMatrix colorMatrix = new
ColorMatrix(); colorMatrix.setSaturation(0); ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix); paint.setColorFilter(filter); canvas.drawBitmap(src, 0, 0, paint); return bitmap; }

關於ColorMatrix,參考官方文件。

@Override
    public Bitmap doProByPix
(Bitmap src) { int width = src.getWidth(); int height = src.getHeight(); //建立畫素點陣列 int[] pixels = new int[width*height]; int alpha,grey,red,green,blue; src.getPixels(pixels,0,width,0,0,width,height); alpha = 0xFF<<24; for (int i = 0 ; i < height ; i++){ for (int j = 0 ; j < width ; j++){ grey = pixels[width*i+j]; red = ((grey & 0x00FF0000)>>16); green = ((grey & 0x0000FF00)>>8); blue = ((grey & 0x000000FF)); grey = (int)((float)red*0.3+(float)green*0.59+(float)blue*0.11); grey = alpha | (grey<<16)|(grey<<8)|grey; pixels[width*i+j]=grey; } } Bitmap pro = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888); pro.setPixels(pixels,0,width,0,0,width,height); return pro; }

上面這個通過對顏色的 與,左移,右移來拿到顏色分量,當然,也有更簡單的方法拿到分離,後面會說。

  • 對亮度/飽和度/對比度的操作
    這裡的操作就會用到顏色矩陣,顏色矩陣,我並不會多說。如果感興趣就取檢視官方文件。這並不是重點
public Bitmap doPro(Bitmap src) {
        int width = src.getWidth();
        int height = src.getHeight();
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        ColorMatrix colorMatrix = new ColorMatrix();
        //設定飽和度 設定為0.7  範圍 0 - 1
        //colorMatrix.setSaturation((float) 0.7);
        //設定亮度
        colorMatrix.set(new float[]{1,0,0,0,70,
                                    0,1,0,0,70,
                                    0,0,1,0,70,
                                    0,0,0,1,0});
        //改變對比度
//        colorMatrix.set(new float[]{2, 0, 0, 0, 0,
//                                    0, 2, 0, 0, 0,
//                                    0, 0, 2, 0, 0,
//                                    0, 0, 0, 1, 0});
        Paint paint = new Paint();
        paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
        Canvas canvas = new Canvas(bitmap);
        canvas.drawBitmap(src,0,0,paint);
        return bitmap;
    }
  • 水印文字
    就是在圖片之上繪製文字,詳情看程式碼。
  • 懷舊效果的實現
    原理:

R=0.393r+0.769g+0.189b
G=0.349r+0.686g+0.168b
B=0.272r+0.534g+0.131b

懷舊效果也可以通過顏色矩陣來實現,下面是通過顏色矩陣實現的部分程式碼

colorMatrix.set(new float[]{
                (float) 0.393, (float) 0.768, (float) 0.189, 0, 0,
                (float) 0.349, (float) 0.686, (float) 0.168, 0, 0,
                (float) 0.272, (float) 0.534, (float) 0.131, 0, 0,
                0, 0, 0, 1, 0
        });

接下來我們看如何通過操作畫素分量來實現

public Bitmap doProByPix(Bitmap src) {
        /**
         * 懷舊效果原理
         * R=0.393r+0.769g+0.189b
         * G=0.349r+0.686g+0.168b
         * B=0.272r+0.534g+0.131b
         */
        long startTime = System.currentTimeMillis();
        int width = src.getWidth();
        int height = src.getHeight();
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        int pixColor,pixR,pixG,pixB,newR,newG,newB;
        int[] pixels= new int[width*height];
        src.getPixels(pixels, 0, width, 0, 0, width, height);
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                //獲取對應點的畫素
                pixColor  = pixels[width*i+j];

                pixR = Color.red(pixColor);
                pixG = Color.green(pixColor);
                pixB = Color.blue(pixColor);
                newR = (int) (0.393 * pixR + 0.769 * pixG + 0.189 * pixB);
                newG = (int) (0.349 * pixR + 0.686 * pixG + 0.168 * pixB);
                newB = (int) (0.272 * pixR + 0.534 * pixG + 0.131 * pixB);
                int newColor = Color.argb(255,Math.min(255,newR),Math.min(255,newG),Math.min(255, newB));
                pixels[width*i+j]=newColor;
            }
        }
        bitmap.setPixels(pixels,0,width,0,0,width,height);
        long endTime = System.currentTimeMillis();
        Log.e("tag","this is old used time "+(endTime-startTime)+"ms");
        return bitmap;
    }

程式碼都很簡單,而且相同部分很多,重點就在於顏色分量的處理,這個處理是根據原理 來的。

  • 模糊效果
    原理:周邊畫素平均值
    高斯模糊原理:周邊畫素的加權平均值

1.通過RenderScript來實現模糊

public Bitmap RSblur(Bitmap src){
        long startTime = System.currentTimeMillis();
        Bitmap bitmap = Bitmap.createBitmap(src.getWidth(),src.getHeight(), Bitmap.Config.ARGB_8888);

        RenderScript renderScript = RenderScript.create(MyApplication.app);
        ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));

        Allocation allIn = Allocation.createFromBitmap(renderScript, src);
        Allocation allOut = Allocation.createFromBitmap(renderScript,bitmap);

        // 控制模糊程度
        scriptIntrinsicBlur.setRadius(2.f);

        scriptIntrinsicBlur.setInput(allIn);
        scriptIntrinsicBlur.forEach(allOut);

        allOut.copyTo(bitmap);

        src.recycle();
        renderScript.destroy();

        long endTime = System.currentTimeMillis();

        Log.e("tag","this is RenderScript blur use time "+(endTime-startTime)+"ms");
        return bitmap;
    }

2.操作畫素RGB
這裡我用了網上的一個FastBlur類,這個類實現了模糊並且做了優化。

  • 銳化效果
    原理:分別獲取當前畫素點和八個周圍畫素點的RGB值,先求出當前畫素點的RGB值與八個畫素點RGB值的和的平均數,再乘以相應的係數,然後在與當前畫素點之和
pixColor = pixels[(i + n) * width + k + m];
                        pixR = Color.red(pixColor);
                        pixG = Color.green(pixColor);
                        pixB = Color.blue(pixColor);

                        newR = newR + (int) (pixR * laplacian[idx] * alpha);
                        newG = newG + (int) (pixG * laplacian[idx] * alpha);
                        newB = newB + (int) (pixB * laplacian[idx] * alpha);
                        idx++;

這裡利用拉普拉斯矩陣。

  • 浮雕效果
    原理:前一畫素分量值-當前畫素分量值+127
    核心程式碼:
oldColor=oldPixels[i-1];
            oldPixR = Color.red(oldColor);
            oldPixG = Color.green(oldColor);
            oldPixB = Color.blue(oldColor);

            newColor = newPixels[i];
            newPixR = Color.red(newColor);
            newPixG = Color.green(newColor);
            newPixB = Color.blue(newColor);

            //計算
            newPixR = (oldPixR-newPixR +127)>255?255:(oldPixR-newPixR +127) ;
            newPixG = (oldPixG -newPixG+127)>255?255:(oldPixG -newPixG+127) ;
            newPixB = (oldPixB-newPixB+127)>255?255:(oldPixB-newPixB+127);
  • 底片效果
    原理:取當前RGB分量與255差值作為最終分量值
    程式碼:
color = piexls[i];
            pixR = 255 - Color.red(color);
            pixG = 255 - Color.green(color);
            pixB = 255 - Color.blue(color);
  • 光照效果
    原理,以指定點為中心,置頂光照半徑,計算點到光照中心的距離,根據距離增加光照值

核心程式碼:

color = pixels[j*width+i];
                pixR= Color.red(color);
                pixG= Color.green(color);
                pixB = Color.blue(color);
                int distance = (int) (Math.pow((centerY-j),2)+Math.pow((centerX-i),2));
                if (distance<radius*radius);
                {
                    //按照距離大小計算增強的光照值
                    int result = (int) (strength*(1.0-Math.sqrt(distance)/radius));
                    pixR = pixR+result;
                    pixG = pixG+result;
                    pixB = pixB+result;
                }
                //做左右限制
                pixR = Math.min(255,Math.max(0,pixR));
                pixG = Math.min(255,Math.max(0,pixG));
                pixB = Math.min(255,Math.max(0,pixB));
  • 熔鑄效果
    原理:當前RGB分量*128然後對(其他2個分量之和+1)取整,並保留分量的修改
    核心程式碼:
color=pixels[i];
            pixR = Color.red(color);
            pixG = Color.green(color);
            pixB = Color.blue(color);
            //R 分量
            pixR = pixR * 128/(pixG+pixB+1);
            pixR = Math.min(255,Math.max(0,pixR));
            //G 分量
            pixG = pixG*128/(pixB+pixR+1);
            pixG =Math.min(255,Math.max(0,pixG));
            //B 分量
            pixB = pixB*128/(pixR+pixG+1);
            pixB = Math.min(255,Math.max(0,pixB));
  • 冰凍效果
    原理:分量值 = (當前分量值-其他2個分量值的絕對值)*3/2;
    核心程式碼:
color = pixels[i];
            pixR = Color.red(color);
            pixG = Color.green(color);
            pixB = Color.blue(color);
            pixR = Math.abs((pixR - pixG - pixB) * 3 / 2);
            pixR = Math.min(255, pixR);
            pixG = Math.abs((pixG - pixR - pixB) * 3 / 2);
            pixG = Math.min(255, pixG);
            pixB = Math.abs((pixB - pixR - pixG) * 3 / 2);
            pixB = Math.min(255,pixB);
  • 霧化效果
    原理:在影象中引入一定的隨機值,打亂影象中的畫素值
    核心程式碼:
k = random.nextInt(123456);
                int dx = i+k%8;
                int dy = j+k%8;
                if (dx>=width){
                    dx=width-1;
                }
                if (dy>=height){
                    dy=height-1;
                }
                pos = dy*width +dx;
                pos1 = j*width+i;

                pixels[pos1]=pixels[pos];
  • 積木效果
    原理:取3分量平均值,若大於128,則取255,否則取0 作為新分量值
    核心程式碼:
color = pixels[i];
            sum = (Color.red(color)+Color.blue(color)+Color.green(color))/3;
            if (sum>=128){
                sum = 255;
            }else{
                sum = 0;
            }
            pixels[i]=Color.argb(Color.alpha(color),sum,sum,sum);
  • 連環畫效果
    原理:
    R=|g-b+g+r|*r/256
    G=|b-g+b+r|*r/256
    B=|b-g+b+r|*g/256
    核心程式碼
color=pixels[i];
            pixR= Color.red(color);
            pixG = Color.green(color);
            pixB = Color.blue(color);
            //r
            pixR = Math.abs(pixG-pixB+pixG+pixR) * pixR /256;
            if (pixR>255){
                pixR = 255;
            }

            pixG =Math.abs(pixB-pixG+pixB+pixR) *pixR/256;
            if (pixG>255){
                pixG = 255;

            }
            //B=|b-g+b+r|*g/256
            pixB = Math.abs(pixB-pixG+pixB+pixR) * pixG/256;
            if (pixB>256){
                pixB=255;
            }
  • 邊緣高亮效果(霓虹處理)
    原理:計算原畫素的RGB分量與相同行(i+1)以及相同列(j+1)相鄰畫素的梯度,就是差的平方和的平方根,然後將梯度值作為處理後的畫素
    核心程式碼:
color=pixels[j*width+i];
                //獲取i+1畫素
                color_right = pixels[j*width+i+1];
                //獲取j+1畫素
                color_bottom = pixels[(j+1)*width+i];

                //計算R分量
                pixR = (int) (Math.pow((Color.red(color)-Color.red(color_right)),2)
                        +Math.pow((Color.red(color)-Color.red(color_bottom)),2));
                pixR = ((int) (Math.sqrt(pixR)*2));
                pixR = Math.min(255,Math.max(0,pixR));

                //計算G 分量
                pixG = (int) (Math.pow((Color.green(color)-Color.green(color_right)),2)
                        +Math.pow((Color.green(color)-Color.green(color_bottom)),2));
                pixG = ((int) (Math.sqrt(pixG)*2));
                pixG = Math.min(255,Math.max(0,pixG));
                //計算B分量
                pixB = (int) (Math.pow((Color.blue(color)-Color.blue(color_right)),2)
                        +Math.pow((Color.blue(color)-Color.blue(color_bottom)),2));
                pixB = ((int) (Math.sqrt(pixB)*2));
                pixB = Math.min(255,Math.max(0,pixB));

                pixels[j*width+i] = Color.argb(Color.alpha(color),pixR,pixG,pixB);

到這裡就結束了,當然,圖片處理特效還有很多,我這裡僅僅是一小部分。在這裡推薦一個庫:ImageFilter

程式碼地址:github
參考資料:落日小屋
參考資料:其他原理