/* Large image load library -- PNG decoder Copyright (C) 2004 Maksim Orlovich 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 //For endian #include 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(curLoader)->haveInfo(); } static void dispHaveRow(png_structp, png_bytep row, png_uint_32 rowNum, int pass) { const_cast(curLoader)->haveRow(rowNum, pass, row); } static void dispHaveEnd(png_structp, png_infop) { const_cast(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;