libpng 源码的使用 第6节:调整/定制

上一篇是简化用户接口

libpng 源码的使用 第五节:简化用户接口

本篇翻译libpng的调整和定制

调整和定制

这里有两个问题。 首先是更改libpng如何执行标准操作,例如内存分配,输入/输出和错误处理。

第二部分处理更复杂的事情,例如添加新的块,添加新的转换以及通常更改libpng的工作方式。

这两个都是编译时问题; 也就是说,它们通常是在编写代码时确定的,几乎不需要为用户提供更改它们的方法。

内存分配,输入/输出和错误处理

libpng中的所有内存分配,输入/输出和错误处理都通过用户可设置的回调进行。

默认例程分别在pngmem.c,pngrio.c,pngwio.c和pngerror.c中。要更改这些函数,请调用适当的png_set _ * _ fn()函数。
内存分配是通过函数png_malloc(),png_calloc()和png_free()完成的。

png_malloc()和png_free()函数当前仅调用标准C函数,而png_calloc()调用png_malloc(),然后将新分配的内存清除为零;请注意,png_calloc(png_ptr,size)与stdlib.h提供的calloc(number,size)函数不同。对某些具有分段内存架构的系统的支持有限,并且png.h声明的指针类型与此相匹配。

您将必须在应用程序中使用适当的指针。如果您喜欢使用其他分配和释放数据的方法,则可以使用png_create_read_struct_2()或png_create_write_struct_2()来注册自己的函数,如上所述。这些函数还提供了可以通过以下方式检索的空指针

mem_ptr=png_get_mem_ptr(png_ptr);

您的替换内存功能必须具有以下原型:

    png_voidp malloc_fn(png_structp png_ptr,
       png_alloc_size_t size);

    void free_fn(png_structp png_ptr, png_voidp ptr);

如果失败,您的malloc_fn()必须返回NULL。 如果png_malloc()函数从系统内存分配器或替换的malloc_fn()接收到NULL,则通常会调用png_error()。
您的free_fn()绝不会使用NULL ptr来调用,因为libpng的png_free()会在调用free_fn()之前检查NULL。
libpng中的输入/输出通过png_read()和png_write()完成,它们当前仅调用fread()和fwrite()。 FILE *存储在png_struct中,并通过png_init_io()初始化。 如果您希望更改I / O的方法,该库将提供您可以在运行时通过函数png_set_read_fn()和png_set_write_fn()设置的回调,而不是调用png_init_io()函数。 这些函数还提供可以通过函数png_get_io_ptr()检索的void指针。 例如:

    png_set_read_fn(png_structp read_ptr,
        voidp read_io_ptr, png_rw_ptr read_data_fn)

    png_set_write_fn(png_structp write_ptr,
        voidp write_io_ptr, png_rw_ptr write_data_fn,
        png_flush_ptr output_flush_fn);

    voidp read_io_ptr = png_get_io_ptr(read_ptr);
    voidp write_io_ptr = png_get_io_ptr(write_ptr);

替换I / O功能必须具有以下原型:

    void user_read_data(png_structp png_ptr,
        png_bytep data, size_t length);

    void user_write_data(png_structp png_ptr,
        png_bytep data, size_t length);

    void user_flush_data(png_structp png_ptr);

user_read_data()函数负责检测和处理数据结束错误。
为读取,写入或刷新函数提供NULL会将它们设置回使用默认的C流函数,该函数期望io_ptr指向标准的* FILE结构。除非您用定义的PNG_NO_WRITE_FLUSH构建了libpng,否则对write_data_fn和output_flush_fn中的一个使用NULL可能不是一个正确的选择,这可能是一个错误。从写流中读取是错误的,反之亦然。
libpng中的错误处理是通过png_error()和png_warning()完成的。通过png_error()处理的错误是致命的,这意味着png_error()永远不要返回到其调用者。当前,这是通过setjmp()和longjmp()处理的(除非您已经用PNG_NO_SETJMP编译了libpng,在这种情况下,它是通过PNG_ABORT()处理的),但是如果您愿意的话,可以将其更改为执行exit()之类的操作,只要您的函数不返回。
对于非严重错误,将调用png_warning()来打印警告消息,然后控制返回到调用代码。默认情况下,除非库是使用已定义的PNG_NO_CONSOLE_IO(因为您不希望收到消息)或已定义的PNG_NO_STDIO(因为fprintf()不可用)编译的,否则png_error()和png_warning()通过fprintf()在stderr上打印消息。如果要更改错误功能的行为,则需要设置自己的消息回调。这些函数通常在创建png_struct时提供。在调用png_create _ * _ struct()之后,还可以将错误和警告重定向到您自己的替换函数:

    png_set_error_fn(png_structp png_ptr,
        png_voidp error_ptr, png_error_ptr error_fn,
        png_error_ptr warning_fn);

如果为error_fn或warning_fn提供NULL,则将使用libpng默认函数,如果遇到问题,则调用fprintf()和/或longjmp()。 替换错误函数应具有以下参数:

    void user_error_fn(png_structp png_ptr,
        png_const_charp error_msg);

    void user_warning_fn(png_structp png_ptr,
        png_const_charp warning_msg);

然后,在user_error_fn或user_warning_fn中,可以通过调用以下内容来检索error_ptr(如果需要)

    png_voidp error_ptr = png_get_error_ptr(png_ptr);

使用setjmp()和longjmp()的动机是C ++ throw和catch异常处理方法。 这使代码更容易编写,因为无需检查每个函数调用的每个返回代码。 但是,longjmp之后局部变量的状态存在一些不确定性,因此用户可能要小心在setjmp返回非零后除了返回自身以外的任何操作。 有关更多详细信息,请查阅您的编译器文档。 对于另一种方法,您可能希望使用“ cexcept”功能(请参阅https://cexcept.sourceforge.io/),该功能在pngvalid.c和contrib / visupng中进行了说明。
从libpng-1.4.0开始,png_set_benign_errors()API可用。 您可以使用它来将某些错误(通常作为错误处理)作为警告来处理。

    png_set_benign_errors (png_ptr, int allowed);

    allowed: 0: treat png_benign_error() as an error.
             1: treat png_benign_error() as a warning.

从libpng-1.6.0开始,默认条件是将良性错误在读取时视为警告,在写入时视为错误。

自定义块

如果需要读取或编写自定义块,则可能需要更深入地了解libpng代码。该库现在具有用于存储和写入未知类型的块的机制。您甚至可以为自定义块声明回调。但是,如果库代码本身需要了解您的块与现有“本能”块之间的交互作用,这可能还不够好。
如果需要编写新的内部块,请首先阅读PNG规范。对它如何工作有一个初步的了解。请特别注意描述块名称的部分,并查看其他块的设计方式,以便您可以类似地进行操作。其次,检查libpng读取和写入块的部分。尝试查找与您的块相似的块并将其用作模板。可以在代码内的注释中找到更多详细信息。最好通过回调函数,而不是通过修改libpng函数,以通用方法处理私有或未知块。 pngtest.c中对此进行了说明,该文件使用回调函数来处理私有的“ vpAg”块和新的“ sTER”块,这对于libpng都是未知的。
如果您希望为数据编写自己的转换,请查看进行转换的代码部分,并查看一些较简单的代码,以了解其工作原理。尝试找到与您要添加并复制的转换类似的转换。可以在代码本身的注释中找到更多详细信息。

配置GUI /窗口平台:

如前所述,您将需要编写使用GUI界面的新错误和警告功能,并在调用png_create _ * _ struct()时将它们设置为错误和警告功能,以使它们在调用过程中可用。 结构初始化。 以后可以通过png_set_error_fn()对其进行更改。 在某些编译器上,您可能还必须更改内存分配器(png_malloc等)。

配置zlib:

有一些特殊功能可配置压缩。 也许最有用的一种更改压缩级别,该级别当前使用的输入压缩值范围为0-9。该库通常使用默认压缩级别(Z_DEFAULT_COMPRESSION = 6)。 测试表明,对于大多数图像,3-6范围内的压缩值几乎可以压缩到更高的水平,并且压缩速度更快。 对于在线应用程序,可能希望具有最大速度(Z_BEST_SPEED = 1)。 对于v0.99之后的zlib版本,您也可以不指定任何压缩(Z_NO_COMPRESSION = 0),但这将创建比仅存储原始位图更大的文件。 您可以通过调用以下命令指定压缩级别:

    #include zlib.h
    png_set_compression_level(png_ptr, level);

另一个有用的方法是减少库使用的内存级别。 内存级别默认为8,但是如果内存不足(例如,在只有640K的DOS下运行DOS),则可以将其降低。 请注意,内存级别确实会影响压缩。 除其他事项外,较低的级别将导致不可压缩的数据段以较小的存储块发出,在最坏的情况下,相应的较大相对开销高达15%。

    #include zlib.h
    png_set_compression_mem_level(png_ptr, level);

其他功能用于配置zlib。 不建议将它们正常使用,否则可能会导致写入无效的PNG文件。 有关这些含义的更多信息,请参见zlib.h。

    #include zlib.h
    png_set_compression_strategy(png_ptr,
        strategy);

    png_set_compression_window_bits(png_ptr,
        window_bits);

    png_set_compression_method(png_ptr, method);

控制IDAT块的大小(默认8192):

    png_set_compression_buffer_size(png_ptr, size);

从libpng版本1.5.4开始,可以使用其他API分别为非IDAT压缩块(例如zTXt,iTXt和iCCP)设置这些API:

    #include zlib.h
    #if PNG_LIBPNG_VER >= 10504
    png_set_text_compression_level(png_ptr, level);

    png_set_text_compression_mem_level(png_ptr, level);

    png_set_text_compression_strategy(png_ptr,
        strategy);

    png_set_text_compression_window_bits(png_ptr,
        window_bits);

    png_set_text_compression_method(png_ptr, method);
    #endif

控制行过滤

如果要控制libpng是否使用过滤器,使用了哪些过滤器以及如何选择行过滤器,则可以调用以下函数之一。行过滤器的选择和配置可能会对大小和编码速度产生重大影响,而对图像的解码速度则会产生较小的影响。默认情况下,将为RGB和灰度图像(带和不带alpha)启用过滤,但不对调色板图像或位深度小于8位/像素的任何图像启用过滤。
“ method”参数设置主要过滤方法,当前在PNG 1.2规范中仅为“ 0”。 “过滤器”参数设置每个扫描线应使用的过滤器(如果有)。可能的值是PNG_ALL_FILTERS,PNG_NO_FILTERS或PNG_FAST_FILTERS,分别打开和关闭过滤或仅打开过滤器的快速解码子集。
各个过滤器类型为PNG_FILTER_NONE,PNG_FILTER_SUB,PNG_FILTER_UP,PNG_FILTER_AVG,PNG_FILTER_PAETH,可以与“ |”按位进行“或”运算指定一个或多个要使用的过滤器。这些过滤器在PNG规范中有更详细的描述。如果要在写入图像的过程中更改过滤器类型,则应从为要使用的所有过滤器设置标志开始,以便libpng可以针对所有过滤器类型适当地初始化其内部结构。 (请注意,这意味着必须始终对第一行进行自适应过滤,因为在首次调用png_write_row()之前,libpng当前不会分配过滤缓冲区。)

    filters = PNG_NO_FILTERS;
    filters = PNG_ALL_FILTERS;
    filters = PNG_FAST_FILTERS;

    or

    filters = PNG_FILTER_NONE | PNG_FILTER_SUB |
              PNG_FILTER_UP | PNG_FILTER_AVG |
              PNG_FILTER_PAETH;

    png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE,
       filters);

              The second parameter can also be
              PNG_INTRAPIXEL_DIFFERENCING if you are
              writing a PNG to be embedded in a MNG
              datastream.  This parameter must be the
              same as the value of filter_method used
              in png_set_IHDR().

调试打印输出

宏定义PNG_DEBUG可用于请求调试打印输出。 将其设置为0到3范围内的整数。数字越大,调试信息的数量越多。 除非在PNG_DEBUG_FILE宏定义中指定了另一个文件名,否则该信息将打印到“ stderr”文件中。
当PNG_DEBUG> 0时,以下功能(宏)可用:

   png_debug(level, message)
   png_debug1(level, message, p1)
   png_debug2(level, message, p1, p2)

其中“ level”与PNG_DEBUG进行比较,以决定是否打印消息,“ message”是要打印的格式化字符串,而p1和p2是根据printf样式格式化指令要嵌入在字符串中的参数。 例如,

   png_debug1(2, "foo=%d", foo);

其实是
   if (PNG_DEBUG > 2)
      fprintf(PNG_DEBUG_FILE, "foo=%d\n", foo);
当定义了PNG_DEBUG但为零时,则没有定义宏,但是您仍然可以使用PNG_DEBUG来控制自己的调试:

   #ifdef PNG_DEBUG
       fprintf(stderr, ...
   #endif

当PNG_DEBUG = 1时,将定义宏,但仅会打印级别= 0的png_debug语句。 在这个版本的libpng中没有任何这样的语句,但是如果您插入一些语句,它们将被打印出来。

猜你喜欢

转载自blog.csdn.net/catshit322/article/details/114846061
今日推荐