Coverage Report

Created: 2026-07-25 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/gcore/gdalcachedpixelaccessor.h
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Fast access to individual pixels in a GDALRasterBand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2022, Planet Labs
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#ifndef GDAL_CACHED_PIXEL_ACCESSOR_INCLUDED
14
#define GDAL_CACHED_PIXEL_ACCESSOR_INCLUDED
15
16
#include "gdal_priv.h"
17
#include "cpl_error.h"
18
#include "cpl_float.h"
19
20
#include <algorithm>
21
#include <array>
22
#include <vector>
23
24
/************************************************************************/
25
/*                       GDALCachedPixelAccessor                        */
26
/************************************************************************/
27
28
/** Class to have reasonably fast random pixel access to a raster band, when
29
 * accessing multiple pixels that are close to each other.
30
 *
31
 * This gives faster access than using GDALRasterBand::RasterIO() with
32
 * a 1x1 window.
33
 *
34
 * @since GDAL 3.5
35
 */
36
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT = 4>
37
class GDALCachedPixelAccessor
38
{
39
    GDALRasterBand *m_poBand = nullptr;
40
41
    struct CachedTile
42
    {
43
        std::vector<Type> m_data{};
44
        int m_nTileX = -1;
45
        int m_nTileY = -1;
46
        bool m_bModified = false;
47
    };
48
49
    int m_nCachedTileCount = 0;
50
    std::array<CachedTile, static_cast<size_t>(CACHED_TILE_COUNT)>
51
        m_aCachedTiles{};
52
53
    bool LoadTile(int nTileX, int nTileY);
54
    bool FlushTile(int iSlot);
55
56
    Type GetSlowPath(int nTileX, int nTileY, int nXInTile, int nYInTile,
57
                     bool *pbSuccess);
58
    bool SetSlowPath(int nTileX, int nTileY, int nXInTile, int nYInTile,
59
                     Type val);
60
61
    GDALCachedPixelAccessor(const GDALCachedPixelAccessor &) = delete;
62
    GDALCachedPixelAccessor &
63
    operator=(const GDALCachedPixelAccessor &) = delete;
64
65
  public:
66
    explicit GDALCachedPixelAccessor(GDALRasterBand *poBand);
67
    ~GDALCachedPixelAccessor();
68
69
    /** Assign the raster band if not known at construction time. */
70
    void SetBand(GDALRasterBand *poBand)
71
0
    {
72
0
        m_poBand = poBand;
73
0
    }
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::SetBand(GDALRasterBand*)
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::SetBand(GDALRasterBand*)
74
75
    Type Get(int nX, int nY, bool *pbSuccess = nullptr);
76
    bool Set(int nX, int nY, Type val);
77
78
    bool FlushCache();
79
    void ResetModifiedFlag();
80
};
81
82
/************************************************************************/
83
/*                      GDALCachedPixelAccessor()                       */
84
/************************************************************************/
85
86
/** Constructor.
87
 *
88
 * The template accepts the following parameters:
89
 * - Type: should be one of GByte, GUInt16, GInt16, GUInt32, GInt32, GUInt64,
90
 * GInt64, float or double
91
 * - TILE_SIZE: the tile size for the cache of GDALCachedPixelAccessor.
92
 *              Use a power of two for faster computation.
93
 *              It doesn't need to be the same of the underlying raster
94
 * - CACHED_TILE_COUNT: number of tiles to cache. Should be >= 1. Defaults to 4.
95
 *
96
 * @param poBand Raster band.
97
 */
98
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
99
GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::
100
    GDALCachedPixelAccessor(GDALRasterBand *poBand)
101
0
    : m_poBand(poBand)
102
0
{
103
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::GDALCachedPixelAccessor(GDALRasterBand*)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::GDALCachedPixelAccessor(GDALRasterBand*)
104
105
/************************************************************************/
106
/*                      ~GDALCachedPixelAccessor()                      */
107
/************************************************************************/
108
109
/** Destructor.
110
 *
111
 * Will call FlushCache()
112
 */
113
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
114
GDALCachedPixelAccessor<Type, TILE_SIZE,
115
                        CACHED_TILE_COUNT>::~GDALCachedPixelAccessor()
116
0
{
117
0
    FlushCache();
118
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::~GDALCachedPixelAccessor()
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::~GDALCachedPixelAccessor()
119
120
/************************************************************************/
121
/*                                Get()                                 */
122
/************************************************************************/
123
124
/** Get the value of a pixel.
125
 *
126
 * No bound checking of nX, nY is done.
127
 *
128
 * @param nX X coordinate (between 0 and GetXSize()-1)
129
 * @param nY Y coordinate (between 0 and GetYSize()-1)
130
 * @param[out] pbSuccess Optional pointer to a success flag
131
 * @return the pixel value
132
 */
133
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
134
inline Type GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::Get(
135
    int nX, int nY, bool *pbSuccess)
136
0
{
137
0
    const int nTileX = nX / TILE_SIZE;
138
0
    const int nTileY = nY / TILE_SIZE;
139
0
    const int nXInTile = nX % TILE_SIZE;
140
0
    const int nYInTile = nY % TILE_SIZE;
141
0
    if (m_aCachedTiles[0].m_nTileX == nTileX &&
142
0
        m_aCachedTiles[0].m_nTileY == nTileY)
143
0
    {
144
0
        if (pbSuccess)
145
0
            *pbSuccess = true;
146
0
        return m_aCachedTiles[0].m_data[nYInTile * TILE_SIZE + nXInTile];
147
0
    }
148
0
    return GetSlowPath(nTileX, nTileY, nXInTile, nYInTile, pbSuccess);
149
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::Get(int, int, bool*)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::Get(int, int, bool*)
150
151
/************************************************************************/
152
/*                            GetSlowPath()                             */
153
/************************************************************************/
154
155
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
156
Type GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::GetSlowPath(
157
    int nTileX, int nTileY, int nXInTile, int nYInTile, bool *pbSuccess)
158
0
{
159
0
    for (int i = 1; i < m_nCachedTileCount; ++i)
160
0
    {
161
0
        const auto &cachedTile = m_aCachedTiles[i];
162
0
        if (cachedTile.m_nTileX == nTileX && cachedTile.m_nTileY == nTileY)
163
0
        {
164
0
            const auto ret = cachedTile.m_data[nYInTile * TILE_SIZE + nXInTile];
165
0
            CachedTile tmp = std::move(m_aCachedTiles[i]);
166
0
            for (int j = i; j >= 1; --j)
167
0
                m_aCachedTiles[j] = std::move(m_aCachedTiles[j - 1]);
168
0
            m_aCachedTiles[0] = std::move(tmp);
169
0
            if (pbSuccess)
170
0
                *pbSuccess = true;
171
0
            return ret;
172
0
        }
173
0
    }
174
0
    if (!LoadTile(nTileX, nTileY))
175
0
    {
176
0
        if (pbSuccess)
177
0
            *pbSuccess = false;
178
0
        return 0;
179
0
    }
180
0
    if (pbSuccess)
181
0
        *pbSuccess = true;
182
0
    return m_aCachedTiles[0].m_data[nYInTile * TILE_SIZE + nXInTile];
183
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::GetSlowPath(int, int, int, int, bool*)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::GetSlowPath(int, int, int, int, bool*)
184
185
/************************************************************************/
186
/*                                Set()                                 */
187
/************************************************************************/
188
189
/** Set the value of a pixel.
190
 *
191
 * The actual modification of the underlying raster is deferred until the tile
192
 * is implicit flushed while loading a new tile, or an explicit call to
193
 * FlushCache().
194
 *
195
 * The destructor of GDALCachedPixelAccessor will take care of calling
196
 * FlushCache(), if the user hasn't done it explicitly.
197
 *
198
 * No bound checking of nX, nY is done.
199
 *
200
 * @param nX X coordinate (between 0 and GetXSize()-1)
201
 * @param nY Y coordinate (between 0 and GetYSize()-1)
202
 * @param val pixel value
203
 * @return true if success
204
 */
205
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
206
inline bool
207
GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::Set(int nX, int nY,
208
                                                                 Type val)
209
0
{
210
0
    const int nTileX = nX / TILE_SIZE;
211
0
    const int nTileY = nY / TILE_SIZE;
212
0
    const int nXInTile = nX % TILE_SIZE;
213
0
    const int nYInTile = nY % TILE_SIZE;
214
0
    if (m_aCachedTiles[0].m_nTileX == nTileX &&
215
0
        m_aCachedTiles[0].m_nTileY == nTileY)
216
0
    {
217
0
        m_aCachedTiles[0].m_data[nYInTile * TILE_SIZE + nXInTile] = val;
218
0
        m_aCachedTiles[0].m_bModified = true;
219
0
        return true;
220
0
    }
221
0
    return SetSlowPath(nTileX, nTileY, nXInTile, nYInTile, val);
222
0
}
223
224
/************************************************************************/
225
/*                            SetSlowPath()                             */
226
/************************************************************************/
227
228
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
229
bool GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::SetSlowPath(
230
    int nTileX, int nTileY, int nXInTile, int nYInTile, Type val)
231
0
{
232
0
    for (int i = 1; i < m_nCachedTileCount; ++i)
233
0
    {
234
0
        auto &cachedTile = m_aCachedTiles[i];
235
0
        if (cachedTile.m_nTileX == nTileX && cachedTile.m_nTileY == nTileY)
236
0
        {
237
0
            cachedTile.m_data[nYInTile * TILE_SIZE + nXInTile] = val;
238
0
            cachedTile.m_bModified = true;
239
0
            CachedTile tmp = std::move(m_aCachedTiles[i]);
240
0
            for (int j = i; j >= 1; --j)
241
0
                m_aCachedTiles[j] = std::move(m_aCachedTiles[j - 1]);
242
0
            m_aCachedTiles[0] = std::move(tmp);
243
0
            return true;
244
0
        }
245
0
    }
246
0
    if (!LoadTile(nTileX, nTileY))
247
0
    {
248
0
        return false;
249
0
    }
250
0
    m_aCachedTiles[0].m_data[nYInTile * TILE_SIZE + nXInTile] = val;
251
0
    m_aCachedTiles[0].m_bModified = true;
252
0
    return true;
253
0
}
254
255
/************************************************************************/
256
/*                             FlushCache()                             */
257
/************************************************************************/
258
259
/** Flush content of modified tiles and drop caches
260
 *
261
 * @return true if success
262
 */
263
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
264
bool GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::FlushCache()
265
0
{
266
0
    bool bRet = true;
267
0
    for (int i = 0; i < m_nCachedTileCount; ++i)
268
0
    {
269
0
        if (!FlushTile(i))
270
0
            bRet = false;
271
0
        m_aCachedTiles[i].m_nTileX = -1;
272
0
        m_aCachedTiles[i].m_nTileY = -1;
273
0
    }
274
0
    return bRet;
275
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::FlushCache()
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::FlushCache()
276
277
/************************************************************************/
278
/*                         ResetModifiedFlag()                          */
279
/************************************************************************/
280
281
/** Reset the modified flag for cached tiles.
282
 */
283
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
284
void GDALCachedPixelAccessor<Type, TILE_SIZE,
285
                             CACHED_TILE_COUNT>::ResetModifiedFlag()
286
0
{
287
0
    for (int i = 0; i < m_nCachedTileCount; ++i)
288
0
    {
289
0
        m_aCachedTiles[i].m_bModified = false;
290
0
    }
291
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::ResetModifiedFlag()
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::ResetModifiedFlag()
292
293
/************************************************************************/
294
/*                  GDALCachedPixelAccessorGetDataType                  */
295
/************************************************************************/
296
297
/*! @cond Doxygen_Suppress */
298
template <class T> struct GDALCachedPixelAccessorGetDataType
299
{
300
};
301
302
template <> struct GDALCachedPixelAccessorGetDataType<GByte>
303
{
304
    static constexpr GDALDataType DataType = GDT_Byte;
305
};
306
307
template <> struct GDALCachedPixelAccessorGetDataType<GInt8>
308
{
309
    static constexpr GDALDataType DataType = GDT_Int8;
310
};
311
312
template <> struct GDALCachedPixelAccessorGetDataType<GUInt16>
313
{
314
    static constexpr GDALDataType DataType = GDT_UInt16;
315
};
316
317
template <> struct GDALCachedPixelAccessorGetDataType<GInt16>
318
{
319
    static constexpr GDALDataType DataType = GDT_Int16;
320
};
321
322
template <> struct GDALCachedPixelAccessorGetDataType<GUInt32>
323
{
324
    static constexpr GDALDataType DataType = GDT_UInt32;
325
};
326
327
template <> struct GDALCachedPixelAccessorGetDataType<GInt32>
328
{
329
    static constexpr GDALDataType DataType = GDT_Int32;
330
};
331
#if SIZEOF_UNSIGNED_LONG == 8
332
// std::uint64_t on Linux 64-bit resolves as unsigned long
333
template <> struct GDALCachedPixelAccessorGetDataType<unsigned long>
334
{
335
    static constexpr GDALDataType DataType = GDT_UInt64;
336
};
337
338
template <> struct GDALCachedPixelAccessorGetDataType<long>
339
{
340
    static constexpr GDALDataType DataType = GDT_Int64;
341
};
342
#endif
343
template <> struct GDALCachedPixelAccessorGetDataType<GUInt64>
344
{
345
    static constexpr GDALDataType DataType = GDT_UInt64;
346
};
347
348
template <> struct GDALCachedPixelAccessorGetDataType<GInt64>
349
{
350
    static constexpr GDALDataType DataType = GDT_Int64;
351
};
352
353
template <> struct GDALCachedPixelAccessorGetDataType<GFloat16>
354
{
355
    static constexpr GDALDataType DataType = GDT_Float16;
356
};
357
358
template <> struct GDALCachedPixelAccessorGetDataType<float>
359
{
360
    static constexpr GDALDataType DataType = GDT_Float32;
361
};
362
363
template <> struct GDALCachedPixelAccessorGetDataType<double>
364
{
365
    static constexpr GDALDataType DataType = GDT_Float64;
366
};
367
368
/*! @endcond */
369
370
/************************************************************************/
371
/*                              LoadTile()                              */
372
/************************************************************************/
373
374
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
375
bool GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::LoadTile(
376
    int nTileX, int nTileY)
377
0
{
378
0
    if (m_nCachedTileCount == CACHED_TILE_COUNT)
379
0
    {
380
0
        if (!FlushTile(CACHED_TILE_COUNT - 1))
381
0
            return false;
382
0
        CachedTile tmp = std::move(m_aCachedTiles[CACHED_TILE_COUNT - 1]);
383
0
        for (int i = CACHED_TILE_COUNT - 1; i >= 1; --i)
384
0
            m_aCachedTiles[i] = std::move(m_aCachedTiles[i - 1]);
385
0
        m_aCachedTiles[0] = std::move(tmp);
386
0
    }
387
0
    else
388
0
    {
389
0
        if (m_nCachedTileCount > 0)
390
0
            std::swap(m_aCachedTiles[0], m_aCachedTiles[m_nCachedTileCount]);
391
0
        m_aCachedTiles[0].m_data.resize(TILE_SIZE * TILE_SIZE);
392
0
        m_nCachedTileCount++;
393
0
    }
394
395
#if 0
396
    CPLDebug("GDAL", "Load tile(%d, %d) of band %d of dataset %s",
397
              nTileX, nTileY, m_poBand->GetBand(),
398
              m_poBand->GetDataset() ? m_poBand->GetDataset()->GetDescription() : "(unknown)");
399
#endif
400
0
    CPLAssert(!m_aCachedTiles[0].m_bModified);
401
0
    const int nXOff = nTileX * TILE_SIZE;
402
0
    const int nYOff = nTileY * TILE_SIZE;
403
0
    const int nReqXSize = std::min(m_poBand->GetXSize() - nXOff, TILE_SIZE);
404
0
    const int nReqYSize = std::min(m_poBand->GetYSize() - nYOff, TILE_SIZE);
405
0
    if (m_poBand->RasterIO(
406
0
            GF_Read, nXOff, nYOff, nReqXSize, nReqYSize,
407
0
            m_aCachedTiles[0].m_data.data(), nReqXSize, nReqYSize,
408
0
            GDALCachedPixelAccessorGetDataType<Type>::DataType, sizeof(Type),
409
0
            TILE_SIZE * sizeof(Type), nullptr) != CE_None)
410
0
    {
411
0
        m_aCachedTiles[0].m_nTileX = -1;
412
0
        m_aCachedTiles[0].m_nTileY = -1;
413
0
        return false;
414
0
    }
415
0
    m_aCachedTiles[0].m_nTileX = nTileX;
416
0
    m_aCachedTiles[0].m_nTileY = nTileY;
417
0
    return true;
418
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::LoadTile(int, int)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::LoadTile(int, int)
419
420
/************************************************************************/
421
/*                             FlushTile()                              */
422
/************************************************************************/
423
424
template <class Type, int TILE_SIZE, int CACHED_TILE_COUNT>
425
bool GDALCachedPixelAccessor<Type, TILE_SIZE, CACHED_TILE_COUNT>::FlushTile(
426
    int iSlot)
427
0
{
428
0
    if (!m_aCachedTiles[iSlot].m_bModified)
429
0
        return true;
430
431
0
    m_aCachedTiles[iSlot].m_bModified = false;
432
0
    const int nXOff = m_aCachedTiles[iSlot].m_nTileX * TILE_SIZE;
433
0
    const int nYOff = m_aCachedTiles[iSlot].m_nTileY * TILE_SIZE;
434
0
    const int nReqXSize = std::min(m_poBand->GetXSize() - nXOff, TILE_SIZE);
435
0
    const int nReqYSize = std::min(m_poBand->GetYSize() - nYOff, TILE_SIZE);
436
0
    return m_poBand->RasterIO(
437
0
               GF_Write, nXOff, nYOff, nReqXSize, nReqYSize,
438
0
               m_aCachedTiles[iSlot].m_data.data(), nReqXSize, nReqYSize,
439
0
               GDALCachedPixelAccessorGetDataType<Type>::DataType, sizeof(Type),
440
0
               TILE_SIZE * sizeof(Type), nullptr) == CE_None;
441
0
}
Unexecuted instantiation: GDALCachedPixelAccessor<double, 256, 64>::FlushTile(int)
Unexecuted instantiation: GDALCachedPixelAccessor<float, 256, 64>::FlushTile(int)
442
443
#endif  // GDAL_PIXEL_ACCESSOR_INCLUDED