LoginSignup
7
6

More than 5 years have passed since last update.

WebP Advanced API の使い方

Last updated at Posted at 2016-03-12

概要

WebP には libwebp ライブラリーに Simple Decoding APISimple Encoding API が用意されているので、ごく簡単に扱いたいだけであればデコードもエンコードも API Reference を少し眺めるだけでその名の通り API 自体はワンラインだけで簡単に使える。

しかし、 libwebp を用いてアプリケーションへ WebP を組み込む場合には Simple API では性能や特性を十分に活かせなかったり、あるいは libwebp API のバッファーとアプリで使用するメモリーとの間でのコピーコストが気になるなど、一般に libwebp ライブラリーを組み込んで扱いたい用途では Advanced Decoding API と Advanced Encoding API を使いたい。

Simple API と異なり、 Advanced API は少々癖があり、一般に使用するにあたっては API Reference の簡単な概要説明だけではやや不親切なところがあるのでここで Advanced API を用いた具体的なデコード、エンコードの手法を紹介したい。

Advanced Decoding API

// glm ライブラリーの glm::u8vec4 など必要に応じて読み替える
struct rgba_pixel { std::uint8_t r, g, b, a; };

// デコードされたRGBAピクセルの塊と必要最低限の画像情報を保持する型
struct decoded_image_type
{
  int width;
  int height;
  int elements;
  std::vector< rgba_pixel > data;
};

// WebP 以外にも対応したり、何かと応用したい理由があれば shared_ptr で扱う。必要なければそのまま扱えばいい。
using shared_decoded_image_type = std::shared_ptr< decoded_image_type >;

// ソースバイナリーデータを WebP Advance API でデコードして返す関数定義の例
auto decode_image_libwebp( const std::vector< std::uint8_t >& in )
  -> shared_decoded_image_type
{
  // 返す画像情報を用意
  const auto i = std::make_shared< decoded_image_type >();

  // in が WebP 形式か確認し、 WebP 形式でなければ 空の shared_ptr を返して終わる。
  // この API は異常時に 0 を返す。これ以降で VP8_STATUS_OK (=0) を使う API とは異なるので注意。
  constexpr auto webp_header_error = 0;
  if ( WebPGetInfo( in.data(), in.size(), &i->width, &i->height ) == webp_header_error )
    return { };

  // WebP のデコード設定を制御する重要な WebPDeccoderConfig 型を用意。
  WebPDecoderConfig c;

  // コンフィグ型を初期化
  if ( not WebPInitDecoderConfig( &c ) )
    throw std::runtime_error( "WebPInitDecoderConfig error" );

  // 初期化後のコンフィグをカスタムする
  // さまざまな設定項目については API Reference の一覧を見て必要に応じて値をセットする
  c.options.use_threads = true;

  // コンフィグに入力となる WebP エンコードされたデータに基づく値を設定する
  if ( WebPGetFeatures( in.data(), in.size(), &c.input ) != VP8_STATUS_OK )
    throw std::runtime_error( "WebPGetFeatures error" );

  // 今回は RGBA 決め打ちでデコードする例とするのでピクセルの要素数は R, G, B, A の 4 つとなります。
  constexpr auto rgba_elements = 4;
  // API Reference には特に解説されていませんが、スキャンラインのためにRGBAピクセルの1ラインにつき7bytes余分に必要なようです。
  // これは libwebp に含まれる dwebp.c を読むと書いてあります。
  constexpr auto scanline_additional_bytes = 7;
  // libwebp における stride とはターゲットデータにおける1ライン分のデータサイズ bytes
  const auto stride = i->width * rgba_elements + scanline_additional_bytes;

  // スキャンラインのために最終的な書き出し先へ直接 RGBA ピクセルを密に書き出せないので、デコード用にバッファー領域を用意する。
  // このバッファー領域は 1 ライン中にスキャンライン分のデータも含める必要があるので注意。
  std::vector< std::uint8_t > decoding_buffer( stride * i->height );

  // デコード先のピクセル形式を指示する。今回は RGBA を使う。
  // 他の形式については API Reference にも掲載されている。
  c.output.colorspace = MODE_RGBA;
  {
    // このブロックの設定を施すと、独自に確保したメモリー領域を用いてデコード処理を行うようになる。
    // これらの設定をせずに is_external_memory = 0 のままデコードを行うと内部的にバッファーを自動確保して動作するモードとなる
    // なお、独自確保したメモリー領域を使わない場合に libwebp が確保したメモリー領域を WebPFreeDecBuffer( &c.output ) 
    // で開放する必要が生じるのでバッファーを自動確保させる場合には注意する。
    c.output.u.RGBA.rgba = &decoding_buffer[0];
    c.output.u.RGBA.stride = stride;
    c.output.u.RGBA.size = stride * i->height;
    c.output.is_external_memory = 1;
  }

  // 設定を施したコンフィグを用いて入力された WebP バイナリーデータをデコードする。
  if ( WebPDecode( in.data(), in.size(), &c ) != VP8_STATUS_OK )
    throw std::runtime_error( "WebPDecode error" );

  // 今回はデコード後に RGBA ピクセルが密に詰まったスキャンラインデータの無い画像データを出力するために、
  // 最終データの出力先に領域を確保し、スキャンラインデータを飛ばしてデコード用に用意した中間バッファーからデコードされたピクセルデータライン単位でコピーする。
  i->data.resize( i->width * i->height );
  {
    auto source_address = &decoding_buffer[0];

    for ( auto y = 0; y < i->height; ++y, source_address += stride )
    {
      const auto p = reinterpret_cast< glm::u8vec4* >( source_address );
      std::copy( p, p + i->width, &i->data[ y * i->width ] );
    }
  }

  // 出力構造にピクセルの要素数を入れる。
  i->elements = rgba_elements;

  // デコードされた画像データを返す。
  return i;
}

Advanced Encoding API

// shared_decoded_image_type は ↑ の Advanced Decoding API の解説冒頭のものと同じ。
// RGBA ピクセルの詰まったデコードされた画像データの入力から WebP(lossy) 形式のバイナリーデータを出力します。
auto decode_image_libwebp( const shared_decoded_image_type& in, const float compression_quality = 75.0f )
  -> std::vector< std::uint8_t >
{
  // 入力データの stride なので、今回は RGBA が密に詰まっており width と elements だけでスキャンラインなどの値は要らない点に一応注意。
  constexpr auto in_stride = in->width * in->elements;

  // 圧縮設定を制御するコンフィグオブジェクトを作ります。
  WebPConfig c;
  if ( not WebPConfigInit( &c ) )
    throw std::runtime_error( "WebPConfigInit error" );
  // 作られたコンフィグオブジェクトが正常か確認します。
  if ( not WebPValidateConfig( &c ) )
    throw std::runtime_error( "WebPValidateConfig error" );

  // libwebp が画像を扱うための WebPPicture 型を用意します。
  // 後始末で WebPPictureFree をセットで使う必要があるので std::unique_ptr に入れておきます。
  const auto& webp_picture_deleter = []( WebPPicture* p ) { WebPPictureFree( p ); };
  std::unique_ptr< WebPPicture, decltype( webp_picture_deleter ) > p( new WebPPicture(), webp_picture_deleter );

  // 用意した WebPPicture を初期化します。
  if ( not WebPPictureInit( p.get() ) )
    throw std::runtime_error( "WebPPictureInit error" );

  // libwebp がエンコード処理でメモリーへの書き出しに用いる WebPMemoryWriter 型を用意します。
  WebPMemoryWriter w;
  // 初期化します。なお、この API は return 型が void なのでエラー判定などはしません。
  WebPMemoryWriterInit( &w );

  // この後で WebPEncode が成功し WebPMemoryWriter がメモリー領域を確保した場合は
  // セットで WebPFree を使う必要が生じるので用意しておきます。
  const auto& webp_deleter = []( auto* p ){ WebPFree( p ); };
  std::unique_ptr< std::uint8_t, decltype( webp_deleter ) > unique_writer_memory( nullptr, webp_deleter );

  // 圧縮設定をカスタムします。設定項目については API Reference に一応 Experimental なものを除いて掲載されています。
  // 今回は lossy に圧縮するので lossless = 0 です。この値を 1 にするとロスレス圧縮に動作モードが切り替わります。
  c.lossless = 0;
  // lossy 圧縮の品質を quality で制御する場合、 0.0f .. 100.0f の範囲で低品質から高品質に品質を変化させます。
  c.quality = out_quality_factor;
  // lossy 圧縮では 0 .. 6 までの複数の種類の method (WebP圧縮に用いられる内部アルゴリズム)を選択できます。
  // 0 が高速、 6 が低速という事に API Reference ではなっていますが、以下の参考資料に簡単な実験結果の紹介があり設定の参考になります。
  // http://www.slideshare.net/FukushimaNorishige/webp
  c.method = 3;

  // エンコーダーが扱う画像情報を設定します。
  p->width  = in->width;
  p->height = in->height;
  // この値はコンフィグによって適切な設定値が変わります。クロッピングなどしている場合にも設定します。
  // これは libwebp に付属の cwebp.c ソースを読むと例が書いてあります。
  p->use_argb =
       c.lossless
    or c.preprocessing > 0
    ;

  // WebPPicture に入力画像を読み取らせます。
  // この API は API Reference よりも libwebp に付属する cwebp.c ソースが参考になります。
  if ( not WebPPictureImportRGBA( p.get(), in.data.data(), in_stride ) )
    throw std::runtime_error( "WebPPictureImportRGBA error" );

  // WebPPicture に書き出し方法として WebPMemoryWrite を設定します。 WebPMemoryWriter ではないので地味にタイポ等に注意します。
  p->writer = WebPMemoryWrite;
  // WebPPicture に書き出しを行う WebPMemoryWriter オブジェクトを設定します。
  p->custom_ptr = &w;

  // ここまでに用意した設定と画像情報を用いてデータをエンコードします。
  // 成功した場合には w.mem に WebPFree が必要となるので用意しておいた unique_writer_memory へ入れておきます。
  if ( WebPEncode( &c, p.get() ) )
    unique_writer_memory.reset( w.mem );
  else
    throw std::runtime_error( "WebPEncode error" );

  // エンコード結果を取得します。
  std::vector< std::uint8_t > webp_binary_data( w.mem, w.mem + w.size );

  // WebPMemoryWriter に確保させたデコード用の中間バッファーの開放と
  // WebPPicture の後始末は std::unique_ptr がしてくれます。

  return webp_binary_data;
}

Reference

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6