/* * Copyright (c) 2018, Mapbox * 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 WEBP_SUPPORT /* * TIFF Library. * * WEBP Compression Support * */ #include "webp/decode.h" #include "webp/encode.h" #include #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, ¤t_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 */