C++
CMake
image
WebP
libwebp

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

More than 3 years have passed since last update.


概要

「せんぱい! 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 git@github.com: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