关于png压缩 的方法目前分为大概三种:
1:直方图,2:中位切分 3:八叉树。
本文示例小试牛刀写最简单的直方图方法,此方法只使用与小范围的图片。
具体算法示例如下:
#pragma once
#include <vector>
#include <map>
#include<algorithm>
//简单的PNG压缩算法
//将32位 PNG压缩为 8位PNG 或者更低
//适用较小的图片
class PngCompress
{
unsigned char *m_srcbuff;
unsigned char * m_dstbuff;
//统计图
std::map<unsigned int, int> m_histogramMap;
int m_w, m_h;
int m_depth;
bool m_ColorMapNeedSparsing;
//最终的颜色索引值
std::vector<unsigned int> m_IndexColor;
//抽取的颜色表
std::vector<unsigned int> m_colorTable;//256
public:
PngCompress() {}
~PngCompress() {}
//传入一个位图的颜色数据,将颜色索引存储在m_colorTable 和m_IndexColor成员
//仅仅实现了32 转8位的,转成1,2,4位等不需要计算直接生成索引图即可。
bool AdaptColorDepth(unsigned char * pbuff, int w, int h)
{
m_srcbuff = pbuff;
m_w = w;
m_h = h;
#pragma omp parallel for
for (int i = 0; i < m_w; i++)
for (int j = 0; j < m_h; j++)
{
unsigned char r = (unsigned char)pbuff[i*m_w * 4 + j * 4];
unsigned char g = (unsigned char)pbuff[i*m_w * 4 + j * 4 + 1];
unsigned char b = (unsigned char)pbuff[i*m_w * 4 + j * 4 + 2];
unsigned char a = (unsigned char)pbuff[i*m_w * 4 + j * 4 + 3];//0 全透明 255 不透明,透明色单独一个颜色索引
//int Gray = (r * 19595 + g * 38469 + b * 7472) >> 16;//灰度图生成值
unsigned int rgba = r | (g << 8) | (b << 16);
m_histogramMap[rgba]++;
}
m_ColorMapNeedSparsing = false;
if (m_histogramMap.size() <= 2)
m_depth = 1;
if (m_histogramMap.size() <= 128 && m_histogramMap.size() > 2)
m_depth = 4;
if (m_histogramMap.size() >= 128 && m_histogramMap.size() <= 256)//无损压缩
{
m_depth = 8;
m_ColorMapNeedSparsing = false;
}
if (m_histogramMap.size() >= 256)//有损压缩
{
m_ColorMapNeedSparsing = true;
m_depth = 8;
}
if (m_ColorMapNeedSparsing && m_depth == 8)
{
//直方图统计算法
ColorMapSparsing();
FloydSteinbergDither();
return true;
}
return false;
}
private:
typedef std::pair<unsigned int, int> PAIR;
static int cmp_sparsing(const PAIR& x, const PAIR& y)
{
return x.second > y.second;
}
//提出现频率高的颜色值
void ColorMapSparsing()
{
if (!m_ColorMapNeedSparsing)
return;
std::vector<PAIR> vec(m_histogramMap.begin(), m_histogramMap.end());
sort(vec.begin(), vec.end(), this->cmp_sparsing);
#pragma omp parallel for
for (int i = 0; i < 254; i++)//少一个颜色存全透明的色彩,不存半透明的颜色
m_colorTable.push_back(vec[i].first);
//透明的颜色
m_colorTable.push_back(247 | (255 << 8) | (255 << 16) | (0 << 24));
}
//根据颜色在索引表的位置找出最近颜色值
//找不到的使用最近颜色距离的颜色索引(加权的颜色距离)
void FloydSteinbergDither()
{
m_dstbuff = new unsigned char(m_w*m_h * sizeof(unsigned char));
int size = sizeof(unsigned char);
unsigned char* pbuff = m_srcbuff;
#pragma omp parallel for
for (int i = 0; i < m_w; i++)
for (int j = 0; j < m_h; j++)
{
unsigned char r = (unsigned char)pbuff[i*m_w * 4 + j * 4];
unsigned char g = (unsigned char)pbuff[i*m_w * 4 + j * 4 + 1];
unsigned char b = (unsigned char)pbuff[i*m_w * 4 + j * 4 + 2];
unsigned char a = (unsigned char)pbuff[i*m_w * 4 + j * 4 + 3];//0 全透明 255 不透明,透明色单独一个颜色索引
if (a == 0)//无效色直接给透明色
{
m_dstbuff[i*m_w * size + j * size] = (unsigned char)255;
continue;
}
unsigned int rgba = r | (g << 8) | (b << 16);
std::vector<unsigned int>::iterator it = std::find(m_colorTable.begin(), m_colorTable.end(), rgba);
if (it == m_colorTable.end())
{
//到这里都是没有找到的颜色需要计算
unsigned int srcColor = rgba;
std::pair<int, double> col_index = FindClosestPaletteColor(srcColor);
m_dstbuff[i*m_w * size + j * size] = (unsigned char)col_index.first;
unsigned int nearColor = m_colorTable[col_index.first];
}
else
{
m_dstbuff[i*m_w * size + j * size] = (unsigned char)(it - m_colorTable.begin());
continue;
}
}
}
typedef std::pair<int, double> col_Index_pair;
static bool cmp_closest(const col_Index_pair& s1, const col_Index_pair& s2)
{
return s1.second < s2.second;
}
// 索引和距离
std::pair<int, double> FindClosestPaletteColor(unsigned int SrcColor)
{
unsigned int col;
int index;
int size = m_colorTable.size();
std::vector<col_Index_pair> vecs;
#pragma omp parallel for
for (int i = 0; i < 254; i++)
{
double s = ColorDistance(m_colorTable[i], SrcColor);
vecs.push_back(std::make_pair(i, s));
}
sort(vecs.begin(), vecs.end(), this->cmp_closest);
return vecs[0];
}
//颜色空间距离,非欧式距离,颜色为
double ColorDistance(unsigned int SrcColor, unsigned int DstColor)
{
char sR = SrcColor & 0xff;
char sG = (SrcColor & 0xff) >> 8;
char sB = (SrcColor & 0xff) >> 16;
char dR = DstColor & 0xff;
char dG = (DstColor & 0xff) >> 8;
char dB = (DstColor & 0xff) >> 16;
long rmean = (sR + dR) / 2;
long r = sR - dR;
long g = sG - dG;
long b = sB - dB;
return sqrt((((512 + rmean)*r*r) >> 8) + 4 * g*g + (((767 - rmean)*b*b) >> 8));
}
};