Android實現BMP和PNG轉換為JPEG格式
阿新 • • 發佈:2018-11-03
專案需求,需要把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);
}
以後有空再繼續研究,完善!