Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/mrf/JPNG_band.cpp
Line
Count
Source
1
/*
2
 * Copyright 2016-2021 Esri
3
 *
4
 * Author: Lucian Plesea
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
/*
20
 * JPNG band, uses JPEG or PNG encoding, depending on the input data
21
 */
22
23
#include "marfa.h"
24
25
CPL_C_START
26
#include <jpeglib.h>
27
#include <png.h>
28
CPL_C_END
29
30
NAMESPACE_MRF_START
31
32
// Test that all alpha values are equal to N
33
template <int N> static bool AllAlpha(const buf_mgr &src, const ILImage &img)
34
0
{
35
0
    int stride = img.pagesize.c;
36
0
    char *s = src.buffer + img.pagesize.c - 1;
37
0
    char *stop = src.buffer + img.pageSizeBytes;
38
0
    while (s < stop && N == static_cast<unsigned char>(*s))
39
0
        s += stride;
40
0
    return s >= stop;
41
0
}
Unexecuted instantiation: JPNG_band.cpp:bool GDAL_MRF::AllAlpha<255>(GDAL_MRF::buf_mgr const&, GDAL_MRF::ILImage const&)
Unexecuted instantiation: JPNG_band.cpp:bool GDAL_MRF::AllAlpha<0>(GDAL_MRF::buf_mgr const&, GDAL_MRF::ILImage const&)
42
43
// Strip the alpha from an RGBA buffer, safe to use in place
44
static void RGBA2RGB(const char *start, const char *stop, char *target)
45
0
{
46
0
    while (start < stop)
47
0
    {
48
0
        *target++ = *start++;
49
0
        *target++ = *start++;
50
0
        *target++ = *start++;
51
0
        start++;  // Skip the alpha
52
0
    }
53
0
}
54
55
// Add opaque alpha to an RGB buffer, safe to use in place
56
// works from stop to start, the last parameter is the end of the source region
57
static void RGB2RGBA(const char *start, char *stop, const char *source_end)
58
0
{
59
0
    while (start < stop)
60
0
    {
61
0
        *--stop = ~static_cast<char>(0);
62
0
        *--stop = *--source_end;
63
0
        *--stop = *--source_end;
64
0
        *--stop = *--source_end;
65
0
    }
66
0
}
67
68
// Strip the alpha from an Luma Alpha buffer, safe to use in place
69
static void LA2L(const char *start, const char *stop, char *target)
70
0
{
71
0
    while (start < stop)
72
0
    {
73
0
        *target++ = *start++;
74
0
        start++;  // Skip the alpha
75
0
    }
76
0
}
77
78
// Add opaque alpha to a Luma buffer, safe to use in place
79
// works from stop to start, the last parameter is the end of the source region
80
static void L2LA(const char *start, char *stop, const char *source_end)
81
0
{
82
0
    while (start < stop)
83
0
    {
84
0
        *--stop = ~static_cast<char>(0);
85
0
        *--stop = *--source_end;
86
0
    }
87
0
}
88
89
static CPLErr initBuffer(buf_mgr &b)
90
0
{
91
0
    b.buffer = (char *)(CPLMalloc(b.size));
92
0
    if (b.buffer != nullptr)
93
0
        return CE_None;
94
0
    CPLError(CE_Failure, CPLE_OutOfMemory, "Allocating temporary JPNG buffer");
95
0
    return CE_Failure;
96
0
}
97
98
CPLErr JPNG_Band::Decompress(buf_mgr &dst, buf_mgr &src)
99
0
{
100
0
    const static GUInt32 PNG_SIG = 0x474e5089;  // PNG 4CC code
101
102
0
    CPLErr retval = CE_None;
103
0
    ILImage image(img);
104
0
    GUInt32 signature;
105
0
    memcpy(&signature, src.buffer, sizeof(signature));
106
107
    // test against an LSB signature
108
0
    if (JPEG_Codec::IsJPEG(src))
109
0
    {
110
0
        image.pagesize.c -= 1;
111
0
        JPEG_Codec codec(image);
112
113
        // JPEG decoder expects the destination size to be accurate
114
0
        buf_mgr temp = dst;  // dst still owns the storage
115
0
        temp.size = (image.pagesize.c == 3) ? dst.size / 4 * 3 : dst.size / 2;
116
117
0
        retval = codec.DecompressJPEG(temp, src);
118
0
        if (CE_None == retval)
119
0
        {  // add opaque alpha, in place
120
0
            if (image.pagesize.c == 3)
121
0
                RGB2RGBA(dst.buffer, dst.buffer + dst.size,
122
0
                         temp.buffer + temp.size);
123
0
            else
124
0
                L2LA(dst.buffer, dst.buffer + dst.size,
125
0
                     temp.buffer + temp.size);
126
0
        }
127
0
    }
128
0
    else if (PNG_SIG == CPL_LSBWORD32(signature))
129
0
    {  // Should be PNG, it reads as 4 bands
130
0
        PNG_Codec codec(image);
131
0
        return codec.DecompressPNG(dst, src);
132
0
    }
133
0
    else
134
0
    {
135
0
        CPLError(CE_Failure, CPLE_NotSupported, "Not a JPEG or PNG tile");
136
0
        retval = CE_Failure;
137
0
    }
138
139
0
    return retval;
140
0
}
141
142
// The PNG internal palette is set on first band write
143
CPLErr JPNG_Band::Compress(buf_mgr &dst, buf_mgr &src)
144
0
{
145
0
    ILImage image(img);
146
0
    buf_mgr temp = {nullptr, static_cast<size_t>(img.pageSizeBytes)};
147
0
    CPLErr retval = initBuffer(temp);
148
0
    if (retval != CE_None)
149
0
        return retval;
150
151
0
    if (AllAlpha<255>(src, image))
152
0
    {  // If all pixels are opaque, compress as JPEG
153
0
        if (image.pagesize.c == 4)
154
0
            RGBA2RGB(src.buffer, src.buffer + src.size, temp.buffer);
155
0
        else
156
0
            LA2L(src.buffer, src.buffer + src.size, temp.buffer);
157
158
0
        image.pagesize.c -= 1;  // RGB or Grayscale only for JPEG
159
0
        JPEG_Codec codec(image);
160
0
        codec.rgb = rgb;
161
0
        codec.optimize = optimize;
162
0
        codec.sameres = sameres;
163
0
        codec.JFIF = JFIF;
164
0
        retval = codec.CompressJPEG(dst, temp);
165
0
    }
166
0
    else if (!AllAlpha<0>(src, image))
167
0
    {
168
0
        PNG_Codec codec(image);
169
0
        codec.deflate_flags = deflate_flags;
170
0
        retval = codec.CompressPNG(dst, src);
171
0
    }
172
0
    else
173
0
        dst.size = 0;  // Don't store fully transparent pages
174
175
0
    CPLFree(temp.buffer);
176
0
    return retval;
177
0
}
178
179
/**
180
 * \brief For PPNG, builds the data structures needed to write the palette
181
 * The presence of the PNGColors and PNGAlpha is used as a flag for PPNG only
182
 */
183
184
JPNG_Band::JPNG_Band(MRFDataset *pDS, const ILImage &image, int b, int level)
185
0
    : MRFRasterBand(pDS, image, b, level), rgb(FALSE), sameres(FALSE),
186
0
      optimize(false), JFIF(false)
187
0
{  // Check error conditions
188
0
    if (image.dt != GDT_UInt8)
189
0
    {
190
0
        CPLError(CE_Failure, CPLE_NotSupported,
191
0
                 "Data type not supported by MRF JPNG");
192
0
        return;
193
0
    }
194
0
    if (image.order != IL_Interleaved ||
195
0
        (image.pagesize.c != 4 && image.pagesize.c != 2))
196
0
    {
197
0
        CPLError(CE_Failure, CPLE_NotSupported,
198
0
                 "MRF JPNG can only handle 2 or 4 interleaved bands");
199
0
        return;
200
0
    }
201
202
0
    if (img.pagesize.c == 4)
203
0
    {  // RGBA can have storage flavors
204
0
        CPLString const &pm = pDS->GetPhotometricInterpretation();
205
0
        if (pm == "RGB" || pm == "MULTISPECTRAL")
206
0
        {  // Explicit RGB or MS
207
0
            rgb = TRUE;
208
0
            sameres = TRUE;
209
0
        }
210
0
        if (pm == "YCC")
211
0
            sameres = TRUE;
212
0
    }
213
214
0
    optimize = GetOptlist().FetchBoolean("OPTIMIZE", FALSE) != FALSE;
215
0
    JFIF = GetOptlist().FetchBoolean("JFIF", FALSE) != FALSE;
216
217
    // PNGs and JPGs can be larger than the source, especially for
218
    // small page size.
219
0
    poMRFDS->SetPBufferSize(image.pageSizeBytes + 100);
220
0
}
221
222
JPNG_Band::~JPNG_Band()
223
0
{
224
0
}
225
226
NAMESPACE_MRF_END