mirror of
https://github.com/opencv/opencv.git
synced 2025-06-24 04:36:26 +08:00
1728 lines
56 KiB
C++
1728 lines
56 KiB
C++
/*M///////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
|
|
//
|
|
// By downloading, copying, installing or using the software you agree to this license.
|
|
// If you do not agree to this license, do not download, install,
|
|
// copy or use the software.
|
|
//
|
|
//
|
|
// License Agreement
|
|
// For Open Source Computer Vision Library
|
|
//
|
|
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
|
|
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
|
|
// Third party copyrights are property of their respective owners.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without modification,
|
|
// are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistribution's of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
//
|
|
// * Redistribution's in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
//
|
|
// * The name of the copyright holders may not be used to endorse or promote products
|
|
// derived from this software without specific prior written permission.
|
|
//
|
|
// This software is provided by the copyright holders and contributors "as is" and
|
|
// any express or implied warranties, including, but not limited to, the implied
|
|
// warranties of merchantability and fitness for a particular purpose are disclaimed.
|
|
// In no event shall the Intel Corporation or contributors be liable for any direct,
|
|
// indirect, incidental, special, exemplary, or consequential damages
|
|
// (including, but not limited to, procurement of substitute goods or services;
|
|
// loss of use, data, or profits; or business interruption) however caused
|
|
// and on any theory of liability, whether in contract, strict liability,
|
|
// or tort (including negligence or otherwise) arising in any way out of
|
|
// the use of this software, even if advised of the possibility of such damage.
|
|
//
|
|
//M*/
|
|
|
|
#include "precomp.hpp"
|
|
|
|
#ifdef HAVE_PNG
|
|
|
|
/****************************************************************************************\
|
|
This part of the file implements PNG codec on base of libpng library,
|
|
in particular, this code is based on example.c from libpng
|
|
(see otherlibs/_graphics/readme.txt for copyright notice)
|
|
and png2bmp sample from libpng distribution (Copyright (C) 1999-2001 MIYASAKA Masaru)
|
|
\****************************************************************************************/
|
|
|
|
/****************************************************************************\
|
|
*
|
|
* this file includes some modified part of apngasm and APNG Optimizer 1.4
|
|
* both have zlib license.
|
|
*
|
|
****************************************************************************/
|
|
|
|
|
|
/* apngasm
|
|
*
|
|
* The next generation of apngasm, the APNG Assembler.
|
|
* The apngasm CLI tool and library can assemble and disassemble APNG image files.
|
|
*
|
|
* https://github.com/apngasm/apngasm
|
|
|
|
|
|
* APNG Optimizer 1.4
|
|
*
|
|
* Makes APNG files smaller.
|
|
*
|
|
* http://sourceforge.net/projects/apng/files
|
|
*
|
|
* Copyright (c) 2011-2015 Max Stepin
|
|
* maxst at users.sourceforge.net
|
|
*
|
|
* zlib license
|
|
* ------------
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*
|
|
*/
|
|
|
|
#ifndef _LFS64_LARGEFILE
|
|
# define _LFS64_LARGEFILE 0
|
|
#endif
|
|
#ifndef _FILE_OFFSET_BITS
|
|
# define _FILE_OFFSET_BITS 0
|
|
#endif
|
|
|
|
#include "grfmt_png.hpp"
|
|
#include <opencv2/core/utils/logger.hpp>
|
|
|
|
#if defined _MSC_VER && _MSC_VER >= 1200
|
|
// interaction between '_setjmp' and C++ object destruction is non-portable
|
|
#pragma warning( disable: 4611 )
|
|
#pragma warning( disable: 4244 )
|
|
#endif
|
|
|
|
// the following defines are a hack to avoid multiple problems with frame pointer handling and setjmp
|
|
// see http://gcc.gnu.org/ml/gcc/2011-10/msg00324.html for some details
|
|
#define mingw_getsp(...) 0
|
|
#define __builtin_frame_address(...) 0
|
|
|
|
namespace cv
|
|
{
|
|
|
|
const uint32_t id_IHDR = 0x52444849; // PNG header
|
|
const uint32_t id_acTL = 0x4C546361; // Animation control chunk
|
|
const uint32_t id_fcTL = 0x4C546366; // Frame control chunk
|
|
const uint32_t id_IDAT = 0x54414449; // first frame and/or default image
|
|
const uint32_t id_fdAT = 0x54416466; // Frame data chunk
|
|
const uint32_t id_PLTE = 0x45544C50;
|
|
const uint32_t id_bKGD = 0x44474B62;
|
|
const uint32_t id_tRNS = 0x534E5274;
|
|
const uint32_t id_IEND = 0x444E4549; // end/footer chunk
|
|
|
|
APNGFrame::APNGFrame()
|
|
{
|
|
_pixels = NULL;
|
|
_width = 0;
|
|
_height = 0;
|
|
_colorType = 0;
|
|
_paletteSize = 0;
|
|
_transparencySize = 0;
|
|
_delayNum = 1;
|
|
_delayDen = 1000;
|
|
}
|
|
|
|
APNGFrame::~APNGFrame() {}
|
|
|
|
bool APNGFrame::setMat(const cv::Mat& src, unsigned delayNum, unsigned delayDen)
|
|
{
|
|
_delayNum = delayNum;
|
|
_delayDen = delayDen;
|
|
|
|
if (!src.empty())
|
|
{
|
|
png_uint_32 rowbytes = src.depth() == CV_16U ? src.cols * src.channels() * 2 : src.cols * src.channels();
|
|
_width = src.cols;
|
|
_height = src.rows;
|
|
_colorType = src.channels() == 1 ? PNG_COLOR_TYPE_GRAY : src.channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
|
|
_pixels = src.data;
|
|
_rows.resize(_height);
|
|
|
|
for (unsigned int i = 0; i < _height; ++i)
|
|
_rows[i] = _pixels + i * rowbytes;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void APNGFrame::setWidth(unsigned int width) { _width = width; }
|
|
void APNGFrame::setHeight(unsigned int height) { _height = height;}
|
|
void APNGFrame::setColorType(unsigned char colorType) { _colorType = colorType; }
|
|
void APNGFrame::setPalette(const rgb* palette) { std::copy(palette, palette + 256, _palette); }
|
|
void APNGFrame::setTransparency(const unsigned char* transparency) { std::copy(transparency, transparency + 256, _transparency); }
|
|
void APNGFrame::setPaletteSize(int paletteSize) { _paletteSize = paletteSize; }
|
|
void APNGFrame::setTransparencySize(int transparencySize) { _transparencySize = transparencySize; }
|
|
void APNGFrame::setDelayNum(unsigned int delayNum) { _delayNum = delayNum; }
|
|
void APNGFrame::setDelayDen(unsigned int delayDen) { _delayDen = delayDen; }
|
|
void APNGFrame::setPixels(unsigned char* pixels) { _pixels = pixels; }
|
|
|
|
PngDecoder::PngDecoder()
|
|
{
|
|
m_signature = "\x89\x50\x4e\x47\xd\xa\x1a\xa";
|
|
m_color_type = 0;
|
|
m_png_ptr = nullptr;
|
|
m_info_ptr = m_end_info = nullptr;
|
|
m_f = 0;
|
|
m_buf_supported = true;
|
|
m_buf_pos = 0;
|
|
m_bit_depth = 0;
|
|
m_frame_no = 0;
|
|
w0 = 0;
|
|
h0 = 0;
|
|
x0 = 0;
|
|
y0 = 0;
|
|
delay_num = 0;
|
|
delay_den = 0;
|
|
dop = 0;
|
|
bop = 0;
|
|
}
|
|
|
|
PngDecoder::~PngDecoder()
|
|
{
|
|
if( m_f )
|
|
{
|
|
fclose( m_f );
|
|
m_f = nullptr;
|
|
}
|
|
|
|
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 = nullptr;
|
|
}
|
|
}
|
|
|
|
ImageDecoder PngDecoder::newDecoder() const
|
|
{
|
|
return makePtr<PngDecoder>();
|
|
}
|
|
|
|
void PngDecoder::readDataFromBuf( void* _png_ptr, unsigned char* 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;
|
|
}
|
|
|
|
bool PngDecoder::readHeader()
|
|
{
|
|
volatile bool result = false;
|
|
|
|
png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );
|
|
png_infop info_ptr = nullptr;
|
|
png_infop end_info = nullptr;
|
|
|
|
if( png_ptr )
|
|
{
|
|
info_ptr = png_create_info_struct( png_ptr );
|
|
end_info = png_create_info_struct( png_ptr );
|
|
|
|
m_buf_pos = 0;
|
|
|
|
if( info_ptr && end_info )
|
|
{
|
|
if( setjmp( png_jmpbuf( png_ptr ) ) == 0 )
|
|
{
|
|
unsigned char sig[8];
|
|
uint32_t id = 0;
|
|
Chunk chunk;
|
|
|
|
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_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
|
|
return false;
|
|
}
|
|
png_init_io(png_ptr, m_f);
|
|
|
|
if (fread(sig, 1, 8, m_f))
|
|
id = read_chunk(m_chunkIHDR);
|
|
}
|
|
|
|
if (id != id_IHDR)
|
|
{
|
|
read_from_io(&sig, 8, 1);
|
|
id = read_chunk(m_chunkIHDR);
|
|
}
|
|
|
|
if (!(id == id_IHDR && m_chunkIHDR.p.size() == 25))
|
|
{
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
|
|
return false;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
m_is_fcTL_loaded = false;
|
|
id = read_chunk(chunk);
|
|
|
|
if ((m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total()))
|
|
{
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
|
|
return false;
|
|
}
|
|
|
|
if (id == id_IDAT)
|
|
{
|
|
if (m_f)
|
|
fseek(m_f, 0, SEEK_SET);
|
|
else
|
|
m_buf_pos = 0;
|
|
break;
|
|
}
|
|
|
|
if (id == id_acTL && chunk.p.size() == 20)
|
|
{
|
|
m_animation.loop_count = png_get_uint_32(&chunk.p[12]);
|
|
|
|
if (chunk.p[8] > 0)
|
|
{
|
|
chunk.p[8] = 0;
|
|
chunk.p[9] = 0;
|
|
m_frame_count = png_get_uint_32(&chunk.p[8]);
|
|
m_frame_count++;
|
|
}
|
|
else
|
|
m_frame_count = png_get_uint_32(&chunk.p[8]);
|
|
}
|
|
|
|
if (id == id_fcTL)
|
|
{
|
|
m_is_fcTL_loaded = true;
|
|
w0 = png_get_uint_32(&chunk.p[12]);
|
|
h0 = png_get_uint_32(&chunk.p[16]);
|
|
x0 = png_get_uint_32(&chunk.p[20]);
|
|
y0 = png_get_uint_32(&chunk.p[24]);
|
|
delay_num = png_get_uint_16(&chunk.p[28]);
|
|
delay_den = png_get_uint_16(&chunk.p[30]);
|
|
dop = chunk.p[32];
|
|
bop = chunk.p[33];
|
|
}
|
|
|
|
if (id == id_bKGD)
|
|
{
|
|
int bgcolor = png_get_uint_32(&chunk.p[8]);
|
|
m_animation.bgcolor[3] = (bgcolor >> 24) & 0xFF;
|
|
m_animation.bgcolor[2] = (bgcolor >> 16) & 0xFF;
|
|
m_animation.bgcolor[1] = (bgcolor >> 8) & 0xFF;
|
|
m_animation.bgcolor[0] = bgcolor & 0xFF;
|
|
}
|
|
|
|
if (id == id_PLTE || id == id_tRNS)
|
|
m_chunksInfo.push_back(chunk);
|
|
}
|
|
|
|
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)
|
|
{
|
|
m_png_ptr = png_ptr;
|
|
m_info_ptr = info_ptr;
|
|
m_end_info = end_info;
|
|
}
|
|
else
|
|
{
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool PngDecoder::readData( Mat& img )
|
|
{
|
|
if (m_frame_count > 1)
|
|
{
|
|
Mat mat_cur = Mat::zeros(img.rows, img.cols, m_type);
|
|
uint32_t id = 0;
|
|
uint32_t j = 0;
|
|
uint32_t imagesize = m_width * m_height * mat_cur.channels();
|
|
m_is_IDAT_loaded = false;
|
|
|
|
if (m_frame_no == 0)
|
|
{
|
|
m_mat_raw = Mat(img.rows, img.cols, m_type);
|
|
m_mat_next = Mat(img.rows, img.cols, m_type);
|
|
frameRaw.setMat(m_mat_raw);
|
|
frameNext.setMat(m_mat_next);
|
|
if (m_f)
|
|
fseek(m_f, -8, SEEK_CUR);
|
|
else
|
|
m_buf_pos -= 8;
|
|
}
|
|
else
|
|
m_mat_next.copyTo(mat_cur);
|
|
|
|
frameCur.setMat(mat_cur);
|
|
|
|
processing_start((void*)&frameRaw, mat_cur);
|
|
png_structp png_ptr = (png_structp)m_png_ptr;
|
|
png_infop info_ptr = (png_infop)m_info_ptr;
|
|
|
|
while (true)
|
|
{
|
|
Chunk chunk;
|
|
id = read_chunk(chunk);
|
|
if (!id)
|
|
return false;
|
|
|
|
if (id == id_fcTL && m_is_IDAT_loaded)
|
|
{
|
|
if (!m_is_fcTL_loaded)
|
|
{
|
|
m_is_fcTL_loaded = true;
|
|
w0 = m_width;
|
|
h0 = m_height;
|
|
}
|
|
|
|
if (processing_finish())
|
|
{
|
|
if (dop == 2)
|
|
memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize);
|
|
|
|
compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur);
|
|
if (!delay_den)
|
|
delay_den = 100;
|
|
m_animation.durations.push_back(cvRound(1000.*delay_num/delay_den));
|
|
|
|
if (mat_cur.channels() == img.channels())
|
|
mat_cur.copyTo(img);
|
|
else if (img.channels() == 1)
|
|
cvtColor(mat_cur, img, COLOR_BGRA2GRAY);
|
|
else if (img.channels() == 3)
|
|
cvtColor(mat_cur, img, COLOR_BGRA2BGR);
|
|
|
|
if (dop != 2)
|
|
{
|
|
memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize);
|
|
if (dop == 1)
|
|
for (j = 0; j < h0; j++)
|
|
memset(frameNext.getRows()[y0 + j] + x0 * img.channels(), 0, w0 * img.channels());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
w0 = png_get_uint_32(&chunk.p[12]);
|
|
h0 = png_get_uint_32(&chunk.p[16]);
|
|
x0 = png_get_uint_32(&chunk.p[20]);
|
|
y0 = png_get_uint_32(&chunk.p[24]);
|
|
delay_num = png_get_uint_16(&chunk.p[28]);
|
|
delay_den = png_get_uint_16(&chunk.p[30]);
|
|
dop = chunk.p[32];
|
|
bop = chunk.p[33];
|
|
|
|
if (int(x0 + w0) > img.cols || int(y0 + h0) > img.rows || dop > 2 || bop > 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
memcpy(&m_chunkIHDR.p[8], &chunk.p[12], 8);
|
|
return true;
|
|
}
|
|
else if (id == id_IDAT)
|
|
{
|
|
m_is_IDAT_loaded = true;
|
|
png_process_data(png_ptr, info_ptr, chunk.p.data(), chunk.p.size());
|
|
}
|
|
else if (id == id_fdAT && m_is_fcTL_loaded)
|
|
{
|
|
m_is_IDAT_loaded = true;
|
|
png_save_uint_32(&chunk.p[4], static_cast<uint32_t>(chunk.p.size() - 16));
|
|
memcpy(&chunk.p[8], "IDAT", 4);
|
|
png_process_data(png_ptr, info_ptr, &chunk.p[4], chunk.p.size() - 4);
|
|
}
|
|
else if (id == id_IEND)
|
|
{
|
|
if (processing_finish())
|
|
{
|
|
compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur);
|
|
if (!delay_den)
|
|
delay_den = 100;
|
|
m_animation.durations.push_back(cvRound(1000.*delay_num/delay_den));
|
|
|
|
if (mat_cur.channels() == img.channels())
|
|
mat_cur.copyTo(img);
|
|
else if (img.channels() == 1)
|
|
cvtColor(mat_cur, img, COLOR_BGRA2GRAY);
|
|
else if (img.channels() == 3)
|
|
cvtColor(mat_cur, img, COLOR_BGRA2BGR);
|
|
}
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
else
|
|
png_process_data(png_ptr, info_ptr, chunk.p.data(), chunk.p.size());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
volatile bool result = false;
|
|
AutoBuffer<unsigned char*> _buffer(m_height);
|
|
unsigned char** buffer = _buffer.data();
|
|
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)
|
|
{
|
|
/* 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 && !m_use_rgb)
|
|
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 );
|
|
|
|
#ifdef PNG_eXIf_SUPPORTED
|
|
png_uint_32 num_exif = 0;
|
|
png_bytep exif = 0;
|
|
|
|
// Exif info could be in info_ptr (intro_info) or end_info per specification
|
|
if( png_get_valid(png_ptr, info_ptr, PNG_INFO_eXIf) )
|
|
png_get_eXIf_1(png_ptr, info_ptr, &num_exif, &exif);
|
|
else if( png_get_valid(png_ptr, end_info, PNG_INFO_eXIf) )
|
|
png_get_eXIf_1(png_ptr, end_info, &num_exif, &exif);
|
|
|
|
if( exif && num_exif > 0 )
|
|
{
|
|
m_exif.parseExif(exif, num_exif);
|
|
}
|
|
#endif
|
|
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool PngDecoder::nextPage() {
|
|
return ++m_frame_no < (int)m_frame_count;
|
|
}
|
|
|
|
void PngDecoder::compose_frame(std::vector<png_bytep>& rows_dst, const std::vector<png_bytep>& rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img)
|
|
{
|
|
int channels = img.channels();
|
|
if (img.depth() == CV_16U)
|
|
cv::parallel_for_(cv::Range(0, h), [&](const cv::Range& range) {
|
|
for (int j = range.start; j < range.end; j++) {
|
|
uint16_t* sp = reinterpret_cast<uint16_t*>(rows_src[j]);
|
|
uint16_t* dp = reinterpret_cast<uint16_t*>(rows_dst[j + y]) + x * channels;
|
|
|
|
if (_bop == 0) {
|
|
// Overwrite mode: copy source row directly to destination
|
|
memcpy(dp, sp, w * channels * sizeof(uint16_t));
|
|
}
|
|
else {
|
|
// Blending mode
|
|
for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) {
|
|
if (sp[3] == 65535) { // Fully opaque in 16-bit (max value)
|
|
memcpy(dp, sp, channels * sizeof(uint16_t));
|
|
}
|
|
else if (sp[3] != 0) { // Partially transparent
|
|
if (dp[3] != 0) { // Both source and destination have alpha
|
|
uint32_t u = sp[3] * 65535; // 16-bit max
|
|
uint32_t v = (65535 - sp[3]) * dp[3];
|
|
uint32_t al = u + v;
|
|
dp[0] = static_cast<uint16_t>((sp[0] * u + dp[0] * v) / al); // Red
|
|
dp[1] = static_cast<uint16_t>((sp[1] * u + dp[1] * v) / al); // Green
|
|
dp[2] = static_cast<uint16_t>((sp[2] * u + dp[2] * v) / al); // Blue
|
|
dp[3] = static_cast<uint16_t>(al / 65535); // Alpha
|
|
}
|
|
else {
|
|
// If destination alpha is 0, copy source pixel
|
|
memcpy(dp, sp, channels * sizeof(uint16_t));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
else
|
|
cv::parallel_for_(cv::Range(0, h), [&](const cv::Range& range) {
|
|
for (int j = range.start; j < range.end; j++) {
|
|
unsigned char* sp = rows_src[j];
|
|
unsigned char* dp = rows_dst[j + y] + x * channels;
|
|
|
|
if (_bop == 0) {
|
|
// Overwrite mode: copy source row directly to destination
|
|
memcpy(dp, sp, w * channels);
|
|
}
|
|
else {
|
|
// Blending mode
|
|
for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) {
|
|
if (sp[3] == 255) {
|
|
// Fully opaque: copy source pixel directly
|
|
memcpy(dp, sp, channels);
|
|
}
|
|
else if (sp[3] != 0) {
|
|
// Alpha blending
|
|
if (dp[3] != 0) {
|
|
int u = sp[3] * 255;
|
|
int v = (255 - sp[3]) * dp[3];
|
|
int al = u + v;
|
|
dp[0] = (sp[0] * u + dp[0] * v) / al; // Red
|
|
dp[1] = (sp[1] * u + dp[1] * v) / al; // Green
|
|
dp[2] = (sp[2] * u + dp[2] * v) / al; // Blue
|
|
dp[3] = al / 255; // Alpha
|
|
}
|
|
else {
|
|
// If destination alpha is 0, copy source pixel
|
|
memcpy(dp, sp, channels);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
size_t PngDecoder::read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount)
|
|
{
|
|
if (m_f)
|
|
return fread(_Buffer, _ElementSize, _ElementCount, m_f);
|
|
|
|
if (m_buf_pos + _ElementSize > m_buf.cols * m_buf.rows * m_buf.elemSize())
|
|
CV_Error(Error::StsInternal, "PNG input buffer is incomplete");
|
|
|
|
memcpy( _Buffer, m_buf.ptr() + m_buf_pos, _ElementSize );
|
|
m_buf_pos += _ElementSize;
|
|
return 1;
|
|
}
|
|
|
|
uint32_t PngDecoder::read_chunk(Chunk& chunk)
|
|
{
|
|
unsigned char len[4];
|
|
if (read_from_io(&len, 4, 1) == 1)
|
|
{
|
|
const size_t size = png_get_uint_32(len) + 12;
|
|
if (size > PNG_USER_CHUNK_MALLOC_MAX)
|
|
{
|
|
CV_LOG_WARNING(NULL, "chunk data is too large");
|
|
}
|
|
chunk.p.resize(size);
|
|
memcpy(chunk.p.data(), len, 4);
|
|
if (read_from_io(&chunk.p[4], chunk.p.size() - 4, 1) == 1)
|
|
return *(uint32_t*)(&chunk.p[4]);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool PngDecoder::processing_start(void* frame_ptr, const Mat& img)
|
|
{
|
|
static uint8_t header[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
|
|
|
|
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 = nullptr;
|
|
}
|
|
|
|
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
|
png_infop info_ptr = png_create_info_struct(png_ptr);
|
|
|
|
m_png_ptr = png_ptr;
|
|
m_info_ptr = info_ptr;
|
|
|
|
if (!png_ptr || !info_ptr)
|
|
return false;
|
|
|
|
if (setjmp(png_jmpbuf(png_ptr)))
|
|
{
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
|
return false;
|
|
}
|
|
|
|
png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
|
|
png_set_progressive_read_fn(png_ptr, frame_ptr, (png_progressive_info_ptr)info_fn, row_fn, NULL);
|
|
|
|
if (img.channels() < 4)
|
|
png_set_strip_alpha(png_ptr);
|
|
else
|
|
png_set_tRNS_to_alpha(png_ptr);
|
|
|
|
png_process_data(png_ptr, info_ptr, header, 8);
|
|
png_process_data(png_ptr, info_ptr, m_chunkIHDR.p.data(), m_chunkIHDR.p.size());
|
|
|
|
if ((m_color_type & PNG_COLOR_MASK_COLOR) && img.channels() > 1 && !m_use_rgb)
|
|
png_set_bgr(png_ptr); // convert RGB to BGR
|
|
else if (img.channels() > 1)
|
|
png_set_gray_to_rgb(png_ptr); // Gray->RGB
|
|
else
|
|
png_set_rgb_to_gray(png_ptr, 1, 0.299, 0.587); // RGB->Gray
|
|
|
|
for (size_t i = 0; i < m_chunksInfo.size(); i++)
|
|
png_process_data(png_ptr, info_ptr, m_chunksInfo[i].p.data(), m_chunksInfo[i].p.size());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PngDecoder::processing_finish()
|
|
{
|
|
static uint8_t footer[12] = { 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 };
|
|
|
|
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 (!png_ptr)
|
|
return false;
|
|
|
|
if (setjmp(png_jmpbuf(png_ptr)))
|
|
{
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
|
|
return false;
|
|
}
|
|
|
|
png_process_data(png_ptr, info_ptr, footer, 12);
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
|
|
m_png_ptr = nullptr;
|
|
m_info_ptr = nullptr;
|
|
m_end_info = nullptr;
|
|
|
|
return true;
|
|
}
|
|
|
|
void PngDecoder::info_fn(png_structp png_ptr, png_infop info_ptr)
|
|
{
|
|
png_set_expand(png_ptr);
|
|
(void)png_set_interlace_handling(png_ptr);
|
|
png_read_update_info(png_ptr, info_ptr);
|
|
}
|
|
|
|
void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass)
|
|
{
|
|
CV_UNUSED(pass);
|
|
APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr);
|
|
png_progressive_combine_row(png_ptr, frame->getRows()[row_num], new_row);
|
|
}
|
|
|
|
/////////////////////// PngEncoder ///////////////////
|
|
|
|
PngEncoder::PngEncoder()
|
|
{
|
|
m_description = "Portable Network Graphics files (*.png)";
|
|
m_buf_supported = true;
|
|
op_zstream1.zalloc = NULL;
|
|
op_zstream2.zalloc = NULL;
|
|
next_seq_num = 0;
|
|
trnssize = 0;
|
|
palsize = 0;
|
|
memset(palette, 0, sizeof(palette));
|
|
memset(trns, 0, sizeof(trns));
|
|
memset(op, 0, sizeof(op));
|
|
}
|
|
|
|
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, unsigned char* 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.data() );
|
|
png_write_end( png_ptr, info_ptr );
|
|
|
|
result = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
png_destroy_write_struct( &png_ptr, &info_ptr );
|
|
if(f) fclose( (FILE*)f );
|
|
|
|
return result;
|
|
}
|
|
|
|
size_t PngEncoder::write_to_io(void const* _Buffer, size_t _ElementSize, size_t _ElementCount, FILE * _Stream)
|
|
{
|
|
if (_Stream)
|
|
return fwrite(_Buffer, _ElementSize, _ElementCount, _Stream);
|
|
|
|
size_t cursz = m_buf->size();
|
|
m_buf->resize(cursz + _ElementCount);
|
|
memcpy( &(*m_buf)[cursz], _Buffer, _ElementCount );
|
|
return _ElementCount;
|
|
}
|
|
|
|
void PngEncoder::writeChunk(FILE* f, const char* name, unsigned char* data, uint32_t length)
|
|
{
|
|
unsigned char buf[4];
|
|
uint32_t crc = crc32(0, Z_NULL, 0);
|
|
|
|
png_save_uint_32(buf, length);
|
|
write_to_io(buf, 1, 4, f);
|
|
write_to_io(name, 1, 4, f);
|
|
crc = crc32(crc, (const Bytef*)name, 4);
|
|
|
|
if (memcmp(name, "fdAT", 4) == 0)
|
|
{
|
|
png_save_uint_32(buf, next_seq_num++);
|
|
write_to_io(buf, 1, 4, f);
|
|
crc = crc32(crc, buf, 4);
|
|
length -= 4;
|
|
}
|
|
|
|
if (data != NULL && length > 0)
|
|
{
|
|
write_to_io(data, 1, length, f);
|
|
crc = crc32(crc, data, length);
|
|
}
|
|
|
|
png_save_uint_32(buf, crc);
|
|
write_to_io(buf, 1, 4, f);
|
|
}
|
|
|
|
void PngEncoder::writeIDATs(FILE* f, int frame, unsigned char* data, uint32_t length, uint32_t idat_size)
|
|
{
|
|
uint32_t z_cmf = data[0];
|
|
if ((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70)
|
|
{
|
|
if (length >= 2)
|
|
{
|
|
uint32_t z_cinfo = z_cmf >> 4;
|
|
uint32_t half_z_window_size = 1 << (z_cinfo + 7);
|
|
while (idat_size <= half_z_window_size && half_z_window_size >= 256)
|
|
{
|
|
z_cinfo--;
|
|
half_z_window_size >>= 1;
|
|
}
|
|
z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4);
|
|
if (data[0] != (unsigned char)z_cmf)
|
|
{
|
|
data[0] = (unsigned char)z_cmf;
|
|
data[1] &= 0xe0;
|
|
data[1] += (unsigned char)(0x1f - ((z_cmf << 8) + data[1]) % 0x1f);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (length > 0)
|
|
{
|
|
uint32_t ds = length;
|
|
if (ds > 32768)
|
|
ds = 32768;
|
|
|
|
if (frame == 0)
|
|
writeChunk(f, "IDAT", data, ds);
|
|
else
|
|
writeChunk(f, "fdAT", data, ds + 4);
|
|
|
|
data += ds;
|
|
length -= ds;
|
|
}
|
|
}
|
|
|
|
void PngEncoder::processRect(unsigned char* row, int rowbytes, int bpp, int stride, int h, unsigned char* rows)
|
|
{
|
|
int i, j, v;
|
|
int a, b, c, pa, pb, pc, p;
|
|
unsigned char* prev = NULL;
|
|
unsigned char* dp = rows;
|
|
unsigned char* out;
|
|
|
|
for (j = 0; j < h; j++)
|
|
{
|
|
uint32_t sum = 0;
|
|
unsigned char* best_row = row_buf.data();
|
|
uint32_t mins = ((uint32_t)(-1)) >> 1;
|
|
|
|
out = row_buf.data() + 1;
|
|
for (i = 0; i < rowbytes; i++)
|
|
{
|
|
v = out[i] = row[i];
|
|
sum += (v < 128) ? v : 256 - v;
|
|
}
|
|
mins = sum;
|
|
|
|
sum = 0;
|
|
out = sub_row.data() + 1;
|
|
for (i = 0; i < bpp; i++)
|
|
{
|
|
v = out[i] = row[i];
|
|
sum += (v < 128) ? v : 256 - v;
|
|
}
|
|
for (i = bpp; i < rowbytes; i++)
|
|
{
|
|
v = out[i] = row[i] - row[i - bpp];
|
|
sum += (v < 128) ? v : 256 - v;
|
|
if (sum > mins)
|
|
break;
|
|
}
|
|
if (sum < mins)
|
|
{
|
|
mins = sum;
|
|
best_row = sub_row.data();
|
|
}
|
|
|
|
if (prev)
|
|
{
|
|
sum = 0;
|
|
out = up_row.data() + 1;
|
|
for (i = 0; i < rowbytes; i++)
|
|
{
|
|
v = out[i] = row[i] - prev[i];
|
|
sum += (v < 128) ? v : 256 - v;
|
|
if (sum > mins)
|
|
break;
|
|
}
|
|
if (sum < mins)
|
|
{
|
|
mins = sum;
|
|
best_row = up_row.data();
|
|
}
|
|
|
|
sum = 0;
|
|
out = avg_row.data() + 1;
|
|
for (i = 0; i < bpp; i++)
|
|
{
|
|
v = out[i] = row[i] - prev[i] / 2;
|
|
sum += (v < 128) ? v : 256 - v;
|
|
}
|
|
for (i = bpp; i < rowbytes; i++)
|
|
{
|
|
v = out[i] = row[i] - (prev[i] + row[i - bpp]) / 2;
|
|
sum += (v < 128) ? v : 256 - v;
|
|
if (sum > mins)
|
|
break;
|
|
}
|
|
if (sum < mins)
|
|
{
|
|
mins = sum;
|
|
best_row = avg_row.data();
|
|
}
|
|
|
|
sum = 0;
|
|
out = paeth_row.data() + 1;
|
|
for (i = 0; i < bpp; i++)
|
|
{
|
|
v = out[i] = row[i] - prev[i];
|
|
sum += (v < 128) ? v : 256 - v;
|
|
}
|
|
for (i = bpp; i < rowbytes; i++)
|
|
{
|
|
a = row[i - bpp];
|
|
b = prev[i];
|
|
c = prev[i - bpp];
|
|
p = b - c;
|
|
pc = a - c;
|
|
pa = abs(p);
|
|
pb = abs(pc);
|
|
pc = abs(p + pc);
|
|
p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b
|
|
: c;
|
|
v = out[i] = row[i] - p;
|
|
sum += (v < 128) ? v : 256 - v;
|
|
if (sum > mins)
|
|
break;
|
|
}
|
|
if (sum < mins)
|
|
{
|
|
best_row = paeth_row.data();
|
|
}
|
|
}
|
|
|
|
if (rows == NULL)
|
|
{
|
|
// deflate_rect_op()
|
|
op_zstream1.next_in = row_buf.data();
|
|
op_zstream1.avail_in = rowbytes + 1;
|
|
deflate(&op_zstream1, Z_NO_FLUSH);
|
|
|
|
op_zstream2.next_in = best_row;
|
|
op_zstream2.avail_in = rowbytes + 1;
|
|
deflate(&op_zstream2, Z_NO_FLUSH);
|
|
}
|
|
else
|
|
{
|
|
// deflate_rect_fin()
|
|
memcpy(dp, best_row, rowbytes + 1);
|
|
dp += rowbytes + 1;
|
|
}
|
|
|
|
prev = row;
|
|
row += stride;
|
|
}
|
|
}
|
|
|
|
void PngEncoder::deflateRectOp(unsigned char* pdata, int x, int y, int w, int h, int bpp, int stride, int zbuf_size, int n)
|
|
{
|
|
unsigned char* row = pdata + y * stride + x * bpp;
|
|
int rowbytes = w * bpp;
|
|
|
|
op_zstream1.data_type = Z_BINARY;
|
|
op_zstream1.next_out = op_zbuf1.data();
|
|
op_zstream1.avail_out = zbuf_size;
|
|
|
|
op_zstream2.data_type = Z_BINARY;
|
|
op_zstream2.next_out = op_zbuf2.data();
|
|
op_zstream2.avail_out = zbuf_size;
|
|
|
|
processRect(row, rowbytes, bpp, stride, h, NULL);
|
|
|
|
deflate(&op_zstream1, Z_FINISH);
|
|
deflate(&op_zstream2, Z_FINISH);
|
|
op[n].p = pdata;
|
|
|
|
if (op_zstream1.total_out < op_zstream2.total_out)
|
|
{
|
|
op[n].size = op_zstream1.total_out;
|
|
op[n].filters = 0;
|
|
}
|
|
else
|
|
{
|
|
op[n].size = op_zstream2.total_out;
|
|
op[n].filters = 1;
|
|
}
|
|
op[n].x = x;
|
|
op[n].y = y;
|
|
op[n].w = w;
|
|
op[n].h = h;
|
|
op[n].valid = 1;
|
|
deflateReset(&op_zstream1);
|
|
deflateReset(&op_zstream2);
|
|
}
|
|
|
|
bool PngEncoder::getRect(uint32_t w, uint32_t h, unsigned char* pimage1, unsigned char* pimage2, unsigned char* ptemp, uint32_t bpp, uint32_t stride, int zbuf_size, uint32_t has_tcolor, uint32_t tcolor, int n)
|
|
{
|
|
uint32_t i, j, x0, y0, w0, h0;
|
|
uint32_t x_min = w - 1;
|
|
uint32_t y_min = h - 1;
|
|
uint32_t x_max = 0;
|
|
uint32_t y_max = 0;
|
|
uint32_t diffnum = 0;
|
|
uint32_t over_is_possible = 1;
|
|
|
|
if (!has_tcolor)
|
|
over_is_possible = 0;
|
|
|
|
if (bpp == 1)
|
|
{
|
|
unsigned char* pa = pimage1;
|
|
unsigned char* pb = pimage2;
|
|
unsigned char* pc = ptemp;
|
|
|
|
for (j = 0; j < h; j++)
|
|
for (i = 0; i < w; i++)
|
|
{
|
|
unsigned char c = *pb++;
|
|
if (*pa++ != c)
|
|
{
|
|
diffnum++;
|
|
if (has_tcolor && c == tcolor)
|
|
over_is_possible = 0;
|
|
if (i < x_min)
|
|
x_min = i;
|
|
if (i > x_max)
|
|
x_max = i;
|
|
if (j < y_min)
|
|
y_min = j;
|
|
if (j > y_max)
|
|
y_max = j;
|
|
}
|
|
else
|
|
c = tcolor;
|
|
|
|
*pc++ = c;
|
|
}
|
|
}
|
|
else if (bpp == 2)
|
|
{
|
|
unsigned short* pa = (unsigned short*)pimage1;
|
|
unsigned short* pb = (unsigned short*)pimage2;
|
|
unsigned short* pc = (unsigned short*)ptemp;
|
|
|
|
for (j = 0; j < h; j++)
|
|
for (i = 0; i < w; i++)
|
|
{
|
|
uint32_t c1 = *pa++;
|
|
uint32_t c2 = *pb++;
|
|
if ((c1 != c2) && ((c1 >> 8) || (c2 >> 8)))
|
|
{
|
|
diffnum++;
|
|
if ((c2 >> 8) != 0xFF)
|
|
over_is_possible = 0;
|
|
if (i < x_min)
|
|
x_min = i;
|
|
if (i > x_max)
|
|
x_max = i;
|
|
if (j < y_min)
|
|
y_min = j;
|
|
if (j > y_max)
|
|
y_max = j;
|
|
}
|
|
else
|
|
c2 = 0;
|
|
|
|
*pc++ = c2;
|
|
}
|
|
}
|
|
else if (bpp == 3)
|
|
{
|
|
unsigned char* pa = pimage1;
|
|
unsigned char* pb = pimage2;
|
|
unsigned char* pc = ptemp;
|
|
|
|
for (j = 0; j < h; j++)
|
|
for (i = 0; i < w; i++)
|
|
{
|
|
uint32_t c1 = (pa[2] << 16) + (pa[1] << 8) + pa[0];
|
|
uint32_t c2 = (pb[2] << 16) + (pb[1] << 8) + pb[0];
|
|
if (c1 != c2)
|
|
{
|
|
diffnum++;
|
|
if (has_tcolor && c2 == tcolor)
|
|
over_is_possible = 0;
|
|
if (i < x_min)
|
|
x_min = i;
|
|
if (i > x_max)
|
|
x_max = i;
|
|
if (j < y_min)
|
|
y_min = j;
|
|
if (j > y_max)
|
|
y_max = j;
|
|
}
|
|
else
|
|
c2 = tcolor;
|
|
|
|
memcpy(pc, &c2, 3);
|
|
pa += 3;
|
|
pb += 3;
|
|
pc += 3;
|
|
}
|
|
}
|
|
else if (bpp == 4)
|
|
{
|
|
uint32_t* pa = (uint32_t*)pimage1;
|
|
uint32_t* pb = (uint32_t*)pimage2;
|
|
uint32_t* pc = (uint32_t*)ptemp;
|
|
|
|
for (j = 0; j < h; j++)
|
|
for (i = 0; i < w; i++)
|
|
{
|
|
uint32_t c1 = *pa++;
|
|
uint32_t c2 = *pb++;
|
|
if ((c1 != c2) && ((c1 >> 24) || (c2 >> 24)))
|
|
{
|
|
diffnum++;
|
|
if ((c2 >> 24) != 0xFF)
|
|
over_is_possible = 0;
|
|
if (i < x_min)
|
|
x_min = i;
|
|
if (i > x_max)
|
|
x_max = i;
|
|
if (j < y_min)
|
|
y_min = j;
|
|
if (j > y_max)
|
|
y_max = j;
|
|
}
|
|
else
|
|
c2 = 0;
|
|
|
|
*pc++ = c2;
|
|
}
|
|
}
|
|
|
|
if (diffnum == 0)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
x0 = x_min;
|
|
y0 = y_min;
|
|
w0 = x_max - x_min + 1;
|
|
h0 = y_max - y_min + 1;
|
|
}
|
|
|
|
if (n < 3)
|
|
{
|
|
deflateRectOp(pimage2, x0, y0, w0, h0, bpp, stride, zbuf_size, n * 2);
|
|
|
|
if (over_is_possible)
|
|
deflateRectOp(ptemp, x0, y0, w0, h0, bpp, stride, zbuf_size, n * 2 + 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PngEncoder::deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, int stride, unsigned char* rows, int zbuf_size, int n)
|
|
{
|
|
unsigned char* row = op[n].p + op[n].y * stride + op[n].x * bpp;
|
|
int rowbytes = op[n].w * bpp;
|
|
|
|
if (op[n].filters == 0)
|
|
{
|
|
unsigned char* dp = rows;
|
|
for (int j = 0; j < op[n].h; j++)
|
|
{
|
|
*dp++ = 0;
|
|
memcpy(dp, row, rowbytes);
|
|
dp += rowbytes;
|
|
row += stride;
|
|
}
|
|
}
|
|
else
|
|
processRect(row, rowbytes, bpp, stride, op[n].h, rows);
|
|
|
|
z_stream fin_zstream;
|
|
fin_zstream.data_type = Z_BINARY;
|
|
fin_zstream.zalloc = Z_NULL;
|
|
fin_zstream.zfree = Z_NULL;
|
|
fin_zstream.opaque = Z_NULL;
|
|
deflateInit2(&fin_zstream, Z_BEST_COMPRESSION, 8, 15, 8, op[n].filters ? Z_FILTERED : Z_DEFAULT_STRATEGY);
|
|
|
|
fin_zstream.next_out = zbuf;
|
|
fin_zstream.avail_out = zbuf_size;
|
|
fin_zstream.next_in = rows;
|
|
fin_zstream.avail_in = op[n].h * (rowbytes + 1);
|
|
deflate(&fin_zstream, Z_FINISH);
|
|
*zsize = fin_zstream.total_out;
|
|
deflateEnd(&fin_zstream);
|
|
}
|
|
|
|
bool PngEncoder::writeanimation(const Animation& animation, const std::vector<int>& params)
|
|
{
|
|
int compression_level = 6;
|
|
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;
|
|
}
|
|
}
|
|
|
|
CV_UNUSED(isBilevel);
|
|
uint32_t first =0;
|
|
uint32_t loops= animation.loop_count;
|
|
uint32_t coltype= animation.frames[0].channels() == 1 ? PNG_COLOR_TYPE_GRAY : animation.frames[0].channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
|
|
|
|
FILE* m_f = NULL;
|
|
uint32_t i, j, k;
|
|
uint32_t x0, y0, w0, h0, dop, bop;
|
|
uint32_t idat_size, zbuf_size, zsize;
|
|
unsigned char header[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
|
|
uint32_t num_frames = (int)animation.frames.size();
|
|
uint32_t width = animation.frames[0].cols;
|
|
uint32_t height = animation.frames[0].rows;
|
|
uint32_t bpp = (coltype == 6) ? 4 : (coltype == 2) ? 3
|
|
: (coltype == 4) ? 2
|
|
: 1;
|
|
uint32_t has_tcolor = (coltype >= 4 || (coltype <= 2 && trnssize)) ? 1 : 0;
|
|
uint32_t tcolor = 0;
|
|
uint32_t rowbytes = width * bpp;
|
|
uint32_t imagesize = rowbytes * height;
|
|
|
|
AutoBuffer<unsigned char> temp(imagesize);
|
|
AutoBuffer<unsigned char> over1(imagesize);
|
|
AutoBuffer<unsigned char> over2(imagesize);
|
|
AutoBuffer<unsigned char> over3(imagesize);
|
|
AutoBuffer<unsigned char> rest(imagesize);
|
|
AutoBuffer<unsigned char> rows((rowbytes + 1) * height);
|
|
|
|
std::vector<APNGFrame> frames;
|
|
std::vector<Mat> tmpframes;
|
|
|
|
for (i = 0; i < (uint32_t)animation.frames.size(); i++)
|
|
{
|
|
APNGFrame apngFrame;
|
|
tmpframes.push_back(animation.frames[i].clone());
|
|
// TO DO optimize BGR RGB conversations
|
|
if (animation.frames[i].channels() == 4)
|
|
cvtColor(animation.frames[i], tmpframes[i], COLOR_BGRA2RGBA);
|
|
if (animation.frames[i].channels() == 3)
|
|
cvtColor(animation.frames[i], tmpframes[i], COLOR_BGR2RGB);
|
|
|
|
if (tmpframes[i].depth() != CV_8U)
|
|
tmpframes[i].convertTo(tmpframes[i], CV_8U, 1.0 / 255);
|
|
apngFrame.setMat(tmpframes[i], animation.durations[i]);
|
|
|
|
if (i > 0 && !getRect(width, height, frames.back().getPixels(), apngFrame.getPixels(), over1.data(), bpp, rowbytes, 0, 0, 0, 3))
|
|
{
|
|
frames.back().setDelayNum(frames.back().getDelayNum() + apngFrame.getDelayNum());
|
|
num_frames--;
|
|
}
|
|
else
|
|
frames.push_back(apngFrame);
|
|
}
|
|
|
|
if (trnssize)
|
|
{
|
|
if (coltype == 0)
|
|
tcolor = trns[1];
|
|
else if (coltype == 2)
|
|
tcolor = (((trns[5] << 8) + trns[3]) << 8) + trns[1];
|
|
else if (coltype == 3)
|
|
{
|
|
for (i = 0; i < trnssize; i++)
|
|
if (trns[i] == 0)
|
|
{
|
|
has_tcolor = 1;
|
|
tcolor = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_buf || (m_f = fopen(m_filename.c_str(), "wb")) != 0)
|
|
{
|
|
unsigned char buf_IHDR[13];
|
|
unsigned char buf_acTL[8];
|
|
unsigned char buf_fcTL[26];
|
|
|
|
png_save_uint_32(buf_IHDR, width);
|
|
png_save_uint_32(buf_IHDR + 4, height);
|
|
buf_IHDR[8] = 8;
|
|
buf_IHDR[9] = coltype;
|
|
buf_IHDR[10] = 0;
|
|
buf_IHDR[11] = 0;
|
|
buf_IHDR[12] = 0;
|
|
|
|
png_save_uint_32(buf_acTL, num_frames - first);
|
|
png_save_uint_32(buf_acTL + 4, loops);
|
|
|
|
write_to_io(header, 1, 8, m_f);
|
|
|
|
writeChunk(m_f, "IHDR", buf_IHDR, 13);
|
|
|
|
if (num_frames > 1)
|
|
writeChunk(m_f, "acTL", buf_acTL, 8);
|
|
else
|
|
first = 0;
|
|
|
|
if (palsize > 0)
|
|
writeChunk(m_f, "PLTE", (unsigned char*)(&palette), palsize * 3);
|
|
|
|
if ((animation.bgcolor != Scalar()) && (animation.frames.size() > 1))
|
|
{
|
|
uint64_t bgvalue = (static_cast<int>(animation.bgcolor[0]) & 0xFF) << 24 |
|
|
(static_cast<int>(animation.bgcolor[1]) & 0xFF) << 16 |
|
|
(static_cast<int>(animation.bgcolor[2]) & 0xFF) << 8 |
|
|
(static_cast<int>(animation.bgcolor[3]) & 0xFF);
|
|
writeChunk(m_f, "bKGD", (unsigned char*)(&bgvalue), 6); //the bKGD chunk must precede the first IDAT chunk, and must follow the PLTE chunk.
|
|
}
|
|
|
|
if (trnssize > 0)
|
|
writeChunk(m_f, "tRNS", trns, trnssize);
|
|
|
|
op_zstream1.data_type = Z_BINARY;
|
|
op_zstream1.zalloc = Z_NULL;
|
|
op_zstream1.zfree = Z_NULL;
|
|
op_zstream1.opaque = Z_NULL;
|
|
deflateInit2(&op_zstream1, compression_level, 8, 15, 8, compression_strategy);
|
|
|
|
op_zstream2.data_type = Z_BINARY;
|
|
op_zstream2.zalloc = Z_NULL;
|
|
op_zstream2.zfree = Z_NULL;
|
|
op_zstream2.opaque = Z_NULL;
|
|
deflateInit2(&op_zstream2, compression_level, 8, 15, 8, Z_FILTERED);
|
|
|
|
idat_size = (rowbytes + 1) * height;
|
|
zbuf_size = idat_size + ((idat_size + 7) >> 3) + ((idat_size + 63) >> 6) + 11;
|
|
|
|
AutoBuffer<unsigned char> zbuf(zbuf_size);
|
|
op_zbuf1.allocate(zbuf_size);
|
|
op_zbuf2.allocate(zbuf_size);
|
|
row_buf.allocate(rowbytes + 1);
|
|
sub_row.allocate(rowbytes + 1);
|
|
up_row.allocate(rowbytes + 1);
|
|
avg_row.allocate(rowbytes + 1);
|
|
paeth_row.allocate(rowbytes + 1);
|
|
|
|
row_buf[0] = 0;
|
|
sub_row[0] = 1;
|
|
up_row[0] = 2;
|
|
avg_row[0] = 3;
|
|
paeth_row[0] = 4;
|
|
|
|
x0 = 0;
|
|
y0 = 0;
|
|
w0 = width;
|
|
h0 = height;
|
|
bop = 0;
|
|
next_seq_num = 0;
|
|
|
|
for (j = 0; j < 6; j++)
|
|
op[j].valid = 0;
|
|
deflateRectOp(frames[0].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0);
|
|
deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0);
|
|
|
|
if (first)
|
|
{
|
|
writeIDATs(m_f, 0, zbuf.data(), zsize, idat_size);
|
|
for (j = 0; j < 6; j++)
|
|
op[j].valid = 0;
|
|
deflateRectOp(frames[1].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0);
|
|
deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0);
|
|
}
|
|
|
|
for (i = first; i < num_frames - 1; i++)
|
|
{
|
|
uint32_t op_min;
|
|
int op_best;
|
|
|
|
for (j = 0; j < 6; j++)
|
|
op[j].valid = 0;
|
|
|
|
/* dispose = none */
|
|
getRect(width, height, frames[i].getPixels(), frames[i + 1].getPixels(), over1.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 0);
|
|
|
|
/* dispose = background */
|
|
if (has_tcolor)
|
|
{
|
|
memcpy(temp.data(), frames[i].getPixels(), imagesize);
|
|
if (coltype == 2)
|
|
for (j = 0; j < h0; j++)
|
|
for (k = 0; k < w0; k++)
|
|
memcpy(temp.data() + ((j + y0) * width + (k + x0)) * 3, &tcolor, 3);
|
|
else
|
|
for (j = 0; j < h0; j++)
|
|
memset(temp.data() + ((j + y0) * width + x0) * bpp, tcolor, w0 * bpp);
|
|
|
|
getRect(width, height, temp.data(), frames[i + 1].getPixels(), over2.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 1);
|
|
}
|
|
|
|
/* dispose = previous */
|
|
if (i > first)
|
|
getRect(width, height, rest.data(), frames[i + 1].getPixels(), over3.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 2);
|
|
|
|
op_min = op[0].size;
|
|
op_best = 0;
|
|
for (j = 1; j < 6; j++)
|
|
if (op[j].valid)
|
|
{
|
|
if (op[j].size < op_min)
|
|
{
|
|
op_min = op[j].size;
|
|
op_best = j;
|
|
}
|
|
}
|
|
|
|
dop = op_best >> 1;
|
|
|
|
png_save_uint_32(buf_fcTL, next_seq_num++);
|
|
png_save_uint_32(buf_fcTL + 4, w0);
|
|
png_save_uint_32(buf_fcTL + 8, h0);
|
|
png_save_uint_32(buf_fcTL + 12, x0);
|
|
png_save_uint_32(buf_fcTL + 16, y0);
|
|
png_save_uint_16(buf_fcTL + 20, frames[i].getDelayNum());
|
|
png_save_uint_16(buf_fcTL + 22, frames[i].getDelayDen());
|
|
buf_fcTL[24] = dop;
|
|
buf_fcTL[25] = bop;
|
|
writeChunk(m_f, "fcTL", buf_fcTL, 26);
|
|
|
|
writeIDATs(m_f, i, zbuf.data(), zsize, idat_size);
|
|
|
|
/* process apng dispose - begin */
|
|
if (dop != 2)
|
|
memcpy(rest.data(), frames[i].getPixels(), imagesize);
|
|
|
|
if (dop == 1)
|
|
{
|
|
if (coltype == 2)
|
|
for (j = 0; j < h0; j++)
|
|
for (k = 0; k < w0; k++)
|
|
memcpy(rest.data() + ((j + y0) * width + (k + x0)) * 3, &tcolor, 3);
|
|
else
|
|
for (j = 0; j < h0; j++)
|
|
memset(rest.data() + ((j + y0) * width + x0) * bpp, tcolor, w0 * bpp);
|
|
}
|
|
/* process apng dispose - end */
|
|
|
|
x0 = op[op_best].x;
|
|
y0 = op[op_best].y;
|
|
w0 = op[op_best].w;
|
|
h0 = op[op_best].h;
|
|
bop = op_best & 1;
|
|
|
|
deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, op_best);
|
|
}
|
|
|
|
if (num_frames > 1)
|
|
{
|
|
png_save_uint_32(buf_fcTL, next_seq_num++);
|
|
png_save_uint_32(buf_fcTL + 4, w0);
|
|
png_save_uint_32(buf_fcTL + 8, h0);
|
|
png_save_uint_32(buf_fcTL + 12, x0);
|
|
png_save_uint_32(buf_fcTL + 16, y0);
|
|
png_save_uint_16(buf_fcTL + 20, frames[i].getDelayNum());
|
|
png_save_uint_16(buf_fcTL + 22, frames[i].getDelayDen());
|
|
buf_fcTL[24] = 0;
|
|
buf_fcTL[25] = bop;
|
|
writeChunk(m_f, "fcTL", buf_fcTL, 26);
|
|
}
|
|
|
|
writeIDATs(m_f, num_frames - 1, zbuf.data(), zsize, idat_size);
|
|
|
|
writeChunk(m_f, "IEND", 0, 0);
|
|
|
|
if (m_f)
|
|
fclose(m_f);
|
|
|
|
deflateEnd(&op_zstream1);
|
|
deflateEnd(&op_zstream2);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
/* End of file. */
|