前言
由于png文件解码较为复杂,所以本次仅针对部分形式的图像,鲁棒性较差,仅作实验使用。
关于本次实验使用的文件:
实验过程
获取文件信息
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <set>
using namespace std;
struct fileHeader
{
unsigned char head[8];
void GetHead( ifstream &in ) { in.read((char *)head,8); }
void Print()
{
for(auto i : head ) cout << (int) i<< " ";
cout << endl;
}
}file_header;
struct chunks
{
unsigned int length = 0;
char type[4];
string s_type = "";
unsigned char *data;
unsigned char CRC[4];
void GetChunk( ifstream &in )
{
unsigned char* buffer = new unsigned char[4];
in.read((char*)buffer,4);
for( int i = 0; i < 4; ++ i ) { length = (length << 8) + buffer[i]; }
in.read(type,4);
if( length != 0 ) {
data = new unsigned char[length];
in.read((char *) data, length);
}
in.read((char*)CRC,4);
for( auto i : type ) s_type += (int)i;
return;
}
};
const string path = "2.png";
map< string, vector<int> > mp;
set<string> ancillary_types;
int main()
{
ifstream in(path,ios::binary);
file_header.GetHead(in);
vector<chunks> v;
int pos = -1;
while(1)
{
chunks c_tmp;
c_tmp.GetChunk(in);
v.push_back(c_tmp); pos ++;
mp[c_tmp.s_type].push_back(pos);
if(c_tmp.s_type=="IEND") break;
if(c_tmp.s_type != "IDAT"&& c_tmp.s_type != "IHDR" && c_tmp.s_type != "PLTE")
{
ancillary_types.insert(c_tmp.s_type);
}
}
cout << "All Chunks information:" << endl;
for( auto &i : mp )
{
cout << "Chunk type: " << i.first << ", Chunk position: ";
auto &v = i.second;
for( auto &j : v ) cout << j << " ";
cout << endl;
}
cout << "ancillary chunks:" << endl;
for( auto i : ancillary_types )
cout << i << endl;
}
通过以上代码,可以获取图片的基本信息,具体如下:
可以看到它的各个块的分布情况,以及有一个tRNS辅助块。
获取IHDR
添加如下定义或实现:
struct iHDR
{
unsigned int width, height;
unsigned char bit_depth, color_type, compression_method;
unsigned char filter_method, interlace_method;
void GetIHDR(unsigned char* buffer )
{
for( int i = 0; i < 4; ++ i ) { width = (width << 8) + buffer[i]; }
for( int i = 0; i < 4; ++ i ) { height = (height << 8) + buffer[i+4]; }
int pos = 8;
for( auto i : { &bit_depth, &color_type, &compression_method,
&filter_method, &interlace_method } )
*i = buffer[pos ++];
}
void Print()
{
cout << width << " " << height << endl;
for( auto i : {bit_depth, color_type, compression_method, filter_method,
interlace_method})
cout << (int)i << " ";
}
}ihdr;
在主程序中添加:
int position = mp["IHDR"][0];
ihdr.GetIHDR(chunks_vector[position].data);
ihdr.Print();
结果为:
对照IHDR各个位的含义:
名称 | 大小(byte) | 说明 |
---|---|---|
Width | 4 | 图像宽度,像素为单位 |
Height | 4 | 图像高度,像素为单位 |
Bit depth | 1 | 图像深度: 索引彩色图像:1,2,4或8 灰度图像:1,2,4,8或16 真彩色图像:8或16 |
ColorType | 1 | 颜色类型: 0:灰度图像, 1,2,4,8或16 2:真彩色图像,8或16 3:索引彩色图像,1,2,4或8 4:带α通道数据的灰度图像,8或16 6:带α通道数据的真彩色图像,8或16 |
Compression method | 1 | 压缩方法(LZ77派生算法) |
Filter method | 1 | 滤波器方法 |
Interlace method | 1 | 隔行扫描方法: 0::非隔行扫描 1:Adam7隔行扫描方式 |
可以得到该图像的信息:
名称 | 取值 |
---|---|
Width | 80 |
Height | 80 |
Bit depth | 8位 |
ColorType | 3,带有调色板 |
Compression method | 0 ,deflate压缩算法 |
Filter method | 0,即滤波方法 0 |
Interlace method | 0,非隔行扫描 |
获取PLTE调色板信息
添加如下定义
struct pLTE
{
int r, g, b;
static pLTE* GetPLTE(unsigned char* buffer)
{
int plte_size = 1 << ihdr.bit_depth;
pLTE *plte = new pLTE[plte_size];
int pos = 0;
for( int i = 0; i < plte_size; ++ i )
{
for( auto j : {&plte[i].r, &plte[i].g, &plte[i].b} )
*j = buffer[pos ++];
}
return plte;
}
void Print()
{
for( auto i : {r,g,b} ) cout << i << " ";
cout << endl;
}
}*plte;
在主程序中添加如下代码:
position = mp["PLTE"][0];
plte = pLTE::GetPLTE(chunks_vector[position].data);
即可获取文件的调色板。
输出IDAT数据
添加如下定义及实现:
const string compressed_data_path = "out.idat";
void TranlateData(ofstream &out, unsigned char* buffer, int buffer_length )
{
out.write((char*)buffer, buffer_length);
}
void OutputIDAT()
{
ifstream in( compressed_data_path, ios::binary );
if( in.is_open() ) return;
ofstream out(compressed_data_path, ios::binary);
for( auto pos : mp["IDAT"] )
TranlateData(out, chunks_vector[pos].data, chunks_vector[pos].length );
out.close();
}
在主程序中添加如下语句:
OutputIDAT();
即可讲将DAT中的数据输出。
对IDAT文件进行解压缩
由于解码较为繁琐,本次利用python的zlib库进行解码。如果后续有时间会对deflate算法进行实现。
python代码:
import zlib
list_dec = []
f = open('out.idat', 'rb')
data = f.read()
new_data = zlib.decompress(data)
fp = open('in.idat','wb')
fp.write(new_data)
fp.close()
print('done')
即可完成解码工作。
将解压缩候的IDAT文件进行转换
添加如下定义或实现:
const string uncompressed_data_path= "in.idat";
const string yuv_path = "out.yuv";
bool png2yuv(const string out_yuv_path, unsigned char* data_buffer, unsigned int buffer_size )
{
unsigned char* y, * u, * v;
int width = ihdr.width, height = ihdr.height;
int y_size = width * height;
int uv_size = y_size / 4;
y = new unsigned char[y_size];
u = new unsigned char[uv_size];
v = new unsigned char[uv_size];
int uv_pos = 0, y_pos = 0;
for( int i = 0; i < buffer_size; ++ i )
{
if( (i + 1 ) % ( width + 1 ) == 0 ) continue;
int r, g, b; auto plte_tmp = plte[data_buffer[i]];
r = plte_tmp.r, g = plte_tmp.g, b = plte_tmp.b;
int h = i / (width + 1 ), w = i % (width + 1);
y[y_pos++] = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
if((h&1)||(w&1)) continue;
u[uv_pos] = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
v[uv_pos++] = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
}
ofstream out(out_yuv_path, ios::binary);
out.write((char*)y, y_size);
out.write((char*)u, uv_size); out.write((char*)v, uv_size);
out.close();
for( auto i : {&y, &u, &v} ) delete[] *i;
}
在主程序中添加:
in.open(uncompressed_data_path, ios::binary);
in.seekg(0, ios::end);
int length = in.tellg();
in.seekg(0, ios::beg);
unsigned char* data_buffer = new unsigned char[length];
in.read((char*)data_buffer, length);
png2yuv(yuv_path, data_buffer, length);
即先将调色板信息取出,反解为真彩信息,然后再转化为4:2:0格式的yuv文件。
最终代码
c++部分:
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <set>
#include <mapinls.h>
using namespace std;
const string path = "2.png";
const string compressed_data_path = "out.idat";
const string uncompressed_data_path= "in.idat";
const string yuv_path = "out.yuv";
struct fileHeader
{
unsigned char head[8];
void GetHead( ifstream &in ) { in.read((char *)head,8); }
void Print()
{
for(auto i : head ) cout << (int) i<< " ";
cout << endl;
}
}file_header;
struct tIME
{
int year, month, day, hour, minute, second;
void GetTime(unsigned char *buffer)
{
year = ((buffer[0]) << 8)+buffer[1];
int pos = 2;
for( auto i : {&month,&day,&hour,&minute,&second} ) *i = buffer[pos++];
}
void Print()
{
cout << year << "-" << month << "-" << day << " " << hour << ":" << minute << ":" << second << endl;
}
}time;
struct iHDR
{
unsigned int width, height;
unsigned char bit_depth, color_type, compression_method, filter_method, interlace_method;
void GetIHDR(unsigned char* buffer )
{
for( int i = 0; i < 4; ++ i ) { width = (width << 8) + buffer[i]; }
for( int i = 0; i < 4; ++ i ) { height = (height << 8) + buffer[i+4]; }
int pos = 8;
for( auto i : { &bit_depth, &color_type, &compression_method, &filter_method, &interlace_method } )
*i = buffer[pos ++];
}
void Print()
{
cout << width << " " << height << endl;
for( auto i : {bit_depth, color_type, compression_method, filter_method, interlace_method})
cout << (int)i << " ";
}
}ihdr;
struct chunks
{
unsigned int length = 0;
char type[4];
string s_type = "";
unsigned char *data;
unsigned char CRC[4];
void GetChunk( ifstream &in )
{
unsigned char* buffer = new unsigned char[4];
in.read((char*)buffer,4);
for( int i = 0; i < 4; ++ i ) { length = (length << 8) + buffer[i]; }
in.read(type,4);
if( length != 0 ) {
data = new unsigned char[length];
in.read((char *) data, length);
}
in.read((char*)CRC,4);
for( auto i : type ) s_type += (int)i;
return;
}
};
struct pLTE
{
int r, g, b;
static pLTE* GetPLTE(unsigned char* buffer)
{
int plte_size = 1 << ihdr.bit_depth;
pLTE *plte = new pLTE[plte_size];
int pos = 0;
for( int i = 0; i < plte_size; ++ i )
{
for( auto j : {&plte[i].r, &plte[i].g, &plte[i].b} )
*j = buffer[pos ++];
}
return plte;
}
void Print()
{
for( auto i : {r,g,b} ) cout << i << " ";
cout << endl;
}
}*plte;
map< string, vector<int> > mp;
set<string> ancillary_types;
vector<chunks> chunks_vector;
void TranlateData(ofstream &out, unsigned char* buffer, int buffer_length )
{
out.write((char*)buffer, buffer_length);
}
void OutputIDAT()
{
ifstream in( compressed_data_path, ios::binary );
if( in.is_open() ) return;
ofstream out(compressed_data_path, ios::binary);
for( auto pos : mp["IDAT"] )
TranlateData(out, chunks_vector[pos].data, chunks_vector[pos].length );
out.close();
}
bool png2yuv(const string out_yuv_path, unsigned char* data_buffer, unsigned int buffer_size )
{
unsigned char* y, * u, * v;
int width = ihdr.width, height = ihdr.height;
int y_size = width * height;
int uv_size = y_size / 4;
y = new unsigned char[y_size];
u = new unsigned char[uv_size];
v = new unsigned char[uv_size];
int uv_pos = 0, y_pos = 0;
for( int i = 0; i < buffer_size; ++ i )
{
if( (i + 1 ) % ( width + 1 ) == 0 ) continue;
int r, g, b; auto plte_tmp = plte[data_buffer[i]];
r = plte_tmp.r, g = plte_tmp.g, b = plte_tmp.b;
int h = i / (width + 1 ), w = i % (width + 1);
y[y_pos++] = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
if((h&1)||(w&1)) continue;
u[uv_pos] = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
v[uv_pos++] = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
}
ofstream out(out_yuv_path, ios::binary);
out.write((char*)y, y_size); out.write((char*)u, uv_size); out.write((char*)v, uv_size);
out.close();
for( auto i : {&y, &u, &v} ) delete[] *i;
}
int main()
{
ifstream in(path,ios::binary);
file_header.GetHead(in);
int pos = -1;
while(1)
{
chunks c_tmp;
c_tmp.GetChunk(in);
chunks_vector.push_back(c_tmp); pos ++;
mp[c_tmp.s_type].push_back(pos);
if(c_tmp.s_type=="IEND") break;
if(c_tmp.s_type != "IDAT"&& c_tmp.s_type != "IHDR" && c_tmp.s_type != "PLTE")
{
ancillary_types.insert(c_tmp.s_type);
}
}
in.close();
cout << "All Chunks information:" << endl;
for( auto &i : mp )
{
cout << "Chunk type: " << i.first << ", Chunk position: ";
auto &v = i.second;
for( auto &j : v ) cout << j << " ";
cout << endl;
}
cout << "\nAncillary chunks:" << endl;
for( auto i : ancillary_types )
cout << i << endl;
int position = mp["IHDR"][0];
ihdr.GetIHDR(chunks_vector[position].data);
position = mp["PLTE"][0];
plte = pLTE::GetPLTE(chunks_vector[position].data);
OutputIDAT();
in.open(uncompressed_data_path, ios::binary);
in.seekg(0, ios::end);
int length = in.tellg();
in.seekg(0, ios::beg);
unsigned char* data_buffer = new unsigned char[length];
in.read((char*)data_buffer, length);
png2yuv(yuv_path, data_buffer, length);
}
python部分:
import zlib
list_dec = []
f = open('out.idat', 'rb')
data = f.read()
new_data = zlib.decompress(data)
fp = open('in.idat','wb')
fp.write(new_data)
fp.close()
print('done')
实验结果
利用yuv查看器可以查看转换是否成功:
可见转换时成功的。
那背景为什么时黑色呢?因为该图片含有tRNS块,存有部分透明度信息,原来背景部分的透明度为100%,而yuv文件并不支持透明度的显示,故背景为黑色。