diff options
Diffstat (limited to 'vcl/source/filter/png')
-rw-r--r-- | vcl/source/filter/png/PngImageReader.cxx | 532 | ||||
-rw-r--r-- | vcl/source/filter/png/png.hxx | 33 | ||||
-rw-r--r-- | vcl/source/filter/png/pngwrite.cxx | 660 |
3 files changed, 1225 insertions, 0 deletions
diff --git a/vcl/source/filter/png/PngImageReader.cxx b/vcl/source/filter/png/PngImageReader.cxx new file mode 100644 index 000000000..6a3053a72 --- /dev/null +++ b/vcl/source/filter/png/PngImageReader.cxx @@ -0,0 +1,532 @@ +/* -*- 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/. + * + */ + +#include <vcl/filter/PngImageReader.hxx> +#include <png.h> +#include <rtl/crc.h> +#include <tools/stream.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/alpha.hxx> +#include <vcl/BitmapTools.hxx> +#include <unotools/configmgr.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> +#include <svdata.hxx> +#include <salinst.hxx> + +#include "png.hxx" + +namespace +{ +void lclReadStream(png_structp pPng, png_bytep pOutBytes, png_size_t nBytesToRead) +{ + png_voidp pIO = png_get_io_ptr(pPng); + + if (pIO == nullptr) + return; + + SvStream* pStream = static_cast<SvStream*>(pIO); + + sal_Size nBytesRead = pStream->ReadBytes(pOutBytes, nBytesToRead); + + if (nBytesRead != nBytesToRead) + { + if (!nBytesRead) + png_error(pPng, "Error reading"); + else + { + // Make sure to not reuse old data (could cause infinite loop). + memset(pOutBytes + nBytesRead, 0, nBytesToRead - nBytesRead); + png_warning(pPng, "Short read"); + } + } +} + +constexpr int PNG_SIGNATURE_SIZE = 8; + +bool isPng(SvStream& rStream) +{ + // Check signature bytes + sal_uInt8 aHeader[PNG_SIGNATURE_SIZE]; + if (rStream.ReadBytes(aHeader, PNG_SIGNATURE_SIZE) != PNG_SIGNATURE_SIZE) + return false; + return png_sig_cmp(aHeader, 0, PNG_SIGNATURE_SIZE) == 0; +} + +struct PngDestructor +{ + ~PngDestructor() { png_destroy_read_struct(&pPng, &pInfo, nullptr); } + png_structp pPng; + png_infop pInfo; +}; + +#if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclobbered" +#endif +bool reader(SvStream& rStream, BitmapEx& rBitmapEx, + GraphicFilterImportFlags nImportFlags = GraphicFilterImportFlags::NONE, + BitmapScopedWriteAccess* pAccess = nullptr, + AlphaScopedWriteAccess* pAlphaAccess = nullptr) +{ + if (!isPng(rStream)) + return false; + + png_structp pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!pPng) + return false; + + png_infop pInfo = png_create_info_struct(pPng); + if (!pInfo) + { + png_destroy_read_struct(&pPng, nullptr, nullptr); + return false; + } + + PngDestructor pngDestructor = { pPng, pInfo }; + + // All variables holding resources need to be declared here in order to be + // properly cleaned up in case of an error, otherwise libpng's longjmp() + // jumps over the destructor calls. + Bitmap aBitmap; + AlphaMask aBitmapAlpha; + Size prefSize; + BitmapScopedWriteAccess pWriteAccessInstance; + AlphaScopedWriteAccess pWriteAccessAlphaInstance; + const bool bFuzzing = utl::ConfigManager::IsFuzzing(); + const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32(); + const bool bOnlyCreateBitmap + = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap); + const bool bUseExistingBitmap + = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap); + + if (setjmp(png_jmpbuf(pPng))) + { + if (!bUseExistingBitmap) + { + // Set the bitmap if it contains something, even on failure. This allows + // reading images that are only partially broken. + pWriteAccessInstance.reset(); + pWriteAccessAlphaInstance.reset(); + if (!aBitmap.IsEmpty() && !aBitmapAlpha.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); + else if (!aBitmap.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap); + if (!rBitmapEx.IsEmpty() && !prefSize.IsEmpty()) + { + rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + rBitmapEx.SetPrefSize(prefSize); + } + } + return false; + } + + png_set_option(pPng, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); + + png_set_read_fn(pPng, &rStream, lclReadStream); + + if (!bFuzzing) + png_set_crc_action(pPng, PNG_CRC_ERROR_QUIT, PNG_CRC_WARN_DISCARD); + else + png_set_crc_action(pPng, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + + png_set_sig_bytes(pPng, PNG_SIGNATURE_SIZE); + + png_read_info(pPng, pInfo); + + png_uint_32 width = 0; + png_uint_32 height = 0; + int bitDepth = 0; + int colorType = -1; + int interlace = -1; + + png_uint_32 returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType, + &interlace, nullptr, nullptr); + + if (returnValue != 1) + return false; + + if (colorType == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(pPng); + + if (colorType == PNG_COLOR_TYPE_GRAY) + png_set_expand_gray_1_2_4_to_8(pPng); + + if (png_get_valid(pPng, pInfo, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(pPng); + + if (bitDepth == 16) + png_set_scale_16(pPng); + + if (bitDepth < 8) + png_set_packing(pPng); + + // Convert gray+alpha to RGBA, keep gray as gray. + if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA + || (colorType == PNG_COLOR_TYPE_GRAY && png_get_valid(pPng, pInfo, PNG_INFO_tRNS))) + { + png_set_gray_to_rgb(pPng); + } + + // Sets the filler byte - if RGB it converts to RGBA + // png_set_filler(pPng, 0xFF, PNG_FILLER_AFTER); + + int nNumberOfPasses = png_set_interlace_handling(pPng); + + png_read_update_info(pPng, pInfo); + returnValue = png_get_IHDR(pPng, pInfo, &width, &height, &bitDepth, &colorType, nullptr, + nullptr, nullptr); + + if (returnValue != 1) + return false; + + if (bitDepth != 8 + || (colorType != PNG_COLOR_TYPE_RGB && colorType != PNG_COLOR_TYPE_RGB_ALPHA + && colorType != PNG_COLOR_TYPE_GRAY)) + { + return false; + } + + png_uint_32 res_x = 0; + png_uint_32 res_y = 0; + int unit_type = 0; + if (png_get_pHYs(pPng, pInfo, &res_x, &res_y, &unit_type) != 0 + && unit_type == PNG_RESOLUTION_METER && res_x && res_y) + { + // convert into MapUnit::Map100thMM + prefSize = Size(static_cast<sal_Int32>((100000.0 * width) / res_x), + static_cast<sal_Int32>((100000.0 * height) / res_y)); + } + + if (!bUseExistingBitmap) + { + switch (colorType) + { + case PNG_COLOR_TYPE_RGB: + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); + break; + case PNG_COLOR_TYPE_RGBA: + if (bSupportsBitmap32) + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP); + else + { + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); + aBitmapAlpha = AlphaMask(Size(width, height), nullptr); + } + break; + case PNG_COLOR_TYPE_GRAY: + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N8_BPP, + &Bitmap::GetGreyPalette(256)); + break; + default: + abort(); + } + + if (bOnlyCreateBitmap) + { + if (!aBitmapAlpha.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); + else + rBitmapEx = BitmapEx(aBitmap); + if (!prefSize.IsEmpty()) + { + rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + rBitmapEx.SetPrefSize(prefSize); + } + return true; + } + + pWriteAccessInstance = BitmapScopedWriteAccess(aBitmap); + if (!pWriteAccessInstance) + return false; + if (!aBitmapAlpha.IsEmpty()) + { + pWriteAccessAlphaInstance = AlphaScopedWriteAccess(aBitmapAlpha); + if (!pWriteAccessAlphaInstance) + return false; + } + } + BitmapScopedWriteAccess& pWriteAccess = pAccess ? *pAccess : pWriteAccessInstance; + AlphaScopedWriteAccess& pWriteAccessAlpha + = pAlphaAccess ? *pAlphaAccess : pWriteAccessAlphaInstance; + + if (colorType == PNG_COLOR_TYPE_RGB) + { + ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat(); + if (eFormat == ScanlineFormat::N24BitTcBgr) + png_set_bgr(pPng); + + for (int pass = 0; pass < nNumberOfPasses; pass++) + { + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + png_read_row(pPng, pScanline, nullptr); + } + } + } + else if (colorType == PNG_COLOR_TYPE_RGB_ALPHA) + { + size_t aRowSizeBytes = png_get_rowbytes(pPng, pInfo); + + if (bSupportsBitmap32) + { + ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat(); + if (eFormat == ScanlineFormat::N32BitTcAbgr || eFormat == ScanlineFormat::N32BitTcBgra) + png_set_bgr(pPng); + + for (int pass = 0; pass < nNumberOfPasses; pass++) + { + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + png_read_row(pPng, pScanline, nullptr); + } + } +#if !ENABLE_WASM_STRIP_PREMULTIPLY + const vcl::bitmap::lookup_table& premultiply = vcl::bitmap::get_premultiply_table(); +#endif + if (eFormat == ScanlineFormat::N32BitTcAbgr || eFormat == ScanlineFormat::N32BitTcArgb) + { // alpha first and premultiply + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + for (size_t i = 0; i < aRowSizeBytes; i += 4) + { + const sal_uInt8 alpha = pScanline[i + 3]; +#if ENABLE_WASM_STRIP_PREMULTIPLY + pScanline[i + 3] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]); + pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]); + pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i]); +#else + pScanline[i + 3] = premultiply[alpha][pScanline[i + 2]]; + pScanline[i + 2] = premultiply[alpha][pScanline[i + 1]]; + pScanline[i + 1] = premultiply[alpha][pScanline[i]]; +#endif + pScanline[i] = alpha; + } + } + } + else + { // keep alpha last, only premultiply + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + for (size_t i = 0; i < aRowSizeBytes; i += 4) + { + const sal_uInt8 alpha = pScanline[i + 3]; +#if ENABLE_WASM_STRIP_PREMULTIPLY + pScanline[i] = vcl::bitmap::premultiply(alpha, pScanline[i]); + pScanline[i + 1] = vcl::bitmap::premultiply(alpha, pScanline[i + 1]); + pScanline[i + 2] = vcl::bitmap::premultiply(alpha, pScanline[i + 2]); +#else + pScanline[i] = premultiply[alpha][pScanline[i]]; + pScanline[i + 1] = premultiply[alpha][pScanline[i + 1]]; + pScanline[i + 2] = premultiply[alpha][pScanline[i + 2]]; +#endif + } + } + } + } + else + { + ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat(); + if (eFormat == ScanlineFormat::N24BitTcBgr) + png_set_bgr(pPng); + + if (nNumberOfPasses == 1) + { + // optimise the common case, where we can use a buffer of only a single row + std::vector<png_byte> aRow(aRowSizeBytes, 0); + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y); + png_bytep pRow = aRow.data(); + png_read_row(pPng, pRow, nullptr); + size_t iAlpha = 0; + size_t iColor = 0; + for (size_t i = 0; i < aRowSizeBytes; i += 4) + { + pScanline[iColor++] = pRow[i + 0]; + pScanline[iColor++] = pRow[i + 1]; + pScanline[iColor++] = pRow[i + 2]; + pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3]; + } + } + } + else + { + std::vector<std::vector<png_byte>> aRows(height); + for (auto& rRow : aRows) + rRow.resize(aRowSizeBytes, 0); + for (int pass = 0; pass < nNumberOfPasses; pass++) + { + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + Scanline pScanAlpha = pWriteAccessAlpha->GetScanline(y); + png_bytep pRow = aRows[y].data(); + png_read_row(pPng, pRow, nullptr); + size_t iAlpha = 0; + size_t iColor = 0; + for (size_t i = 0; i < aRowSizeBytes; i += 4) + { + pScanline[iColor++] = pRow[i + 0]; + pScanline[iColor++] = pRow[i + 1]; + pScanline[iColor++] = pRow[i + 2]; + pScanAlpha[iAlpha++] = 0xFF - pRow[i + 3]; + } + } + } + } + } + } + else if (colorType == PNG_COLOR_TYPE_GRAY) + { + for (int pass = 0; pass < nNumberOfPasses; pass++) + { + for (png_uint_32 y = 0; y < height; y++) + { + Scanline pScanline = pWriteAccess->GetScanline(y); + png_read_row(pPng, pScanline, nullptr); + } + } + } + + png_read_end(pPng, pInfo); + + if (!bUseExistingBitmap) + { + pWriteAccess.reset(); + pWriteAccessAlpha.reset(); + if (!aBitmapAlpha.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); + else + rBitmapEx = BitmapEx(aBitmap); + if (!prefSize.IsEmpty()) + { + rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + rBitmapEx.SetPrefSize(prefSize); + } + } + + return true; +} + +std::unique_ptr<sal_uInt8[]> getMsGifChunk(SvStream& rStream, sal_Int32* chunkSize) +{ + if (chunkSize) + *chunkSize = 0; + if (!isPng(rStream)) + return nullptr; + // It's easier to read manually the contents and find the chunk than + // try to get it using libpng. + // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_format + // Each chunk is: 4 bytes length, 4 bytes type, <length> bytes, 4 bytes crc + bool ignoreCrc = utl::ConfigManager::IsFuzzing(); + for (;;) + { + sal_uInt32 length(0), type(0), crc(0); + rStream.ReadUInt32(length); + rStream.ReadUInt32(type); + if (!rStream.good()) + return nullptr; + constexpr sal_uInt32 PNGCHUNK_msOG = 0x6d734f47; // Microsoft Office Animated GIF + constexpr sal_uInt64 MSGifHeaderSize = 11; // "MSOFFICE9.0" + if (type == PNGCHUNK_msOG && length > MSGifHeaderSize) + { + // calculate chunktype CRC (swap it back to original byte order) + sal_uInt32 typeForCrc = type; +#if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN) + typeForCrc = OSL_SWAPDWORD(typeForCrc); +#endif + sal_uInt32 computedCrc = rtl_crc32(0, &typeForCrc, 4); + const sal_uInt64 pos = rStream.Tell(); + if (pos + length >= rStream.TellEnd()) + return nullptr; // broken PNG + + char msHeader[MSGifHeaderSize]; + if (rStream.ReadBytes(msHeader, MSGifHeaderSize) != MSGifHeaderSize) + return nullptr; + computedCrc = rtl_crc32(computedCrc, msHeader, MSGifHeaderSize); + length -= MSGifHeaderSize; + + std::unique_ptr<sal_uInt8[]> chunk(new sal_uInt8[length]); + if (rStream.ReadBytes(chunk.get(), length) != length) + return nullptr; + computedCrc = rtl_crc32(computedCrc, chunk.get(), length); + rStream.ReadUInt32(crc); + if (!ignoreCrc && crc != computedCrc) + continue; // invalid chunk, ignore + if (chunkSize) + *chunkSize = length; + return chunk; + } + if (rStream.remainingSize() < length) + return nullptr; + rStream.SeekRel(length); + rStream.ReadUInt32(crc); + constexpr sal_uInt32 PNGCHUNK_IEND = 0x49454e44; + if (type == PNGCHUNK_IEND) + return nullptr; + } +} +#if defined __GNUC__ && __GNUC__ == 8 && !defined __clang__ +#pragma GCC diagnostic pop +#endif + +} // anonymous namespace + +namespace vcl +{ +PngImageReader::PngImageReader(SvStream& rStream) + : mrStream(rStream) +{ +} + +bool PngImageReader::read(BitmapEx& rBitmapEx) { return reader(mrStream, rBitmapEx); } + +BitmapEx PngImageReader::read() +{ + BitmapEx bitmap; + read(bitmap); + return bitmap; +} + +std::unique_ptr<sal_uInt8[]> PngImageReader::getMicrosoftGifChunk(SvStream& rStream, + sal_Int32* chunkSize) +{ + sal_uInt64 originalPosition = rStream.Tell(); + SvStreamEndian originalEndian = rStream.GetEndian(); + rStream.SetEndian(SvStreamEndian::BIG); + std::unique_ptr<sal_uInt8[]> chunk = getMsGifChunk(rStream, chunkSize); + rStream.SetEndian(originalEndian); + rStream.Seek(originalPosition); + return chunk; +} + +bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, + BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess) +{ + // Creating empty bitmaps should be practically a no-op, and thus thread-safe. + BitmapEx bitmap; + if (reader(rInputStream, bitmap, nImportFlags, pAccess, pAlphaAccess)) + { + if (!(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap)) + rGraphic = bitmap; + return true; + } + return false; +} + +} // namespace vcl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/png/png.hxx b/vcl/source/filter/png/png.hxx new file mode 100644 index 000000000..b3a1b7b17 --- /dev/null +++ b/vcl/source/filter/png/png.hxx @@ -0,0 +1,33 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/graph.hxx> +#include <vcl/graphicfilter.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> + +namespace vcl +{ +bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, + BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/png/pngwrite.cxx b/vcl/source/filter/png/pngwrite.cxx new file mode 100644 index 000000000..865fe38eb --- /dev/null +++ b/vcl/source/filter/png/pngwrite.cxx @@ -0,0 +1,660 @@ +/* -*- 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 <vcl/pngwrite.hxx> +#include <vcl/bitmapex.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/uno/Sequence.hxx> + +#include <limits> +#include <rtl/crc.h> +#include <tools/solar.h> +#include <tools/zcodec.hxx> +#include <tools/stream.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/alpha.hxx> +#include <osl/endian.h> +#include <memory> +#include <vcl/BitmapTools.hxx> + +#define PNG_DEF_COMPRESSION 6 + +#define PNGCHUNK_IHDR 0x49484452 +#define PNGCHUNK_PLTE 0x504c5445 +#define PNGCHUNK_IDAT 0x49444154 +#define PNGCHUNK_IEND 0x49454e44 +#define PNGCHUNK_pHYs 0x70485973 + +namespace vcl +{ +class PNGWriterImpl +{ +public: + PNGWriterImpl(const BitmapEx& BmpEx, + const css::uno::Sequence<css::beans::PropertyValue>* pFilterData); + + bool Write(SvStream& rOutStream); + + std::vector<vcl::PNGWriter::ChunkData>& GetChunks() { return maChunkSeq; } + +private: + std::vector<vcl::PNGWriter::ChunkData> maChunkSeq; + + sal_Int32 mnCompLevel; + sal_Int32 mnInterlaced; + sal_uInt32 mnMaxChunkSize; + bool mbStatus; + + Bitmap::ScopedReadAccess mpAccess; + BitmapReadAccess* mpMaskAccess; + ZCodec mpZCodec; + + std::unique_ptr<sal_uInt8[]> + mpDeflateInBuf; // as big as the size of a scanline + alphachannel + 1 + std::unique_ptr<sal_uInt8[]> mpPreviousScan; // as big as mpDeflateInBuf + std::unique_ptr<sal_uInt8[]> mpCurrentScan; + sal_uLong mnDeflateInSize; + + sal_uLong mnWidth; + sal_uLong mnHeight; + sal_uInt8 mnBitsPerPixel; + sal_uInt8 mnFilterType; // 0 or 4; + sal_uLong mnBBP; // bytes per pixel ( needed for filtering ) + bool mbTrueAlpha; + + void ImplWritepHYs(const BitmapEx& rBitmapEx); + void ImplWriteIDAT(); + sal_uLong ImplGetFilter(sal_uLong nY, sal_uLong nXStart = 0, sal_uLong nXAdd = 1); + void ImplClearFirstScanline(); + bool ImplWriteHeader(); + void ImplWritePalette(); + void ImplOpenChunk(sal_uLong nChunkType); + void ImplWriteChunk(sal_uInt8 nNumb); + void ImplWriteChunk(sal_uInt32 nNumb); + void ImplWriteChunk(unsigned char const* pSource, sal_uInt32 nDatSize); +}; + +PNGWriterImpl::PNGWriterImpl(const BitmapEx& rBitmapEx, + const css::uno::Sequence<css::beans::PropertyValue>* pFilterData) + : mnCompLevel(PNG_DEF_COMPRESSION) + , mnInterlaced(0) + , mnMaxChunkSize(0) + , mbStatus(true) + , mpMaskAccess(nullptr) + , mnDeflateInSize(0) + , mnWidth(0) + , mnHeight(0) + , mnBitsPerPixel(0) + , mnFilterType(0) + , mnBBP(0) + , mbTrueAlpha(false) +{ + if (rBitmapEx.IsEmpty()) + return; + + BitmapEx aBitmapEx; + + if (rBitmapEx.GetBitmap().getPixelFormat() == vcl::PixelFormat::N32_BPP) + { + if (!vcl::bitmap::convertBitmap32To24Plus8(rBitmapEx, aBitmapEx)) + return; + } + else + { + aBitmapEx = rBitmapEx; + } + + Bitmap aBmp(aBitmapEx.GetBitmap()); + + mnMaxChunkSize = std::numeric_limits<sal_uInt32>::max(); + bool bTranslucent = true; + + if (pFilterData) + { + for (const auto& rPropVal : *pFilterData) + { + if (rPropVal.Name == "Compression") + rPropVal.Value >>= mnCompLevel; + else if (rPropVal.Name == "Interlaced") + rPropVal.Value >>= mnInterlaced; + else if (rPropVal.Name == "Translucent") + { + tools::Long nTmp = 0; + rPropVal.Value >>= nTmp; + if (!nTmp) + bTranslucent = false; + } + else if (rPropVal.Name == "MaxChunkSize") + { + sal_Int32 nVal = 0; + if (rPropVal.Value >>= nVal) + mnMaxChunkSize = static_cast<sal_uInt32>(nVal); + } + } + } + mnBitsPerPixel = sal_uInt8(vcl::pixelFormatBitCount(aBmp.getPixelFormat())); + + if (aBitmapEx.IsAlpha() && bTranslucent) + { + if (mnBitsPerPixel <= 8) + { + aBmp.Convert(BmpConversion::N24Bit); + mnBitsPerPixel = 24; + } + + mpAccess = Bitmap::ScopedReadAccess(aBmp); // true RGB with alphachannel + if (mpAccess) + { + mbTrueAlpha = true; + AlphaMask aMask(aBitmapEx.GetAlpha()); + mpMaskAccess = aMask.AcquireReadAccess(); + if (mpMaskAccess) + { + if (ImplWriteHeader()) + { + ImplWritepHYs(aBitmapEx); + ImplWriteIDAT(); + } + aMask.ReleaseAccess(mpMaskAccess); + mpMaskAccess = nullptr; + } + else + { + mbStatus = false; + } + mpAccess.reset(); + } + else + { + mbStatus = false; + } + } + else + { + mpAccess = Bitmap::ScopedReadAccess(aBmp); // palette + RGB without alphachannel + if (mpAccess) + { + if (ImplWriteHeader()) + { + ImplWritepHYs(aBitmapEx); + if (mpAccess->HasPalette()) + ImplWritePalette(); + + ImplWriteIDAT(); + } + mpAccess.reset(); + } + else + { + mbStatus = false; + } + } + + if (mbStatus) + { + ImplOpenChunk(PNGCHUNK_IEND); // create an IEND chunk + } +} + +bool PNGWriterImpl::Write(SvStream& rOStm) +{ + /* png signature is always an array of 8 bytes */ + SvStreamEndian nOldMode = rOStm.GetEndian(); + rOStm.SetEndian(SvStreamEndian::BIG); + rOStm.WriteUInt32(0x89504e47); + rOStm.WriteUInt32(0x0d0a1a0a); + + for (auto const& chunk : maChunkSeq) + { + sal_uInt32 nType = chunk.nType; +#if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN) + nType = OSL_SWAPDWORD(nType); +#endif + sal_uInt32 nCRC = rtl_crc32(0, &nType, 4); + sal_uInt32 nDataSize = chunk.aData.size(); + if (nDataSize) + nCRC = rtl_crc32(nCRC, chunk.aData.data(), nDataSize); + rOStm.WriteUInt32(nDataSize); + rOStm.WriteUInt32(chunk.nType); + if (nDataSize) + rOStm.WriteBytes(chunk.aData.data(), nDataSize); + rOStm.WriteUInt32(nCRC); + } + rOStm.SetEndian(nOldMode); + return mbStatus; +} + +bool PNGWriterImpl::ImplWriteHeader() +{ + ImplOpenChunk(PNGCHUNK_IHDR); + mnWidth = mpAccess->Width(); + ImplWriteChunk(sal_uInt32(mnWidth)); + mnHeight = mpAccess->Height(); + ImplWriteChunk(sal_uInt32(mnHeight)); + + if (mnWidth && mnHeight && mnBitsPerPixel && mbStatus) + { + sal_uInt8 nBitDepth = mnBitsPerPixel; + if (mnBitsPerPixel <= 8) + mnFilterType = 0; + else + mnFilterType = 4; + + sal_uInt8 nColorType = 2; // colortype: + + // bit 0 -> palette is used + if (mpAccess->HasPalette()) // bit 1 -> color is used + nColorType |= 1; // bit 2 -> alpha channel is used + else + nBitDepth /= 3; + + if (mpMaskAccess) + nColorType |= 4; + + ImplWriteChunk(nBitDepth); + ImplWriteChunk(nColorType); // colortype + ImplWriteChunk(static_cast<sal_uInt8>(0)); // compression type + ImplWriteChunk(static_cast<sal_uInt8>(0)); // filter type - is not supported in this version + ImplWriteChunk(static_cast<sal_uInt8>(mnInterlaced)); // interlace type + } + else + { + mbStatus = false; + } + return mbStatus; +} + +void PNGWriterImpl::ImplWritePalette() +{ + const sal_uLong nCount = mpAccess->GetPaletteEntryCount(); + std::unique_ptr<sal_uInt8[]> pTempBuf(new sal_uInt8[nCount * 3]); + sal_uInt8* pTmp = pTempBuf.get(); + + ImplOpenChunk(PNGCHUNK_PLTE); + + for (sal_uLong i = 0; i < nCount; i++) + { + const BitmapColor& rColor = mpAccess->GetPaletteColor(i); + *pTmp++ = rColor.GetRed(); + *pTmp++ = rColor.GetGreen(); + *pTmp++ = rColor.GetBlue(); + } + ImplWriteChunk(pTempBuf.get(), nCount * 3); +} + +void PNGWriterImpl::ImplWritepHYs(const BitmapEx& rBmpEx) +{ + if (rBmpEx.GetPrefMapMode().GetMapUnit() != MapUnit::Map100thMM) + return; + + Size aPrefSize(rBmpEx.GetPrefSize()); + + if (aPrefSize.Width() && aPrefSize.Height() && mnWidth && mnHeight) + { + ImplOpenChunk(PNGCHUNK_pHYs); + sal_uInt32 nPrefSizeX = static_cast<sal_uInt32>( + 100000.0 / (static_cast<double>(aPrefSize.Width()) / mnWidth) + 0.5); + sal_uInt32 nPrefSizeY = static_cast<sal_uInt32>( + 100000.0 / (static_cast<double>(aPrefSize.Height()) / mnHeight) + 0.5); + ImplWriteChunk(nPrefSizeX); + ImplWriteChunk(nPrefSizeY); + ImplWriteChunk(sal_uInt8(1)); // nMapUnit + } +} + +void PNGWriterImpl::ImplWriteIDAT() +{ + mnDeflateInSize = mnBitsPerPixel; + + if (mpMaskAccess) + mnDeflateInSize += 8; + + mnBBP = (mnDeflateInSize + 7) >> 3; + + mnDeflateInSize = mnBBP * mnWidth + 1; + + mpDeflateInBuf.reset(new sal_uInt8[mnDeflateInSize]); + + if (mnFilterType) // using filter type 4 we need memory for the scanline 3 times + { + mpPreviousScan.reset(new sal_uInt8[mnDeflateInSize]); + mpCurrentScan.reset(new sal_uInt8[mnDeflateInSize]); + ImplClearFirstScanline(); + } + mpZCodec.BeginCompression(mnCompLevel); + SvMemoryStream aOStm; + if (mnInterlaced == 0) + { + for (sal_uLong nY = 0; nY < mnHeight; nY++) + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY)); + } + } + else + { + // interlace mode + sal_uLong nY; + for (nY = 0; nY < mnHeight; nY += 8) // pass 1 + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 8)); + } + ImplClearFirstScanline(); + + for (nY = 0; nY < mnHeight; nY += 8) // pass 2 + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 4, 8)); + } + ImplClearFirstScanline(); + + if (mnHeight >= 5) // pass 3 + { + for (nY = 4; nY < mnHeight; nY += 8) + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 4)); + } + ImplClearFirstScanline(); + } + + for (nY = 0; nY < mnHeight; nY += 4) // pass 4 + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 2, 4)); + } + ImplClearFirstScanline(); + + if (mnHeight >= 3) // pass 5 + { + for (nY = 2; nY < mnHeight; nY += 4) + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 0, 2)); + } + ImplClearFirstScanline(); + } + + for (nY = 0; nY < mnHeight; nY += 2) // pass 6 + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY, 1, 2)); + } + ImplClearFirstScanline(); + + if (mnHeight >= 2) // pass 7 + { + for (nY = 1; nY < mnHeight; nY += 2) + { + mpZCodec.Write(aOStm, mpDeflateInBuf.get(), ImplGetFilter(nY)); + } + } + } + mpZCodec.EndCompression(); + + if (mnFilterType) // using filter type 4 we need memory for the scanline 3 times + { + mpCurrentScan.reset(); + mpPreviousScan.reset(); + } + mpDeflateInBuf.reset(); + + sal_uInt32 nIDATSize = aOStm.Tell(); + sal_uInt32 nBytes, nBytesToWrite = nIDATSize; + while (nBytesToWrite) + { + nBytes = nBytesToWrite <= mnMaxChunkSize ? nBytesToWrite : mnMaxChunkSize; + ImplOpenChunk(PNGCHUNK_IDAT); + ImplWriteChunk( + const_cast<unsigned char*>(static_cast<unsigned char const*>(aOStm.GetData())) + + (nIDATSize - nBytesToWrite), + nBytes); + nBytesToWrite -= nBytes; + } +} + +// ImplGetFilter writes the complete Scanline (nY) - in interlace mode the parameter nXStart and nXAdd +// appends to the currently used pass +// the complete size of scanline will be returned - in interlace mode zero is possible! + +sal_uLong PNGWriterImpl::ImplGetFilter(sal_uLong nY, sal_uLong nXStart, sal_uLong nXAdd) +{ + sal_uInt8* pDest; + + if (mnFilterType) + pDest = mpCurrentScan.get(); + else + pDest = mpDeflateInBuf.get(); + + if (nXStart < mnWidth) + { + *pDest++ = mnFilterType; // in this version the filter type is either 0 or 4 + + if (mpAccess + ->HasPalette()) // alphachannel is not allowed by pictures including palette entries + { + switch (mnBitsPerPixel) + { + case 1: + { + Scanline pScanline = mpAccess->GetScanline(nY); + sal_uLong nX, nXIndex; + for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++) + { + sal_uLong nShift = (nXIndex & 7) ^ 7; + if (nShift == 7) + *pDest = mpAccess->GetIndexFromData(pScanline, nX) << nShift; + else if (nShift == 0) + *pDest++ |= mpAccess->GetIndexFromData(pScanline, nX) << nShift; + else + *pDest |= mpAccess->GetIndexFromData(pScanline, nX) << nShift; + } + if ((nXIndex & 7) != 0) + pDest++; // byte is not completely used, so the bufferpointer is to correct + } + break; + + case 4: + { + Scanline pScanline = mpAccess->GetScanline(nY); + sal_uLong nX, nXIndex; + for (nX = nXStart, nXIndex = 0; nX < mnWidth; nX += nXAdd, nXIndex++) + { + if (nXIndex & 1) + *pDest++ |= mpAccess->GetIndexFromData(pScanline, nX); + else + *pDest = mpAccess->GetIndexFromData(pScanline, nX) << 4; + } + if (nXIndex & 1) + pDest++; + } + break; + + case 8: + { + Scanline pScanline = mpAccess->GetScanline(nY); + for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd) + { + *pDest++ = mpAccess->GetIndexFromData(pScanline, nX); + } + } + break; + + default: + mbStatus = false; + break; + } + } + else + { + if (mpMaskAccess) // mpMaskAccess != NULL -> alphachannel is to create + { + if (mbTrueAlpha) + { + Scanline pScanline = mpAccess->GetScanline(nY); + Scanline pScanlineMask = mpMaskAccess->GetScanline(nY); + for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd) + { + const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX); + *pDest++ = rColor.GetRed(); + *pDest++ = rColor.GetGreen(); + *pDest++ = rColor.GetBlue(); + *pDest++ = 255 - mpMaskAccess->GetIndexFromData(pScanlineMask, nX); + } + } + else + { + const BitmapColor aTrans(mpMaskAccess->GetBestMatchingColor(COL_WHITE)); + Scanline pScanline = mpAccess->GetScanline(nY); + Scanline pScanlineMask = mpMaskAccess->GetScanline(nY); + + for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd) + { + const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX); + *pDest++ = rColor.GetRed(); + *pDest++ = rColor.GetGreen(); + *pDest++ = rColor.GetBlue(); + + if (mpMaskAccess->GetPixelFromData(pScanlineMask, nX) == aTrans) + *pDest++ = 0; + else + *pDest++ = 0xff; + } + } + } + else + { + Scanline pScanline = mpAccess->GetScanline(nY); + for (sal_uLong nX = nXStart; nX < mnWidth; nX += nXAdd) + { + const BitmapColor& rColor = mpAccess->GetPixelFromData(pScanline, nX); + *pDest++ = rColor.GetRed(); + *pDest++ = rColor.GetGreen(); + *pDest++ = rColor.GetBlue(); + } + } + } + } + // filter type4 ( PAETH ) will be used only for 24bit graphics + if (mnFilterType) + { + mnDeflateInSize = pDest - mpCurrentScan.get(); + pDest = mpDeflateInBuf.get(); + *pDest++ = 4; // filter type + + sal_uInt8* p1 = mpCurrentScan.get() + 1; // Current Pixel + sal_uInt8* p2 = p1 - mnBBP; // left pixel + sal_uInt8* p3 = mpPreviousScan.get(); // upper pixel + sal_uInt8* p4 = p3 - mnBBP; // upperleft Pixel; + + while (pDest < mpDeflateInBuf.get() + mnDeflateInSize) + { + sal_uLong nb = *p3++; + sal_uLong na, nc; + if (p2 >= mpCurrentScan.get() + 1) + { + na = *p2; + nc = *p4; + } + else + { + na = nc = 0; + } + + tools::Long np = na + nb - nc; + tools::Long npa = np - na; + tools::Long npb = np - nb; + tools::Long npc = np - nc; + + if (npa < 0) + npa = -npa; + if (npb < 0) + npb = -npb; + if (npc < 0) + npc = -npc; + + if (npa <= npb && npa <= npc) + *pDest++ = *p1++ - static_cast<sal_uInt8>(na); + else if (npb <= npc) + *pDest++ = *p1++ - static_cast<sal_uInt8>(nb); + else + *pDest++ = *p1++ - static_cast<sal_uInt8>(nc); + + p4++; + p2++; + } + for (tools::Long i = 0; i < static_cast<tools::Long>(mnDeflateInSize - 1); i++) + { + mpPreviousScan[i] = mpCurrentScan[i + 1]; + } + } + else + { + mnDeflateInSize = pDest - mpDeflateInBuf.get(); + } + return mnDeflateInSize; +} + +void PNGWriterImpl::ImplClearFirstScanline() +{ + if (mnFilterType) + memset(mpPreviousScan.get(), 0, mnDeflateInSize); +} + +void PNGWriterImpl::ImplOpenChunk(sal_uLong nChunkType) +{ + maChunkSeq.emplace_back(); + maChunkSeq.back().nType = nChunkType; +} + +void PNGWriterImpl::ImplWriteChunk(sal_uInt8 nSource) +{ + maChunkSeq.back().aData.push_back(nSource); +} + +void PNGWriterImpl::ImplWriteChunk(sal_uInt32 nSource) +{ + vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back(); + rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 24)); + rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 16)); + rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource >> 8)); + rChunkData.aData.push_back(static_cast<sal_uInt8>(nSource)); +} + +void PNGWriterImpl::ImplWriteChunk(unsigned char const* pSource, sal_uInt32 nDatSize) +{ + if (nDatSize) + { + vcl::PNGWriter::ChunkData& rChunkData = maChunkSeq.back(); + sal_uInt32 nSize = rChunkData.aData.size(); + rChunkData.aData.resize(nSize + nDatSize); + memcpy(&rChunkData.aData[nSize], pSource, nDatSize); + } +} + +PNGWriter::PNGWriter(const BitmapEx& rBmpEx, + const css::uno::Sequence<css::beans::PropertyValue>* pFilterData) + : mpImpl(new vcl::PNGWriterImpl(rBmpEx, pFilterData)) +{ +} + +PNGWriter::~PNGWriter() {} + +bool PNGWriter::Write(SvStream& rStream) { return mpImpl->Write(rStream); } + +std::vector<vcl::PNGWriter::ChunkData>& PNGWriter::GetChunks() { return mpImpl->GetChunks(); } + +} // namespace vcl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |