libpng 源码的使用 第四节:写 (接口)

上一篇

libpng 源码的使用 第四节:写 (设置)

主要是一些初始化及辅助信息设置,及整体概念介绍,本篇主要是写操作的接口即用户接口介绍

高级写接口

在这一点上,有两种方法可以进行: 通过高级写接口或一系列低级写操作。如果信息结构中存在图像数据,则可以使用高级接口。 允许所有定义的输出转换,并通过以下掩码启用。

   PNG_TRANSFORM_IDENTITY      No transformation
    PNG_TRANSFORM_PACKING       Pack 1, 2 and 4-bit samples
    PNG_TRANSFORM_PACKSWAP      Change order of packed
                                pixels to LSB first
    PNG_TRANSFORM_INVERT_MONO   Invert monochrome images
    PNG_TRANSFORM_SHIFT         Normalize pixels to the
                                sBIT depth
    PNG_TRANSFORM_BGR           Flip RGB to BGR, RGBA
                                to BGRA
    PNG_TRANSFORM_SWAP_ALPHA    Flip RGBA to ARGB or GA
                                to AG
    PNG_TRANSFORM_INVERT_ALPHA  Change alpha from opacity
                                to transparency
    PNG_TRANSFORM_SWAP_ENDIAN   Byte-swap 16-bit samples
    PNG_TRANSFORM_STRIP_FILLER        Strip out filler
                                      bytes (deprecated).
    PNG_TRANSFORM_STRIP_FILLER_BEFORE Strip out leading
                                      filler bytes
    PNG_TRANSFORM_STRIP_FILLER_AFTER  Strip out trailing
                                      filler bytes

如果信息结构中有有效的图像数据(可以使用png_set_rows()将图像数据放入信息结构中),则只需执行以下操作:

    png_write_png(png_ptr, info_ptr, png_transforms, NULL)

其中png_transforms是一个整数,其中包含一些转换标志集的按位或。 此调用等效于png_write_info(),然后遵循由转换掩码指示的一组转换,然后是png_write_image(),最后是png_write_end()。
(此调用的最终参数尚未使用。总有一天,它可能指向某些将来的输出转换所需的转换参数。)
使用png_write_png()时,必须使用png_transforms且不能调用任何png_set_transform()函数。

底层写接口

如果要改用低级路由,则现在可以将所有文件信息写入实际的图像数据。 您可以通过调用png_write_info()来实现。

    png_write_info(png_ptr, info_ptr);

请注意,在png_write_info()之前,您可能需要执行一次转换。 在PNG文件中,图像中的Alpha通道为不透明度级别。 如果以透明级别提供数据,则可以在写入之前反转alpha通道,以使0完全透明,而255(在8位或调色板图像中)或65535(在16位图像中)完全不透明。 使用

    png_set_invert_alpha(png_ptr);

这必须出现在png_write_info()之前,而不是其他转换之后,因为对于调色板图像,tRNS块数据必须在tRNS之前反转
  块被写入。 如果您的图像不是调色板图像,则无需更改tRNS数据(在这种情况下,该颜色表示要呈现为透明的单一颜色),并且可以在调用png_write_info()之后安全地进行此转换。
如果需要在存在PLTE的情况下编写要显示在PLTE块之前的私有块,则可以分两步编写PNG信息,并在其中插入代码以编写自己的块:

    png_write_info_before_PLTE(png_ptr, info_ptr);
    png_set_unknown_chunks(png_ptr, info_ptr, ...);
    png_write_info(png_ptr, info_ptr);

写入文件信息后,可以设置该库以处理图像数据的任何特殊转换。 将按照应该发生的顺序描述转换数据的各种方式。 这很重要,因为其中一些会更改数据的颜色类型和/或位深,而其他一些仅适用于某些颜色类型和位深。 即使每个转换都检查它是否具有可以执行某些操作的数据,也应确保仅在对数据有效的情况下才启用转换。 例如,不要在灰度数据上交换红色和蓝色。
PNG文件存储RGB像素,打包成3或6个字节。 此代码告诉库将每个像素具有4或8字节的输入数据剥离到3或6字节(或将2或4字节的灰度+填充数据剥离到每个像素1或2字节)。

    png_set_filler(png_ptr, 0, PNG_FILLER_BEFORE);

其中未使用0,其位置是PNG_FILLER_BEFORE或PNG_FILLER_AFTER,具体取决于像素中的填充字节是存储的XRGB还是RGBX。
PNG文件将位深度1、2和4的像素打包到尽可能小的字节中,例如1位文件每字节8个像素。如果以每字节1像素提供数据,请使用此代码 ,它将正确地将像素打包为一个字节:

    png_set_packing(png_ptr);

PNG文件将可能的位深度减小为1、2、4、8和16。如果您的数据具有其他位深度,则可以将sBIT块写入文件中,以便解码器可以根据需要恢复原始数据。

    /* Set the true bit depth of the image data */
    if (color_type & PNG_COLOR_MASK_COLOR)
    {
       sig_bit.red = true_bit_depth;
       sig_bit.green = true_bit_depth;
       sig_bit.blue = true_bit_depth;
    }

    else
    {
       sig_bit.gray = true_bit_depth;
    }

    if (color_type & PNG_COLOR_MASK_ALPHA)
    {
       sig_bit.alpha = true_bit_depth;
    }

    png_set_sBIT(png_ptr, info_ptr, &sig_bit);

如果数据以除PNG支持的位深度以外的位深度存储在行缓冲区中(例如,对于4位PNG,3位数据在0-7范围内),这将缩放这些值以显示为正确的位深度 PNG要求的理由。

    png_set_shift(png_ptr, &sig_bit);

PNG文件以网络字节顺序(big-endian,即,最高有效位在前)存储16位像素。 如果以其他方式(即,最低有效位在前,即PC的存储方式)提供它们,则将使用此代码:

    if (bit_depth > 8)
       png_set_swap(png_ptr);

如果使用的是像素压缩图像(1、2或4位/像素),并且需要更改像素压缩为字节的顺序,则可以使用:

    if (bit_depth < 8)
       png_set_packswap(png_ptr);

PNG文件以红色,绿色,蓝色的顺序存储3个彩色像素。 如果以蓝色,绿色,红色提供它们,则将使用以下代码:

    png_set_bgr(png_ptr);

PNG文件将单色描述为黑色为零,白色为1。 如果提供的像素具有相反的颜色(黑色为一,白色为零),则将使用以下代码:

    png_set_invert_mono(png_ptr);

最后,如果现有的转换函数都不满足您的需求,则可以编写自己的转换函数。 这是通过使用以下命令设置回调来完成的

    png_set_write_user_transform_fn(png_ptr,
       write_transform_fn);

您必须提供功能

    void write_transform_fn(png_structp png_ptr, png_row_infop
       row_info, png_bytep data)

有关工作示例,请参见pngtest.c。 在处理任何其他转换之前,将调用您的函数。 如果supportedlibpng也提供了一个信息例程,该例程可以从您的回调中调用:

   png_get_current_row_number(png_ptr);
   png_get_current_pass_number(png_ptr);

这将返回传递给转换的当前行。 对于隔行图像,返回的值是输入子图像图像中的行。 使用PNG_ROW_FROM_PASS_ROW(行,pass)和PNG_COL_FROM_PASS_COL(行,pass)来查找给定隔行扫描子图像像素(行,行,pass)的输出像素(x,y)。
上面有关隔行处理的讨论包含有关如何使用这些值的更多信息。
您还可以设置一个指向用户结构的指针,以供您的回调函数使用。

    png_set_user_transform_info(png_ptr, user_ptr, 0, 0);

写入时将忽略此函数的user_channels和user_depth参数; 您可以将它们设置为零,如图所示。
您可以通过函数png_get_user_transform_ptr()检索指针。 例如:

    voidp write_user_transform_ptr =
       png_get_user_transform_ptr(png_ptr);

可以在写入一定数量的行之后,使libpng手动或自动刷新任何未决的输出。 要一次刷新输出流,请执行以下操作:

    png_write_flush(png_ptr);

并在写入一定数量的扫描线后让libpng定期刷新输出流,请调用:

    png_set_flush(png_ptr, nrows);

请注意,行之间的距离是从上次调用png_write_flush()以来的距离,或者从图像的第一行开始(如果从未调用过)。 因此,如果先写入50行,然后写入png_set_flush 25,它将刷新下一条扫描线的输出,此后每25行刷新一次,除非在写入25行之前调用png_write_flush()。 如果行数太小(对于640像素宽的RGB图像,少于大约10条线),则图像压缩可能会显着降低(尽管这对于实时应用程序是可以接受的)。 与不使用刷新的图像相比,不频繁的刷新只会使压缩性能降低百分之几。

意思就是在使用 png_write_rows 或者 png_write_row 手动写入图像数据的时候,最好调用 png_write_flush 对图像数据进行刷新。如果不手动调用,可以设置 png_set_flush (n)自动调用 n为 调用间隔,调用间隔越大,压缩率越高。 这样理解的,还没有详细看 png_write_flush 的实现。

写入图像数据

转换就是这样。 现在您可以写入图像数据了。 最简单的方法是在一个函数调用中。 如果整个图像都在内存中,则只需调用png_write_image(),libpng就会写入图像。 您将需要将指针数组传递给每一行。 该函数会自动处理隔行扫描,因此您无需调用png_set_interlace_handling()或多次调用此函数,也无需调用png_write_rows()所需的其他任何东西。

    png_write_image(png_ptr, row_pointers);

其中row_pointers是:

    png_byte *row_pointers[height];

您可以指向void或char或任何用于像素的对象。
如果您不想一次写入整个图像,则可以改用png_write_rows()。 如果文件不是隔行扫描的,这很简单:

    png_write_rows(png_ptr, row_pointers,
       number_of_rows);

row_pointers与png_write_image()调用中的相同。
如果您一次只写一行,则可以使用一个row_pointer而不是row_pointers数组来做到这一点:

    png_bytep row_pointer = row;

    png_write_row(png_ptr, row_pointer);

当文件隔行扫描时,事情可能会变得更加复杂。 当前(自1999年7月发布的PNG规范版本1.2)定义的唯一PNG隔行扫描方案是“ Adam7”隔行扫描方案,它将一个图像分解为七个大小不同的较小图像。 libpng将为您构建这些图像,或者您可以自己完成。 如果要自己构建它们,请参阅PNG规范以了解何时写入的像素的详细信息。
如果您不希望libpng处理隔行扫描细节,只需使用png_set_interlace_handling()并以正确的次数调用png_write_rows()来写入所有子图像(png_set_interlace_handling()返回子图像的数目。)
如果要使用libpng构建子图像,请在开始编写任何行之前调用此方法:

    number_of_passes = png_set_interlace_handling(png_ptr);

这将返回所需的通过次数。 当前为7,但是如果添加其他隔行扫描类型,则可能会更改。
然后写完整的图像number_of_passes次。

    png_write_rows(png_ptr, row_pointers, number_of_rows);

写入隔行扫描图像之前,请仔细考虑。通常,读取此类图像的代码会在执行任何处理之前将所有图像数据未经压缩地读入内存。只有可以即时显示图像的代码才能利用隔行扫描,即使这样,图像也必须恰好是输出设备的正确大小,因为缩放图像需要相邻的像素,并且只有在所有遍数都经过后才能使用这些像素。被阅读。
如果您确实写了隔行扫描图像,则几乎不需要自己处理隔行扫描。调用png_set_interlace_handling()并使用上述方法。
唯一可以想象的是,您真正需要编写隔行扫描图像的过程是,当您逐遍读取一次并对其进行了逐像素转换时,如上面的读取代码中所述。在这种情况下,请使用PNG_PASS_ROWS和PNG_PASS_COLS宏依次确定每个子图像的大小,并简单地写入从读取代码中获得的行。

完成顺序写入

完成写入图像后,应该完成写入文件。 如果您有兴趣编写评论或时间,则应传递适当填充的png_info指针。 如果您不感兴趣,则可以传递NULL。

    png_write_end(png_ptr, info_ptr);

完成后,可以释放libpng使用的所有内存,如下所示:

    png_destroy_write_struct(&png_ptr, &info_ptr);

也可以使用以下函数分别释放指向libpng分配的存储的info_ptr成员:

    png_free_data(png_ptr, info_ptr, mask, seq)

    mask  - identifies data to be freed, a mask
            containing the bitwise OR of one or
            more of
              PNG_FREE_PLTE, PNG_FREE_TRNS,
              PNG_FREE_HIST, PNG_FREE_ICCP,
              PNG_FREE_PCAL, PNG_FREE_ROWS,
              PNG_FREE_SCAL, PNG_FREE_SPLT,
              PNG_FREE_TEXT, PNG_FREE_UNKN,
            or simply PNG_FREE_ALL

    seq   - sequence number of item to be freed
            (-1 for all items)

当相关存储已被释放,尚未分配,或者由用户而非libpng分配时,可以安全地调用此函数,在这些情况下将不执行任何操作。 如果仅允许所选数据类型的一项(例如PLTE),则忽略“ seq”参数。 如果“ seq”不为-1,并且掩码中标识的数据类型允许多个项目(例如文本或sPLT),则仅释放结构中的第n个项目,其中n为“ seq”。
如果分配的数据(例如使用png_set_ *传递给libpng的调色板),则必须在调用png_destroy_write_struct()之前将其释放。
默认行为是仅释放由libpng内部分配的数据。 可以更改它,以便libpng不会释放数据,或者它将释放用户使用png_malloc()或png_calloc()分配并通过png_set _ *()函数传入的数据,

    png_data_freer(png_ptr, info_ptr, freer, mask)

    freer  - one of
               PNG_DESTROY_WILL_FREE_DATA
               PNG_SET_WILL_FREE_DATA
               PNG_USER_WILL_FREE_DATA

    mask   - which data elements are affected
             same choices as in png_free_data()

例如,要将某些数据的责任从读取结构转移到写入结构,可以使用

    png_data_freer(read_ptr, read_info_ptr,
       PNG_USER_WILL_FREE_DATA,
       PNG_FREE_PLTE|PNG_FREE_tRNS|PNG_FREE_hIST)

    png_data_freer(write_ptr, write_info_ptr,
       PNG_DESTROY_WILL_FREE_DATA,
       PNG_FREE_PLTE|PNG_FREE_tRNS|PNG_FREE_hIST)

从而短暂地将释放用户的责任重新分配给用户,但之后立即又将其再次分配给write_destroy函数。完成此操作后,销毁读取结构并继续在写入结构中使用PLTE,tRNS和hIST数据将是安全的。
此功能仅影响已经分配的数据。您可以先调用此函数,然后再调用png_set _ *()函数以控制用户还是png_destroy _ *()应该释放数据。当用户对libpng分配的数据承担责任时,应用程序必须使用png_free()释放它,并且当用户将对用户分配的数据的责任转移给libpng时,用户必须已使用png_malloc()或png_calloc()分配它。
如果您分别分配了text_ptr.text,text_ptr.lang和text_ptr.translated_keyword,则不要将释放text_ptr的责任转移给libpng,因为当libpng填充png_text结构时,它将这些成员与键成员结合在一起,而png_free_data()仅会释放text_ptr.key。同样,如果您将释放text_ptr的责任从libpng转移到您的应用程序,则您的应用程序不得单独释放这些成员。有关编写PNG图像的更紧凑的示例,请参见文件example.c。

写入机器翻译完,下一篇是简单用户接口,对使用也比较重要。

猜你喜欢

转载自blog.csdn.net/catshit322/article/details/114836992