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