Coverage Report

Created: 2025-11-15 08:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/mrf/PNG_band.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2002-2012, California Institute of Technology.
3
 * All rights reserved.  Based on Government Sponsored Research under contracts
4
 * NAS7-1407 and/or NAS7-03001.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *   1. Redistributions of source code must retain the above copyright notice,
9
 * this list of conditions and the following disclaimer.
10
 *   2. Redistributions in binary form must reproduce the above copyright
11
 * notice, this list of conditions and the following disclaimer in the
12
 * documentation and/or other materials provided with the distribution.
13
 *   3. Neither the name of the California Institute of Technology (Caltech),
14
 * its operating division the Jet Propulsion Laboratory (JPL), the National
15
 * Aeronautics and Space Administration (NASA), nor the names of its
16
 * contributors may be used to endorse or promote products derived from this
17
 * software without specific prior written permission.
18
 *
19
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
 * ARE DISCLAIMED. IN NO EVENT SHALL THE CALIFORNIA INSTITUTE OF TECHNOLOGY BE
23
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
 * POSSIBILITY OF SUCH DAMAGE.
30
 *
31
 * Copyright (c) 2014-2021 Esri
32
 *
33
 * Licensed under the Apache License, Version 2.0 (the "License");
34
 * you may not use this file except in compliance with the License.
35
 * You may obtain a copy of the License at
36
 *
37
 * http://www.apache.org/licenses/LICENSE-2.0
38
 *
39
 * Unless required by applicable law or agreed to in writing, software
40
 * distributed under the License is distributed on an "AS IS" BASIS,
41
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
42
 * See the License for the specific language governing permissions and
43
 * limitations under the License.
44
 *
45
 * Author: Lucian Plesea
46
 *
47
 */
48
49
/*
50
 * PNG band
51
 * PNG page compression and decompression functions
52
 * These functions are not methods, they reside in the global space
53
 *
54
 */
55
56
#include "marfa.h"
57
#include <cassert>
58
#include <algorithm>
59
60
#include "gdal_colortable.h"
61
62
#if defined(__clang__)
63
#pragma clang diagnostic push
64
#pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
65
#endif
66
67
CPL_C_START
68
#include <png.h>
69
CPL_C_END
70
71
#if defined(__clang__)
72
#pragma clang diagnostic pop
73
#endif
74
75
NAMESPACE_MRF_START
76
77
// Do Nothing
78
static void flush_png(png_structp)
79
0
{
80
0
}
81
82
// Warning Emit
83
static void pngWH(png_struct * /*png*/, png_const_charp message)
84
0
{
85
0
    CPLError(CE_Warning, CPLE_AppDefined, "MRF: PNG warning %s", message);
86
0
}
87
88
// Fatal Warning
89
static void pngEH(png_struct *png, png_const_charp message)
90
0
{
91
0
    CPLError(CE_Failure, CPLE_AppDefined, "MRF: PNG Failure %s", message);
92
0
    longjmp(png_jmpbuf(png), 1);
93
0
}
94
95
// Read memory handlers for PNG
96
// No check for attempting to read past the end of the buffer
97
98
static void read_png(png_structp pngp, png_bytep data, png_size_t length)
99
3.45k
{
100
3.45k
    buf_mgr *pmgr = (buf_mgr *)png_get_io_ptr(pngp);
101
3.45k
    if (pmgr->size < length)
102
180
    {
103
180
        CPLError(CE_Failure, CPLE_AppDefined,
104
180
                 "MRF: PNG Failure: Not enough bytes in buffer");
105
180
        longjmp(png_jmpbuf(pngp), 1);
106
180
    }
107
3.27k
    memcpy(data, pmgr->buffer, length);
108
3.27k
    pmgr->buffer += length;
109
3.27k
    pmgr->size -= length;
110
3.27k
}
111
112
static void write_png(png_structp pngp, png_bytep data, png_size_t length)
113
0
{
114
0
    buf_mgr *mgr = (buf_mgr *)png_get_io_ptr(pngp);
115
    // Buffer could be too small, trigger an error on debug mode
116
0
    assert(length <= mgr->size);
117
0
    memcpy(mgr->buffer, data, length);
118
0
    mgr->buffer += length;
119
0
    mgr->size -= length;
120
0
}
121
122
/**
123
 *\brief In memory decompression of PNG file
124
 */
125
126
CPLErr PNG_Codec::DecompressPNG(buf_mgr &dst, buf_mgr &src)
127
345
{
128
345
    const buf_mgr src_ori = src;
129
345
    png_bytep *png_rowp = nullptr;
130
345
    volatile png_bytep *p_volatile_png_rowp =
131
345
        reinterpret_cast<volatile png_bytep *>(&png_rowp);
132
133
    // pngp=png_create_read_struct(PNG_LIBPNG_VER_STRING,0,pngEH,pngWH);
134
345
    png_structp pngp = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr,
135
345
                                              nullptr, nullptr);
136
345
    if (nullptr == pngp)
137
0
    {
138
0
        CPLError(CE_Failure, CPLE_AppDefined,
139
0
                 "MRF: Error creating PNG decompress");
140
0
        return CE_Failure;
141
0
    }
142
143
345
    png_infop infop = png_create_info_struct(pngp);
144
345
    if (nullptr == infop)
145
0
    {
146
0
        png_destroy_read_struct(&pngp, &infop, nullptr);
147
0
        CPLError(CE_Failure, CPLE_AppDefined, "MRF: Error creating PNG info");
148
0
        return CE_Failure;
149
0
    }
150
151
345
    if (setjmp(png_jmpbuf(pngp)))
152
340
    {
153
340
        CPLError(CE_Failure, CPLE_AppDefined,
154
340
                 "MRF: Error during PNG decompress");
155
340
        CPLFree((void *)(*p_volatile_png_rowp));
156
340
        png_destroy_read_struct(&pngp, &infop, nullptr);
157
340
        return CE_Failure;
158
340
    }
159
160
    // The mgr data ptr is already set up
161
5
    png_set_read_fn(pngp, &src, read_png);
162
    // Ready to read
163
5
    png_read_info(pngp, infop);
164
165
5
    if (png_get_bit_depth(pngp, infop) == 8)
166
64
    {
167
        // Use the PNG driver for decompression of 8-bit images, as it
168
        // has optimizations for whole image decompression.
169
64
        const CPLString osTmpFilename(VSIMemGenerateHiddenFilename("mrf.png"));
170
64
        VSIFCloseL(VSIFileFromMemBuffer(
171
64
            osTmpFilename.c_str(), reinterpret_cast<GByte *>(src_ori.buffer),
172
64
            src_ori.size, false));
173
64
        const char *const apszAllowedDrivers[] = {"PNG", nullptr};
174
64
        auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
175
64
            osTmpFilename.c_str(), GDAL_OF_RASTER, apszAllowedDrivers));
176
64
        if (poDS && static_cast<GUIntBig>(poDS->GetRasterXSize()) *
177
0
                            poDS->GetRasterYSize() * poDS->GetRasterCount() ==
178
0
                        dst.size)
179
0
        {
180
0
            if (poDS->RasterIO(
181
0
                    GF_Read, 0, 0, poDS->GetRasterXSize(),
182
0
                    poDS->GetRasterYSize(), dst.buffer, poDS->GetRasterXSize(),
183
0
                    poDS->GetRasterYSize(), GDT_Byte, poDS->GetRasterCount(),
184
0
                    nullptr, poDS->GetRasterCount(), 0, 1, nullptr) == CE_None)
185
0
            {
186
0
                png_destroy_read_struct(&pngp, &infop, nullptr);
187
0
                VSIUnlink(osTmpFilename.c_str());
188
0
                return CE_None;
189
0
            }
190
0
        }
191
64
        VSIUnlink(osTmpFilename.c_str());
192
64
    }
193
194
5
    GInt32 height = static_cast<GInt32>(png_get_image_height(pngp, infop));
195
    // Check the size
196
5
    if (dst.size < (png_get_rowbytes(pngp, infop) * height))
197
3
    {
198
3
        CPLError(CE_Failure, CPLE_AppDefined,
199
3
                 "MRF: PNG Page data bigger than the buffer provided");
200
3
        png_destroy_read_struct(&pngp, &infop, nullptr);
201
3
        return CE_Failure;
202
3
    }
203
204
2
    png_rowp = (png_bytep *)CPLMalloc(sizeof(png_bytep) * height);
205
206
2
    int rowbytes = static_cast<int>(png_get_rowbytes(pngp, infop));
207
31.2k
    for (int i = 0; i < height; i++)
208
31.2k
        png_rowp[i] = (png_bytep)dst.buffer + i * rowbytes;
209
210
2
#if defined(CPL_LSB)
211
2
    if (png_get_bit_depth(pngp, infop) > 8)
212
0
    {
213
0
        png_set_swap(pngp);
214
        // Call update info if any png_set is used
215
0
        png_read_update_info(pngp, infop);
216
0
    }
217
2
#endif
218
219
    // Finally, the read
220
    // This is the lower level, the png_read_end allows some transforms
221
    // Like palette to RGBA
222
2
    png_read_image(pngp, png_rowp);
223
224
    //    ppmWrite("Test.ppm",(char *)data,ILSize(512,512,1,4,0));
225
    // Required
226
2
    png_read_end(pngp, infop);
227
228
    // png_set_rows(pngp,infop,png_rowp);
229
    // png_read_png(pngp,infop,PNG_TRANSFORM_IDENTITY,0);
230
231
2
    CPLFree(png_rowp);
232
2
    png_destroy_read_struct(&pngp, &infop, nullptr);
233
2
    return CE_None;
234
5
}
235
236
/**
237
 *\brief Compress a page in PNG format
238
 * Returns the compressed size in dst.size
239
 *
240
 */
241
242
CPLErr PNG_Codec::CompressPNG(buf_mgr &dst, const buf_mgr &src)
243
244
0
{
245
0
    png_structp pngp;
246
0
    png_infop infop;
247
0
    buf_mgr mgr = dst;
248
249
0
    pngp =
250
0
        png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, pngEH, pngWH);
251
0
    if (!pngp)
252
0
    {
253
0
        CPLError(CE_Failure, CPLE_AppDefined,
254
0
                 "MRF: Error creating png structure");
255
0
        return CE_Failure;
256
0
    }
257
0
    infop = png_create_info_struct(pngp);
258
0
    if (!infop)
259
0
    {
260
0
        png_destroy_write_struct(&pngp, nullptr);
261
0
        CPLError(CE_Failure, CPLE_AppDefined,
262
0
                 "MRF: Error creating png info structure");
263
0
        return CE_Failure;
264
0
    }
265
266
0
    if (setjmp(png_jmpbuf(pngp)))
267
0
    {
268
0
        png_destroy_write_struct(&pngp, &infop);
269
0
        CPLError(CE_Failure, CPLE_AppDefined, "MRF: Error during png init");
270
0
        return CE_Failure;
271
0
    }
272
273
0
    png_set_write_fn(pngp, &mgr, write_png, flush_png);
274
275
0
    int png_ctype;
276
277
0
    switch (img.pagesize.c)
278
0
    {
279
0
        case 1:
280
0
            if (PNGColors != nullptr)
281
0
                png_ctype = PNG_COLOR_TYPE_PALETTE;
282
0
            else
283
0
                png_ctype = PNG_COLOR_TYPE_GRAY;
284
0
            break;
285
0
        case 2:
286
0
            png_ctype = PNG_COLOR_TYPE_GRAY_ALPHA;
287
0
            break;
288
0
        case 3:
289
0
            png_ctype = PNG_COLOR_TYPE_RGB;
290
0
            break;
291
0
        case 4:
292
0
            png_ctype = PNG_COLOR_TYPE_RGB_ALPHA;
293
0
            break;
294
0
        default:
295
0
        {  // This never happens if we check at the open
296
0
            CPLError(CE_Failure, CPLE_AppDefined,
297
0
                     "MRF:PNG Write with %d colors called", img.pagesize.c);
298
0
            return CE_Failure;
299
0
        }
300
0
    }
301
302
0
    png_set_IHDR(pngp, infop, img.pagesize.x, img.pagesize.y,
303
0
                 GDALGetDataTypeSizeBits(img.dt), png_ctype, PNG_INTERLACE_NONE,
304
0
                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
305
306
    // Optional, force certain filters only.  Makes it somewhat faster but worse
307
    // compression png_set_filter(pngp, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB);
308
309
#if defined(PNG_LIBPNG_VER) && (PNG_LIBPNG_VER > 10200) &&                     \
310
    defined(PNG_SELECT_READ)
311
    png_uint_32 mask, flags;
312
313
    flags = png_get_asm_flags(pngp);
314
    mask = png_get_asm_flagmask(PNG_SELECT_READ | PNG_SELECT_WRITE);
315
    png_set_asm_flags(pngp, flags | mask);  // use flags &~mask to disable all
316
#endif
317
318
    // Let the quality control the compression level
319
0
    png_set_compression_level(pngp, std::clamp(img.quality / 10, 1, 9));
320
321
    // Custom strategy for zlib, set using the band option Z_STRATEGY
322
0
    if (deflate_flags & ZFLAG_SMASK)
323
0
        png_set_compression_strategy(pngp, (deflate_flags & ZFLAG_SMASK) >> 6);
324
325
    // Write the palette and the transparencies if they exist
326
0
    if (PNGColors != nullptr)
327
0
    {
328
0
        png_set_PLTE(pngp, infop, (png_colorp)PNGColors, PalSize);
329
0
        if (TransSize != 0)
330
0
            png_set_tRNS(pngp, infop, (unsigned char *)PNGAlpha, TransSize,
331
0
                         nullptr);
332
0
    }
333
334
0
    png_write_info(pngp, infop);
335
336
0
#if defined(CPL_LSB)
337
0
    if (img.dt != GDT_Byte)
338
0
        png_set_swap(pngp);
339
0
#endif
340
341
0
    png_bytep *png_rowp =
342
0
        (png_bytep *)CPLMalloc(sizeof(png_bytep) * img.pagesize.y);
343
344
0
    if (setjmp(png_jmpbuf(pngp)))
345
0
    {
346
0
        CPLFree(png_rowp);
347
0
        png_destroy_write_struct(&pngp, &infop);
348
0
        CPLError(CE_Failure, CPLE_AppDefined,
349
0
                 "MRF: Error during png compression");
350
0
        return CE_Failure;
351
0
    }
352
353
0
    int rowbytes = static_cast<int>(png_get_rowbytes(pngp, infop));
354
0
    for (int i = 0; i < img.pagesize.y; i++)
355
0
        png_rowp[i] = (png_bytep)(src.buffer + i * rowbytes);
356
357
0
    png_write_image(pngp, png_rowp);
358
0
    png_write_end(pngp, infop);
359
360
    // Done
361
0
    CPLFree(png_rowp);
362
0
    png_destroy_write_struct(&pngp, &infop);
363
364
    // Done
365
    // mgr.size holds the available bytes, so the size of the compressed png
366
    // is the original destination size minus the still available bytes
367
0
    dst.size -= mgr.size;
368
369
0
    return CE_None;
370
0
}
371
372
// Builds a PNG palette from a GDAL color table
373
static void ResetPalette(GDALColorTable *poCT, PNG_Codec &codec)
374
0
{  // Convert the GDAL LUT to PNG style
375
0
    codec.TransSize = codec.PalSize = poCT->GetColorEntryCount();
376
377
0
    png_color *pasPNGColors =
378
0
        (png_color *)CPLMalloc(sizeof(png_color) * codec.PalSize);
379
0
    unsigned char *pabyAlpha = (unsigned char *)CPLMalloc(codec.TransSize);
380
0
    codec.PNGColors = (void *)pasPNGColors;
381
0
    codec.PNGAlpha = (void *)pabyAlpha;
382
0
    bool NoTranspYet = true;
383
384
    // Set the palette from the end to reduce the size of the opacity mask
385
0
    for (int iColor = codec.PalSize - 1; iColor >= 0; iColor--)
386
0
    {
387
0
        GDALColorEntry sEntry;
388
0
        poCT->GetColorEntryAsRGB(iColor, &sEntry);
389
390
0
        pasPNGColors[iColor].red = (png_byte)sEntry.c1;
391
0
        pasPNGColors[iColor].green = (png_byte)sEntry.c2;
392
0
        pasPNGColors[iColor].blue = (png_byte)sEntry.c3;
393
0
        if (NoTranspYet && sEntry.c4 == 255)
394
0
            codec.TransSize--;
395
0
        else
396
0
        {
397
0
            NoTranspYet = false;
398
0
            pabyAlpha[iColor] = (unsigned char)sEntry.c4;
399
0
        }
400
0
    }
401
0
}
402
403
CPLErr PNG_Band::Decompress(buf_mgr &dst, buf_mgr &src)
404
345
{
405
345
    return codec.DecompressPNG(dst, src);
406
345
}
407
408
CPLErr PNG_Band::Compress(buf_mgr &dst, buf_mgr &src)
409
0
{
410
0
    if (!codec.PNGColors && img.comp == IL_PPNG)
411
0
    {  // Late set PNG palette to conserve memory
412
0
        GDALColorTable *poCT = GetColorTable();
413
0
        if (!poCT)
414
0
        {
415
0
            CPLError(CE_Failure, CPLE_NotSupported,
416
0
                     "MRF PPNG needs a color table");
417
0
            return CE_Failure;
418
0
        }
419
0
        ResetPalette(poCT, codec);
420
0
    }
421
422
0
    codec.deflate_flags = deflate_flags;
423
0
    return codec.CompressPNG(dst, src);
424
0
}
425
426
/**
427
 * \brief For PPNG, builds the data structures needed to write the palette
428
 * The presence of the PNGColors and PNGAlpha is used as a flag for PPNG only
429
 */
430
431
PNG_Band::PNG_Band(MRFDataset *pDS, const ILImage &image, int b, int level)
432
6.14k
    : MRFRasterBand(pDS, image, b, level), codec(image)
433
6.14k
{  // Check error conditions
434
6.14k
    if (image.dt != GDT_Byte && image.dt != GDT_Int16 && image.dt != GDT_UInt16)
435
0
    {
436
0
        CPLError(CE_Failure, CPLE_NotSupported,
437
0
                 "Data type not supported by MRF PNG");
438
0
        return;
439
0
    }
440
6.14k
    if (image.pagesize.c > 4)
441
48
    {
442
48
        CPLError(CE_Failure, CPLE_NotSupported,
443
48
                 "MRF PNG can only handle up to 4 bands per page");
444
48
        return;
445
48
    }
446
    // PNGs can be larger than the source, especially for small page size
447
    // If PPNG is used, the palette can take up to 2100 bytes
448
6.10k
    poMRFDS->SetPBufferSize(
449
6.10k
        static_cast<unsigned int>(1.1 * image.pageSizeBytes + 4000));
450
6.10k
}
451
452
NAMESPACE_MRF_END