前言
RGB图片转256色,我知道的有两种方法:
①用Windows系统自带的画图工具打开图片,再另存为256色bmp图片
②用Photoshop打开图片,然后图像→模式→索引颜色,选择局部(可感知)→确定→存储为bmp图片
方法① 转出来的256色图片,会有明显的掉色(如下图)。
方法② 不会掉色,因为photoshop的局部(可感知)会给图片定制最合适的颜色表,而不是像画图工具一样使用系统颜色表。但每次都要打开photoshop,就有点麻烦。
RGB转256色的步骤,可以参考真彩色转256色算法,统计图片中所有颜色出现的次数,选取出现次数最多的256种颜色作为颜色表,就不会感觉到掉色。
思路:定义一个很大的数组,对应所有可能的颜色,遍历图片的每个像素,每遇到一种颜色,就在数组的对应位置加一,最后给数组从大到小排序,取数组开头的256种颜色作为颜色表。完美。
但是RGB图片有255*255*255=16581375种可能的颜色,不想开辟这么长的数组,而且很多相近的、人眼难以看出差别的颜色,完全合并为一种颜色,于是RGB分量原本有8位,统计的时候只取高4位,这样就只有16*16*16 = 4096种可能的颜色,一下就节省99%的空间。
首要问题:如何用C++操作图片
我找到了一个很适合我(新手)的开源库 EasyBMP ,可以读写1位、4位、8位、16位、24位、32位的BMP图片,可以访问图片中任一像素的颜色;可以很容易地读进来一个24位的图片,转换成8位,以256色保存出去。
#include "EasyBMP.h"
using namespace std;int main( int argc, char* argv[] )
{BMP myPicture;myPicture.ReadFromFile("24bitTest.bmp"); // 读进来一个24位的图片myPicture.SetBitDepth( 8 ); // 转换成8位myPicture.WriteToFile("24bitTest-8bit.bmp"); // 以256色保存出去return 0;
}
只不过转换成8位再保存(保存成256色),用的颜色表是windows系统颜色表,保存出来会掉色。不过 EasyBMP 提供了修改颜色表的接口,还带一个RGB转灰度的函数可以参考。
开始写RGB转256色
首先在EasyBMP的 EasyBMP_VariousBMPutilities.h 头文件里,声明定制颜色表函数。其中用到的std::sort()排序,需要引用<algorithm>:
/* FILE : EasyBMP_VariousBMPutilities.h */
#include <algorithm>
bool CreateBestColorTable(BMP&InputImage);
然后在EasyBMP.cpp里写定制颜色表函数的实现:
/* FILE : EasyBMP.cpp */
struct Nm{ int num; //数字 int count=0; //个数
};
bool cmp (const Nm &A,const Nm &B){ return A.count > B.count;
}
bool CreateBestColorTable(BMP&InputImage)
{using namespace std;// 检查当前的色彩深度有没有颜色表int BitDepth = InputImage.TellBitDepth();if( BitDepth != 1 && BitDepth != 4 && BitDepth != 8 ){if( EasyBMPwarnings ){cout << "EasyBMP Warning: Attempted to create color table at a bit" << endl<< " depth that does not require a color table." << endl<< " Ignoring request." << endl;}return false;}int i;// 选取RGB颜色前4bit,组成12bit,统计频率。struct Nm Num[4096];for(i = 0; i < 4096; i++) Num[i].num=i;for(int row = 0; row < InputImage.TellHeight(); row++){for(int col = 0; col < InputImage.TellWidth(); col++){i = 0;i |= (InputImage(col,row)->Red&0b11110000)<<4;i |= (InputImage(col,row)->Green&0b11110000);i |= (InputImage(col,row)->Blue&0b11110000)>>4;Num[i].count++;}}sort(Num,Num+4096,cmp);// 设置颜色表int NumberOfColors = InputImage.TellNumberOfColors();for( i=0 ; i < NumberOfColors ; i++ ){RGBApixel TempColor;TempColor.Red = (ebmpBYTE)((Num[i].num&0b111100000000)>>4);TempColor.Green = (ebmpBYTE)( Num[i].num&0b000011110000);TempColor.Blue = (ebmpBYTE)((Num[i].num&0b000000001111)<<4);TempColor.Alpha = 0;InputImage.SetColor( i , TempColor ); }return true;
}
在主函数里使用:
/* FILE : EasyBMPsample.cpp */
#include "EasyBMP.h"
using namespace std;int main( int argc, char* argv[] )
{BMP myPicture;myPicture.ReadFromFile("24bitTest.bmp");myPicture.SetBitDepth( 8 );CreateBestColorTable(myPicture);myPicture.WriteToFile("24bitTest-8bit-best.bmp");return 0;
}
结果:
转换效果不太好,在渐变的地方能看到“等高线”。这可能是因为统计颜色的时候,只统计高4位太少了。
改成统计高6位
统计高6位的话,有64*64*64=262144种可能的颜色,不想开辟这么长的数组,况且图片不一定有这么多颜色。于是改用链表。用链表的话,统计到多少种颜色,链表就有多长,对于色彩单一的图片来说,可以节省很多不必要的空间开销。但是对于大尺寸图片,会在查找颜色是否存在的过程中消耗更多时间。
思路:新建一个链表,遍历图片的每个像素,如果此像素的颜色在链表中存在,此颜色的记录值加一;否则把新颜色加入链表。最后给链表逆序排序,取链表的前256种颜色作为颜色表。
/* FILE : EasyBMP.cpp */
bool CreatePerfectColorTable(BMP&InputImage)
{using namespace std;int BitDepth = InputImage.TellBitDepth();if( BitDepth != 1 && BitDepth != 4 && BitDepth != 8 ){if( EasyBMPwarnings ){cout << "EasyBMP Warning: Attempted to create color table at a bit" << endl<< " depth that does not require a color table." << endl<< " Ignoring request." << endl;}return false;}int i;list<struct Nm> Num;for(int row = 0; row < InputImage.TellHeight(); row++){for(int col = 0; col < InputImage.TellWidth(); col++){i = 0;i |= (InputImage(col,row)->Red&0b11111100)<<10;i |= (InputImage(col,row)->Green&0b11111100)<<4;i |= (InputImage(col,row)->Blue&0b11111100)>>2;bool is_find = false;for(list<struct Nm>::iterator itor = Num.begin();itor!=Num.end();itor++){if(itor->num == i){is_find = true;itor->count++;break;}}if(!is_find){struct Nm newNum;newNum.num = i;newNum.count = 1;Num.push_back(newNum);}}}Num.sort(cmp);int NumberOfColors = InputImage.TellNumberOfColors();list<struct Nm>::iterator itor = Num.begin();for( i=0 ; i < NumberOfColors ; i++ ){RGBApixel TempColor;TempColor.Red = (ebmpBYTE)((itor->num&0b111111000000000000)>>10);TempColor.Green = (ebmpBYTE)((itor->num&0b000000111111000000)>>4);TempColor.Blue = (ebmpBYTE)((itor->num&0b000000000000111111)<<2);TempColor.Alpha = 0;InputImage.SetColor( i , TempColor ); itor++; }return true;
}
结果:
效果好多了。