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