由于工作需要,需要在内存中直接对数据进行png编码,然后再解码以测试其解码的速度,与现有的图像压缩方法进行比较。由于初次接触libpng,而网上这种直接在内存中操作的文章并不多,从头学习要花不少的时间。鉴于此,我借助第3方库:opencv库,来学习opencv是怎么在内存中对数据进行操作的(opencv的imread和imwrite PNG格式的数据,底层就是使用libpng操作的)。
opencv中的imread和imwrite方法就是对相应格式的数据的一种编解码,其源码在modules\imgcodecs\src中,找到grfmt_base.hpp
class BaseImageDecoder
{
public:
BaseImageDecoder();
virtual ~BaseImageDecoder() {}
int width() const { return m_width; }
int height() const { return m_height; }
virtual int type() const { return m_type; }
virtual bool setSource( const String& filename );\\设置解码文件路径
virtual bool setSource( const Mat& buf );\\解码数据源,不设置的话,将使用的是解码指定文件路径的文件
virtual int setScale( const int& scale_denom );
virtual bool readHeader() = 0;
virtual bool readData( Mat& img ) = 0;
/// Called after readData to advance to the next page, if any.
virtual bool nextPage() { return false; }
virtual size_t signatureLength() const;
virtual bool checkSignature( const String& signature ) const;
virtual ImageDecoder newDecoder() const;
protected:
int m_width; // width of the image ( filled by readHeader )
int m_height; // height of the image ( filled by readHeader )
int m_type;
int m_scale_denom;
String m_filename;
String m_signature;
Mat m_buf;
bool m_buf_supported;
};
///////////////////////////// base class for encoders ////////////////////////////
class BaseImageEncoder
{
public:
BaseImageEncoder();
virtual ~BaseImageEncoder() {}
virtual bool isFormatSupported( int depth ) const;
virtual bool setDestination( const String& filename );\\设置输出接口为文件,将文件名告诉编码器
virtual bool setDestination( std::vector<uchar>& buf );\\这个函数实际上是将buf交给m_buf,是编码后的数据存放的地方,如果不设置m_buf,那么默认将是IO输出为文件
virtual bool write( const Mat& img, const std::vector<int>& params ) = 0;\\编码
virtual bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params);
virtual String getDescription() const;
virtual ImageEncoder newEncoder() const;
virtual void throwOnEror() const;
protected:
String m_description;
String m_filename;
std::vector<uchar>* m_buf;
\\保存编码后的数据,可变数组
bool m_buf_supported;
String m_last_error;
};
从父类的方法能看出Png子类应该有两种方法:一个是来源于文件,另一个就是buffer了,其编解码器中均使用 m_buf来表示这个buffer。
找到文件grfmt_png.hpp
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);
};
而看到这些之后,基本就能看懂grfmt_png.cpp里是怎么分装libpng了,那么,就使用粘贴复制大法,完成自己的一个基于内存的libpng编解码的类吧。
一下是修改的内存操作png的hpp和cpp的代码如下:
#pragma once #include <png.h> #include <string> #include <opencv2/core/core.hpp> bool isBigEndian() { unsigned short usData = 0x1122; unsigned char *pucData = (unsigned char*)&usData; return (*pucData == 0x22); } enum ImwritePNGFlags_zc { IMWRITE_PNG_STRATEGY_DEFAULT = 0, IMWRITE_PNG_STRATEGY_FILTERED = 1, IMWRITE_PNG_STRATEGY_HUFFMAN_ONLY = 2, IMWRITE_PNG_STRATEGY_RLE = 3, IMWRITE_PNG_STRATEGY_FIXED = 4 }; enum ImwriteFlags_zc { IMWRITE_PNG_COMPRESSION = 16, IMWRITE_PNG_STRATEGY = 17, IMWRITE_PNG_BILEVEL = 18 }; class PngImageDecoder { public: PngImageDecoder(); ~PngImageDecoder(); int width() const { return m_width; } int height() const { return m_height; } bool setSource(const std::string& filename); bool setSource(const cv::Mat& buf); int setScale(const int& scale_denom); bool readHeader(); bool readData(cv::Mat& img); void close(); //virtual size_t signatureLength() const; //virtual bool checkSignature(const String& signature) const; //PngImageDecoder newDecoder() const; protected: int m_width; // width of the image ( filled by readHeader ) int m_height; // height of the image ( filled by readHeader ) int m_type; int m_scale_denom; std::string m_filename; //String m_signature; cv::Mat m_buf; 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; }; ///////////////////////////// base class for encoders //////////////////////////// class PngImageEncoder { public: PngImageEncoder(); ~PngImageEncoder() {} //bool isFormatSupported(int depth) const; bool setDestination(const std::string& filename); bool setDestination(std::vector<uchar>& buf); bool write(const cv::Mat& img, const std::vector<int>& params); //virtual bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params); //virtual String getDescription() const; //PngImageEncoder newEncoder() const; //virtual void throwOnEror() const; protected: //String m_description; static void writeDataToBuf(void* png_ptr, uchar* src, size_t size); static void flushBuf(void* png_ptr); std::string m_filename; std::vector<uchar>* m_buf; //bool m_buf_supported; //String m_last_error; };
#include "stdafx.h" #include "pngCoder.h" #define Z_NO_COMPRESSION 0 #define Z_BEST_SPEED 1 #define Z_BEST_COMPRESSION 9 #define Z_DEFAULT_COMPRESSION (-1) #define Z_FILTERED 1 #define Z_HUFFMAN_ONLY 2 #define Z_RLE 3 #define Z_FIXED 4 #define Z_DEFAULT_STRATEGY 0 //#include <zlib.h> PngImageEncoder::PngImageEncoder() { m_buf = 0; } bool PngImageEncoder::setDestination(const std::string& filename) { m_filename = filename; m_buf = 0; return true; } bool PngImageEncoder::setDestination(std::vector<uchar>& buf) { m_buf = &buf; m_buf->clear(); m_filename = std::string(); return true; } void PngImageEncoder::writeDataToBuf(void* _png_ptr, uchar* src, size_t size) { if (size == 0) return; png_structp png_ptr = reinterpret_cast<png_structp>(_png_ptr); PngImageEncoder* encoder = (PngImageEncoder*)(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 PngImageEncoder::flushBuf(void*) { } bool PngImageEncoder::write(const cv::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; cv::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 { 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; unsigned char** ptr = buffer.operator uchar **(); //png_write_image(png_ptr, buffer.data()); png_write_image(png_ptr, ptr); png_write_end(png_ptr, info_ptr); result = true; } } } } png_destroy_write_struct(&png_ptr, &info_ptr); if (f) fclose((FILE*)f); return result; } //------------------decoder--------------- PngImageDecoder::PngImageDecoder() { //m_signature = "\x89\x50\x4e\x47\xd\xa\x1a\xa"; m_color_type = 0; m_png_ptr = 0; m_info_ptr = m_end_info = 0; m_f = 0; //m_buf_supported = true; m_buf_pos = 0; m_bit_depth = 0; } PngImageDecoder::~PngImageDecoder() { close(); } bool PngImageDecoder::setSource(const std::string& filename) { m_filename = filename; m_buf.release(); return true; } bool PngImageDecoder::setSource(const cv::Mat& buf) { m_filename = std::string(); m_buf = buf; return true; } int PngImageDecoder::setScale(const int& scale_denom) { int temp = m_scale_denom; m_scale_denom = scale_denom; return temp; } void PngImageDecoder::readDataFromBuf(void* _png_ptr, uchar* dst, size_t size) { png_structp png_ptr = (png_structp)_png_ptr; PngImageDecoder* decoder = (PngImageDecoder*)(png_get_io_ptr(png_ptr)); //CV_Assert(decoder); const cv::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; } void PngImageDecoder::close() { if (m_f) { fclose(m_f); m_f = 0; } if (m_png_ptr) { 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; png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); m_png_ptr = m_info_ptr = m_end_info = 0; } } bool PngImageDecoder::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; } bool PngImageDecoder::readData(cv::Mat& img) { volatile bool result = false; cv::AutoBuffer<uchar*> _buffer(m_height); uchar** buffer = _buffer.operator uchar **(); bool 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) { 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; }