32位PNG有损压缩为8位

关于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));
	}
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值