Skip to content

Commit af7f56a

Browse files
committed
Add 'gdal raster nodata-to-alpha'
Fixes #12524
1 parent 750b2f6 commit af7f56a

11 files changed

Lines changed: 410 additions & 0 deletions

apps/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ add_library(
3737
gdalalg_raster_index.cpp
3838
gdalalg_raster_mosaic.cpp
3939
gdalalg_raster_mosaic_stack_common.cpp
40+
gdalalg_raster_nodata_to_alpha.cpp
4041
gdalalg_raster_pipeline.cpp
4142
gdalalg_raster_pixel_info.cpp
4243
gdalalg_raster_polygonize.cpp

apps/gdalalg_raster.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "gdalalg_raster_hillshade.h"
2929
#include "gdalalg_raster_index.h"
3030
#include "gdalalg_raster_mosaic.h"
31+
#include "gdalalg_raster_nodata_to_alpha.h"
3132
#include "gdalalg_raster_overview.h"
3233
#include "gdalalg_raster_pansharpen.h"
3334
#include "gdalalg_raster_pipeline.h"
@@ -98,6 +99,7 @@ class GDALRasterAlgorithm final : public GDALAlgorithm
9899
RegisterSubAlgorithm<GDALRasterReclassifyAlgorithmStandalone>();
99100
RegisterSubAlgorithm<GDALRasterReprojectAlgorithmStandalone>();
100101
RegisterSubAlgorithm<GDALRasterMosaicAlgorithmStandalone>();
102+
RegisterSubAlgorithm<GDALRasterNoDataToAlphaAlgorithmStandalone>();
101103
RegisterSubAlgorithm<GDALRasterPansharpenAlgorithmStandalone>();
102104
RegisterSubAlgorithm<GDALRasterPolygonizeAlgorithm>();
103105
RegisterSubAlgorithm<GDALRasterResizeAlgorithmStandalone>();
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/******************************************************************************
2+
*
3+
* Project: GDAL
4+
* Purpose: "nodata-to-alpha" step of "raster pipeline"
5+
* Author: Even Rouault <even dot rouault at spatialys.com>
6+
*
7+
******************************************************************************
8+
* Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9+
*
10+
* SPDX-License-Identifier: MIT
11+
****************************************************************************/
12+
13+
#include "gdalalg_raster_nodata_to_alpha.h"
14+
15+
#include "gdal_priv.h"
16+
#include "gdal_utils.h"
17+
18+
//! @cond Doxygen_Suppress
19+
20+
#ifndef _
21+
#define _(x) (x)
22+
#endif
23+
24+
/************************************************************************/
25+
/* GDALRasterNoDataToAlphaAlgorithm::GDALRasterNoDataToAlphaAlgorithm() */
26+
/************************************************************************/
27+
28+
GDALRasterNoDataToAlphaAlgorithm::GDALRasterNoDataToAlphaAlgorithm(
29+
bool standaloneStep)
30+
: GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
31+
standaloneStep)
32+
{
33+
AddArg("nodata", 0,
34+
_("Override nodata value of input band(s) "
35+
"(numeric value, 'nan', 'inf', '-inf')"),
36+
&m_nodata);
37+
}
38+
39+
/************************************************************************/
40+
/* GDALRasterNoDataToAlphaAlgorithm::RunStep() */
41+
/************************************************************************/
42+
43+
bool GDALRasterNoDataToAlphaAlgorithm::RunStep(GDALPipelineStepRunContext &)
44+
{
45+
GDALDataset *poSrcDS = m_inputDataset[0].GetDatasetRef();
46+
CPLAssert(poSrcDS);
47+
CPLAssert(m_outputDataset.GetName().empty());
48+
CPLAssert(!m_outputDataset.GetDatasetRef());
49+
50+
if (!m_nodata.empty())
51+
{
52+
CPLStringList aosOptions;
53+
aosOptions.AddString("-of");
54+
aosOptions.AddString("VRT");
55+
56+
if (m_nodata.size() == 1)
57+
{
58+
aosOptions.AddString("-a_nodata");
59+
aosOptions.AddString(CPLSPrintf("%.17g", m_nodata[0]));
60+
}
61+
else
62+
{
63+
if (m_nodata.size() !=
64+
static_cast<size_t>(poSrcDS->GetRasterCount()))
65+
{
66+
ReportError(CE_Failure, CPLE_IllegalArg,
67+
"There should be %d nodata values given the input "
68+
"dataset has %d bands",
69+
poSrcDS->GetRasterCount(),
70+
poSrcDS->GetRasterCount());
71+
return false;
72+
}
73+
aosOptions.AddString("-mo");
74+
std::string nodataValues("NODATA_VALUES=");
75+
for (size_t i = 0; i < m_nodata.size(); ++i)
76+
{
77+
if (i > 0)
78+
nodataValues += ' ';
79+
nodataValues += CPLSPrintf("%.17g", m_nodata[i]);
80+
}
81+
aosOptions.AddString(nodataValues.c_str());
82+
}
83+
84+
GDALTranslateOptions *psOptions =
85+
GDALTranslateOptionsNew(aosOptions.List(), nullptr);
86+
if (psOptions)
87+
{
88+
m_tempDS.reset(GDALDataset::FromHandle(GDALTranslate(
89+
"", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
90+
GDALTranslateOptionsFree(psOptions);
91+
}
92+
poSrcDS = m_tempDS.get();
93+
}
94+
95+
bool bRet = poSrcDS != nullptr;
96+
if (poSrcDS)
97+
{
98+
CPLStringList aosOptions;
99+
aosOptions.AddString("-of");
100+
aosOptions.AddString("VRT");
101+
102+
if (poSrcDS->GetRasterCount() > 0 &&
103+
poSrcDS->GetRasterBand(1)->GetMaskFlags() != GMF_ALL_VALID &&
104+
poSrcDS->GetRasterBand(1)->GetMaskFlags() !=
105+
(GMF_ALPHA | GMF_PER_DATASET))
106+
{
107+
aosOptions.AddString("-a_nodata");
108+
aosOptions.AddString("none");
109+
110+
for (int i = 1; i <= poSrcDS->GetRasterCount(); ++i)
111+
{
112+
aosOptions.AddString("-b");
113+
aosOptions.AddString(CPLSPrintf("%d", i));
114+
}
115+
aosOptions.AddString("-b");
116+
aosOptions.AddString("mask");
117+
118+
aosOptions.AddString(
119+
CPLSPrintf("-colorinterp_%d", poSrcDS->GetRasterCount() + 1));
120+
aosOptions.AddString("alpha");
121+
}
122+
123+
std::unique_ptr<GDALDataset> poOutDS;
124+
GDALTranslateOptions *psOptions =
125+
GDALTranslateOptionsNew(aosOptions.List(), nullptr);
126+
if (psOptions)
127+
{
128+
poOutDS.reset(GDALDataset::FromHandle(GDALTranslate(
129+
"", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
130+
GDALTranslateOptionsFree(psOptions);
131+
132+
poOutDS->GetRasterBand(1)->GetMaskFlags();
133+
}
134+
bRet = poOutDS != nullptr;
135+
if (poOutDS)
136+
{
137+
m_outputDataset.Set(std::move(poOutDS));
138+
}
139+
}
140+
141+
return bRet;
142+
}
143+
144+
GDALRasterNoDataToAlphaAlgorithmStandalone::
145+
~GDALRasterNoDataToAlphaAlgorithmStandalone() = default;
146+
147+
//! @endcond
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/******************************************************************************
2+
*
3+
* Project: GDAL
4+
* Purpose: "nodata-to-alpha" step of "raster pipeline"
5+
* Author: Even Rouault <even dot rouault at spatialys.com>
6+
*
7+
******************************************************************************
8+
* Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9+
*
10+
* SPDX-License-Identifier: MIT
11+
****************************************************************************/
12+
13+
#ifndef GDALALG_RASTER_NODATA_TO_ALPHA_INCLUDED
14+
#define GDALALG_RASTER_NODATA_TO_ALPHA_INCLUDED
15+
16+
#include "gdalalg_raster_pipeline.h"
17+
18+
//! @cond Doxygen_Suppress
19+
20+
/************************************************************************/
21+
/* GDALRasterNoDataToAlphaAlgorithm */
22+
/************************************************************************/
23+
24+
class GDALRasterNoDataToAlphaAlgorithm /* non final */
25+
: public GDALRasterPipelineStepAlgorithm
26+
{
27+
public:
28+
static constexpr const char *NAME = "nodata-to-alpha";
29+
static constexpr const char *DESCRIPTION =
30+
"Replace nodata value(s) with an alpha band.";
31+
static constexpr const char *HELP_URL =
32+
"/programs/gdal_raster_nodata_to_alpha.html";
33+
34+
explicit GDALRasterNoDataToAlphaAlgorithm(bool standaloneStep = false);
35+
36+
private:
37+
bool RunStep(GDALPipelineStepRunContext &ctxt) override;
38+
39+
std::vector<double> m_nodata{};
40+
41+
// Work variables
42+
std::unique_ptr<GDALDataset> m_tempDS{};
43+
};
44+
45+
/************************************************************************/
46+
/* GDALRasterNoDataToAlphaAlgorithmStandalone */
47+
/************************************************************************/
48+
49+
class GDALRasterNoDataToAlphaAlgorithmStandalone final
50+
: public GDALRasterNoDataToAlphaAlgorithm
51+
{
52+
public:
53+
GDALRasterNoDataToAlphaAlgorithmStandalone()
54+
: GDALRasterNoDataToAlphaAlgorithm(/* standaloneStep = */ true)
55+
{
56+
}
57+
58+
~GDALRasterNoDataToAlphaAlgorithmStandalone() override;
59+
};
60+
61+
//! @endcond
62+
63+
#endif /* GDALALG_RASTER_NODATA_TO_ALPHA_INCLUDED */

apps/gdalalg_raster_pipeline.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "gdalalg_raster_fill_nodata.h"
2121
#include "gdalalg_raster_hillshade.h"
2222
#include "gdalalg_raster_mosaic.h"
23+
#include "gdalalg_raster_nodata_to_alpha.h"
2324
#include "gdalalg_raster_pansharpen.h"
2425
#include "gdalalg_raster_proximity.h"
2526
#include "gdalalg_raster_reclassify.h"
@@ -208,6 +209,7 @@ GDALRasterPipelineAlgorithm::GDALRasterPipelineAlgorithm(
208209
m_stepRegistry.Register<GDALRasterFillNodataAlgorithm>();
209210
m_stepRegistry.Register<GDALRasterHillshadeAlgorithm>();
210211
m_stepRegistry.Register<GDALRasterMosaicAlgorithm>();
212+
m_stepRegistry.Register<GDALRasterNoDataToAlphaAlgorithm>();
211213
m_stepRegistry.Register<GDALRasterPansharpenAlgorithm>();
212214
m_stepRegistry.Register<GDALRasterProximityAlgorithm>();
213215
m_stepRegistry.Register<GDALRasterReclassifyAlgorithm>();
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env pytest
2+
# -*- coding: utf-8 -*-
3+
###############################################################################
4+
# Project: GDAL/OGR Test Suite
5+
# Purpose: 'gdal raster nodata-to-alph' testing
6+
# Author: Even Rouault <even dot rouault @ spatialys.com>
7+
#
8+
###############################################################################
9+
# Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
10+
#
11+
# SPDX-License-Identifier: MIT
12+
###############################################################################
13+
14+
import pytest
15+
16+
from osgeo import gdal
17+
18+
19+
def test_gdalalg_raster_nodata_to_alpha_noop():
20+
21+
with gdal.Run(
22+
"raster", "nodata-to-alpha", input="../gcore/data/byte.tif", output_format="MEM"
23+
) as alg:
24+
out_ds = alg.Output()
25+
assert out_ds.RasterCount == 1
26+
assert out_ds.GetRasterBand(1).Checksum() == 4672
27+
assert out_ds.GetRasterBand(1).GetMaskFlags() == gdal.GMF_ALL_VALID
28+
29+
30+
def test_gdalalg_raster_nodata_to_alpha_nominal():
31+
32+
src_ds = gdal.GetDriverByName("MEM").Create("", 2, 1)
33+
src_ds.GetRasterBand(1).SetNoDataValue(1)
34+
src_ds.GetRasterBand(1).WriteRaster(0, 0, 2, 1, b"\x01\x02")
35+
36+
with gdal.Run(
37+
"raster", "nodata-to-alpha", input=src_ds, output_format="MEM"
38+
) as alg:
39+
out_ds = alg.Output()
40+
assert out_ds.RasterCount == 2
41+
assert (
42+
out_ds.GetRasterBand(1).ReadRaster() == src_ds.GetRasterBand(1).ReadRaster()
43+
)
44+
assert (
45+
out_ds.GetRasterBand(1).GetMaskFlags()
46+
== gdal.GMF_ALPHA | gdal.GMF_PER_DATASET
47+
)
48+
assert out_ds.GetRasterBand(2).ReadRaster() == b"\x00\xFF"
49+
assert out_ds.GetRasterBand(2).GetMaskFlags() == gdal.GMF_ALL_VALID
50+
51+
52+
def test_gdalalg_raster_nodata_override_nodata_single_value():
53+
54+
src_ds = gdal.GetDriverByName("MEM").Create("", 2, 1)
55+
src_ds.GetRasterBand(1).WriteRaster(0, 0, 2, 1, b"\x01\x02")
56+
57+
with gdal.Run(
58+
"raster", "nodata-to-alpha", input=src_ds, output_format="MEM", nodata=[1]
59+
) as alg:
60+
out_ds = alg.Output()
61+
assert out_ds.RasterCount == 2
62+
assert (
63+
out_ds.GetRasterBand(1).ReadRaster() == src_ds.GetRasterBand(1).ReadRaster()
64+
)
65+
assert (
66+
out_ds.GetRasterBand(1).GetMaskFlags()
67+
== gdal.GMF_ALPHA | gdal.GMF_PER_DATASET
68+
)
69+
assert out_ds.GetRasterBand(2).ReadRaster() == b"\x00\xFF"
70+
assert out_ds.GetRasterBand(2).GetMaskFlags() == gdal.GMF_ALL_VALID
71+
72+
73+
def test_gdalalg_raster_nodata_override_nodata_several_values():
74+
75+
src_ds = gdal.GetDriverByName("MEM").Create("", 3, 1, 3)
76+
src_ds.GetRasterBand(1).WriteRaster(0, 0, 3, 1, b"\x01\x02\x03")
77+
src_ds.GetRasterBand(2).WriteRaster(0, 0, 3, 1, b"\x03\x01\x02")
78+
src_ds.GetRasterBand(3).WriteRaster(0, 0, 3, 1, b"\x03\x02\x01")
79+
80+
with gdal.Run(
81+
"raster", "nodata-to-alpha", input=src_ds, output_format="MEM", nodata=[3, 2, 1]
82+
) as alg:
83+
out_ds = alg.Output()
84+
assert out_ds.RasterCount == 4
85+
assert (
86+
out_ds.GetRasterBand(1).ReadRaster() == src_ds.GetRasterBand(1).ReadRaster()
87+
)
88+
assert (
89+
out_ds.GetRasterBand(1).GetMaskFlags()
90+
== gdal.GMF_ALPHA | gdal.GMF_PER_DATASET
91+
)
92+
assert (
93+
out_ds.GetRasterBand(2).ReadRaster() == src_ds.GetRasterBand(2).ReadRaster()
94+
)
95+
assert (
96+
out_ds.GetRasterBand(2).GetMaskFlags()
97+
== gdal.GMF_ALPHA | gdal.GMF_PER_DATASET
98+
)
99+
assert (
100+
out_ds.GetRasterBand(3).ReadRaster() == src_ds.GetRasterBand(3).ReadRaster()
101+
)
102+
assert (
103+
out_ds.GetRasterBand(3).GetMaskFlags()
104+
== gdal.GMF_ALPHA | gdal.GMF_PER_DATASET
105+
)
106+
assert out_ds.GetRasterBand(4).ReadRaster() == b"\xFF\xFF\x00"
107+
assert out_ds.GetRasterBand(4).GetMaskFlags() == gdal.GMF_ALL_VALID
108+
109+
with pytest.raises(
110+
Exception,
111+
match="There should be 3 nodata values given the input dataset has 3 bands",
112+
):
113+
gdal.Run(
114+
"raster",
115+
"nodata-to-alpha",
116+
input=src_ds,
117+
output_format="MEM",
118+
nodata=[3, 2],
119+
)

doc/source/conf.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,13 @@ def check_python_bindings():
393393
[author_evenr],
394394
1,
395395
),
396+
(
397+
"programs/gdal_raster_nodata_to_alpha",
398+
"gdal-raster-nodata-to-alpha",
399+
"Replace nodata value(s) with an alpha band",
400+
[author_evenr],
401+
1,
402+
),
396403
(
397404
"programs/gdal_raster_overview_add",
398405
"gdal-raster-overview-add",

doc/source/programs/gdal_raster.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Available sub-commands
3434
- :ref:`gdal_raster_hillshade`
3535
- :ref:`gdal_raster_index`
3636
- :ref:`gdal_raster_mosaic`
37+
- :ref:`gdal_raster_nodata_to_alpha`
3738
- :ref:`gdal_raster_overview`
3839
- :ref:`gdal_raster_pipeline`
3940
- :ref:`gdal_raster_pixel_info`

0 commit comments

Comments
 (0)