From cebfa32c9f3396ef6fbe8978a2236aa3a501053a Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 23 Mar 2025 11:48:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0hdr=EF=BC=88=E5=81=8F?= =?UTF-8?q?=E6=9A=97=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 3 + .../main/assets/shaders/hdr_merge.comp.spv | Bin 0 -> 4772 bytes app/src/main/cpp/BmpLoader.cpp | 124 ++ app/src/main/cpp/BmpLoader.h | 54 + app/src/main/cpp/CMakeLists.txt | 87 +- app/src/main/cpp/HdrWriter.cpp | 170 +++ app/src/main/cpp/HdrWriter.h | 40 + app/src/main/cpp/MpPreview.cpp | 71 +- app/src/main/cpp/shaders/hdr_merge.comp | 72 + app/src/main/cpp/stb_image_write.h | 1178 +++++++++++++++++ app/src/main/cpp/vulkan_hdr_generator.cpp | 704 ++++++++++ app/src/main/cpp/vulkan_hdr_generator.h | 76 ++ .../xypower/mppreview/Camera2RawFragment.java | 5 + .../com/xypower/mppreview/MainActivity.java | 70 +- .../com/xypower/mppreview/utils/HdrUtil.java | 96 ++ app/src/main/res/layout/activity_main.xml | 9 + 16 files changed, 2755 insertions(+), 4 deletions(-) create mode 100644 app/src/main/assets/shaders/hdr_merge.comp.spv create mode 100644 app/src/main/cpp/BmpLoader.cpp create mode 100644 app/src/main/cpp/BmpLoader.h create mode 100644 app/src/main/cpp/HdrWriter.cpp create mode 100644 app/src/main/cpp/HdrWriter.h create mode 100644 app/src/main/cpp/shaders/hdr_merge.comp create mode 100644 app/src/main/cpp/stb_image_write.h create mode 100644 app/src/main/cpp/vulkan_hdr_generator.cpp create mode 100644 app/src/main/cpp/vulkan_hdr_generator.h diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c56cbf0..5c8fff4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + diff --git a/app/src/main/assets/shaders/hdr_merge.comp.spv b/app/src/main/assets/shaders/hdr_merge.comp.spv new file mode 100644 index 0000000000000000000000000000000000000000..c0276d50feaee22441107c5e91efa72ee7469c9b GIT binary patch literal 4772 zcmZ9OiE>=k5r&UF9+`ky%whw^j*J5VObG&*-DZq2vWP%(9B{zFVkC{Efssbc;w1|h z8)6{DKyV1eBrI8gkgz)&PN>QgDf-F^C;IZ|oin#CzC zNQ=|S>Ekr_ElmqyQaUO1WV(NF+hAY2Gv0UAy336?C6%&3V@@Tej9!FHSL-u6PC`zD zms^@)ZL1_!mdKvcKQR`i6I1`-NdM4K|KR44vDw*?c4u#+Hqxli)JDeZt=d?pzN=>3 z35B@&%vhs4UJNJ`QLJC4#tGEegOow`GuasFZ!|}%je(h6&9Q2y-kce@CG(fE-u7mA zQ91`bTy0gS+ld|YWHwZ)Pgf^vo0{F3j(j=e-Ouj&cxMV7oO_$9)hDO2xW!qV)2i*6 zZMM6u+FjK~w?=MzUI9Ow*DAyGY1jAE=yAM8^JTKG3$8s}YYo_hd@0=s-{Le}w*fpb zGu!Rl+?|-HwX$C1{Kl)Ds@k{;x^7&Srwvm|Ti_O-!_F?BD$N!?iMhDbtfV{+_Wp-j8lgj%M)(!40xI zpF`ledgkg$N3dg#w^qkGO%lt006*02d@%rQ&qLlXn`t)layp9L%wp$;*-stshjJdG zeKxuW@y>1`k9Wdv&73;UdH!Pt<-C8vdH!t`nBGtOGxf%6f+uvde_zaDHa zQ6tXPe17JWvxe&{=wd!?^ZDt2A6?vA%&3@8F7i3AVm@v2xp(vZ1MTeG(;0Ma->X^e zdhuDrSg#lH>&525$wYoR_E*qi*XTbHZGQP1a@!hmYq5=8g7_JKGFqQ}_+pb>) zHa2?Ie__t)zX9xg>}4fAd;a28V9#0GxSP=Ck+To&3L+PEH-i0Qznj1@+&nfoICE^PafH{NyMLHsUdH}ZEQav>4-1l+$qxO+p`Pa3=Ynw!G@G4>0eJhwjb zJcaEV-TLB+q2wWu=V@4hhM#8p6%Nne&c=Di#AX3mu%nn@LO+x z!FK(@dA4tT#Optj+Z&mZzwc8J-vEF2>zSFqpHmU}4NTPeX-E9&VB?*C#Ge6{H{SVb zhwm(~Ib82=^sC4U#NTej^n&HzCdM~qHR8Hn^UVbKaN7Tm?2x-gD_g%f&3N2G6f^ z4V-+$T${%f=XD*NeDrcX*md&O@%JMaeccSUcEsNTmfM7EWH+19w<49yPD#IOm!bO+ zedhIds2zC+z{R|`Vau5}X7Ek0ymvKf4T39~os#~jbvsy}wYH$Oqt+0(SZf$tu2}0^ z*z#MEsI?7T$?TN$N3Cyz^;zoR;&qLU*(--|b43;n6+kMy-#D4TgKaYU-A@<|nA?@%T06Vj=9|hZU z*pGo-Gt6)PPV|ou*BGbIocq!8t}*6uMD8$m()0OA&OL=T{$S2Ujc2gs4&?Da1pP4r;hi`3dBoUr5&ili_s_vkBgWXvA+%id@&Z^ca{U5q zu8RmoUene8}=LEdl1(<-!~CC<6N&# z&Ub7C?K`7(y|ei};%wsXzLj$;(XQ7gS9~XTVn_df0NZ2q^GC2Xan f5Z4>8-}~sjIfni_ax}A3vIl#UbB+IpT#I}T=ntVq literal 0 HcmV?d00001 diff --git a/app/src/main/cpp/BmpLoader.cpp b/app/src/main/cpp/BmpLoader.cpp new file mode 100644 index 0000000..c8fa77e --- /dev/null +++ b/app/src/main/cpp/BmpLoader.cpp @@ -0,0 +1,124 @@ +#include "BmpLoader.h" +#include +#include + +BmpInfo BmpLoader::readBmpInfo(const std::string& filePath) { + std::ifstream file(filePath, std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Failed to open BMP file: " + filePath); + } + + BmpHeader header; + file.read(reinterpret_cast(&header), sizeof(header)); + + if (header.signature != 0x4D42) { // 'BM' + throw std::runtime_error("Invalid BMP signature in file: " + filePath); + } + + BmpInfoHeader infoHeader; + file.read(reinterpret_cast(&infoHeader), sizeof(infoHeader)); + + BmpInfo info; + info.width = infoHeader.width; + info.height = std::abs(infoHeader.height); // Handle bottom-up or top-down BMPs + info.bitsPerPixel = infoHeader.bitsPerPixel; + info.dataOffset = header.dataOffset; + + // Calculate row padding (rows are padded to 4-byte boundary) + int bytesPerPixel = info.bitsPerPixel / 8; + info.rowSize = info.width * bytesPerPixel; + info.rowPadding = (4 - (info.rowSize % 4)) % 4; + + file.close(); + + return info; +} + +std::vector BmpLoader::readBmpRegion( + const std::string& filePath, + const BmpInfo& info, + int32_t startX, int32_t startY, + int32_t width, int32_t height) { + + std::ifstream file(filePath, std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Failed to open BMP file: " + filePath); + } + + // Clamp region to image dimensions + startX = std::max(0, startX); + startY = std::max(0, startY); + width = std::min(width, info.width - startX); + height = std::min(height, info.height - startY); + + if (width <= 0 || height <= 0) { + return std::vector(); + } + + // Calculate bytes per pixel + int bytesPerPixel = info.bitsPerPixel / 8; + int regionRowSize = width * bytesPerPixel; + + // Allocate memory for the region + std::vector regionData(width * height * bytesPerPixel); + + // Read data row by row + for (int y = 0; y < height; y++) { + // Calculate source position in file + int sourceY = (info.height - 1 - (startY + y)); // Account for BMP being stored bottom-up + long offset = info.dataOffset + (sourceY * (info.rowSize + info.rowPadding)) + startX * bytesPerPixel; + + file.seekg(offset, std::ios::beg); + file.read(reinterpret_cast(regionData.data() + y * regionRowSize), regionRowSize); + } + + file.close(); + + return regionData; +} + +// Convert sRGB to linear +float srgbToLinear(float srgb) { + if (srgb <= 0.04045f) + return srgb / 12.92f; + else + return pow((srgb + 0.055f) / 1.055f, 2.4f); +} + +std::vector BmpLoader::readBmpRegionAsFloat( + const std::string& filePath, + const BmpInfo& info, + int32_t startX, int32_t startY, + int32_t width, int32_t height) { + + auto data = readBmpRegion(filePath, info, startX, startY, width, height); + if (data.empty()) { + return std::vector(); + } + + int bytesPerPixel = info.bitsPerPixel / 8; + std::vector floatData(width * height * 3); // Always RGB float output + + // Convert each component from sRGB to linear space +#if 0 + for (int i = 0; i < width * height; i++) { + floatData[i * 3 + 0] = srgbToLinear(data[i * bytesPerPixel + 2] / 255.0f); // R + floatData[i * 3 + 1] = srgbToLinear(data[i * bytesPerPixel + 1] / 255.0f); // G + floatData[i * 3 + 2] = srgbToLinear(data[i * bytesPerPixel + 0] / 255.0f); // B + } +#endif + + for (int i = 0; i < width * height; i++) { + if (bytesPerPixel == 3 || bytesPerPixel == 4) { // RGB or RGBA + floatData[i * 3 + 0] = data[i * bytesPerPixel + 2] / 255.0f; // R (BGR format in BMP) + floatData[i * 3 + 1] = data[i * bytesPerPixel + 1] / 255.0f; // G + floatData[i * 3 + 2] = data[i * bytesPerPixel + 0] / 255.0f; // B + } else if (bytesPerPixel == 1) { // Grayscale + floatData[i * 3 + 0] = data[i] / 255.0f; // R + floatData[i * 3 + 1] = data[i] / 255.0f; // G + floatData[i * 3 + 2] = data[i] / 255.0f; // B + } + } + + return floatData; +} \ No newline at end of file diff --git a/app/src/main/cpp/BmpLoader.h b/app/src/main/cpp/BmpLoader.h new file mode 100644 index 0000000..029f5cf --- /dev/null +++ b/app/src/main/cpp/BmpLoader.h @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include +#include +#include + +#pragma pack(push, 1) +struct BmpHeader { + uint16_t signature; // 'BM' + uint32_t fileSize; // Size of the BMP file + uint16_t reserved1; // Reserved + uint16_t reserved2; // Reserved + uint32_t dataOffset; // Offset to the start of image data +}; + +struct BmpInfoHeader { + uint32_t headerSize; // Size of the info header + int32_t width; // Width of the image + int32_t height; // Height of the image + uint16_t planes; // Number of color planes + uint16_t bitsPerPixel; // Bits per pixel + uint32_t compression; // Compression type + uint32_t imageSize; // Image size in bytes + int32_t xPixelsPerMeter; // X resolution + int32_t yPixelsPerMeter; // Y resolution + uint32_t colorsUsed; // Number of colors used + uint32_t colorsImportant;// Number of important colors +}; +#pragma pack(pop) + +struct BmpInfo { + int32_t width; + int32_t height; + int32_t bitsPerPixel; + uint32_t dataOffset; + int32_t rowPadding; + int32_t rowSize; +}; + +class BmpLoader { +public: + static BmpInfo readBmpInfo(const std::string& filePath); + static std::vector readBmpRegion( + const std::string& filePath, + const BmpInfo& info, + int32_t startX, int32_t startY, + int32_t width, int32_t height); + static std::vector readBmpRegionAsFloat( + const std::string& filePath, + const BmpInfo& info, + int32_t startX, int32_t startY, + int32_t width, int32_t height); +}; \ No newline at end of file diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 4b9301d..4f33d52 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -21,6 +21,56 @@ ENDIF() project("mppreview") +message(WARNING "ANDROID_NDK ${ANDROID_NDK}") + +# Find glslc compiler from Vulkan SDK - use explicit path +if(ANDROID) + # For Android Studio/NDK builds + if(DEFINED ENV{ANDROID_NDK}) + set(GLSLC_EXECUTABLE "$ENV{ANDROID_NDK}/shader-tools/${ANDROID_ABI}/glslc") + else() + set(GLSLC_EXECUTABLE "${ANDROID_NDK}/shader-tools/${ANDROID_ABI}/glslc") + endif() +else() + # For Windows builds + if(DEFINED ENV{VULKAN_SDK}) + set(GLSLC_EXECUTABLE "$ENV{VULKAN_SDK}/Bin/glslc.exe") + else() + # Common installation paths + find_program(GLSLC_EXECUTABLE + NAMES glslc + PATHS + "C:/VulkanSDK/*/Bin" + "D:/VulkanSDK/*/Bin" + "$ENV{PROGRAMFILES}/VulkanSDK/*/Bin" + ) + endif() +endif() + + + +set(GLSLC_EXECUTABLE ${ANDROID_NDK}/shader-tools/windows-x86_64/glslc) + +if(NOT GLSLC_EXECUTABLE) + message(WARNING "Could not find glslc executable. Shader compilation will be skipped.") +endif() + +# Find Vulkan +find_package(Vulkan REQUIRED) +# Remove existing shaders directory to ensure clean copy +file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/shaders) + +# Create directory for shader files +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/shaders) + +# Copy each shader file individually +file(GLOB SHADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.comp" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.vert" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.frag" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.spv") +foreach(SHADER_FILE ${SHADER_FILES}) + get_filename_component(SHADER_FILENAME ${SHADER_FILE} NAME) + configure_file(${SHADER_FILE} ${CMAKE_CURRENT_BINARY_DIR}/shaders/${SHADER_FILENAME} COPYONLY) +endforeach() + + include_directories(D:/Workspace/deps/hdrplus_libs/${ANDROID_ABI}/include) link_directories(D:/Workspace/deps/hdrplus_libs/${ANDROID_ABI}/lib) @@ -48,7 +98,7 @@ endif(OpenCV_FOUND) find_package(OpenMP REQUIRED) add_library( # Sets the name of the library. - mppreview + ${PROJECT_NAME} # Sets the library as a shared library. SHARED @@ -56,6 +106,9 @@ add_library( # Sets the name of the library. # Provides a relative path to your source file(s). MpPreview.cpp HdrImpl.cpp + BmpLoader.cpp + vulkan_hdr_generator.cpp + HdrWriter.cpp ) @@ -65,6 +118,32 @@ add_library( # Sets the name of the library. # you want to add. CMake verifies that the library exists before # completing its build. + +# Find glslc compiler from Vulkan SDK +find_program(GLSLC_EXECUTABLE glslc REQUIRED) + +# First compile the shader +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/shaders/hdr_merge.comp.spv + COMMAND ${GLSLC_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/shaders/hdr_merge.comp -o ${CMAKE_CURRENT_BINARY_DIR}/shaders/hdr_merge.comp.spv + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/shaders/hdr_merge.comp + COMMENT "Compiling compute shader hdr_merge.comp" +) + +# CREATE THE MISSING TARGET - Add this line +add_custom_target(shaders DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/shaders/hdr_merge.comp.spv) + +# Now you can use the target in a post-build command +add_custom_command( + TARGET shaders POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_SOURCE_DIR}/../assets/shaders + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/shaders/hdr_merge.comp.spv ${CMAKE_CURRENT_SOURCE_DIR}/../assets/shaders/ + COMMENT "Copying shader to assets directory" +) + +# Make sure your main library depends on the shaders target - Add this line +add_dependencies(${PROJECT_NAME} shaders) + find_library( # Sets the name of the path variable. log-lib @@ -76,6 +155,12 @@ find_library( # Sets the name of the path variable. # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. + +# Link with Vulkan +target_include_directories(${PROJECT_NAME} PRIVATE ${Vulkan_INCLUDE_DIRS}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${Vulkan_LIBRARIES}) + + target_link_libraries( # Specifies the target library. ${PROJECT_NAME} PUBLIC -fopenmp -static-openmp diff --git a/app/src/main/cpp/HdrWriter.cpp b/app/src/main/cpp/HdrWriter.cpp new file mode 100644 index 0000000..d7c7033 --- /dev/null +++ b/app/src/main/cpp/HdrWriter.cpp @@ -0,0 +1,170 @@ +// +// Created by Matthew on 2025/3/22. +// + +#include "HdrWriter.h" + +#include +#include +#include + +// Define STB_IMAGE_WRITE_IMPLEMENTATION in exactly one CPP file +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" // Download this header from https://github.com/nothings/stb + +float toneMap(float value) { + return value / (1.0f + value); // Reinhard tone mapping +} +float applyGammaCorrection(float value) { + return pow(value, 1.0f / 2.2f); // Gamma correction +} + +bool HdrWriter::writeRGBE(const std::string& filename, + const std::vector& data, + int width, int height) { + std::ofstream file(filename, std::ios::binary); + if (!file.is_open()) { + return false; + } + + writeHeader(file, width, height); + + // Write pixel data in RGBE format + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pixelIndex = (y * width + x) * 3; + float r = data[pixelIndex]; + float g = data[pixelIndex + 1]; + float b = data[pixelIndex + 2]; + + uint8_t rgbe[4]; + rgbeFromFloat(r, g, b, rgbe); + file.write(reinterpret_cast(rgbe), 4); + } + } + + return true; +} + +void HdrWriter::writeHeader(std::ofstream& file, int width, int height) { + // Write Radiance HDR header + file << "#?RADIANCE\n"; + file << "FORMAT=32-bit_rle_rgbe\n\n"; + file << "-Y " << height << " +X " << width << "\n"; +} + +void HdrWriter::rgbeFromFloat(float r, float g, float b, uint8_t rgbe[4]) { + float v = std::max(r, std::max(g, b)); + + if (v < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + int e; + v = frexpf(v, &e) * 256.0f / v; + rgbe[0] = static_cast(r * v); + rgbe[1] = static_cast(g * v); + rgbe[2] = static_cast(b * v); + rgbe[3] = static_cast(e + 128); + } +} + + +bool HdrWriter::writeRGB(const std::string& filename, + const std::vector& data, + int width, int height, + Format format) { + + if (format == Format::BMP) { + return writeBMP(filename, data, width, height); + } + + // For PNG/JPEG via stb_image_write.h + // (Add this implementation if needed) + return false; +} + +bool HdrWriter::writeBMP(const std::string& filename, + const std::vector& data, + int width, int height) { + std::ofstream file(filename, std::ios::binary); + if (!file.is_open()) { + return false; + } + + // BMP requires rows to be padded to multiples of 4 bytes + int paddingSize = (4 - (width * 3) % 4) % 4; + int rowSize = width * 3 + paddingSize; + int dataSize = rowSize * height; + int fileSize = 54 + dataSize; // 54 bytes for header + pixel data + + // BMP File Header (14 bytes) + uint8_t fileHeader[14] = { + 'B', 'M', // Signature + (uint8_t)(fileSize), (uint8_t)(fileSize >> 8), // File size in bytes + (uint8_t)(fileSize >> 16), (uint8_t)(fileSize >> 24), + 0, 0, 0, 0, // Reserved + 54, 0, 0, 0 // Offset to pixel data + }; + + // BMP Info Header (40 bytes) + uint8_t infoHeader[40] = { + 40, 0, 0, 0, // Info header size + (uint8_t)(width), (uint8_t)(width >> 8), // Width + (uint8_t)(width >> 16), (uint8_t)(width >> 24), + (uint8_t)(height), (uint8_t)(height >> 8), // Height (negative for top-down) + (uint8_t)(height >> 16), (uint8_t)(height >> 24), + 1, 0, // Number of color planes + 24, 0, // Bits per pixel (24 for RGB) + 0, 0, 0, 0, // No compression + 0, 0, 0, 0, // Image size (can be 0 for no compression) + 0, 0, 0, 0, // X pixels per meter + 0, 0, 0, 0, // Y pixels per meter + 0, 0, 0, 0, // Total colors (default) + 0, 0, 0, 0 // Important colors (default) + }; + + file.write(reinterpret_cast(fileHeader), 14); + file.write(reinterpret_cast(infoHeader), 40); + + // Padding bytes (zeros) + std::vector padding(paddingSize, 0); + + // Write pixel data (BGR order, bottom-to-top for standard BMP) + for (int y = height - 1; y >= 0; y--) { // BMP stores rows bottom-to-top + for (int x = 0; x < width; x++) { + int pixelIndex = (y * width + x) * 3; + + // Apply tone mapping and gamma correction +#if 0 + float r = toneMap(data[pixelIndex + 0]); + float g = toneMap(data[pixelIndex + 1]); + float b = toneMap(data[pixelIndex + 2]); +#endif + + float exposure = 1.5f; // Adjust this value as needed + float r = toneMap(data[pixelIndex + 0] * exposure); + float g = toneMap(data[pixelIndex + 1] * exposure); + float b = toneMap(data[pixelIndex + 2] * exposure); + + r = applyGammaCorrection(r); + g = applyGammaCorrection(g); + b = applyGammaCorrection(b); + + // Clamp and convert to byte + uint8_t pixelData[3] = { + static_cast(std::min(1.0f, b) * 255.0f), // B + static_cast(std::min(1.0f, g) * 255.0f), // G + static_cast(std::min(1.0f, r) * 255.0f) // R + }; + + file.write(reinterpret_cast(pixelData), 3); + } + + // Write padding bytes + if (paddingSize > 0) { + file.write(reinterpret_cast(padding.data()), paddingSize); + } + } + + return file.good(); +} diff --git a/app/src/main/cpp/HdrWriter.h b/app/src/main/cpp/HdrWriter.h new file mode 100644 index 0000000..82e600b --- /dev/null +++ b/app/src/main/cpp/HdrWriter.h @@ -0,0 +1,40 @@ +// +// Created by Matthew on 2025/3/22. +// + +#ifndef MPPREVIEW_HDRWRITER_H +#define MPPREVIEW_HDRWRITER_H + + +#include +#include +#include +#include + +class HdrWriter { +public: + static bool writeRGBE(const std::string& filename, + const std::vector& data, + int width, int height); + + // New RGB file format support + enum class Format { + PNG, + JPEG, + BMP + }; + + static bool writeRGB(const std::string& filename, + const std::vector& data, + int width, int height, + Format format = Format::PNG); + +private: + static void writeHeader(std::ofstream& file, int width, int height); + static void rgbeFromFloat(float r, float g, float b, uint8_t rgbe[4]); + static bool writeBMP(const std::string& filename, + const std::vector& data, + int width, int height); +}; + +#endif //MPPREVIEW_HDRWRITER_H diff --git a/app/src/main/cpp/MpPreview.cpp b/app/src/main/cpp/MpPreview.cpp index c1165f4..1f5529e 100644 --- a/app/src/main/cpp/MpPreview.cpp +++ b/app/src/main/cpp/MpPreview.cpp @@ -18,6 +18,9 @@ #include "hdr.h" +#include "BmpLoader.h" +#include "vulkan_hdr_generator.h" + namespace cv2 { using namespace cv; @@ -434,6 +437,7 @@ Java_com_xypower_mppreview_Camera2RawFragment_makeHdr3(JNIEnv *env, jclass clazz if ((ANDROID_BITMAP_FLAGS_IS_HARDWARE & bmpInfo.flags) == ANDROID_BITMAP_FLAGS_IS_HARDWARE) { +#if 0 AHardwareBuffer* hardwareBuffer = NULL; result = AndroidBitmap_getHardwareBuffer(env, bitmaps[idx], &hardwareBuffer); @@ -444,6 +448,7 @@ Java_com_xypower_mppreview_Camera2RawFragment_makeHdr3(JNIEnv *env, jclass clazz tmp.copyTo(images[idx]); AHardwareBuffer_unlock(hardwareBuffer, &fence); AHardwareBuffer_release(hardwareBuffer); +#endif } else { @@ -492,9 +497,73 @@ Java_com_xypower_mppreview_Camera2RawFragment_makeHdr3(JNIEnv *env, jclass clazz return JNI_FALSE; } + + extern "C" JNIEXPORT jboolean JNICALL Java_com_xypower_mppreview_Camera2RawFragment_decodeDng(JNIEnv *env, jclass clazz, jobject byte_buffer, jstring output_path) { // TODO: implement decodeDng() -} \ No newline at end of file +} + + +extern "C" +JNIEXPORT jboolean JNICALL +Java_com_xypower_mppreview_Camera2RawFragment_makeHdr5(JNIEnv *env, jclass clazz, + jstring compFile, + jlong exposureTime1, jstring img1, + jlong exposureTime2, jstring img2, + jstring outputPath) { + + + std::string compFilePath = jstring2string(env, compFile); + std::string outputFile = jstring2string(env, outputPath); + std::vector inputFiles; + std::vector exposureValues; + + inputFiles.push_back(jstring2string(env, img1)); + inputFiles.push_back(jstring2string(env, img2)); + + exposureValues.push_back((double)(exposureTime1) / 1000000000.0); + exposureValues.push_back((double)(exposureTime2) / 1000000000.0); + + if (inputFiles.empty()) { + // std::cerr << "No input BMP files specified" << std::endl; + return JNI_FALSE; + } + + // std::cout << "Processing " << inputFiles.size() << " images..." << std::endl; + for (size_t i = 0; i < inputFiles.size(); i++) { + // std::cout << " " << inputFiles[i] << " (exposure: " << exposureValues[i] << ")" << std::endl; + } + + try { + // Compile shader if needed (in a production app, you'd precompile these) +#ifdef _WIN32 + // system("glslc.exe shaders/hdr_merge.comp -o shaders/hdr_merge.comp.spv"); +#else + // system("glslc shaders/hdr_merge.comp -o shaders/hdr_merge.comp.spv"); +#endif + + // Create HDR generator and process images + VulkanHdrGenerator generator(compFilePath); + + // Process with 256x256 tiles to keep memory usage low + bool success = generator.generateHdr(inputFiles, outputFile, exposureValues, 256, 256); + + if (success) { + // std::cout << "HDR image successfully created: " << outputFile << std::endl; + return JNI_TRUE; + } else { + // std::cerr << "Failed to generate HDR image" << std::endl; + return JNI_FALSE; + } + } catch (const std::exception& e) + { + ALOGE("Error: %s", e.what()); + return JNI_FALSE; + } + + return JNI_FALSE; +} + diff --git a/app/src/main/cpp/shaders/hdr_merge.comp b/app/src/main/cpp/shaders/hdr_merge.comp new file mode 100644 index 0000000..f099ee1 --- /dev/null +++ b/app/src/main/cpp/shaders/hdr_merge.comp @@ -0,0 +1,72 @@ +#version 450 + +layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +// Input images packed into one buffer +layout(set = 0, binding = 0) buffer InputBuffer { + float data[]; +} inputImages; + +// Output HDR image +layout(set = 0, binding = 1) buffer OutputBuffer { + vec4 pixels[]; // Make sure this matches your C++ expectations +} outputImage; + +// Check if parameter layout matches C++ struct +layout(set = 0, binding = 2) uniform Params { + uint imageCount; + uint width; + uint height; + float exposureValues[16]; // Must match C++ struct size +} params; + +void main() { + uint x = gl_GlobalInvocationID.x; + uint y = gl_GlobalInvocationID.y; + + if (x >= params.width || y >= params.height) + return; + + uint pixelIndex = y * params.width + x; + uint pixelsPerImage = params.width * params.height; + + // Debug RGB values for the first image + float r0 = inputImages.data[pixelIndex * 3]; + float g0 = inputImages.data[pixelIndex * 3 + 1]; + float b0 = inputImages.data[pixelIndex * 3 + 2]; + + // HDR merging logic - weighted average with exposure + vec3 hdrPixel = vec3(0.0); + float weightSum = 0.0; + + for (uint i = 0; i < params.imageCount; i++) { + // FIXED: Correct buffer access pattern for packed images + uint baseOffset = i * pixelsPerImage * 3; + float r = inputImages.data[baseOffset + pixelIndex * 3 + 0]; + float g = inputImages.data[baseOffset + pixelIndex * 3 + 1]; + float b = inputImages.data[baseOffset + pixelIndex * 3 + 2]; + + vec3 rgb = vec3(r, g, b); + + // Calculate luminance for weighting + float lum = dot(rgb, vec3(0.2126, 0.7152, 0.0722)); + + // Well-exposed pixels get higher weight + float weight = 1.0 - pow(abs(lum - 0.5) * 2.0, 2.0); + weight = max(weight, 0.001); + + // Apply exposure value + float exposureFactor = params.exposureValues[i]; + + hdrPixel += rgb * weight * exposureFactor; + weightSum += weight; + } + + // Apply stronger exposure boost directly in the shader + // hdrPixel *= 2.5; // Boost by 2.5x + // Normalize + hdrPixel = hdrPixel / max(weightSum, 0.001); + + // Store the result + outputImage.pixels[pixelIndex] = vec4(hdrPixel, 1.0); +} \ No newline at end of file diff --git a/app/src/main/cpp/stb_image_write.h b/app/src/main/cpp/stb_image_write.h new file mode 100644 index 0000000..cef6a3d --- /dev/null +++ b/app/src/main/cpp/stb_image_write.h @@ -0,0 +1,1178 @@ +/* stb_image_write - v1.02 - public domain - +http://nothings.org/stb/stb_image_write.h writes out PNG/BMP/TGA images to C +stdio - Sean Barrett 2010-2015 no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio. It could be + adapted to write to memory or a general streaming interface; let me know. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation. This library is designed + for source code compactness and simplicity, not optimal image file size + or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can define STBIW_MEMMOVE() to replace memmove() + +USAGE: + + There are four functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void +*data, int stride_in_bytes); int stbi_write_bmp(char const *filename, int w, int +h, int comp, const void *data); int stbi_write_tga(char const *filename, int w, +int h, int comp, const void *data); int stbi_write_hdr(char const *filename, int +w, int h, int comp, const float *data); + + There are also four equivalent functions that use an arbitrary write +function. You are expected to open/close your file-equivalent before and after +calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int +h, int comp, const void *data, int stride_in_bytes); int +stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int +comp, const void *data); int stbi_write_tga_to_func(stbi_write_func *func, void +*context, int w, int h, int comp, const void *data); int +stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int +comp, const float *data); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + +CREDITS: + + PNG/BMP/TGA + Sean Barrett + HDR + Baldur Karlsson + TGA monochrome: + Jean-Sebastien Guay + misc enhancements: + Tim Kelsey + TGA RLE + Alan Hickman + initial file IO callback implementation + Emmanuel Julien + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + +LICENSE + +This software is dual-licensed to the public domain and under the following +license: you are granted a perpetual, irrevocable license to copy, modify, +publish, and distribute this file as you see fit. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#define STBIWDEF extern +// STBIWDEF int stbi_write_tga_with_rle; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, + const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, + const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, + const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, + const float *data); +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, + int h, int comp, const void *data, + int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, + int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, + int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, + int h, int comp, const float *data); + +#ifdef __cplusplus +} +#endif + +#endif // INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && \ + (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && \ + !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error \ + "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p, newsz) realloc(p, newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p, oldsz, newsz) STBIW_REALLOC(p, newsz) +#endif + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a, b, sz) memmove(a, b, sz) +#endif + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char)((x) & 0xff) + +typedef struct { + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, + stbi_write_func *c, void *context) { + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) { + fwrite(data, 1, size, (FILE *)context); +} + +static int stbi__start_write_file(stbi__write_context *s, + const char *filename) { + FILE *f = fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *)f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) { + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32) == 4 ? 1 : -1]; + +#ifdef STB_IMAGE_WRITE_STATIC +#define stbi_write_tga_with_rle 1 +#else +#define stbi_write_tga_with_rle 1 +#endif + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) { + while (*fmt) { + switch (*fmt++) { + case ' ': + break; + case '1': { + unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context, &x, 1); + break; + } + case '2': { + int x = va_arg(v, int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x >> 8); + s->func(s->context, b, 2); + break; + } + case '4': { + stbiw_uint32 x = va_arg(v, int); + unsigned char b[4]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x >> 8); + b[2] = STBIW_UCHAR(x >> 16); + b[3] = STBIW_UCHAR(x >> 24); + s->func(s->context, b, 4); + break; + } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, + unsigned char b, unsigned char c) { + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + s->func(s->context, arr, 3); +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, + int write_alpha, int expand_mono, + unsigned char *d) { + unsigned char bg[3] = {255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + s->func(s->context, &d[comp - 1], 1); + + switch (comp) { + case 1: + s->func(s->context, d, 1); + break; + case 2: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + s->func(s->context, d, 1); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + s->func(s->context, &d[comp - 1], 1); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, + int x, int y, int comp, void *data, + int write_alpha, int scanline_pad, + int expand_mono) { + stbiw_uint32 zero = 0; + int i, j, j_end; + + if (y <= 0) + return; + + if (vdir < 0) + j_end = -1, j = y - 1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i = 0; i < x; ++i) { + unsigned char *d = (unsigned char *)data + (j * x + i) * comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, + int y, int comp, int expand_mono, void *data, + int alpha, int pad, const char *fmt, ...) { + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s, rgb_dir, vdir, x, y, comp, data, alpha, pad, + expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, + const void *data) { + int pad = (-x * 3) & 3; + return stbiw__outfile(s, -1, -1, x, y, comp, 1, (void *)data, 0, pad, + "11 4 22 4" + "4 44 22 444444", + 'B', 'M', 14 + 40 + (x * 3 + pad) * y, 0, 0, + 14 + 40, // file header + 40, x, y, 1, 24, 0, 0, 0, 0, 0, 0); // bitmap header +} + +STBIWDEF inline int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, + int y, int comp, const void *data) { + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF inline int stbi_write_bmp(char const *filename, int x, int y, int comp, + const void *data) { + stbi__write_context s; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //! STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, + void *data) { + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp - 1 : comp; + int format = + colorbytes < 2 + ? 3 + : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *)data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, + (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i, j, k; + + stbiw__writef(s, "111 221 2222 11", 0, 0, format + 8, 0, 0, 0, 0, 0, x, y, + (colorbytes + has_alpha) * 8, has_alpha * 8); + + for (j = y - 1; j >= 0; --j) { + unsigned char *row = (unsigned char *)data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + s->func(s->context, &header, 1); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + s->func(s->context, &header, 1); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + } + return 1; +} + +inline int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, + int comp, const void *data) { + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *)data); +} + +#ifndef STBI_WRITE_NO_STDIO +inline int stbi_write_tga(char const *filename, int x, int y, int comp, + const void *data) { + stbi__write_context s; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *)data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson +#ifndef STBI_WRITE_NO_STDIO + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) { + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float)frexp(maxcomp, &exponent) * 256.0f / maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +void stbiw__write_run_data(stbi__write_context *s, int length, + unsigned char databyte) { + unsigned char lengthbyte = STBIW_UCHAR(length + 128); + STBIW_ASSERT(length + 128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +void stbiw__write_dump_data(stbi__write_context *s, int length, + unsigned char *data) { + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= + 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, + unsigned char *scratch, float *scanline) { + unsigned char scanlineheader[4] = {2, 2, 0, 0}; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width & 0xff00) >> 8; + scanlineheader[3] = (width & 0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x = 0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: + linear[2] = scanline[x * ncomp + 2]; + linear[1] = scanline[x * ncomp + 1]; + linear[0] = scanline[x * ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x * ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c, r; + /* encode into scratch buffer */ + for (x = 0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: + linear[2] = scanline[x * ncomp + 2]; + linear[1] = scanline[x * ncomp + 1]; + linear[0] = scanline[x * ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x * ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width * 0] = rgbe[0]; + scratch[x + width * 1] = rgbe[1]; + scratch[x + width * 2] = rgbe[2]; + scratch[x + width * 3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c = 0; c < 4; c++) { + unsigned char *comp = &scratch[width * c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r + 2 < width) { + if (comp[r] == comp[r + 1] && comp[r] == comp[r + 2]) + break; + ++r; + } + if (r + 2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r - x; + if (len > 128) + len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r + 2 < width) { // same test as what we break out of in search + // loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r - x; + if (len > 127) + len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +inline int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, + float *data) { + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full + // output scanline. + unsigned char *scratch = (unsigned char *)STBIW_MALLOC(x * 4); + int i, len; + char buffer[128]; + char header[] = + "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header) - 1); + + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", + y, x); + s->func(s->context, buffer, len); + + for (i = 0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp * i * x); + STBIW_FREE(scratch); + return 1; + } +} + +inline int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, + int comp, const float *data) { + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *)data); +} + +inline int stbi_write_hdr(char const *filename, int x, int y, int comp, + const float *data) { + stbi__write_context s; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *)data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() +// == vector<>::size() +#define stbiw__sbraw(a) ((int *)(a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a, n) ((a) == 0 || stbiw__sbn(a) + n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a, n) \ + (stbiw__sbneedgrow(a, (n)) ? stbiw__sbgrow(a, n) : 0) +#define stbiw__sbgrow(a, n) stbiw__sbgrowf((void **)&(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) \ + (stbiw__sbmaybegrow(a, 1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)), 0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) { + int m = *arr ? 2 * stbiw__sbm(*arr) + increment : increment + 1; + void *p = STBIW_REALLOC_SIZED( + *arr ? stbiw__sbraw(*arr) : 0, + *arr ? (stbiw__sbm(*arr) * itemsize + sizeof(int) * 2) : 0, + itemsize * m + sizeof(int) * 2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) + ((int *)p)[1] = 0; + *arr = (void *)((int *)p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, + unsigned int *bitbuffer, + int *bitcount) { + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) { + int res = 0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, + int limit) { + int i; + for (i = 0; i < limit && i < 258; ++i) + if (a[i] != b[i]) + break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) { + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code, codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b, c) stbiw__zlib_add(stbiw__zlib_bitrev(b, c), c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n) - 144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n) - 256, 7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n) - 280, 8) +#define stbiw__zlib_huff(n) \ + ((n) <= 143 ? stbiw__zlib_huff1(n) \ + : (n) <= 255 ? stbiw__zlib_huff2(n) \ + : (n) <= 279 ? stbiw__zlib_huff3(n) \ + : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) \ + ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +unsigned char *stbi_zlib_compress(unsigned char *data, int data_len, + int *out_len, int quality) { + static unsigned short lengthc[] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, + 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 259}; + static unsigned char lengtheb[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static unsigned short distc[] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, + 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, + 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 32768}; + static unsigned char disteb[] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, + 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + unsigned int bitbuf = 0; + int i, j, bitcount = 0; + unsigned char *out = NULL; + unsigned char ***hash_table = + (unsigned char ***)STBIW_MALLOC(stbiw__ZHASH * sizeof(char **)); + if (quality < 5) + quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1, 1); // BFINAL = 1 + stbiw__zlib_add(1, 2); // BTYPE = 1 -- fixed huffman + + for (i = 0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i = 0; + while (i < data_len - 3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data + i) & (stbiw__ZHASH - 1), best = 3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j = 0; j < n; ++j) { + if (hlist[j] - data > i - 32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data + i, data_len - i); + if (d >= best) + best = d, bestloc = hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2 * quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h] + quality, + sizeof(hash_table[h][0]) * quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h], data + i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do + // cur byte as literal + h = stbiw__zhash(data + i + 1) & (stbiw__ZHASH - 1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j = 0; j < n; ++j) { + if (hlist[j] - data > i - 32767) { + int e = stbiw__zlib_countm(hlist[j], data + i + 1, data_len - i - 1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int)(data + i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j = 0; best > lengthc[j + 1] - 1; ++j) + ; + stbiw__zlib_huff(j + 257); + if (lengtheb[j]) + stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j = 0; d > distc[j + 1] - 1; ++j) + ; + stbiw__zlib_add(stbiw__zlib_bitrev(j, 5), 5); + if (disteb[j]) + stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (; i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0, 1); + + for (i = 0; i < stbiw__ZHASH; ++i) + (void)stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + // compute adler32 on input + unsigned int s1 = 1, s2 = 0; + int blocklen = (int)(data_len % 5552); + j = 0; + while (j < data_len) { + for (i = 0; i < blocklen; ++i) + s1 += data[j + i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *)stbiw__sbraw(out); +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) { + static unsigned int crc_table[256] = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D}; + + unsigned int crc = ~0u; + int i; + for (i = 0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +#define stbiw__wpng4(o, a, b, c, d) \ + ((o)[0] = STBIW_UCHAR(a), (o)[1] = STBIW_UCHAR(b), (o)[2] = STBIW_UCHAR(c), \ + (o)[3] = STBIW_UCHAR(d), (o) += 4) +#define stbiw__wp32(data, v) \ + stbiw__wpng4(data, (v) >> 24, (v) >> 16, (v) >> 8, (v)); +#define stbiw__wptag(data, s) stbiw__wpng4(data, s[0], s[1], s[2], s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) { + unsigned int crc = stbiw__crc32(*data - len - 4, len + 4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) { + int p = a + b - c, pa = abs(p - a), pb = abs(p - b), pc = abs(p - c); + if (pa <= pb && pa <= pc) + return STBIW_UCHAR(a); + if (pb <= pc) + return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +inline unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, + int x, int y, int n, int *out_len) { + int ctype[5] = {-1, 0, 4, 2, 6}; + unsigned char sig[8] = {137, 80, 78, 71, 13, 10, 26, 10}; + unsigned char *out, *o, *filt, *zlib; + signed char *line_buffer; + int i, j, k, p, zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + filt = (unsigned char *)STBIW_MALLOC((x * n + 1) * y); + if (!filt) + return 0; + line_buffer = (signed char *)STBIW_MALLOC(x * n); + if (!line_buffer) { + STBIW_FREE(filt); + return 0; + } + for (j = 0; j < y; ++j) { + static int mapping[] = {0, 1, 2, 3, 4}; + static int firstmap[] = {0, 1, 0, 5, 6}; + int *mymap = j ? mapping : firstmap; + int best = 0, bestval = 0x7fffffff; + for (p = 0; p < 2; ++p) { + for (k = p ? best : 0; k < 5; ++k) { + int type = mymap[k], est = 0; + unsigned char *z = pixels + stride_bytes * j; + for (i = 0; i < n; ++i) + switch (type) { + case 0: + line_buffer[i] = z[i]; + break; + case 1: + line_buffer[i] = z[i]; + break; + case 2: + line_buffer[i] = z[i] - z[i - stride_bytes]; + break; + case 3: + line_buffer[i] = z[i] - (z[i - stride_bytes] >> 1); + break; + case 4: + line_buffer[i] = + (signed char)(z[i] - stbiw__paeth(0, z[i - stride_bytes], 0)); + break; + case 5: + line_buffer[i] = z[i]; + break; + case 6: + line_buffer[i] = z[i]; + break; + } + for (i = n; i < x * n; ++i) { + switch (type) { + case 0: + line_buffer[i] = z[i]; + break; + case 1: + line_buffer[i] = z[i] - z[i - n]; + break; + case 2: + line_buffer[i] = z[i] - z[i - stride_bytes]; + break; + case 3: + line_buffer[i] = z[i] - ((z[i - n] + z[i - stride_bytes]) >> 1); + break; + case 4: + line_buffer[i] = z[i] - stbiw__paeth(z[i - n], z[i - stride_bytes], + z[i - stride_bytes - n]); + break; + case 5: + line_buffer[i] = z[i] - (z[i - n] >> 1); + break; + case 6: + line_buffer[i] = z[i] - stbiw__paeth(z[i - n], 0, 0); + break; + } + } + if (p) + break; + for (i = 0; i < x * n; ++i) + est += abs((signed char)line_buffer[i]); + if (est < bestval) { + bestval = est; + best = k; + } + } + } + // when we get here, best contains the filter type, and line_buffer contains + // the data + filt[j * (x * n + 1)] = (unsigned char)best; + STBIW_MEMMOVE(filt + j * (x * n + 1) + 1, line_buffer, x * n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y * (x * n + 1), &zlen, + 8); // increase 8 to get smaller but use more memory + STBIW_FREE(filt); + if (!zlib) + return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *)STBIW_MALLOC(8 + 12 + 13 + 12 + zlen + 12); + if (!out) + return 0; + *out_len = 8 + 12 + 13 + 12 + zlen + 12; + + o = out; + STBIW_MEMMOVE(o, sig, 8); + o += 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o, 13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o, 0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o, 0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF inline int stbi_write_png(char const *filename, int x, int y, int comp, + const void *data, int stride_bytes) { + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *)data, + stride_bytes, x, y, comp, &len); + if (png == NULL) + return 0; + f = fopen(filename, "wb"); + if (!f) { + STBIW_FREE(png); + return 0; + } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF inline int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, + int y, int comp, const void *data, + int stride_bytes) { + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *)data, + stride_bytes, x, y, comp, &len); + if (png == NULL) + return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ diff --git a/app/src/main/cpp/vulkan_hdr_generator.cpp b/app/src/main/cpp/vulkan_hdr_generator.cpp new file mode 100644 index 0000000..309de4f --- /dev/null +++ b/app/src/main/cpp/vulkan_hdr_generator.cpp @@ -0,0 +1,704 @@ +// +// Created by Matthew on 2025/3/22. +// + +#include "vulkan_hdr_generator.h" + +#include "vulkan_hdr_generator.h" +#include "HdrWriter.h" +#include +#include +#include +#include +#include + +#include +#include + +#define MP_TAG "VOLKAN" +#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, MP_TAG,__VA_ARGS__) +#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, MP_TAG,__VA_ARGS__) +#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, MP_TAG, __VA_ARGS__) +#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, MP_TAG, __VA_ARGS__) +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, MP_TAG,__VA_ARGS__) + +VulkanHdrGenerator::VulkanHdrGenerator(const std::string& compFile) : compFilePath(compFile) { + + // Add initializers for all Vulkan handles + instance = VK_NULL_HANDLE; + physicalDevice = VK_NULL_HANDLE; + device = VK_NULL_HANDLE; + computeQueue = VK_NULL_HANDLE; + commandPool = VK_NULL_HANDLE; + descriptorPool = VK_NULL_HANDLE; + commandBuffer = VK_NULL_HANDLE; + + inputBuffer = VK_NULL_HANDLE; + inputBufferMemory = VK_NULL_HANDLE; + outputBuffer = VK_NULL_HANDLE; + outputBufferMemory = VK_NULL_HANDLE; + paramsBuffer = VK_NULL_HANDLE; + paramsBufferMemory = VK_NULL_HANDLE; + + computeShaderModule = VK_NULL_HANDLE; + computePipeline = VK_NULL_HANDLE; + pipelineLayout = VK_NULL_HANDLE; + descriptorSetLayout = VK_NULL_HANDLE; + descriptorSet = VK_NULL_HANDLE; + + setupVulkan(); + createComputeResources(); +} + +VulkanHdrGenerator::~VulkanHdrGenerator() { + cleanup(); +} + +void VulkanHdrGenerator::setupVulkan() { + // Create Vulkan instance + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "HDR Generator"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("Failed to create Vulkan instance"); + } + + // Select physical device + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + + if (deviceCount == 0) { + throw std::runtime_error("Failed to find GPUs with Vulkan support"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + // Just use the first device + physicalDevice = devices[0]; + + // Find compute queue family index + computeQueueFamilyIndex = findComputeQueueFamily(physicalDevice); + + // Create logical device + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = computeQueueFamilyIndex; + queueCreateInfo.queueCount = 1; + float queuePriority = 1.0f; + queueCreateInfo.pQueuePriorities = &queuePriority; + + VkPhysicalDeviceFeatures deviceFeatures{}; + + VkDeviceCreateInfo deviceCreateInfo{}; + deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; + deviceCreateInfo.queueCreateInfoCount = 1; + deviceCreateInfo.pEnabledFeatures = &deviceFeatures; + + if (vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("Failed to create logical device"); + } + + vkGetDeviceQueue(device, computeQueueFamilyIndex, 0, &computeQueue); + + // Create command pool + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.queueFamilyIndex = computeQueueFamilyIndex; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("Failed to create command pool"); + } + + // Create descriptor pool + VkDescriptorPoolSize poolSizes[2]; + poolSizes[0].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + poolSizes[0].descriptorCount = 3; // Input, output, params + poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + poolSizes[1].descriptorCount = 1; + + VkDescriptorPoolCreateInfo descriptorPoolInfo{}; + descriptorPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + descriptorPoolInfo.poolSizeCount = 2; + descriptorPoolInfo.pPoolSizes = poolSizes; + descriptorPoolInfo.maxSets = 1; + + if (vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor pool"); + } + + // Allocate command buffer + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate command buffer"); + } +} + +void VulkanHdrGenerator::createComputeResources() { + // Create descriptor set layout + VkDescriptorSetLayoutBinding bindings[3]; + + bindings[0].binding = 0; + bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bindings[0].descriptorCount = 1; + bindings[0].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bindings[0].pImmutableSamplers = nullptr; + + bindings[1].binding = 1; + bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bindings[1].descriptorCount = 1; + bindings[1].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bindings[1].pImmutableSamplers = nullptr; + + bindings[2].binding = 2; + bindings[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[2].descriptorCount = 1; + bindings[2].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + bindings[2].pImmutableSamplers = nullptr; + + VkDescriptorSetLayoutCreateInfo layoutInfo{}; + layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layoutInfo.bindingCount = 3; + layoutInfo.pBindings = bindings; + + if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create descriptor set layout"); + } + + // Create pipeline layout + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 1; + pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("Failed to create pipeline layout"); + } + + // Load shader + std::vector shaderCode = readFile(compFilePath); + VkShaderModule shaderModule = createShaderModule(shaderCode); + + // Create compute pipeline + VkPipelineShaderStageCreateInfo shaderStageInfo{}; + shaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shaderStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; + shaderStageInfo.module = shaderModule; + shaderStageInfo.pName = "main"; + + VkComputePipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + pipelineInfo.stage = shaderStageInfo; + pipelineInfo.layout = pipelineLayout; + + if (vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &computePipeline) != VK_SUCCESS) { + throw std::runtime_error("Failed to create compute pipeline"); + } + + vkDestroyShaderModule(device, shaderModule, nullptr); +} + +void VulkanHdrGenerator::createBuffers(VkDeviceSize inputSize, VkDeviceSize outputSize) { + // Create input buffer + VkBufferCreateInfo inputBufferInfo{}; + inputBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + inputBufferInfo.size = inputSize; + inputBufferInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + inputBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &inputBufferInfo, nullptr, &inputBuffer) != VK_SUCCESS) { + throw std::runtime_error("Failed to create input buffer"); + } + + VkMemoryRequirements inputMemRequirements; + vkGetBufferMemoryRequirements(device, inputBuffer, &inputMemRequirements); + + VkMemoryAllocateInfo inputAllocInfo{}; + inputAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + inputAllocInfo.allocationSize = inputMemRequirements.size; + inputAllocInfo.memoryTypeIndex = findMemoryType(inputMemRequirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + if (vkAllocateMemory(device, &inputAllocInfo, nullptr, &inputBufferMemory) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate input buffer memory"); + } + + vkBindBufferMemory(device, inputBuffer, inputBufferMemory, 0); + + // Create output buffer + VkBufferCreateInfo outputBufferInfo{}; + outputBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + outputBufferInfo.size = outputSize; + outputBufferInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + outputBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &outputBufferInfo, nullptr, &outputBuffer) != VK_SUCCESS) { + throw std::runtime_error("Failed to create output buffer"); + } + + VkMemoryRequirements outputMemRequirements; + vkGetBufferMemoryRequirements(device, outputBuffer, &outputMemRequirements); + + VkMemoryAllocateInfo outputAllocInfo{}; + outputAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + outputAllocInfo.allocationSize = outputMemRequirements.size; + outputAllocInfo.memoryTypeIndex = findMemoryType(outputMemRequirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + if (vkAllocateMemory(device, &outputAllocInfo, nullptr, &outputBufferMemory) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate output buffer memory"); + } + + vkBindBufferMemory(device, outputBuffer, outputBufferMemory, 0); + + // Create params buffer + VkBufferCreateInfo paramsBufferInfo{}; + paramsBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + paramsBufferInfo.size = sizeof(HdrMergeParams); + paramsBufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + paramsBufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, ¶msBufferInfo, nullptr, ¶msBuffer) != VK_SUCCESS) { + throw std::runtime_error("Failed to create params buffer"); + } + + VkMemoryRequirements paramsMemRequirements; + vkGetBufferMemoryRequirements(device, paramsBuffer, ¶msMemRequirements); + + VkMemoryAllocateInfo paramsAllocInfo{}; + paramsAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + paramsAllocInfo.allocationSize = paramsMemRequirements.size; + paramsAllocInfo.memoryTypeIndex = findMemoryType(paramsMemRequirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + if (vkAllocateMemory(device, ¶msAllocInfo, nullptr, ¶msBufferMemory) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate params buffer memory"); + } + + vkBindBufferMemory(device, paramsBuffer, paramsBufferMemory, 0); +} + +void VulkanHdrGenerator::createDescriptorSet() { + // Allocate descriptor set + VkDescriptorSetAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocInfo.descriptorPool = descriptorPool; + allocInfo.descriptorSetCount = 1; + allocInfo.pSetLayouts = &descriptorSetLayout; + + if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { + throw std::runtime_error("Failed to allocate descriptor set"); + } + + // Update descriptor set + VkDescriptorBufferInfo inputBufferInfo{}; + inputBufferInfo.buffer = inputBuffer; + inputBufferInfo.offset = 0; + inputBufferInfo.range = VK_WHOLE_SIZE; + + VkDescriptorBufferInfo outputBufferInfo{}; + outputBufferInfo.buffer = outputBuffer; + outputBufferInfo.offset = 0; + outputBufferInfo.range = VK_WHOLE_SIZE; + + VkDescriptorBufferInfo paramsBufferInfo{}; + paramsBufferInfo.buffer = paramsBuffer; + paramsBufferInfo.offset = 0; + paramsBufferInfo.range = VK_WHOLE_SIZE; + + VkWriteDescriptorSet descriptorWrites[3]; + + descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[0].dstSet = descriptorSet; + descriptorWrites[0].dstBinding = 0; + descriptorWrites[0].dstArrayElement = 0; + descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[0].descriptorCount = 1; + descriptorWrites[0].pBufferInfo = &inputBufferInfo; + descriptorWrites[0].pImageInfo = nullptr; + descriptorWrites[0].pTexelBufferView = nullptr; + descriptorWrites[0].pNext = nullptr; + + descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[1].dstSet = descriptorSet; + descriptorWrites[1].dstBinding = 1; + descriptorWrites[1].dstArrayElement = 0; + descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorWrites[1].descriptorCount = 1; + descriptorWrites[1].pBufferInfo = &outputBufferInfo; + descriptorWrites[1].pImageInfo = nullptr; + descriptorWrites[1].pTexelBufferView = nullptr; + descriptorWrites[1].pNext = nullptr; + + descriptorWrites[2].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorWrites[2].dstSet = descriptorSet; + descriptorWrites[2].dstBinding = 2; + descriptorWrites[2].dstArrayElement = 0; + descriptorWrites[2].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + descriptorWrites[2].descriptorCount = 1; + descriptorWrites[2].pBufferInfo = ¶msBufferInfo; + descriptorWrites[2].pImageInfo = nullptr; + descriptorWrites[2].pTexelBufferView = nullptr; + descriptorWrites[2].pNext = nullptr; + + vkUpdateDescriptorSets(device, 3, descriptorWrites, 0, nullptr); +} + +void VulkanHdrGenerator::processImageBatch( + const std::vector>& images, + std::vector& outputData, + int32_t width, int32_t height, + const std::vector& exposures) { + + if (images.empty() || width <= 0 || height <= 0 || exposures.empty()) { + ALOGE("Invalid input parameters to processImageBatch"); + return; + } + + size_t imageCount = images.size(); + size_t pixelCount = width * height; + size_t inputBufferSize = imageCount * pixelCount * 3 * sizeof(float); + size_t outputBufferSize = pixelCount * 4 * sizeof(float); + + // Create buffers if they don't exist or need resizing + static VkDeviceSize lastInputSize = 0; + static VkDeviceSize lastOutputSize = 0; + + if (lastInputSize != inputBufferSize || lastOutputSize != outputBufferSize) { + // Clean up old buffers if they exist + if (lastInputSize > 0) { + // Free descriptor set first before destroying resources it references + if (descriptorSet != VK_NULL_HANDLE) { + vkFreeDescriptorSets(device, descriptorPool, 1, &descriptorSet); + descriptorSet = VK_NULL_HANDLE; + } + + vkDestroyBuffer(device, inputBuffer, nullptr); + vkFreeMemory(device, inputBufferMemory, nullptr); + vkDestroyBuffer(device, outputBuffer, nullptr); + vkFreeMemory(device, outputBufferMemory, nullptr); + vkDestroyBuffer(device, paramsBuffer, nullptr); + vkFreeMemory(device, paramsBufferMemory, nullptr); + } + + createBuffers(inputBufferSize, outputBufferSize); + createDescriptorSet(); + + lastInputSize = inputBufferSize; + lastOutputSize = outputBufferSize; + } + + // Upload input images + void* inputData; + vkMapMemory(device, inputBufferMemory, 0, inputBufferSize, 0, &inputData); + + float* floatPtr = static_cast(inputData); + for (size_t i = 0; i < imageCount; i++) { + std::memcpy( + floatPtr + i * pixelCount * 3, + images[i].data(), + pixelCount * 3 * sizeof(float) + ); + } + + vkUnmapMemory(device, inputBufferMemory); + + // Upload parameters + HdrMergeParams params{}; + params.imageCount = static_cast(imageCount); + params.width = width; + params.height = height; + + for (size_t i = 0; i < imageCount && i < 16; i++) { + params.exposureValues[i] = exposures[i]; + } + + void* paramsData; + vkMapMemory(device, paramsBufferMemory, 0, sizeof(HdrMergeParams), 0, ¶msData); + std::memcpy(paramsData, ¶ms, sizeof(HdrMergeParams)); + vkUnmapMemory(device, paramsBufferMemory); + + // Record and submit command buffer + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + vkBeginCommandBuffer(commandBuffer, &beginInfo); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline); + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); + + uint32_t groupCountX = (width + 15) / 16; + uint32_t groupCountY = (height + 15) / 16; + vkCmdDispatch(commandBuffer, groupCountX, groupCountY, 1); + + vkEndCommandBuffer(commandBuffer); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + vkQueueSubmit(computeQueue, 1, &submitInfo, VK_NULL_HANDLE); + vkQueueWaitIdle(computeQueue); + + // Debug input values - print first few pixels +#if 0 + if (width > 0 && height > 0) { + for (size_t img = 0; img < std::min(images.size(), size_t(2)); img++) { + ALOGD("Image %zu first pixel: R=%.3f G=%.3f B=%.3f", + img, + images[img][0], // R + images[img][1], // G + images[img][2]); // B + } + } +#endif + + // Download results + outputData.resize(pixelCount * 3); // We'll extract RGB and ignore alpha + + void* outputMappedMemory; + vkMapMemory(device, outputBufferMemory, 0, outputBufferSize, 0, &outputMappedMemory); + + float* outputFloats = static_cast(outputMappedMemory); + + // Debug first few pixels of output +#if 0 + ALOGD("Output first pixel: R=%.3f G=%.3f B=%.3f A=%.3f", + outputFloats[0], outputFloats[1], outputFloats[2], outputFloats[3]); +#endif + + for (size_t i = 0; i < pixelCount; i++) { + outputData[i * 3 + 0] = outputFloats[i * 4 + 0]; // R + outputData[i * 3 + 1] = outputFloats[i * 4 + 1]; // G + outputData[i * 3 + 2] = outputFloats[i * 4 + 2]; // B + // Ignore alpha + } + + vkUnmapMemory(device, outputBufferMemory); +} + +bool VulkanHdrGenerator::generateHdr( + const std::vector& bmpFiles, + const std::string& outputFile, + const std::vector& exposureValues, + int32_t tileWidth, + int32_t tileHeight) { + + if (bmpFiles.empty()) { + std::cerr << "No input BMP files specified" << std::endl; + return false; + } + + // Check all images are the same dimensions + std::vector bmpInfos(bmpFiles.size()); + for (size_t i = 0; i < bmpFiles.size(); i++) { + bmpInfos[i] = BmpLoader::readBmpInfo(bmpFiles[i]); + + if (i > 0) { + if (bmpInfos[i].width != bmpInfos[0].width || + bmpInfos[i].height != bmpInfos[0].height) { + std::cerr << "All BMP files must have the same dimensions" << std::endl; + return false; + } + } + } + + int32_t imageWidth = bmpInfos[0].width; + int32_t imageHeight = bmpInfos[0].height; + + // Adjust tile dimensions if necessary + tileWidth = std::min(tileWidth, imageWidth); + tileHeight = std::min(tileHeight, imageHeight); + + // Output HDR data + std::vector outputHdrData(imageWidth * imageHeight * 3); + + // Process image in tiles + for (int32_t y = 0; y < imageHeight; y += tileHeight) { +#ifndef NDEBUG + ALOGI("Processing tile at Y=%d", y); +#endif + for (int32_t x = 0; x < imageWidth; x += tileWidth) { + int32_t currentTileWidth = std::min(tileWidth, imageWidth - x); + int32_t currentTileHeight = std::min(tileHeight, imageHeight - y); + + // Load tile from each BMP + std::vector> tileData(bmpFiles.size()); + for (size_t i = 0; i < bmpFiles.size(); i++) { + tileData[i] = BmpLoader::readBmpRegionAsFloat( + bmpFiles[i], bmpInfos[i], x, y, currentTileWidth, currentTileHeight); + } + + // Process tile with Vulkan + std::vector tileOutput; + processImageBatch(tileData, tileOutput, currentTileWidth, currentTileHeight, exposureValues); + + // Copy tile data to output HDR + for (int32_t tileY = 0; tileY < currentTileHeight; tileY++) { + for (int32_t tileX = 0; tileX < currentTileWidth; tileX++) { + int32_t imagePixelIndex = ((y + tileY) * imageWidth + (x + tileX)) * 3; + int32_t tilePixelIndex = (tileY * currentTileWidth + tileX) * 3; + + outputHdrData[imagePixelIndex + 0] = tileOutput[tilePixelIndex + 0]; + outputHdrData[imagePixelIndex + 1] = tileOutput[tilePixelIndex + 1]; + outputHdrData[imagePixelIndex + 2] = tileOutput[tilePixelIndex + 2]; + } + } + } + + + } + + // Write output HDR + return HdrWriter::writeRGB(outputFile, outputHdrData, (int)imageWidth, (int)imageHeight, HdrWriter::Format::BMP); +} + +uint32_t VulkanHdrGenerator::findComputeQueueFamily(VkPhysicalDevice device) { + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + for (uint32_t i = 0; i < queueFamilyCount; i++) { + if (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { + return i; + } + } + + throw std::runtime_error("Failed to find a compute queue family"); +} + +uint32_t VulkanHdrGenerator::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) { + VkPhysicalDeviceMemoryProperties memProperties; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); + + for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { + if ((typeFilter & (1 << i)) && + (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { + return i; + } + } + + throw std::runtime_error("Failed to find suitable memory type"); +} + +VkShaderModule VulkanHdrGenerator::createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("Failed to create shader module"); + } + + return shaderModule; +} + +std::vector VulkanHdrGenerator::readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + filename); + } + + size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); + + file.seekg(0); + file.read(reinterpret_cast(buffer.data()), fileSize); + + return buffer; +} + +void VulkanHdrGenerator::cleanup() { + if (device != VK_NULL_HANDLE) { + vkDeviceWaitIdle(device); + + // Destroy buffers if they were created + if (inputBuffer != VK_NULL_HANDLE) { + vkDestroyBuffer(device, inputBuffer, nullptr); + inputBuffer = VK_NULL_HANDLE; + } + + if (inputBufferMemory != VK_NULL_HANDLE) { + vkFreeMemory(device, inputBufferMemory, nullptr); + inputBufferMemory = VK_NULL_HANDLE; + } + + if (outputBuffer != VK_NULL_HANDLE) { + vkDestroyBuffer(device, outputBuffer, nullptr); + outputBuffer = VK_NULL_HANDLE; + } + + if (outputBufferMemory != VK_NULL_HANDLE) { + vkFreeMemory(device, outputBufferMemory, nullptr); + outputBufferMemory = VK_NULL_HANDLE; + } + + if (paramsBuffer != VK_NULL_HANDLE) { + vkDestroyBuffer(device, paramsBuffer, nullptr); + paramsBuffer = VK_NULL_HANDLE; + } + + if (paramsBufferMemory != VK_NULL_HANDLE) { + vkFreeMemory(device, paramsBufferMemory, nullptr); + paramsBufferMemory = VK_NULL_HANDLE; + } + + // Destroy other resources + if (descriptorPool != VK_NULL_HANDLE) { + vkDestroyDescriptorPool(device, descriptorPool, nullptr); + descriptorPool = VK_NULL_HANDLE; + } + + if (descriptorSetLayout != VK_NULL_HANDLE) { + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + descriptorSetLayout = VK_NULL_HANDLE; + } + + if (computePipeline != VK_NULL_HANDLE) { + vkDestroyPipeline(device, computePipeline, nullptr); + computePipeline = VK_NULL_HANDLE; + } + + if (pipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + pipelineLayout = VK_NULL_HANDLE; + } + + if (commandPool != VK_NULL_HANDLE) { + vkDestroyCommandPool(device, commandPool, nullptr); + commandPool = VK_NULL_HANDLE; + } + + vkDestroyDevice(device, nullptr); + device = VK_NULL_HANDLE; + } + + if (instance != VK_NULL_HANDLE) { + vkDestroyInstance(instance, nullptr); + instance = VK_NULL_HANDLE; + } +} + diff --git a/app/src/main/cpp/vulkan_hdr_generator.h b/app/src/main/cpp/vulkan_hdr_generator.h new file mode 100644 index 0000000..344e8e5 --- /dev/null +++ b/app/src/main/cpp/vulkan_hdr_generator.h @@ -0,0 +1,76 @@ +// +// Created by Matthew on 2025/3/22. +// + +#ifndef MPPREVIEW_VULKAN_HDR_GENERATOR_H +#define MPPREVIEW_VULKAN_HDR_GENERATOR_H + +#include +#include +#include +#include "BmpLoader.h" + +struct HdrMergeParams { + uint32_t imageCount; + uint32_t width; + uint32_t height; + float exposureValues[16]; // Support up to 16 images +}; + +class VulkanHdrGenerator { +private: + VkInstance instance; + VkPhysicalDevice physicalDevice; + VkDevice device; + VkQueue computeQueue; + VkCommandPool commandPool; + VkDescriptorPool descriptorPool; + VkCommandBuffer commandBuffer; + + VkBuffer stagingBuffer; + VkDeviceMemory stagingBufferMemory; + + VkBuffer inputBuffer = nullptr; + VkDeviceMemory inputBufferMemory; + VkBuffer outputBuffer; + VkDeviceMemory outputBufferMemory; + VkBuffer paramsBuffer; + VkDeviceMemory paramsBufferMemory; + + VkShaderModule computeShaderModule; + VkPipeline computePipeline; + VkPipelineLayout pipelineLayout; + VkDescriptorSetLayout descriptorSetLayout; + VkDescriptorSet descriptorSet; + + uint32_t computeQueueFamilyIndex; + + std::string compFilePath; + + void setupVulkan(); + void createComputeResources(); + void createBuffers(VkDeviceSize inputSize, VkDeviceSize outputSize); + void createDescriptorSet(); + uint32_t findComputeQueueFamily(VkPhysicalDevice device); + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties); + void processImageBatch(const std::vector>& images, + std::vector& outputData, + int32_t width, int32_t height, + const std::vector& exposures); + + VkShaderModule createShaderModule(const std::vector& code); + std::vector readFile(const std::string& filename); + void cleanup(); + +public: + VulkanHdrGenerator(const std::string& compFile); + ~VulkanHdrGenerator(); + + // Generate HDR from multiple BMP files with tile-based processing + bool generateHdr(const std::vector& bmpFiles, + const std::string& outputFile, + const std::vector& exposureValues, + int32_t tileWidth = 256, int32_t tileHeight = 256); +}; + +#endif //MPPREVIEW_VULKAN_HDR_GENERATOR_H diff --git a/app/src/main/java/com/xypower/mppreview/Camera2RawFragment.java b/app/src/main/java/com/xypower/mppreview/Camera2RawFragment.java index 79ad193..1213c52 100644 --- a/app/src/main/java/com/xypower/mppreview/Camera2RawFragment.java +++ b/app/src/main/java/com/xypower/mppreview/Camera2RawFragment.java @@ -148,6 +148,8 @@ public class Camera2RawFragment extends Fragment { public static native boolean makeHdr3(long exposureTime1, Bitmap img1, int length1, long exposureTime2, Bitmap img2, int length2, String outputPath); + public static native boolean makeHdr5(String compFile, long exposureTime1, String img1, long exposureTime2, String img2, String outputPath); + // public static native boolean decodeDng(ByteBuffer byteBuffer, String outputPath); private int mExposureComp = MainActivity.ExposureComp; @@ -543,6 +545,9 @@ public class Camera2RawFragment extends Fragment { if (arguments != null) { pic1 = arguments.getInt(Contants.HDRNUM); } + + + mOrientationListener = new OrientationEventListener(getActivity(), SensorManager.SENSOR_DELAY_NORMAL) { @Override public void onOrientationChanged(int orientation) { diff --git a/app/src/main/java/com/xypower/mppreview/MainActivity.java b/app/src/main/java/com/xypower/mppreview/MainActivity.java index 4c6fa6a..9a323e2 100644 --- a/app/src/main/java/com/xypower/mppreview/MainActivity.java +++ b/app/src/main/java/com/xypower/mppreview/MainActivity.java @@ -21,6 +21,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.os.Bundle; +import android.os.Handler; import android.util.Log; import android.view.View; import android.widget.AdapterView; @@ -31,10 +32,10 @@ import com.xypower.mppreview.adapter.ItemAdapter; import com.xypower.mppreview.interfaces.OnItemClickListener; import com.xypower.mppreview.ui.CameraActivity; import com.xypower.mppreview.ui.CameraChannelActivity; +import com.xypower.mppreview.utils.HdrUtil; import com.xypower.mppreview.utils.PhotoUtil; import com.xypower.mppreview.bean.Contants; import com.xypower.mppreview.utils.CameraUtils; -import com.xypower.mppreview.utils.RouteManager; import java.io.File; @@ -71,7 +72,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe initView(); initActivityResult(); - String[] accessPermissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.FOREGROUND_SERVICE, Manifest.permission.READ_PHONE_STATE, /*Manifest.permission.PACKAGE_USAGE_STATS,*/ /*Manifest.permission.SET_TIME,*/}; @@ -108,8 +108,74 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe viewBinding.recyclerView.setAdapter(itemAdapter); viewBinding.recyclerView.setLayoutManager(new GridLayoutManager(this,3)); + + viewBinding.btnTest.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + final File outputFile = new File(getFilesDir(), "out.bmp"); + final File shadersPath = new File(getFilesDir(), "shaders"); + if (!shadersPath.exists()) { + shadersPath.mkdirs(); + } + if (outputFile.exists()) { + outputFile.delete(); + } + HdrUtil.copyAssetsDir(getApplicationContext(), "shaders", shadersPath.getAbsolutePath()); + + final File f1 = new File(getFilesDir(), "5.bmp"); + final File f2 = new File(getFilesDir(), "6.bmp"); + + if (f1.exists()) { + f1.delete(); + } + if (f2.exists()) { + f2.delete(); + } + + HdrUtil.copyAssetsFile(getApplicationContext(), "5.bmp", f1.getAbsolutePath()); + HdrUtil.copyAssetsFile(getApplicationContext(), "6.bmp", f2.getAbsolutePath()); + + f1.setReadable(true); + Thread th = new Thread(new Runnable() { + @Override + public void run() { + Boolean bigger = true; + + if (bigger) { + + File out = new File("/sdcard/com.xypower.mpapp/tmp/out.bmp"); + Camera2RawFragment.makeHdr5(shadersPath.getAbsolutePath() + "/hdr_merge.comp.spv", 1000000, "/sdcard/com.xypower.mpapp/tmp/0.bmp", 4000000, + "/sdcard/com.xypower.mpapp/tmp/4.bmp", + out.getAbsolutePath()); + } else { + File f11 = new File("/sdcard/com.xypower.mpapp/tmp/IMG_20250323_104953_582.bmp"); + File f12 = new File("/sdcard/com.xypower.mpapp/tmp/IMG_20250323_104954_960.bmp"); + if (!f11.exists()) { + int aa = 0; + } + if (!f12.exists()) { + int aa = 0; + } + Camera2RawFragment.makeHdr5(shadersPath .getAbsolutePath() + "/hdr_merge.comp.spv", + 200000000, f11.getAbsolutePath(), + 200000000, f12.getAbsolutePath(), + "/sdcard/com.xypower.mpapp/tmp/291out.bmp"); + } + } + }); + + th.start(); + } + }); // initNetWork(); + Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + viewBinding.btnTest.performClick(); + } + }, 1000); } private void initNetWork() { diff --git a/app/src/main/java/com/xypower/mppreview/utils/HdrUtil.java b/app/src/main/java/com/xypower/mppreview/utils/HdrUtil.java index 87d6387..b5bb689 100644 --- a/app/src/main/java/com/xypower/mppreview/utils/HdrUtil.java +++ b/app/src/main/java/com/xypower/mppreview/utils/HdrUtil.java @@ -1,6 +1,16 @@ package com.xypower.mppreview.utils; +import android.app.Activity; +import android.content.Context; +import android.content.res.AssetManager; +import android.util.Log; + +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; @@ -8,6 +18,92 @@ import java.util.Locale; public class HdrUtil { + public static void copyAssetsDir(Context context, String directory, String destPath) { + try { + AssetManager assetManager = context.getAssets(); + String[] fileList = assetManager.list(directory); + if (fileList != null && fileList.length > 0) { + File file = new File(destPath); + if (!file.exists()) { + file.mkdirs(); + } + + if (!directory.endsWith(File.separator)) { + directory += File.separator; + } + if (!destPath.endsWith(File.separator)) { + destPath += File.separator; + } + + for (String fileName : fileList) { + copyAssetsDir(context, directory + fileName, destPath + fileName); + } + } else { + // Try to file + copyAssetsFile(context, directory, destPath); + } + } catch (Exception e) { + e.printStackTrace(); + } + + // else {//如果是文件 + // InputStream inputStream=context.getAssets().open(filePath); + // File file=new File(context.getFilesDir().getAbsolutePath()+ File.separator+filePath); + // Log.i("copyAssets2Phone","file:"+file); + // if(!file.exists() || file.length()==0) { + // FileOutputStream fos=new FileOutputStream(file); + // int len=-1; + // byte[] buffer=new byte[1024]; + // while ((len=inputStream.read(buffer))!=-1){ + // fos.write(buffer,0,len); + // } + // fos.flush(); + // inputStream.close(); + // fos.close(); + // showToast(context,"模型文件复制完毕"); + // } else { + // showToast(context,"模型文件已存在,无需复制"); + // } + // } + } + + + public static void copyAssetsFile(Context context, String fileName, String destPath) { + InputStream inputStream = null; + FileOutputStream fos = null; + try { + inputStream = context.getAssets().open(fileName); + //getFilesDir() 获得当前APP的安装路径 /data/data/包名/files 目录 + File file = new File(destPath); + if (file.exists()) { + file.delete(); + } + + fos = new FileOutputStream(file); + int len = -1; + byte[] buffer = new byte[1024]; + while ((len = inputStream.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + fos.flush(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + closeFriendly(inputStream); + closeFriendly(fos); + } + } + + public static void closeFriendly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception ex) { + } + closeable = null; + } + } + public static String generateTimestamp() { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US); return sdf.format(new Date()); diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 47b33e8..784c367 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -46,6 +46,15 @@ app:layout_constraintLeft_toRightOf="@id/hdrtakepic" app:layout_constraintTop_toTopOf="@+id/hdrtakepic" /> +