之前有个需求是服务器上传图片来设置开机LOGO,但是系统限制LOGO图片必须是16位的BMP图片格式,服务器上传的图片不仅包含BMP图片,还包含了JPG的图片格式,因此需要将JPG格式的图片转换成16位的BMP格式。
BMP图片数据实际也是一位位的0和1组成,因此我们可以将JPG格式的图片转换成Bitmap对象,再遍历其中的每个像素点,将对应的颜色信息转换成实际数字,并写入BMP图片文件中。
同时BMP文件不仅包含了像素颜色信息,还要有对应的文件头来表示文件格式等信息;如果图片分辨率和颜色深度不变的话,这些信息都是固定的,也是可以直接写入文件中,这里我的图片格式需求是1920*1080 16位颜色深度。
首先转换JPG图片为Bitmap对象。
FileInputStream in = null;
try {
in = new FileInputStream("/storage/emulated/0/Pictures/bootup.jpg");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Bitmap bitMap = BitmapFactory.decodeStream(in); //由输入流获取Bitmap对象 操作图片
/*改变图片大小*/
int width = bitMap.getWidth(); //原始宽度
int height = bitMap.getHeight(); //原始高度
int newWidth = 1920; //需求宽度
int newHeight = 1080; //需求高度
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight); // 缩放图片
bitMap = Bitmap.createBitmap(bitMap, 0, 0, width, height, matrix, true); //获取最终修改完成之后的Bitmap对象
通过位运算将32位颜色信息转换为16位。
public void convertBitmapTo16BitBMP(Bitmap bitmap, String outputPath) throws IOException {
// 将Bitmap转换为ARGB_8888格式
if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false);
}
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
// 创建一个新的16位bmp文件
FileOutputStream fos = new FileOutputStream(outputPath);
// 写入bmp文件头
int bfType = 0x4d42;
long bfSize = 56 + (long) width *height*2 + ((long) width *height*2)%4;
int bfReserved1 = 0;
int bfReserved2 = 0;
long bfOffBits = 54;
// 保存bmp文件头
writeWord(fos, bfType);
writeDword(fos, bfSize);
writeWord(fos, bfReserved1);
writeWord(fos, bfReserved2);
writeDword(fos, bfOffBits);
// bmp信息头
long biSize = 40L;
int biPlanes = 1;
int biBitCount = 16;
long biCompression = 0L;
long biSizeImage = 2+(long) width *height*2 + ((long) width *height*2)%4;
long biXpelsPerMeter = 11815L;
long biYPelsPerMeter = 11815L;
long biClrUsed = 0L;
long biClrImportant = 0L;
// 保存bmp信息头
writeDword(fos, biSize);
writeLong(fos, width);
writeLong(fos, height);
writeWord(fos, biPlanes);
writeWord(fos, biBitCount);
writeDword(fos, biCompression);
writeDword(fos, biSizeImage);
writeLong(fos, biXpelsPerMeter);
writeLong(fos, biYPelsPerMeter);
writeDword(fos, biClrUsed);
writeDword(fos, biClrImportant);
// 遍历Bitmap的每个像素,将其颜色值从32位减少到16位
for( int i = height-1;i >=0;i--) {
for (int j =0;j<width;j++) {
int color = pixels[i*width+j];
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
int alpha = Color.alpha(color);
// 将颜色值从32位减少到16位
int newColor = ((red >> 3) << 10) | ((green >> 3) << 5) | (blue >> 3);
fos.write(Integer.valueOf(newColor).byteValue());
fos.write(Integer.valueOf((newColor >> 8)).byteValue());
}
}
fos.close();
}
这样我们就得到一个固定大小的16位BMP格式图片,实际上,通过类似的方法,我们还可以转换Bitmap为更高位深的24位BMP图片,只需要简单修改一下BMP信息头和遍历时的位运算即可。
但是最后得到的图片却不能成功设置为开机LOGO,通过文件比较工具发现,实际上的LOGO文件文件头与我们转换写入的文件头有几位数不同,并且像素位也相差几位,查阅资料发现可能是与图片的加密压缩有关,这里就不再深入探究了。