LCOV - code coverage report
Current view: top level - core/bitmap - SPBitmapWebp.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 141 172 82.0 %
Date: 2024-05-12 00:16:13 Functions: 18 18 100.0 %

          Line data    Source code
       1             : /**
       2             : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
       3             : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
       4             : 
       5             : Permission is hereby granted, free of charge, to any person obtaining a copy
       6             : of this software and associated documentation files (the "Software"), to deal
       7             : in the Software without restriction, including without limitation the rights
       8             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       9             : copies of the Software, and to permit persons to whom the Software is
      10             : furnished to do so, subject to the following conditions:
      11             : 
      12             : The above copyright notice and this permission notice shall be included in
      13             : all copies or substantial portions of the Software.
      14             : 
      15             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      16             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      17             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      18             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      19             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      20             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      21             : THE SOFTWARE.
      22             : **/
      23             : 
      24             : #include "SPBitmapFormat.h"
      25             : #include "SPLog.h"
      26             : #include "SPFilesystem.h"
      27             : #include "webp/decode.h"
      28             : #include "webp/encode.h"
      29             : 
      30             : namespace STAPPLER_VERSIONIZED stappler::bitmap::webp {
      31             : 
      32        4250 : static bool isWebpLossless(const uint8_t * data, size_t dataLen) {
      33        4250 :         if (dataLen <= 12) {
      34           0 :                 return false;
      35             :         }
      36             : 
      37             :         static const char* WEBP_RIFF = "RIFF";
      38             :         static const char* WEBP_WEBP = "WEBPVP8L";
      39             : 
      40        4250 :         return memcmp(data, WEBP_RIFF, 4) == 0
      41        4250 :                         && memcmp(static_cast<const unsigned char*>(data) + 8, WEBP_WEBP, 8) == 0;
      42             : }
      43             : 
      44         950 : static bool getWebpLosslessImageSize(const io::Producer &file, StackBuffer<512> &data, uint32_t &width, uint32_t &height) {
      45         950 :         if (isWebpLossless(data.data(), data.size())) {
      46          75 :                 auto reader = BytesViewTemplate<Endian::Big>(data.data() + 21, 4);
      47             : 
      48          75 :                 auto b0 = reader.readUnsigned();
      49          75 :                 auto b1 = reader.readUnsigned();
      50          75 :                 auto b2 = reader.readUnsigned();
      51          75 :                 auto b3 = reader.readUnsigned();
      52             : 
      53             :                 // first 14 bits - width, last 14 bits - height
      54             : 
      55          75 :                 width = (b0 | ((b1 & 0x3F) << 8)) + 1;
      56          75 :                 height = (((b3 & 0xF) << 10) | (b2 << 2) | ((b1 & 0xC0) >> 6)) + 1;
      57             : 
      58          75 :                 return true;
      59             :         }
      60         875 :         return false;
      61             : }
      62             : 
      63        3900 : static bool isWebp(const uint8_t * data, size_t dataLen) {
      64        3900 :         if (dataLen <= 12) {
      65           0 :                 return false;
      66             :         }
      67             : 
      68             :         static const char* WEBP_RIFF = "RIFF";
      69             :         static const char* WEBP_WEBP = "WEBP";
      70             : 
      71        3900 :         return memcmp(data, WEBP_RIFF, 4) == 0
      72        3900 :                 && memcmp(static_cast<const unsigned char*>(data) + 8, WEBP_WEBP, 4) == 0;
      73             : }
      74             : 
      75         875 : static bool getWebpImageSize(const io::Producer &file, StackBuffer<512> &data, uint32_t &width, uint32_t &height) {
      76         875 :         if (isWebp(data.data(), data.size())) {
      77          75 :                 auto reader = BytesViewTemplate<Endian::Little>(data.data() + 24, 6);
      78             : 
      79          75 :                 auto b0 = reader.readUnsigned();
      80          75 :                 auto b1 = reader.readUnsigned();
      81          75 :                 auto b2 = reader.readUnsigned();
      82          75 :                 auto b3 = reader.readUnsigned();
      83          75 :                 auto b4 = reader.readUnsigned();
      84          75 :                 auto b5 = reader.readUnsigned();
      85             : 
      86             :                 // first 14 bits - width, last 14 bits - height
      87             : 
      88          75 :                 width = (b0 | (b1 << 8) | (b2 << 8)) + 1;
      89          75 :                 height = (b3 | (b4 << 8) | (b5 << 8)) + 1;
      90             : 
      91          75 :                 return true;
      92             :         }
      93         800 :         return false;
      94             : }
      95             : 
      96         150 : static bool infoWebp(WebPDecoderConfig *config, const uint8_t *inputData, size_t size, ImageInfo &outputData) {
      97         150 :         if (WebPInitDecoderConfig(config) == 0) return false;
      98         150 :         if (WebPGetFeatures(inputData, size, &config->input) != VP8_STATUS_OK) return false;
      99         150 :         if (config->input.width == 0 || config->input.height == 0) return false;
     100             : 
     101         150 :         outputData.color = config->input.has_alpha ? PixelFormat::RGBA8888 : PixelFormat::RGB888;
     102         150 :         outputData.width = config->input.width;
     103         150 :         outputData.height = config->input.height;
     104             : 
     105         150 :         outputData.alpha = (config->input.has_alpha != 0) ? AlphaFormat::Unpremultiplied : AlphaFormat::Opaque;
     106         150 :         outputData.stride = (uint32_t)outputData.width * getBytesPerPixel(outputData.color);
     107         150 :         return true;
     108             : }
     109             : 
     110          50 : static bool infoWebp(const uint8_t *inputData, size_t size, ImageInfo &outputData) {
     111             :         WebPDecoderConfig config;
     112         100 :         return infoWebp(&config, inputData, size, outputData);
     113             : }
     114             : 
     115         100 : static bool loadWebp(const uint8_t *inputData, size_t size, BitmapWriter &outputData) {
     116             :         WebPDecoderConfig config;
     117         100 :         if (!infoWebp(&config, inputData, size, outputData)) {
     118           0 :                 return false;
     119             :         }
     120             : 
     121         100 :         if (outputData.getStride) {
     122          50 :                 outputData.stride = max((uint32_t)outputData.getStride(outputData.target, outputData.color, outputData.width),
     123         100 :                                 (uint32_t)outputData.width * getBytesPerPixel(outputData.color));
     124             :         }
     125             : 
     126         100 :         outputData.resize(outputData.target, outputData.stride * outputData.height);
     127             : 
     128         100 :         config.output.colorspace = config.input.has_alpha ? MODE_RGBA : MODE_RGB;
     129         100 :         config.output.u.RGBA.rgba = outputData.getData(outputData.target, 0);
     130         100 :         config.output.u.RGBA.stride = outputData.stride;
     131         100 :         config.output.u.RGBA.size = outputData.stride * outputData.height;
     132         100 :         config.output.is_external_memory = 1;
     133             : 
     134         100 :         if (WebPDecode(inputData, size, &config) != VP8_STATUS_OK) {
     135           0 :                 outputData.clear(outputData.target);
     136           0 :                 return false;
     137             :         }
     138             : 
     139         100 :         return true;
     140             : }
     141             : 
     142             : struct WebpStruct {
     143         450 :         static bool isWebpSupported(PixelFormat format) {
     144         450 :                 switch (format) {
     145           0 :                 case PixelFormat::A8:
     146             :                 case PixelFormat::I8:
     147             :                 case PixelFormat::IA88:
     148             :                 case PixelFormat::Auto:
     149           0 :                         log::error("Bitmap", "Webp supports only RGB888 and RGBA8888");
     150           0 :                         return false;
     151         450 :                 default:
     152         450 :                         break;
     153             :                 }
     154         450 :                 return true;
     155             :         }
     156             : 
     157        1223 :         static int FileWriter(const uint8_t* data, size_t data_size, const WebPPicture* const pic) {
     158        1223 :                 FILE* const out = (FILE *)pic->custom_ptr;
     159        1223 :                 return data_size ? (fwrite(data, data_size, 1, out) == 1) : 1;
     160             :         }
     161             : 
     162             :         FILE *fp = nullptr;
     163             :         BitmapWriter *out = nullptr;
     164             : 
     165             :         WebPConfig config;
     166             :         WebPPicture pic;
     167             :         WebPMemoryWriter writer;
     168             : 
     169             :         bool pictureInit = false;
     170             :         bool memoryInit = false;
     171             :         bool valid = true;
     172             : 
     173         450 :         ~WebpStruct() {
     174         450 :                 if (pictureInit) {
     175         450 :                         WebPPictureFree(&pic);
     176         450 :                         pictureInit = false;
     177             :                 }
     178             : 
     179         450 :                 if (memoryInit) {
     180         150 :                         WebPMemoryWriterClear(&writer);
     181         150 :                         memoryInit = false;
     182             :                 }
     183             : 
     184         450 :                 if (fp) {
     185         300 :                 fclose(fp);
     186             :                 }
     187         450 :         }
     188             : 
     189         450 :         WebpStruct(bool lossless) {
     190         450 :                 if (!WebPPictureInit(&pic)) {
     191           0 :                         valid = false;
     192             :                 }
     193             : 
     194         450 :                 if (lossless) {
     195         225 :                         if (!WebPConfigPreset(&config, WEBP_PRESET_ICON, 100.0f)) {
     196           0 :                                 valid = false;
     197             :                         }
     198             : 
     199         225 :                         config.lossless = 1;
     200         225 :                         config.method = 6;
     201             :                 } else {
     202         225 :                         if (!WebPConfigPreset(&config, WEBP_PRESET_PICTURE, 90.0f)) {
     203           0 :                                 valid = false;
     204             :                         }
     205             : 
     206         225 :                         config.lossless = 0;
     207         225 :                         config.method = 6;
     208             :                 }
     209             : 
     210         450 :                 if (!WebPValidateConfig(&config)) {
     211           0 :                         valid = false;
     212             :                 }
     213         450 :         }
     214             : 
     215         150 :         WebpStruct(BitmapWriter *v, bool lossless) : WebpStruct(lossless) {
     216         150 :                 out = v;
     217         150 :         }
     218             : 
     219         300 :         WebpStruct(const StringView &filename, bool lossless) : WebpStruct(lossless) {
     220         300 :             fp = filesystem::native::fopen_fn(filename, "wb");
     221         300 :             if (!fp) {
     222           0 :                         log::format(log::Error, "Bitmap", "fail to open file '%s' to write png data", filename.data());
     223           0 :                         valid = false;
     224           0 :                         return;
     225             :             }
     226           0 :         }
     227             : 
     228             :         explicit operator bool () const {
     229             :                 return valid;
     230             :         }
     231             : 
     232         450 :         bool write(const uint8_t *data, BitmapWriter &state) {
     233         450 :                 if (!valid) {
     234           0 :                         return false;
     235             :                 }
     236             : 
     237         450 :                 if (!fp && !out) {
     238           0 :                         return false;
     239             :                 }
     240             : 
     241             :                 WebPPicture pic;
     242         450 :                 WebPPictureInit(&pic);
     243             : 
     244         450 :                 pic.use_argb = 1;
     245         450 :                 pic.width = state.width;
     246         450 :                 pic.height = state.height;
     247             : 
     248         450 :                 if (state.stride == 0) {
     249           0 :                         state.stride = getBytesPerPixel(state.color) * state.width;
     250             :                 }
     251             : 
     252         450 :                 switch (state.color) {
     253           0 :                 case PixelFormat::A8:
     254             :                 case PixelFormat::I8:
     255             :                 case PixelFormat::IA88:
     256             :                 case PixelFormat::Auto:
     257           0 :                         return false;
     258         150 :                 case PixelFormat::RGB888:
     259         150 :                         WebPPictureImportRGB(&pic, data, state.stride);
     260             : 
     261         150 :                         break;
     262         300 :                 case PixelFormat::RGBA8888:
     263         300 :                         WebPPictureImportRGBA(&pic, data, state.stride);
     264         300 :                         break;
     265             :                 }
     266         450 :                 pictureInit = true;
     267             : 
     268         450 :                 if (fp) {
     269         300 :                         pic.writer = FileWriter;
     270         300 :                         pic.custom_ptr = fp;
     271             :                 } else {
     272         150 :                         WebPMemoryWriterInit(&writer);
     273         150 :                         pic.writer = WebPMemoryWrite;
     274         150 :                         pic.custom_ptr = &writer;
     275         150 :                         memoryInit = true;
     276             :                 }
     277             : 
     278         450 :                 if (!WebPEncode(&config, &pic)) {
     279           0 :                         return false;
     280             :                 }
     281             : 
     282         450 :                 if (out) {
     283         150 :                         out->resize(out->target, writer.size);
     284         150 :                         memcpy(out->getData(out->target, 0), writer.mem, writer.size);
     285             :                 }
     286             : 
     287         450 :             return true;
     288             :         }
     289             : };
     290             : 
     291         150 : static bool saveWebpLossless(StringView filename, const uint8_t *data, BitmapWriter &state, bool invert) {
     292         150 :         if (!WebpStruct::isWebpSupported(state.color)) {
     293           0 :                 return false;
     294             :         }
     295         150 :         if (invert) {
     296           0 :                 log::error("Bitmap", "Inverted output is not supported for webp");
     297           0 :                 return false;
     298             :         }
     299             : 
     300         150 :         WebpStruct coder(filename, true);
     301         150 :         return coder.write(data, state);
     302         150 : }
     303             : 
     304          75 : static bool writeWebpLossless(const uint8_t *data, BitmapWriter &state, bool invert) {
     305          75 :         if (!WebpStruct::isWebpSupported(state.color) || invert) {
     306           0 :                 if (invert) { log::error("Bitmap", "Inverted output is not supported for webp"); }
     307           0 :                 return false;
     308             :         }
     309             : 
     310          75 :         WebpStruct coder(&state, true);
     311          75 :         return coder.write(data, state);
     312          75 : }
     313             : 
     314         150 : static bool saveWebpLossy(StringView filename, const uint8_t *data, BitmapWriter &state, bool invert) {
     315         150 :         if (!WebpStruct::isWebpSupported(state.color) || invert) {
     316           0 :                 if (invert) { log::error("Bitmap", "Inverted output is not supported for webp"); }
     317           0 :                 return false;
     318             :         }
     319             : 
     320         150 :         WebpStruct coder(filename, false);
     321         150 :         return coder.write(data, state);
     322         150 : }
     323             : 
     324          75 : static bool writeWebpLossy(const uint8_t *data, BitmapWriter &state, bool invert) {
     325          75 :         if (!WebpStruct::isWebpSupported(state.color) || invert) {
     326           0 :                 if (invert) { log::error("Bitmap", "Inverted output is not supported for webp"); }
     327           0 :                 return false;
     328             :         }
     329             : 
     330          75 :         WebpStruct coder(&state, false);
     331          75 :         return coder.write(data, state);
     332          75 : }
     333             : 
     334             : }

Generated by: LCOV version 1.14