opencv/3rdparty/libtiff/tif_webp.c
2019-02-14 09:28:14 +01:00

685 lines
18 KiB
C

/*
* Copyright (c) 2018, Mapbox
* Author: <norman.barker at mapbox.com>
*
* 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 WEBP_SUPPORT
/*
* TIFF Library.
*
* WEBP Compression Support
*
*/
#include "webp/decode.h"
#include "webp/encode.h"
#include <stdio.h>
#define LSTATE_INIT_DECODE 0x01
#define LSTATE_INIT_ENCODE 0x02
/*
* State block for each open TIFF
* file using WEBP compression/decompression.
*/
typedef struct {
uint16 nSamples; /* number of samples per pixel */
int lossless; /* lossy/lossless compression */
int quality_level; /* compression level */
WebPPicture sPicture; /* WebP Picture */
WebPConfig sEncoderConfig; /* WebP encoder config */
uint8* pBuffer; /* buffer to hold raw data on encoding */
unsigned int buffer_offset; /* current offset into the buffer */
unsigned int buffer_size;
WebPIDecoder* psDecoder; /* WebPIDecoder */
WebPDecBuffer sDecBuffer; /* Decoder buffer */
int last_y; /* Last row decoded */
int state; /* state flags */
TIFFVGetMethod vgetparent; /* super-class method */
TIFFVSetMethod vsetparent; /* super-class method */
} WebPState;
#define LState(tif) ((WebPState*) (tif)->tif_data)
#define DecoderState(tif) LState(tif)
#define EncoderState(tif) LState(tif)
static int TWebPEncode(TIFF* tif, uint8* bp, tmsize_t cc, uint16 s);
static int TWebPDecode(TIFF* tif, uint8* op, tmsize_t occ, uint16 s);
static
int TWebPDatasetWriter(const uint8_t* data, size_t data_size,
const WebPPicture* const picture)
{
static const char module[] = "TWebPDatasetWriter";
TIFF* tif = (TIFF*)(picture->custom_ptr);
if ( (tif->tif_rawcc + (tmsize_t)data_size) > tif->tif_rawdatasize ) {
TIFFErrorExt(tif->tif_clientdata, module,
"Buffer too small by " TIFF_SIZE_FORMAT " bytes.",
(size_t) (tif->tif_rawcc + data_size - tif->tif_rawdatasize));
return 0;
} else {
_TIFFmemcpy(tif->tif_rawcp, data, data_size);
tif->tif_rawcc += data_size;
tif->tif_rawcp += data_size;
return 1;
}
}
/*
* Encode a chunk of pixels.
*/
static int
TWebPEncode(TIFF* tif, uint8* bp, tmsize_t cc, uint16 s)
{
static const char module[] = "TWebPEncode";
WebPState *sp = EncoderState(tif);
(void) s;
assert(sp != NULL);
assert(sp->state == LSTATE_INIT_ENCODE);
if( (uint64)sp->buffer_offset +
(uint64)cc > sp->buffer_size )
{
TIFFErrorExt(tif->tif_clientdata, module,
"Too many bytes to be written");
return 0;
}
memcpy(sp->pBuffer + sp->buffer_offset,
bp, cc);
sp->buffer_offset += (unsigned)cc;
return 1;
}
static int
TWebPDecode(TIFF* tif, uint8* op, tmsize_t occ, uint16 s)
{
static const char module[] = "WebPDecode";
VP8StatusCode status = VP8_STATUS_OK;
WebPState *sp = DecoderState(tif);
(void) s;
assert(sp != NULL);
assert(sp->state == LSTATE_INIT_DECODE);
if (occ % sp->sDecBuffer.u.RGBA.stride)
{
TIFFErrorExt(tif->tif_clientdata, module,
"Fractional scanlines cannot be read");
return 0;
}
status = WebPIAppend(sp->psDecoder, tif->tif_rawcp, tif->tif_rawcc);
if (status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED) {
if (status == VP8_STATUS_INVALID_PARAM) {
TIFFErrorExt(tif->tif_clientdata, module,
"Invalid parameter used.");
} else if (status == VP8_STATUS_OUT_OF_MEMORY) {
TIFFErrorExt(tif->tif_clientdata, module,
"Out of memory.");
} else {
TIFFErrorExt(tif->tif_clientdata, module,
"Unrecognized error.");
}
return 0;
} else {
int current_y, stride;
uint8_t* buf;
/* Returns the RGB/A image decoded so far */
buf = WebPIDecGetRGB(sp->psDecoder, &current_y, NULL, NULL, &stride);
if ((buf != NULL) &&
(occ <= stride * (current_y - sp->last_y))) {
memcpy(op,
buf + (sp->last_y * stride),
occ);
tif->tif_rawcp += tif->tif_rawcc;
tif->tif_rawcc = 0;
sp->last_y += occ / sp->sDecBuffer.u.RGBA.stride;
return 1;
} else {
TIFFErrorExt(tif->tif_clientdata, module, "Unable to decode WebP data.");
return 0;
}
}
}
static int
TWebPFixupTags(TIFF* tif)
{
(void) tif;
if (tif->tif_dir.td_planarconfig != PLANARCONFIG_CONTIG) {
static const char module[] = "TWebPFixupTags";
TIFFErrorExt(tif->tif_clientdata, module,
"TIFF WEBP requires data to be stored contiguously in RGB e.g. RGBRGBRGB "
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
"or RGBARGBARGBA"
#endif
);
return 0;
}
return 1;
}
static int
TWebPSetupDecode(TIFF* tif)
{
static const char module[] = "WebPSetupDecode";
uint16 nBitsPerSample = tif->tif_dir.td_bitspersample;
uint16 sampleFormat = tif->tif_dir.td_sampleformat;
WebPState* sp = DecoderState(tif);
assert(sp != NULL);
sp->nSamples = tif->tif_dir.td_samplesperpixel;
/* check band count */
if ( sp->nSamples != 3
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
&& sp->nSamples != 4
#endif
)
{
TIFFErrorExt(tif->tif_clientdata, module,
"WEBP driver doesn't support %d bands. Must be 3 (RGB) "
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
"or 4 (RGBA) "
#endif
"bands.",
sp->nSamples );
return 0;
}
/* check bits per sample and data type */
if ((nBitsPerSample != 8) && (sampleFormat != 1)) {
TIFFErrorExt(tif->tif_clientdata, module,
"WEBP driver requires 8 bit unsigned data");
return 0;
}
/* if we were last encoding, terminate this mode */
if (sp->state & LSTATE_INIT_ENCODE) {
WebPPictureFree(&sp->sPicture);
if (sp->pBuffer != NULL) {
_TIFFfree(sp->pBuffer);
sp->pBuffer = NULL;
}
sp->buffer_offset = 0;
sp->state = 0;
}
sp->state |= LSTATE_INIT_DECODE;
return 1;
}
/*
* Setup state for decoding a strip.
*/
static int
TWebPPreDecode(TIFF* tif, uint16 s)
{
static const char module[] = "TWebPPreDecode";
uint32 segment_width, segment_height;
WebPState* sp = DecoderState(tif);
TIFFDirectory* td = &tif->tif_dir;
(void) s;
assert(sp != NULL);
if (isTiled(tif)) {
segment_width = td->td_tilewidth;
segment_height = td->td_tilelength;
} else {
segment_width = td->td_imagewidth;
segment_height = td->td_imagelength - tif->tif_row;
if (segment_height > td->td_rowsperstrip)
segment_height = td->td_rowsperstrip;
}
if( (sp->state & LSTATE_INIT_DECODE) == 0 )
tif->tif_setupdecode(tif);
if (sp->psDecoder != NULL) {
WebPIDelete(sp->psDecoder);
WebPFreeDecBuffer(&sp->sDecBuffer);
sp->psDecoder = NULL;
}
sp->last_y = 0;
WebPInitDecBuffer(&sp->sDecBuffer);
sp->sDecBuffer.is_external_memory = 0;
sp->sDecBuffer.width = segment_width;
sp->sDecBuffer.height = segment_height;
sp->sDecBuffer.u.RGBA.stride = segment_width * sp->nSamples;
sp->sDecBuffer.u.RGBA.size = segment_width * sp->nSamples * segment_height;
if (sp->nSamples > 3) {
sp->sDecBuffer.colorspace = MODE_RGBA;
} else {
sp->sDecBuffer.colorspace = MODE_RGB;
}
sp->psDecoder = WebPINewDecoder(&sp->sDecBuffer);
if (sp->psDecoder == NULL) {
TIFFErrorExt(tif->tif_clientdata, module,
"Unable to allocate WebP decoder.");
return 0;
}
return 1;
}
static int
TWebPSetupEncode(TIFF* tif)
{
static const char module[] = "WebPSetupEncode";
uint16 nBitsPerSample = tif->tif_dir.td_bitspersample;
uint16 sampleFormat = tif->tif_dir.td_sampleformat;
WebPState* sp = EncoderState(tif);
assert(sp != NULL);
sp->nSamples = tif->tif_dir.td_samplesperpixel;
/* check band count */
if ( sp->nSamples != 3
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
&& sp->nSamples != 4
#endif
)
{
TIFFErrorExt(tif->tif_clientdata, module,
"WEBP driver doesn't support %d bands. Must be 3 (RGB) "
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
"or 4 (RGBA) "
#endif
"bands.",
sp->nSamples );
return 0;
}
/* check bits per sample and data type */
if ((nBitsPerSample != 8) && (sampleFormat != 1)) {
TIFFErrorExt(tif->tif_clientdata, module,
"WEBP driver requires 8 bit unsigned data");
return 0;
}
if (sp->state & LSTATE_INIT_DECODE) {
WebPIDelete(sp->psDecoder);
WebPFreeDecBuffer(&sp->sDecBuffer);
sp->psDecoder = NULL;
sp->last_y = 0;
sp->state = 0;
}
sp->state |= LSTATE_INIT_ENCODE;
if (!WebPConfigInitInternal(&sp->sEncoderConfig, WEBP_PRESET_DEFAULT,
sp->quality_level,
WEBP_ENCODER_ABI_VERSION)) {
TIFFErrorExt(tif->tif_clientdata, module,
"Error creating WebP encoder configuration.");
return 0;
}
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
sp->sEncoderConfig.lossless = sp->lossless;
#endif
if (!WebPValidateConfig(&sp->sEncoderConfig)) {
TIFFErrorExt(tif->tif_clientdata, module,
"Error with WebP encoder configuration.");
return 0;
}
if (!WebPPictureInit(&sp->sPicture)) {
TIFFErrorExt(tif->tif_clientdata, module,
"Error initializing WebP picture.");
return 0;
}
return 1;
}
/*
* Reset encoding state at the start of a strip.
*/
static int
TWebPPreEncode(TIFF* tif, uint16 s)
{
static const char module[] = "TWebPPreEncode";
uint32 segment_width, segment_height;
WebPState *sp = EncoderState(tif);
TIFFDirectory* td = &tif->tif_dir;
(void) s;
assert(sp != NULL);
if( sp->state != LSTATE_INIT_ENCODE )
tif->tif_setupencode(tif);
/*
* Set encoding parameters for this strip/tile.
*/
if (isTiled(tif)) {
segment_width = td->td_tilewidth;
segment_height = td->td_tilelength;
} else {
segment_width = td->td_imagewidth;
segment_height = td->td_imagelength - tif->tif_row;
if (segment_height > td->td_rowsperstrip)
segment_height = td->td_rowsperstrip;
}
if( segment_width > 16383 || segment_height > 16383 ) {
TIFFErrorExt(tif->tif_clientdata, module,
"WEBP maximum image dimensions are 16383 x 16383.");
return 0;
}
/* set up buffer for raw data */
/* given above check and that nSamples <= 4, buffer_size is <= 1 GB */
sp->buffer_size = segment_width * segment_height * sp->nSamples;
sp->pBuffer = _TIFFmalloc(sp->buffer_size);
if( !sp->pBuffer) {
TIFFErrorExt(tif->tif_clientdata, module, "Cannot allocate buffer");
return 0;
}
sp->buffer_offset = 0;
sp->sPicture.width = segment_width;
sp->sPicture.height = segment_height;
sp->sPicture.writer = TWebPDatasetWriter;
sp->sPicture.custom_ptr = tif;
return 1;
}
/*
* Finish off an encoded strip by flushing it.
*/
static int
TWebPPostEncode(TIFF* tif)
{
static const char module[] = "WebPPostEncode";
int64_t stride;
WebPState *sp = EncoderState(tif);
assert(sp != NULL);
assert(sp->state == LSTATE_INIT_ENCODE);
stride = (int64_t)sp->sPicture.width * sp->nSamples;
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
if (sp->nSamples == 4) {
if (!WebPPictureImportRGBA(&sp->sPicture, sp->pBuffer, (int)stride)) {
TIFFErrorExt(tif->tif_clientdata, module,
"WebPPictureImportRGBA() failed" );
return 0;
}
}
else
#endif
if (!WebPPictureImportRGB(&sp->sPicture, sp->pBuffer, (int)stride)) {
TIFFErrorExt(tif->tif_clientdata, module,
"WebPPictureImportRGB() failed");
return 0;
}
if (!WebPEncode(&sp->sEncoderConfig, &sp->sPicture)) {
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
const char* pszErrorMsg = NULL;
switch(sp->sPicture.error_code) {
case VP8_ENC_ERROR_OUT_OF_MEMORY:
pszErrorMsg = "Out of memory"; break;
case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
pszErrorMsg = "Out of memory while flushing bits"; break;
case VP8_ENC_ERROR_NULL_PARAMETER:
pszErrorMsg = "A pointer parameter is NULL"; break;
case VP8_ENC_ERROR_INVALID_CONFIGURATION:
pszErrorMsg = "Configuration is invalid"; break;
case VP8_ENC_ERROR_BAD_DIMENSION:
pszErrorMsg = "Picture has invalid width/height"; break;
case VP8_ENC_ERROR_PARTITION0_OVERFLOW:
pszErrorMsg = "Partition is bigger than 512k. Try using less "
"SEGMENTS, or increase PARTITION_LIMIT value";
break;
case VP8_ENC_ERROR_PARTITION_OVERFLOW:
pszErrorMsg = "Partition is bigger than 16M";
break;
case VP8_ENC_ERROR_BAD_WRITE:
pszErrorMsg = "Error while fludshing bytes"; break;
case VP8_ENC_ERROR_FILE_TOO_BIG:
pszErrorMsg = "File is bigger than 4G"; break;
case VP8_ENC_ERROR_USER_ABORT:
pszErrorMsg = "User interrupted";
break;
default:
TIFFErrorExt(tif->tif_clientdata, module,
"WebPEncode returned an unknown error code: %d",
sp->sPicture.error_code);
pszErrorMsg = "Unknown WebP error type.";
break;
}
TIFFErrorExt(tif->tif_clientdata, module,
"WebPEncode() failed : %s", pszErrorMsg);
#else
TIFFErrorExt(tif->tif_clientdata, module,
"Error in WebPEncode()");
#endif
return 0;
}
sp->sPicture.custom_ptr = NULL;
if (!TIFFFlushData1(tif))
{
TIFFErrorExt(tif->tif_clientdata, module,
"Error flushing TIFF WebP encoder.");
return 0;
}
return 1;
}
static void
TWebPCleanup(TIFF* tif)
{
WebPState* sp = LState(tif);
assert(sp != 0);
tif->tif_tagmethods.vgetfield = sp->vgetparent;
tif->tif_tagmethods.vsetfield = sp->vsetparent;
if (sp->state & LSTATE_INIT_ENCODE) {
WebPPictureFree(&sp->sPicture);
}
if (sp->psDecoder != NULL) {
WebPIDelete(sp->psDecoder);
WebPFreeDecBuffer(&sp->sDecBuffer);
sp->psDecoder = NULL;
sp->last_y = 0;
}
if (sp->pBuffer != NULL) {
_TIFFfree(sp->pBuffer);
sp->pBuffer = NULL;
}
if (tif->tif_data) {
_TIFFfree(tif->tif_data);
tif->tif_data = NULL;
}
_TIFFSetDefaultCompressionState(tif);
}
static int
TWebPVSetField(TIFF* tif, uint32 tag, va_list ap)
{
static const char module[] = "WebPVSetField";
WebPState* sp = LState(tif);
switch (tag) {
case TIFFTAG_WEBP_LEVEL:
sp->quality_level = (int) va_arg(ap, int);
if( sp->quality_level <= 0 ||
sp->quality_level > 100.0f ) {
TIFFWarningExt(tif->tif_clientdata, module,
"WEBP_LEVEL should be between 1 and 100");
}
return 1;
case TIFFTAG_WEBP_LOSSLESS:
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
sp->lossless = va_arg(ap, int);
return 1;
#else
TIFFErrorExt(tif->tif_clientdata, module,
"Need to upgrade WEBP driver, this version doesn't support "
"lossless compression.");
return 0;
#endif
default:
return (*sp->vsetparent)(tif, tag, ap);
}
/*NOTREACHED*/
}
static int
TWebPVGetField(TIFF* tif, uint32 tag, va_list ap)
{
WebPState* sp = LState(tif);
switch (tag) {
case TIFFTAG_WEBP_LEVEL:
*va_arg(ap, int*) = sp->quality_level;
break;
case TIFFTAG_WEBP_LOSSLESS:
*va_arg(ap, int*) = sp->lossless;
break;
default:
return (*sp->vgetparent)(tif, tag, ap);
}
return 1;
}
static const TIFFField TWebPFields[] = {
{ TIFFTAG_WEBP_LEVEL, 0, 0, TIFF_ANY, 0, TIFF_SETGET_INT,
TIFF_SETGET_UNDEFINED,
FIELD_PSEUDO, TRUE, FALSE, "WEBP quality", NULL },
{ TIFFTAG_WEBP_LOSSLESS, 0, 0, TIFF_ANY, 0, TIFF_SETGET_INT,
TIFF_SETGET_UNDEFINED,
FIELD_PSEUDO, TRUE, FALSE, "WEBP lossless/lossy", NULL
},
};
int
TIFFInitWebP(TIFF* tif, int scheme)
{
static const char module[] = "TIFFInitWebP";
WebPState* sp;
assert( scheme == COMPRESSION_WEBP );
/*
* Merge codec-specific tag information.
*/
if ( !_TIFFMergeFields(tif, TWebPFields, TIFFArrayCount(TWebPFields)) ) {
TIFFErrorExt(tif->tif_clientdata, module,
"Merging WebP codec-specific tags failed");
return 0;
}
/*
* Allocate state block so tag methods have storage to record values.
*/
tif->tif_data = (uint8*) _TIFFmalloc(sizeof(WebPState));
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 = TWebPVGetField; /* hook for codec tags */
sp->vsetparent = tif->tif_tagmethods.vsetfield;
tif->tif_tagmethods.vsetfield = TWebPVSetField; /* hook for codec tags */
/* Default values for codec-specific fields */
sp->quality_level = 75.0f; /* default comp. level */
sp->lossless = 0; /* default to false */
sp->state = 0;
sp->nSamples = 0;
sp->psDecoder = NULL;
sp->last_y = 0;
sp->buffer_offset = 0;
sp->pBuffer = NULL;
/*
* Install codec methods.
* Notes:
* encoderow is not supported
*/
tif->tif_fixuptags = TWebPFixupTags;
tif->tif_setupdecode = TWebPSetupDecode;
tif->tif_predecode = TWebPPreDecode;
tif->tif_decoderow = TWebPDecode;
tif->tif_decodestrip = TWebPDecode;
tif->tif_decodetile = TWebPDecode;
tif->tif_setupencode = TWebPSetupEncode;
tif->tif_preencode = TWebPPreEncode;
tif->tif_postencode = TWebPPostEncode;
tif->tif_encoderow = TWebPEncode;
tif->tif_encodestrip = TWebPEncode;
tif->tif_encodetile = TWebPEncode;
tif->tif_cleanup = TWebPCleanup;
return 1;
bad:
TIFFErrorExt(tif->tif_clientdata, module,
"No space for WebP state block");
return 0;
}
#endif /* WEBP_SUPPORT */