imagecore/formats/internal/webp.cpp (339 lines of code) (raw):
/*
* MIT License
*
* Copyright (c) 2017 Twitter
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "webp.h"
#include "imagecore/utils/mathutils.h"
#include "imagecore/utils/securemath.h"
#include "imagecore/image/rgba.h"
#include "imagecore/image/yuv.h"
#include <string.h>
namespace imagecore {
REGISTER_IMAGE_READER(ImageReaderWebP);
REGISTER_IMAGE_WRITER(ImageWriterWebP);
bool ImageReaderWebP::Factory::matchesSignature(const uint8_t* sig, unsigned int sigLen)
{
if( sigLen >= 4 && sig[0] == 0x52 && sig[1] == 0x49 && sig[2] == 0x46 && sig[3] == 0x46 ) {
return true;
}
return false;
}
ImageReaderWebP::ImageReaderWebP()
: m_Source(NULL)
, m_Width(0)
, m_Height(0)
, m_TotalRowsRead(0)
, m_NativeColorModel(kColorModel_RGBX)
, m_DecodeBuffer(NULL)
, m_DecodeLength(0)
, m_OwnDecodeBuffer(false)
{
}
ImageReaderWebP::~ImageReaderWebP()
{
if( m_OwnDecodeBuffer ) {
free(m_DecodeBuffer);
}
}
bool ImageReaderWebP::initWithStorage(Storage* source)
{
m_Source = source;
return true;
}
bool ImageReaderWebP::readHeader()
{
if( WebPInitDecoderConfig(&m_DecoderConfig) ) {
if( m_Source->asBuffer(m_DecodeBuffer, m_DecodeLength) ) {
m_OwnDecodeBuffer = false;
} else {
// WebP requires having the entire image in memory, so if the source was unable to provide a buffer, read it in.
ImageWriter::MemoryStorage tempStorage;
tempStorage.writeStream(m_Source);
if( tempStorage.ownBuffer(m_DecodeBuffer, m_DecodeLength) ) {
m_OwnDecodeBuffer = true;
}
}
if( WebPGetFeatures(m_DecodeBuffer, (size_t)m_DecodeLength, &m_Features) == VP8_STATUS_OK ) {
m_Width = m_Features.width > 0 ? m_Features.width : 0;
m_Height = m_Features.height > 0 ? m_Features.height : 0;
m_NativeColorModel = m_Features.has_alpha ? kColorModel_RGBA : kColorModel_YUV_420;
return true;
}
}
return false;
}
void ImageReaderWebP::computeReadDimensions(unsigned int desiredWidth, unsigned int desiredHeight, unsigned int& readWidth, unsigned int& readHeight)
{
readWidth = m_Width;
readHeight = m_Height;
unsigned int reduceCount = 0;
while( div2_round(readWidth) >= desiredWidth && div2_round(readHeight) >= desiredHeight && reduceCount < 2 ) {
readWidth = div2_round(readWidth);
readHeight = div2_round(readHeight);
reduceCount++;
}
}
bool ImageReaderWebP::beginRead(unsigned int outputWidth, unsigned int outputHeight, EImageColorModel outputColorModel)
{
return false;
}
unsigned int ImageReaderWebP::readRows(Image* dest, unsigned int destRow, unsigned int numRows)
{
return false;
}
bool ImageReaderWebP::endRead()
{
return false;
}
bool ImageReaderWebP::readImage(Image* destImage)
{
if( !supportsOutputColorModel(destImage->getColorModel()) ) {
return false;
}
unsigned int destWidth = destImage->getWidth();
unsigned int destHeight = destImage->getHeight();
m_DecoderConfig.output.width = destWidth;
m_DecoderConfig.output.height = destHeight;
m_DecoderConfig.output.is_external_memory = true;
m_DecoderConfig.options.use_threads = 0;
m_DecoderConfig.options.no_fancy_upsampling = 0;
if( destWidth != m_Width || destHeight != m_Height ) {
m_DecoderConfig.options.use_scaling = 1;
m_DecoderConfig.options.scaled_width = destWidth;
m_DecoderConfig.options.scaled_height = destHeight;
}
EImageColorModel colorModel = destImage->getColorModel();
bool success = false;
if( Image::colorModelIsRGBA(colorModel)) {
ImageInterleaved* image = destImage->asInterleaved();
unsigned int destPitch = 0;
uint8_t* destBuffer = image->lockRect(destWidth, destHeight, destPitch);
SECURE_ASSERT(SafeUMul(destWidth, image->getComponentSize()) <= destPitch);
m_DecoderConfig.output.colorspace = MODE_RGBA;
m_DecoderConfig.output.u.RGBA.rgba = destBuffer;
m_DecoderConfig.output.u.RGBA.size = image->getImageSize();
m_DecoderConfig.output.u.RGBA.stride = destPitch;
START_CLOCK(decodeRGB);
if( WebPDecode(m_DecodeBuffer, (size_t)m_DecodeLength, &m_DecoderConfig) == VP8_STATUS_OK ) {
success = true;
}
END_CLOCK(decodeRGB);
} else if( Image::colorModelIsYUV(colorModel) ) {
ImageYUV* image = destImage->asYUV();
ImagePlane8* planeY = image->getPlaneY();
ImagePlane8* planeU = image->getPlaneU();
ImagePlane8* planeV = image->getPlaneV();
EYUVRange desiredRange = image->getRange();
unsigned int pitchY = 0;
uint8_t* bufferY = planeY->lockRect(0, 0, planeY->getWidth(), planeY->getHeight(), pitchY);
unsigned int pitchU = 0;
uint8_t* bufferU = planeU->lockRect(0, 0, planeU->getWidth(), planeU->getHeight(), pitchU);
unsigned int pitchV = 0;
uint8_t* bufferV = planeV->lockRect(0, 0, planeV->getWidth(), planeV->getHeight(), pitchV);
m_DecoderConfig.output.colorspace = MODE_YUV;
m_DecoderConfig.output.u.YUVA.y = bufferY;
m_DecoderConfig.output.u.YUVA.y_size = planeY->getImageSize();
m_DecoderConfig.output.u.YUVA.y_stride = pitchY;
m_DecoderConfig.output.u.YUVA.u = bufferU;
m_DecoderConfig.output.u.YUVA.u_size = planeU->getImageSize();
m_DecoderConfig.output.u.YUVA.u_stride = pitchU;
m_DecoderConfig.output.u.YUVA.v = bufferV;
m_DecoderConfig.output.u.YUVA.v_size = planeV->getImageSize();
m_DecoderConfig.output.u.YUVA.v_stride = pitchV;
START_CLOCK(decodeYUV);
if( WebPDecode(m_DecodeBuffer, (size_t)m_DecodeLength, &m_DecoderConfig) == VP8_STATUS_OK ) {
success = true;
}
END_CLOCK(decodeYUV);
START_CLOCK(remap);
image->setRange(kYUVRange_Compressed);
if( desiredRange == kYUVRange_Full ) {
image->expandRange(image);
}
END_CLOCK(remap);
}
m_TotalRowsRead += m_Height;
return success;
}
EImageFormat ImageReaderWebP::getFormat()
{
return kImageFormat_WebP;
}
const char* ImageReaderWebP::getFormatName()
{
return "WebP";
}
unsigned int ImageReaderWebP::getWidth()
{
return m_Width;
}
unsigned int ImageReaderWebP::getHeight()
{
return m_Height;
}
EImageColorModel ImageReaderWebP::getNativeColorModel()
{
return m_NativeColorModel;
}
bool ImageReaderWebP::supportsOutputColorModel(EImageColorModel colorModel)
{
return Image::colorModelIsRGBA(colorModel) || colorModel == m_NativeColorModel;
}
//////
bool ImageWriterWebP::Factory::matchesExtension(const char *extension)
{
return strcasecmp(extension, "webp") == 0;
}
EImageFormat ImageWriterWebP::Factory::getFormat()
{
return kImageFormat_WebP;
}
bool ImageWriterWebP::Factory::appropriateForInputFormat(EImageFormat inputFormat)
{
return true;
}
bool ImageWriterWebP::Factory::supportsInputColorModel(EImageColorModel colorModel)
{
return Image::colorModelIsRGBA(colorModel) || Image::colorModelIsYUV(colorModel);
}
ImageWriterWebP::ImageWriterWebP()
: m_SourceReader(NULL)
, m_OutputStorage(NULL)
{
WebPConfigPreset(&m_Config, WEBP_PRESET_PHOTO, 80.0f);
}
ImageWriterWebP::~ImageWriterWebP()
{
}
bool ImageWriterWebP::initWithStorage(ImageWriter::Storage* output)
{
m_OutputStorage = output;
return true;
}
void ImageWriterWebP::setSourceReader(ImageReader* hintReader)
{
m_SourceReader = hintReader;
}
void ImageWriterWebP::setQuality(unsigned int quality)
{
m_Config.quality = (float)quality;
}
bool ImageWriterWebP::applyExtraOptions(const char** optionNames, const char** optionValues, unsigned int numOptions)
{
for( unsigned int i = 0; i < numOptions; i++ ) {
if( strcasecmp(optionNames[i], "filter_strength") == 0) {
m_Config.filter_strength = atoi(optionValues[i]);
} else if( strcasecmp(optionNames[i], "filter_sharpness") == 0) {
m_Config.filter_sharpness = atoi(optionValues[i]);
} else if( strcasecmp(optionNames[i], "filter_type") == 0) {
m_Config.filter_type = atoi(optionValues[i]);
} else if( strcasecmp(optionNames[i], "method") == 0) {
m_Config.method = atoi(optionValues[i]);
} else if( strcasecmp(optionNames[i], "sns_strength") == 0) {
m_Config.sns_strength = atoi(optionValues[i]);
} else if( strcasecmp(optionNames[i], "preprocessing") == 0) {
m_Config.preprocessing = atoi(optionValues[i]);
} else if( strcasecmp(optionNames[i], "segments") == 0) {
m_Config.segments = atoi(optionValues[i]);
} else if( strcasecmp(optionNames[i], "partitions") == 0) {
m_Config.partitions = atoi(optionValues[i]);
} else if( strcasecmp(optionNames[i], "target_size") == 0) {
m_Config.target_size = atoi(optionValues[i]);
} else {
return false;
}
}
return true;
}
bool ImageWriterWebP::beginWrite(unsigned int width, unsigned int height, EImageColorModel colorModel)
{
return false;
}
unsigned int ImageWriterWebP::writeRows(Image* source, unsigned int sourceRow, unsigned int numRows)
{
return false;
}
bool ImageWriterWebP::endWrite()
{
return false;
}
int webpWrite(const uint8_t* data, size_t size, const WebPPicture* picture)
{
ImageWriter::Storage* storage = (ImageWriter::Storage*)picture->custom_ptr;
return storage->write(data, size) == size;
}
bool ImageWriterWebP::writeImage(Image* sourceImage)
{
if( !Image::colorModelIsRGBA(sourceImage->getColorModel()) && !Image::colorModelIsYUV(sourceImage->getColorModel()) ) {
return false;
}
if( !WebPValidateConfig(&m_Config) ) {
return false;
}
WebPPicture pic;
if( !WebPPictureInit(&pic) ) {
return false;
}
pic.width = sourceImage->getWidth();
pic.height = sourceImage->getHeight();
bool success = false;
if( Image::colorModelIsRGBA(sourceImage->getColorModel()) ) {
ImageRGBA* image = sourceImage->asRGBA();
unsigned int pitch = image->getPitch();
uint32_t* imageRGBA = (uint32_t*)image->getBytes();
uint32_t* swapBufferARGB = (uint32_t*)malloc(image->getImageSize());
if( swapBufferARGB != NULL ) {
// Swap RGBA -> BGRA.
for( unsigned int y = 0; y < pic.height; y++ ) {
for( unsigned int x = 0; x < pic.width; x++ ) {
unsigned int offset = (y * pitch / 4) + x;
uint32_t rgba = imageRGBA[offset];
#if __BYTE_ORDER == __LITTLE_ENDIAN
uint32_t argb = ((rgba & 0xFF) << 16) | ((rgba >> 16) & 0xFF) | (rgba & 0xFF00FF00);
#else
uint32_t argb = (((rgba >> 24) & 0xFF) << 8) | (((rgba >> 8) & 0xFF) << 24) | (rgba & 0x00FF00FF);
#endif
swapBufferARGB[offset] = argb;
}
}
if( WebPPictureAlloc(&pic) ) {
pic.writer = &webpWrite;
pic.custom_ptr = m_OutputStorage;
pic.use_argb = 1;
pic.argb = (uint32_t*)swapBufferARGB;
pic.argb_stride = image->getPitch() / image->getComponentSize();
success = WebPEncode(&m_Config, &pic);
WebPPictureFree(&pic);
}
free(swapBufferARGB);
}
} else if( Image::colorModelIsYUV(sourceImage->getColorModel()) ) {
ImageYUV* yuvImage = sourceImage->asYUV();
ImageYUV* image = yuvImage;
ImageYUV* tempImage = NULL;
if( yuvImage->getRange() == kYUVRange_Full ) {
tempImage = ImageYUV::create(yuvImage->getWidth(), yuvImage->getHeight(), yuvImage->getPadding(), 16);
if( tempImage == NULL ) {
return 0;
}
START_CLOCK(remap);
yuvImage->compressRange(tempImage);
END_CLOCK(remap);
image = tempImage;
}
if( (image->getHeight() & 1) == 1 ) {
ImagePlane8* uPlane = image->getPlaneU();
ImagePlane8* vPlane = image->getPlaneV();
// The WebP codec seems to read into the UV padding region, make sure it's filled out correctly.
uPlane->copyRect(uPlane, 0, uPlane->getHeight() - 2, 0, uPlane->getHeight() - 1, uPlane->getWidth(), 1);
vPlane->copyRect(vPlane, 0, vPlane->getHeight() - 2, 0, vPlane->getHeight() - 1, vPlane->getWidth(), 1);
}
if( WebPPictureAlloc(&pic) ) {
pic.writer = &webpWrite;
pic.custom_ptr = m_OutputStorage;
pic.use_argb = 0;
pic.y = (uint8_t*)image->getPlaneY()->getBytes();
pic.u = (uint8_t*)image->getPlaneU()->getBytes();
pic.v = (uint8_t*)image->getPlaneV()->getBytes();
pic.y_stride = image->getPlaneY()->getPitch();
pic.uv_stride = image->getPlaneU()->getPitch();
success = WebPEncode(&m_Config, &pic);
WebPPictureFree(&pic);
}
delete tempImage;
}
return success;
}
}