From 62b9df5d2af68262fa6fcfb66086bf128f7d67c3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 21 Dec 2017 13:32:02 +0100 Subject: [PATCH] Add ZSTD compression codec From https://github.com/facebook/zstd "Zstandard, or zstd as short version, is a fast lossless compression algorithm, targeting real-time compression scenarios at zlib-level and better compression ratios. It's backed by a very fast entropy stage, provided by Huff0 and FSE library." We require libzstd >= 1.0.0 so as to be able to use streaming compression and decompression methods. The default compression level we have selected is 9 (range goes from 1 to 22), which experimentally offers equivalent or better compression ratio than the default deflate/ZIP level of 6, and much faster compression. For example on a 6600x4400 16bit image, tiffcp -c zip runs in 10.7 seconds, while tiffcp -c zstd runs in 5.3 seconds. Decompression time for zip is 840 ms, and for zstd 650 ms. File size is 42735936 for zip, and 42586822 for zstd. Similar findings on other images. On a 25894x16701 16bit image, Compression time Decompression time File size ZSTD 35 s 3.2 s 399 700 498 ZIP/Deflate 1m 20 s 4.9 s 419 622 336 --- CMakeLists.txt | 28 +++ configure.ac | 55 +++++ libtiff/CMakeLists.txt | 3 +- libtiff/Makefile.am | 3 +- libtiff/tif_codec.c | 4 + libtiff/tif_config.h.cmake.in | 3 + libtiff/tif_config.h.in | 3 + libtiff/tif_dirinfo.c | 4 + libtiff/tif_zstd.c | 442 ++++++++++++++++++++++++++++++++++ libtiff/tiff.h | 2 + libtiff/tiffiop.h | 3 + tools/tiffcp.c | 9 +- 12 files changed, 556 insertions(+), 3 deletions(-) create mode 100644 libtiff/tif_zstd.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 52b5ae99..2e1229ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -578,6 +578,27 @@ if(LIBLZMA_FOUND) set(LZMA_SUPPORT 1) endif() +# libzstd +option(zstd "use libzstd (required for ZSTD compression)" ON) +if (zstd) + find_path(ZSTD_INCLUDE_DIR zstd.h) + find_library(ZSTD_LIBRARY NAMES zstd) + if (ZSTD_INCLUDE_DIR AND ZSTD_LIBRARY) + check_library_exists ("${ZSTD_LIBRARY}" ZSTD_decompressStream "" ZSTD_RECENT_ENOUGH) + if (ZSTD_RECENT_ENOUGH) + set(ZSTD_FOUND TRUE) + set(ZSTD_LIBRARIES ${ZSTD_LIBRARY}) + message(STATUS "Found ZSTD library: ${ZSTD_LIBRARY}") + else () + message(WARNING "Found ZSTD library, but not recent enough. Use zstd >= 1.0") + endif () + endif () +endif() +set(ZSTD_SUPPORT 0) +if(ZSTD_FOUND) + set(ZSTD_SUPPORT 1) +endif() + # 8/12-bit jpeg mode option(jpeg12 "enable libjpeg 8/12-bit dual mode (requires separate 12-bit libjpeg build)" ON) @@ -692,6 +713,9 @@ endif() if(LIBLZMA_INCLUDE_DIRS) list(APPEND TIFF_INCLUDES ${LIBLZMA_INCLUDE_DIRS}) endif() +if(ZSTD_INCLUDE_DIR) + list(APPEND TIFF_INCLUDES ${ZSTD_INCLUDE_DIR}) +endif() # Libraries required by libtiff set(TIFF_LIBRARY_DEPS) @@ -713,6 +737,9 @@ endif() if(LIBLZMA_LIBRARIES) list(APPEND TIFF_LIBRARY_DEPS ${LIBLZMA_LIBRARIES}) endif() +if(ZSTD_LIBRARIES) + list(APPEND TIFF_LIBRARY_DEPS ${ZSTD_LIBRARIES}) +endif() #report_values(TIFF_INCLUDES TIFF_LIBRARY_DEPS) @@ -756,6 +783,7 @@ message(STATUS " Old JPEG support: ${old-jpeg} (requested) ${ message(STATUS " JPEG 8/12 bit dual mode: ${jpeg12} (requested) ${JPEG12_FOUND} (availability)") message(STATUS " ISO JBIG support: ${jbig} (requested) ${JBIG_FOUND} (availability)") message(STATUS " LZMA2 support: ${lzma} (requested) ${LIBLZMA_FOUND} (availability)") +message(STATUS " ZSTD support: ${zstd} (requested) ${ZSTD_FOUND} (availability)") message(STATUS "") message(STATUS " C++ support: ${cxx} (requested) ${CXX_SUPPORT} (availability)") message(STATUS "") diff --git a/configure.ac b/configure.ac index 0dd32b75..6a8e6e47 100644 --- a/configure.ac +++ b/configure.ac @@ -826,6 +826,60 @@ fi AM_CONDITIONAL(HAVE_LZMA, test "$HAVE_LZMA" = 'yes') +dnl --------------------------------------------------------------------------- +dnl Check for libzstd. +dnl --------------------------------------------------------------------------- + +HAVE_ZSTD=no + +AC_ARG_ENABLE(zstd, + AS_HELP_STRING([--disable-zstd], + [disable libzstd usage (required for zstd compression, enabled by default)]),,) +AC_ARG_WITH(zstd-include-dir, + AS_HELP_STRING([--with-zstd-include-dir=DIR], + [location of libzstd headers]),,) +AC_ARG_WITH(zstd-lib-dir, + AS_HELP_STRING([--with-zstd-lib-dir=DIR], + [location of libzstd library binary]),,) + +if test "x$enable_zstd" != "xno" ; then + + if test "x$with_zstd_lib_dir" != "x" ; then + LDFLAGS="-L$with_zstd_lib_dir $LDFLAGS" + fi + + AC_CHECK_LIB(zstd, ZSTD_decompressStream, [zstd_lib=yes], [zstd_lib=no],) + if test "$zstd_lib" = "no" -a "x$with_zstd_lib_dir" != "x"; then + AC_MSG_ERROR([zstd library not found at $with_zstd_lib_dir]) + fi + + if test "x$with_zstd_include_dir" != "x" ; then + CPPFLAGS="-I$with_zstd_include_dir $CPPFLAGS" + fi + AC_CHECK_HEADER(zstd.h, [zstd_h=yes], [zstd_h=no]) + if test "$zstd_h" = "no" -a "x$with_zstd_include_dir" != "x" ; then + AC_MSG_ERROR([Libzstd headers not found at $with_zstd_include_dir]) + fi + + if test "$zstd_lib" = "yes" -a "$zstd_h" = "yes" ; then + HAVE_ZSTD=yes + fi + +fi + +if test "$HAVE_ZSTD" = "yes" ; then + AC_DEFINE(ZSTD_SUPPORT,1,[Support zstd compression]) + LIBS="-lzstd $LIBS" + tiff_libs_private="-lzstd ${tiff_libs_private}" + + if test "$HAVE_RPATH" = "yes" -a "x$with_zstd_lib_dir" != "x" ; then + LIBDIR="-R $with_zstd_lib_dir $LIBDIR" + fi + +fi + +AM_CONDITIONAL(HAVE_ZSTD, test "$HAVE_ZSTD" = 'yes') + dnl --------------------------------------------------------------------------- dnl Should 8/12 bit jpeg mode be enabled? dnl --------------------------------------------------------------------------- @@ -1103,6 +1157,7 @@ LOC_MSG([ Old JPEG support: ${HAVE_OJPEG}]) LOC_MSG([ JPEG 8/12 bit dual mode: ${HAVE_JPEG12}]) LOC_MSG([ ISO JBIG support: ${HAVE_JBIG}]) LOC_MSG([ LZMA2 support: ${HAVE_LZMA}]) +LOC_MSG([ ZSTD support: ${HAVE_ZSTD}]) LOC_MSG() LOC_MSG([ C++ support: ${HAVE_CXX}]) LOC_MSG() diff --git a/libtiff/CMakeLists.txt b/libtiff/CMakeLists.txt index 087dfa9e..34107efb 100644 --- a/libtiff/CMakeLists.txt +++ b/libtiff/CMakeLists.txt @@ -94,7 +94,8 @@ set(tiff_SOURCES tif_version.c tif_warning.c tif_write.c - tif_zip.c) + tif_zip.c + tif_zstd.c) set(tiffxx_HEADERS tiffio.hxx) diff --git a/libtiff/Makefile.am b/libtiff/Makefile.am index 9cbc5b1d..53271e9e 100644 --- a/libtiff/Makefile.am +++ b/libtiff/Makefile.am @@ -99,7 +99,8 @@ libtiff_la_SOURCES = \ tif_version.c \ tif_warning.c \ tif_write.c \ - tif_zip.c + tif_zip.c \ + tif_zstd.c libtiffxx_la_SOURCES = \ tif_stream.cxx diff --git a/libtiff/tif_codec.c b/libtiff/tif_codec.c index e0aaacff..ac7bad85 100644 --- a/libtiff/tif_codec.c +++ b/libtiff/tif_codec.c @@ -70,6 +70,9 @@ static int NotConfigured(TIFF*, int); #ifndef LZMA_SUPPORT #define TIFFInitLZMA NotConfigured #endif +#ifndef ZSTD_SUPPORT +#define TIFFInitZSTD NotConfigured +#endif /* * Compression schemes statically built into the library. @@ -97,6 +100,7 @@ TIFFCodec _TIFFBuiltinCODECS[] = { { "SGILog", COMPRESSION_SGILOG, TIFFInitSGILog }, { "SGILog24", COMPRESSION_SGILOG24, TIFFInitSGILog }, { "LZMA", COMPRESSION_LZMA, TIFFInitLZMA }, + { "ZSTD", COMPRESSION_ZSTD, TIFFInitZSTD }, { NULL, 0, NULL } }; diff --git a/libtiff/tif_config.h.cmake.in b/libtiff/tif_config.h.cmake.in index de0f3a3c..1e19483c 100644 --- a/libtiff/tif_config.h.cmake.in +++ b/libtiff/tif_config.h.cmake.in @@ -146,6 +146,9 @@ /* Support LZMA2 compression */ #cmakedefine LZMA_SUPPORT 1 +/* Support ZSTD compression */ +#cmakedefine ZSTD_SUPPORT 1 + /* Name of package */ #define PACKAGE "@PACKAGE_NAME@" diff --git a/libtiff/tif_config.h.in b/libtiff/tif_config.h.in index a4b2e60a..f8af9ed4 100644 --- a/libtiff/tif_config.h.in +++ b/libtiff/tif_config.h.in @@ -380,6 +380,9 @@ /* Support Deflate compression */ #undef ZIP_SUPPORT +/* Support zstd compression */ +#undef ZSTD_SUPPORT + /* Enable large inode numbers on Mac OS X 10.5. */ #ifndef _DARWIN_USE_64_BIT_INODE # define _DARWIN_USE_64_BIT_INODE 1 diff --git a/libtiff/tif_dirinfo.c b/libtiff/tif_dirinfo.c index d26fd120..fd12b737 100644 --- a/libtiff/tif_dirinfo.c +++ b/libtiff/tif_dirinfo.c @@ -1052,6 +1052,10 @@ _TIFFCheckFieldIsValidForCodec(TIFF *tif, ttag_t tag) if (tag == TIFFTAG_PREDICTOR) return 1; break; + case COMPRESSION_ZSTD: + if (tag == TIFFTAG_PREDICTOR) + return 1; + break; } return 0; diff --git a/libtiff/tif_zstd.c b/libtiff/tif_zstd.c new file mode 100644 index 00000000..2bdf1129 --- /dev/null +++ b/libtiff/tif_zstd.c @@ -0,0 +1,442 @@ +/* +* Copyright (c) 2017, Planet Labs +* Author: +* +* Permission to use, copy, modify, distribute, and sell this software and +* its documentation for any purpose is hereby granted without fee, provided +* that (i) the above copyright notices and this permission notice appear in +* all copies of the software and related documentation, and (ii) the names of +* Sam Leffler and Silicon Graphics may not be used in any advertising or +* publicity relating to the software without the specific, prior written +* permission of Sam Leffler and Silicon Graphics. +* +* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, +* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY +* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. +* +* IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR +* ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, +* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +* WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF +* LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +* OF THIS SOFTWARE. +*/ + +#include "tiffiop.h" +#ifdef ZSTD_SUPPORT +/* +* TIFF Library. +* +* ZSTD Compression Support +* +*/ + +#include "tif_predict.h" +#include "zstd.h" + +#include + +/* +* State block for each open TIFF file using ZSTD compression/decompression. +*/ +typedef struct { + TIFFPredictorState predict; + ZSTD_DStream* dstream; + ZSTD_CStream* cstream; + int compression_level; /* compression level */ + ZSTD_outBuffer out_buffer; + int state; /* state flags */ +#define LSTATE_INIT_DECODE 0x01 +#define LSTATE_INIT_ENCODE 0x02 + + TIFFVGetMethod vgetparent; /* super-class method */ + TIFFVSetMethod vsetparent; /* super-class method */ +} ZSTDState; + +#define LState(tif) ((ZSTDState*) (tif)->tif_data) +#define DecoderState(tif) LState(tif) +#define EncoderState(tif) LState(tif) + +static int ZSTDEncode(TIFF* tif, uint8* bp, tmsize_t cc, uint16 s); +static int ZSTDDecode(TIFF* tif, uint8* op, tmsize_t occ, uint16 s); + +static int +ZSTDFixupTags(TIFF* tif) +{ + (void) tif; + return 1; +} + +static int +ZSTDSetupDecode(TIFF* tif) +{ + ZSTDState* sp = DecoderState(tif); + + assert(sp != NULL); + + /* if we were last encoding, terminate this mode */ + if (sp->state & LSTATE_INIT_ENCODE) { + ZSTD_freeCStream(sp->cstream); + sp->cstream = NULL; + sp->state = 0; + } + + sp->state |= LSTATE_INIT_DECODE; + return 1; +} + +/* +* Setup state for decoding a strip. +*/ +static int +ZSTDPreDecode(TIFF* tif, uint16 s) +{ + static const char module[] = "ZSTDPreDecode"; + ZSTDState* sp = DecoderState(tif); + size_t zstd_ret; + + (void) s; + assert(sp != NULL); + + if( (sp->state & LSTATE_INIT_DECODE) == 0 ) + tif->tif_setupdecode(tif); + + if( sp->dstream ) + { + ZSTD_freeDStream(sp->dstream); + sp->dstream = NULL; + } + + sp->dstream = ZSTD_createDStream(); + if( sp->dstream == NULL ) { + TIFFErrorExt(tif->tif_clientdata, module, + "Cannot allocate decompression stream"); + return 0; + } + zstd_ret = ZSTD_initDStream(sp->dstream); + if( ZSTD_isError(zstd_ret) ) { + TIFFErrorExt(tif->tif_clientdata, module, + "Error in ZSTD_initDStream(): %s", + ZSTD_getErrorName(zstd_ret)); + return 0; + } + + return 1; +} + +static int +ZSTDDecode(TIFF* tif, uint8* op, tmsize_t occ, uint16 s) +{ + static const char module[] = "ZSTDDecode"; + ZSTDState* sp = DecoderState(tif); + ZSTD_inBuffer in_buffer; + ZSTD_outBuffer out_buffer; + size_t zstd_ret; + + (void) s; + assert(sp != NULL); + assert(sp->state == LSTATE_INIT_DECODE); + + in_buffer.src = tif->tif_rawcp; + in_buffer.size = (size_t) tif->tif_rawcc; + in_buffer.pos = 0; + + out_buffer.dst = op; + out_buffer.size = (size_t) occ; + out_buffer.pos = 0; + + do { + zstd_ret = ZSTD_decompressStream(sp->dstream, &out_buffer, + &in_buffer); + if( ZSTD_isError(zstd_ret) ) { + TIFFErrorExt(tif->tif_clientdata, module, + "Error in ZSTD_decompressStream(): %s", + ZSTD_getErrorName(zstd_ret)); + return 0; + } + } while( zstd_ret != 0 && + in_buffer.pos < in_buffer.size && + out_buffer.pos < out_buffer.size ); + + if (out_buffer.pos < (size_t)occ) { + TIFFErrorExt(tif->tif_clientdata, module, + "Not enough data at scanline %lu (short %lu bytes)", + (unsigned long) tif->tif_row, + (unsigned long) (size_t)occ - out_buffer.pos); + return 0; + } + + tif->tif_rawcp += in_buffer.pos; + tif->tif_rawcc -= in_buffer.pos; + + return 1; +} + +static int +ZSTDSetupEncode(TIFF* tif) +{ + ZSTDState* sp = EncoderState(tif); + + assert(sp != NULL); + if (sp->state & LSTATE_INIT_DECODE) { + ZSTD_freeDStream(sp->dstream); + sp->dstream = NULL; + sp->state = 0; + } + + sp->state |= LSTATE_INIT_ENCODE; + return 1; +} + +/* +* Reset encoding state at the start of a strip. +*/ +static int +ZSTDPreEncode(TIFF* tif, uint16 s) +{ + static const char module[] = "ZSTDPreEncode"; + ZSTDState *sp = EncoderState(tif); + size_t zstd_ret; + + (void) s; + assert(sp != NULL); + if( sp->state != LSTATE_INIT_ENCODE ) + tif->tif_setupencode(tif); + + if (sp->cstream) { + ZSTD_freeCStream(sp->cstream); + sp->cstream = NULL; + } + sp->cstream = ZSTD_createCStream(); + if( sp->cstream == NULL ) { + TIFFErrorExt(tif->tif_clientdata, module, + "Cannot allocate compression stream"); + return 0; + } + + zstd_ret = ZSTD_initCStream(sp->cstream, sp->compression_level); + if( ZSTD_isError(zstd_ret) ) { + TIFFErrorExt(tif->tif_clientdata, module, + "Error in ZSTD_initCStream(): %s", + ZSTD_getErrorName(zstd_ret)); + return 0; + } + + sp->out_buffer.dst = tif->tif_rawdata; + sp->out_buffer.size = (size_t)tif->tif_rawdatasize; + sp->out_buffer.pos = 0; + + return 1; +} + +/* +* Encode a chunk of pixels. +*/ +static int +ZSTDEncode(TIFF* tif, uint8* bp, tmsize_t cc, uint16 s) +{ + static const char module[] = "ZSTDEncode"; + ZSTDState *sp = EncoderState(tif); + ZSTD_inBuffer in_buffer; + size_t zstd_ret; + + assert(sp != NULL); + assert(sp->state == LSTATE_INIT_ENCODE); + + (void) s; + + in_buffer.src = bp; + in_buffer.size = (size_t)cc; + in_buffer.pos = 0; + + do { + zstd_ret = ZSTD_compressStream(sp->cstream, &sp->out_buffer, + &in_buffer); + if( ZSTD_isError(zstd_ret) ) { + TIFFErrorExt(tif->tif_clientdata, module, + "Error in ZSTD_compressStream(): %s", + ZSTD_getErrorName(zstd_ret)); + return 0; + } + if( sp->out_buffer.pos == sp->out_buffer.size ) { + tif->tif_rawcc = tif->tif_rawdatasize; + TIFFFlushData1(tif); + sp->out_buffer.dst = tif->tif_rawcp; + sp->out_buffer.size = (size_t) tif->tif_rawcc; + sp->out_buffer.pos = 0; + } + } while( in_buffer.pos < in_buffer.size ); + + return 1; +} + +/* +* Finish off an encoded strip by flushing it. +*/ +static int +ZSTDPostEncode(TIFF* tif) +{ + static const char module[] = "ZSTDPostEncode"; + ZSTDState *sp = EncoderState(tif); + size_t zstd_ret; + + do { + zstd_ret = ZSTD_endStream(sp->cstream, &sp->out_buffer); + if( ZSTD_isError(zstd_ret) ) { + TIFFErrorExt(tif->tif_clientdata, module, + "Error in ZSTD_endStream(): %s", + ZSTD_getErrorName(zstd_ret)); + return 0; + } + if( sp->out_buffer.pos > 0 ) { + tif->tif_rawcc = sp->out_buffer.pos; + TIFFFlushData1(tif); + sp->out_buffer.dst = tif->tif_rawcp; + sp->out_buffer.size = (size_t) tif->tif_rawcc; + sp->out_buffer.pos = 0; + } + } while (zstd_ret != 0); + return 1; +} + +static void +ZSTDCleanup(TIFF* tif) +{ + ZSTDState* sp = LState(tif); + + assert(sp != 0); + + (void)TIFFPredictorCleanup(tif); + + tif->tif_tagmethods.vgetfield = sp->vgetparent; + tif->tif_tagmethods.vsetfield = sp->vsetparent; + + if (sp->dstream) { + ZSTD_freeDStream(sp->dstream); + sp->dstream = NULL; + } + if (sp->cstream) { + ZSTD_freeCStream(sp->cstream); + sp->cstream = NULL; + } + _TIFFfree(sp); + tif->tif_data = NULL; + + _TIFFSetDefaultCompressionState(tif); +} + +static int +ZSTDVSetField(TIFF* tif, uint32 tag, va_list ap) +{ + static const char module[] = "ZSTDVSetField"; + ZSTDState* sp = LState(tif); + + switch (tag) { + case TIFFTAG_ZSTD_LEVEL: + sp->compression_level = (int) va_arg(ap, int); + if( sp->compression_level <= 0 || + sp->compression_level > ZSTD_maxCLevel() ) + { + TIFFWarningExt(tif->tif_clientdata, module, + "ZSTD_LEVEL should be between 1 and %d", + ZSTD_maxCLevel()); + } + return 1; + default: + return (*sp->vsetparent)(tif, tag, ap); + } + /*NOTREACHED*/ +} + +static int +ZSTDVGetField(TIFF* tif, uint32 tag, va_list ap) +{ + ZSTDState* sp = LState(tif); + + switch (tag) { + case TIFFTAG_ZSTD_LEVEL: + *va_arg(ap, int*) = sp->compression_level; + break; + default: + return (*sp->vgetparent)(tif, tag, ap); + } + return 1; +} + +static const TIFFField ZSTDFields[] = { + { TIFFTAG_ZSTD_LEVEL, 0, 0, TIFF_ANY, 0, TIFF_SETGET_INT, + TIFF_SETGET_UNDEFINED, + FIELD_PSEUDO, TRUE, FALSE, "ZSTD compression_level", NULL }, +}; + +int +TIFFInitZSTD(TIFF* tif, int scheme) +{ + static const char module[] = "TIFFInitZSTD"; + ZSTDState* sp; + + assert( scheme == COMPRESSION_ZSTD ); + + /* + * Merge codec-specific tag information. + */ + if (!_TIFFMergeFields(tif, ZSTDFields, TIFFArrayCount(ZSTDFields))) { + TIFFErrorExt(tif->tif_clientdata, module, + "Merging ZSTD codec-specific tags failed"); + return 0; + } + + /* + * Allocate state block so tag methods have storage to record values. + */ + tif->tif_data = (uint8*) _TIFFmalloc(sizeof(ZSTDState)); + if (tif->tif_data == NULL) + goto bad; + sp = LState(tif); + + /* + * Override parent get/set field methods. + */ + sp->vgetparent = tif->tif_tagmethods.vgetfield; + tif->tif_tagmethods.vgetfield = ZSTDVGetField; /* hook for codec tags */ + sp->vsetparent = tif->tif_tagmethods.vsetfield; + tif->tif_tagmethods.vsetfield = ZSTDVSetField; /* hook for codec tags */ + + /* Default values for codec-specific fields */ + sp->compression_level = 9; /* default comp. level */ + sp->state = 0; + sp->dstream = 0; + sp->cstream = 0; + sp->out_buffer.dst = NULL; + sp->out_buffer.size = 0; + sp->out_buffer.pos = 0; + + /* + * Install codec methods. + */ + tif->tif_fixuptags = ZSTDFixupTags; + tif->tif_setupdecode = ZSTDSetupDecode; + tif->tif_predecode = ZSTDPreDecode; + tif->tif_decoderow = ZSTDDecode; + tif->tif_decodestrip = ZSTDDecode; + tif->tif_decodetile = ZSTDDecode; + tif->tif_setupencode = ZSTDSetupEncode; + tif->tif_preencode = ZSTDPreEncode; + tif->tif_postencode = ZSTDPostEncode; + tif->tif_encoderow = ZSTDEncode; + tif->tif_encodestrip = ZSTDEncode; + tif->tif_encodetile = ZSTDEncode; + tif->tif_cleanup = ZSTDCleanup; + /* + * Setup predictor setup. + */ + (void) TIFFPredictorInit(tif); + return 1; +bad: + TIFFErrorExt(tif->tif_clientdata, module, + "No space for ZSTD state block"); + return 0; +} +#endif /* ZSTD_SUPPORT */ + +/* vim: set ts=8 sts=8 sw=8 noet: */ diff --git a/libtiff/tiff.h b/libtiff/tiff.h index 51c0a44c..b0d49220 100644 --- a/libtiff/tiff.h +++ b/libtiff/tiff.h @@ -188,6 +188,7 @@ typedef enum { #define COMPRESSION_SGILOG24 34677 /* SGI Log 24-bit packed */ #define COMPRESSION_JP2000 34712 /* Leadtools JPEG2000 */ #define COMPRESSION_LZMA 34925 /* LZMA2 */ +#define COMPRESSION_ZSTD 34926 /* ZSTD */ #define TIFFTAG_PHOTOMETRIC 262 /* photometric interpretation */ #define PHOTOMETRIC_MINISWHITE 0 /* min value is white */ #define PHOTOMETRIC_MINISBLACK 1 /* min value is black */ @@ -601,6 +602,7 @@ typedef enum { #define TIFFTAG_PERSAMPLE 65563 /* interface for per sample tags */ #define PERSAMPLE_MERGED 0 /* present as a single value */ #define PERSAMPLE_MULTI 1 /* present as multiple values */ +#define TIFFTAG_ZSTD_LEVEL 65534 /* ZSTD compression level */ /* * EXIF tags diff --git a/libtiff/tiffiop.h b/libtiff/tiffiop.h index 0f59a705..d44e76a5 100644 --- a/libtiff/tiffiop.h +++ b/libtiff/tiffiop.h @@ -422,6 +422,9 @@ extern int TIFFInitSGILog(TIFF*, int); #ifdef LZMA_SUPPORT extern int TIFFInitLZMA(TIFF*, int); #endif +#ifdef ZSTD_SUPPORT +extern int TIFFInitZSTD(TIFF*, int); +#endif #ifdef VMS extern const TIFFCodec _TIFFBuiltinCODECS[]; #else diff --git a/tools/tiffcp.c b/tools/tiffcp.c index 08df56d5..482f5f4f 100644 --- a/tools/tiffcp.c +++ b/tools/tiffcp.c @@ -389,6 +389,9 @@ processCompressOptions(char* opt) } else if (strneq(opt, "lzma", 4)) { processZIPOptions(opt); defcompression = COMPRESSION_LZMA; + } else if (strneq(opt, "zstd", 4)) { + processZIPOptions(opt); + defcompression = COMPRESSION_ZSTD; } else if (strneq(opt, "jbig", 4)) { defcompression = COMPRESSION_JBIG; } else if (strneq(opt, "sgilog", 6)) { @@ -427,6 +430,7 @@ char* stuff[] = { " -c lzw[:opts] compress output with Lempel-Ziv & Welch encoding", " -c zip[:opts] compress output with deflate encoding", " -c lzma[:opts] compress output with LZMA2 encoding", +" -c zstd[:opts] compress output with ZSTD encoding", " -c jpeg[:opts] compress output with JPEG encoding", " -c jbig compress output with ISO JBIG encoding", " -c packbits compress output with packbits encoding", @@ -446,7 +450,7 @@ char* stuff[] = { " r output color image as RGB rather than YCbCr", "For example, -c jpeg:r:50 to get JPEG-encoded RGB data with 50% comp. quality", "", -"LZW, Deflate (ZIP) and LZMA2 options:", +"LZW, Deflate (ZIP), LZMA2 and ZSTD options:", " # set predictor value", " p# set compression level (preset)", "For example, -c lzw:2 to get LZW-encoded data with horizontal differencing,", @@ -731,6 +735,7 @@ tiffcp(TIFF* in, TIFF* out) case COMPRESSION_ADOBE_DEFLATE: case COMPRESSION_DEFLATE: case COMPRESSION_LZMA: + case COMPRESSION_ZSTD: if (predictor != (uint16)-1) TIFFSetField(out, TIFFTAG_PREDICTOR, predictor); else @@ -741,6 +746,8 @@ tiffcp(TIFF* in, TIFF* out) TIFFSetField(out, TIFFTAG_ZIPQUALITY, preset); else if (compression == COMPRESSION_LZMA) TIFFSetField(out, TIFFTAG_LZMAPRESET, preset); + else if (compression == COMPRESSION_ZSTD) + TIFFSetField(out, TIFFTAG_ZSTD_LEVEL, preset); } break; case COMPRESSION_CCITTFAX3: