CMake プロジェクトの C++ アプリで webp 形式の読み書きに libwebp で対応することはじめ


概要

「せんぱい! FLIF っていうのすごいらしいですね!!!!うちで作ってるアプリで扱っている PNG や JPG の内部キャッシュもこの FLIF 形式にトランスコードしたら・・・」

っ『LGPLv3』

そのプロダクトでは LGPLv3 ライセンスのライブラリーを使っちゃうといろいろと問題が起きるからだめー、でも webp なら使ってみてもイイヨヽ(´ー`)ノ

そういうわけでせんぱいは今日もこそこそ定時退社DAYでオシゴトを終わった後にこっそりと Qiita に記事を書くのであった。(†0)

CMake のプロジェクトを libwebp に ExternalProject で対応しよう

さっそく↓こんなのを書いて CMakeLists.txt から include して ExternalProject で管理してみよう:

  • libwebp.cmake
cmake_minimum_required( VERSION 3.2 )

include_directories(${CMAKE_CURRENT_BINARY_DIR}/include)
link_directories(${CMAKE_CURRENT_BINARY_DIR}/lib)

include( ExternalProject )

ExternalProject_Add( external_libwebp
  # https://github.com/webmproject/libwebp
  # http://www.webmproject.org/
  GIT_REPOSITORY    [email protected]:webmproject/libwebp.git
  # v0.5.0 タグなどには cmake が無くて少し手間が増えるのでとりあえず master
  GIT_TAG           master
  # WEBP 系のオプションは webp リポジトリーの CMakeLists.txt 冒頭にわかりやすく整理されているよ
  # おまけツールの cwebp, dwebp コマンドのビルドを on にする場合は libjpeg が必要になるのでとりあえず off にしておくよ
  CMAKE_ARGS        -DWEBP_BUILD_CWEBP=off
                    -DWEBP_BUILD_DWEBP=off
                    -DWEBP_EXPERIMENTAL_FEATURES=on
                    -DWEBP_FORCE_ALIGNED=on
                    -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
                    -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
                    -DCMAKE_COMPILER_IS_GNUCXX=${CMAKE_COMPILER_IS_GNUCXX}
                    -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
                    -DCMAKE_C_FLAGS=${GL_BINDING_C_FLAGS}
  # cmake -> ninja -> install ターゲットなどしたいところだけどカスタムプリフィックスの挙動がおかしいようなので install だけごりごり書いておく
  INSTALL_COMMAND
    COMMAND ${CMAKE_COMMAND} -E copy_directory    ${CMAKE_CURRENT_BINARY_DIR}/external/libwebp/src/external_libwebp/src/webp ${CMAKE_CURRENT_BINARY_DIR}/include/webp
    COMMAND ${CMAKE_COMMAND} -E copy_directory    ${CMAKE_CURRENT_BINARY_DIR}/external/libwebp/src/external_libwebp-build/include ${CMAKE_CURRENT_BINARY_DIR}/include
    COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/external/libwebp/src/external_libwebp-build/libwebp.a ${CMAKE_CURRENT_BINARY_DIR}/lib/libwebp.a
    # cwebp, dwebp もビルドする時は付ける
    #COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/external/libwebp/src/external_libwebp-build/cwebp.exe ${CMAKE_CURRENT_BINARY_DIR}/bin/cwebp.exe
    #COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/external/libwebp/src/external_libwebp-build/dwebp.exe ${CMAKE_CURRENT_BINARY_DIR}/bin/dwebp.exe
)

あとはライブラリーを使いたいアプリの add_dependenciesexternal_libwebptarget_link_librarieswebp を追加すればOK。

webp 形式のデータを C++ ソースのアプリで生成してみる

#include <webp/decode.h>
#include <webp/encode.h>
#include <webp/types.h>

#include <cstdint>
#include <iostream>
#include <fstream>
#include <vector>

auto main() -> int
{
  // メモリーに 256 pixels * 256 pixels * 4 elements な変換元の入力画像データを適当に作るよ
  constexpr auto in_width = 256;
  constexpr auto in_height = 256;
  constexpr auto in_elements = 4;
  std::vector< std::uint8_t > in( in_width * in_height * in_elements );

  for ( int y = 0 ; y < in_height; ++y )
    for ( int x = 0 ; x < in_width; ++x )
    {
      in[ y * in_width * in_elements + x * in_elements + 0] = x;
      in[ y * in_width * in_elements + x * in_elements + 1] = 0;
      in[ y * in_width * in_elements + x * in_elements + 2] = y;
      in[ y * in_width * in_elements + x * in_elements + 3] = 255;
    }

  // webp 形式に変換した結果をこれに入れるよ
  std::uint8_t* out_data = nullptr;
  auto out_size = 0ull;

  constexpr auto in_stride = in_width * in_elements;

  const auto write_file = [&]( const auto& filename )
  {
    std::ofstream o( filename, std::ios::binary );
    o.write( reinterpret_cast< const char* >( out_data ), out_size );
    if ( o.bad() )
      return EXIT_FAILURE;
  };

#ifndef LOSSLESS

  // ロッシーで最高品質の webp に変換してファイルを出力してみる
  {
    constexpr auto out_quality_factor = 100.0f;
    out_size = WebPEncodeRGBA
    ( in.data(), in_width, in_height, in_stride
    , out_quality_factor
    , &out_data
    );
    write_file( "lossy-100.webp" );
  }

#else

  // ロスレスで最高品質の webp に変換してファイルを出力してみる
  {
    out_size = WebPEncodeLosslessRGBA
    ( in.data(), in_width, in_height, in_stride
    , &out_data
    );
    write_file( "lossless.webp" );
  }

#endif

}

webp 形式のデータを C++ アプリで読み出してみる

#include <webp/decode.h>
#include <webp/encode.h>
#include <webp/types.h>

#include <cstdint>
#include <iostream>
#include <fstream>
#include <vector>

auto main() -> int
{
  std::ifstream i( "hoge.lossless.webp", std::ios::binary );
  std::vector< std::uint8_t > in
  ( ( std::istreambuf_iterator< char >( i ) )
  , ( std::istreambuf_iterator< char >(   ) )
  );

  // 対象のバイナリーデータが webp 形式か確認(†1)
  if ( WebPGetInfo( in.data(), in.size(), nullptr, nullptr ) == 0 )
    return EXIT_FAILURE;

  int width    = 0;
  int height   = 0;

  const auto deleter = []( auto* p ){ free( p ); };
  const std::unique_ptr< std::uint8_t, decltype( deleter ) > decoded_data
  ( WebPDecodeRGBA( in.data(), in.size(), &width, &height )
  , deleter
  );
);

  /* 読みだした画像でお好みの処理をどうぞ */
}

だそく

libwebp は他にも扱い易い API が用意されていて楽でよいです。(†Reference)

ちなみに、今回生成した webp 形式の画像の元にしたデータと同じものを paint.net で生成してみたところ、以下の様なファイルサイズの結果が得られました。

type bits/elem. quality file size[KB]
BMP 32 n/a 193
BMP 8 n/a 66
PNG 32 n/a 42
PNG 24 n/a 40
PNG 8 n/a 20
JPG 24 100 15
DDS DXT1 33
WEBP 32 100 3
WEBP 32 Lossless 1

webp さん優秀ヽ(´ー`)ノ

  • †0 : 記事冒頭の概要本文中のどうでもよさそうな表現はフィクションです。
  • †1 : WebPGetInfo は必要最小限のヘッダー部分しか見ていなくて、ファイル構造のヘッダー部の "RIFF""WEBPVP8L" を改変すると 0 を返すけれど、ヘッダー構造上 null であるべき場所が 1 だったりとかの程度ではデータ異常とは検出しないみたい。バイナリーエディターで遊んで見るとよい。

Reference