From 97c75856ade5fdb4a01b7a21e99b78e795bc157b Mon Sep 17 00:00:00 2001 From: mwilsnd <53413200+mwilsnd@users.noreply.github.com> Date: Sun, 9 Jul 2023 08:57:42 -0500 Subject: [PATCH] Add WebP decoding support to Linux and Windows (#1314) --- .github/workflows/linux-ci.yml | 5 +++- .github/workflows/node-ci.yml | 3 +- .github/workflows/node-release.yml | 3 +- CHANGELOG.md | 1 + platform/default/BUILD.bazel | 1 + platform/default/src/mbgl/util/image.cpp | 19 +++++++++++-- .../default/src/mbgl/util/webp_reader.cpp | 26 ++++++++++++++++++ platform/linux/BUILD.bazel | 1 + platform/linux/README.md | 2 +- platform/linux/linux.cmake | 4 +++ platform/windows/Get-VendorPackages.ps1 | 2 +- platform/windows/windows.cmake | 4 +++ test/fixtures/image/tile.webp | Bin 0 -> 31916 bytes test/util/image.test.cpp | 6 ++++ 14 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 platform/default/src/mbgl/util/webp_reader.cpp create mode 100644 test/fixtures/image/tile.webp diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index edda7bc81..b373ac65c 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -60,7 +60,8 @@ jobs: libuv1-dev \ libjpeg-dev \ libpng-dev \ - libglfw3-dev + libglfw3-dev \ + libwebp-dev - name: Build MapLibre Native Core run: | @@ -185,7 +186,9 @@ jobs: libuv1-dev \ libjpeg-dev \ libpng-dev \ + libwebp-dev \ libglfw3-dev \ + libsqlite3-dev \ xserver-xorg-video-dummy \ xinit diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index d018098bf..30c921624 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -132,7 +132,8 @@ jobs: libc++-9-dev \ libc++abi-9-dev \ libjpeg-dev \ - libpng-dev + libpng-dev \ + libwebp-dev /usr/sbin/update-ccache-symlinks - name: Setup node diff --git a/.github/workflows/node-release.yml b/.github/workflows/node-release.yml index 54cf6a8bb..c227e1c1b 100644 --- a/.github/workflows/node-release.yml +++ b/.github/workflows/node-release.yml @@ -128,7 +128,8 @@ jobs: libc++-9-dev \ libc++abi-9-dev \ libjpeg-dev \ - libpng-dev + libpng-dev \ + libwebp-dev /usr/sbin/update-ccache-symlinks - name: Setup node diff --git a/CHANGELOG.md b/CHANGELOG.md index 738686f82..99b43b89e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### ✨ New features - *...Add new stuff here...* +- [core] Add WebP image decoding support to default platform (Linux, Windows) - [core] All CMake properties are now prefixed `MLN_*` [1054](https://github.com/maplibre/maplibre-native/pull/1054). - [windows] Added windows build support for core applications and node [#707](https://github.com/maplibre/maplibre-native/pull/707) - [core] Add `ClientOptions` to configure client information [#365](https://github.com/maplibre/maplibre-native/pull/365). diff --git a/platform/default/BUILD.bazel b/platform/default/BUILD.bazel index 8d6a24f90..4826e6541 100644 --- a/platform/default/BUILD.bazel +++ b/platform/default/BUILD.bazel @@ -70,6 +70,7 @@ cc_library( "src/mbgl/util/string_stdlib.cpp", "src/mbgl/util/thread.cpp", "src/mbgl/util/timer.cpp", + "src/mbgl/util/webp_reader.cpp", ], "//:ios": [], }), diff --git a/platform/default/src/mbgl/util/image.cpp b/platform/default/src/mbgl/util/image.cpp index 7d36dbd54..1ff505d6f 100644 --- a/platform/default/src/mbgl/util/image.cpp +++ b/platform/default/src/mbgl/util/image.cpp @@ -6,14 +6,29 @@ namespace mbgl { PremultipliedImage decodePNG(const uint8_t*, size_t); PremultipliedImage decodeJPEG(const uint8_t*, size_t); +PremultipliedImage decodeWEBP(const uint8_t*, size_t); PremultipliedImage decodeImage(const std::string& string) { const auto* data = reinterpret_cast(string.data()); const size_t size = string.size(); + const auto readUInt = [](const uint8_t* imageData, ptrdiff_t offset) -> uint32_t { + const auto ptr = imageData + offset; + return (static_cast(ptr[0]) << 24) | (static_cast(ptr[1]) << 16) | + (static_cast(ptr[2]) << 8) | static_cast(ptr[3]); + }; + + if (size >= 16) { + uint32_t magic1 = readUInt(data, 0x0); + uint32_t magic2 = readUInt(data, 0x8); + // RIFF WEBP + if (magic1 == 0x52494646 && magic2 == 0x57454250) { + return decodeWEBP(data, size); + } + } + if (size >= 4) { - uint32_t magic = (static_cast(data[0]) << 24) | (static_cast(data[1]) << 16) | - (static_cast(data[2]) << 8) | static_cast(data[3]); + uint32_t magic = readUInt(data, 0x0); if (magic == 0x89504E47U) { return decodePNG(data, size); } diff --git a/platform/default/src/mbgl/util/webp_reader.cpp b/platform/default/src/mbgl/util/webp_reader.cpp new file mode 100644 index 000000000..7faf6641d --- /dev/null +++ b/platform/default/src/mbgl/util/webp_reader.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#include +#include + +namespace mbgl { + +PremultipliedImage decodeWEBP(const uint8_t* data, size_t size) { + int32_t width, height; + if (!WebPGetInfo(data, size, &width, &height)) { + Log::Warning(Event::Image, "Failed to decode WebP image header!"); + return {}; + } + + auto img = UnassociatedImage({static_cast(width), static_cast(height)}); + if (!WebPDecodeRGBAInto(data, size, img.data.get(), img.bytes(), static_cast(img.stride()))) { + Log::Warning(Event::Image, "Failed to decode WebP image contents!"); + return {}; + } + + return util::premultiply(std::move(img)); +} + +} // namespace mbgl diff --git a/platform/linux/BUILD.bazel b/platform/linux/BUILD.bazel index dfbc21b1f..f7ffb87cf 100644 --- a/platform/linux/BUILD.bazel +++ b/platform/linux/BUILD.bazel @@ -23,6 +23,7 @@ cc_library( "-lcurl", "-lpng", "-ljpeg", + "-lwebp", ] + ICU_FLAGS, visibility = ["//visibility:public"], deps = [ diff --git a/platform/linux/README.md b/platform/linux/README.md index 9f42948a3..7e1d8ad96 100644 --- a/platform/linux/README.md +++ b/platform/linux/README.md @@ -7,7 +7,7 @@ This guide explains how to get started building and running MapLibre Native on L The following system libraries need to be installed. ```bash -apt install libcurl4-openssl-dev libglfw3-dev libuv1-dev libpng-dev libicu-dev libjpeg62-turbo +apt install libcurl4-openssl-dev libglfw3-dev libuv1-dev libpng-dev libicu-dev libjpeg62-turbo libwebp-dev ``` Optional: `libsqlite3-dev` (also available as vendored dependency). diff --git a/platform/linux/linux.cmake b/platform/linux/linux.cmake index 69e32b90e..68e55d588 100644 --- a/platform/linux/linux.cmake +++ b/platform/linux/linux.cmake @@ -12,6 +12,7 @@ if (MLN_WITH_X11) endif () find_package(Threads REQUIRED) +pkg_search_module(WEBP libwebp REQUIRED) pkg_search_module(LIBUV libuv REQUIRED) target_sources( @@ -45,6 +46,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/filesystem.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/image.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/jpeg_reader.cpp + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/webp_reader.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/logging_stderr.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/monotonic_timer.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/png_reader.cpp @@ -100,6 +102,7 @@ target_include_directories( ${JPEG_INCLUDE_DIRS} ${LIBUV_INCLUDE_DIRS} ${X11_INCLUDE_DIRS} + ${WEBP_INCLUDE_DIRS} ) include(${PROJECT_SOURCE_DIR}/vendor/nunicode.cmake) @@ -127,6 +130,7 @@ target_link_libraries( ${LIBUV_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + ${WEBP_LIBRARIES} $<$>:ICU::i18n> $<$>:ICU::uc> $<$:mbgl-vendor-icu> diff --git a/platform/windows/Get-VendorPackages.ps1 b/platform/windows/Get-VendorPackages.ps1 index c37a64d1d..fe18fc034 100644 --- a/platform/windows/Get-VendorPackages.ps1 +++ b/platform/windows/Get-VendorPackages.ps1 @@ -30,7 +30,7 @@ if(-not (Test-Path "$vcpkg_temp_dir\vcpkg.exe")) & "$vcpkg_temp_dir\bootstrap-vcpkg.bat" } -& "$vcpkg_temp_dir\vcpkg.exe" --disable-metrics --overlay-triplets=$([System.IO.Path]::Combine($PWD.Path, 'vendor', 'vcpkg-custom-triplets')) --triplet=$($Triplet) --clean-after-build install curl dlfcn-win32 glfw3 icu libuv libjpeg-turbo libpng $($renderer_packages) +& "$vcpkg_temp_dir\vcpkg.exe" --disable-metrics --overlay-triplets=$([System.IO.Path]::Combine($PWD.Path, 'vendor', 'vcpkg-custom-triplets')) --triplet=$($Triplet) --clean-after-build install curl dlfcn-win32 glfw3 icu libuv libjpeg-turbo libpng libwebp $($renderer_packages) subst $vcpkg_temp_dir /D diff --git a/platform/windows/windows.cmake b/platform/windows/windows.cmake index fcebee157..f9b2591e2 100644 --- a/platform/windows/windows.cmake +++ b/platform/windows/windows.cmake @@ -23,6 +23,7 @@ find_package(ICU OPTIONAL_COMPONENTS i18n uc) find_package(JPEG REQUIRED) find_package(libuv REQUIRED) find_package(PNG REQUIRED) +find_package(WebP REQUIRED) find_path(DLFCN_INCLUDE_DIRS dlfcn.h) find_path(LIBUV_INCLUDE_DIRS uv.h) @@ -57,6 +58,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/filesystem.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/image.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/jpeg_reader.cpp + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/webp_reader.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/logging_stderr.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/monotonic_timer.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/util/png_reader.cpp @@ -147,6 +149,7 @@ target_include_directories( ${DLFCN_INCLUDE_DIRS} ${JPEG_INCLUDE_DIRS} ${LIBUV_INCLUDE_DIRS} + ${WEBP_INCLUDE_DIRS} ) include(${PROJECT_SOURCE_DIR}/vendor/nunicode.cmake) @@ -172,6 +175,7 @@ target_link_libraries( ${CURL_LIBRARIES} ${JPEG_LIBRARIES} ${LIBUV_LIBRARIES} + ${WEBP_LIBRARIES} dlfcn-win32::dl $<$>:ICU::data> $<$>:ICU::i18n> diff --git a/test/fixtures/image/tile.webp b/test/fixtures/image/tile.webp new file mode 100644 index 0000000000000000000000000000000000000000..a85c33286450bf9a01915e2ef7aa8a461ca29346 GIT binary patch literal 31916 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 literal 0 HcmV?d00001 diff --git a/test/util/image.test.cpp b/test/util/image.test.cpp index a520f94fa..b0f89bba7 100644 --- a/test/util/image.test.cpp +++ b/test/util/image.test.cpp @@ -78,6 +78,12 @@ TEST(Image, JPEGTile) { EXPECT_EQ(256u, image.size.height); } +TEST(Image, WebPTile) { + PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/tile.webp")); + EXPECT_EQ(256u, image.size.width); + EXPECT_EQ(256u, image.size.height); +} + TEST(Image, Resize) { AlphaImage image({0, 0});