Series of changes for the tiff encoder:
* Added configuration options for the TIFF encoder. The options are: - compression: adobe_deflate(default), deflate, lzw, none - zlevel: 0-9 - scanline: 1,0 (Forces scanline encoding) * Fixed bug in encoder with scanline where memory was being modified * by TIFFWriteScanline. A buffer is now created for each row to prevent * modification of the underlying image_data.
This commit is contained in:
parent
af4c2620aa
commit
bf5c532269
3 changed files with 163 additions and 26 deletions
|
@ -156,10 +156,23 @@ static int tiff_dummy_map_proc(thandle_t , tdata_t*, toff_t* )
|
|||
return 0;
|
||||
}
|
||||
|
||||
struct tiff_config
|
||||
{
|
||||
tiff_config()
|
||||
: compression(COMPRESSION_ADOBE_DEFLATE),
|
||||
zlevel(4),
|
||||
scanline(false) {}
|
||||
|
||||
int compression;
|
||||
int zlevel;
|
||||
bool scanline;
|
||||
};
|
||||
|
||||
struct tag_setter : public mapnik::util::static_visitor<>
|
||||
{
|
||||
tag_setter(TIFF * output)
|
||||
: output_(output) {}
|
||||
tag_setter(TIFF * output, tiff_config & config)
|
||||
: output_(output),
|
||||
config_(config) {}
|
||||
|
||||
template <typename T>
|
||||
void operator() (T const&) const
|
||||
|
@ -174,28 +187,56 @@ struct tag_setter : public mapnik::util::static_visitor<>
|
|||
TIFFSetField(output_, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
|
||||
TIFFSetField(output_, TIFFTAG_BITSPERSAMPLE, 8);
|
||||
TIFFSetField(output_, TIFFTAG_SAMPLESPERPIXEL, 4);
|
||||
TIFFSetField(output_, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL);
|
||||
uint16 extras[] = { EXTRASAMPLE_UNASSALPHA };
|
||||
TIFFSetField(output_, TIFFTAG_EXTRASAMPLES, 1, extras);
|
||||
if (config_.compression == COMPRESSION_DEFLATE
|
||||
|| config_.compression == COMPRESSION_ADOBE_DEFLATE
|
||||
|| config_.compression == COMPRESSION_LZW)
|
||||
{
|
||||
TIFFSetField(output_, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL);
|
||||
|
||||
}
|
||||
}
|
||||
inline void operator() (image_data_gray32f const&) const
|
||||
{
|
||||
TIFFSetField(output_, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
|
||||
TIFFSetField(output_, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP);
|
||||
TIFFSetField(output_, TIFFTAG_BITSPERSAMPLE, 32);
|
||||
TIFFSetField(output_, TIFFTAG_PREDICTOR, PREDICTOR_FLOATINGPOINT);
|
||||
TIFFSetField(output_, TIFFTAG_SAMPLESPERPIXEL, 1);
|
||||
if (config_.compression == COMPRESSION_DEFLATE
|
||||
|| config_.compression == COMPRESSION_ADOBE_DEFLATE
|
||||
|| config_.compression == COMPRESSION_LZW)
|
||||
{
|
||||
TIFFSetField(output_, TIFFTAG_PREDICTOR, PREDICTOR_FLOATINGPOINT);
|
||||
}
|
||||
}
|
||||
inline void operator() (image_data_gray16 const&) const
|
||||
{
|
||||
TIFFSetField(output_, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
|
||||
TIFFSetField(output_, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
|
||||
TIFFSetField(output_, TIFFTAG_BITSPERSAMPLE, 16);
|
||||
TIFFSetField(output_, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL);
|
||||
TIFFSetField(output_, TIFFTAG_SAMPLESPERPIXEL, 1);
|
||||
if (config_.compression == COMPRESSION_DEFLATE
|
||||
|| config_.compression == COMPRESSION_ADOBE_DEFLATE
|
||||
|| config_.compression == COMPRESSION_LZW)
|
||||
{
|
||||
TIFFSetField(output_, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL);
|
||||
|
||||
}
|
||||
}
|
||||
inline void operator() (image_data_gray8 const&) const
|
||||
{
|
||||
TIFFSetField(output_, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
|
||||
TIFFSetField(output_, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);
|
||||
TIFFSetField(output_, TIFFTAG_BITSPERSAMPLE, 8);
|
||||
TIFFSetField(output_, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL);
|
||||
TIFFSetField(output_, TIFFTAG_SAMPLESPERPIXEL, 1);
|
||||
if (config_.compression == COMPRESSION_DEFLATE
|
||||
|| config_.compression == COMPRESSION_ADOBE_DEFLATE
|
||||
|| config_.compression == COMPRESSION_LZW)
|
||||
{
|
||||
TIFFSetField(output_, TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL);
|
||||
|
||||
}
|
||||
}
|
||||
inline void operator() (image_data_null const&) const
|
||||
{
|
||||
|
@ -205,10 +246,29 @@ struct tag_setter : public mapnik::util::static_visitor<>
|
|||
|
||||
private:
|
||||
TIFF * output_;
|
||||
tiff_config config_;
|
||||
};
|
||||
|
||||
void set_tiff_config(TIFF* output, tiff_config & config)
|
||||
{
|
||||
// Set some constent tiff information that doesn't vary based on type of data
|
||||
// or image size
|
||||
TIFFSetField(output, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
|
||||
|
||||
// Set the compression for the TIFF
|
||||
TIFFSetField(output, TIFFTAG_COMPRESSION, config.compression);
|
||||
|
||||
if (COMPRESSION_ADOBE_DEFLATE == config.compression || COMPRESSION_DEFLATE == config.compression)
|
||||
{
|
||||
// Set the zip level for the compression
|
||||
// http://en.wikipedia.org/wiki/DEFLATE#Encoder.2Fcompressor
|
||||
// Changes the time spent trying to compress
|
||||
TIFFSetField(output, TIFFTAG_ZIPQUALITY, config.zlevel);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
void save_as_tiff(T1 & file, T2 const& image)
|
||||
void save_as_tiff(T1 & file, T2 const& image, tiff_config & config)
|
||||
{
|
||||
const int width = image.width();
|
||||
const int height = image.height();
|
||||
|
@ -228,43 +288,39 @@ void save_as_tiff(T1 & file, T2 const& image)
|
|||
throw ImageWriterException("Could not write TIFF");
|
||||
}
|
||||
|
||||
// Set some constent tiff information that doesn't vary based on type of data
|
||||
// or image size
|
||||
TIFFSetField(output, TIFFTAG_IMAGEWIDTH, width);
|
||||
TIFFSetField(output, TIFFTAG_IMAGELENGTH, height);
|
||||
TIFFSetField(output, TIFFTAG_IMAGEDEPTH, 1);
|
||||
TIFFSetField(output, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
|
||||
|
||||
// Set the compression for the TIFF
|
||||
//TIFFSetField(output, TIFFTAG_COMPRESSION, COMPRESSION_DEFLATE);
|
||||
TIFFSetField(output, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE);
|
||||
set_tiff_config(output, config);
|
||||
|
||||
// Set the zip level for the compression
|
||||
// http://en.wikipedia.org/wiki/DEFLATE#Encoder.2Fcompressor
|
||||
// Changes the time spent trying to compress
|
||||
TIFFSetField(output, TIFFTAG_ZIPQUALITY, 4);
|
||||
|
||||
// Set tags that vary based on the type of data being provided.
|
||||
tag_setter set(output);
|
||||
tag_setter set(output, config);
|
||||
set(image);
|
||||
//util::apply_visitor(set, image);
|
||||
|
||||
// If the image is greater then 8MB uncompressed, then lets use scanline rather then
|
||||
// tile. TIFF also requires that all TIFFTAG_TILEWIDTH and TIFF_TILELENGTH all be
|
||||
// a multiple of 16, if they are not we will use scanline.
|
||||
if (image.getSize() > 8 * 32 * 1024 * 1024 || width % 16 != 0 || height % 16 != 0)
|
||||
if (image.getSize() > 8 * 32 * 1024 * 1024
|
||||
|| width % 16 != 0
|
||||
|| height % 16 != 0
|
||||
|| config.scanline)
|
||||
{
|
||||
// Process Scanline
|
||||
TIFFSetField(output, TIFFTAG_ROWSPERSTRIP, 1);
|
||||
|
||||
int next_scanline = 0;
|
||||
typename T2::pixel_type * row = reinterpret_cast<typename T2::pixel_type*>(::operator new(image.getRowSize()));
|
||||
|
||||
while (next_scanline < height)
|
||||
{
|
||||
typename T2::pixel_type * row = const_cast<typename T2::pixel_type *>(image.getRow(next_scanline));
|
||||
memcpy(row, image.getRow(next_scanline), image.getRowSize());
|
||||
//typename T2::pixel_type * row = const_cast<typename T2::pixel_type *>(image.getRow(next_scanline));
|
||||
TIFFWriteScanline(output, row, next_scanline, 0);
|
||||
++next_scanline;
|
||||
}
|
||||
::operator delete(row);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -272,8 +328,11 @@ void save_as_tiff(T1 & file, T2 const& image)
|
|||
TIFFSetField(output, TIFFTAG_TILELENGTH, height);
|
||||
TIFFSetField(output, TIFFTAG_TILEDEPTH, 1);
|
||||
// Process as tiles
|
||||
typename T2::pixel_type * image_data = const_cast<typename T2::pixel_type *>(image.getData());
|
||||
typename T2::pixel_type * image_data = reinterpret_cast<typename T2::pixel_type*>(::operator new(image.getSize()));
|
||||
memcpy(image_data, image.getData(), image.getSize());
|
||||
//typename T2::pixel_type * image_data = const_cast<typename T2::pixel_type *>(image.getData());
|
||||
TIFFWriteTile(output, image_data, 0, 0, 0, 0);
|
||||
::operator delete(image_data);
|
||||
}
|
||||
// TODO - handle palette images
|
||||
// std::vector<mapnik::rgb> const& palette
|
||||
|
|
|
@ -255,6 +255,82 @@ void handle_png_options(std::string const& type,
|
|||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_TIFF)
|
||||
void handle_tiff_options(std::string const& type,
|
||||
tiff_config & config)
|
||||
{
|
||||
if (type == "tiff")
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (type.length() > 4)
|
||||
{
|
||||
boost::char_separator<char> sep(":");
|
||||
boost::tokenizer< boost::char_separator<char> > tokens(type, sep);
|
||||
for (auto const& t : tokens)
|
||||
{
|
||||
if (t == "tiff")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (boost::algorithm::starts_with(t, "compression="))
|
||||
{
|
||||
std::string val = t.substr(12);
|
||||
if (!val.empty())
|
||||
{
|
||||
if (val == "deflate")
|
||||
{
|
||||
config.compression = COMPRESSION_DEFLATE;
|
||||
}
|
||||
else if (val == "adobedeflate")
|
||||
{
|
||||
config.compression = COMPRESSION_ADOBE_DEFLATE;
|
||||
}
|
||||
else if (val == "lzw")
|
||||
{
|
||||
config.compression = COMPRESSION_LZW;
|
||||
}
|
||||
else if (val == "none")
|
||||
{
|
||||
config.compression = COMPRESSION_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ImageWriterException("invalid tiff compression: '" + val + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::starts_with(t, "zlevel="))
|
||||
{
|
||||
std::string val = t.substr(7);
|
||||
if (!val.empty())
|
||||
{
|
||||
if (!mapnik::util::string2int(val,config.zlevel) || config.zlevel < 0 || config.zlevel > 9)
|
||||
{
|
||||
throw ImageWriterException("invalid tiff zlevel: '" + val + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::starts_with(t, "scanline="))
|
||||
{
|
||||
std::string val = t.substr(9);
|
||||
if (!val.empty())
|
||||
{
|
||||
if (!mapnik::util::string2bool(val,config.scanline))
|
||||
{
|
||||
throw ImageWriterException("invalid tiff scanline: '" + val + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ImageWriterException("unhandled tiff option: " + t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_WEBP)
|
||||
void handle_webp_options(std::string const& type,
|
||||
WebPConfig & config,
|
||||
|
@ -621,7 +697,9 @@ void save_to_stream(T const& image,
|
|||
else if (boost::algorithm::starts_with(t, "tif"))
|
||||
{
|
||||
#if defined(HAVE_TIFF)
|
||||
save_as_tiff(stream, image);
|
||||
tiff_config config;
|
||||
handle_tiff_options(t, config);
|
||||
save_as_tiff(stream, image, config);
|
||||
#else
|
||||
throw ImageWriterException("tiff output is not enabled in your build of Mapnik");
|
||||
#endif
|
||||
|
|
|
@ -27,10 +27,10 @@ def test_image_premultiply():
|
|||
#im = mapnik.Image(-40,40)
|
||||
|
||||
def test_tiff_round_trip_scanline():
|
||||
filepath = '/tmp/mapnik-tiff-io.tiff'
|
||||
filepath = '/tmp/mapnik-tiff-io-scanline.tiff'
|
||||
im = mapnik.Image(255,267)
|
||||
im.background = mapnik.Color('rgba(1,2,3,.5)')
|
||||
im.save(filepath,'tiff')
|
||||
im.save(filepath,'tiff:zlevel=0:scanline=1:compression=lzw')
|
||||
im2 = mapnik.Image.open(filepath)
|
||||
eq_(im.width(),im2.width())
|
||||
eq_(im.height(),im2.height())
|
||||
|
@ -38,7 +38,7 @@ def test_tiff_round_trip_scanline():
|
|||
eq_(len(im.tostring('tiff')),len(im2.tostring('tiff')))
|
||||
|
||||
def test_tiff_round_trip_tiled():
|
||||
filepath = '/tmp/mapnik-tiff-io.tiff'
|
||||
filepath = '/tmp/mapnik-tiff-io-tiled.tiff'
|
||||
im = mapnik.Image(256,256)
|
||||
im.background = mapnik.Color('rgba(1,2,3,.5)')
|
||||
im.save(filepath,'tiff')
|
||||
|
|
Loading…
Add table
Reference in a new issue