Skip to content

Commit

Permalink
Merge pull request #10751 from rouault/tiff_jpeg_error_msg
Browse files Browse the repository at this point in the history
GTiff: better error messages when trying to create too-large untiled JPEG/WEBP compressed files (or with huge tiles)
  • Loading branch information
rouault authored Sep 18, 2024
2 parents 7ca9ed0 + 6260dd9 commit 86cfe19
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 2 deletions.
82 changes: 82 additions & 0 deletions autotest/gcore/tiff_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -11597,3 +11597,85 @@ def test_tiff_write_colormap_256_mult_factor(tmp_vsimem):
and ct.GetColorEntry(1) == (0, 1, 2, 255)
and ct.GetColorEntry(2) == (254, 254, 254, 255)
), "Wrong color table entry."


###############################################################################
@pytest.mark.require_creation_option("GTiff", "JPEG")
@pytest.mark.parametrize(
"xsize,ysize,options,expected_error_msg",
[
(
65501,
1,
["COMPRESS=JPEG"],
"COMPRESS=JPEG is only compatible of un-tiled images whose width is lesser or equal to 65500 pixels",
),
(
1,
65501,
["COMPRESS=JPEG", "BLOCKYSIZE=65501"],
"COMPRESS=JPEG is only compatible of images whose BLOCKYSIZE is lesser or equal to 65500 pixels",
),
(
1,
1,
["COMPRESS=JPEG", "TILED=YES", "BLOCKXSIZE=65536"],
"COMPRESS=JPEG is only compatible of tiled images whose BLOCKXSIZE is lesser or equal to 65500 pixels",
),
(
1,
1,
["COMPRESS=JPEG", "TILED=YES", "BLOCKYSIZE=65536"],
"COMPRESS=JPEG is only compatible of images whose BLOCKYSIZE is lesser or equal to 65500 pixels",
),
],
)
@gdaltest.enable_exceptions()
def test_tiff_write_too_large_jpeg(
tmp_vsimem, xsize, ysize, options, expected_error_msg
):

filename = str(tmp_vsimem / "test.tif")
with pytest.raises(Exception, match=expected_error_msg):
gdal.GetDriverByName("GTiff").Create(filename, xsize, ysize, options=options)


###############################################################################
@pytest.mark.require_creation_option("GTiff", "WEBP")
@pytest.mark.parametrize(
"xsize,ysize,options,expected_error_msg",
[
(
16384,
1,
["COMPRESS=WEBP"],
"COMPRESS=WEBP is only compatible of un-tiled images whose width is lesser or equal to 16383 pixels",
),
(
1,
16384,
["COMPRESS=WEBP", "BLOCKYSIZE=16384"],
"COMPRESS=WEBP is only compatible of images whose BLOCKYSIZE is lesser or equal to 16383 pixels",
),
(
1,
1,
["COMPRESS=WEBP", "TILED=YES", "BLOCKXSIZE=16384"],
"COMPRESS=WEBP is only compatible of tiled images whose BLOCKXSIZE is lesser or equal to 16383 pixels",
),
(
1,
1,
["COMPRESS=WEBP", "TILED=YES", "BLOCKYSIZE=16384"],
"COMPRESS=WEBP is only compatible of images whose BLOCKYSIZE is lesser or equal to 16383 pixels",
),
],
)
@gdaltest.enable_exceptions()
def test_tiff_write_too_large_webp(
tmp_vsimem, xsize, ysize, options, expected_error_msg
):

filename = str(tmp_vsimem / "test.tif")
with pytest.raises(Exception, match=expected_error_msg):
gdal.GetDriverByName("GTiff").Create(filename, xsize, ysize, options=options)
49 changes: 49 additions & 0 deletions frmts/gtiff/gtiffdataset_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5122,6 +5122,55 @@ TIFF *GTiffDataset::CreateLL(const char *pszFilename, int nXSize, int nYSize,
return nullptr;
}

constexpr int JPEG_MAX_DIMENSION = 65500; // Defined in jpeglib.h
constexpr int WEBP_MAX_DIMENSION = 16383;

const struct
{
int nCodecID;
const char *pszCodecName;
int nMaxDim;
} asLimitations[] = {
{COMPRESSION_JPEG, "JPEG", JPEG_MAX_DIMENSION},
{COMPRESSION_WEBP, "WEBP", WEBP_MAX_DIMENSION},
};

for (const auto &sLimitation : asLimitations)
{
if (l_nCompression == sLimitation.nCodecID && !bTiled &&
nXSize > sLimitation.nMaxDim)
{
ReportError(
pszFilename, CE_Failure, CPLE_IllegalArg,
"COMPRESS=%s is only compatible of un-tiled images whose "
"width is lesser or equal to %d pixels. "
"To overcome this limitation, set the TILED=YES creation "
"option.",
sLimitation.pszCodecName, sLimitation.nMaxDim);
return nullptr;
}
else if (l_nCompression == sLimitation.nCodecID && bTiled &&
l_nBlockXSize > sLimitation.nMaxDim)
{
ReportError(pszFilename, CE_Failure, CPLE_IllegalArg,
"COMPRESS=%s is only compatible of tiled images whose "
"BLOCKXSIZE is lesser or equal to %d pixels.",
sLimitation.pszCodecName, sLimitation.nMaxDim);
return nullptr;
}
else if (l_nCompression == sLimitation.nCodecID &&
l_nBlockYSize > sLimitation.nMaxDim)
{
ReportError(pszFilename, CE_Failure, CPLE_IllegalArg,
"COMPRESS=%s is only compatible of images whose "
"BLOCKYSIZE is lesser or equal to %d pixels. "
"To overcome this limitation, set the TILED=YES "
"creation option",
sLimitation.pszCodecName, sLimitation.nMaxDim);
return nullptr;
}
}

/* -------------------------------------------------------------------- */
/* How many bits per sample? We have a special case if NBITS */
/* specified for GDT_Byte, GDT_UInt16, GDT_UInt32. */
Expand Down
7 changes: 5 additions & 2 deletions frmts/gtiff/libtiff/tif_jpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -2191,9 +2191,12 @@ static int JPEGPreEncode(TIFF *tif, uint16_t s)
segment_width = TIFFhowmany_32(segment_width, sp->h_sampling);
segment_height = TIFFhowmany_32(segment_height, sp->v_sampling);
}
if (segment_width > 65535 || segment_height > 65535)
if (segment_width > (uint32_t)JPEG_MAX_DIMENSION ||
segment_height > (uint32_t)JPEG_MAX_DIMENSION)
{
TIFFErrorExtR(tif, module, "Strip/tile too large for JPEG");
TIFFErrorExtR(tif, module,
"Strip/tile too large for JPEG. Maximum dimension is %d",
(int)JPEG_MAX_DIMENSION);
return (0);
}
sp->cinfo.c.image_width = segment_width;
Expand Down

0 comments on commit 86cfe19

Please sign in to comment.