Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/nearblack_lib.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL Utilities
4
 * Purpose:  Convert nearly black or nearly white border to exact black/white.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 * ****************************************************************************
8
 * Copyright (c) 2006, MapShots Inc (www.mapshots.com)
9
 * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "gdal_utils.h"
16
#include "gdal_utils_priv.h"
17
#include "commonutils.h"
18
#include "gdalargumentparser.h"
19
20
#include <cassert>
21
#include <cstdlib>
22
#include <cstring>
23
24
#include <algorithm>
25
#include <memory>
26
#include <vector>
27
28
#include "cpl_conv.h"
29
#include "cpl_error.h"
30
#include "cpl_progress.h"
31
#include "cpl_string.h"
32
#include "gdal.h"
33
#include "gdal_priv.h"
34
35
#include "nearblack_lib.h"
36
37
static void ProcessLine(GByte *pabyLine, GByte *pabyMask, int iStart, int iEnd,
38
                        int nSrcBands, int nDstBands, int nNearDist,
39
                        int nMaxNonBlack, const Colors &oColors,
40
                        int *panLastLineCounts, bool bDoHorizontalCheck,
41
                        bool bDoVerticalCheck, bool bBottomUp,
42
                        int iLineFromTopOrBottom);
43
44
/************************************************************************/
45
/*                           GDALNearblack()                            */
46
/************************************************************************/
47
48
/* clang-format off */
49
/**
50
 * Convert nearly black/white borders to exact value.
51
 *
52
 * This is the equivalent of the
53
 * <a href="/programs/nearblack.html">nearblack</a> utility.
54
 *
55
 * GDALNearblackOptions* must be allocated and freed with
56
 * GDALNearblackOptionsNew() and GDALNearblackOptionsFree() respectively.
57
 * pszDest and hDstDS cannot be used at the same time.
58
 *
59
 * In-place update (i.e. hDstDS == hSrcDataset) is possible for formats that
60
 * support it, and if the dataset is opened in update mode.
61
 *
62
 * @param pszDest the destination dataset path or NULL.
63
 * @param hDstDS the destination dataset or NULL. Might be equal to hSrcDataset.
64
 * @param hSrcDataset the source dataset handle.
65
 * @param psOptionsIn the options struct returned by GDALNearblackOptionsNew()
66
 * or NULL.
67
 * @param pbUsageError pointer to a integer output variable to store if any
68
 * usage error has occurred or NULL.
69
 * @return the output dataset (new dataset that must be closed using
70
 * GDALClose(), or hDstDS when it is not NULL) or NULL in case of error.
71
 *
72
 * @since GDAL 2.1
73
 */
74
/* clang-format on */
75
76
GDALDatasetH CPL_DLL GDALNearblack(const char *pszDest, GDALDatasetH hDstDS,
77
                                   GDALDatasetH hSrcDataset,
78
                                   const GDALNearblackOptions *psOptionsIn,
79
                                   int *pbUsageError)
80
81
0
{
82
0
    if (pszDest == nullptr && hDstDS == nullptr)
83
0
    {
84
0
        CPLError(CE_Failure, CPLE_AppDefined,
85
0
                 "pszDest == NULL && hDstDS == NULL");
86
87
0
        if (pbUsageError)
88
0
            *pbUsageError = TRUE;
89
0
        return nullptr;
90
0
    }
91
0
    if (hSrcDataset == nullptr)
92
0
    {
93
0
        CPLError(CE_Failure, CPLE_AppDefined, "hSrcDataset== NULL");
94
95
0
        if (pbUsageError)
96
0
            *pbUsageError = TRUE;
97
0
        return nullptr;
98
0
    }
99
100
    // to keep in that scope
101
0
    std::unique_ptr<GDALNearblackOptions> psTmpOptions;
102
0
    const GDALNearblackOptions *psOptions = psOptionsIn;
103
0
    if (!psOptionsIn)
104
0
    {
105
0
        psTmpOptions = std::make_unique<GDALNearblackOptions>();
106
0
        psOptions = psTmpOptions.get();
107
0
    }
108
109
0
    const bool bCloseOutDSOnError = hDstDS == nullptr;
110
0
    if (pszDest == nullptr)
111
0
        pszDest = GDALGetDescription(hDstDS);
112
113
0
    const int nXSize = GDALGetRasterXSize(hSrcDataset);
114
0
    const int nYSize = GDALGetRasterYSize(hSrcDataset);
115
0
    int nBands = GDALGetRasterCount(hSrcDataset);
116
0
    int nDstBands = nBands;
117
118
0
    const bool bNearWhite = psOptions->bNearWhite;
119
0
    const bool bSetAlpha = psOptions->bSetAlpha;
120
0
    bool bSetMask = psOptions->bSetMask;
121
0
    Colors oColors = psOptions->oColors;
122
123
    /* -------------------------------------------------------------------- */
124
    /*      Do we need to create output file?                               */
125
    /* -------------------------------------------------------------------- */
126
127
0
    if (hDstDS == nullptr)
128
0
    {
129
0
        CPLString osFormat;
130
0
        if (psOptions->osFormat.empty())
131
0
        {
132
0
            osFormat = GetOutputDriverForRaster(pszDest);
133
0
            if (osFormat.empty())
134
0
            {
135
0
                return nullptr;
136
0
            }
137
0
        }
138
0
        else
139
0
        {
140
0
            osFormat = psOptions->osFormat;
141
0
        }
142
143
0
        GDALDriverH hDriver = GDALGetDriverByName(osFormat);
144
0
        if (hDriver == nullptr)
145
0
        {
146
0
            return nullptr;
147
0
        }
148
149
0
        if (bSetAlpha)
150
0
        {
151
0
            if (nBands != 0 &&
152
0
                GDALGetRasterColorInterpretation(
153
0
                    GDALGetRasterBand(hSrcDataset, nBands)) == GCI_AlphaBand)
154
0
            {
155
0
                nBands--;
156
0
            }
157
0
            else
158
0
            {
159
0
                nDstBands++;
160
0
            }
161
0
        }
162
163
0
        if (bSetMask)
164
0
        {
165
0
            if (nBands != 0 &&
166
0
                GDALGetRasterColorInterpretation(
167
0
                    GDALGetRasterBand(hSrcDataset, nBands)) == GCI_AlphaBand)
168
0
            {
169
0
                nDstBands--;
170
0
                nBands--;
171
0
            }
172
0
        }
173
174
0
        hDstDS = GDALCreate(hDriver, pszDest, nXSize, nYSize, nDstBands,
175
0
                            GDT_UInt8, psOptions->aosCreationOptions.List());
176
0
        if (hDstDS == nullptr)
177
0
        {
178
0
            return nullptr;
179
0
        }
180
181
0
        double adfGeoTransform[6] = {};
182
183
0
        if (GDALGetGeoTransform(hSrcDataset, adfGeoTransform) == CE_None)
184
0
        {
185
0
            GDALSetGeoTransform(hDstDS, adfGeoTransform);
186
0
            GDALSetProjection(hDstDS, GDALGetProjectionRef(hSrcDataset));
187
0
        }
188
189
0
        if (bSetAlpha &&
190
0
            GDALGetRasterColorInterpretation(GDALGetRasterBand(
191
0
                hDstDS, GDALGetRasterCount(hDstDS))) != GCI_AlphaBand)
192
0
        {
193
0
            GDALSetRasterColorInterpretation(
194
0
                GDALGetRasterBand(hDstDS, GDALGetRasterCount(hDstDS)),
195
0
                GCI_AlphaBand);
196
0
        }
197
0
    }
198
0
    else
199
0
    {
200
0
        if (!psOptions->aosCreationOptions.empty())
201
0
        {
202
0
            CPLError(CE_Warning, CPLE_AppDefined,
203
0
                     "Warning: creation options are ignored when writing to "
204
0
                     "an existing file.");
205
0
        }
206
207
        /***** check the input and output datasets are the same size *****/
208
0
        if (GDALGetRasterXSize(hDstDS) != nXSize ||
209
0
            GDALGetRasterYSize(hDstDS) != nYSize)
210
0
        {
211
0
            CPLError(CE_Failure, CPLE_AppDefined,
212
0
                     "The dimensions of the output dataset don't match "
213
0
                     "the dimensions of the input dataset.");
214
0
            return nullptr;
215
0
        }
216
217
0
        const bool bSrcLastIsAlpha =
218
0
            (nBands != 0 && GDALGetRasterColorInterpretation(GDALGetRasterBand(
219
0
                                hSrcDataset, nBands)) == GCI_AlphaBand);
220
0
        const bool bDstLastIsAlpha =
221
0
            (GDALGetRasterCount(hDstDS) != 0 &&
222
0
             GDALGetRasterColorInterpretation(GDALGetRasterBand(
223
0
                 hDstDS, GDALGetRasterCount(hDstDS))) == GCI_AlphaBand);
224
225
0
        nDstBands = GDALGetRasterCount(hDstDS);
226
0
        if (nDstBands - (bDstLastIsAlpha ? 1 : 0) !=
227
0
            nBands - (bSrcLastIsAlpha ? 1 : 0))
228
0
        {
229
0
            CPLError(CE_Failure, CPLE_AppDefined,
230
0
                     "Inconsistent number of source and destination bands.");
231
0
            return nullptr;
232
0
        }
233
234
0
        if (bSetAlpha)
235
0
        {
236
0
            if (!bDstLastIsAlpha)
237
0
            {
238
0
                CPLError(CE_Failure, CPLE_AppDefined,
239
0
                         "Last band is not an alpha band.");
240
0
                return nullptr;
241
0
            }
242
243
0
            if (nBands == nDstBands && bSrcLastIsAlpha)
244
0
                nBands--;
245
0
        }
246
247
0
        if (bSetMask)
248
0
        {
249
0
            if (bSrcLastIsAlpha)
250
0
            {
251
0
                nBands--;
252
0
            }
253
254
0
            if (bDstLastIsAlpha)
255
0
            {
256
0
                nDstBands--;
257
0
            }
258
0
        }
259
0
    }
260
261
    /***** set a color if there are no colors set? *****/
262
263
0
    if (oColors.empty())
264
0
    {
265
0
        Color oColor;
266
267
        /***** loop over the bands to get the right number of values *****/
268
0
        for (int iBand = 0; iBand < nBands; iBand++)
269
0
        {
270
            // black or white?
271
0
            oColor.push_back(bNearWhite ? 255 : 0);
272
0
        }
273
274
        /***** add the color to the colors *****/
275
0
        oColors.push_back(std::move(oColor));
276
0
        assert(!oColors.empty());
277
0
    }
278
279
    /***** does the number of bands match the number of color values? *****/
280
281
0
    if (static_cast<int>(oColors.front().size()) != nBands)
282
0
    {
283
0
        CPLError(CE_Failure, CPLE_AppDefined,
284
0
                 "-color args must have the same number of values as "
285
0
                 "the non alpha input band count.\n");
286
0
        if (bCloseOutDSOnError)
287
0
            GDALClose(hDstDS);
288
0
        return nullptr;
289
0
    }
290
291
0
    for (int iBand = 0; iBand < nBands; iBand++)
292
0
    {
293
0
        GDALRasterBandH hBand = GDALGetRasterBand(hSrcDataset, iBand + 1);
294
0
        if (GDALGetRasterDataType(hBand) != GDT_UInt8)
295
0
        {
296
0
            CPLError(CE_Warning, CPLE_AppDefined,
297
0
                     "Band %d is not of type GDT_UInt8. "
298
0
                     "It can lead to unexpected results.",
299
0
                     iBand + 1);
300
0
        }
301
0
        if (GDALGetRasterColorTable(hBand) != nullptr)
302
0
        {
303
0
            CPLError(
304
0
                CE_Warning, CPLE_AppDefined,
305
0
                "Band %d has a color table, which is ignored by nearblack. "
306
0
                "It can lead to unexpected results.",
307
0
                iBand + 1);
308
0
        }
309
0
    }
310
311
0
    GDALRasterBandH hMaskBand = nullptr;
312
313
0
    if (bSetMask)
314
0
    {
315
        // If there isn't already a mask band on the output file create one.
316
0
        if (GMF_PER_DATASET != GDALGetMaskFlags(GDALGetRasterBand(hDstDS, 1)))
317
0
        {
318
319
0
            if (CE_None != GDALCreateDatasetMaskBand(hDstDS, GMF_PER_DATASET))
320
0
            {
321
0
                CPLError(CE_Failure, CPLE_AppDefined,
322
0
                         "Failed to create mask band on output DS");
323
0
                bSetMask = false;
324
0
            }
325
0
        }
326
327
0
        if (bSetMask)
328
0
        {
329
0
            hMaskBand = GDALGetMaskBand(GDALGetRasterBand(hDstDS, 1));
330
0
        }
331
0
    }
332
333
0
    bool bRet;
334
0
    if (psOptions->bFloodFill)
335
0
    {
336
0
        bRet = GDALNearblackFloodFill(psOptions, hSrcDataset, hDstDS, hMaskBand,
337
0
                                      nBands, nDstBands, bSetMask, oColors);
338
0
    }
339
0
    else
340
0
    {
341
0
        bRet = GDALNearblackTwoPassesAlgorithm(psOptions, hSrcDataset, hDstDS,
342
0
                                               hMaskBand, nBands, nDstBands,
343
0
                                               bSetMask, oColors);
344
0
    }
345
0
    if (!bRet)
346
0
    {
347
0
        if (bCloseOutDSOnError)
348
0
            GDALClose(hDstDS);
349
0
        hDstDS = nullptr;
350
0
    }
351
352
0
    return hDstDS;
353
0
}
354
355
/************************************************************************/
356
/*                   GDALNearblackTwoPassesAlgorithm()                  */
357
/*                                                                      */
358
/* Do a top-to-bottom pass, followed by a bottom-to-top one.            */
359
/************************************************************************/
360
361
bool GDALNearblackTwoPassesAlgorithm(const GDALNearblackOptions *psOptions,
362
                                     GDALDatasetH hSrcDataset,
363
                                     GDALDatasetH hDstDS,
364
                                     GDALRasterBandH hMaskBand, int nBands,
365
                                     int nDstBands, bool bSetMask,
366
                                     const Colors &oColors)
367
0
{
368
0
    const int nXSize = GDALGetRasterXSize(hSrcDataset);
369
0
    const int nYSize = GDALGetRasterYSize(hSrcDataset);
370
371
0
    const int nMaxNonBlack = psOptions->nMaxNonBlack;
372
0
    const int nNearDist = psOptions->nNearDist;
373
0
    const bool bSetAlpha = psOptions->bSetAlpha;
374
375
    /* -------------------------------------------------------------------- */
376
    /*      Allocate a line buffer.                                         */
377
    /* -------------------------------------------------------------------- */
378
379
0
    std::vector<GByte> abyLine(static_cast<size_t>(nXSize) * nDstBands);
380
0
    GByte *pabyLine = abyLine.data();
381
382
0
    std::vector<GByte> abyMask;
383
0
    GByte *pabyMask = nullptr;
384
0
    if (bSetMask)
385
0
    {
386
0
        abyMask.resize(nXSize);
387
0
        pabyMask = abyMask.data();
388
0
    }
389
390
0
    std::vector<int> anLastLineCounts(nXSize);
391
0
    int *panLastLineCounts = anLastLineCounts.data();
392
393
    /* -------------------------------------------------------------------- */
394
    /*      Processing data one line at a time.                             */
395
    /* -------------------------------------------------------------------- */
396
0
    int iLine;
397
398
0
    for (iLine = 0; iLine < nYSize; iLine++)
399
0
    {
400
0
        CPLErr eErr = GDALDatasetRasterIO(
401
0
            hSrcDataset, GF_Read, 0, iLine, nXSize, 1, pabyLine, nXSize, 1,
402
0
            GDT_UInt8, nBands, nullptr, nDstBands, nXSize * nDstBands, 1);
403
0
        if (eErr != CE_None)
404
0
        {
405
0
            return false;
406
0
        }
407
408
0
        if (bSetAlpha)
409
0
        {
410
0
            for (int iCol = 0; iCol < nXSize; iCol++)
411
0
            {
412
0
                pabyLine[iCol * nDstBands + nDstBands - 1] = 255;
413
0
            }
414
0
        }
415
416
0
        if (bSetMask)
417
0
        {
418
0
            for (int iCol = 0; iCol < nXSize; iCol++)
419
0
            {
420
0
                pabyMask[iCol] = 255;
421
0
            }
422
0
        }
423
424
0
        ProcessLine(pabyLine, pabyMask, 0, nXSize - 1, nBands, nDstBands,
425
0
                    nNearDist, nMaxNonBlack, oColors, panLastLineCounts,
426
0
                    true,   // bDoHorizontalCheck
427
0
                    true,   // bDoVerticalCheck
428
0
                    false,  // bBottomUp
429
0
                    iLine);
430
0
        ProcessLine(pabyLine, pabyMask, nXSize - 1, 0, nBands, nDstBands,
431
0
                    nNearDist, nMaxNonBlack, oColors, panLastLineCounts,
432
0
                    true,   // bDoHorizontalCheck
433
0
                    false,  // bDoVerticalCheck
434
0
                    false,  // bBottomUp
435
0
                    iLine);
436
437
0
        eErr = GDALDatasetRasterIO(hDstDS, GF_Write, 0, iLine, nXSize, 1,
438
0
                                   pabyLine, nXSize, 1, GDT_UInt8, nDstBands,
439
0
                                   nullptr, nDstBands, nXSize * nDstBands, 1);
440
441
0
        if (eErr != CE_None)
442
0
        {
443
0
            return false;
444
0
        }
445
446
        /***** write out the mask band line *****/
447
448
0
        if (bSetMask)
449
0
        {
450
0
            eErr = GDALRasterIO(hMaskBand, GF_Write, 0, iLine, nXSize, 1,
451
0
                                pabyMask, nXSize, 1, GDT_UInt8, 0, 0);
452
0
            if (eErr != CE_None)
453
0
            {
454
0
                CPLError(CE_Warning, CPLE_AppDefined,
455
0
                         "ERROR writing out line to mask band.");
456
0
                return false;
457
0
            }
458
0
        }
459
460
0
        if (!(psOptions->pfnProgress(
461
0
                0.5 * ((iLine + 1) / static_cast<double>(nYSize)), nullptr,
462
0
                psOptions->pProgressData)))
463
0
        {
464
0
            return false;
465
0
        }
466
0
    }
467
468
    /* -------------------------------------------------------------------- */
469
    /*      Now process from the bottom back up                            .*/
470
    /* -------------------------------------------------------------------- */
471
0
    memset(panLastLineCounts, 0, sizeof(int) * nXSize);
472
473
0
    for (iLine = nYSize - 1; hDstDS != nullptr && iLine >= 0; iLine--)
474
0
    {
475
0
        CPLErr eErr = GDALDatasetRasterIO(
476
0
            hDstDS, GF_Read, 0, iLine, nXSize, 1, pabyLine, nXSize, 1,
477
0
            GDT_UInt8, nDstBands, nullptr, nDstBands, nXSize * nDstBands, 1);
478
0
        if (eErr != CE_None)
479
0
        {
480
0
            return false;
481
0
        }
482
483
        /***** read the mask band line back in *****/
484
485
0
        if (bSetMask)
486
0
        {
487
0
            eErr = GDALRasterIO(hMaskBand, GF_Read, 0, iLine, nXSize, 1,
488
0
                                pabyMask, nXSize, 1, GDT_UInt8, 0, 0);
489
0
            if (eErr != CE_None)
490
0
            {
491
0
                return false;
492
0
            }
493
0
        }
494
495
0
        ProcessLine(pabyLine, pabyMask, 0, nXSize - 1, nBands, nDstBands,
496
0
                    nNearDist, nMaxNonBlack, oColors, panLastLineCounts,
497
0
                    true,  // bDoHorizontalCheck
498
0
                    true,  // bDoVerticalCheck
499
0
                    true,  // bBottomUp
500
0
                    nYSize - 1 - iLine);
501
0
        ProcessLine(pabyLine, pabyMask, nXSize - 1, 0, nBands, nDstBands,
502
0
                    nNearDist, nMaxNonBlack, oColors, panLastLineCounts,
503
0
                    true,   // bDoHorizontalCheck
504
0
                    false,  // bDoVerticalCheck
505
0
                    true,   // bBottomUp
506
0
                    nYSize - 1 - iLine);
507
508
0
        eErr = GDALDatasetRasterIO(hDstDS, GF_Write, 0, iLine, nXSize, 1,
509
0
                                   pabyLine, nXSize, 1, GDT_UInt8, nDstBands,
510
0
                                   nullptr, nDstBands, nXSize * nDstBands, 1);
511
0
        if (eErr != CE_None)
512
0
        {
513
0
            return false;
514
0
        }
515
516
        /***** write out the mask band line *****/
517
518
0
        if (bSetMask)
519
0
        {
520
0
            eErr = GDALRasterIO(hMaskBand, GF_Write, 0, iLine, nXSize, 1,
521
0
                                pabyMask, nXSize, 1, GDT_UInt8, 0, 0);
522
0
            if (eErr != CE_None)
523
0
            {
524
0
                return false;
525
0
            }
526
0
        }
527
528
0
        if (!(psOptions->pfnProgress(0.5 + 0.5 * (nYSize - iLine) /
529
0
                                               static_cast<double>(nYSize),
530
0
                                     nullptr, psOptions->pProgressData)))
531
0
        {
532
0
            return false;
533
0
        }
534
0
    }
535
536
0
    return true;
537
0
}
538
539
/************************************************************************/
540
/*                            ProcessLine()                             */
541
/*                                                                      */
542
/*      Process a single scanline of image data.                        */
543
/************************************************************************/
544
545
static void ProcessLine(GByte *pabyLine, GByte *pabyMask, int iStart, int iEnd,
546
                        int nSrcBands, int nDstBands, int nNearDist,
547
                        int nMaxNonBlack, const Colors &oColors,
548
                        int *panLastLineCounts, bool bDoHorizontalCheck,
549
                        bool bDoVerticalCheck, bool bBottomUp,
550
                        int iLineFromTopOrBottom)
551
0
{
552
0
    const GByte nReplaceValue = !oColors.empty() && oColors.size() == 1 &&
553
0
                                        !oColors[0].empty() &&
554
0
                                        oColors[0][0] == 255
555
0
                                    ? 255
556
0
                                    : 0;
557
558
    /* -------------------------------------------------------------------- */
559
    /*      Vertical checking.                                              */
560
    /* -------------------------------------------------------------------- */
561
562
0
    if (bDoVerticalCheck)
563
0
    {
564
0
        const int nXSize = std::max(iStart + 1, iEnd + 1);
565
566
0
        for (int i = 0; i < nXSize; i++)
567
0
        {
568
            // are we already terminated for this column?
569
0
            if (panLastLineCounts[i] > nMaxNonBlack)
570
0
                continue;
571
572
            /***** is the pixel valid data? ****/
573
574
0
            bool bIsNonBlack = false;
575
576
            /***** loop over the colors *****/
577
578
0
            for (int iColor = 0; iColor < static_cast<int>(oColors.size());
579
0
                 iColor++)
580
0
            {
581
582
0
                const Color &oColor = oColors[iColor];
583
584
0
                bIsNonBlack = false;
585
586
                /***** loop over the bands *****/
587
588
0
                for (int iBand = 0; iBand < nSrcBands; iBand++)
589
0
                {
590
0
                    const int nPix = pabyLine[i * nDstBands + iBand];
591
592
0
                    if (oColor[iBand] - nPix > nNearDist ||
593
0
                        nPix > nNearDist + oColor[iBand])
594
0
                    {
595
0
                        bIsNonBlack = true;
596
0
                        break;
597
0
                    }
598
0
                }
599
600
0
                if (!bIsNonBlack)
601
0
                    break;
602
0
            }
603
604
0
            if (bIsNonBlack)
605
0
            {
606
0
                panLastLineCounts[i]++;
607
608
0
                if (panLastLineCounts[i] > nMaxNonBlack)
609
0
                    continue;
610
611
0
                if (iLineFromTopOrBottom == 0 && nMaxNonBlack > 0)
612
0
                {
613
                    // if there's a valid value just at the top or bottom
614
                    // of the raster, then ignore the nMaxNonBlack setting
615
0
                    panLastLineCounts[i] = nMaxNonBlack + 1;
616
0
                    continue;
617
0
                }
618
0
            }
619
            // else
620
            //   panLastLineCounts[i] = 0; // not sure this even makes sense
621
622
            /***** replace the pixel values *****/
623
0
            for (int iBand = 0; iBand < nSrcBands; iBand++)
624
0
                pabyLine[i * nDstBands + iBand] = nReplaceValue;
625
626
            /***** alpha *****/
627
0
            if (nDstBands > nSrcBands)
628
0
                pabyLine[i * nDstBands + nDstBands - 1] = 0;
629
630
            /***** mask *****/
631
0
            if (pabyMask != nullptr)
632
0
                pabyMask[i] = 0;
633
0
        }
634
0
    }
635
636
    /* -------------------------------------------------------------------- */
637
    /*      Horizontal Checking.                                            */
638
    /* -------------------------------------------------------------------- */
639
640
0
    if (bDoHorizontalCheck)
641
0
    {
642
0
        int nNonBlackPixels = 0;
643
644
        /***** on a bottom up pass assume nMaxNonBlack is 0 *****/
645
646
0
        if (bBottomUp)
647
0
            nMaxNonBlack = 0;
648
649
0
        const int iDir = iStart < iEnd ? 1 : -1;
650
651
0
        bool bDoTest = TRUE;
652
653
0
        for (int i = iStart; i != iEnd; i += iDir)
654
0
        {
655
            /***** not seen any valid data? *****/
656
657
0
            if (bDoTest)
658
0
            {
659
                /***** is the pixel valid data? ****/
660
661
0
                bool bIsNonBlack = false;
662
663
                /***** loop over the colors *****/
664
665
0
                for (int iColor = 0; iColor < static_cast<int>(oColors.size());
666
0
                     iColor++)
667
0
                {
668
669
0
                    const Color &oColor = oColors[iColor];
670
671
0
                    bIsNonBlack = false;
672
673
                    /***** loop over the bands *****/
674
675
0
                    for (int iBand = 0; iBand < nSrcBands; iBand++)
676
0
                    {
677
0
                        const int nPix = pabyLine[i * nDstBands + iBand];
678
679
0
                        if (oColor[iBand] - nPix > nNearDist ||
680
0
                            nPix > nNearDist + oColor[iBand])
681
0
                        {
682
0
                            bIsNonBlack = true;
683
0
                            break;
684
0
                        }
685
0
                    }
686
687
0
                    if (bIsNonBlack == false)
688
0
                        break;
689
0
                }
690
691
0
                if (bIsNonBlack)
692
0
                {
693
                    /***** use nNonBlackPixels in grey areas  *****/
694
                    /***** from the vertical pass's grey areas ****/
695
696
0
                    if (panLastLineCounts[i] <= nMaxNonBlack)
697
0
                        nNonBlackPixels = panLastLineCounts[i];
698
0
                    else
699
0
                        nNonBlackPixels++;
700
0
                }
701
702
0
                if (nNonBlackPixels > nMaxNonBlack)
703
0
                {
704
0
                    bDoTest = false;
705
0
                    continue;
706
0
                }
707
708
0
                if (bIsNonBlack && nMaxNonBlack > 0 && i == iStart)
709
0
                {
710
                    // if there's a valid value just at the left or right
711
                    // of the raster, then ignore the nMaxNonBlack setting
712
0
                    bDoTest = false;
713
0
                    continue;
714
0
                }
715
716
                /***** replace the pixel values *****/
717
718
0
                for (int iBand = 0; iBand < nSrcBands; iBand++)
719
0
                    pabyLine[i * nDstBands + iBand] = nReplaceValue;
720
721
                /***** alpha *****/
722
723
0
                if (nDstBands > nSrcBands)
724
0
                    pabyLine[i * nDstBands + nDstBands - 1] = 0;
725
726
                /***** mask *****/
727
728
0
                if (pabyMask != nullptr)
729
0
                    pabyMask[i] = 0;
730
0
            }
731
732
            /***** seen valid data but test if the *****/
733
            /***** vertical pass saw any non valid data *****/
734
735
0
            else if (panLastLineCounts[i] == 0)
736
0
            {
737
0
                bDoTest = true;
738
0
                nNonBlackPixels = 0;
739
0
            }
740
0
        }
741
0
    }
742
0
}
743
744
/************************************************************************/
745
/*                               IsInt()                                */
746
/************************************************************************/
747
748
static bool IsInt(const char *pszArg)
749
0
{
750
0
    if (pszArg[0] == '-')
751
0
        pszArg++;
752
753
0
    if (*pszArg == '\0')
754
0
        return false;
755
756
0
    while (*pszArg != '\0')
757
0
    {
758
0
        if (*pszArg < '0' || *pszArg > '9')
759
0
            return false;
760
0
        pszArg++;
761
0
    }
762
763
0
    return true;
764
0
}
765
766
/************************************************************************/
767
/*                   GDALNearblackOptionsGetParser()                    */
768
/************************************************************************/
769
770
static std::unique_ptr<GDALArgumentParser>
771
GDALNearblackOptionsGetParser(GDALNearblackOptions *psOptions,
772
                              GDALNearblackOptionsForBinary *psOptionsForBinary)
773
0
{
774
0
    auto argParser = std::make_unique<GDALArgumentParser>(
775
0
        "nearblack", /* bForBinary=*/psOptionsForBinary != nullptr);
776
777
0
    argParser->add_description(
778
0
        _("Convert nearly black/white borders to black."));
779
780
0
    argParser->add_epilog(_(
781
0
        "For more details, consult https://gdal.org/programs/nearblack.html"));
782
783
0
    argParser->add_output_format_argument(psOptions->osFormat);
784
785
    // Written that way so that in library mode, users can still use the -q
786
    // switch, even if it has no effect
787
0
    argParser->add_quiet_argument(
788
0
        psOptionsForBinary ? &(psOptionsForBinary->bQuiet) : nullptr);
789
790
0
    argParser->add_creation_options_argument(psOptions->aosCreationOptions);
791
792
0
    auto &oOutputFileArg =
793
0
        argParser->add_argument("-o")
794
0
            .metavar("<output_file>")
795
0
            .help(_("The name of the output file to be created."));
796
0
    if (psOptionsForBinary)
797
0
        oOutputFileArg.store_into(psOptionsForBinary->osOutFile);
798
799
0
    {
800
0
        auto &group = argParser->add_mutually_exclusive_group();
801
0
        group.add_argument("-white")
802
0
            .store_into(psOptions->bNearWhite)
803
0
            .help(_("Search for nearly white (255) pixels instead of nearly "
804
0
                    "black pixels."));
805
806
0
        group.add_argument("-color")
807
0
            .append()
808
0
            .metavar("<c1,c2,c3...cn>")
809
0
            .action(
810
0
                [psOptions](const std::string &s)
811
0
                {
812
0
                    Color oColor;
813
814
                    /***** tokenize the arg on , *****/
815
816
0
                    const CPLStringList aosTokens(
817
0
                        CSLTokenizeString2(s.c_str(), ",", 0));
818
819
                    /***** loop over the tokens *****/
820
821
0
                    for (int iToken = 0; iToken < aosTokens.size(); iToken++)
822
0
                    {
823
824
                        /***** ensure the token is an int and add it to the color *****/
825
826
0
                        if (IsInt(aosTokens[iToken]))
827
0
                        {
828
0
                            oColor.push_back(atoi(aosTokens[iToken]));
829
0
                        }
830
0
                        else
831
0
                        {
832
0
                            throw std::invalid_argument(
833
0
                                "Colors must be valid integers.");
834
0
                        }
835
0
                    }
836
837
                    /***** check if the number of bands is consistent *****/
838
839
0
                    if (!psOptions->oColors.empty() &&
840
0
                        psOptions->oColors.front().size() != oColor.size())
841
0
                    {
842
0
                        throw std::invalid_argument(
843
0
                            "all -color args must have the same number of "
844
0
                            "values.\n");
845
0
                    }
846
847
                    /***** add the color to the colors *****/
848
849
0
                    psOptions->oColors.push_back(std::move(oColor));
850
0
                })
851
0
            .help(_("Search for pixels near the specified color."));
852
0
    }
853
854
0
    argParser->add_argument("-nb")
855
0
        .metavar("<non_black_pixels>")
856
0
        .nargs(1)
857
0
        .default_value(psOptions->nMaxNonBlack)
858
0
        .store_into(psOptions->nMaxNonBlack)
859
0
        .help(_("Number of consecutive non-black pixels."));
860
861
0
    argParser->add_argument("-near")
862
0
        .metavar("<dist>")
863
0
        .nargs(1)
864
0
        .default_value(psOptions->nNearDist)
865
0
        .store_into(psOptions->nNearDist)
866
0
        .help(_("Select how far from black, white or custom colors the pixel "
867
0
                "values can be and still considered."));
868
869
0
    argParser->add_argument("-setalpha")
870
0
        .store_into(psOptions->bSetAlpha)
871
0
        .help(_("Adds an alpha band if needed."));
872
873
0
    argParser->add_argument("-setmask")
874
0
        .store_into(psOptions->bSetMask)
875
0
        .help(_("Adds a mask band to the output file if -o is used, or to the "
876
0
                "input file otherwise."));
877
878
0
    argParser->add_argument("-alg")
879
0
        .choices("floodfill", "twopasses")
880
0
        .metavar("floodfill|twopasses")
881
0
        .action([psOptions](const std::string &s)
882
0
                { psOptions->bFloodFill = EQUAL(s.c_str(), "floodfill"); })
883
0
        .help(_("Selects the algorithm to apply."));
884
885
0
    if (psOptionsForBinary)
886
0
    {
887
0
        argParser->add_argument("input_file")
888
0
            .metavar("<input_file>")
889
0
            .store_into(psOptionsForBinary->osInFile)
890
0
            .help(_("The input file. Any GDAL supported format, any number of "
891
0
                    "bands, normally 8bit Byte bands."));
892
0
    }
893
894
0
    return argParser;
895
0
}
896
897
/************************************************************************/
898
/*                    GDALNearblackGetParserUsage()                     */
899
/************************************************************************/
900
901
std::string GDALNearblackGetParserUsage()
902
0
{
903
0
    try
904
0
    {
905
0
        GDALNearblackOptions sOptions;
906
0
        GDALNearblackOptionsForBinary sOptionsForBinary;
907
0
        auto argParser =
908
0
            GDALNearblackOptionsGetParser(&sOptions, &sOptionsForBinary);
909
0
        return argParser->usage();
910
0
    }
911
0
    catch (const std::exception &err)
912
0
    {
913
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
914
0
                 err.what());
915
0
        return std::string();
916
0
    }
917
0
}
918
919
/************************************************************************/
920
/*                      GDALNearblackOptionsNew()                       */
921
/************************************************************************/
922
923
/**
924
 * Allocates a GDALNearblackOptions struct.
925
 *
926
 * @param papszArgv NULL terminated list of options (potentially including
927
 * filename and open options too), or NULL. The accepted options are the ones of
928
 * the <a href="/programs/nearblack.html">nearblack</a> utility.
929
 * @param psOptionsForBinary (output) may be NULL (and should generally be
930
 * NULL), otherwise (gdal_translate_bin.cpp use case) must be allocated with
931
 *                           GDALNearblackOptionsForBinaryNew() prior to this
932
 * function. Will be filled with potentially present filename, open options,...
933
 * @return pointer to the allocated GDALNearblackOptions struct. Must be freed
934
 * with GDALNearblackOptionsFree().
935
 *
936
 * @since GDAL 2.1
937
 */
938
939
GDALNearblackOptions *
940
GDALNearblackOptionsNew(char **papszArgv,
941
                        GDALNearblackOptionsForBinary *psOptionsForBinary)
942
0
{
943
0
    auto psOptions = std::make_unique<GDALNearblackOptions>();
944
945
0
    try
946
0
    {
947
948
0
        auto argParser =
949
0
            GDALNearblackOptionsGetParser(psOptions.get(), psOptionsForBinary);
950
951
0
        argParser->parse_args_without_binary_name(papszArgv);
952
953
0
        return psOptions.release();
954
0
    }
955
0
    catch (const std::exception &err)
956
0
    {
957
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s", err.what());
958
0
        return nullptr;
959
0
    }
960
0
}
961
962
/************************************************************************/
963
/*                      GDALNearblackOptionsFree()                      */
964
/************************************************************************/
965
966
/**
967
 * Frees the GDALNearblackOptions struct.
968
 *
969
 * @param psOptions the options struct for GDALNearblack().
970
 *
971
 * @since GDAL 2.1
972
 */
973
974
void GDALNearblackOptionsFree(GDALNearblackOptions *psOptions)
975
0
{
976
0
    delete psOptions;
977
0
}
978
979
/************************************************************************/
980
/*                  GDALNearblackOptionsSetProgress()                   */
981
/************************************************************************/
982
983
/**
984
 * Set a progress function.
985
 *
986
 * @param psOptions the options struct for GDALNearblack().
987
 * @param pfnProgress the progress callback.
988
 * @param pProgressData the user data for the progress callback.
989
 *
990
 * @since GDAL 2.1
991
 */
992
993
void GDALNearblackOptionsSetProgress(GDALNearblackOptions *psOptions,
994
                                     GDALProgressFunc pfnProgress,
995
                                     void *pProgressData)
996
0
{
997
0
    psOptions->pfnProgress = pfnProgress ? pfnProgress : GDALDummyProgress;
998
0
    psOptions->pProgressData = pProgressData;
999
0
}