在OpenCV源码剖析之ImageDecoder 中,ImageCodecInitializer注册了众多图像类型,这一节我们将讲解PNG的编解码。由于这部分比较简单,只将简单看下代码。PNG 解码是从BaseImageDecoder继承而来,PNG 编码则是从BaseImageEncoder继承而来。在grfmt_png.hpp中可以查看他们的定义:
namespace cv { class PngDecoder CV_FINAL : public BaseImageDecoder { public: PngDecoder(); virtual ~PngDecoder(); bool readData( Mat& img ) CV_OVERRIDE; bool readHeader() CV_OVERRIDE; void close(); ImageDecoder newDecoder() const CV_OVERRIDE; protected: static void readDataFromBuf(void* png_ptr, uchar* dst, size_t size); int m_bit_depth; void* m_png_ptr; // pointer to decompression structure void* m_info_ptr; // pointer to image information structure void* m_end_info; // pointer to one more image information structure FILE* m_f; int m_color_type; size_t m_buf_pos; }; class PngEncoder CV_FINAL : public BaseImageEncoder { public: PngEncoder(); virtual ~PngEncoder(); bool isFormatSupported( int depth ) const CV_OVERRIDE; bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; protected: static void writeDataToBuf(void* png_ptr, uchar* src, size_t size); static void flushBuf(void* png_ptr); }; }
对于解码主要就是readHeader和readData这两个函数了,其中readHeader实现如下:
bool PngDecoder::readHeader() { volatile bool result = false; close(); png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (png_ptr) { png_infop info_ptr = png_create_info_struct(png_ptr); png_infop end_info = png_create_info_struct(png_ptr); m_png_ptr = png_ptr; m_info_ptr = info_ptr; m_end_info = end_info; m_buf_pos = 0; if (info_ptr && end_info) { if (setjmp(png_jmpbuf(png_ptr)) == 0) { if (!m_buf.empty()) { png_set_read_fn(png_ptr, this, (png_rw_ptr)readDataFromBuf); } else { m_f = fopen(m_filename.c_str(), "rb"); if (m_f) { png_init_io(png_ptr, m_f); } } if (!m_buf.empty() || m_f) { png_uint_32 wdth, hght; int bit_depth, color_type, num_trans = 0; png_bytep trans; png_color_16p trans_values; png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &wdth, &hght, &bit_depth, &color_type, 0, 0, 0); m_width = (int)wdth; m_height = (int)hght; m_color_type = color_type; m_bit_depth = bit_depth; if ((bit_depth <= 8) || (bit_depth == 16)) { switch (color_type) { case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_PALETTE: png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); if (num_trans > 0) { m_type = CV_8UC4; } else { m_type = CV_8UC3; } break; case PNG_COLOR_TYPE_GRAY_ALPHA: case PNG_COLOR_TYPE_RGB_ALPHA: m_type = CV_8UC4; break; default: m_type = CV_8UC1; } if (bit_depth == 16) { m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); } result = true; } } } } } if (!result) { close(); } return result; }从代码中可以看出,图片数据有2种来源DataFromBuf和文件,DataFromBuf实现通过png_set_read_fn指定,其处理函数实现如下所示:
void PngDecoder::readDataFromBuf(void *_png_ptr, uchar *dst, size_t size) { png_structp png_ptr = (png_structp)_png_ptr; PngDecoder *decoder = (PngDecoder *)(png_get_io_ptr(png_ptr)); CV_Assert(decoder); const Mat& buf = decoder->m_buf; if (decoder->m_buf_pos + size > buf.cols * buf.rows * buf.elemSize()) { png_error(png_ptr, "PNG input buffer is incomplete"); return; } memcpy(dst, decoder->m_buf.ptr() + decoder->m_buf_pos, size); decoder->m_buf_pos += size; }
可以看出这个函数最主要的实现就是memcpy(dst, decoder->m_buf.ptr() + decoder->m_buf_pos, size);它是将数据从decoder->m_buf拷贝到dst buf中去,decoder->m_buf就是基类中setSource 2种实现之一的来自内存了,其类型为Mat。
在指定数据源后,通过png_read_info接口读取png chunk信息,再调用png_get_IHDR获取png文件头数据块信息,从未获取图片的宽高、颜色类型、位深信息。再根据位深和颜色类型得到对应的Mat 数据类型。
获取到图片的基本信息后,接下来就是读取数据,其实现为
bool PngDecoder::readData(Mat& img) { volatile bool result = false; AutoBuffer<uchar *> _buffer(m_height); uchar **buffer = _buffer; int color = img.channels() > 1; png_structp png_ptr = (png_structp)m_png_ptr; png_infop info_ptr = (png_infop)m_info_ptr; png_infop end_info = (png_infop)m_end_info; if (m_png_ptr && m_info_ptr && m_end_info && m_width && m_height) { if (setjmp(png_jmpbuf(png_ptr)) == 0) { int y; if ((img.depth() == CV_8U) && (m_bit_depth == 16)) { png_set_strip_16(png_ptr); } else if (!isBigEndian()) { png_set_swap(png_ptr); } if (img.channels() < 4) { /* observation: png_read_image() writes 400 bytes beyond * end of data when reading a 400x118 color png * "mpplus_sand.png". OpenCV crashes even with demo * programs. Looking at the loaded image I'd say we get 4 * bytes per pixel instead of 3 bytes per pixel. Test * indicate that it is a good idea to always ask for * stripping alpha.. 18.11.2004 Axel Walthelm */ png_set_strip_alpha(png_ptr); } else { png_set_tRNS_to_alpha(png_ptr); } if (m_color_type == PNG_COLOR_TYPE_PALETTE) { png_set_palette_to_rgb(png_ptr); } if (((m_color_type & PNG_COLOR_MASK_COLOR) == 0) && (m_bit_depth < 8)) #if (PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE >= 10209) || \ (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR == 0 && PNG_LIBPNG_VER_RELEASE >= 18) { png_set_expand_gray_1_2_4_to_8(png_ptr); } #else { png_set_gray_1_2_4_to_8(png_ptr); } #endif if ((m_color_type & PNG_COLOR_MASK_COLOR) && color) { png_set_bgr(png_ptr); // convert RGB to BGR } else if (color) { png_set_gray_to_rgb(png_ptr); // Gray->RGB } else { png_set_rgb_to_gray(png_ptr, 1, 0.299, 0.587); // RGB->Gray } png_set_interlace_handling(png_ptr); png_read_update_info(png_ptr, info_ptr); for (y = 0; y < m_height; y++) { buffer[y] = img.data + y * img.step; } png_read_image(png_ptr, buffer); png_read_end(png_ptr, end_info); result = true; } } close(); return result; }这里有点需要注意的是PNG图片存储是按大端顺序存储数据的,因此在处理前需要判断系统测存储模式。
PNG编码和解码处理差不多,这里将编码代码附上:
/////////////////////// PngEncoder /////////////////// PngEncoder::PngEncoder() { m_description = "Portable Network Graphics files (*.png)"; m_buf_supported = true; } PngEncoder::~PngEncoder() { } bool PngEncoder::isFormatSupported(int depth) const { return depth == CV_8U || depth == CV_16U; } ImageEncoder PngEncoder::newEncoder() const { return makePtr<PngEncoder>(); } void PngEncoder::writeDataToBuf(void *_png_ptr, uchar *src, size_t size) { if (size == 0) { return; } png_structp png_ptr = (png_structp)_png_ptr; PngEncoder *encoder = (PngEncoder *)(png_get_io_ptr(png_ptr)); CV_Assert(encoder && encoder->m_buf); size_t cursz = encoder->m_buf->size(); encoder->m_buf->resize(cursz + size); memcpy(&(*encoder->m_buf)[cursz], src, size); } void PngEncoder::flushBuf(void *) { } bool PngEncoder::write(const Mat& img, const std::vector<int>& params) { png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); png_infop info_ptr = 0; FILE *volatile f = 0; int y, width = img.cols, height = img.rows; int depth = img.depth(), channels = img.channels(); volatile bool result = false; AutoBuffer<uchar *> buffer; if ((depth != CV_8U) && (depth != CV_16U)) { return false; } if (png_ptr) { info_ptr = png_create_info_struct(png_ptr); if (info_ptr) { if (setjmp(png_jmpbuf(png_ptr)) == 0) { if (m_buf) { png_set_write_fn(png_ptr, this, (png_rw_ptr)writeDataToBuf, (png_flush_ptr)flushBuf); } else { f = fopen(m_filename.c_str(), "wb"); if (f) { png_init_io(png_ptr, (png_FILE_p)f); } } int compression_level = -1; // Invalid value to allow setting 0-9 as valid int compression_strategy = IMWRITE_PNG_STRATEGY_RLE; // Default strategy bool isBilevel = false; for (size_t i = 0; i < params.size(); i += 2) { if (params[i] == IMWRITE_PNG_COMPRESSION) { compression_strategy = IMWRITE_PNG_STRATEGY_DEFAULT; // Default strategy compression_level = params[i + 1]; compression_level = MIN(MAX(compression_level, 0), Z_BEST_COMPRESSION); } if (params[i] == IMWRITE_PNG_STRATEGY) { compression_strategy = params[i + 1]; compression_strategy = MIN(MAX(compression_strategy, 0), Z_FIXED); } if (params[i] == IMWRITE_PNG_BILEVEL) { isBilevel = params[i + 1] != 0; } } if (m_buf || f) { if (compression_level >= 0) { png_set_compression_level(png_ptr, compression_level); } else { // tune parameters for speed // (see http://wiki.linuxquestions.org/wiki/Libpng) png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB); png_set_compression_level(png_ptr, Z_BEST_SPEED); } png_set_compression_strategy(png_ptr, compression_strategy); png_set_IHDR(png_ptr, info_ptr, width, height, depth == CV_8U ? isBilevel ? 1 : 8 : 16, channels == 1 ? PNG_COLOR_TYPE_GRAY : channels == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png_ptr, info_ptr); if (isBilevel) { png_set_packing(png_ptr); } png_set_bgr(png_ptr); if (!isBigEndian()) { png_set_swap(png_ptr); } buffer.allocate(height); for (y = 0; y < height; y++) { buffer[y] = img.data + y * img.step; } png_write_image(png_ptr, buffer); png_write_end(png_ptr, info_ptr); result = true; } } } } png_destroy_write_struct(&png_ptr, &info_ptr); if (f) { fclose((FILE *)f); } return result;
从上部分代码实现可以看出OpenCV的PNG支持实现还是挺简单的,主要还是调用了libpng接口实现。