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