#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_PIXELS_SOURCE 1 #include "WebPBitmapCodec.h" #if defined(NUCLEX_PIXELS_HAVE_LIBWEBP) #include "Nuclex/Pixels/Storage/VirtualFile.h" #include "Nuclex/Pixels/Errors/FileFormatError.h" #include "LibWebPHelpers.h" #include // for std::min() #include // for std::vector // https://developers.google.com/speed/webp/docs/api namespace { // ------------------------------------------------------------------------------------------- // // ------------------------------------------------------------------------------------------- // } // anonymous namespace namespace Nuclex { namespace Pixels { namespace Storage { namespace WebP { // ------------------------------------------------------------------------------------------- // WebPBitmapCodec::WebPBitmapCodec() : name(u8"Google WebP (.webp) via libwebp") { this->knownFileExtensions.push_back(u8"webp"); int result = ::WebPInitDecoderConfig(&this->decoderConfiguration); if(result == 0) { // Literally returns 0, but only on version mismatch or null pointer input throw std::runtime_error(u8"WebP decoder configuration failed to initialize"); } } // ------------------------------------------------------------------------------------------- // std::optional WebPBitmapCodec::TryReadInfo( const VirtualFile &source, const std::string &extensionHint /* = std::string() */ ) const { (void)extensionHint; // Unused // If this file is too small to be a valid .webp image, report that we can't load it std::uint64_t fileSize = source.GetSize(); if(fileSize < SmallestPossibleWebPSize) { return std::optional(); } // If the file header is not indicative of a .webp image, bail out { std::uint8_t fileHeader[16]; source.ReadAt(0, 16, fileHeader); if(!Helpers::IsValidWebPHeader(fileHeader)) { return std::optional(); } } // We don't know how much data we need to buffer. The RIFF header at the beginning // of a .webp is optional and a VP8X header with lots of "optional" chunks may // follow or the VP8 / VP8L headers may follow, but it may be omitted, too, // so reading a few length markers here and there isn't enough. // // Instead, we'll start out with a guess that should cover any sane .webp image // but if somehow a file should have bloated its header even further, we'll retrieve // more data and try again. // std::uint8_t buffer[256]; std::size_t bytesToRead; { if(fileSize < 256) { bytesToRead = static_cast(fileSize); } else { bytesToRead = 256; } } source.ReadAt(0, bytesToRead, buffer); ::WebPBitstreamFeatures features; ::VP8StatusCode result = ::WebPGetFeatures(buffer, bytesToRead, &features); if(unlikely(result != VP8_STATUS_OK)) { if(likely(result == VP8_STATUS_NOT_ENOUGH_DATA)) { if(fileSize > bytesToRead) { // TODO: Try another read with more data } } std::string message; message.assign(u8"Failed decoding .webp bit stream features. ", 43); message.append(Helpers::GetErrorMessage(result)); throw Errors::FileFormatError(message); } BitmapInfo bitmapInfo; bitmapInfo.Width = static_cast(features.width); bitmapInfo.Height = static_cast(features.height); if(features.has_alpha) { bitmapInfo.MemoryUsage = 4 * bitmapInfo.Width * bitmapInfo.Height; bitmapInfo.PixelFormat = PixelFormat::A8_B8_G8_R8_Unsigned; // CHECK: Is this the one? } else { bitmapInfo.MemoryUsage = 3 * bitmapInfo.Width * bitmapInfo.Height; bitmapInfo.PixelFormat = PixelFormat::B8_G8_R8_Unsigned; // CHECK: Is this the one? } return bitmapInfo; } // ------------------------------------------------------------------------------------------- // bool WebPBitmapCodec::CanLoad( const VirtualFile &source, const std::string &extensionHint /* = std::string() */ ) const { // If a file extension is offered, do an early exit if it doesn't match. // Should the codec be used through the BitmapSerializer (which is very likely // always the case), the extension will either match or be missing. bool mightBeWebP; { std::size_t hintLength = extensionHint.length(); if(hintLength == 4) { // extension without dot possible mightBeWebP = ( ((extensionHint[0] == 'w') || (extensionHint[0] == 'W')) && ((extensionHint[1] == 'e') || (extensionHint[1] == 'E')) && ((extensionHint[2] == 'b') || (extensionHint[2] == 'B')) && ((extensionHint[3] == 'p') || (extensionHint[3] == 'P')) ); } else if(hintLength == 5) { // extension with dot possible mightBeWebP = ( (extensionHint[0] == '.') && ((extensionHint[1] == 'w') || (extensionHint[1] == 'W')) && ((extensionHint[2] == 'e') || (extensionHint[2] == 'E')) && ((extensionHint[3] == 'b') || (extensionHint[3] == 'B')) && ((extensionHint[4] == 'p') || (extensionHint[4] == 'P')) ); } else if(extensionHint.empty()) { // extension missing mightBeWebP = true; } else { // extension wrong mightBeWebP = false; } } // If the extension indicates a WebP file (or no extension was provided), // check the file header to see if this is really a WebP file if(mightBeWebP) { std::size_t fileLength = source.GetSize(); if(fileLength >= SmallestPossibleWebPSize) { std::uint8_t fileHeader[16]; source.ReadAt(0, 16, fileHeader); return Helpers::IsValidWebPHeader(fileHeader); } else { // file is too short to be a WebP return false; } } else { // wrong file extension return false; } } // ------------------------------------------------------------------------------------------- // bool WebPBitmapCodec::CanSave() const { return false; // Still working on this... } // ------------------------------------------------------------------------------------------- // std::optional WebPBitmapCodec::TryLoad( const VirtualFile &source, const std::string &extensionHint /* = std::string() */ ) const { (void)extensionHint; // Unused // If this file is too small to be a valid .webp image, report that we can't load it std::uint64_t fileSize = source.GetSize(); if(fileSize < SmallestPossibleWebPSize) { return std::optional(); } std::vector buffer(256); std::size_t bytesToRead; { if(fileSize < 256) { bytesToRead = static_cast(fileSize); } else { bytesToRead = 256; } } source.ReadAt(0, bytesToRead, buffer.data()); ::WebPBitstreamFeatures features; ::VP8StatusCode result = ::WebPGetFeatures(buffer.data(), bytesToRead, &features); if(unlikely(result != VP8_STATUS_OK)) { if(likely(result == VP8_STATUS_NOT_ENOUGH_DATA)) { if(fileSize > bytesToRead) { // TODO: Try another read with more data } } std::string message; message.assign(u8"Failed decoding .webp bit stream features. ", 43); message.append(Helpers::GetErrorMessage(result)); throw Errors::FileFormatError(message); } return Bitmap( static_cast(features.width), static_cast(features.height), PixelFormat::R8_G8_B8_A8_Unsigned ); } // ------------------------------------------------------------------------------------------- // bool WebPBitmapCodec::TryReload( Bitmap &exactlyFittingBitmap, const VirtualFile &source, const std::string &extensionHint /* = std::string() */ ) const { (void)exactlyFittingBitmap; (void)source; (void)extensionHint; throw std::runtime_error(u8"Not implemented yet"); } // ------------------------------------------------------------------------------------------- // void WebPBitmapCodec::Save( const Bitmap &bitmap, VirtualFile &target, float compressionEffortHint /* = 0.75f */, float outputQualityHint /* = 0.95f */ ) const { (void)bitmap; (void)target; (void)compressionEffortHint; (void)outputQualityHint; throw std::runtime_error(u8"Not implemented yet"); } // ------------------------------------------------------------------------------------------- // }}}} // namespace Nuclex::Pixels::Storage::WebP #endif //defined(NUCLEX_PIXELS_HAVE_LIBWEBP)