feat: add deterministic raw development

This commit is contained in:
fy59 2026-08-27 10:27:05 +02:00
parent 3bfbfc0534
commit c8fb2e4094
7 changed files with 896 additions and 61 deletions

View file

@ -16,6 +16,16 @@ typedef enum {
LARDON3D_IMAGE_CATALOG_DB_ERROR
} Lardon3DImageCatalogImportResult;
typedef enum {
LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED = 0,
LARDON3D_IMAGE_CATALOG_ASSET_INVALID_ARGUMENT,
LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_ERROR,
LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_CHANGED,
LARDON3D_IMAGE_CATALOG_ASSET_TOO_LARGE,
LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR,
LARDON3D_IMAGE_CATALOG_ASSET_DB_ERROR
} Lardon3DImageCatalogAssetPublishResult;
bool lardon3d_image_catalog_create_scanset(
Lardon3DAppState *state, const char *name,
Lardon3DProjectDbScanSet *scanset
@ -25,6 +35,12 @@ Lardon3DImageCatalogImportResult lardon3d_image_catalog_import_file(
uint64_t producer_task_id, Lardon3DProjectDbImage *image,
Lardon3DProjectDbImageAsset *asset
);
/* Publishes immutable bytes to the managed asset store without creating an image. */
Lardon3DImageCatalogAssetPublishResult lardon3d_image_catalog_publish_asset_file(
Lardon3DAppState *state, const char *source_path, int64_t created_at,
uint64_t max_source_bytes,
Lardon3DProjectDbImageAsset *asset
);
Lardon3DProjectDbResult lardon3d_image_catalog_list(
Lardon3DAppState *state, uint64_t scanset_id, uint64_t after_image_id,
Lardon3DProjectDbImage *images, Lardon3DProjectDbImageAsset *assets,

View file

@ -0,0 +1,57 @@
#ifndef LARDON3D_RAW_DEVELOPMENT_H
#define LARDON3D_RAW_DEVELOPMENT_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <lardon3d/app_state.h>
#include <lardon3d/project_db.h>
typedef enum {
LARDON3D_RAW_DEVELOPMENT_OK = 0,
LARDON3D_RAW_DEVELOPMENT_INVALID_ARGUMENT,
LARDON3D_RAW_DEVELOPMENT_SOURCE_NOT_FOUND,
LARDON3D_RAW_DEVELOPMENT_SOURCE_CHANGED,
LARDON3D_RAW_DEVELOPMENT_UNSUPPORTED_RAW,
LARDON3D_RAW_DEVELOPMENT_CORRUPT_RAW,
LARDON3D_RAW_DEVELOPMENT_DECODER_FAILURE,
LARDON3D_RAW_DEVELOPMENT_OUT_OF_MEMORY,
LARDON3D_RAW_DEVELOPMENT_OUTPUT_LIMIT_EXCEEDED,
LARDON3D_RAW_DEVELOPMENT_IO_ERROR,
LARDON3D_RAW_DEVELOPMENT_DB_ERROR,
LARDON3D_RAW_DEVELOPMENT_CONSTRAINT,
LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR
} Lardon3DRawDevelopmentResult;
typedef struct {
Lardon3DProjectDbImageAsset source_asset;
Lardon3DProjectDbImageAsset derived_asset;
Lardon3DProjectDbImage image;
unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE];
float resolved_camera_wb[4];
uint32_t width;
uint32_t height;
char libraw_version[64];
char png_encoder_version[64];
} Lardon3DRawDevelopmentOutput;
/* Computes the fixed RAW policy v1 fingerprint for explicitly resolved camera WB. */
Lardon3DRawDevelopmentResult lardon3d_raw_development_policy_fingerprint(
const float resolved_camera_wb[4],
unsigned char fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE]
);
/* Publishes a managed ARW source and a derived PNG into an existing Capture. */
Lardon3DRawDevelopmentResult lardon3d_raw_develop_to_capture(
Lardon3DAppState *state, uint64_t capture_id, const char *source_arw_path,
uint64_t producer_task_id, int64_t created_at, Lardon3DRawDevelopmentOutput *output
);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -36,6 +36,9 @@ opencv = dependency(
include_type: 'system',
modules: ['opencv_core', 'opencv_imgcodecs', 'opencv_features2d', 'opencv_geometry'],
)
libraw = dependency('libraw', required: true)
libpng = dependency('libpng', required: true)
libdeflate = dependency('libdeflate', required: true)
opencv_benchmark = dependency(
'opencv5',
required: true,
@ -144,6 +147,7 @@ executable(
'src/import_task.c',
'src/image_catalog.c',
'src/image_catalog_persistent.c',
'src/raw_development.cpp',
'src/feature_extractor_opencv.cpp',
'src/feature_store.c',
'src/feature_extractor_opencv.cpp',
@ -193,6 +197,9 @@ executable(
threads,
sqlite3,
openssl,
libraw,
libpng,
libdeflate,
opencv,
opencv_geometry,
opencv_dense_mvs,
@ -294,6 +301,25 @@ persistent_image_catalog_test = executable(
test('persistent-image-catalog', persistent_image_catalog_test, timeout: 60)
raw_development_test = executable(
'test-raw-development',
sources: [
'tests/test_raw_development.cpp',
'src/app_state.c',
'src/image_catalog_persistent.c',
'src/raw_development.cpp',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
],
include_directories: include_directories('include'),
cpp_args: ['-DLARDON3D_RAW_DEVELOPMENT_TESTING'],
dependencies: [threads, sqlite3, openssl, opencv, libraw, libpng, libdeflate],
)
test('raw-development', raw_development_test, timeout: 30)
feature_store_test = executable(
'test-feature-store',
sources: [

View file

@ -14,6 +14,12 @@
enum { COPY_BUFFER_SIZE = 64 * 1024 };
typedef enum {
COPY_OK = 0,
COPY_SOURCE_ERROR,
COPY_DESTINATION_ERROR
} CopyResult;
static bool
join_path(char output[PATH_MAX], const char *parent, const char *child)
{
@ -46,34 +52,44 @@ write_all(int descriptor, const unsigned char *data, size_t size)
return true;
}
static bool
static CopyResult
hash_stream(int input, int output,
unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE], uint64_t *size)
{
EVP_MD_CTX *context = EVP_MD_CTX_new();
if (!context) return false;
if (!context) return COPY_SOURCE_ERROR;
bool success = EVP_DigestInit_ex(context, EVP_sha256(), NULL) == 1;
CopyResult result = COPY_OK;
unsigned char buffer[COPY_BUFFER_SIZE];
uint64_t total = 0;
while (success) {
ssize_t count = read(input, buffer, sizeof(buffer));
if (count < 0 && errno == EINTR) continue;
if (count < 0) { success = false; break; }
if (count < 0) { success = false; result = COPY_SOURCE_ERROR; break; }
if (count == 0) break;
size_t bytes = (size_t)count;
if (total > (uint64_t)INT64_MAX - bytes
|| EVP_DigestUpdate(context, buffer, bytes) != 1
|| (output >= 0 && !write_all(output, buffer, bytes))) {
success = false; break;
|| EVP_DigestUpdate(context, buffer, bytes) != 1) {
success = false;
result = COPY_SOURCE_ERROR;
break;
}
if (output >= 0 && !write_all(output, buffer, bytes)) {
success = false;
result = COPY_DESTINATION_ERROR;
break;
}
total += bytes;
}
unsigned int hash_size = 0;
if (!success || EVP_DigestFinal_ex(context, hash, &hash_size) != 1
|| hash_size != LARDON3D_PROJECT_DB_SHA256_SIZE) success = false;
|| hash_size != LARDON3D_PROJECT_DB_SHA256_SIZE) {
success = false;
if (result == COPY_OK) result = COPY_SOURCE_ERROR;
}
EVP_MD_CTX_free(context);
if (success) *size = total;
return success;
return success ? COPY_OK : result;
}
static void
@ -100,7 +116,7 @@ same_published_asset(const char *path,
uint64_t size = 0;
bool success = fstat(descriptor, &information) == 0
&& S_ISREG(information.st_mode)
&& hash_stream(descriptor, -1, actual, &size)
&& hash_stream(descriptor, -1, actual, &size) == COPY_OK
&& size == expected_size
&& memcmp(actual, expected, sizeof(actual)) == 0;
(void)close(descriptor);
@ -117,6 +133,114 @@ sync_directory(const char *path)
return success;
}
static bool
same_source_snapshot(const struct stat *before, const struct stat *after)
{
return before->st_dev == after->st_dev && before->st_ino == after->st_ino
&& before->st_size == after->st_size
&& before->st_mtim.tv_sec == after->st_mtim.tv_sec
&& before->st_mtim.tv_nsec == after->st_mtim.tv_nsec
&& before->st_ctim.tv_sec == after->st_ctim.tv_sec
&& before->st_ctim.tv_nsec == after->st_ctim.tv_nsec;
}
Lardon3DImageCatalogAssetPublishResult
lardon3d_image_catalog_publish_asset_file(Lardon3DAppState *state,
const char *source_path, int64_t created_at, uint64_t max_source_bytes,
Lardon3DProjectDbImageAsset *asset)
{
if (!state || !state->project_loaded || !state->project_db || !source_path
|| !source_path[0] || created_at < 0 || max_source_bytes == 0 || !asset)
return LARDON3D_IMAGE_CATALOG_ASSET_INVALID_ARGUMENT;
memset(asset, 0, sizeof(*asset));
int input = open(source_path, O_RDONLY | O_NOFOLLOW);
if (input < 0) return LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_ERROR;
struct stat source_before;
if (fstat(input, &source_before) != 0 || !S_ISREG(source_before.st_mode)
|| source_before.st_size < 0) {
(void)close(input);
return LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_ERROR;
}
if ((uint64_t)source_before.st_size > max_source_bytes) {
(void)close(input);
return LARDON3D_IMAGE_CATALOG_ASSET_TOO_LARGE;
}
char assets[PATH_MAX], images[PATH_MAX], temporary[PATH_MAX];
if (!join_path(assets, state->project_path, "assets")
|| !ensure_directory(assets) || !sync_directory(state->project_path)
|| !join_path(images, assets, "images") || !ensure_directory(images)
|| !sync_directory(assets)
|| snprintf(temporary, sizeof(temporary), "%s/.asset.tmp.XXXXXX", images) <= 0) {
(void)close(input);
return LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR;
}
int output = mkstemp(temporary);
if (output < 0) {
(void)close(input);
return LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR;
}
unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE];
uint64_t size = 0;
struct stat source_after;
CopyResult copy = hash_stream(input, output, hash, &size);
bool copied = copy == COPY_OK;
bool source_changed = copied && (fstat(input, &source_after) != 0
|| !same_source_snapshot(&source_before, &source_after));
bool source_close_ok = close(input) == 0;
bool destination_sync_ok = copied && !source_changed && source_close_ok
&& fsync(output) == 0;
int destination_close_result = close(output);
if (copy == COPY_DESTINATION_ERROR || (copied && !source_changed && source_close_ok
&& (!destination_sync_ok || destination_close_result != 0))) {
(void)unlink(temporary);
return LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR;
}
if (source_changed) {
(void)unlink(temporary);
return LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_CHANGED;
}
if (copy != COPY_OK || !source_close_ok) {
(void)unlink(temporary);
return LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_ERROR;
}
char hex[65], prefix[3], prefix_path[PATH_MAX], final[PATH_MAX];
hash_hex(hash, hex);
prefix[0] = hex[0]; prefix[1] = hex[1]; prefix[2] = '\0';
if (!join_path(prefix_path, images, prefix) || !ensure_directory(prefix_path)
|| !sync_directory(images) || !join_path(final, prefix_path, hex)) {
(void)unlink(temporary);
return LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR;
}
bool published = false;
if (link(temporary, final) == 0) {
published = unlink(temporary) == 0 && sync_directory(prefix_path);
} else if (errno == EEXIST && same_published_asset(final, hash, size)) {
published = unlink(temporary) == 0;
}
if (!published) {
(void)unlink(temporary);
return LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR;
}
char relative[LARDON3D_PROJECT_DB_PATH_CAPACITY];
int relative_size = snprintf(relative, sizeof(relative), "assets/images/%s/%s", prefix, hex);
if (relative_size <= 0 || (size_t)relative_size >= sizeof(relative)
|| lardon3d_project_db_register_image_asset(state->project_db, hash, relative,
size, created_at, asset) != LARDON3D_PROJECT_DB_OK)
return LARDON3D_IMAGE_CATALOG_ASSET_DB_ERROR;
return LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED;
}
Lardon3DImageCatalogAssetPublishResult
lardon3d_image_catalog_test_copy_hash(int input, int output)
{
unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE];
uint64_t size = 0;
CopyResult result = hash_stream(input, output, hash, &size);
return result == COPY_OK ? LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED
: result == COPY_DESTINATION_ERROR ? LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR
: LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_ERROR;
}
bool
lardon3d_image_catalog_create_scanset(Lardon3DAppState *state,
const char *name, Lardon3DProjectDbScanSet *scanset)
@ -144,59 +268,22 @@ lardon3d_image_catalog_import_file(Lardon3DAppState *state,
Lardon3DProjectDbScanSet scanset;
if (lardon3d_project_db_load_scanset(state->project_db, scanset_id, &scanset)
!= LARDON3D_PROJECT_DB_OK) return LARDON3D_IMAGE_CATALOG_INVALID_ARGUMENT;
int input = open(source_path, O_RDONLY | O_NOFOLLOW);
if (input < 0) return LARDON3D_IMAGE_CATALOG_SOURCE_ERROR;
struct stat source_information;
if (fstat(input, &source_information) != 0
|| !S_ISREG(source_information.st_mode) || source_information.st_size < 0) {
(void)close(input); return LARDON3D_IMAGE_CATALOG_SOURCE_ERROR;
}
char assets[PATH_MAX], images[PATH_MAX], temporary[PATH_MAX];
if (!join_path(assets, state->project_path, "assets")
|| !ensure_directory(assets) || !sync_directory(state->project_path)
|| !join_path(images, assets, "images") || !ensure_directory(images)
|| !sync_directory(assets)
|| snprintf(temporary, sizeof(temporary), "%s/.asset.tmp.XXXXXX", images) <= 0) {
(void)close(input); return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
}
int output = mkstemp(temporary);
if (output < 0) { (void)close(input); return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR; }
unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE];
uint64_t size = 0;
bool copied = hash_stream(input, output, hash, &size);
if (close(input) != 0) copied = false;
if (copied && fsync(output) != 0) copied = false;
if (close(output) != 0) copied = false;
if (!copied) {
(void)unlink(temporary); return LARDON3D_IMAGE_CATALOG_SOURCE_ERROR;
}
char hex[65], prefix[3], prefix_path[PATH_MAX], final[PATH_MAX];
hash_hex(hash, hex); prefix[0] = hex[0]; prefix[1] = hex[1]; prefix[2] = '\0';
if (!join_path(prefix_path, images, prefix) || !ensure_directory(prefix_path)
|| !sync_directory(images)
|| !join_path(final, prefix_path, hex)) {
(void)unlink(temporary); return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
}
bool published = false;
if (link(temporary, final) == 0) {
bool temporary_removed = unlink(temporary) == 0;
bool directory_synced = sync_directory(prefix_path);
published = temporary_removed && directory_synced;
} else if (errno == EEXIST && same_published_asset(final, hash, size)) {
published = unlink(temporary) == 0;
}
if (!published) {
(void)unlink(temporary); return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
}
char relative[LARDON3D_PROJECT_DB_PATH_CAPACITY];
int relative_size = snprintf(relative, sizeof(relative),
"assets/images/%s/%s", prefix, hex);
if (relative_size <= 0 || (size_t)relative_size >= sizeof(relative))
return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
Lardon3DProjectDbImageRegisterStatus status;
int64_t imported_at = (int64_t)time(NULL);
if (imported_at < 0) return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
Lardon3DImageCatalogAssetPublishResult published =
lardon3d_image_catalog_publish_asset_file(state, source_path, imported_at,
UINT64_MAX, asset);
if (published == LARDON3D_IMAGE_CATALOG_ASSET_INVALID_ARGUMENT)
return LARDON3D_IMAGE_CATALOG_INVALID_ARGUMENT;
if (published == LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_ERROR)
return LARDON3D_IMAGE_CATALOG_SOURCE_ERROR;
if (published == LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR)
return LARDON3D_IMAGE_CATALOG_PUBLICATION_ERROR;
if (published != LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED)
return LARDON3D_IMAGE_CATALOG_DB_ERROR;
Lardon3DProjectDbImageRegisterStatus status;
if (imported_at < 0 || lardon3d_project_db_register_image(state->project_db,
scanset_id, hash, relative, size, basename, source_path,
scanset_id, asset->sha256, asset->path, asset->size_bytes, basename, source_path,
producer_task_id, imported_at, &status, image)
!= LARDON3D_PROJECT_DB_OK) return LARDON3D_IMAGE_CATALOG_DB_ERROR;
if (lardon3d_project_db_load_image(state->project_db, image->image_id,

331
src/raw_development.cpp Normal file
View file

@ -0,0 +1,331 @@
#include <lardon3d/raw_development.h>
extern "C" {
#include <lardon3d/image_catalog.h>
}
#include <libraw/libraw.h>
#include <libdeflate.h>
#include <opencv2/core/version.hpp>
#include <opencv2/imgcodecs.hpp>
#include <openssl/evp.h>
#include <png.h>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <limits>
#include <new>
#include <string>
#include <vector>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
namespace {
constexpr uint32_t kMaxDimension = 16384;
constexpr uint64_t kMaxPixels = 40000000;
constexpr uint64_t kMaxSourceBytes = UINT64_C(1024) * 1024U * 1024U;
constexpr int kPngCompression = 6;
constexpr int kPngStrategy = cv::IMWRITE_PNG_STRATEGY_DEFAULT;
constexpr uint32_t kBackendKindLibRaw = 1;
constexpr uint32_t kEncoderKindOpenCvPng = 1;
constexpr size_t kRawFingerprintPayloadSize = 200;
bool valid_wb(const float wb[4]) {
for (size_t i = 0; i < 4; ++i) if (!std::isfinite(wb[i]) || wb[i] <= 0.0F) return false;
return true;
}
void put_u32(std::vector<unsigned char> *bytes, uint32_t value) {
for (unsigned i = 0; i < 4; ++i) bytes->push_back((unsigned char)(value >> (8U * i)));
}
void put_float(std::vector<unsigned char> *bytes, float value) {
uint32_t bits = 0; static_assert(sizeof(bits) == sizeof(value), "float width");
std::memcpy(&bits, &value, sizeof(bits)); put_u32(bytes, bits);
}
bool sha256(const std::vector<unsigned char> &bytes, unsigned char out[32]) {
unsigned int size = 0;
return EVP_Digest(bytes.data(), bytes.size(), out, &size, EVP_sha256(), nullptr) == 1 && size == 32;
}
bool checked_rgb_size(uint32_t width, uint32_t height, size_t *bytes) {
if (width == 0 || height == 0 || width > kMaxDimension || height > kMaxDimension) return false;
uint64_t pixels = (uint64_t)width * height;
if (pixels > kMaxPixels || pixels > SIZE_MAX / 3U) return false;
*bytes = (size_t)pixels * 3U; return true;
}
Lardon3DRawDevelopmentResult catalog_result(Lardon3DImageCatalogAssetPublishResult result) {
switch (result) {
case LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED: return LARDON3D_RAW_DEVELOPMENT_OK;
case LARDON3D_IMAGE_CATALOG_ASSET_INVALID_ARGUMENT: return LARDON3D_RAW_DEVELOPMENT_INVALID_ARGUMENT;
case LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_ERROR: return LARDON3D_RAW_DEVELOPMENT_SOURCE_NOT_FOUND;
case LARDON3D_IMAGE_CATALOG_ASSET_SOURCE_CHANGED: return LARDON3D_RAW_DEVELOPMENT_SOURCE_CHANGED;
case LARDON3D_IMAGE_CATALOG_ASSET_TOO_LARGE: return LARDON3D_RAW_DEVELOPMENT_OUTPUT_LIMIT_EXCEEDED;
case LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR: return LARDON3D_RAW_DEVELOPMENT_IO_ERROR;
case LARDON3D_IMAGE_CATALOG_ASSET_DB_ERROR: return LARDON3D_RAW_DEVELOPMENT_DB_ERROR;
}
return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR;
}
Lardon3DRawDevelopmentResult db_result(Lardon3DProjectDbResult result) {
if (result == LARDON3D_PROJECT_DB_OK) return LARDON3D_RAW_DEVELOPMENT_OK;
if (result == LARDON3D_PROJECT_DB_CONSTRAINT) return LARDON3D_RAW_DEVELOPMENT_CONSTRAINT;
if (result == LARDON3D_PROJECT_DB_NOT_FOUND) return LARDON3D_RAW_DEVELOPMENT_SOURCE_NOT_FOUND;
return LARDON3D_RAW_DEVELOPMENT_DB_ERROR;
}
bool same_scientific_derivation(const Lardon3DProjectDbAssetDerivation &stored,
const Lardon3DProjectDbAssetDerivation &expected) {
return stored.parent_asset_id == expected.parent_asset_id
&& stored.child_asset_id == expected.child_asset_id && stored.kind == expected.kind
&& stored.version == expected.version
&& std::memcmp(stored.parameter_fingerprint, expected.parameter_fingerprint,
sizeof(expected.parameter_fingerprint)) == 0;
}
Lardon3DRawDevelopmentResult ensure_derivation(Lardon3DProjectDb *database,
const Lardon3DProjectDbAssetDerivation &expected) {
Lardon3DProjectDbAssetDerivation stored = {};
Lardon3DProjectDbResult load = lardon3d_project_db_load_asset_derivation(
database, expected.child_asset_id, &stored);
if (load == LARDON3D_PROJECT_DB_NOT_FOUND)
return db_result(lardon3d_project_db_record_asset_derivation(database, &expected));
if (load != LARDON3D_PROJECT_DB_OK) return LARDON3D_RAW_DEVELOPMENT_DB_ERROR;
return same_scientific_derivation(stored, expected) ? LARDON3D_RAW_DEVELOPMENT_OK
: LARDON3D_RAW_DEVELOPMENT_CONSTRAINT;
}
Lardon3DRawDevelopmentResult ensure_capture_asset_role(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
Lardon3DProjectDbCaptureAssetRole expected_role) {
Lardon3DProjectDbResult attach = lardon3d_project_db_attach_capture_asset(
database, capture_id, asset_id, expected_role);
if (attach == LARDON3D_PROJECT_DB_OK) return LARDON3D_RAW_DEVELOPMENT_OK;
if (attach != LARDON3D_PROJECT_DB_CONSTRAINT) return db_result(attach);
Lardon3DProjectDbCaptureAsset page[LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX];
uint64_t after_asset_id = 0;
for (;;) {
size_t count = 0;
Lardon3DProjectDbResult listed = lardon3d_project_db_list_capture_assets(
database, capture_id, after_asset_id, page,
LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX, &count);
if (listed != LARDON3D_PROJECT_DB_OK) return LARDON3D_RAW_DEVELOPMENT_DB_ERROR;
for (size_t index = 0; index < count; ++index) {
if (page[index].asset_id == asset_id)
return page[index].role == expected_role ? LARDON3D_RAW_DEVELOPMENT_OK
: LARDON3D_RAW_DEVELOPMENT_CONSTRAINT;
if (page[index].asset_id > asset_id) return LARDON3D_RAW_DEVELOPMENT_CONSTRAINT;
}
if (count < LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX) return LARDON3D_RAW_DEVELOPMENT_CONSTRAINT;
after_asset_id = page[count - 1].asset_id;
}
}
Lardon3DRawDevelopmentResult libraw_result(int code) {
if (code == LIBRAW_FILE_UNSUPPORTED) return LARDON3D_RAW_DEVELOPMENT_UNSUPPORTED_RAW;
if (code == LIBRAW_UNSUFFICIENT_MEMORY) return LARDON3D_RAW_DEVELOPMENT_OUT_OF_MEMORY;
if (code == LIBRAW_DATA_ERROR || code == LIBRAW_IO_ERROR) return LARDON3D_RAW_DEVELOPMENT_CORRUPT_RAW;
return LARDON3D_RAW_DEVELOPMENT_DECODER_FAILURE;
}
bool write_all(int fd, const unsigned char *data, size_t size) {
size_t offset = 0;
while (offset < size) {
ssize_t written = write(fd, data + offset, size - offset);
if (written < 0 && errno == EINTR) continue;
if (written <= 0) return false;
offset += (size_t)written;
}
return true;
}
bool asset_file_path(const Lardon3DAppState *state, const Lardon3DProjectDbImageAsset &asset,
char path[LARDON3D_APP_STATE_PATH_CAPACITY]) {
int count = std::snprintf(path, LARDON3D_APP_STATE_PATH_CAPACITY, "%s/%s",
state->project_path, asset.path);
return count > 0 && count < LARDON3D_APP_STATE_PATH_CAPACITY;
}
bool verify_managed_asset(const char *path, const unsigned char expected[32]) {
int fd = open(path, O_RDONLY | O_NOFOLLOW);
if (fd < 0) return false;
struct stat info; EVP_MD_CTX *context = EVP_MD_CTX_new();
bool ok = fstat(fd, &info) == 0 && S_ISREG(info.st_mode) && context
&& EVP_DigestInit_ex(context, EVP_sha256(), nullptr) == 1;
unsigned char buffer[65536];
while (ok) {
ssize_t count = read(fd, buffer, sizeof(buffer));
if (count < 0 && errno == EINTR) continue;
if (count < 0) { ok = false; break; }
if (count == 0) break;
ok = EVP_DigestUpdate(context, buffer, (size_t)count) == 1;
}
unsigned char actual[32]; unsigned int length = 0;
if (ok) ok = EVP_DigestFinal_ex(context, actual, &length) == 1 && length == sizeof(actual)
&& std::memcmp(actual, expected, sizeof(actual)) == 0;
EVP_MD_CTX_free(context); (void)close(fd); return ok;
}
bool valid_name(const char *path, char name[LARDON3D_PROJECT_DB_IMAGE_NAME_CAPACITY]) {
const char *base = std::strrchr(path, '/'); base = base ? base + 1 : path;
size_t size = strnlen(base, LARDON3D_PROJECT_DB_IMAGE_NAME_CAPACITY);
if (size == 0 || size >= LARDON3D_PROJECT_DB_IMAGE_NAME_CAPACITY || std::strchr(base, '\\')
|| std::strchr(base, '\t') || std::strchr(base, '\r') || std::strchr(base, '\n')) return false;
std::memcpy(name, base, size + 1); return true;
}
}
extern "C" Lardon3DRawDevelopmentResult lardon3d_raw_development_policy_fingerprint(
const float wb[4], unsigned char fingerprint[32]) {
if (!wb || !fingerprint || !valid_wb(wb)) return LARDON3D_RAW_DEVELOPMENT_INVALID_ARGUMENT;
try {
if (std::strcmp(png_get_libpng_ver(nullptr), PNG_LIBPNG_VER_STRING) != 0)
return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR;
std::vector<unsigned char> bytes;
bytes.reserve(kRawFingerprintPayloadSize);
const char magic[] = "L3DRAWD1";
bytes.insert(bytes.end(), magic, magic + sizeof(magic) - 1);
put_u32(&bytes, 1); put_u32(&bytes, kBackendKindLibRaw);
put_u32(&bytes, LIBRAW_VERSION); put_u32(&bytes, libraw_versionNumber());
put_u32(&bytes, 3); put_u32(&bytes, 0); put_u32(&bytes, 0);
for (size_t i = 0; i < 4; ++i) put_float(&bytes, wb[i]);
put_u32(&bytes, 0); put_u32(&bytes, 0); put_u32(&bytes, 0); put_u32(&bytes, 1);
put_float(&bytes, 1.0F / 2.4F); put_float(&bytes, 12.92F);
put_u32(&bytes, 1); put_float(&bytes, 1.0F); put_float(&bytes, 0.0F);
put_u32(&bytes, 0); put_float(&bytes, 1.0F); put_float(&bytes, 0.0F);
put_u32(&bytes, 0); put_float(&bytes, 0.0F); put_u32(&bytes, 0); put_u32(&bytes, 0);
put_u32(&bytes, 0); put_u32(&bytes, 0); put_u32(&bytes, 0); put_u32(&bytes, 0);
put_u32(&bytes, 8); put_u32(&bytes, 3); put_u32(&bytes, kEncoderKindOpenCvPng);
put_u32(&bytes, kPngCompression);
put_u32(&bytes, kPngStrategy); put_u32(&bytes, 0); put_u32(&bytes, kMaxDimension);
put_u32(&bytes, (uint32_t)kMaxPixels);
put_u32(&bytes, (uint32_t)(kMaxSourceBytes >> 20)); put_u32(&bytes, CV_VERSION_MAJOR);
put_u32(&bytes, CV_VERSION_MINOR); put_u32(&bytes, CV_VERSION_REVISION);
put_u32(&bytes, PNG_LIBPNG_VER_MAJOR); put_u32(&bytes, PNG_LIBPNG_VER_MINOR);
put_u32(&bytes, PNG_LIBPNG_VER_RELEASE);
put_u32(&bytes, LIBDEFLATE_VERSION_MAJOR); put_u32(&bytes, LIBDEFLATE_VERSION_MINOR);
if (bytes.size() != kRawFingerprintPayloadSize)
return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR;
return sha256(bytes, fingerprint) ? LARDON3D_RAW_DEVELOPMENT_OK
: LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR;
} catch (const std::bad_alloc &) {
return LARDON3D_RAW_DEVELOPMENT_OUT_OF_MEMORY;
} catch (...) {
return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR;
}
}
#ifdef LARDON3D_RAW_DEVELOPMENT_TESTING
extern "C" size_t lardon3d_raw_development_test_policy_payload_size(void) {
return kRawFingerprintPayloadSize;
}
extern "C" Lardon3DRawDevelopmentResult
lardon3d_raw_development_test_ensure_derivation(
Lardon3DProjectDb *database, const Lardon3DProjectDbAssetDerivation *expected) {
return !database || !expected ? LARDON3D_RAW_DEVELOPMENT_INVALID_ARGUMENT
: ensure_derivation(database, *expected);
}
extern "C" Lardon3DRawDevelopmentResult
lardon3d_raw_development_test_ensure_capture_asset_role(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
Lardon3DProjectDbCaptureAssetRole expected_role) {
return ensure_capture_asset_role(database, capture_id, asset_id, expected_role);
}
#endif
extern "C" Lardon3DRawDevelopmentResult lardon3d_raw_develop_to_capture(
Lardon3DAppState *state, uint64_t capture_id, const char *source_arw_path,
uint64_t producer_task_id, int64_t created_at, Lardon3DRawDevelopmentOutput *output) {
if (!state || !state->project_loaded || !state->project_db || capture_id == 0 || !source_arw_path
|| !source_arw_path[0] || created_at < 0 || !output) return LARDON3D_RAW_DEVELOPMENT_INVALID_ARGUMENT;
std::memset(output, 0, sizeof(*output));
try {
Lardon3DProjectDbCapture capture;
Lardon3DRawDevelopmentResult result = db_result(lardon3d_project_db_load_capture(
state->project_db, capture_id, &capture));
if (result != LARDON3D_RAW_DEVELOPMENT_OK) return result;
result = catalog_result(lardon3d_image_catalog_publish_asset_file(
state, source_arw_path, created_at, kMaxSourceBytes, &output->source_asset));
if (result != LARDON3D_RAW_DEVELOPMENT_OK) return result;
result = ensure_capture_asset_role(state->project_db, capture_id,
output->source_asset.asset_id, LARDON3D_DB_CAPTURE_ASSET_SOURCE);
if (result != LARDON3D_RAW_DEVELOPMENT_OK) return result;
char managed_source[LARDON3D_APP_STATE_PATH_CAPACITY];
if (!asset_file_path(state, output->source_asset, managed_source)) return LARDON3D_RAW_DEVELOPMENT_IO_ERROR;
if (!verify_managed_asset(managed_source, output->source_asset.sha256)) return LARDON3D_RAW_DEVELOPMENT_SOURCE_CHANGED;
LibRaw raw;
raw.imgdata.params.user_qual = 3; raw.imgdata.params.half_size = 0;
raw.imgdata.params.user_flip = 0; raw.imgdata.params.use_auto_wb = 0;
raw.imgdata.params.use_camera_wb = 0; raw.imgdata.params.use_camera_matrix = 0;
raw.imgdata.params.output_profile = nullptr; raw.imgdata.params.camera_profile = nullptr;
raw.imgdata.params.output_color = 1; raw.imgdata.params.gamm[0] = 1.0 / 2.4;
raw.imgdata.params.gamm[1] = 12.92; raw.imgdata.params.no_auto_bright = 1;
raw.imgdata.params.bright = 1.0F; raw.imgdata.params.adjust_maximum_thr = 0.0F;
raw.imgdata.params.exp_correc = 0; raw.imgdata.params.exp_shift = 1.0F;
raw.imgdata.params.exp_preser = 0.0F; raw.imgdata.params.highlight = 0;
raw.imgdata.params.threshold = 0.0F; raw.imgdata.params.fbdd_noiserd = 0;
raw.imgdata.params.med_passes = 0; raw.imgdata.params.output_bps = 8;
raw.imgdata.params.output_tiff = 0; raw.imgdata.params.user_black = 0;
raw.imgdata.params.user_sat = 0; raw.imgdata.params.no_auto_scale = 0;
raw.imgdata.params.no_interpolation = 0;
for (size_t i = 0; i < 4; ++i) raw.imgdata.params.aber[i] = 1.0;
int code = raw.open_file(managed_source);
if (code != LIBRAW_SUCCESS) return libraw_result(code);
size_t decoded_size = 0;
if (!checked_rgb_size(raw.imgdata.sizes.iwidth, raw.imgdata.sizes.iheight, &decoded_size))
return LARDON3D_RAW_DEVELOPMENT_OUTPUT_LIMIT_EXCEEDED;
code = raw.unpack(); if (code != LIBRAW_SUCCESS) return libraw_result(code);
for (size_t i = 0; i < 4; ++i) output->resolved_camera_wb[i] = raw.imgdata.color.cam_mul[i];
if (!valid_wb(output->resolved_camera_wb)) return LARDON3D_RAW_DEVELOPMENT_UNSUPPORTED_RAW;
for (size_t i = 0; i < 4; ++i) raw.imgdata.params.user_mul[i] = output->resolved_camera_wb[i];
result = lardon3d_raw_development_policy_fingerprint(output->resolved_camera_wb,
output->parameter_fingerprint);
if (result != LARDON3D_RAW_DEVELOPMENT_OK) return result;
code = raw.dcraw_process(); if (code != LIBRAW_SUCCESS) return libraw_result(code);
int image_error = LIBRAW_SUCCESS;
libraw_processed_image_t *processed = raw.dcraw_make_mem_image(&image_error);
if (!processed) return libraw_result(image_error);
output->width = processed->width; output->height = processed->height;
size_t rgb_size = 0;
if (processed->type != LIBRAW_IMAGE_BITMAP || processed->colors != 3
|| !checked_rgb_size(output->width, output->height, &rgb_size)
|| processed->data_size != rgb_size) { LibRaw::dcraw_clear_mem(processed); return LARDON3D_RAW_DEVELOPMENT_OUTPUT_LIMIT_EXCEEDED; }
std::vector<unsigned char> bgr(rgb_size);
for (size_t i = 0; i < rgb_size; i += 3) { bgr[i] = processed->data[i + 2]; bgr[i + 1] = processed->data[i + 1]; bgr[i + 2] = processed->data[i]; }
LibRaw::dcraw_clear_mem(processed);
cv::Mat image((int)output->height, (int)output->width, CV_8UC3, bgr.data());
std::vector<unsigned char> png;
std::vector<int> parameters = {cv::IMWRITE_PNG_COMPRESSION, kPngCompression,
cv::IMWRITE_PNG_STRATEGY, kPngStrategy};
if (!cv::imencode(".png", image, png, parameters) || png.empty()) return LARDON3D_RAW_DEVELOPMENT_IO_ERROR;
cv::Mat validated = cv::imdecode(png, cv::IMREAD_UNCHANGED);
if (validated.empty() || validated.cols != (int)output->width
|| validated.rows != (int)output->height || validated.type() != CV_8UC3)
return LARDON3D_RAW_DEVELOPMENT_IO_ERROR;
char staging[LARDON3D_APP_STATE_PATH_CAPACITY];
int written = std::snprintf(staging, sizeof(staging), "%s/assets/images/.raw-png.tmp.XXXXXX", state->project_path);
if (written <= 0 || written >= (int)sizeof(staging)) return LARDON3D_RAW_DEVELOPMENT_IO_ERROR;
int fd = mkstemp(staging); if (fd < 0) return LARDON3D_RAW_DEVELOPMENT_IO_ERROR;
bool write_ok = write_all(fd, png.data(), png.size());
bool sync_ok = write_ok && fsync(fd) == 0;
int close_result = close(fd);
if (!write_ok || !sync_ok || close_result != 0) {
(void)unlink(staging);
return LARDON3D_RAW_DEVELOPMENT_IO_ERROR;
}
result = catalog_result(lardon3d_image_catalog_publish_asset_file(state, staging, created_at,
kMaxSourceBytes, &output->derived_asset));
(void)unlink(staging);
if (result != LARDON3D_RAW_DEVELOPMENT_OK) return result;
Lardon3DProjectDbAssetDerivation derivation = {};
derivation.parent_asset_id = output->source_asset.asset_id; derivation.child_asset_id = output->derived_asset.asset_id;
derivation.kind = LARDON3D_DB_ASSET_DERIVATION_GENERIC_VERSIONED; derivation.version = 1;
std::memcpy(derivation.parameter_fingerprint, output->parameter_fingerprint, sizeof(derivation.parameter_fingerprint));
derivation.has_producer_task = producer_task_id != 0; derivation.producer_task_id = producer_task_id;
derivation.created_at = created_at;
result = ensure_derivation(state->project_db, derivation);
if (result != LARDON3D_RAW_DEVELOPMENT_OK) return result;
char name[LARDON3D_PROJECT_DB_IMAGE_NAME_CAPACITY];
if (!valid_name(source_arw_path, name)) return LARDON3D_RAW_DEVELOPMENT_INVALID_ARGUMENT;
Lardon3DProjectDbImageRegisterStatus status;
result = db_result(lardon3d_project_db_publish_derived_capture_image(state->project_db, capture_id,
output->derived_asset.asset_id, name, source_arw_path, producer_task_id, created_at, &status, &output->image));
if (result != LARDON3D_RAW_DEVELOPMENT_OK) return result;
std::snprintf(output->libraw_version, sizeof(output->libraw_version), "%s", libraw_version());
std::snprintf(output->png_encoder_version, sizeof(output->png_encoder_version), "%s", CV_VERSION);
return LARDON3D_RAW_DEVELOPMENT_OK;
} catch (const std::bad_alloc &) { return LARDON3D_RAW_DEVELOPMENT_OUT_OF_MEMORY; }
catch (...) { return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR; }
}

View file

@ -12,6 +12,9 @@
#include <lardon3d/image_catalog.h>
Lardon3DImageCatalogAssetPublishResult lardon3d_image_catalog_test_copy_hash(
int input, int output);
#define CHECK(condition) do { if (!(condition)) { \
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); return false; \
} } while (0)
@ -139,12 +142,27 @@ run_test(void)
{
char root[] = "/tmp/lardon3d-catalog-v1-XXXXXX";
CHECK(mkdtemp(root));
char database_path[PATH_MAX], source_a[PATH_MAX], source_b[PATH_MAX];
char database_path[PATH_MAX], source_a[PATH_MAX], source_b[PATH_MAX], copied_path[PATH_MAX];
CHECK(join_path(database_path, root, "project.db"));
CHECK(join_path(source_a, root, "photo001.jpg"));
CHECK(join_path(source_b, root, "other-name.jpg"));
CHECK(join_path(copied_path, root, "copy.bin"));
CHECK(write_file(source_a, "same-image-content"));
CHECK(write_file(source_b, "same-image-content"));
int copy_source = open(source_a, O_RDONLY);
int copy_destination = open(copied_path, O_WRONLY | O_CREAT | O_EXCL, 0600);
CHECK(copy_source >= 0 && copy_destination >= 0);
CHECK(lardon3d_image_catalog_test_copy_hash(copy_source, copy_destination)
== LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED);
CHECK(close(copy_source) == 0 && close(copy_destination) == 0);
struct stat full_information;
CHECK(stat("/dev/full", &full_information) == 0 && S_ISCHR(full_information.st_mode));
copy_source = open(source_a, O_RDONLY);
copy_destination = open("/dev/full", O_WRONLY);
CHECK(copy_source >= 0 && copy_destination >= 0);
CHECK(lardon3d_image_catalog_test_copy_hash(copy_source, copy_destination)
== LARDON3D_IMAGE_CATALOG_ASSET_PUBLICATION_ERROR);
CHECK(close(copy_source) == 0 && close(copy_destination) == 0);
Lardon3DProjectDb *database = NULL;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
CHECK(lardon3d_project_db_open(database_path, &database, error)

View file

@ -0,0 +1,300 @@
#include <cerrno>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
extern "C" {
#include <lardon3d/image_catalog.h>
}
#include <lardon3d/raw_development.h>
extern "C" size_t lardon3d_raw_development_test_policy_payload_size(void);
extern "C" Lardon3DRawDevelopmentResult lardon3d_raw_development_test_ensure_derivation(
Lardon3DProjectDb *database, const Lardon3DProjectDbAssetDerivation *expected);
extern "C" Lardon3DRawDevelopmentResult
lardon3d_raw_development_test_ensure_capture_asset_role(
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
Lardon3DProjectDbCaptureAssetRole expected_role);
#define CHECK(condition) do { if (!(condition)) { \
std::fprintf(stderr, "Failure line %d: %s\\n", __LINE__, #condition); return false; \
} } while (0)
static bool write_file(const char *path, const char *text) {
int fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) return false;
size_t size = std::strlen(text);
bool ok = write(fd, text, size) == (ssize_t)size;
return close(fd) == 0 && ok;
}
static bool remove_tree(const char *path) {
struct stat info;
if (lstat(path, &info) != 0) return errno == ENOENT;
if (!S_ISDIR(info.st_mode)) return unlink(path) == 0;
DIR *directory = opendir(path);
if (!directory) return false;
bool ok = true;
for (dirent *entry = readdir(directory); entry; entry = readdir(directory)) {
if (std::strcmp(entry->d_name, ".") == 0 || std::strcmp(entry->d_name, "..") == 0) continue;
char child[4096];
int count = std::snprintf(child, sizeof(child), "%s/%s", path, entry->d_name);
if (count <= 0 || count >= (int)sizeof(child) || !remove_tree(child)) ok = false;
}
return closedir(directory) == 0 && rmdir(path) == 0 && ok;
}
static void print_hex(const char *key, const unsigned char value[32]) {
static const char digits[] = "0123456789abcdef";
char text[65];
for (size_t index = 0; index < 32; ++index) {
text[index * 2] = digits[value[index] >> 4];
text[index * 2 + 1] = digits[value[index] & 15U];
}
text[64] = '\0';
std::printf("%s=%s\n", key, text);
}
static bool capture_has_asset(Lardon3DProjectDb *db, uint64_t capture_id,
uint64_t asset_id, Lardon3DProjectDbCaptureAssetRole role) {
Lardon3DProjectDbCaptureAsset assets[4];
size_t count = 0;
if (lardon3d_project_db_list_capture_assets(db, capture_id, 0, assets, 4, &count)
!= LARDON3D_PROJECT_DB_OK) return false;
for (size_t index = 0; index < count; ++index) {
if (assets[index].asset_id == asset_id && assets[index].role == role) return true;
}
return false;
}
static bool run_golden(Lardon3DAppState *state, Lardon3DProjectDb *db,
const char *golden_path, bool keep, const char *root) {
Lardon3DProjectDbScanSet scanset;
CHECK(lardon3d_project_db_create_scanset(db, "A6000 golden", &scanset)
== LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbCapture capture;
CHECK(lardon3d_project_db_create_capture(db, scanset.scanset_id, 2, &capture)
== LARDON3D_PROJECT_DB_OK);
Lardon3DRawDevelopmentOutput first;
Lardon3DRawDevelopmentResult first_result = lardon3d_raw_develop_to_capture(
state, capture.capture_id, golden_path, 0, 2, &first);
std::fprintf(stderr, "GOLDEN_FIRST_RESULT=%d\n", (int)first_result);
CHECK(first_result == LARDON3D_RAW_DEVELOPMENT_OK);
CHECK(first.source_asset.asset_id != 0 && first.derived_asset.asset_id != 0
&& first.image.image_id != 0);
CHECK(first.source_asset.asset_id != first.derived_asset.asset_id);
Lardon3DProjectDbCapture image_capture;
CHECK(lardon3d_project_db_find_capture_for_image(db, first.image.image_id, &image_capture)
== LARDON3D_PROJECT_DB_OK && image_capture.capture_id == capture.capture_id);
CHECK(capture_has_asset(db, capture.capture_id, first.source_asset.asset_id,
LARDON3D_DB_CAPTURE_ASSET_SOURCE));
CHECK(capture_has_asset(db, capture.capture_id, first.derived_asset.asset_id,
LARDON3D_DB_CAPTURE_ASSET_DERIVED));
Lardon3DProjectDbAssetDerivation derivation;
CHECK(lardon3d_project_db_load_asset_derivation(db, first.derived_asset.asset_id, &derivation)
== LARDON3D_PROJECT_DB_OK);
CHECK(derivation.parent_asset_id == first.source_asset.asset_id
&& derivation.child_asset_id == first.derived_asset.asset_id);
CHECK(std::memcmp(derivation.parameter_fingerprint, first.parameter_fingerprint,
sizeof(first.parameter_fingerprint)) == 0);
uint64_t selected = 0;
CHECK(lardon3d_project_db_get_selected_capture_image(db, capture.capture_id, &selected)
== LARDON3D_PROJECT_DB_NOT_FOUND);
CHECK(first.width > 0 && first.height > 0 && first.width <= 16384 && first.height <= 16384
&& (uint64_t)first.width * first.height <= 40000000U);
for (size_t index = 0; index < 4; ++index)
CHECK(std::isfinite(first.resolved_camera_wb[index]) && first.resolved_camera_wb[index] > 0.0F);
char png_path[4096];
CHECK(std::snprintf(png_path, sizeof(png_path), "%s/%s", state->project_path,
first.derived_asset.path) > 0);
struct stat png_info;
CHECK(stat(png_path, &png_info) == 0 && S_ISREG(png_info.st_mode) && png_info.st_size > 0);
std::printf("GOLDEN_WIDTH=%u\n", first.width);
std::printf("GOLDEN_HEIGHT=%u\n", first.height);
for (size_t index = 0; index < 4; ++index)
std::printf("GOLDEN_WB_%zu=%.9g\n", index, first.resolved_camera_wb[index]);
print_hex("GOLDEN_SOURCE_SHA256", first.source_asset.sha256);
print_hex("GOLDEN_DERIVED_SHA256", first.derived_asset.sha256);
print_hex("GOLDEN_PARAMETER_FINGERPRINT", first.parameter_fingerprint);
std::printf("GOLDEN_SOURCE_ASSET_ID=%llu\n",
(unsigned long long)first.source_asset.asset_id);
std::printf("GOLDEN_DERIVED_ASSET_ID=%llu\n",
(unsigned long long)first.derived_asset.asset_id);
std::printf("GOLDEN_IMAGE_ID=%llu\n", (unsigned long long)first.image.image_id);
std::printf("GOLDEN_LIBRAW=%s\n", first.libraw_version);
std::printf("GOLDEN_PNG_ENCODER=%s\n", first.png_encoder_version);
std::printf("GOLDEN_PNG_PATH=%s\n", png_path);
std::printf("GOLDEN_PNG_BYTES=%llu\n", (unsigned long long)png_info.st_size);
Lardon3DRawDevelopmentOutput second;
Lardon3DRawDevelopmentResult second_result = lardon3d_raw_develop_to_capture(
state, capture.capture_id, golden_path, 0, 2, &second);
std::fprintf(stderr, "GOLDEN_SECOND_RESULT=%d\n", (int)second_result);
CHECK(second_result == LARDON3D_RAW_DEVELOPMENT_OK);
CHECK(second.source_asset.asset_id == first.source_asset.asset_id
&& second.derived_asset.asset_id == first.derived_asset.asset_id
&& second.image.image_id == first.image.image_id
&& std::memcmp(second.source_asset.sha256, first.source_asset.sha256, 32) == 0
&& std::memcmp(second.derived_asset.sha256, first.derived_asset.sha256, 32) == 0
&& std::memcmp(second.parameter_fingerprint, first.parameter_fingerprint, 32) == 0
&& second.width == first.width && second.height == first.height
&& std::memcmp(second.resolved_camera_wb, first.resolved_camera_wb,
sizeof(first.resolved_camera_wb)) == 0);
uint64_t image_count = 0;
CHECK(lardon3d_project_db_count_images(db, scanset.scanset_id, &image_count)
== LARDON3D_PROJECT_DB_OK && image_count == 1);
CHECK(lardon3d_project_db_get_selected_capture_image(db, capture.capture_id, &selected)
== LARDON3D_PROJECT_DB_NOT_FOUND);
if (keep) std::printf("GOLDEN_PROJECT_PATH=%s\n", root);
std::printf("GOLDEN_A6000_RESULT=PASS\n");
return true;
}
static bool run_test() {
float wb_a[4] = {2.0F, 1.0F, 1.0F, 1.5F};
float wb_b[4] = {2.1F, 1.0F, 1.0F, 1.5F};
unsigned char first[32], second[32], changed[32];
CHECK(lardon3d_raw_development_policy_fingerprint(wb_a, first) == LARDON3D_RAW_DEVELOPMENT_OK);
CHECK(lardon3d_raw_development_policy_fingerprint(wb_a, second) == LARDON3D_RAW_DEVELOPMENT_OK);
CHECK(lardon3d_raw_development_policy_fingerprint(wb_b, changed) == LARDON3D_RAW_DEVELOPMENT_OK);
CHECK(std::memcmp(first, second, sizeof(first)) == 0);
CHECK(std::memcmp(first, changed, sizeof(first)) != 0);
CHECK(lardon3d_raw_development_test_policy_payload_size() == 200);
CHECK(lardon3d_raw_development_policy_fingerprint(nullptr, first) == LARDON3D_RAW_DEVELOPMENT_INVALID_ARGUMENT);
char root[] = "/tmp/lardon3d-raw-XXXXXX";
CHECK(mkdtemp(root) != nullptr);
char database_path[4096], corrupt_path[4096], link_path[4096], parent_path[4096], child_path[4096],
other_parent_path[4096];
CHECK(std::snprintf(database_path, sizeof(database_path), "%s/project.db", root) > 0);
CHECK(std::snprintf(corrupt_path, sizeof(corrupt_path), "%s/not-arw.bin", root) > 0);
CHECK(std::snprintf(link_path, sizeof(link_path), "%s/link.arw", root) > 0);
CHECK(std::snprintf(parent_path, sizeof(parent_path), "%s/parent.bin", root) > 0);
CHECK(std::snprintf(child_path, sizeof(child_path), "%s/child.bin", root) > 0);
CHECK(std::snprintf(other_parent_path, sizeof(other_parent_path), "%s/other.bin", root) > 0);
CHECK(write_file(corrupt_path, "not a raw image"));
CHECK(write_file(parent_path, "parent"));
CHECK(write_file(child_path, "child"));
CHECK(write_file(other_parent_path, "other"));
Lardon3DProjectDb *db = nullptr;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
CHECK(lardon3d_project_db_open(database_path, &db, error) == LARDON3D_PROJECT_DB_OK);
Lardon3DAppState state; lardon3d_app_state_init(&state);
state.project_loaded = true; state.project_db = db;
CHECK(std::snprintf(state.project_path, sizeof(state.project_path), "%s", root) > 0);
Lardon3DProjectDbImageAsset parent_asset, child_asset, other_parent_asset;
CHECK(lardon3d_image_catalog_publish_asset_file(&state, parent_path, 1, UINT64_MAX,
&parent_asset) == LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED);
CHECK(lardon3d_image_catalog_publish_asset_file(&state, child_path, 1, UINT64_MAX,
&child_asset) == LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED);
CHECK(lardon3d_image_catalog_publish_asset_file(&state, other_parent_path, 1, UINT64_MAX,
&other_parent_asset) == LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED);
Lardon3DProjectDbAssetDerivation expected = {};
expected.parent_asset_id = parent_asset.asset_id;
expected.child_asset_id = child_asset.asset_id;
expected.kind = LARDON3D_DB_ASSET_DERIVATION_GENERIC_VERSIONED;
expected.version = 1;
std::memset(expected.parameter_fingerprint, 0x5a, sizeof(expected.parameter_fingerprint));
expected.created_at = 5;
Lardon3DProjectDbAssetDerivation stored;
CHECK(lardon3d_project_db_load_asset_derivation(db, child_asset.asset_id, &stored)
== LARDON3D_PROJECT_DB_NOT_FOUND);
CHECK(lardon3d_raw_development_test_ensure_derivation(db, &expected)
== LARDON3D_RAW_DEVELOPMENT_OK);
CHECK(lardon3d_project_db_load_asset_derivation(db, child_asset.asset_id, &stored)
== LARDON3D_PROJECT_DB_OK && stored.parent_asset_id == parent_asset.asset_id
&& stored.child_asset_id == child_asset.asset_id && stored.kind == expected.kind
&& stored.version == expected.version && !stored.has_producer_task
&& stored.created_at == expected.created_at
&& std::memcmp(stored.parameter_fingerprint, expected.parameter_fingerprint, 32) == 0);
Lardon3DProjectDbAssetDerivation retry = expected;
retry.has_producer_task = true;
retry.producer_task_id = 1;
retry.created_at = 9;
CHECK(lardon3d_raw_development_test_ensure_derivation(db, &retry)
== LARDON3D_RAW_DEVELOPMENT_OK);
CHECK(lardon3d_project_db_load_asset_derivation(db, child_asset.asset_id, &stored)
== LARDON3D_PROJECT_DB_OK && !stored.has_producer_task && stored.created_at == 5);
retry.parent_asset_id = other_parent_asset.asset_id;
CHECK(lardon3d_raw_development_test_ensure_derivation(db, &retry)
== LARDON3D_RAW_DEVELOPMENT_CONSTRAINT);
retry.parent_asset_id = parent_asset.asset_id;
retry.parameter_fingerprint[0] ^= 1U;
CHECK(lardon3d_raw_development_test_ensure_derivation(db, &retry)
== LARDON3D_RAW_DEVELOPMENT_CONSTRAINT);
CHECK(lardon3d_project_db_load_asset_derivation(db, child_asset.asset_id, &stored)
== LARDON3D_PROJECT_DB_OK && stored.parent_asset_id == parent_asset.asset_id
&& std::memcmp(stored.parameter_fingerprint, expected.parameter_fingerprint, 32) == 0);
Lardon3DProjectDbScanSet attachment_scanset;
CHECK(lardon3d_project_db_create_scanset(db, "Attachment", &attachment_scanset)
== LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbCapture attachment_capture;
CHECK(lardon3d_project_db_create_capture(db, attachment_scanset.scanset_id, 1,
&attachment_capture) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_raw_development_test_ensure_capture_asset_role(db,
attachment_capture.capture_id, parent_asset.asset_id, LARDON3D_DB_CAPTURE_ASSET_SOURCE)
== LARDON3D_RAW_DEVELOPMENT_OK);
CHECK(capture_has_asset(db, attachment_capture.capture_id, parent_asset.asset_id,
LARDON3D_DB_CAPTURE_ASSET_SOURCE));
CHECK(lardon3d_raw_development_test_ensure_capture_asset_role(db,
attachment_capture.capture_id, parent_asset.asset_id, LARDON3D_DB_CAPTURE_ASSET_SOURCE)
== LARDON3D_RAW_DEVELOPMENT_OK);
CHECK(lardon3d_project_db_attach_capture_asset(db, attachment_capture.capture_id,
other_parent_asset.asset_id, LARDON3D_DB_CAPTURE_ASSET_SOURCE) == LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbCaptureAsset attachment_page[4];
size_t attachment_count = 0;
CHECK(lardon3d_project_db_list_capture_assets(db, attachment_capture.capture_id, 0,
attachment_page, 4, &attachment_count) == LARDON3D_PROJECT_DB_OK
&& attachment_count == 2);
Lardon3DProjectDbCapture conflict_capture;
CHECK(lardon3d_project_db_create_capture(db, attachment_scanset.scanset_id, 2,
&conflict_capture) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_attach_capture_asset(db, conflict_capture.capture_id,
child_asset.asset_id, LARDON3D_DB_CAPTURE_ASSET_DERIVED) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_raw_development_test_ensure_capture_asset_role(db,
conflict_capture.capture_id, child_asset.asset_id, LARDON3D_DB_CAPTURE_ASSET_SOURCE)
== LARDON3D_RAW_DEVELOPMENT_CONSTRAINT);
CHECK(capture_has_asset(db, conflict_capture.capture_id, child_asset.asset_id,
LARDON3D_DB_CAPTURE_ASSET_DERIVED));
Lardon3DProjectDbImageAsset rejected_asset;
CHECK(lardon3d_image_catalog_publish_asset_file(&state, corrupt_path, 1, 1,
&rejected_asset) == LARDON3D_IMAGE_CATALOG_ASSET_TOO_LARGE);
CHECK(rejected_asset.asset_id == 0);
Lardon3DProjectDbScanSet scanset;
CHECK(lardon3d_project_db_create_scanset(db, "RAW", &scanset) == LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbCapture capture;
CHECK(lardon3d_project_db_create_capture(db, scanset.scanset_id, 1, &capture) == LARDON3D_PROJECT_DB_OK);
Lardon3DRawDevelopmentOutput output;
CHECK(lardon3d_raw_develop_to_capture(&state, capture.capture_id, "/missing/file.arw", 0, 1, &output)
== LARDON3D_RAW_DEVELOPMENT_SOURCE_NOT_FOUND);
CHECK(symlink(corrupt_path, link_path) == 0);
CHECK(lardon3d_raw_develop_to_capture(&state, capture.capture_id, link_path, 0, 1, &output)
== LARDON3D_RAW_DEVELOPMENT_SOURCE_NOT_FOUND);
Lardon3DRawDevelopmentResult corrupt = lardon3d_raw_develop_to_capture(
&state, capture.capture_id, corrupt_path, 0, 1, &output);
CHECK(corrupt == LARDON3D_RAW_DEVELOPMENT_UNSUPPORTED_RAW
|| corrupt == LARDON3D_RAW_DEVELOPMENT_CORRUPT_RAW);
uint64_t selected = 0;
CHECK(lardon3d_project_db_get_selected_capture_image(db, capture.capture_id, &selected)
== LARDON3D_PROJECT_DB_NOT_FOUND);
uint64_t count = 1;
CHECK(lardon3d_project_db_count_images(db, scanset.scanset_id, &count) == LARDON3D_PROJECT_DB_OK && count == 0);
const char *golden_path = std::getenv("LARDON3D_RAW_GOLDEN");
bool golden = golden_path && golden_path[0];
const char *keep_value = std::getenv("LARDON3D_RAW_GOLDEN_KEEP");
bool keep = golden && keep_value && std::strcmp(keep_value, "1") == 0;
if (golden) CHECK(run_golden(&state, db, golden_path, keep, root));
lardon3d_project_db_close(db);
return keep || remove_tree(root);
}
int main() { return run_test() ? 0 : 1; }