diff --git a/android/filament-utils-android/CMakeLists.txt b/android/filament-utils-android/CMakeLists.txt index d0e014ed48..33263ecd63 100644 --- a/android/filament-utils-android/CMakeLists.txt +++ b/android/filament-utils-android/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.19) project(filament-utils-android) set(FILAMENT_DIR ${FILAMENT_DIST_DIR}) +set(IMAGEIO_DIR ../../libs/imageio) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../gltfio-android ${CMAKE_CURRENT_BINARY_DIR}/gltfio-android) @@ -35,14 +36,21 @@ add_library(filament-utils-jni SHARED src/main/cpp/Manipulator.cpp src/main/cpp/RemoteServer.cpp + ${IMAGEIO_DIR}/include/imageio/ImageDecoder.h + ${IMAGEIO_DIR}/include/imageio/HDRDecoder.h + ${IMAGEIO_DIR}/src/HDRDecoder.cpp + ../common/CallbackUtils.cpp ../common/NioUtils.cpp ) +target_compile_definitions(filament-utils-jni PUBLIC IMAGEIO_LITE=1) + target_include_directories(filament-utils-jni PRIVATE ${FILAMENT_DIR}/include .. ../../third_party/stb + ${IMAGEIO_DIR}/include ../../libs/utils/include) set_target_properties(filament-utils-jni PROPERTIES LINK_DEPENDS diff --git a/libs/imageio/CMakeLists.txt b/libs/imageio/CMakeLists.txt index 0c3a6a37a3..25a417d899 100644 --- a/libs/imageio/CMakeLists.txt +++ b/libs/imageio/CMakeLists.txt @@ -9,6 +9,7 @@ set(PUBLIC_HDR_DIR include) # ================================================================================================== set(PUBLIC_HDRS include/imageio/BlockCompression.h + include/imageio/HDRDecoder.h include/imageio/ImageDecoder.h include/imageio/ImageDiffer.h include/imageio/ImageEncoder.h @@ -16,6 +17,7 @@ set(PUBLIC_HDRS set(SRCS src/BlockCompression.cpp + src/HDRDecoder.cpp src/ImageDecoder.cpp src/ImageDiffer.cpp src/ImageEncoder.cpp diff --git a/libs/imageio/include/imageio/HDRDecoder.h b/libs/imageio/include/imageio/HDRDecoder.h new file mode 100644 index 0000000000..bb7519ee6f --- /dev/null +++ b/libs/imageio/include/imageio/HDRDecoder.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IMAGE_HDRDECODER_H_ +#define IMAGE_HDRDECODER_H_ + +#include + +namespace image { + +class HDRDecoder : public ImageDecoder::Decoder { +public: + static HDRDecoder* create(std::istream& stream); + static bool checkSignature(char const* buf); + + HDRDecoder(const HDRDecoder&) = delete; + HDRDecoder& operator=(const HDRDecoder&) = delete; + +private: + explicit HDRDecoder(std::istream& stream); + ~HDRDecoder() override; + + // ImageDecoder::Decoder interface + LinearImage decode() override; + + static const char sigRadiance[]; + static const char sigRGBE[]; + std::istream& mStream; + std::streampos mStreamStartPos; +}; + +} // namespace image + +#endif /* IMAGE_IMAGEDECODER_H_ */ diff --git a/libs/imageio/src/HDRDecoder.cpp b/libs/imageio/src/HDRDecoder.cpp new file mode 100644 index 0000000000..be3527217d --- /dev/null +++ b/libs/imageio/src/HDRDecoder.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +#include + +#include +#include +#include + +// for ntohs +#if defined(WIN32) +# include +# include +#else +# include +#endif + +using namespace utils; + +namespace image { + +const char HDRDecoder::sigRadiance[] = { '#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E', 0xa }; +const char HDRDecoder::sigRGBE[] = { '#', '?', 'R', 'G', 'B', 'E', 0xa }; + +HDRDecoder* HDRDecoder::create(std::istream& stream) { + HDRDecoder* decoder = new HDRDecoder(stream); + return decoder; +} + +bool HDRDecoder::checkSignature(char const* buf) { + return !memcmp(buf, sigRadiance, sizeof(sigRadiance)) || + !memcmp(buf, sigRGBE, sizeof(sigRGBE)); +} + +HDRDecoder::HDRDecoder(std::istream& stream) + : mStream(stream), mStreamStartPos(stream.tellg()) { +} + +HDRDecoder::~HDRDecoder() = default; + +LinearImage HDRDecoder::decode() { + float gamma; + float exposure; + char sy, sx; + unsigned int height = 0, width = 0; + { + char buf[1024]; + do { + char format[128]; + mStream.getline(buf, sizeof(buf), 0xa); + if (buf[0] == '#') continue; + sscanf(buf, "FORMAT=%127s", format); // NOLINT + sscanf(buf, "GAMMA=%f", &gamma); // NOLINT + sscanf(buf, "EXPOSURE=%f", &exposure); // NOLINT + if ((sscanf(buf, "%cY %u %cX %u", &sy, &height, &sx, &width) == 4)|| // NOLINT + (sscanf(buf, "%cX %u %cY %u", &sx, &width, &sy, &height) == 4)) { // NOLINT + break; + } + } while (true); + } + LinearImage image(width, height, 3); + + if (sx == '-') image = (image); + if (sy == '+') image = verticalFlip(image); + + // Allocate memory to hold one row of decoded pixel data. + std::unique_ptr rgbe(new uint8_t[width * 4]); + + // First, test for non-RLE images. + const auto pos = mStream.tellg(); + mStream.read((char*) rgbe.get(), 3); + mStream.seekg(pos); + + if (rgbe[0] != 0x2 || rgbe[1] != 0x2 || (rgbe[2] & 0x80) || width < 8 || width > 32767) { + for (uint32_t y = 0; y < height; y++) { + filament::math::float3* dst = reinterpret_cast(image.getPixelRef(0, y)); + mStream.read((char*) rgbe.get(), width * 4); + // (rgb/256) * 2^(e-128) + size_t pixel = 0; + for (size_t x = 0; x < width; x++, pixel += 4) { + if (rgbe[pixel + 3] == 0.0f) { + dst[x] = filament::math::float3{0.0f}; + } else { + filament::math::float3 v(rgbe[pixel], rgbe[pixel + 1], rgbe[pixel + 2]); + dst[x] = (v + 0.5f) * std::ldexp(1.0f, rgbe[pixel + 3] - (128 + 8)); + } + } + } + } else { + for (uint32_t y = 0; y < height; y++) { + uint16_t magic; + mStream.read((char*) &magic, 2); + if (magic != 0x0202) { + slog.e << "invalid scanline (magic)" << io::endl; + return {}; + } + + uint16_t w; + mStream.read((char*) &w, 2); + if (ntohs(w) != width) { + slog.e << "invalid scanline (width)" << io::endl; + return {}; + } + + char* d = (char*) rgbe.get(); + for (size_t p = 0; p < 4; p++) { + size_t num_bytes = 0; + while (num_bytes < width) { + uint8_t rle_count; + mStream.read((char*) &rle_count, 1); + if (rle_count > 128) { + char v; + mStream.read(&v, 1); + memset(d, v, size_t(rle_count - 128)); + d += rle_count - 128; + num_bytes += rle_count - 128; + } else { + if (rle_count == 0) { + slog.e << "run length is zero" << io::endl; + return {}; + } + mStream.read(d, rle_count); + d += rle_count; + num_bytes += rle_count; + } + } + } + + uint8_t const* r = &rgbe[0]; + uint8_t const* g = &rgbe[width]; + uint8_t const* b = &rgbe[2 * width]; + uint8_t const* e = &rgbe[3 * width]; + filament::math::float3* dst = reinterpret_cast(image.getPixelRef(0, y)); + // (rgb/256) * 2^(e-128) + for (size_t x = 0; x < width; x++, r++, g++, b++, e++) { + if (e[0] == 0.0f) { + dst[x] = filament::math::float3{0.0f}; + } else { + filament::math::float3 v(r[0], g[0], b[0]); + dst[x] = (v + 0.5f) * std::ldexp(1.0f, e[0] - (128 + 8)); + } + } + } + } + + return image; +} + +#ifdef IMAGEIO_LITE + +LinearImage ImageDecoder::decode(std::istream& stream, const std::string& sourceName, + ColorSpace sourceSpace) { + + Format format = Format::NONE; + + std::streampos pos = stream.tellg(); + char buf[16]; + stream.read(buf, sizeof(buf)); + + if (HDRDecoder::checkSignature(buf)) { + format = Format::HDR; + } + + stream.seekg(pos); + + std::unique_ptr decoder; + switch (format) { + case Format::HDR: + decoder.reset(HDRDecoder::create(stream)); + decoder->setColorSpace(ColorSpace::LINEAR); + break; + default: + return LinearImage(); + } + + return decoder->decode(); +} + +#endif + +} // namespace image diff --git a/libs/imageio/src/ImageDecoder.cpp b/libs/imageio/src/ImageDecoder.cpp index 9d0b257775..373c20166b 100644 --- a/libs/imageio/src/ImageDecoder.cpp +++ b/libs/imageio/src/ImageDecoder.cpp @@ -27,6 +27,7 @@ #include +// for ntohs #if defined(WIN32) # include # include @@ -44,6 +45,8 @@ #include #include +#include + namespace image { class PNGDecoder : public ImageDecoder::Decoder { @@ -77,29 +80,6 @@ private: // ----------------------------------------------------------------------------------------------- -class HDRDecoder : public ImageDecoder::Decoder { -public: - static HDRDecoder* create(std::istream& stream); - static bool checkSignature(char const* buf); - - HDRDecoder(const HDRDecoder&) = delete; - HDRDecoder& operator=(const HDRDecoder&) = delete; - -private: - explicit HDRDecoder(std::istream& stream); - ~HDRDecoder() override; - - // ImageDecoder::Decoder interface - LinearImage decode() override; - - static const char sigRadiance[]; - static const char sigRGBE[]; - std::istream& mStream; - std::streampos mStreamStartPos; -}; - -// ----------------------------------------------------------------------------------------------- - class PSDDecoder : public ImageDecoder::Decoder { public: static PSDDecoder* create(std::istream& stream); @@ -333,142 +313,6 @@ void PNGDecoder::error() { // ----------------------------------------------------------------------------------------------- -const char HDRDecoder::sigRadiance[] = { '#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E', 0xa }; -const char HDRDecoder::sigRGBE[] = { '#', '?', 'R', 'G', 'B', 'E', 0xa }; - -HDRDecoder* HDRDecoder::create(std::istream& stream) { - HDRDecoder* decoder = new HDRDecoder(stream); - return decoder; -} - -bool HDRDecoder::checkSignature(char const* buf) { - return !memcmp(buf, sigRadiance, sizeof(sigRadiance)) || - !memcmp(buf, sigRGBE, sizeof(sigRGBE)); -} - -HDRDecoder::HDRDecoder(std::istream& stream) - : mStream(stream), mStreamStartPos(stream.tellg()) { -} - -HDRDecoder::~HDRDecoder() = default; - -LinearImage HDRDecoder::decode() { - try { - float gamma; - float exposure; - char sy, sx; - unsigned int height, width; - - { - char buf[1024]; - do { - char format[128]; - mStream.getline(buf, sizeof(buf), 0xa); - if (buf[0] == '#') continue; - sscanf(buf, "FORMAT=%127s", format); // NOLINT - sscanf(buf, "GAMMA=%f", &gamma); // NOLINT - sscanf(buf, "EXPOSURE=%f", &exposure); // NOLINT - if ((sscanf(buf, "%cY %u %cX %u", &sy, &height, &sx, &width) == 4)|| // NOLINT - (sscanf(buf, "%cX %u %cY %u", &sx, &width, &sy, &height) == 4)) { // NOLINT - break; - } - } while (true); - } - - LinearImage image(width, height, 3); - - if (sx == '-') image = horizontalFlip(image); - if (sy == '+') image = verticalFlip(image); - - // Allocate memory to hold one row of decoded pixel data. - std::unique_ptr rgbe(new uint8_t[width * 4]); - - // First, test for non-RLE images. - const auto pos = mStream.tellg(); - mStream.read((char*) rgbe.get(), 3); - mStream.seekg(pos); - - if (rgbe[0] != 0x2 || rgbe[1] != 0x2 || (rgbe[2] & 0x80) || width < 8 || width > 32767) { - for (uint32_t y = 0; y < height; y++) { - filament::math::float3* dst = reinterpret_cast(image.getPixelRef(0, y)); - mStream.read((char*) rgbe.get(), width * 4); - // (rgb/256) * 2^(e-128) - size_t pixel = 0; - for (size_t x = 0; x < width; x++, pixel += 4) { - if (rgbe[pixel + 3] == 0.0f) { - dst[x] = filament::math::float3{0.0f}; - } else { - filament::math::float3 v(rgbe[pixel], rgbe[pixel + 1], rgbe[pixel + 2]); - dst[x] = (v + 0.5f) * std::ldexp(1.0f, rgbe[pixel + 3] - (128 + 8)); - } - } - } - } else { - for (uint32_t y = 0; y < height; y++) { - uint16_t magic; - mStream.read((char*) &magic, 2); - if (magic != 0x0202) { - throw std::runtime_error("invalid scanline (magic)"); - } - - uint16_t w; - mStream.read((char*) &w, 2); - if (ntohs(w) != width) { - throw std::runtime_error("invalid scanline (width)"); - } - - char* d = (char*) rgbe.get(); - for (size_t p = 0; p < 4; p++) { - size_t num_bytes = 0; - while (num_bytes < width) { - uint8_t rle_count; - mStream.read((char*) &rle_count, 1); - if (rle_count > 128) { - char v; - mStream.read(&v, 1); - memset(d, v, size_t(rle_count - 128)); - d += rle_count - 128; - num_bytes += rle_count - 128; - } else { - if (rle_count == 0) { - throw std::runtime_error("run length is zero"); - } - mStream.read(d, rle_count); - d += rle_count; - num_bytes += rle_count; - } - } - } - - uint8_t const* r = &rgbe[0]; - uint8_t const* g = &rgbe[width]; - uint8_t const* b = &rgbe[2 * width]; - uint8_t const* e = &rgbe[3 * width]; - filament::math::float3* dst = reinterpret_cast(image.getPixelRef(0, y)); - // (rgb/256) * 2^(e-128) - for (size_t x = 0; x < width; x++, r++, g++, b++, e++) { - if (e[0] == 0.0f) { - dst[x] = filament::math::float3{0.0f}; - } else { - filament::math::float3 v(r[0], g[0], b[0]); - dst[x] = (v + 0.5f) * std::ldexp(1.0f, e[0] - (128 + 8)); - } - } - } - } - - return image; - - } catch(std::runtime_error& e) { - // reset the stream, like we found it - std::cerr << "Runtime error while decoding HDR: " << e.what() << std::endl; - mStream.seekg(mStreamStartPos); - } - return LinearImage(); -} - -// ----------------------------------------------------------------------------------------------- - const char PSDDecoder::sig[] = { '8', 'B', 'P', 'S', 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; PSDDecoder* PSDDecoder::create(std::istream& stream) {