Skip to content

Commit 8242577

Browse files
authored
Merge pull request #14264 from boxerab/issue_14257
JP2Grok: readBlockInit initializes synchronous decompression
2 parents d89c3e9 + fb03e9e commit 8242577

File tree

2 files changed

+95
-9
lines changed

2 files changed

+95
-9
lines changed

autotest/gdrivers/jp2grok.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,51 @@ def test_jp2grok_lossless_byte():
10231023
assert maxdiff == 0, "Lossless round-trip failed"
10241024

10251025

1026+
###############################################################################
1027+
# Test multi-tile multi-row round-trip via DirectRasterIO swath path.
1028+
# Regression test for TileCompletion releasing tile images too early:
1029+
# TileCache::release() must respect tile_cache_strategy so that
1030+
# GRK_TILE_CACHE_IMAGE keeps per-tile images alive until copied.
1031+
1032+
1033+
def test_jp2grok_multitile_multirow(tmp_vsimem):
1034+
gdaltest.importorskip_gdal_array()
1035+
np = pytest.importorskip("numpy")
1036+
1037+
# 256x256 image with 64x64 tiles → 4x4 tile grid (4 tile rows)
1038+
width, height = 256, 256
1039+
src_ds = gdal.GetDriverByName("MEM").Create("", width, height, 3, gdal.GDT_Byte)
1040+
# Fill each band with a distinct gradient so we can detect corruption
1041+
for b in range(3):
1042+
band = src_ds.GetRasterBand(b + 1)
1043+
arr = np.empty((height, width), dtype=np.uint8)
1044+
for y in range(height):
1045+
arr[y, :] = (y + b * 80) % 256
1046+
band.WriteArray(arr)
1047+
1048+
out_path = tmp_vsimem / "jp2grok_multitile_multirow.jp2"
1049+
out_ds = gdaltest.jp2grok_drv.CreateCopy(
1050+
out_path,
1051+
src_ds,
1052+
options=["BLOCKXSIZE=64", "BLOCKYSIZE=64", "REVERSIBLE=YES", "QUALITY=100"],
1053+
)
1054+
del out_ds
1055+
1056+
# Re-open and read via DirectRasterIO (triggers async swath decompress)
1057+
ds = gdal.Open(out_path)
1058+
assert ds is not None
1059+
for b in range(3):
1060+
ref_arr = np.empty((height, width), dtype=np.uint8)
1061+
for y in range(height):
1062+
ref_arr[y, :] = (y + b * 80) % 256
1063+
got_arr = ds.GetRasterBand(b + 1).ReadAsArray()
1064+
assert np.array_equal(
1065+
ref_arr, got_arr
1066+
), f"Band {b + 1} data mismatch after multi-row tile decompress"
1067+
ds = None
1068+
src_ds = None
1069+
1070+
10261071
###############################################################################
10271072
# Test driver metadata
10281073

frmts/grok/grkdatasetbase.h

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ struct GRKCodecWrapper
522522
* @param numResolutions number of resolutions
523523
* @return true if successful
524524
*/
525-
bool setUpDecompress(int numThreads, char *pszFilename,
525+
bool setUpDecompress(int numThreads, const char *pszFilename,
526526
vsi_l_offset nCodeStreamLength, uint32_t *nTileW,
527527
uint32_t *nTileH, int *numResolutions)
528528
{
@@ -1780,8 +1780,17 @@ struct JP2GRKDatasetBase : public JP2DatasetBase
17801780
nPromoteAlphaBandIdx);
17811781
};
17821782

1783-
auto postPreload = decompressAsynch(nullptr, nullptr, nXOff, nYOff,
1784-
nXSize, nYSize, rowCopy);
1783+
// When reading a subset of bands, tile images must persist
1784+
// across per-band reads. Use CACHE_IMAGE so that tile row
1785+
// release keeps the decoded pixels alive.
1786+
const int nTotalComps =
1787+
(m_codec && m_codec->psImage) ? m_codec->psImage->numcomps : 0;
1788+
const bool needPersistentTiles =
1789+
(!this->bSingleTiled && nBandCount < nTotalComps);
1790+
1791+
auto postPreload =
1792+
decompressAsynch(nullptr, nullptr, nXOff, nYOff, nXSize, nYSize,
1793+
rowCopy, needPersistentTiles);
17851794

17861795
try
17871796
{
@@ -1862,7 +1871,8 @@ struct JP2GRKDatasetBase : public JP2DatasetBase
18621871
PostPreload decompressAsynch(decompress_callback cb, void *user_data,
18631872
int swath_x0, int swath_y0, int swath_width,
18641873
int swath_height,
1865-
RowCopyFunc rowCopy = nullptr)
1874+
RowCopyFunc rowCopy = nullptr,
1875+
bool needPersistentTiles = false)
18661876
{
18671877
PostPreload rc;
18681878

@@ -1876,8 +1886,7 @@ struct JP2GRKDatasetBase : public JP2DatasetBase
18761886
m_codec->open(fp_, nCodeStreamStart);
18771887
uint32_t nTileW = 0, nTileH = 0;
18781888
int numRes = 0;
1879-
char *pszFilename = const_cast<char *>(m_osFilename.c_str());
1880-
if (!m_codec->setUpDecompress(GetNumThreads(), pszFilename,
1889+
if (!m_codec->setUpDecompress(GetNumThreads(), m_osFilename.c_str(),
18811890
nCodeStreamLength, &nTileW, &nTileH,
18821891
&numRes))
18831892
{
@@ -1944,7 +1953,9 @@ struct JP2GRKDatasetBase : public JP2DatasetBase
19441953
decompressParams.simulate_synchronous = true;
19451954
decompressParams.decompress_callback = cb;
19461955
decompressParams.decompress_callback_user_data = user_data;
1947-
decompressParams.core.tile_cache_strategy = GRK_TILE_CACHE_IMAGE;
1956+
decompressParams.core.tile_cache_strategy =
1957+
needPersistentTiles ? GRK_TILE_CACHE_IMAGE
1958+
: GRK_TILE_CACHE_NONE;
19481959
// For multi-tile images, skip composite allocation to avoid
19491960
// a full-resolution buffer. Single-tile images need the
19501961
// composite because Grok stores their data there (not in the
@@ -2096,6 +2107,33 @@ struct JP2GRKDatasetBase : public JP2DatasetBase
20962107
const int nWidthToRead = std::min(nBlockXSize, nRasterXSize_ - nXOff);
20972108
const int nHeightToRead = std::min(nBlockYSize, nRasterYSize_ - nYOff);
20982109

2110+
// The async decompress pipeline decodes a fixed decode window and
2111+
// cleans up tiles after processing. IReadBlock calls us for each
2112+
// block, potentially a different tile each time. For multi-tile
2113+
// images, reset the codec so a fresh decompress can target just
2114+
// this block's region.
2115+
if (!bSingleTiled && (hasCachedPostPreload_ || initializedAsync))
2116+
{
2117+
delete m_codec;
2118+
m_codec = new GRKCodecWrapper();
2119+
m_codec->open(fp_, nCodeStreamStart);
2120+
uint32_t tileW = 0, tileH = 0;
2121+
int numRes = 0;
2122+
if (!m_codec->setUpDecompress(GetNumThreads(), m_osFilename.c_str(),
2123+
nCodeStreamLength, &tileW, &tileH,
2124+
&numRes))
2125+
{
2126+
delete m_codec;
2127+
m_codec = nullptr;
2128+
CPLError(CE_Failure, CPLE_AppDefined,
2129+
"readBlockInit: codec reinit failed");
2130+
return CE_Failure;
2131+
}
2132+
CPLErrorReset();
2133+
hasCachedPostPreload_ = false;
2134+
initializedAsync = false;
2135+
}
2136+
20992137
auto postPreload = decompressAsynch(nullptr, nullptr, nXOff, nYOff,
21002138
nWidthToRead, nHeightToRead);
21012139
if (!postPreload.asynch_)
@@ -2105,8 +2143,11 @@ struct JP2GRKDatasetBase : public JP2DatasetBase
21052143
return CE_Failure;
21062144
}
21072145

2108-
uint16_t tileno = postPreload.tile_y0 * postPreload.num_tile_cols +
2109-
postPreload.tile_x0;
2146+
// Compute the tile index from block coordinates (not from
2147+
// postPreload.tile_x0/y0, which reflect the full decompress range).
2148+
uint16_t tileno =
2149+
static_cast<uint16_t>(nBlockYOff) * postPreload.num_tile_cols +
2150+
static_cast<uint16_t>(nBlockXOff);
21102151
grk_image *img =
21112152
grk_decompress_get_tile_image(m_codec->pCodec, tileno, true);
21122153
// For single-tile images, Grok puts data in the composite

0 commit comments

Comments
 (0)