404 lines
13 KiB
C++
404 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include "pnghelper.hxx"
|
|
#include <sal/macros.h>
|
|
|
|
#include <zlib.h>
|
|
|
|
using namespace pdfi;
|
|
|
|
// checksum helpers, courtesy of libpng.org
|
|
|
|
/* Table of CRCs of all 8-bit messages. */
|
|
sal_uInt32 PngHelper::crc_table[256];
|
|
|
|
/* Flag: has the table been computed? Initially false. */
|
|
bool PngHelper::bCRCTableInit = true;
|
|
|
|
/* Make the table for a fast CRC. */
|
|
void PngHelper::initCRCTable()
|
|
{
|
|
for (sal_uInt32 n = 0; n < 256; n++)
|
|
{
|
|
sal_uInt32 c = n;
|
|
for (int k = 0; k < 8; k++)
|
|
{
|
|
if (c & 1)
|
|
c = 0xedb88320L ^ (c >> 1);
|
|
else
|
|
c = c >> 1;
|
|
}
|
|
crc_table[n] = c;
|
|
}
|
|
bCRCTableInit = false;
|
|
}
|
|
|
|
/* Update a running CRC with the bytes buf[0..len-1]--the CRC
|
|
should be initialized to all 1's, and the transmitted value
|
|
is the 1's complement of the final running CRC (see the
|
|
crc() routine below)). */
|
|
|
|
void PngHelper::updateCRC( sal_uInt32& io_rCRC, const sal_uInt8* i_pBuf, size_t i_nLen )
|
|
{
|
|
if( bCRCTableInit )
|
|
initCRCTable();
|
|
|
|
sal_uInt32 nCRC = io_rCRC;
|
|
for( size_t n = 0; n < i_nLen; n++ )
|
|
nCRC = crc_table[(nCRC ^ i_pBuf[n]) & 0xff] ^ (nCRC >> 8);
|
|
io_rCRC = nCRC;
|
|
}
|
|
|
|
sal_uInt32 PngHelper::getCRC( const sal_uInt8* i_pBuf, size_t i_nLen )
|
|
{
|
|
sal_uInt32 nCRC = 0xffffffff;
|
|
updateCRC( nCRC, i_pBuf, i_nLen );
|
|
return nCRC ^ 0xffffffff;
|
|
}
|
|
|
|
sal_uInt32 PngHelper::deflateBuffer( const Output_t* i_pBuf, size_t i_nLen, OutputBuffer& o_rOut )
|
|
{
|
|
size_t nOrigSize = o_rOut.size();
|
|
|
|
// prepare z stream
|
|
z_stream aStream;
|
|
aStream.zalloc = Z_NULL;
|
|
aStream.zfree = Z_NULL;
|
|
aStream.opaque = Z_NULL;
|
|
aStream.total_out = aStream.total_in = 0;
|
|
if (Z_OK != deflateInit(&aStream, Z_BEST_COMPRESSION))
|
|
return 0;
|
|
aStream.avail_in = uInt(i_nLen);
|
|
aStream.next_in = i_pBuf;
|
|
|
|
sal_uInt8 aOutBuf[ 32768 ];
|
|
do
|
|
{
|
|
aStream.avail_out = sizeof( aOutBuf );
|
|
aStream.next_out = aOutBuf;
|
|
|
|
if( deflate( &aStream, Z_FINISH ) == Z_STREAM_ERROR )
|
|
{
|
|
deflateEnd( &aStream );
|
|
// scrap the data of this broken stream
|
|
o_rOut.resize( nOrigSize );
|
|
return 0;
|
|
}
|
|
|
|
// append compressed bytes
|
|
sal_uInt32 nCompressedBytes = sizeof( aOutBuf ) - aStream.avail_out;
|
|
if( nCompressedBytes )
|
|
o_rOut.insert( o_rOut.end(), aOutBuf, aOutBuf+nCompressedBytes );
|
|
|
|
} while( aStream.avail_out == 0 );
|
|
|
|
// cleanup
|
|
deflateEnd( &aStream );
|
|
|
|
return sal_uInt32( o_rOut.size() - nOrigSize );
|
|
}
|
|
|
|
void PngHelper::appendFileHeader( OutputBuffer& o_rOutputBuf )
|
|
{
|
|
static const unsigned char aHeader[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
|
|
|
o_rOutputBuf.insert( o_rOutputBuf.end(), aHeader, aHeader + SAL_N_ELEMENTS(aHeader) );
|
|
}
|
|
|
|
size_t PngHelper::startChunk( const char* pChunkName, OutputBuffer& o_rOutputBuf )
|
|
{
|
|
size_t nIndex = sal_uInt32( o_rOutputBuf.size() );
|
|
o_rOutputBuf.insert( o_rOutputBuf.end(), 4, Output_t(0) );
|
|
o_rOutputBuf.push_back( pChunkName[0] );
|
|
o_rOutputBuf.push_back( pChunkName[1] );
|
|
o_rOutputBuf.push_back( pChunkName[2] );
|
|
o_rOutputBuf.push_back( pChunkName[3] );
|
|
return nIndex;
|
|
}
|
|
|
|
void PngHelper::set( sal_uInt32 i_nValue, OutputBuffer& o_rOutputBuf, size_t i_nIndex )
|
|
{
|
|
o_rOutputBuf[ i_nIndex ] = (i_nValue & 0xff000000) >> 24;
|
|
o_rOutputBuf[ i_nIndex+1 ] = (i_nValue & 0x00ff0000) >> 16;
|
|
o_rOutputBuf[ i_nIndex+2 ] = (i_nValue & 0x0000ff00) >> 8;
|
|
o_rOutputBuf[ i_nIndex+3 ] = (i_nValue & 0x000000ff);
|
|
}
|
|
|
|
void PngHelper::endChunk( size_t nStart, OutputBuffer& o_rOutputBuf )
|
|
{
|
|
if( nStart+8 > o_rOutputBuf.size() )
|
|
return; // something broken is going on
|
|
|
|
// update chunk length
|
|
size_t nLen = o_rOutputBuf.size() - nStart;
|
|
sal_uInt32 nDataLen = sal_uInt32(nLen)-8;
|
|
set( nDataLen, o_rOutputBuf, nStart );
|
|
|
|
// append chunk crc
|
|
sal_uInt32 nChunkCRC = getCRC( reinterpret_cast<sal_uInt8*>(&o_rOutputBuf[nStart+4]), nLen-4 );
|
|
append( nChunkCRC, o_rOutputBuf );
|
|
}
|
|
|
|
void PngHelper::appendIHDR( OutputBuffer& o_rOutputBuf, int width, int height, int depth, int colortype )
|
|
{
|
|
size_t nStart = startChunk( "IHDR", o_rOutputBuf );
|
|
append( width, o_rOutputBuf );
|
|
append( height, o_rOutputBuf );
|
|
o_rOutputBuf.push_back( Output_t(depth) );
|
|
o_rOutputBuf.push_back( Output_t(colortype) );
|
|
o_rOutputBuf.push_back( 0 ); // compression method deflate
|
|
o_rOutputBuf.push_back( 0 ); // filtering method 0 (default)
|
|
o_rOutputBuf.push_back( 0 ); // no interlacing
|
|
endChunk( nStart, o_rOutputBuf );
|
|
}
|
|
|
|
void PngHelper::appendIEND( OutputBuffer& o_rOutputBuf )
|
|
{
|
|
size_t nStart = startChunk( "IEND", o_rOutputBuf );
|
|
endChunk( nStart, o_rOutputBuf );
|
|
}
|
|
|
|
void PngHelper::createPng( OutputBuffer& o_rOutputBuf,
|
|
Stream* str,
|
|
int width,
|
|
int height,
|
|
GfxRGB const & zeroColor,
|
|
GfxRGB const & oneColor,
|
|
bool bIsMask
|
|
)
|
|
{
|
|
appendFileHeader( o_rOutputBuf );
|
|
appendIHDR( o_rOutputBuf, width, height, 1, 3 );
|
|
|
|
// write palette
|
|
size_t nIdx = startChunk( "PLTE", o_rOutputBuf );
|
|
// write colors 0 and 1
|
|
o_rOutputBuf.push_back(colToByte(zeroColor.r));
|
|
o_rOutputBuf.push_back(colToByte(zeroColor.g));
|
|
o_rOutputBuf.push_back(colToByte(zeroColor.b));
|
|
o_rOutputBuf.push_back(colToByte(oneColor.r));
|
|
o_rOutputBuf.push_back(colToByte(oneColor.g));
|
|
o_rOutputBuf.push_back(colToByte(oneColor.b));
|
|
// end PLTE chunk
|
|
endChunk( nIdx, o_rOutputBuf );
|
|
|
|
if( bIsMask )
|
|
{
|
|
// write tRNS chunk
|
|
nIdx = startChunk( "tRNS", o_rOutputBuf );
|
|
o_rOutputBuf.push_back( 0xff );
|
|
o_rOutputBuf.push_back( 0 );
|
|
// end tRNS chunk
|
|
endChunk( nIdx, o_rOutputBuf );
|
|
}
|
|
|
|
// create scan line data buffer
|
|
OutputBuffer aScanlines;
|
|
int nLineSize = (width + 7)/8;
|
|
aScanlines.reserve( nLineSize * height + height );
|
|
|
|
str->reset();
|
|
for( int y = 0; y < height; y++ )
|
|
{
|
|
// determine filter type (none) for this scanline
|
|
aScanlines.push_back( 0 );
|
|
for( int x = 0; x < nLineSize; x++ )
|
|
aScanlines.push_back( str->getChar() );
|
|
}
|
|
|
|
// begin IDAT chunk for scanline data
|
|
nIdx = startChunk( "IDAT", o_rOutputBuf );
|
|
// compress scanlines
|
|
deflateBuffer( aScanlines.data(), aScanlines.size(), o_rOutputBuf );
|
|
// end IDAT chunk
|
|
endChunk( nIdx, o_rOutputBuf );
|
|
|
|
// output IEND
|
|
appendIEND( o_rOutputBuf );
|
|
}
|
|
|
|
void PngHelper::createPng( OutputBuffer& o_rOutputBuf,
|
|
Stream* str,
|
|
int width, int height, GfxImageColorMap* colorMap,
|
|
Stream* maskStr,
|
|
int maskWidth, int maskHeight, GfxImageColorMap* maskColorMap )
|
|
{
|
|
appendFileHeader( o_rOutputBuf );
|
|
appendIHDR( o_rOutputBuf, width, height, 8, 6 ); // RGBA image
|
|
|
|
// initialize stream
|
|
unsigned char *p, *pm;
|
|
GfxRGB rgb;
|
|
GfxGray alpha;
|
|
std::unique_ptr<ImageStream> imgStr(
|
|
new ImageStream(str,
|
|
width,
|
|
colorMap->getNumPixelComps(),
|
|
colorMap->getBits()));
|
|
imgStr->reset();
|
|
|
|
// create scan line data buffer
|
|
OutputBuffer aScanlines;
|
|
aScanlines.reserve( width*height*4 + height );
|
|
|
|
for( int y=0; y<height; ++y)
|
|
{
|
|
aScanlines.push_back( 0 );
|
|
p = imgStr->getLine();
|
|
for( int x=0; x<width; ++x)
|
|
{
|
|
colorMap->getRGB(p, &rgb);
|
|
aScanlines.push_back(colToByte(rgb.r));
|
|
aScanlines.push_back(colToByte(rgb.g));
|
|
aScanlines.push_back(colToByte(rgb.b));
|
|
aScanlines.push_back( 0xff );
|
|
|
|
p +=colorMap->getNumPixelComps();
|
|
}
|
|
}
|
|
|
|
|
|
// now fill in the mask data
|
|
|
|
// CAUTION: originally this was done in one single loop
|
|
// it caused merry chaos; the reason is that maskStr and str are
|
|
// not independent streams, it happens that reading one advances
|
|
// the other, too. Hence the two passes are imperative !
|
|
|
|
// initialize mask stream
|
|
std::unique_ptr<ImageStream> imgStrMask(
|
|
new ImageStream(maskStr,
|
|
maskWidth,
|
|
maskColorMap->getNumPixelComps(),
|
|
maskColorMap->getBits()));
|
|
|
|
imgStrMask->reset();
|
|
for( int y = 0; y < maskHeight; ++y )
|
|
{
|
|
pm = imgStrMask->getLine();
|
|
for( int x = 0; x < maskWidth; ++x )
|
|
{
|
|
maskColorMap->getGray(pm,&alpha);
|
|
pm += maskColorMap->getNumPixelComps();
|
|
int nIndex = (y*height/maskHeight) * (width*4+1) + // mapped line
|
|
(x*width/maskWidth)*4 + 1 + 3 // mapped column
|
|
;
|
|
aScanlines[ nIndex ] = colToByte(alpha);
|
|
}
|
|
}
|
|
|
|
imgStr.reset();
|
|
imgStrMask.reset();
|
|
|
|
// begind IDAT chunk for scanline data
|
|
size_t nIdx = startChunk( "IDAT", o_rOutputBuf );
|
|
// compress scanlines
|
|
deflateBuffer( aScanlines.data(), aScanlines.size(), o_rOutputBuf );
|
|
// end IDAT chunk
|
|
endChunk( nIdx, o_rOutputBuf );
|
|
// output IEND
|
|
appendIEND( o_rOutputBuf );
|
|
}
|
|
|
|
// one bit mask; 0 bits opaque
|
|
void PngHelper::createPng( OutputBuffer& o_rOutputBuf,
|
|
Stream* str,
|
|
int width, int height, GfxImageColorMap* colorMap,
|
|
Stream* maskStr,
|
|
int maskWidth, int maskHeight,
|
|
bool maskInvert
|
|
)
|
|
{
|
|
appendFileHeader( o_rOutputBuf );
|
|
appendIHDR( o_rOutputBuf, width, height, 8, 6 ); // RGBA image
|
|
|
|
// initialize stream
|
|
unsigned char *p;
|
|
GfxRGB rgb;
|
|
std::unique_ptr<ImageStream> imgStr(
|
|
new ImageStream(str,
|
|
width,
|
|
colorMap->getNumPixelComps(),
|
|
colorMap->getBits()));
|
|
imgStr->reset();
|
|
|
|
// create scan line data buffer
|
|
OutputBuffer aScanlines;
|
|
aScanlines.reserve( width*height*4 + height );
|
|
|
|
for( int y=0; y<height; ++y)
|
|
{
|
|
aScanlines.push_back( 0 );
|
|
p = imgStr->getLine();
|
|
for( int x=0; x<width; ++x)
|
|
{
|
|
colorMap->getRGB(p, &rgb);
|
|
aScanlines.push_back(colToByte(rgb.r));
|
|
aScanlines.push_back(colToByte(rgb.g));
|
|
aScanlines.push_back(colToByte(rgb.b));
|
|
aScanlines.push_back( 0xff );
|
|
|
|
p +=colorMap->getNumPixelComps();
|
|
}
|
|
}
|
|
|
|
|
|
// now fill in the mask data
|
|
|
|
// CAUTION: originally this was done in one single loop
|
|
// it caused merry chaos; the reason is that maskStr and str are
|
|
// not independent streams, it happens that reading one advances
|
|
// the other, too. Hence the two passes are imperative !
|
|
|
|
// initialize mask stream
|
|
std::unique_ptr<ImageStream> imgStrMask(
|
|
new ImageStream(maskStr, maskWidth, 1, 1));
|
|
|
|
imgStrMask->reset();
|
|
for( int y = 0; y < maskHeight; ++y )
|
|
{
|
|
for( int x = 0; x < maskWidth; ++x )
|
|
{
|
|
unsigned char aPixel = 0;
|
|
imgStrMask->getPixel( &aPixel );
|
|
int nIndex = (y*height/maskHeight) * (width*4+1) + // mapped line
|
|
(x*width/maskWidth)*4 + 1 + 3 // mapped column
|
|
;
|
|
if( maskInvert )
|
|
aScanlines[ nIndex ] = aPixel ? 0xff : 0x00;
|
|
else
|
|
aScanlines[ nIndex ] = aPixel ? 0x00 : 0xff;
|
|
}
|
|
}
|
|
|
|
imgStr.reset();
|
|
imgStrMask.reset();
|
|
|
|
// begind IDAT chunk for scanline data
|
|
size_t nIdx = startChunk( "IDAT", o_rOutputBuf );
|
|
// compress scanlines
|
|
deflateBuffer( aScanlines.data(), aScanlines.size(), o_rOutputBuf );
|
|
// end IDAT chunk
|
|
endChunk( nIdx, o_rOutputBuf );
|
|
// output IEND
|
|
appendIEND( o_rOutputBuf );
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|