libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

benchmark_codec_webp.cc (11978B)


      1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
      2 //
      3 // Use of this source code is governed by a BSD-style
      4 // license that can be found in the LICENSE file.
      5 #include "tools/benchmark/benchmark_codec_webp.h"
      6 
      7 #include <jxl/cms.h>
      8 #include <jxl/types.h>
      9 #include <stdint.h>
     10 #include <string.h>
     11 #include <webp/decode.h>
     12 #include <webp/encode.h>
     13 
     14 #include <string>
     15 #include <vector>
     16 
     17 #include "lib/extras/packed_image_convert.h"
     18 #include "lib/extras/time.h"
     19 #include "lib/jxl/base/common.h"
     20 #include "lib/jxl/base/data_parallel.h"
     21 #include "lib/jxl/base/span.h"
     22 #include "lib/jxl/base/status.h"
     23 #include "lib/jxl/dec_external_image.h"
     24 #include "lib/jxl/enc_external_image.h"
     25 #include "lib/jxl/enc_image_bundle.h"
     26 #include "lib/jxl/image_bundle.h"
     27 #include "lib/jxl/image_metadata.h"
     28 #include "lib/jxl/sanitizers.h"
     29 #include "tools/benchmark/benchmark_args.h"
     30 #include "tools/benchmark/benchmark_codec.h"
     31 #include "tools/speed_stats.h"
     32 #include "tools/thread_pool_internal.h"
     33 
     34 namespace jpegxl {
     35 namespace tools {
     36 
     37 using ::jxl::ImageBundle;
     38 using ::jxl::ImageMetadata;
     39 using ::jxl::ThreadPool;
     40 
     41 // Sets image data from 8-bit sRGB pixel array in bytes.
     42 // Amount of input bytes per pixel must be:
     43 // (is_gray ? 1 : 3) + (has_alpha ? 1 : 0)
     44 Status FromSRGB(const size_t xsize, const size_t ysize, const bool is_gray,
     45                 const bool has_alpha, const bool is_16bit,
     46                 const JxlEndianness endianness, const uint8_t* pixels,
     47                 const uint8_t* end, ThreadPool* pool, ImageBundle* ib) {
     48   const ColorEncoding& c = ColorEncoding::SRGB(is_gray);
     49   const size_t bits_per_sample = (is_16bit ? 2 : 1) * jxl::kBitsPerByte;
     50   const uint32_t num_channels = (is_gray ? 1 : 3) + (has_alpha ? 1 : 0);
     51   JxlDataType data_type = is_16bit ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8;
     52   JxlPixelFormat format = {num_channels, data_type, endianness, 0};
     53   const Span<const uint8_t> span(pixels, end - pixels);
     54   return ConvertFromExternal(span, xsize, ysize, c, bits_per_sample, format,
     55                              pool, ib);
     56 }
     57 
     58 struct WebPArgs {
     59   // Empty, no WebP-specific args currently.
     60 };
     61 
     62 static WebPArgs* const webpargs = new WebPArgs;
     63 
     64 Status AddCommandLineOptionsWebPCodec(BenchmarkArgs* args) { return true; }
     65 
     66 class WebPCodec : public ImageCodec {
     67  public:
     68   explicit WebPCodec(const BenchmarkArgs& args) : ImageCodec(args) {}
     69 
     70   Status ParseParam(const std::string& param) override {
     71     // Ensure that the 'q' parameter is not used up by ImageCodec.
     72     if (param[0] == 'q') {
     73       if (near_lossless_) {
     74         near_lossless_quality_ = ParseIntParam(param, 0, 99);
     75       } else {
     76         quality_ = ParseIntParam(param, 1, 100);
     77       }
     78       return true;
     79     } else if (ImageCodec::ParseParam(param)) {
     80       return true;
     81     } else if (param == "ll") {
     82       lossless_ = true;
     83       JXL_CHECK(!near_lossless_);
     84       return true;
     85     } else if (param == "nl") {
     86       near_lossless_ = true;
     87       JXL_CHECK(!lossless_);
     88       return true;
     89     } else if (param[0] == 'm') {
     90       method_ = ParseIntParam(param, 1, 6);
     91       return true;
     92     }
     93     return false;
     94   }
     95 
     96   Status Compress(const std::string& filename, const PackedPixelFile& ppf,
     97                   ThreadPool* pool, std::vector<uint8_t>* compressed,
     98                   jpegxl::tools::SpeedStats* speed_stats) override {
     99     CodecInOut io;
    100     JXL_RETURN_IF_ERROR(
    101         jxl::extras::ConvertPackedPixelFileToCodecInOut(ppf, pool, &io));
    102     return Compress(filename, &io, pool, compressed, speed_stats);
    103   }
    104 
    105   Status Compress(const std::string& filename, const CodecInOut* io,
    106                   ThreadPool* pool, std::vector<uint8_t>* compressed,
    107                   jpegxl::tools::SpeedStats* speed_stats) {
    108     const double start = jxl::Now();
    109     const ImageBundle& ib = io->Main();
    110 
    111     if (ib.HasAlpha() && ib.metadata()->GetAlphaBits() > 8) {
    112       return JXL_FAILURE("WebP alpha must be 8-bit");
    113     }
    114 
    115     size_t num_chans = (ib.HasAlpha() ? 4 : 3);
    116     ImageMetadata metadata = io->metadata.m;
    117     ImageBundle store(&metadata);
    118     const ImageBundle* transformed;
    119     const ColorEncoding& c_desired = ColorEncoding::SRGB(false);
    120     JXL_RETURN_IF_ERROR(jxl::TransformIfNeeded(
    121         ib, c_desired, *JxlGetDefaultCms(), pool, &store, &transformed));
    122     size_t xsize = ib.oriented_xsize();
    123     size_t ysize = ib.oriented_ysize();
    124     size_t stride = xsize * num_chans;
    125     std::vector<uint8_t> srgb(stride * ysize);
    126     JXL_RETURN_IF_ERROR(ConvertToExternal(
    127         *transformed, 8, /*float_out=*/false, num_chans, JXL_BIG_ENDIAN, stride,
    128         pool, srgb.data(), srgb.size(),
    129         /*out_callback=*/{}, metadata.GetOrientation()));
    130 
    131     if (lossless_ || near_lossless_) {
    132       // The lossless codec does not support 16-bit channels.
    133       // Color models are currently not supported here and the sRGB 8-bit
    134       // conversion causes loss due to clipping.
    135       if (!ib.IsSRGB() || ib.metadata()->bit_depth.bits_per_sample > 8 ||
    136           ib.metadata()->bit_depth.exponent_bits_per_sample > 0) {
    137         return JXL_FAILURE("%s: webp:ll/nl requires 8-bit sRGB",
    138                            filename.c_str());
    139       }
    140       JXL_RETURN_IF_ERROR(
    141           CompressInternal(srgb, xsize, ysize, num_chans, 100, compressed));
    142     } else if (bitrate_target_ > 0.0) {
    143       int quality_bad = 100;
    144       int quality_good = 92;
    145       size_t target_size = xsize * ysize * bitrate_target_ / 8.0;
    146       while (quality_good > 0 &&
    147              CompressInternal(srgb, xsize, ysize, num_chans, quality_good,
    148                               compressed) &&
    149              compressed->size() > target_size) {
    150         quality_bad = quality_good;
    151         quality_good -= 8;
    152       }
    153       if (quality_good <= 0) quality_good = 1;
    154       while (quality_good + 1 < quality_bad) {
    155         int quality = (quality_bad + quality_good) / 2;
    156         if (!CompressInternal(srgb, xsize, ysize, num_chans, quality,
    157                               compressed)) {
    158           break;
    159         }
    160         if (compressed->size() <= target_size) {
    161           quality_good = quality;
    162         } else {
    163           quality_bad = quality;
    164         }
    165       }
    166       JXL_RETURN_IF_ERROR(CompressInternal(srgb, xsize, ysize, num_chans,
    167                                            quality_good, compressed));
    168     } else if (quality_ > 0) {
    169       JXL_RETURN_IF_ERROR(CompressInternal(srgb, xsize, ysize, num_chans,
    170                                            quality_, compressed));
    171     } else {
    172       return false;
    173     }
    174     const double end = jxl::Now();
    175     speed_stats->NotifyElapsed(end - start);
    176     return true;
    177   }
    178 
    179   Status Decompress(const std::string& filename,
    180                     const Span<const uint8_t> compressed, ThreadPool* pool,
    181                     PackedPixelFile* ppf,
    182                     jpegxl::tools::SpeedStats* speed_stats) override {
    183     CodecInOut io;
    184     JXL_RETURN_IF_ERROR(
    185         Decompress(filename, compressed, pool, &io, speed_stats));
    186     JxlPixelFormat format{0, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
    187     return jxl::extras::ConvertCodecInOutToPackedPixelFile(
    188         io, format, io.Main().c_current(), pool, ppf);
    189   };
    190 
    191   Status Decompress(const std::string& filename,
    192                     const Span<const uint8_t> compressed, ThreadPool* pool,
    193                     CodecInOut* io, jpegxl::tools::SpeedStats* speed_stats) {
    194     WebPDecoderConfig config;
    195 #ifdef MEMORY_SANITIZER
    196     // config is initialized by libwebp, which we are not instrumenting with
    197     // msan, therefore we need to initialize it here.
    198     memset(&config, 0, sizeof(config));
    199 #endif
    200     JXL_RETURN_IF_ERROR(WebPInitDecoderConfig(&config) == 1);
    201     config.options.use_threads = 0;
    202     config.options.dithering_strength = 0;
    203     config.options.bypass_filtering = 0;
    204     config.options.no_fancy_upsampling = 0;
    205     WebPDecBuffer* const buf = &config.output;
    206     buf->colorspace = MODE_RGBA;
    207     const uint8_t* webp_data = compressed.data();
    208     const int webp_size = compressed.size();
    209     const double start = jxl::Now();
    210     if (WebPDecode(webp_data, webp_size, &config) != VP8_STATUS_OK) {
    211       return JXL_FAILURE("WebPDecode failed");
    212     }
    213     const double end = jxl::Now();
    214     speed_stats->NotifyElapsed(end - start);
    215     JXL_CHECK(buf->u.RGBA.stride == buf->width * 4);
    216 
    217     const bool is_gray = false;
    218     const bool has_alpha = true;
    219     const uint8_t* data_begin = &buf->u.RGBA.rgba[0];
    220     const uint8_t* data_end = data_begin + buf->width * buf->height * 4;
    221     // The image data is initialized by libwebp, which we are not instrumenting
    222     // with msan.
    223     jxl::msan::UnpoisonMemory(data_begin, data_end - data_begin);
    224     if (io->metadata.m.color_encoding.IsGray() != is_gray) {
    225       // TODO(lode): either ensure is_gray matches what the color profile says,
    226       // or set a correct color profile, e.g.
    227       // io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray);
    228       // Return a standard failure because SetFromSRGB triggers a fatal assert
    229       // for this instead.
    230       return JXL_FAILURE("Color profile is-gray mismatch");
    231     }
    232     io->metadata.m.SetAlphaBits(8);
    233     io->SetSize(buf->width, buf->height);
    234     const Status ok = FromSRGB(buf->width, buf->height, is_gray, has_alpha,
    235                                /*is_16bit=*/false, JXL_LITTLE_ENDIAN,
    236                                data_begin, data_end, pool, &io->Main());
    237     WebPFreeDecBuffer(buf);
    238     JXL_RETURN_IF_ERROR(ok);
    239     return true;
    240   }
    241 
    242  private:
    243   static int WebPStringWrite(const uint8_t* data, size_t data_size,
    244                              const WebPPicture* const picture) {
    245     if (data_size) {
    246       std::vector<uint8_t>* const out =
    247           static_cast<std::vector<uint8_t>*>(picture->custom_ptr);
    248       const size_t pos = out->size();
    249       out->resize(pos + data_size);
    250       memcpy(out->data() + pos, data, data_size);
    251     }
    252     return 1;
    253   }
    254   Status CompressInternal(const std::vector<uint8_t>& srgb, size_t xsize,
    255                           size_t ysize, size_t num_chans, int quality,
    256                           std::vector<uint8_t>* compressed) {
    257     compressed->clear();
    258     WebPConfig config;
    259     if (!WebPConfigInit(&config)) {
    260       return JXL_FAILURE("WebPConfigInit failed");
    261     }
    262     JXL_ASSERT(!lossless_ || !near_lossless_);  // can't have both
    263     config.lossless = lossless_ ? 1 : 0;
    264     config.quality = quality;
    265     config.method = method_;
    266 #if WEBP_ENCODER_ABI_VERSION >= 0x020a
    267     config.near_lossless = near_lossless_ ? near_lossless_quality_ : 100;
    268 #else
    269     if (near_lossless_) {
    270       JXL_WARNING("Near lossless not supported by this WebP version");
    271     }
    272 #endif
    273     JXL_CHECK(WebPValidateConfig(&config));
    274 
    275     WebPPicture pic;
    276     if (!WebPPictureInit(&pic)) {
    277       return JXL_FAILURE("WebPPictureInit failed");
    278     }
    279     pic.width = static_cast<int>(xsize);
    280     pic.height = static_cast<int>(ysize);
    281     pic.writer = &WebPStringWrite;
    282     if (lossless_ || near_lossless_) pic.use_argb = 1;
    283     pic.custom_ptr = compressed;
    284 
    285     if (num_chans == 3) {
    286       if (!WebPPictureImportRGB(&pic, srgb.data(), 3 * xsize)) {
    287         return JXL_FAILURE("WebPPictureImportRGB failed");
    288       }
    289     } else {
    290       if (!WebPPictureImportRGBA(&pic, srgb.data(), 4 * xsize)) {
    291         return JXL_FAILURE("WebPPictureImportRGBA failed");
    292       }
    293     }
    294 
    295     // WebP encoding may fail, for example, if the image is more than 16384
    296     // pixels high or wide.
    297     bool ok = FROM_JXL_BOOL(WebPEncode(&config, &pic));
    298     WebPPictureFree(&pic);
    299     // Compressed image data is initialized by libwebp, which we are not
    300     // instrumenting with msan.
    301     jxl::msan::UnpoisonMemory(compressed->data(), compressed->size());
    302     return ok;
    303   }
    304 
    305   int quality_ = 90;
    306   bool lossless_ = false;
    307   bool near_lossless_ = false;
    308   int near_lossless_quality_ = 40;   // only used if near_lossless_
    309   int method_ = 6;                   // smallest, some speed cost
    310 };
    311 
    312 ImageCodec* CreateNewWebPCodec(const BenchmarkArgs& args) {
    313   return new WebPCodec(args);
    314 }
    315 
    316 }  // namespace tools
    317 }  // namespace jpegxl