1. 程式人生 > >Android實現BMP和PNG轉換為JPEG格式

Android實現BMP和PNG轉換為JPEG格式

專案需求,需要把BMP24位的圖片轉換成jpeg的格式,在網上查詢了一些不同格式圖片的基本知識,加以總結,實現了一個簡單的Demo程式,先貼程式碼,然後再進行理解
picSwitcher.java檔案:

package com.example.bmptojpeg;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.File;

import android.graphics.Bitmap;
import
android.graphics.BitmapFactory; import android.graphics.ImageFormat; import android.graphics.Rect; import android.graphics.YuvImage; import android.util.Log; public class picSwitcher { //picture type public static int BMP_TYPE = 100; public static int JPEG_TYPE = 101; public static int PNG_TYPE = 102
; public static int UNKNOW = 104; //BMP type public static int BMP_DEEP_1 = 200; public static int BMP_DEEP_4 = 201; public static int BMP_DEEP_8 = 202; public static int BMP_DEEP_16 = 203; public static int BMP_DEEP_24 = 204; public static int BMP_DEEP_32 = 205; public static
String TAG = "picSwitcher"; private int[] picInfo; public static int mFileType; private String mPath; public picSwitcher(String path){ mPath = path; } public void init(){ if(isBmpFile(mPath)){ mFileType = BMP_TYPE; picInfo = getBmpInfo(mPath); Log.i(TAG, "width = " + picInfo[0] + " height = " + picInfo[1]); }else if(isJpegFile(mPath)){ mFileType = JPEG_TYPE; }else if(isPngFile(mPath)){ picInfo = getPngInfo(mPath); Log.i(TAG, "width = " + picInfo[0] + " height = " + picInfo[1]); mFileType = PNG_TYPE; }else{ mFileType = UNKNOW; } Log.i(TAG, "type = " + mFileType); } public int getBmpType(String path){ int type = UNKNOW; try { FileInputStream fis = new FileInputStream(path); DataInputStream dis = new DataInputStream(fis); int bflen = 2; byte bf[] = new byte[bflen]; dis.skipBytes(28); dis.read(bf, 0, bflen); dis.close(); fis.close(); int deepFlag = byteToInt(bf); switch (deepFlag) { case 1: type = BMP_DEEP_1; break; case 4: type = BMP_DEEP_4; break; case 8: type = BMP_DEEP_8; break; case 16: type = BMP_DEEP_16; break; case 24: type = BMP_DEEP_24; break; case 32: type = BMP_DEEP_32; break; default: type = UNKNOW; break; } } catch (Exception e) { } return type; } public boolean isPngFile(String path){ boolean reasult = true; try { FileInputStream fis = new FileInputStream(path); DataInputStream dis = new DataInputStream(fis); int flag = dis.readInt(); dis.close(); fis.close(); Log.i(TAG, "flag = " + flag); if(flag != 0x89504E47){ reasult = false; } } catch (Exception e) { reasult = false; } return reasult; } //根據前兩個位元組來判斷‘FFD8’ public boolean isJpegFile(String path){ boolean reasult = true; try { FileInputStream fis = new FileInputStream(path); DataInputStream dis = new DataInputStream(fis); int bflen = 2; byte bf[] = new byte[bflen]; dis.read(bf, 0, bflen); dis.close(); fis.close(); if(byteToInt(bf) != 0xD8FF){ reasult = false; } } catch (Exception e) { reasult = false; Log.i(TAG, "Exception: " + e); } return reasult; } //根據前兩個位元組來判斷 BMP為‘BM’ private boolean isBmpFile(String path){ boolean reasult = true; try { FileInputStream fis = new FileInputStream(path); DataInputStream dis = new DataInputStream(fis); int bflen = 2; byte bf[] = new byte[bflen]; dis.read(bf, 0, bflen); dis.close(); fis.close(); if(byteToInt(bf) != 0x4D42){ reasult = false; } dis.close(); fis.close(); } catch (Exception e) { reasult = false; Log.i(TAG, "Exception: " + e); } return reasult; } //18-21位表示width,22-25位表示height private int[] getBmpInfo(String path){ try { FileInputStream fis = new FileInputStream(path); DataInputStream dis = new DataInputStream(fis); int bflen = 8; dis.skipBytes(18); byte bf[] = new byte[bflen]; dis.read(bf, 0, bflen); dis.close(); fis.close(); return byteToInt2(bf); }catch (Exception e) {} return null; } //16-19位表示width,20-23位表示height private int[] getPngInfo(String path){ try { int []info = new int[2]; FileInputStream fis = new FileInputStream(path); DataInputStream dis = new DataInputStream(fis); dis.skipBytes(16); info[0] = dis.readInt(); info[1] = dis.readInt(); dis.close(); fis.close(); return info; }catch (Exception e) {} return null; } //bmp 小端序轉換成int型別 private int byteToInt(byte [] bt){ int t; t = bt[0] & 0xFF; t |= (((int) bt[1] << 8) & 0xFF00); return t; } //bmp 小端序轉換成int型別 private int[] byteToInt2(byte [] bt){ int []b = new int[2]; b[0] = bt[0] & 0xFF; b[0] |= (((int) bt[1] << 8) & 0xFF00); b[0] |= (((int) bt[2] << 16) & 0xFF0000); b[0] |= (((int) bt[3] << 24) & 0xFF000000); b[1] = bt[4] & 0xFF; b[1] |= (((int) bt[5] << 8) & 0xFF00); b[1] |= (((int) bt[6] << 16) & 0xFF0000); b[1] |= (((int) bt[7] << 24) & 0xFF000000); return b; } //將資料轉換成YUV資料 private byte[] bmpToYuv(String path){ BitmapFactory.Options option = new BitmapFactory.Options(); option.inSampleSize = 1; Bitmap bm = BitmapFactory.decodeFile(path,option); int[] argb = new int[picInfo[0] * picInfo[1]]; bm.getPixels(argb, 0, picInfo[0], 0, 0, picInfo[0], picInfo[1]); byte[] yuv = new byte[picInfo[0] * picInfo[1] * 3 / 2]; encodeYUV420SP(yuv, argb, picInfo[0], picInfo[1]); bm.recycle(); return yuv; } //資料轉換演算法,通過標準演算法將RGB分量變成YUV420型別資料 private void encodeYUV420SP(byte[] yuv420sp, int[] argb, int width, int height) { final int frameSize = width * height; int yIndex = 0; int uvIndex = frameSize; int a, R, G, B, Y, U, V; int index = 0; for (int j = 0; j < height; j++) { for (int i = 0; i < width; i++) { a = (argb[index] & 0xff000000) >> 24; R = (argb[index] & 0xff0000) >> 16; G = (argb[index] & 0xff00) >> 8; B = (argb[index] & 0xff) >> 0; // RGB轉換成YUV的公式 Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16; U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128; V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128; //寫資料,每個畫素YYYYUV yuv420sp[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y)); if (j % 2 == 0 && index % 2 == 0) { yuv420sp[uvIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V)); yuv420sp[uvIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U)); } index++; } } } //將YUV轉換成jpeg格式 private boolean yuvToJpeg(byte [] yuv, String dst){ boolean reasult = true; try{ File jpegFile = new File(dst); if(jpegFile.exists()){ jpegFile.delete(); }else{ jpegFile.createNewFile(); } FileOutputStream fos = new FileOutputStream(jpegFile); Rect rect = new Rect(0, 0, picInfo[0], picInfo[1]); YuvImage image = new YuvImage(yuv, ImageFormat.NV21, picInfo[0], picInfo[1], null); image.compressToJpeg(rect, 100, fos); fos.close(); }catch(Exception e){ reasult = false; Log.i(TAG, "Exception: " + e); } return reasult; } //其他格式轉換成jpeg public boolean toJpeg( String dst){ byte[] yuv = bmpToYuv(mPath); return yuvToJpeg(yuv, dst); } }

這裡寫圖片描述
ImageFormat.NV21的YUV分量儲存格式如上圖,對應encodeYUV420SP的演算法

測試部分,比較簡單
MainActivity.java檔案:

package com.example.bmptojpeg;


import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {
    private picSwitcher bs;
    private String src = "/data/local/logo.bmp";
    private String dst = "/data/local/boot0.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bs = new picSwitcher(src);
        bs.init();
        if(bs.mFileType == bs.BMP_TYPE){
            bs.toJpeg(dst);
        }else if(bs.mFileType == bs.PNG_TYPE){
            bs.toJpeg(dst);
        }else if(bs.mFileType == bs.JPEG_TYPE){
            Log.i("picSwitcher", "type is jpeg, doNothing");
        }
    }

}

程式碼的大概流程如下圖,不過只做了從BMP轉換到JPEG格式的部分(紅色部分)
這裡寫圖片描述

要理解這部分的流程,必須要知道bmp的檔案格式:

這裡寫圖片描述
隨意找了一個720P(1920*1080)bmp的檔案開啟後如下
這裡寫圖片描述
紅色部分代表的是圖片的寬和高

重要說一下程式碼裡面用到的部分:
0-1位:bmp格式的圖片為‘BM’,可作為改格式的判斷根據
18-21位表示圖片的寬width
22-25位表示圖片的高height

程式碼裡還涉及到png轉jpeg的部分,做簡單介紹,720P(1920*1080)png的檔案開啟後如下
這裡寫圖片描述
紅色部分代表的是圖片的寬和高

將PNG和BMP的圖片寬高資料比較會發現,資料儲存的模式不一樣,BMP為小端模式儲存,而PNG為大端模式儲存,所以在讀取寬高資料的時候要做相應的轉換

剩餘知識可以根據程式碼來理解,參考雷神部落格基礎篇
http://blog.csdn.net/leixiaohua1020/article/details/50534150

下面簡單寫下其他格式轉換成BMP的思路:

//通過BitmapFactory.decodeFile(path)得到Bitmap資料
Bitmap bitmap=BitmapFactory.decodeFile(path);

//然後根據以上的BMP頭訊息來填充
public void saveBmp(Bitmap bitmap ,String filename) {
        if (bitmap == null)
            return;
        // 點陣圖大小
        int nBmpWidth = bitmap.getWidth();
        int nBmpHeight = bitmap.getHeight();
        // 影象資料大小
        int bufferSize = nBmpHeight * (nBmpWidth * 3 + nBmpWidth % 4);
        try {

            File file = new File(filename);
            Log.w(TAG,"------File : " +filename );
            if (!file.exists()) {
                Log.w(TAG,"- not exist-----File : " +filename );
                file.createNewFile();
            }
             else{
                file.delete();
             }

            FileOutputStream fileos = new FileOutputStream(filename);
            // bmp檔案頭
            int bfType = 0x4d42;
            long bfSize = 14 + 40 + bufferSize;
            int bfReserved1 = 0;
            int bfReserved2 = 0;
            long bfOffBits = 14 + 40;
            // 儲存bmp檔案頭
            writeWord(fileos, bfType);
            writeDword(fileos, bfSize);
            writeWord(fileos, bfReserved1);
            writeWord(fileos, bfReserved2);
            writeDword(fileos, bfOffBits);
            // bmp資訊頭
            long biSize = 40L;
            long biWidth = nBmpWidth;
            long biHeight = nBmpHeight;
            int biPlanes = 1;
            int biBitCount = 24;
            long biCompression = 0L;
            long biSizeImage = 0L;
            long biXpelsPerMeter = 0L;
            long biYPelsPerMeter = 0L;
            long biClrUsed = 0L;
            long biClrImportant = 0L;
            // 儲存bmp資訊頭
            writeDword(fileos, biSize);
            writeLong(fileos, biWidth);
            writeLong(fileos, biHeight);
            writeWord(fileos, biPlanes);
            writeWord(fileos, biBitCount);
            writeDword(fileos, biCompression);
            writeDword(fileos, biSizeImage);
            writeLong(fileos, biXpelsPerMeter);
            writeLong(fileos, biYPelsPerMeter);
            writeDword(fileos, biClrUsed);
            writeDword(fileos, biClrImportant);
            // 畫素掃描
            byte bmpData[] = new byte[bufferSize];
            int wWidth = (nBmpWidth * 3 + nBmpWidth % 4);
            for (int nCol = 0, nRealCol = nBmpHeight - 1; nCol < nBmpHeight; ++nCol, --nRealCol)
                for (int wRow = 0, wByteIdex = 0; wRow < nBmpWidth; wRow++, wByteIdex += 3) {
                    int clr = bitmap.getPixel(wRow, nCol);
                    bmpData[nRealCol * wWidth + wByteIdex] = (byte) Color.blue(clr);
                    bmpData[nRealCol * wWidth + wByteIdex + 1] = (byte) Color.green(clr);
                    bmpData[nRealCol * wWidth + wByteIdex + 2] = (byte) Color.red(clr);
                }

            fileos.write(bmpData);
            fileos.flush();
            fileos.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //其中writeDword等代表按小端序的模式寫入對應的資料位元組數,給一個簡單的例子
        public static void writeLong(FileOutputStream stream, long value) throws IOException {
        byte[] b = new byte[4];
        b[0] = (byte) (value & 0xff);
        b[1] = (byte) (value >> 8 & 0xff);
        b[2] = (byte) (value >> 16 & 0xff);
        b[3] = (byte) (value >> 24 & 0xff);
        stream.write(b);
    }

以後有空再繼續研究,完善!