png文件转为yuv文件

前言

由于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)说明
Width4图像宽度,像素为单位
Height4图像高度,像素为单位
Bit depth1图像深度:
索引彩色图像:1,2,4或8
灰度图像:1,2,4,8或16
真彩色图像:8或16
ColorType1颜色类型:
0:灰度图像, 1,2,4,8或16
2:真彩色图像,8或16
3:索引彩色图像,1,2,4或8
4:带α通道数据的灰度图像,8或16
6:带α通道数据的真彩色图像,8或16
Compression method1压缩方法(LZ77派生算法)
Filter method1滤波器方法
Interlace method1隔行扫描方法:
0::非隔行扫描
1:Adam7隔行扫描方式

可以得到该图像的信息:

名称取值
Width80
Height80
Bit depth8位
ColorType3,带有调色板
Compression method0 ,deflate压缩算法
Filter method0,即滤波方法 0
Interlace method0,非隔行扫描

获取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文件并不支持透明度的显示,故背景为黑色。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值