mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-25 11:22:50 +00:00
295 lines
9.2 KiB
C++
295 lines
9.2 KiB
C++
![]() |
/*
|
||
|
Large image load library -- PNG decoder
|
||
|
|
||
|
Copyright (C) 2004 Maksim Orlovich <maksim@kde.org>
|
||
|
|
||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
of this software and associated documentation files (the "Software"), to deal
|
||
|
in the Software without restriction, including without limitation the rights
|
||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
copies of the Software, and to permit persons to whom the Software is
|
||
|
furnished to do so, subject to the following conditions:
|
||
|
|
||
|
The above copyright notice and this permission notice shall be included in
|
||
|
all copies or substantial portions of the Software.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||
|
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
|
||
|
*/
|
||
|
|
||
|
#include "pngloader.h"
|
||
|
#include "imageloader.h"
|
||
|
#include "imagemanager.h"
|
||
|
|
||
|
#include <config.h> //For endian
|
||
|
#include <png.h>
|
||
|
|
||
|
namespace khtmlImLoad {
|
||
|
|
||
|
|
||
|
class PNGLoader;
|
||
|
/**
|
||
|
Paranoia: since g++ may very well store "this" in a register, we
|
||
|
use and external volatile to refer to our instance in case libPNG longjumps
|
||
|
on us. We also use this variable to find where to map libPNG's callbacks.
|
||
|
*/
|
||
|
static volatile PNGLoader* curLoader;
|
||
|
|
||
|
class PNGLoader: public ImageLoader
|
||
|
{
|
||
|
private:
|
||
|
png_structp pngReadStruct;
|
||
|
png_infop pngInfoStruct;
|
||
|
|
||
|
bool libPngError;
|
||
|
bool done;
|
||
|
|
||
|
bool interlaced;
|
||
|
|
||
|
png_uint_32 width;
|
||
|
png_uint_32 height;
|
||
|
unsigned int depth;
|
||
|
|
||
|
unsigned char* scanlineBuf; //Used only for interlaced images
|
||
|
|
||
|
/**
|
||
|
Dispatch hooks for libPNG callbacks -- call instance methods,
|
||
|
and make parameters slightly more our-style
|
||
|
*/
|
||
|
static void dispHaveInfo(png_structp, png_infop)
|
||
|
{
|
||
|
const_cast<PNGLoader*>(curLoader)->haveInfo();
|
||
|
}
|
||
|
|
||
|
static void dispHaveRow(png_structp, png_bytep row, png_uint_32 rowNum, int pass)
|
||
|
{
|
||
|
const_cast<PNGLoader*>(curLoader)->haveRow(rowNum, pass, row);
|
||
|
}
|
||
|
|
||
|
static void dispHaveEnd(png_structp, png_infop)
|
||
|
{
|
||
|
const_cast<PNGLoader*>(curLoader)->haveEnd();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Our implementation of libPNG callbacks
|
||
|
*/
|
||
|
void haveInfo()
|
||
|
{
|
||
|
int bitDepth, colorType, interlaceType;
|
||
|
|
||
|
png_get_IHDR(pngReadStruct, pngInfoStruct, &width, &height, &bitDepth,
|
||
|
&colorType, &interlaceType, 0, 0);
|
||
|
|
||
|
if (!ImageManager::isAcceptableSize(width, height)) {
|
||
|
libPngError = true;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//Ask libPNG to change bit depths we don't support
|
||
|
if (bitDepth < 8)
|
||
|
#if PNG_LIBPNG_VER < 10400
|
||
|
png_set_gray_1_2_4_to_8(pngReadStruct);
|
||
|
#else
|
||
|
png_set_expand_gray_1_2_4_to_8(pngReadStruct);
|
||
|
#endif
|
||
|
|
||
|
if (bitDepth > 8)
|
||
|
png_set_strip_16 (pngReadStruct);
|
||
|
|
||
|
//Some images (basically, only paletted ones) may have alpha
|
||
|
//included as part of a tRNS chunk. We want to convert that to regular alpha
|
||
|
//channel..
|
||
|
bool haveTRNS = false;
|
||
|
if (png_get_valid(pngReadStruct, pngInfoStruct, PNG_INFO_tRNS))
|
||
|
{
|
||
|
png_set_tRNS_to_alpha(pngReadStruct);
|
||
|
haveTRNS = true;
|
||
|
|
||
|
if (colorType == PNG_COLOR_TYPE_RGB)
|
||
|
colorType = PNG_COLOR_TYPE_RGB_ALPHA; //Paranoia..
|
||
|
else if (colorType == PNG_COLOR_TYPE_GRAY)
|
||
|
colorType = PNG_COLOR_TYPE_GRAY_ALPHA;
|
||
|
}
|
||
|
|
||
|
ImageFormat imFrm;
|
||
|
|
||
|
//Prepare for mapping from colorType to our format descriptors.
|
||
|
switch (colorType)
|
||
|
{
|
||
|
case PNG_COLOR_TYPE_GRAY:
|
||
|
imFrm.greyscaleSetup();
|
||
|
break;
|
||
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||
|
//We don't natively support 8-bit plus alpha, so ask libPNG to expand it out to RGB
|
||
|
png_set_gray_to_rgb(pngReadStruct);
|
||
|
imFrm.type = ImageFormat::Image_ARGB_32;
|
||
|
break;
|
||
|
case PNG_COLOR_TYPE_PALETTE:
|
||
|
//For now, we handle paletted images as RGB or ARGB
|
||
|
//### TODO: handle non-alpha paletted images with a sufficiently small palette as
|
||
|
//paletted
|
||
|
imFrm.type = haveTRNS ? ImageFormat::Image_ARGB_32 : ImageFormat::Image_RGB_32;
|
||
|
png_set_palette_to_rgb(pngReadStruct);
|
||
|
break;
|
||
|
case PNG_COLOR_TYPE_RGB:
|
||
|
imFrm.type = ImageFormat::Image_RGB_32;
|
||
|
break;
|
||
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||
|
imFrm.type = ImageFormat::Image_ARGB_32;
|
||
|
break;
|
||
|
default:
|
||
|
//Huh?
|
||
|
libPngError = true;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//Configure padding/byte swapping if need be (32-bit images)
|
||
|
//We want a 32-bit value with ARGB.
|
||
|
//This means that for little-endian, in memory we should have BGRA,
|
||
|
//and for big-endian, well, ARGB
|
||
|
if (imFrm.type == ImageFormat::Image_RGB_32)
|
||
|
{
|
||
|
//Need fillers, plus perhaps BGR swapping for non-alpha
|
||
|
#if Q_BYTE_ORDER == Q_BIG_ENDIAN || defined(__BIG_ENDIAN__)
|
||
|
png_set_filler(pngReadStruct, 0xff, PNG_FILLER_BEFORE);
|
||
|
#else
|
||
|
png_set_filler(pngReadStruct, 0xff, PNG_FILLER_AFTER);
|
||
|
png_set_bgr (pngReadStruct);
|
||
|
#endif
|
||
|
}
|
||
|
else if (imFrm.type == ImageFormat::Image_ARGB_32)
|
||
|
{
|
||
|
#if Q_BYTE_ORDER == Q_BIG_ENDIAN || defined(__BIG_ENDIAN__)
|
||
|
png_set_swap_alpha(pngReadStruct); //ARGB, not RGBA
|
||
|
#else
|
||
|
png_set_bgr (pngReadStruct); //BGRA
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
//Remember depth, for our own use
|
||
|
depth = imFrm.depth();
|
||
|
|
||
|
//handle interlacing
|
||
|
if (interlaceType != PNG_INTERLACE_NONE)
|
||
|
{
|
||
|
interlaced = true;
|
||
|
scanlineBuf = new unsigned char[depth * width];
|
||
|
png_set_interlace_handling(pngReadStruct);
|
||
|
|
||
|
// Give up on premultiply in this case..
|
||
|
if (imFrm.type == ImageFormat::Image_ARGB_32)
|
||
|
imFrm.type = ImageFormat::Image_ARGB_32_DontPremult;
|
||
|
}
|
||
|
|
||
|
notifySingleFrameImage(width, height, imFrm);
|
||
|
|
||
|
//OK, time to start input
|
||
|
png_read_update_info(pngReadStruct, pngInfoStruct);
|
||
|
}
|
||
|
|
||
|
void haveRow(unsigned int rowNum, int pass, unsigned char* data)
|
||
|
{
|
||
|
if (interlaced)
|
||
|
{
|
||
|
png_byte pngDepth = png_get_bit_depth(pngReadStruct, pngInfoStruct);
|
||
|
Q_ASSERT(pngDepth <= depth * 8);
|
||
|
requestScanline(rowNum, scanlineBuf);
|
||
|
png_progressive_combine_row(pngReadStruct, scanlineBuf, data);
|
||
|
notifyScanline(pass + 1, scanlineBuf);
|
||
|
}
|
||
|
else
|
||
|
notifyScanline(pass + 1, data);
|
||
|
}
|
||
|
|
||
|
void haveEnd()
|
||
|
{
|
||
|
done = true;
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
PNGLoader()
|
||
|
{
|
||
|
done = false;
|
||
|
libPngError = false;
|
||
|
|
||
|
interlaced = false;
|
||
|
scanlineBuf = 0;
|
||
|
|
||
|
//Setup the basic structures.
|
||
|
//### I think I want to pass the handlers to shut it up?
|
||
|
pngReadStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
|
||
|
|
||
|
pngInfoStruct = png_create_info_struct(pngReadStruct);
|
||
|
|
||
|
//Prepare for progressive loading
|
||
|
png_set_progressive_read_fn(pngReadStruct, 0, dispHaveInfo, dispHaveRow, dispHaveEnd);
|
||
|
}
|
||
|
|
||
|
~PNGLoader()
|
||
|
{
|
||
|
delete[] scanlineBuf;
|
||
|
|
||
|
png_destroy_read_struct(&pngReadStruct, &pngInfoStruct, 0);
|
||
|
//### CHECKME!
|
||
|
}
|
||
|
|
||
|
virtual int processData(uchar* data, int length)
|
||
|
{
|
||
|
if (done)
|
||
|
return Done;
|
||
|
|
||
|
//This bit takes care of dealing with the libPNG error handling
|
||
|
if (libPngError)
|
||
|
return Error;
|
||
|
|
||
|
curLoader = this;
|
||
|
if (setjmp(png_jmpbuf(pngReadStruct)))
|
||
|
{
|
||
|
curLoader->libPngError = true;
|
||
|
return Error;
|
||
|
}
|
||
|
|
||
|
//OK, now we can actually do work.... Push data to libPNG,
|
||
|
//it will use callbacks
|
||
|
//### time limiting?
|
||
|
png_process_data(pngReadStruct, pngInfoStruct, data, length);
|
||
|
|
||
|
return length;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
ImageLoaderProvider::Type PNGLoaderProvider::type()
|
||
|
{
|
||
|
return Efficient;
|
||
|
}
|
||
|
|
||
|
|
||
|
ImageLoader* PNGLoaderProvider::loaderFor(const QByteArray& prefix)
|
||
|
{
|
||
|
uchar* data = (uchar*)prefix.data();
|
||
|
if (prefix.size() < 8) return 0;
|
||
|
|
||
|
if(data[0] == 0x89 &&
|
||
|
data[1] == 'P' &&
|
||
|
data[2] == 'N' &&
|
||
|
data[3] == 'G' &&
|
||
|
data[4] == 0x0D &&
|
||
|
data[5] == 0x0A &&
|
||
|
data[6] == 0x1A &&
|
||
|
data[7] == 0x0A)
|
||
|
return new PNGLoader;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on;
|