Coverage Report

Created: 2026-07-25 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/gtiff/gtiffjpegoverviewds.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GeoTIFF Driver
4
 * Purpose:  GDAL GeoTIFF support.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1998, 2002, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2007-2015, Even Rouault <even dot rouault at spatialys dot com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "gtiffjpegoverviewds.h"
15
16
#include "gtiffdataset.h"
17
#include "gdal_priv.h"
18
19
#include "tifvsi.h"
20
21
/************************************************************************/
22
/* ==================================================================== */
23
/*                     GTiffJPEGOverviewBand                            */
24
/* ==================================================================== */
25
/************************************************************************/
26
27
class GTiffJPEGOverviewBand final : public GDALRasterBand
28
{
29
  public:
30
    GTiffJPEGOverviewBand(GTiffJPEGOverviewDS *poDS, int nBand);
31
32
    CPLErr IReadBlock(int, int, void *) override;
33
34
    GDALColorInterp GetColorInterpretation() override
35
0
    {
36
0
        return cpl::down_cast<GTiffJPEGOverviewDS *>(poDS)
37
0
            ->m_poParentDS->GetRasterBand(nBand)
38
0
            ->GetColorInterpretation();
39
0
    }
40
};
41
42
/************************************************************************/
43
/*                        GTiffJPEGOverviewDS()                         */
44
/************************************************************************/
45
46
GTiffJPEGOverviewDS::GTiffJPEGOverviewDS(GTiffDataset *poParentDSIn,
47
                                         int nOverviewLevelIn,
48
                                         const void *pJPEGTable,
49
                                         int nJPEGTableSizeIn)
50
0
    : m_poParentDS(poParentDSIn), m_nOverviewLevel(nOverviewLevelIn),
51
0
      m_nJPEGTableSize(nJPEGTableSizeIn)
52
0
{
53
0
    ShareLockWithParentDataset(poParentDSIn);
54
55
0
    m_osTmpFilenameJPEGTable = VSIMemGenerateHiddenFilename("jpegtable");
56
57
0
    const GByte abyAdobeAPP14RGB[] = {0xFF, 0xEE, 0x00, 0x0E, 0x41, 0x64,
58
0
                                      0x6F, 0x62, 0x65, 0x00, 0x64, 0x00,
59
0
                                      0x00, 0x00, 0x00, 0x00};
60
0
    const bool bAddAdobe =
61
0
        m_poParentDS->m_nPlanarConfig == PLANARCONFIG_CONTIG &&
62
0
        m_poParentDS->m_nPhotometric != PHOTOMETRIC_YCBCR &&
63
0
        m_poParentDS->nBands == 3;
64
0
    m_pabyJPEGTable = static_cast<GByte *>(CPLMalloc(
65
0
        m_nJPEGTableSize + (bAddAdobe ? sizeof(abyAdobeAPP14RGB) : 0)));
66
0
    memcpy(m_pabyJPEGTable, pJPEGTable, m_nJPEGTableSize);
67
0
    if (bAddAdobe)
68
0
    {
69
0
        memcpy(m_pabyJPEGTable + m_nJPEGTableSize, abyAdobeAPP14RGB,
70
0
               sizeof(abyAdobeAPP14RGB));
71
0
        m_nJPEGTableSize += sizeof(abyAdobeAPP14RGB);
72
0
    }
73
0
    CPL_IGNORE_RET_VAL(VSIFCloseL(VSIFileFromMemBuffer(
74
0
        m_osTmpFilenameJPEGTable, m_pabyJPEGTable, m_nJPEGTableSize, TRUE)));
75
76
0
    const int nScaleFactor = 1 << m_nOverviewLevel;
77
0
    nRasterXSize = DIV_ROUND_UP(m_poParentDS->nRasterXSize, nScaleFactor);
78
0
    nRasterYSize = DIV_ROUND_UP(m_poParentDS->nRasterYSize, nScaleFactor);
79
80
0
    for (int i = 1; i <= m_poParentDS->nBands; ++i)
81
0
        SetBand(i, new GTiffJPEGOverviewBand(this, i));
82
83
0
    SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL", GDAL_MDD_IMAGE_STRUCTURE);
84
0
    if (m_poParentDS->m_nPhotometric == PHOTOMETRIC_YCBCR)
85
0
        SetMetadataItem(GDALMD_COMPRESSION, "YCbCr JPEG",
86
0
                        GDAL_MDD_IMAGE_STRUCTURE);
87
0
    else
88
0
        SetMetadataItem(GDALMD_COMPRESSION, "JPEG", GDAL_MDD_IMAGE_STRUCTURE);
89
0
}
90
91
/************************************************************************/
92
/*                        ~GTiffJPEGOverviewDS()                        */
93
/************************************************************************/
94
95
GTiffJPEGOverviewDS::~GTiffJPEGOverviewDS()
96
0
{
97
0
    m_poJPEGDS.reset();
98
0
    VSIUnlink(m_osTmpFilenameJPEGTable);
99
0
    if (!m_osTmpFilename.empty())
100
0
        VSIUnlink(m_osTmpFilename);
101
0
}
102
103
/************************************************************************/
104
/*                             IRasterIO()                              */
105
/************************************************************************/
106
107
CPLErr GTiffJPEGOverviewDS::IRasterIO(
108
    GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
109
    void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
110
    int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
111
    GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
112
113
0
{
114
    // For non-single strip JPEG-IN-TIFF, the block based strategy will
115
    // be the most efficient one, to avoid decompressing the JPEG content
116
    // for each requested band.
117
0
    if (nBandCount > 1 &&
118
0
        m_poParentDS->m_nPlanarConfig == PLANARCONFIG_CONTIG &&
119
0
        (m_poParentDS->m_nBlockXSize < m_poParentDS->nRasterXSize ||
120
0
         m_poParentDS->m_nBlockYSize > 1))
121
0
    {
122
0
        return BlockBasedRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
123
0
                                  nBufXSize, nBufYSize, eBufType, nBandCount,
124
0
                                  panBandMap, nPixelSpace, nLineSpace,
125
0
                                  nBandSpace, psExtraArg);
126
0
    }
127
128
0
    return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
129
0
                                  nBufXSize, nBufYSize, eBufType, nBandCount,
130
0
                                  panBandMap, nPixelSpace, nLineSpace,
131
0
                                  nBandSpace, psExtraArg);
132
0
}
133
134
/************************************************************************/
135
/*                       GTiffJPEGOverviewBand()                        */
136
/************************************************************************/
137
138
GTiffJPEGOverviewBand::GTiffJPEGOverviewBand(GTiffJPEGOverviewDS *poDSIn,
139
                                             int nBandIn)
140
0
{
141
0
    poDS = poDSIn;
142
0
    nBand = nBandIn;
143
0
    eDataType =
144
0
        poDSIn->m_poParentDS->GetRasterBand(nBandIn)->GetRasterDataType();
145
0
    poDSIn->m_poParentDS->GetRasterBand(nBandIn)->GetBlockSize(&nBlockXSize,
146
0
                                                               &nBlockYSize);
147
0
    const int nScaleFactor = 1 << poDSIn->m_nOverviewLevel;
148
0
    nBlockXSize = DIV_ROUND_UP(nBlockXSize, nScaleFactor);
149
0
    nBlockYSize = DIV_ROUND_UP(nBlockYSize, nScaleFactor);
150
0
}
151
152
/************************************************************************/
153
/*                             IReadBlock()                             */
154
/************************************************************************/
155
156
CPLErr GTiffJPEGOverviewBand::IReadBlock(int nBlockXOff, int nBlockYOff,
157
                                         void *pImage)
158
0
{
159
0
    GTiffJPEGOverviewDS *m_poGDS = cpl::down_cast<GTiffJPEGOverviewDS *>(poDS);
160
161
    // Compute the source block ID.
162
0
    int nBlockId = 0;
163
0
    int nParentBlockXSize, nParentBlockYSize;
164
0
    m_poGDS->m_poParentDS->GetRasterBand(1)->GetBlockSize(&nParentBlockXSize,
165
0
                                                          &nParentBlockYSize);
166
0
    const bool bIsSingleStripAsSplit =
167
0
        (nParentBlockYSize == 1 &&
168
0
         m_poGDS->m_poParentDS->m_nBlockYSize != nParentBlockYSize);
169
0
    if (!bIsSingleStripAsSplit)
170
0
    {
171
0
        nBlockId =
172
0
            nBlockYOff * m_poGDS->m_poParentDS->m_nBlocksPerRow + nBlockXOff;
173
0
    }
174
0
    if (m_poGDS->m_poParentDS->m_nPlanarConfig == PLANARCONFIG_SEPARATE)
175
0
    {
176
0
        nBlockId += (nBand - 1) * m_poGDS->m_poParentDS->m_nBlocksPerBand;
177
0
    }
178
179
    // Make sure it is available.
180
0
    const int nDataTypeSize = GDALGetDataTypeSizeBytes(eDataType);
181
0
    vsi_l_offset nOffset = 0;
182
0
    vsi_l_offset nByteCount = 0;
183
0
    bool bErrOccurred = false;
184
0
    if (!m_poGDS->m_poParentDS->IsBlockAvailable(nBlockId, &nOffset,
185
0
                                                 &nByteCount, &bErrOccurred))
186
0
    {
187
0
        memset(pImage, 0,
188
0
               static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize *
189
0
                   nDataTypeSize);
190
0
        if (bErrOccurred)
191
0
            return CE_Failure;
192
0
        return CE_None;
193
0
    }
194
195
0
    const int nScaleFactor = 1 << m_poGDS->m_nOverviewLevel;
196
0
    if (m_poGDS->m_poJPEGDS == nullptr || nBlockId != m_poGDS->m_nBlockId)
197
0
    {
198
0
        if (nByteCount < 2)
199
0
            return CE_Failure;
200
0
        nOffset += 2;  // Skip leading 0xFF 0xF8.
201
0
        nByteCount -= 2;
202
203
0
        CPLString osFileToOpen;
204
0
        m_poGDS->m_osTmpFilename = VSIMemGenerateHiddenFilename("sparse");
205
0
        VSILFILE *fp = VSIFOpenL(m_poGDS->m_osTmpFilename, "wb+");
206
207
        // If the size of the JPEG strip/tile is small enough, we will
208
        // read it from the TIFF file and forge a in-memory JPEG file with
209
        // the JPEG table followed by the JPEG data.
210
0
        const bool bInMemoryJPEGFile = nByteCount < 256 * 256;
211
0
        if (bInMemoryJPEGFile)
212
0
        {
213
0
            osFileToOpen = m_poGDS->m_osTmpFilename;
214
215
0
            bool bError = false;
216
0
            if (VSIFSeekL(fp, m_poGDS->m_nJPEGTableSize + nByteCount - 1,
217
0
                          SEEK_SET) != 0)
218
0
                bError = true;
219
0
            char ch = 0;
220
0
            if (!bError && VSIFWriteL(&ch, 1, 1, fp) != 1)
221
0
                bError = true;
222
0
            GByte *pabyBuffer =
223
0
                VSIGetMemFileBuffer(m_poGDS->m_osTmpFilename, nullptr, FALSE);
224
0
            memcpy(pabyBuffer, m_poGDS->m_pabyJPEGTable,
225
0
                   m_poGDS->m_nJPEGTableSize);
226
0
            TIFF *hTIFF = m_poGDS->m_poParentDS->m_hTIFF;
227
0
            VSILFILE *fpTIF = VSI_TIFFGetVSILFile(TIFFClientdata(hTIFF));
228
0
            if (!bError && VSIFSeekL(fpTIF, nOffset, SEEK_SET) != 0)
229
0
                bError = true;
230
0
            if (VSIFReadL(pabyBuffer + m_poGDS->m_nJPEGTableSize,
231
0
                          static_cast<size_t>(nByteCount), 1, fpTIF) != 1)
232
0
                bError = true;
233
0
            if (bError)
234
0
            {
235
0
                CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
236
0
                return CE_Failure;
237
0
            }
238
0
        }
239
0
        else
240
0
        {
241
            // If the JPEG strip/tile is too big (e.g. a single-strip
242
            // JPEG-in-TIFF), we will use /vsisparse mechanism to make a
243
            // fake JPEG file.
244
245
0
            osFileToOpen =
246
0
                CPLSPrintf("/vsisparse/%s", m_poGDS->m_osTmpFilename.c_str());
247
248
0
            if (VSIFPrintfL(fp,
249
0
                            "<VSISparseFile><SubfileRegion>"
250
0
                            "<Filename relative='0'>%s</Filename>"
251
0
                            "<DestinationOffset>0</DestinationOffset>"
252
0
                            "<SourceOffset>0</SourceOffset>"
253
0
                            "<RegionLength>%d</RegionLength>"
254
0
                            "</SubfileRegion>"
255
0
                            "<SubfileRegion>"
256
0
                            "<Filename relative='0'>%s</Filename>"
257
0
                            "<DestinationOffset>%d</DestinationOffset>"
258
0
                            "<SourceOffset>" CPL_FRMT_GUIB "</SourceOffset>"
259
0
                            "<RegionLength>" CPL_FRMT_GUIB "</RegionLength>"
260
0
                            "</SubfileRegion></VSISparseFile>",
261
0
                            m_poGDS->m_osTmpFilenameJPEGTable.c_str(),
262
0
                            static_cast<int>(m_poGDS->m_nJPEGTableSize),
263
0
                            m_poGDS->m_poParentDS->GetDescription(),
264
0
                            static_cast<int>(m_poGDS->m_nJPEGTableSize),
265
0
                            nOffset, nByteCount) < 0)
266
0
            {
267
0
                CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
268
0
                return CE_Failure;
269
0
            }
270
0
        }
271
0
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
272
273
0
        const char *const apszDrivers[] = {"JPEG", nullptr};
274
275
0
        CPLConfigOptionSetter oJPEGtoRGBSetter(
276
0
            "GDAL_JPEG_TO_RGB",
277
0
            m_poGDS->m_poParentDS->m_nPlanarConfig == PLANARCONFIG_CONTIG &&
278
0
                    m_poGDS->nBands == 4
279
0
                ? "NO"
280
0
                : "YES",
281
0
            false);
282
283
0
        m_poGDS->m_poJPEGDS.reset(
284
0
            GDALDataset::Open(osFileToOpen, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
285
0
                              apszDrivers, nullptr, nullptr));
286
287
0
        if (m_poGDS->m_poJPEGDS != nullptr)
288
0
        {
289
            // Force all implicit overviews to be available, even for
290
            // small tiles.
291
0
            CPLConfigOptionSetter oInternalOverviewsSetter(
292
0
                "JPEG_FORCE_INTERNAL_OVERVIEWS", "YES", false);
293
0
            GDALGetOverviewCount(
294
0
                GDALGetRasterBand(m_poGDS->m_poJPEGDS.get(), 1));
295
296
0
            m_poGDS->m_nBlockId = nBlockId;
297
0
        }
298
0
    }
299
300
0
    CPLErr eErr = CE_Failure;
301
0
    if (m_poGDS->m_poJPEGDS)
302
0
    {
303
0
        GDALDataset *l_poDS = m_poGDS->m_poJPEGDS.get();
304
305
0
        int nReqXOff = 0;
306
0
        int nReqYOff = 0;
307
0
        int nReqXSize = 0;
308
0
        int nReqYSize = 0;
309
0
        if (bIsSingleStripAsSplit)
310
0
        {
311
0
            nReqYOff = nBlockYOff * nScaleFactor;
312
0
            nReqXSize = l_poDS->GetRasterXSize();
313
0
            nReqYSize = nScaleFactor;
314
0
        }
315
0
        else
316
0
        {
317
0
            if (nBlockXSize == m_poGDS->GetRasterXSize())
318
0
            {
319
0
                nReqXSize = l_poDS->GetRasterXSize();
320
0
            }
321
0
            else
322
0
            {
323
0
                nReqXSize = nBlockXSize * nScaleFactor;
324
0
            }
325
0
            nReqYSize = nBlockYSize * nScaleFactor;
326
0
        }
327
0
        int nBufXSize = nBlockXSize;
328
0
        int nBufYSize = nBlockYSize;
329
0
        if (nBlockXOff == m_poGDS->m_poParentDS->m_nBlocksPerRow - 1)
330
0
        {
331
0
            nReqXSize = m_poGDS->m_poParentDS->nRasterXSize -
332
0
                        nBlockXOff * m_poGDS->m_poParentDS->m_nBlockXSize;
333
0
        }
334
0
        if (nReqXOff + nReqXSize > l_poDS->GetRasterXSize())
335
0
        {
336
0
            nReqXSize = l_poDS->GetRasterXSize() - nReqXOff;
337
0
        }
338
0
        if (!bIsSingleStripAsSplit &&
339
0
            nBlockYOff == m_poGDS->m_poParentDS->m_nBlocksPerColumn - 1)
340
0
        {
341
0
            nReqYSize = m_poGDS->m_poParentDS->nRasterYSize -
342
0
                        nBlockYOff * m_poGDS->m_poParentDS->m_nBlockYSize;
343
0
        }
344
0
        if (nReqYOff + nReqYSize > l_poDS->GetRasterYSize())
345
0
        {
346
0
            nReqYSize = l_poDS->GetRasterYSize() - nReqYOff;
347
0
        }
348
0
        if (nBlockXOff * nBlockXSize > m_poGDS->GetRasterXSize() - nBufXSize)
349
0
        {
350
0
            memset(pImage, 0,
351
0
                   static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize *
352
0
                       nDataTypeSize);
353
0
            nBufXSize = m_poGDS->GetRasterXSize() - nBlockXOff * nBlockXSize;
354
0
        }
355
0
        if (nBlockYOff * nBlockYSize > m_poGDS->GetRasterYSize() - nBufYSize)
356
0
        {
357
0
            memset(pImage, 0,
358
0
                   static_cast<GPtrDiff_t>(nBlockXSize) * nBlockYSize *
359
0
                       nDataTypeSize);
360
0
            nBufYSize = m_poGDS->GetRasterYSize() - nBlockYOff * nBlockYSize;
361
0
        }
362
363
0
        const int nSrcBand =
364
0
            m_poGDS->m_poParentDS->m_nPlanarConfig == PLANARCONFIG_SEPARATE
365
0
                ? 1
366
0
                : nBand;
367
0
        if (nSrcBand <= l_poDS->GetRasterCount())
368
0
        {
369
0
            eErr = l_poDS->GetRasterBand(nSrcBand)->RasterIO(
370
0
                GF_Read, nReqXOff, nReqYOff, nReqXSize, nReqYSize, pImage,
371
0
                nBufXSize, nBufYSize, eDataType, 0,
372
0
                static_cast<GPtrDiff_t>(nBlockXSize) * nDataTypeSize, nullptr);
373
0
        }
374
0
    }
375
376
0
    return eErr;
377
0
}