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