Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] master from OSGeo:master #129

Merged
merged 12 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apps/gdaladdo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,23 @@ MAIN_START(nArgc, papszArgv)
{
for (const auto &level : *levels)
{
if (CPLGetValueType(level.c_str()) != CPL_VALUE_INTEGER)
{
CPLError(
CE_Failure, CPLE_IllegalArg,
"Value '%s' is not a positive integer subsampling factor",
level.c_str());
std::exit(1);
}
anLevels.push_back(atoi(level.c_str()));
if (anLevels.back() <= 0)
{
CPLError(
CE_Failure, CPLE_IllegalArg,
"Value '%s' is not a positive integer subsampling factor",
level.c_str());
std::exit(1);
}
if (anLevels.back() == 1)
{
printf(
Expand Down
Binary file added autotest/gcore/data/test_11555.tif
Binary file not shown.
80 changes: 80 additions & 0 deletions autotest/gcore/tiff_ovr.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,24 @@ def test_tiff_ovr_3(mfloat32_tif, both_endian):
src_ds = None


###############################################################################
#


@gdaltest.enable_exceptions()
def test_tiff_ovr_invalid_ovr_factor(tmp_path):
tif_fname = str(tmp_path / "byte.tif")

shutil.copyfile("data/byte.tif", tif_fname)

ds = gdal.Open(tif_fname, gdal.GA_Update)
with pytest.raises(
Exception,
match=r"panOverviewList\[1\] = 0 is invalid\. It must be a positive value",
):
ds.BuildOverviews(overviewlist=[2, 0])


###############################################################################
# Test generation

Expand Down Expand Up @@ -1675,6 +1693,33 @@ def test_tiff_ovr_43(tmp_path, both_endian):
assert cs == 642, "did not get expected checksum"


###############################################################################
# Test that we do not propagate PHOTOMETRIC=YCBCR on overviews when
# COMPRESS_OVERVIEW != JPEG


@pytest.mark.require_creation_option("GTiff", "JPEG")
@gdaltest.enable_exceptions()
def test_tiff_ovr_do_not_propagate_photometric_ycbcr_if_ovr_if_not_jpeg(tmp_path):

tif_fname = str(tmp_path / "test.tif")

ds = gdal.GetDriverByName("GTiff").Create(
tif_fname, 8, 8, 3, options=["COMPRESS=JPEG", "PHOTOMETRIC=YCBCR"]
)
ds.GetRasterBand(1).Fill(255)
ds.GetRasterBand(2).Fill(255)
ds.GetRasterBand(3).Fill(255)
ds = None

with gdal.Open(tif_fname, gdal.GA_Update) as ds:
with gdal.config_option("COMPRESS_OVERVIEW", "DEFLATE"):
ds.BuildOverviews("NEAR", [2])

with gdal.Open(tif_fname) as ds:
assert ds.GetRasterBand(1).GetOverview(0).ComputeRasterMinMax() == (255, 255)


###############################################################################
# Test that we can change overview block size through GDAL_TIFF_OVR_BLOCKSIZE configuration
# option
Expand Down Expand Up @@ -2950,3 +2995,38 @@ def test_tiff_ovr_JXL_ALPHA_DISTANCE_OVERVIEW(tmp_vsimem):
assert ds.GetRasterBand(4).Checksum() != cs4
del ds
gdal.Unlink(tmpfilename + ".ovr")


###############################################################################
# Test fix for https://github.com/OSGeo/gdal/issues/11555


def test_tiff_ovr_internal_mask_issue_11555(tmp_vsimem):

if "debug build" in gdal.VersionInfo("--version") and "CI" in os.environ:
pytest.skip("test skipped on CI for debug builds (to keep things fast)")

tmpfilename = str(tmp_vsimem / "test.tif")
gdal.FileFromMemBuffer(tmpfilename, open("data/test_11555.tif", "rb").read())

ds = gdal.Open(tmpfilename, gdal.GA_Update)
ds.BuildOverviews("bilinear", [2])
del ds

ds = gdal.Open(tmpfilename)

# Check that we have non-zero data when mask = 255
assert ds.GetRasterBand(1).GetOverview(0).ReadRaster(0, 5270, 1, 1) == b"\x7F"
assert ds.GetRasterBand(2).GetOverview(0).ReadRaster(0, 5270, 1, 1) == b"\x7F"
assert (
ds.GetRasterBand(1).GetMaskBand().GetOverview(0).ReadRaster(0, 5270, 1, 1)
== b"\xFF"
)

# Check that we have zero data when mask = 0
assert ds.GetRasterBand(1).GetOverview(0).ReadRaster(0, 5271, 1, 1) == b"\x00"
assert ds.GetRasterBand(2).GetOverview(0).ReadRaster(0, 5271, 1, 1) == b"\x00"
assert (
ds.GetRasterBand(1).GetMaskBand().GetOverview(0).ReadRaster(0, 5271, 1, 1)
== b"\x00"
)
86 changes: 86 additions & 0 deletions autotest/gcore/tiff_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -5134,6 +5134,92 @@ def method(request):
gdal.VSICurlClearCache()


###############################################################################
# Test that we honor GDAL_DISABLE_READDIR_ON_OPEN when working on a dataset opened with OVERVIEW_LEVEL open option


@pytest.mark.require_curl()
@pytest.mark.skipif(
not check_libtiff_internal_or_at_least(4, 0, 11),
reason="libtiff >= 4.0.11 required",
)
def test_tiff_read_multi_threaded_vsicurl_error_in_IsBlocksAvailable(
tmp_path,
):

webserver_process = None
webserver_port = 0

(webserver_process, webserver_port) = webserver.launch(
handler=webserver.DispatcherHttpHandler
)
if webserver_port == 0:
pytest.skip()

gdal.VSICurlClearCache()

try:
tmp_filename = str(tmp_path / "test.tif")
ds = gdal.GetDriverByName("GTiff").Create(
tmp_filename, 2001, 10000, 1, options=["SPARSE_OK=YES", "BLOCKYSIZE=1"]
)
ds.GetRasterBand(1).SetNoDataValue(255)
ds.GetRasterBand(1).Fill(255)
ds.Close()

filesize = gdal.VSIStatL(tmp_filename).size
handler = webserver.SequentialHandler()
handler.add("HEAD", "/test.tif", 200, {"Content-Length": "%d" % filesize})

def method(request):
# sys.stderr.write('%s\n' % str(request.headers))

if request.headers["Range"].startswith("bytes="):
rng = request.headers["Range"][len("bytes=") :]
assert len(rng.split("-")) == 2
start = int(rng.split("-")[0])
end = int(rng.split("-")[1])

request.protocol_version = "HTTP/1.1"
request.send_response(206)
request.send_header("Content-type", "application/octet-stream")
request.send_header(
"Content-Range", "bytes %d-%d/%d" % (start, end, filesize)
)
request.send_header("Content-Length", end - start + 1)
request.send_header("Connection", "close")
request.end_headers()
with open(tmp_filename, "rb") as f:
f.seek(start, 0)
request.wfile.write(f.read(end - start + 1))

for i in range(2):
handler.add("GET", "/test.tif", custom_method=method)
handler.add("GET", "/test.tif", 404)

with gdaltest.config_options(
{
"GDAL_NUM_THREADS": "2",
"CPL_VSIL_CURL_ALLOWED_EXTENSIONS": ".tif",
"GDAL_DISABLE_READDIR_ON_OPEN": "EMPTY_DIR",
}
):
with webserver.install_http_handler(handler):
ds = gdal.OpenEx(
"/vsicurl/http://127.0.0.1:%d/test.tif" % webserver_port,
)
with pytest.raises(
Exception,
match="_TIFFPartialReadStripArray:Cannot read offset/size for strile",
):
ds.GetRasterBand(1).ReadRaster()

finally:
webserver.server_stop(webserver_process, webserver_port)

gdal.VSICurlClearCache()


###############################################################################
# Test that a user receives a warning when it queries
# GetMetadataItem("PIXELTYPE", "IMAGE_STRUCTURE")
Expand Down
30 changes: 30 additions & 0 deletions autotest/utilities/test_gdaladdo.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,33 @@ def test_gdaladdo_partial_refresh_from_source_timestamp_gti(gdaladdo_path, tmp_p
ovr_data_refreshed[idx] = ovr_data_ori[idx]
assert ovr_data_refreshed == ovr_data_ori
ds = None


###############################################################################
#


def test_gdaladdo_illegal_factor(gdaladdo_path, tmp_path):

shutil.copyfile("../gcore/data/byte.tif", f"{tmp_path}/byte.tif")

_, err = gdaltest.runexternal_out_and_err(
f"{gdaladdo_path} -r average {tmp_path}/byte.tif invalid"
)
assert "Value 'invalid' is not a positive integer subsampling factor" in err
with gdal.Open(f"{tmp_path}/byte.tif") as ds:
assert ds.GetRasterBand(1).GetOverviewCount() == 0

_, err = gdaltest.runexternal_out_and_err(
f"{gdaladdo_path} -r average {tmp_path}/byte.tif 0"
)
assert "Value '0' is not a positive integer subsampling factor" in err
with gdal.Open(f"{tmp_path}/byte.tif") as ds:
assert ds.GetRasterBand(1).GetOverviewCount() == 0

_, err = gdaltest.runexternal_out_and_err(
f"{gdaladdo_path} -r average {tmp_path}/byte.tif -1"
)
assert "Value '-1' is not a positive integer subsampling factor" in err
with gdal.Open(f"{tmp_path}/byte.tif") as ds:
assert ds.GetRasterBand(1).GetOverviewCount() == 0
5 changes: 2 additions & 3 deletions frmts/gtiff/gtiffdataset.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,8 @@ class GTiffDataset final : public GDALPamDataset

int GetJPEGOverviewCount();

bool IsBlockAvailable(int nBlockId, vsi_l_offset *pnOffset = nullptr,
vsi_l_offset *pnSize = nullptr,
bool *pbErrOccurred = nullptr);
bool IsBlockAvailable(int nBlockId, vsi_l_offset *pnOffset,
vsi_l_offset *pnSize, bool *pbErrOccurred);

void ApplyPamInfo();
void PushMetadataToPam();
Expand Down
26 changes: 18 additions & 8 deletions frmts/gtiff/gtiffdataset_read.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ CPLStringList GTiffDataset::GetCompressionFormats(int nXOff, int nYOff,

vsi_l_offset nOffset = 0;
vsi_l_offset nSize = 0;
if (IsBlockAvailable(nBlockId, &nOffset, &nSize) &&
if (IsBlockAvailable(nBlockId, &nOffset, &nSize, nullptr) &&
nSize <
static_cast<vsi_l_offset>(std::numeric_limits<tmsize_t>::max()))
{
Expand Down Expand Up @@ -222,7 +222,7 @@ CPLErr GTiffDataset::ReadCompressedData(const char *pszFormat, int nXOff,

vsi_l_offset nOffset = 0;
vsi_l_offset nSize = 0;
if (IsBlockAvailable(nBlockId, &nOffset, &nSize) &&
if (IsBlockAvailable(nBlockId, &nOffset, &nSize, nullptr) &&
nSize < static_cast<vsi_l_offset>(
std::numeric_limits<tmsize_t>::max()))
{
Expand Down Expand Up @@ -1305,6 +1305,7 @@ CPLErr GTiffDataset::MultiThreadedRead(int nXOff, int nYOff, int nXSize,
nBlockId +=
asJobs[iJob].iSrcBandIdxSeparate * m_nBlocksPerBand;

bool bErrorInIsBlockAvailable = false;
if (!sContext.bHasPRead)
{
// Taking the mutex here is only needed when bHasPRead ==
Expand All @@ -1314,13 +1315,22 @@ CPLErr GTiffDataset::MultiThreadedRead(int nXOff, int nYOff, int nXSize,
std::lock_guard<std::recursive_mutex> oLock(
sContext.oMutex);

IsBlockAvailable(nBlockId, &asJobs[iJob].nOffset,
&asJobs[iJob].nSize);
CPL_IGNORE_RET_VAL(IsBlockAvailable(
nBlockId, &asJobs[iJob].nOffset, &asJobs[iJob].nSize,
&bErrorInIsBlockAvailable));
}
else
{
IsBlockAvailable(nBlockId, &asJobs[iJob].nOffset,
&asJobs[iJob].nSize);
CPL_IGNORE_RET_VAL(IsBlockAvailable(
nBlockId, &asJobs[iJob].nOffset, &asJobs[iJob].nSize,
&bErrorInIsBlockAvailable));
}
if (bErrorInIsBlockAvailable)
{
std::lock_guard<std::recursive_mutex> oLock(
sContext.oMutex);
sContext.bSuccess = false;
return CE_Failure;
}

// Sanity check on block size
Expand All @@ -1344,7 +1354,7 @@ CPLErr GTiffDataset::MultiThreadedRead(int nXOff, int nYOff, int nXSize,
std::lock_guard<std::recursive_mutex> oLock(
sContext.oMutex);
sContext.bSuccess = false;
break;
return CE_Failure;
}
}

Expand Down Expand Up @@ -6383,7 +6393,7 @@ const char *GTiffDataset::GetMetadataItem(const char *pszName,
{
vsi_l_offset nOffset = 0;
vsi_l_offset nSize = 0;
IsBlockAvailable(0, &nOffset, &nSize);
IsBlockAvailable(0, &nOffset, &nSize, nullptr);
if (nSize > 0)
{
const std::string osSubfile(
Expand Down
11 changes: 7 additions & 4 deletions frmts/gtiff/gtiffdataset_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ CPLErr GTiffDataset::FillEmptyTiles()
}

vsi_l_offset nOffset = 0;
if (!IsBlockAvailable(iBlock, &nOffset, &nRawSize))
if (!IsBlockAvailable(iBlock, &nOffset, &nRawSize, nullptr))
break;

// When using compression, get back the compressed block
Expand Down Expand Up @@ -614,7 +614,7 @@ bool GTiffDataset::WriteEncodedTile(uint32_t tile, GByte *pabyData,
/* -------------------------------------------------------------------- */
if (!m_bWriteEmptyTiles && IsFirstPixelEqualToNoData(pabyData))
{
if (!IsBlockAvailable(tile))
if (!IsBlockAvailable(tile, nullptr, nullptr, nullptr))
{
const int nComponents =
m_nPlanarConfig == PLANARCONFIG_CONTIG ? nBands : 1;
Expand Down Expand Up @@ -800,7 +800,7 @@ bool GTiffDataset::WriteEncodedStrip(uint32_t strip, GByte *pabyData,
/* -------------------------------------------------------------------- */
if (!m_bWriteEmptyTiles && IsFirstPixelEqualToNoData(pabyData))
{
if (!IsBlockAvailable(strip))
if (!IsBlockAvailable(strip, nullptr, nullptr, nullptr))
{
const int nComponents =
m_nPlanarConfig == PLANARCONFIG_CONTIG ? nBands : 1;
Expand Down Expand Up @@ -2709,7 +2709,10 @@ bool GTiffDataset::GetOverviewParameters(
/* -------------------------------------------------------------------- */
/* Determine photometric tag */
/* -------------------------------------------------------------------- */
nPhotometric = m_nPhotometric;
if (m_nPhotometric == PHOTOMETRIC_YCBCR && nCompression != COMPRESSION_JPEG)
nPhotometric = PHOTOMETRIC_RGB;
else
nPhotometric = m_nPhotometric;
const char *pszPhotometric =
GetOptionValue("PHOTOMETRIC", "PHOTOMETRIC_OVERVIEW", &pszOptionKey);
if (!GTIFFUpdatePhotometric(pszPhotometric, pszOptionKey, nCompression,
Expand Down
Loading
Loading