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 0000000..c0276d5
Binary files /dev/null and b/app/src/main/assets/shaders/hdr_merge.comp.spv differ
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" />
+
+