LibreOffice Module vcl (master) 1
PngImageWriter.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
9
11#include <png.h>
13#include <vcl/bitmap.hxx>
14#include <vcl/BitmapTools.hxx>
15
16namespace
17{
18void combineScanlineChannels(Scanline pRGBScanline, Scanline pAlphaScanline,
19 std::vector<std::remove_pointer_t<Scanline>>& pResult,
20 sal_uInt32 nBitmapWidth)
21{
22 for (sal_uInt32 i = 0; i < nBitmapWidth; ++i)
23 {
24 pResult[i * 4] = *pRGBScanline++; // R
25 pResult[i * 4 + 1] = *pRGBScanline++; // G
26 pResult[i * 4 + 2] = *pRGBScanline++; // B
27 pResult[i * 4 + 3] = *pAlphaScanline++; // A
28 }
29}
30}
31
32namespace vcl
33{
34static void lclWriteStream(png_structp pPng, png_bytep pData, png_size_t pDataSize)
35{
36 png_voidp pIO = png_get_io_ptr(pPng);
37
38 if (pIO == nullptr)
39 return;
40
41 SvStream* pStream = static_cast<SvStream*>(pIO);
42
43 sal_Size nBytesWritten = pStream->WriteBytes(pData, pDataSize);
44
45 if (nBytesWritten != pDataSize)
46 png_error(pPng, "Write Error");
47}
48
49static bool pngWrite(SvStream& rStream, const BitmapEx& rBitmapEx, int nCompressionLevel,
50 bool bInterlaced, bool bTranslucent,
51 const std::vector<PngChunk>& aAdditionalChunks)
52{
53 if (rBitmapEx.IsAlpha() && !bTranslucent)
54 return false;
55 if (rBitmapEx.IsEmpty())
56 return false;
57
58 png_structp pPng = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
59
60 if (!pPng)
61 return false;
62
63 png_infop pInfo = png_create_info_struct(pPng);
64 if (!pInfo)
65 {
66 png_destroy_write_struct(&pPng, nullptr);
67 return false;
68 }
69
70 BitmapEx aBitmapEx;
72 {
73 if (!vcl::bitmap::convertBitmap32To24Plus8(rBitmapEx, aBitmapEx))
74 return false;
75 }
76 else
77 {
78 aBitmapEx = rBitmapEx;
79 }
80
81 Bitmap aBitmap;
82 AlphaMask aAlphaMask;
84 Bitmap::ScopedReadAccess pAlphaAccess;
85
86 if (setjmp(png_jmpbuf(pPng)))
87 {
88 pAccess.reset();
89 pAlphaAccess.reset();
90 png_destroy_read_struct(&pPng, &pInfo, nullptr);
91 return false;
92 }
93
94 // Set our custom stream writer
95 png_set_write_fn(pPng, &rStream, lclWriteStream, nullptr);
96
97 aBitmap = aBitmapEx.GetBitmap();
98 aAlphaMask = aBitmapEx.GetAlpha();
99
100 {
101 bool bCombineChannels = false;
102 pAccess = Bitmap::ScopedReadAccess(aBitmap);
103 pAlphaAccess = Bitmap::ScopedReadAccess(aAlphaMask);
104 Size aSize = aBitmapEx.GetSizePixel();
105
106 int bitDepth = -1;
107 int colorType = -1;
108
109 /* PNG_COLOR_TYPE_GRAY (1, 2, 4, 8, 16)
110 PNG_COLOR_TYPE_GRAY_ALPHA (8, 16)
111 PNG_COLOR_TYPE_PALETTE (bit depths 1, 2, 4, 8)
112 PNG_COLOR_TYPE_RGB (bit_depths 8, 16)
113 PNG_COLOR_TYPE_RGB_ALPHA (bit_depths 8, 16)
114 PNG_COLOR_MASK_PALETTE
115 PNG_COLOR_MASK_COLOR
116 PNG_COLOR_MASK_ALPHA
117 */
118 auto eScanlineFormat = pAccess->GetScanlineFormat();
119 switch (eScanlineFormat)
120 {
123 {
124 colorType = PNG_COLOR_TYPE_PALETTE;
125 bitDepth = 1;
126 break;
127 }
129 {
130 if (!aBitmap.HasGreyPalette8Bit())
131 colorType = PNG_COLOR_TYPE_PALETTE;
132 else
133 colorType = PNG_COLOR_TYPE_GRAY;
134 bitDepth = 8;
135 break;
136 }
138 {
139 png_set_bgr(pPng);
140 [[fallthrough]];
141 }
143 {
144 colorType = PNG_COLOR_TYPE_RGB;
145 bitDepth = 8;
146 if (pAlphaAccess)
147 {
148 colorType = PNG_COLOR_TYPE_RGBA;
149 bCombineChannels = true;
150 }
151 break;
152 }
154 {
155 png_set_bgr(pPng);
156 [[fallthrough]];
157 }
159 {
160 colorType = PNG_COLOR_TYPE_RGBA;
161 bitDepth = 8;
162 break;
163 }
164 default:
165 {
166 return false;
167 }
168 }
169
170 if (aBitmapEx.GetPrefMapMode().GetMapUnit() == MapUnit::Map100thMM)
171 {
172 Size aPrefSize(aBitmapEx.GetPrefSize());
173 sal_uInt32 nPrefSizeX = o3tl::convert(aSize.Width(), 100000, aPrefSize.Width());
174 sal_uInt32 nPrefSizeY = o3tl::convert(aSize.Height(), 100000, aPrefSize.Height());
175 png_set_pHYs(pPng, pInfo, nPrefSizeX, nPrefSizeY, 1);
176 }
177
178 png_set_compression_level(pPng, nCompressionLevel);
179
180 int interlaceType = bInterlaced ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
181 int compressionType = PNG_COMPRESSION_TYPE_DEFAULT;
182 int filterMethod = PNG_FILTER_TYPE_DEFAULT;
183
184 // Convert BitmapPalette to png_color*
185 if (colorType == PNG_COLOR_TYPE_PALETTE)
186 {
187 // Reserve enough space for 3 channels for each palette entry
188 auto aBitmapPalette = pAccess->GetPalette();
189 auto nEntryCount = aBitmapPalette.GetEntryCount();
190 std::unique_ptr<png_color[]> aPngPaletteArray(new png_color[nEntryCount * 3]);
191 for (sal_uInt16 i = 0; i < nEntryCount; i++)
192 {
193 aPngPaletteArray[i].red = aBitmapPalette[i].GetRed();
194 aPngPaletteArray[i].green = aBitmapPalette[i].GetGreen();
195 aPngPaletteArray[i].blue = aBitmapPalette[i].GetBlue();
196 }
197 // Palette is copied over so it can be safely discarded
198 png_set_PLTE(pPng, pInfo, aPngPaletteArray.get(), nEntryCount);
199 }
200
201 png_set_IHDR(pPng, pInfo, aSize.Width(), aSize.Height(), bitDepth, colorType, interlaceType,
202 compressionType, filterMethod);
203
204 png_write_info(pPng, pInfo);
205
206 int nNumberOfPasses = 1;
207
208 Scanline pSourcePointer;
209
210 tools::Long nHeight = pAccess->Height();
211
212 for (int nPass = 0; nPass < nNumberOfPasses; nPass++)
213 {
214 for (tools::Long y = 0; y < nHeight; y++)
215 {
216 pSourcePointer = pAccess->GetScanline(y);
217 Scanline pFinalPointer = pSourcePointer;
218 std::vector<std::remove_pointer_t<Scanline>> aCombinedChannels;
219 if (bCombineChannels)
220 {
221 auto nBitmapWidth = pAccess->Width();
222 // Allocate enough size to fit all 4 channels
223 aCombinedChannels.resize(nBitmapWidth * 4);
224 Scanline pAlphaPointer = pAlphaAccess->GetScanline(y);
225 if (!pSourcePointer || !pAlphaPointer)
226 return false;
227 // Combine RGB and alpha channels
228 combineScanlineChannels(pSourcePointer, pAlphaPointer, aCombinedChannels,
230 pFinalPointer = aCombinedChannels.data();
231 // Invert alpha channel (255 - a)
232 png_set_invert_alpha(pPng);
233 }
234 png_write_row(pPng, pFinalPointer);
235 }
236 }
237 }
238
239 if (!aAdditionalChunks.empty())
240 {
241 for (const auto& aChunk : aAdditionalChunks)
242 {
243 png_write_chunk(pPng, aChunk.name.data(), aChunk.data.data(), aChunk.size);
244 }
245 }
246
247 png_write_end(pPng, pInfo);
248
249 png_destroy_write_struct(&pPng, &pInfo);
250
251 return true;
252}
253
254void PngImageWriter::setParameters(css::uno::Sequence<css::beans::PropertyValue> const& rParameters)
255{
256 for (auto const& rValue : rParameters)
257 {
258 if (rValue.Name == "Compression")
259 rValue.Value >>= mnCompressionLevel;
260 else if (rValue.Name == "Interlaced")
261 rValue.Value >>= mbInterlaced;
262 else if (rValue.Name == "Translucent")
263 {
264 tools::Long nTmp = 0;
265 rValue.Value >>= nTmp;
266 if (!nTmp)
267 mbTranslucent = false;
268 }
269 else if (rValue.Name == "AdditionalChunks")
270 {
271 css::uno::Sequence<css::beans::PropertyValue> aAdditionalChunkSequence;
272 if (rValue.Value >>= aAdditionalChunkSequence)
273 {
274 for (const auto& rAdditionalChunk : std::as_const(aAdditionalChunkSequence))
275 {
276 if (rAdditionalChunk.Name.getLength() == 4)
277 {
278 vcl::PngChunk aChunk;
279 for (sal_Int32 k = 0; k < 4; k++)
280 {
281 aChunk.name[k] = static_cast<sal_uInt8>(rAdditionalChunk.Name[k]);
282 }
283 aChunk.name[4] = '\0';
284
285 css::uno::Sequence<sal_Int8> aByteSeq;
286 if (rAdditionalChunk.Value >>= aByteSeq)
287 {
288 sal_uInt32 nChunkSize = aByteSeq.getLength();
289 aChunk.size = nChunkSize;
290 if (nChunkSize)
291 {
292 const sal_Int8* pSource = aByteSeq.getConstArray();
293 std::vector<sal_uInt8> aData(pSource, pSource + nChunkSize);
294 aChunk.data = std::move(aData);
295 maAdditionalChunks.push_back(aChunk);
296 }
297 }
298 }
299 }
300 }
301 }
302 }
303}
304
306 : mrStream(rStream)
307 , mnCompressionLevel(6)
308 , mbInterlaced(false)
309 , mbTranslucent(true)
310{
311}
312
313bool PngImageWriter::write(const BitmapEx& rBitmapEx)
314{
317}
318
319} // namespace vcl
320
321/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
AlphaMask GetAlpha() const
Definition: BitmapEx.cxx:215
bool IsAlpha() const
Definition: BitmapEx.cxx:193
bool IsEmpty() const
Definition: BitmapEx.cxx:177
Bitmap GetBitmap(Color aTransparentReplaceColor) const
Definition: BitmapEx.cxx:203
const MapMode & GetPrefMapMode() const
Definition: bitmapex.hxx:78
const Size & GetPrefSize() const
Definition: bitmapex.hxx:75
const Size & GetSizePixel() const
Definition: bitmapex.hxx:72
tools::Long Height() const
tools::Long Width() const
const BitmapPalette & GetPalette() const
ScanlineFormat GetScanlineFormat() const
sal_uInt16 GetEntryCount() const
Scanline GetScanline(tools::Long nY) const
bool HasGreyPalette8Bit() const
vcl::ScopedBitmapAccess< BitmapReadAccess, Bitmap, &Bitmap::AcquireReadAccess > ScopedReadAccess
vcl::PixelFormat getPixelFormat() const
MapUnit GetMapUnit() const
Definition: mapmod.cxx:150
constexpr tools::Long Height() const
constexpr tools::Long Width() const
std::size_t WriteBytes(const void *pData, std::size_t nSize)
std::vector< PngChunk > maAdditionalChunks
void setParameters(css::uno::Sequence< css::beans::PropertyValue > const &rParameters)
bool write(const BitmapEx &rBitmap)
sal_Int32 mnCompressionLevel
PngImageWriter(SvStream &rStream)
float y
std::unique_ptr< sal_Int32[]> pData
constexpr OUStringLiteral aData
int i
constexpr Point convert(const Point &rPoint, o3tl::Length eFrom, o3tl::Length eTo)
const sal_Int32 nBitmapWidth
long Long
bool convertBitmap32To24Plus8(BitmapEx const &rInput, BitmapEx &rResult)
static bool pngWrite(SvStream &rStream, const BitmapEx &rBitmapEx, int nCompressionLevel, bool bInterlaced, bool bTranslucent, const std::vector< PngChunk > &aAdditionalChunks)
static void lclWriteStream(png_structp pPng, png_bytep pData, png_size_t pDataSize)
std::array< uint8_t, 5 > name
std::vector< sal_uInt8 > data
unsigned char sal_uInt8
signed char sal_Int8