FFMPEG之图片系列 --- png格式

来源:互联网 发布:java书籍推荐 知乎 编辑:程序博客网 时间:2024/05/16 19:43

在ffmpeg中,即支持png格式文件,也支持apng格式文件,下面就这两种区别做一描述:

PNG(Portable Network Graphics)是一种无损压缩的位图图形格式。其设计目的是试图替代GIF和过于复杂的TIFF文件格式,同时增加一些GIF文件不具备的特性。

MNG图形文件格式用来表现动画。以下是PNG、MNG、APNG的历史:

1996年6月提出PNF(Portable Network Frame)草案,当年8月改名为MNG(Multiple-image Network Graphics)。
PNG的1.0版本规范于1996年7月1日发布,后来被称为RFC 2083标准,并在1996年10月1日成为W3C建议。
PNG的1.1版本进行了部分小幅修改并增加了三个新的数据块定义,于1998年12月31日发布。
PNG的1.2版本增加了另外一个数据块,于1999年8月11日发布。
PNG现行版本是国际标准(ISO/IEC 15948:2003),并在2003年11月10日作为W3C建议发布。这个版本与1.2版仅有细微差别。
2004年末,PNG的动画扩展——APNG,被提出来。这是一个相对于MNG更简单的动画实现方案,不识别APNG格式的PNG解码器至少能够正常回放第一幅普通PNG画面。

在libavcodec/allcodecs.c中 定义了png和apng的encoder和decoder.

    REGISTER_ENCDEC (PNG,               png);       # ff_png_encoder in libavcodec/pngenc.c        ff_png_decoder in libavcodec/pngdec.c    REGISTER_ENCDEC (APNG,              apng);      # ff_apng_encoder in libavcodec/pngenc.c       ff_png_decoder in libavcodec/pngdec.c    REGISTER_PARSER(PNG,                png);       # ff_png_parser in libavcodec/png_parser.c


PNG文件结构:

PNG文件由一个8字节的PNG文件署名(PNG File Signature)和按照特定结构组织的3个以上的数据块(chunk)组成。

文件署名部分:      

数据块部分:PNG中有两种类型的数据块,一是关键数据块,是一种必须的数据块,另一种是辅助数据块,是可选的。

                      不管是什么样的数据块,都有如下所示的4个字段域:

字段域名称字节数说明length                     长度4-byte指定数据块中数据域的长度,长度不超过2^31 - 1 字节Chunk Type Code  数据块类型码4-byte数据块类型码有ASCII字母组成,区分大小写Chunk Data            数据块数据长度可变存储Chunk Type Code域制定的数据CRC                       循环冗余检测4-byte存储用来检测是否有错误的循环冗余码

关键数据块定义如下表所示的4中类型:

数据块标识符号数据块名称是否为多数据块是否为可选数据块块位置的限制IHDR文件头数据块否,只有一个否,必须有作为第一块PLTE调色板数据块否,只有一个是,可以没有在IDAT之前IDAT图像数据块是,可以有多个否,必须有与其它IDAT数据块相连IEND图像结束数据否,只有一个否,必须有最后一个数据块


IHDR 文件头数据块IHDR: 包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG文件数据流中,而且一个PNG文件有且仅有一个IHDR.

域名称字 节数                        说                    明                                                                                                                                                     Width4-byte图像宽度信息,以像素为单位。Height4-byte图像高度信息;BitDepth1 byte

图像样本深度,代表图像所表示的bit位数:
            索引彩色图像: 1, 2, 4或8
            灰度图像: 1, 2, 4, 8或16
            真彩色图像: 8或16
ColorType1 byte
颜色类型:
           0: 灰度图像,1,2,4,8或16;
           2: 真彩色图像,8或16;
           3: 索引彩色图像, 1, 2, 4或8;
           4: 带a通道数据的灰度图像, 8或16;
           5: 带a通道数据的真彩色图像,8或16;
CompressionMethod1 byte
压缩方法 LZ77派生算法;
FilterMethod1 byte滤波器方法;InterlaceMethod1 byte
隔行扫描方法:
            0: 非隔行扫描(逐行扫描)
            1: Adam7


PLTE调色板数据块,含有与索引彩色图像(indexed-color image)相关的彩色变换数据,它仅与索引彩色图像相关,而且要放在图像数据块之前。

PLTE数据块是定义图像的调色板信息,PLTE可以包含1~256个调色板信息,每个调色板信息由3个字节组成R,G,B.


IDAT图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。

IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像。


IEND图像结束数据IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。
如果我们仔细观察PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:

不难明白,由于数据块结构的定义,IEND数据块的长度总是0(00 00 00 00,除非人为加入信息),数据标识总是IEND(49 45 4E 44),因此,CRC码也总是AE 42 60 82。


在libavcodec/png.h中定义如下的颜色类型,用来表示上面表格中提到的ColorType:

#define PNG_COLOR_MASK_PALETTE    1                    //定义调色板#define PNG_COLOR_MASK_COLOR      2                    //真彩色#define PNG_COLOR_MASK_ALPHA      4                    //a通道#define PNG_COLOR_TYPE_GRAY 0                                                    // 表示灰度图像#define PNG_COLOR_TYPE_PALETTE  (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE)  // 索引彩色图像#define PNG_COLOR_TYPE_RGB        (PNG_COLOR_MASK_COLOR)                         // 彩色图像#define PNG_COLOR_TYPE_RGB_ALPHA  (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA)  // 带a通道的彩色图像#define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA)                         // 带a通道的灰度图像


在libavcodec/png.c中定义如下的函数来获取不同颜色类型对应的通道数:

## 1通道: 灰度图像

## 2通道: 带a通道的灰度图像

## 3通道: 彩色图像或索引彩色图像

## 4通道: 带a通道的彩色图像

int ff_png_get_nb_channels(int color_type){    int channels;    channels = 1;    if ((color_type & (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE)) ==        PNG_COLOR_MASK_COLOR)        channels = 3;    if (color_type & PNG_COLOR_MASK_ALPHA)        channels++;    return channels;}


定义如下的函数用来计算在隔行扫描算法为Adam7时调用:

/* compute the row size of an interleaved pass */int ff_png_pass_row_size(int pass, int bits_per_pixel, int width){    int shift, xmin, pass_width;    xmin = ff_png_pass_xmin[pass];    if (width <= xmin)        return 0;    shift      = ff_png_pass_xshift[pass];    pass_width = (width - xmin + (1 << shift) - 1) >> shift;    return (pass_width * bits_per_pixel + 7) >> 3;}


在libavcodec/png_parser.c中定义png文件解析函数:

static int png_parse(AVCodecParserContext *s, AVCodecContext *avctx,                     const uint8_t **poutbuf, int *poutbuf_size,                     const uint8_t *buf, int buf_size){    PNGParseContext *ppc = s->priv_data;    int next = END_NOT_FOUND;    int i = 0;    s->pict_type = AV_PICTURE_TYPE_NONE;    *poutbuf_size = 0;    if (!ppc->pc.frame_start_found) {        uint64_t state64 = ppc->pc.state64;        for (; i < buf_size; i++) {            state64 = (state64 << 8) | buf[i];            if (state64 == PNGSIG || state64 == MNGSIG) {                i++;                ppc->pc.frame_start_found = 1;                break;            }        }        ppc->pc.state64 = state64;    } else if (ppc->remaining_size) {        i = FFMIN(ppc->remaining_size, buf_size);        ppc->remaining_size -= i;        if (ppc->remaining_size)            goto flush;        if (ppc->chunk_pos == -1) {            next = i;            goto flush;        }    }    for (; ppc->pc.frame_start_found && i < buf_size; i++) {        ppc->pc.state = (ppc->pc.state << 8) | buf[i];        if (ppc->chunk_pos == 3) {            ppc->chunk_length = ppc->pc.state;            if (ppc->chunk_length > 0x7fffffff) {                ppc->chunk_pos = ppc->pc.frame_start_found = 0;                goto flush;            }            ppc->chunk_length += 4;        } else if (ppc->chunk_pos == 7) {            if (ppc->chunk_length >= buf_size - i)                ppc->remaining_size = ppc->chunk_length - buf_size + i + 1;            if (ppc->pc.state == MKBETAG('I', 'E', 'N', 'D')) {                if (ppc->remaining_size)                    ppc->chunk_pos = -1;                else                    next = ppc->chunk_length + i + 1;                break;            } else {                ppc->chunk_pos = 0;                if (ppc->remaining_size)                    break;                else                    i += ppc->chunk_length;                continue;            }        }        ppc->chunk_pos++;