昨晚睡之前想,如果总结一个Bitmap中含有的RGB值,然后如果最常用的颜色在3万种左右,那么可以统计为一个表,名为colorList,直接用把Bitmap中对应的像素替换为最接近的颜色的索引指,假设颜色表限制容量为最长出现的32767种,那么索引只需要两个字节即可表达;如果颜色表限制为最多256种颜色,最只需要1个字节的索引就可以替换1个像素,从而达到压缩作用。
实际上这个demo实现非常粗糙,颜色表其实用字典树去实现可以省下很多的读写时间,但是今天还是工作日,暂时时间不足,直接贴上这次的实验代码吧。
2字节版本
ByteMap:
package com.example.chenjiezhu.bmpcompresstest;
/**
* Created by chenjiezhu on 2020/1/17.
*/
public class ByteMap {
public int width, height;
public short byteMap[];
}
ByteMapUtil
package com.example.chenjiezhu.bmpcompresstest;
import android.graphics.Bitmap;
import android.util.Log;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Created by chenjiezhu on 2020/1/17.
*/
public class ByteMapUtil {
private int width;
private int height;
private class RgbBlock{
public byte rChannel;
public byte gChannel;
public byte bChannel;
public int color;
public int repeatTime;
}
private List<RgbBlock> colorList = new ArrayList<>();
public List<RgbBlock> getColorList() {
return colorList;
}
public ByteMap bitmapToByteMap(Bitmap bitmap) {
width = bitmap.getWidth();
height = bitmap.getHeight();
int pixels[] = new int[width * height];
bitmap.copyPixelsToBuffer(IntBuffer.wrap(pixels));
for (int i = 0; i < pixels.length; i++) {
addPixels(pixels[i]);
}
Log.i("cjztest", "run successed");
sortList();
Log.i("cjztest", "sort successed");
// showAll();
return outputByteMap(pixels, width, height);
}
private ByteMap outputByteMap(int pixels[], int width, int height) {
short byteMap[] = new short[pixels.length];
for(int i = 0; i < byteMap.length; i++){
int color = pixels[i] & 0x00FFFFFF;
byte colorR = (byte) (color >> 16 & 0xFF);
byte colorG = (byte) (color >> 8 & 0xFF);
byte colorB = (byte) (color & 0xFF);
int rgbMinDiff = Integer.MAX_VALUE;
int rgbMinDiffIndexInColorList = 0;
for(int j = 0; j < colorList.size(); j++){
RgbBlock block = colorList.get(j);
int rgbdiff = Math.abs(block.rChannel - colorR) + Math.abs(block.gChannel - colorG) + Math.abs(block.bChannel - colorB);
if(rgbdiff < rgbMinDiff){
rgbMinDiff = rgbdiff;
rgbMinDiffIndexInColorList = j;
}
}
byteMap[i] = (short) (rgbMinDiffIndexInColorList & 0xFFFF);
}
ByteMap bMap = new ByteMap();
bMap.byteMap = byteMap;
bMap.width = width;
bMap.height = height;
// for(int i = 0; i < byteMap.length; i++) {
// Log.i("cjztest", String.format("byte : %d" , byteMap[i] & 0xFF));
// }
return bMap;
}
private void sortList() {
//保留最常用的256*256种颜色
Collections.sort(colorList, new Comparator<RgbBlock>() {
@Override
public int compare(RgbBlock o1, RgbBlock o2) {
if(o1.repeatTime < o2.repeatTime){
return 1;
} else if(o1.repeatTime == o2.repeatTime){
return 0;
} else {
return -1;
}
}
});
while(colorList.size() > 256 * 256){
colorList.remove(colorList.size() - 1);
}
//按照颜色进行排序
Collections.sort(colorList, new Comparator<RgbBlock>() {
@Override
public int compare(RgbBlock o1, RgbBlock o2) {
if(o1.color > o2.color){
return 1;
} else if(o1.color == o2.color){
return 0;
} else {
return -1;
}
}
});
}
private void showAll() {
RgbBlock item = null;
Iterator<RgbBlock> it = colorList.iterator();
while(it.hasNext() && (item = it.next()) != null){
Log.i("cjztest", String.format("color: %02X%02X%02X, repeat: %d", item.rChannel, item.gChannel, item.bChannel, item.repeatTime));
}
}
/**统计颜色种类**/
private void addPixels(int pixel) {
boolean isHadThisColor = false;
RgbBlock item = null;
byte rgb[] = new byte[]{(byte)(pixel >> 16 & 0xFF), (byte)(pixel >> 8 & 0xFF), (byte)(pixel & 0xFF)};
Iterator<RgbBlock> it = colorList.iterator();
while(it.hasNext() && (item = it.next()) != null){
if(rgb[0] == item.rChannel && rgb[1] == item.gChannel && rgb[2] == item.bChannel){
item.repeatTime ++;
isHadThisColor = true;
break;
}
}
if(!isHadThisColor){
RgbBlock rgbBlock = new RgbBlock();
rgbBlock.rChannel = rgb[0];
rgbBlock.gChannel = rgb[1];
rgbBlock.bChannel = rgb[2];
rgbBlock.color = pixel & 0x00FFFFFF;
colorList.add(rgbBlock);
}
}
public Bitmap byteMapToBitmap(ByteMap byteMap){
Bitmap bitmap = Bitmap.createBitmap(byteMap.width, byteMap.height, Bitmap.Config.ARGB_8888);
int index = 0;
for(int y = 0; y < bitmap.getHeight(); y++){
for(int x = 0; x < bitmap.getWidth(); x++) {
int pixel = colorList.get(byteMap.byteMap[index++] & 0xFFFF).color | 0xFF000000;
bitmap.setPixel(x, y, pixel);
}
}
return bitmap;
}
}
调用:
package com.example.chenjiezhu.bmpcompresstest;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends Activity {
private ImageView iv_after, iv_before;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv_before = findViewById(R.id.iv_before);
iv_after = findViewById(R.id.iv_after);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
iv_before.setImageBitmap(bitmap);
ByteMapUtil byteMapUtil = new ByteMapUtil();
//转换成字节图的测试
ByteMap byteMap = byteMapUtil.bitmapToByteMap(bitmap);
//字节图转换成位图的测试
Bitmap bitmapForByteMap = byteMapUtil.byteMapToBitmap(byteMap);
iv_after.setImageBitmap(bitmapForByteMap);
}
}
activity_main layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_weight="1"
android:id="@+id/iv_before"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:layout_weight="1"
android:id="@+id/iv_after"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
效果:
上面的图是原图,后面的图是压缩过后再展开的图。
github地址:https://github.com/cjzjolly/cjz_bmp_compress
将持续优化