blob: b170e736c28d7a776171a2f27da5be77616eb8f5 [file] [log] [blame]
/*
* Copyright 2025 Google LLC.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "rust/icc/FFI.h"
#include "rust/icc/FFI.rs.h" // Generated by CXX bridge
#include "modules/skcms/skcms.h"
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <type_traits>
namespace rust_icc {
bool ApproximateCurveWrapper(rust::Slice<const uint16_t> table,
TransferFunction& out_approx,
float& out_max_error) {
if (table.empty()) {
return false;
}
// Construct skcms_Curve for the table
skcms_Curve curve;
memset(&curve, 0, sizeof(skcms_Curve));
curve.table_entries = static_cast<uint32_t>(table.size());
curve.table_8 = nullptr;
curve.table_16 = reinterpret_cast<const uint8_t*>(table.data());
// Call skcms_ApproximateCurve (signature verified at compile time)
skcms_TransferFunction skcms_approx;
float max_error = 0.0f;
bool success = skcms_ApproximateCurve(&curve, &skcms_approx, &max_error);
if (!success) {
return false;
}
// Copy result field-by-field to avoid memcpy of non-trivial type
out_approx.g = skcms_approx.g;
out_approx.a = skcms_approx.a;
out_approx.b = skcms_approx.b;
out_approx.c = skcms_approx.c;
out_approx.d = skcms_approx.d;
out_approx.e = skcms_approx.e;
out_approx.f = skcms_approx.f;
out_max_error = max_error;
return true;
}
void ToSkcmsMatrix3x3(const Matrix3x3& rust_matrix, skcms_Matrix3x3* out_skcms) {
// Note: std::is_layout_compatible_v (C++20) is not yet implemented in LLVM (P0466R5).
// Use sizeof/alignof + standard_layout + trivially_copyable as the next best alternative.
static_assert(sizeof(Matrix3x3) == sizeof(skcms_Matrix3x3),
"Matrix3x3 must have same size as skcms_Matrix3x3 for memcpy");
static_assert(alignof(Matrix3x3) == alignof(skcms_Matrix3x3),
"Matrix3x3 must have same alignment as skcms_Matrix3x3 for memcpy");
static_assert(std::is_standard_layout_v<Matrix3x3>,
"Matrix3x3 must have standard layout for safe memcpy");
static_assert(std::is_trivially_copyable_v<Matrix3x3>,
"Matrix3x3 must be trivially copyable for memcpy");
memcpy(out_skcms, &rust_matrix, sizeof(skcms_Matrix3x3));
}
void ToSkcmsTransferFunction(const TransferFunction& rust_tf,
skcms_TransferFunction* out_skcms) {
static_assert(sizeof(TransferFunction) == sizeof(skcms_TransferFunction),
"TransferFunction must have same size as skcms_TransferFunction for memcpy");
static_assert(alignof(TransferFunction) == alignof(skcms_TransferFunction),
"TransferFunction must have same alignment as skcms_TransferFunction for memcpy");
static_assert(std::is_standard_layout_v<TransferFunction>,
"TransferFunction must have standard layout for safe memcpy");
static_assert(std::is_trivially_copyable_v<TransferFunction>,
"TransferFunction must be trivially copyable for memcpy");
// Spot-check field offsets in skcms_TransferFunction to verify the unusual ordering (g, a, b, c, d, e, f)
static_assert(offsetof(skcms_TransferFunction, g) == 0,
"skcms_TransferFunction::g must be at offset 0");
static_assert(offsetof(skcms_TransferFunction, a) == sizeof(float),
"skcms_TransferFunction::a must be at offset sizeof(float)");
memcpy(out_skcms, &rust_tf, sizeof(skcms_TransferFunction));
}
static void ToSkcmsCurve(const rust_icc::Curve& rust_curve, skcms_Curve* out_skcms) {
// Note: Curve table data (table_16) is borrowed from Rust Vec via .data().
// See ToSkcmsIccProfile documentation about lifetime requirements.
if (rust_curve.table_entries == 0) {
out_skcms->table_entries = 0;
ToSkcmsTransferFunction(rust_curve.parametric, &out_skcms->parametric);
} else {
out_skcms->table_entries = rust_curve.table_entries;
out_skcms->table_8 = nullptr;
out_skcms->table_16 = rust_curve.table_data.data();
}
}
static bool ToSkcmsA2B(const rust_icc::A2B& rust_a2b, skcms_A2B* out_skcms) {
memset(out_skcms, 0, sizeof(skcms_A2B));
// Input curves: If input_channels is non-zero, ensure we have enough curves
out_skcms->input_channels = rust_a2b.input_channels;
if (rust_a2b.input_channels > 0) {
if (rust_a2b.input_channels < 1 || rust_a2b.input_channels > 4) {
return false;
}
// Only validate curve count if input_channels is specified
if (!rust_a2b.input_curves.empty() && rust_a2b.input_channels > rust_a2b.input_curves.size()) {
return false;
}
for (size_t i = 0; i < rust_a2b.input_curves.size() && i < 4; i++) {
ToSkcmsCurve(rust_a2b.input_curves[i], &out_skcms->input_curves[i]);
}
}
// Grid data
static_assert(std::is_same_v<decltype(out_skcms->grid_points), uint8_t[4]>,
"skcms_A2B::grid_points must be uint8_t[4]");
static_assert(std::is_trivially_copyable_v<uint8_t>,
"uint8_t must be trivially copyable for memcpy");
if (rust_a2b.grid_points.size() != 4) {
return false;
}
memcpy(out_skcms->grid_points, rust_a2b.grid_points.data(), 4);
if (rust_a2b.is_16bit_grid) {
out_skcms->grid_16 = rust_a2b.grid_data.data();
} else {
out_skcms->grid_8 = rust_a2b.grid_data.data();
}
// Matrix curves and matrix
// If matrix_channels is explicitly set, use it. Otherwise, infer from matrix_curves presence.
uint32_t effective_matrix_channels = rust_a2b.matrix_channels;
if (effective_matrix_channels == 0 && !rust_a2b.matrix_curves.empty()) {
// Infer matrix_channels from the number of matrix curves provided
effective_matrix_channels = std::min(static_cast<uint32_t>(rust_a2b.matrix_curves.size()), 3u);
}
if (effective_matrix_channels > rust_a2b.matrix_curves.size()) {
return false;
}
if (effective_matrix_channels > 3) {
return false;
}
out_skcms->matrix_channels = effective_matrix_channels;
for (size_t i = 0; i < rust_a2b.matrix_curves.size() && i < 3; i++) {
ToSkcmsCurve(rust_a2b.matrix_curves[i], &out_skcms->matrix_curves[i]);
}
// Copy 3x3 matrix portion (only if we have matrix curves)
if (effective_matrix_channels > 0) {
skcms_Matrix3x3 temp_matrix;
ToSkcmsMatrix3x3(rust_a2b.matrix, &temp_matrix);
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
out_skcms->matrix.vals[row][col] = temp_matrix.vals[row][col];
}
// 4th column is the bias/offset
out_skcms->matrix.vals[row][3] = rust_a2b.matrix_bias[row];
}
}
// Output curves: If output_channels is non-zero, ensure we have enough curves
out_skcms->output_channels = rust_a2b.output_channels;
if (rust_a2b.output_channels > 0) {
if (rust_a2b.output_channels > 4) {
return false;
}
// Only validate curve count if output_channels is specified
if (!rust_a2b.output_curves.empty() && rust_a2b.output_channels > rust_a2b.output_curves.size()) {
return false;
}
for (size_t i = 0; i < rust_a2b.output_curves.size() && i < 4; i++) {
ToSkcmsCurve(rust_a2b.output_curves[i], &out_skcms->output_curves[i]);
}
}
return true;
}
// Helper to populate skcms_B2A from Rust B2A data
static bool ToSkcmsB2A(const rust_icc::B2A& rust_b2a, skcms_B2A* out_skcms) {
memset(out_skcms, 0, sizeof(skcms_B2A));
// Input curves
if (rust_b2a.input_channels != 3) {
return false;
}
if (rust_b2a.input_curves.size() < 3) {
return false;
}
out_skcms->input_channels = rust_b2a.input_channels;
for (size_t i = 0; i < 3; i++) {
ToSkcmsCurve(rust_b2a.input_curves[i], &out_skcms->input_curves[i]);
}
// Matrix and matrix curves
// If matrix_channels is explicitly set, use it. Otherwise, infer from matrix_curves presence.
uint32_t effective_matrix_channels = rust_b2a.matrix_channels;
if (effective_matrix_channels == 0 && !rust_b2a.matrix_curves.empty()) {
// Infer matrix_channels from the number of matrix curves provided
effective_matrix_channels = std::min(static_cast<uint32_t>(rust_b2a.matrix_curves.size()), 3u);
}
if (effective_matrix_channels > rust_b2a.matrix_curves.size()) {
return false;
}
if (effective_matrix_channels > 3) {
return false;
}
out_skcms->matrix_channels = effective_matrix_channels;
// Copy matrix and matrix curves only if matrix stage is active
if (effective_matrix_channels > 0) {
skcms_Matrix3x3 temp_matrix;
ToSkcmsMatrix3x3(rust_b2a.matrix, &temp_matrix);
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
out_skcms->matrix.vals[row][col] = temp_matrix.vals[row][col];
}
out_skcms->matrix.vals[row][3] = rust_b2a.matrix_bias[row];
}
for (size_t i = 0; i < rust_b2a.matrix_curves.size() && i < 3; i++) {
ToSkcmsCurve(rust_b2a.matrix_curves[i], &out_skcms->matrix_curves[i]);
}
}
// Grid data
static_assert(std::is_same_v<decltype(out_skcms->grid_points), uint8_t[4]>,
"skcms_B2A::grid_points must be uint8_t[4]");
static_assert(std::is_trivially_copyable_v<uint8_t>,
"uint8_t must be trivially copyable for memcpy");
if (rust_b2a.grid_points.size() != 4) {
return false;
}
memcpy(out_skcms->grid_points, rust_b2a.grid_points.data(), 4);
if (rust_b2a.is_16bit_grid) {
out_skcms->grid_16 = rust_b2a.grid_data.data();
} else {
out_skcms->grid_8 = rust_b2a.grid_data.data();
}
// Output curves
if (rust_b2a.output_channels < 1 || rust_b2a.output_channels > 4) {
return false;
}
if (rust_b2a.output_channels > rust_b2a.output_curves.size()) {
return false;
}
out_skcms->output_channels = rust_b2a.output_channels;
for (size_t i = 0; i < rust_b2a.output_channels; i++) {
ToSkcmsCurve(rust_b2a.output_curves[i], &out_skcms->output_curves[i]);
}
return true;
}
bool ToSkcmsIccProfile(const IccProfile& rust_profile, skcms_ICCProfile* out_skcms) {
memset(out_skcms, 0, sizeof(skcms_ICCProfile));
// Copy color space information
out_skcms->data_color_space = static_cast<uint32_t>(rust_profile.data_color_space);
out_skcms->pcs = static_cast<uint32_t>(rust_profile.connection_space);
// Copy toXYZD50 matrix if present
out_skcms->has_toXYZD50 = rust_profile.has_to_xyzd50;
if (rust_profile.has_to_xyzd50) {
ToSkcmsMatrix3x3(rust_profile.to_xyzd50, &out_skcms->toXYZD50);
}
// Copy transfer curves if present
out_skcms->has_trc = rust_profile.has_trc;
if (rust_profile.has_trc) {
// Convert each channel's transfer function
for (int i = 0; i < 3; i++) {
// Set up the skcms_Curve as a parametric function (not a LUT)
out_skcms->trc[i].table_entries = 0; // 0 = parametric, not table
ToSkcmsTransferFunction(rust_profile.trc[i], &out_skcms->trc[i].parametric);
}
}
// Copy CICP data if present
out_skcms->has_CICP = rust_profile.has_cicp;
if (rust_profile.has_cicp) {
out_skcms->CICP.color_primaries = rust_profile.cicp.color_primaries;
out_skcms->CICP.transfer_characteristics = rust_profile.cicp.transfer_characteristics;
out_skcms->CICP.matrix_coefficients = rust_profile.cicp.matrix_coefficients;
out_skcms->CICP.video_full_range_flag = rust_profile.cicp.video_full_range_flag;
}
// Populate A2B and B2A transforms if present
out_skcms->has_A2B = rust_profile.has_a2b;
if (rust_profile.has_a2b) {
if (!ToSkcmsA2B(rust_profile.a2b, &out_skcms->A2B)) {
return false;
}
}
out_skcms->has_B2A = rust_profile.has_b2a;
if (rust_profile.has_b2a) {
if (!ToSkcmsB2A(rust_profile.b2a, &out_skcms->B2A)) {
return false;
}
}
// A profile needs either a matrix, transfer curves, or A2B/B2A to be useful
if (!out_skcms->has_toXYZD50 && !out_skcms->has_trc &&
!out_skcms->has_A2B && !out_skcms->has_B2A) {
return false;
}
return true;
}
} // namespace rust_icc