在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接口实现。