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 pColorScanline, Scanline pAlphaScanline,
19 std::vector<std::remove_pointer_t<Scanline>>& pResult,
20 sal_uInt32 nBitmapWidth, int colorType)
21{
22 if (colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
23 {
24 for (sal_uInt32 i = 0; i < nBitmapWidth; ++i)
25 {
26 pResult[i * 2] = *pColorScanline++; // Gray
27 pResult[i * 2 + 1] = *pAlphaScanline++; // A
28 }
29 return;
30 }
31
32 for (sal_uInt32 i = 0; i < nBitmapWidth; ++i)
33 {
34 pResult[i * 4] = *pColorScanline++; // R
35 pResult[i * 4 + 1] = *pColorScanline++; // G
36 pResult[i * 4 + 2] = *pColorScanline++; // B
37 pResult[i * 4 + 3] = *pAlphaScanline++; // A
38 }
39}
40}
41
42namespace vcl
43{
44static void lclWriteStream(png_structp pPng, png_bytep pData, png_size_t pDataSize)
45{
46 png_voidp pIO = png_get_io_ptr(pPng);
47
48 if (pIO == nullptr)
49 return;
50
51 SvStream* pStream = static_cast<SvStream*>(pIO);
52
53 sal_Size nBytesWritten = pStream->WriteBytes(pData, pDataSize);
54
55 if (nBytesWritten != pDataSize)
56 png_error(pPng, "Write Error");
57}
58
59static bool pngWrite(SvStream& rStream, const BitmapEx& rBitmapEx, int nCompressionLevel,
60 bool bInterlaced, bool bTranslucent,
61 const std::vector<PngChunk>& aAdditionalChunks)
62{
63 if (rBitmapEx.IsAlpha() && !bTranslucent)
64 return false;
65 if (rBitmapEx.IsEmpty())
66 return false;
67
68 png_structp pPng = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
69
70 if (!pPng)
71 return false;
72
73 png_infop pInfo = png_create_info_struct(pPng);
74 if (!pInfo)
75 {
76 png_destroy_write_struct(&pPng, nullptr);
77 return false;
78 }
79
80 BitmapEx aBitmapEx;
82 {
83 if (!vcl::bitmap::convertBitmap32To24Plus8(rBitmapEx, aBitmapEx))
84 return false;
85 }
86 else
87 {
88 aBitmapEx = rBitmapEx;
89 }
90
91 Bitmap aBitmap;
92 AlphaMask aAlphaMask;
94 Bitmap::ScopedReadAccess pAlphaAccess;
95
96 if (setjmp(png_jmpbuf(pPng)))
97 {
98 pAccess.reset();
99 pAlphaAccess.reset();
100 png_destroy_read_struct(&pPng, &pInfo, nullptr);
101 return false;
102 }
103
104 // Set our custom stream writer
105 png_set_write_fn(pPng, &rStream, lclWriteStream, nullptr);
106
107 aBitmap = aBitmapEx.GetBitmap();
108 aAlphaMask = aBitmapEx.GetAlphaMask();
109
110 {
111 bool bCombineChannels = false;
112 pAccess = Bitmap::ScopedReadAccess(aBitmap);
113 pAlphaAccess = Bitmap::ScopedReadAccess(aAlphaMask);
114 Size aSize = aBitmapEx.GetSizePixel();
115
116 int bitDepth = -1;
117 int colorType = -1;
118
119 /* PNG_COLOR_TYPE_GRAY (1, 2, 4, 8, 16)
120 PNG_COLOR_TYPE_GRAY_ALPHA (8, 16)
121 PNG_COLOR_TYPE_PALETTE (bit depths 1, 2, 4, 8)
122 PNG_COLOR_TYPE_RGB (bit_depths 8, 16)
123 PNG_COLOR_TYPE_RGB_ALPHA (bit_depths 8, 16)
124 PNG_COLOR_MASK_PALETTE
125 PNG_COLOR_MASK_COLOR
126 PNG_COLOR_MASK_ALPHA
127 */
128 auto eScanlineFormat = pAccess->GetScanlineFormat();
129 switch (eScanlineFormat)
130 {
133 {
134 colorType = PNG_COLOR_TYPE_PALETTE;
135 bitDepth = 1;
136 break;
137 }
139 {
140 if (!aBitmap.HasGreyPalette8Bit())
141 colorType = PNG_COLOR_TYPE_PALETTE;
142 else
143 {
144 colorType = PNG_COLOR_TYPE_GRAY;
145 if (pAlphaAccess)
146 {
147 colorType = PNG_COLOR_TYPE_GRAY_ALPHA;
148 bCombineChannels = true;
149 }
150 }
151 bitDepth = 8;
152 break;
153 }
155 {
156 png_set_bgr(pPng);
157 [[fallthrough]];
158 }
160 {
161 colorType = PNG_COLOR_TYPE_RGB;
162 bitDepth = 8;
163 if (pAlphaAccess)
164 {
165 colorType = PNG_COLOR_TYPE_RGBA;
166 bCombineChannels = true;
167 }
168 break;
169 }
171 {
172 png_set_bgr(pPng);
173 [[fallthrough]];
174 }
176 {
177 colorType = PNG_COLOR_TYPE_RGBA;
178 bitDepth = 8;
179 break;
180 }
181 default:
182 {
183 return false;
184 }
185 }
186
187 if (aBitmapEx.GetPrefMapMode().GetMapUnit() == MapUnit::Map100thMM)
188 {
189 Size aPrefSize(aBitmapEx.GetPrefSize());
190 if (aPrefSize.Width() && aPrefSize.Height())
191 {
192 sal_uInt32 nPrefSizeX = o3tl::convert(aSize.Width(), 100000, aPrefSize.Width());
193 sal_uInt32 nPrefSizeY = o3tl::convert(aSize.Height(), 100000, aPrefSize.Height());
194 png_set_pHYs(pPng, pInfo, nPrefSizeX, nPrefSizeY, 1);
195 }
196 }
197
198 png_set_compression_level(pPng, nCompressionLevel);
199
200 int interlaceType = bInterlaced ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
201 int compressionType = PNG_COMPRESSION_TYPE_DEFAULT;
202 int filterMethod = PNG_FILTER_TYPE_DEFAULT;
203
204 // Convert BitmapPalette to png_color*
205 if (colorType == PNG_COLOR_TYPE_PALETTE)
206 {
207 // Reserve enough space for 3 channels for each palette entry
208 auto aBitmapPalette = pAccess->GetPalette();
209 auto nEntryCount = aBitmapPalette.GetEntryCount();
210 std::unique_ptr<png_color[]> aPngPaletteArray(new png_color[nEntryCount * 3]);
211 for (sal_uInt16 i = 0; i < nEntryCount; i++)
212 {
213 aPngPaletteArray[i].red = aBitmapPalette[i].GetRed();
214 aPngPaletteArray[i].green = aBitmapPalette[i].GetGreen();
215 aPngPaletteArray[i].blue = aBitmapPalette[i].GetBlue();
216 }
217 // Palette is copied over so it can be safely discarded
218 png_set_PLTE(pPng, pInfo, aPngPaletteArray.get(), nEntryCount);
219 }
220
221 png_set_IHDR(pPng, pInfo, aSize.Width(), aSize.Height(), bitDepth, colorType, interlaceType,
222 compressionType, filterMethod);
223
224 png_write_info(pPng, pInfo);
225
226 int nNumberOfPasses = 1;
227
228 Scanline pSourcePointer;
229
230 tools::Long nHeight = pAccess->Height();
231
232 for (int nPass = 0; nPass < nNumberOfPasses; nPass++)
233 {
234 for (tools::Long y = 0; y < nHeight; y++)
235 {
236 pSourcePointer = pAccess->GetScanline(y);
237 Scanline pFinalPointer = pSourcePointer;
238 std::vector<std::remove_pointer_t<Scanline>> aCombinedChannels;
239 if (bCombineChannels)
240 {
241 auto nBitmapWidth = pAccess->Width();
242 // Allocate enough size to fit all channels
243 aCombinedChannels.resize(nBitmapWidth * png_get_channels(pPng, pInfo));
244 Scanline pAlphaPointer = pAlphaAccess->GetScanline(y);
245 if (!pSourcePointer || !pAlphaPointer)
246 return false;
247 // Combine color and alpha channels
248 combineScanlineChannels(pSourcePointer, pAlphaPointer, aCombinedChannels,
249 nBitmapWidth, colorType);
250 pFinalPointer = aCombinedChannels.data();
251 // Invert alpha channel (255 - a)
252 png_set_invert_alpha(pPng);
253 }
254 png_write_row(pPng, pFinalPointer);
255 }
256 }
257 }
258
259 if (!aAdditionalChunks.empty())
260 {
261 for (const auto& aChunk : aAdditionalChunks)
262 {
263 png_write_chunk(pPng, aChunk.name.data(), aChunk.data.data(), aChunk.size);
264 }
265 }
266
267 png_write_end(pPng, pInfo);
268
269 png_destroy_write_struct(&pPng, &pInfo);
270
271 return true;
272}
273
274void PngImageWriter::setParameters(css::uno::Sequence<css::beans::PropertyValue> const& rParameters)
275{
276 for (auto const& rValue : rParameters)
277 {
278 if (rValue.Name == "Compression")
279 rValue.Value >>= mnCompressionLevel;
280 else if (rValue.Name == "Interlaced")
281 rValue.Value >>= mbInterlaced;
282 else if (rValue.Name == "Translucent")
283 {
284 tools::Long nTmp = 0;
285 rValue.Value >>= nTmp;
286 if (!nTmp)
287 mbTranslucent = false;
288 }
289 else if (rValue.Name == "AdditionalChunks")
290 {
291 css::uno::Sequence<css::beans::PropertyValue> aAdditionalChunkSequence;
292 if (rValue.Value >>= aAdditionalChunkSequence)
293 {
294 for (const auto& rAdditionalChunk : std::as_const(aAdditionalChunkSequence))
295 {
296 if (rAdditionalChunk.Name.getLength() == 4)
297 {
298 vcl::PngChunk aChunk;
299 for (sal_Int32 k = 0; k < 4; k++)
300 {
301 aChunk.name[k] = static_cast<sal_uInt8>(rAdditionalChunk.Name[k]);
302 }
303 aChunk.name[4] = '\0';
304
305 css::uno::Sequence<sal_Int8> aByteSeq;
306 if (rAdditionalChunk.Value >>= aByteSeq)
307 {
308 sal_uInt32 nChunkSize = aByteSeq.getLength();
309 aChunk.size = nChunkSize;
310 if (nChunkSize)
311 {
312 const sal_Int8* pSource = aByteSeq.getConstArray();
313 std::vector<sal_uInt8> aData(pSource, pSource + nChunkSize);
314 aChunk.data = std::move(aData);
315 maAdditionalChunks.push_back(aChunk);
316 }
317 }
318 }
319 }
320 }
321 }
322 }
323}
324
326 : mrStream(rStream)
327 , mnCompressionLevel(6)
328 , mbInterlaced(false)
329 , mbTranslucent(true)
330{
331}
332
333bool PngImageWriter::write(const BitmapEx& rBitmapEx)
334{
337}
338
339} // namespace vcl
340
341/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
const AlphaMask & GetAlphaMask() const
Definition: bitmapex.hxx:70
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:146
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