实现hdr(偏暗)

vulkan
Matthew 3 months ago
parent 4b9232f549
commit cebfa32c9f

@ -7,6 +7,9 @@
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- A camera with RAW capability is required to use this application --> <!-- A camera with RAW capability is required to use this application -->
<uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.raw" /> <uses-feature android:name="android.hardware.camera.raw" />

@ -0,0 +1,124 @@
#include "BmpLoader.h"
#include <algorithm>
#include <cmath>
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<char*>(&header), sizeof(header));
if (header.signature != 0x4D42) { // 'BM'
throw std::runtime_error("Invalid BMP signature in file: " + filePath);
}
BmpInfoHeader infoHeader;
file.read(reinterpret_cast<char*>(&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<uint8_t> 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<uint8_t>();
}
// Calculate bytes per pixel
int bytesPerPixel = info.bitsPerPixel / 8;
int regionRowSize = width * bytesPerPixel;
// Allocate memory for the region
std::vector<uint8_t> 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<char*>(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<float> 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<float>();
}
int bytesPerPixel = info.bitsPerPixel / 8;
std::vector<float> 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;
}

@ -0,0 +1,54 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
#include <fstream>
#include <stdexcept>
#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<uint8_t> readBmpRegion(
const std::string& filePath,
const BmpInfo& info,
int32_t startX, int32_t startY,
int32_t width, int32_t height);
static std::vector<float> readBmpRegionAsFloat(
const std::string& filePath,
const BmpInfo& info,
int32_t startX, int32_t startY,
int32_t width, int32_t height);
};

@ -21,6 +21,56 @@ ENDIF()
project("mppreview") 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) include_directories(D:/Workspace/deps/hdrplus_libs/${ANDROID_ABI}/include)
link_directories(D:/Workspace/deps/hdrplus_libs/${ANDROID_ABI}/lib) link_directories(D:/Workspace/deps/hdrplus_libs/${ANDROID_ABI}/lib)
@ -48,7 +98,7 @@ endif(OpenCV_FOUND)
find_package(OpenMP REQUIRED) find_package(OpenMP REQUIRED)
add_library( # Sets the name of the library. add_library( # Sets the name of the library.
mppreview ${PROJECT_NAME}
# Sets the library as a shared library. # Sets the library as a shared library.
SHARED SHARED
@ -56,6 +106,9 @@ add_library( # Sets the name of the library.
# Provides a relative path to your source file(s). # Provides a relative path to your source file(s).
MpPreview.cpp MpPreview.cpp
HdrImpl.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 # you want to add. CMake verifies that the library exists before
# completing its build. # 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. find_library( # Sets the name of the path variable.
log-lib 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 # can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries. # 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. target_link_libraries( # Specifies the target library.
${PROJECT_NAME} ${PROJECT_NAME}
PUBLIC -fopenmp -static-openmp PUBLIC -fopenmp -static-openmp

@ -0,0 +1,170 @@
//
// Created by Matthew on 2025/3/22.
//
#include "HdrWriter.h"
#include <cmath>
#include <algorithm>
#include <vector>
// 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<float>& 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<char*>(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<uint8_t>(r * v);
rgbe[1] = static_cast<uint8_t>(g * v);
rgbe[2] = static_cast<uint8_t>(b * v);
rgbe[3] = static_cast<uint8_t>(e + 128);
}
}
bool HdrWriter::writeRGB(const std::string& filename,
const std::vector<float>& 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<float>& 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<char*>(fileHeader), 14);
file.write(reinterpret_cast<char*>(infoHeader), 40);
// Padding bytes (zeros)
std::vector<uint8_t> 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<uint8_t>(std::min(1.0f, b) * 255.0f), // B
static_cast<uint8_t>(std::min(1.0f, g) * 255.0f), // G
static_cast<uint8_t>(std::min(1.0f, r) * 255.0f) // R
};
file.write(reinterpret_cast<char*>(pixelData), 3);
}
// Write padding bytes
if (paddingSize > 0) {
file.write(reinterpret_cast<char*>(padding.data()), paddingSize);
}
}
return file.good();
}

@ -0,0 +1,40 @@
//
// Created by Matthew on 2025/3/22.
//
#ifndef MPPREVIEW_HDRWRITER_H
#define MPPREVIEW_HDRWRITER_H
#include <vector>
#include <string>
#include <cstdint>
#include <fstream>
class HdrWriter {
public:
static bool writeRGBE(const std::string& filename,
const std::vector<float>& 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<float>& 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<float>& data,
int width, int height);
};
#endif //MPPREVIEW_HDRWRITER_H

@ -18,6 +18,9 @@
#include "hdr.h" #include "hdr.h"
#include "BmpLoader.h"
#include "vulkan_hdr_generator.h"
namespace cv2 namespace cv2
{ {
using namespace cv; 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 ((ANDROID_BITMAP_FLAGS_IS_HARDWARE & bmpInfo.flags) == ANDROID_BITMAP_FLAGS_IS_HARDWARE)
{ {
#if 0
AHardwareBuffer* hardwareBuffer = NULL; AHardwareBuffer* hardwareBuffer = NULL;
result = AndroidBitmap_getHardwareBuffer(env, bitmaps[idx], &hardwareBuffer); 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]); tmp.copyTo(images[idx]);
AHardwareBuffer_unlock(hardwareBuffer, &fence); AHardwareBuffer_unlock(hardwareBuffer, &fence);
AHardwareBuffer_release(hardwareBuffer); AHardwareBuffer_release(hardwareBuffer);
#endif
} }
else else
{ {
@ -492,9 +497,73 @@ Java_com_xypower_mppreview_Camera2RawFragment_makeHdr3(JNIEnv *env, jclass clazz
return JNI_FALSE; return JNI_FALSE;
} }
extern "C" extern "C"
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_xypower_mppreview_Camera2RawFragment_decodeDng(JNIEnv *env, jclass clazz, Java_com_xypower_mppreview_Camera2RawFragment_decodeDng(JNIEnv *env, jclass clazz,
jobject byte_buffer, jstring output_path) { jobject byte_buffer, jstring output_path) {
// TODO: implement decodeDng() // TODO: implement decodeDng()
} }
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<std::string> inputFiles;
std::vector<float> 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;
}

@ -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);
}

File diff suppressed because it is too large Load Diff

@ -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 <stdexcept>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <jni.h>
#include <android/log.h>
#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<VkPhysicalDevice> 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<uint8_t> 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, &paramsBufferInfo, nullptr, &paramsBuffer) != VK_SUCCESS) {
throw std::runtime_error("Failed to create params buffer");
}
VkMemoryRequirements paramsMemRequirements;
vkGetBufferMemoryRequirements(device, paramsBuffer, &paramsMemRequirements);
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, &paramsAllocInfo, nullptr, &paramsBufferMemory) != 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 = &paramsBufferInfo;
descriptorWrites[2].pImageInfo = nullptr;
descriptorWrites[2].pTexelBufferView = nullptr;
descriptorWrites[2].pNext = nullptr;
vkUpdateDescriptorSets(device, 3, descriptorWrites, 0, nullptr);
}
void VulkanHdrGenerator::processImageBatch(
const std::vector<std::vector<float>>& images,
std::vector<float>& outputData,
int32_t width, int32_t height,
const std::vector<float>& 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<float*>(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<uint32_t>(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, &paramsData);
std::memcpy(paramsData, &params, 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<float*>(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<std::string>& bmpFiles,
const std::string& outputFile,
const std::vector<float>& 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<BmpInfo> 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<float> 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<std::vector<float>> 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<float> 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<VkQueueFamilyProperties> 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<uint8_t>& code) {
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(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<uint8_t> 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<size_t>(file.tellg());
std::vector<uint8_t> buffer(fileSize);
file.seekg(0);
file.read(reinterpret_cast<char*>(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;
}
}

@ -0,0 +1,76 @@
//
// Created by Matthew on 2025/3/22.
//
#ifndef MPPREVIEW_VULKAN_HDR_GENERATOR_H
#define MPPREVIEW_VULKAN_HDR_GENERATOR_H
#include <vector>
#include <string>
#include <vulkan/vulkan.h>
#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<std::vector<float>>& images,
std::vector<float>& outputData,
int32_t width, int32_t height,
const std::vector<float>& exposures);
VkShaderModule createShaderModule(const std::vector<uint8_t>& code);
std::vector<uint8_t> 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<std::string>& bmpFiles,
const std::string& outputFile,
const std::vector<float>& exposureValues,
int32_t tileWidth = 256, int32_t tileHeight = 256);
};
#endif //MPPREVIEW_VULKAN_HDR_GENERATOR_H

@ -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 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); // public static native boolean decodeDng(ByteBuffer byteBuffer, String outputPath);
private int mExposureComp = MainActivity.ExposureComp; private int mExposureComp = MainActivity.ExposureComp;
@ -543,6 +545,9 @@ public class Camera2RawFragment extends Fragment {
if (arguments != null) { if (arguments != null) {
pic1 = arguments.getInt(Contants.HDRNUM); pic1 = arguments.getInt(Contants.HDRNUM);
} }
mOrientationListener = new OrientationEventListener(getActivity(), SensorManager.SENSOR_DELAY_NORMAL) { mOrientationListener = new OrientationEventListener(getActivity(), SensorManager.SENSOR_DELAY_NORMAL) {
@Override @Override
public void onOrientationChanged(int orientation) { public void onOrientationChanged(int orientation) {

@ -21,6 +21,7 @@ import android.net.Network;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.NetworkRequest; import android.net.NetworkRequest;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
@ -31,10 +32,10 @@ import com.xypower.mppreview.adapter.ItemAdapter;
import com.xypower.mppreview.interfaces.OnItemClickListener; import com.xypower.mppreview.interfaces.OnItemClickListener;
import com.xypower.mppreview.ui.CameraActivity; import com.xypower.mppreview.ui.CameraActivity;
import com.xypower.mppreview.ui.CameraChannelActivity; import com.xypower.mppreview.ui.CameraChannelActivity;
import com.xypower.mppreview.utils.HdrUtil;
import com.xypower.mppreview.utils.PhotoUtil; import com.xypower.mppreview.utils.PhotoUtil;
import com.xypower.mppreview.bean.Contants; import com.xypower.mppreview.bean.Contants;
import com.xypower.mppreview.utils.CameraUtils; import com.xypower.mppreview.utils.CameraUtils;
import com.xypower.mppreview.utils.RouteManager;
import java.io.File; import java.io.File;
@ -71,7 +72,6 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
initView(); initView();
initActivityResult(); initActivityResult();
String[] accessPermissions = new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.FOREGROUND_SERVICE, Manifest.permission.READ_PHONE_STATE, 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.PACKAGE_USAGE_STATS,*/
/*Manifest.permission.SET_TIME,*/}; /*Manifest.permission.SET_TIME,*/};
@ -108,8 +108,74 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
viewBinding.recyclerView.setAdapter(itemAdapter); viewBinding.recyclerView.setAdapter(itemAdapter);
viewBinding.recyclerView.setLayoutManager(new GridLayoutManager(this,3)); 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(); // initNetWork();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
viewBinding.btnTest.performClick();
}
}, 1000);
} }
private void initNetWork() { private void initNetWork() {

@ -1,6 +1,16 @@
package com.xypower.mppreview.utils; 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.text.SimpleDateFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
@ -8,6 +18,92 @@ import java.util.Locale;
public class HdrUtil { 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() { public static String generateTimestamp() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss_SSS", Locale.US);
return sdf.format(new Date()); return sdf.format(new Date());

@ -46,6 +46,15 @@
app:layout_constraintLeft_toRightOf="@id/hdrtakepic" app:layout_constraintLeft_toRightOf="@id/hdrtakepic"
app:layout_constraintTop_toTopOf="@+id/hdrtakepic" /> app:layout_constraintTop_toTopOf="@+id/hdrtakepic" />
<Button
android:id="@+id/btnTest"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="Test"
app:layout_constraintLeft_toRightOf="@id/systakepic"
app:layout_constraintTop_toTopOf="@+id/hdrtakepic" />
<!-- <Button--> <!-- <Button-->
<!-- android:id="@+id/channel1"--> <!-- android:id="@+id/channel1"-->
<!-- android:layout_width="100dp"--> <!-- android:layout_width="100dp"-->

Loading…
Cancel
Save