use std::ffi::{CStr, c_char};
use std::sync::LazyLock;
use crate::ffi::colorspace::{
ColorspaceHandle, FZ_COLORSPACE_CMYK, FZ_COLORSPACE_GRAY, FZ_COLORSPACE_RGB,
};
use crate::ffi::{Handle, HandleStore};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RenderingIntent {
#[default]
Perceptual = 0,
RelativeColorimetric = 1,
Saturation = 2,
AbsoluteColorimetric = 3,
}
impl RenderingIntent {
pub fn from_i32(value: i32) -> Self {
match value & 0x7F {
0 => RenderingIntent::Perceptual,
1 => RenderingIntent::RelativeColorimetric,
2 => RenderingIntent::Saturation,
3 => RenderingIntent::AbsoluteColorimetric,
_ => RenderingIntent::Perceptual,
}
}
pub fn name(&self) -> &'static str {
match self {
RenderingIntent::Perceptual => "Perceptual",
RenderingIntent::RelativeColorimetric => "RelativeColorimetric",
RenderingIntent::Saturation => "Saturation",
RenderingIntent::AbsoluteColorimetric => "AbsoluteColorimetric",
}
}
pub fn from_name(name: &str) -> Self {
match name.to_lowercase().as_str() {
"perceptual" => RenderingIntent::Perceptual,
"relativecolorimetric" | "relative" => RenderingIntent::RelativeColorimetric,
"saturation" => RenderingIntent::Saturation,
"absolutecolorimetric" | "absolute" => RenderingIntent::AbsoluteColorimetric,
_ => RenderingIntent::Perceptual,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct ColorParams {
pub ri: u8,
pub bp: u8,
pub op: u8,
pub opm: u8,
}
impl ColorParams {
pub fn new(ri: RenderingIntent, bp: bool, op: bool, opm: bool) -> Self {
Self {
ri: ri as u8,
bp: if bp { 1 } else { 0 },
op: if op { 1 } else { 0 },
opm: if opm { 1 } else { 0 },
}
}
pub fn rendering_intent(&self) -> RenderingIntent {
RenderingIntent::from_i32(self.ri as i32)
}
pub fn black_point_compensation(&self) -> bool {
self.bp != 0
}
pub fn overprint(&self) -> bool {
self.op != 0
}
pub fn overprint_mode(&self) -> bool {
self.opm != 0
}
}
pub static DEFAULT_COLOR_PARAMS: ColorParams = ColorParams {
ri: 0, bp: 1, op: 0, opm: 0, };
#[derive(Debug, Clone)]
pub struct DefaultColorspaces {
pub refs: i32,
pub gray: ColorspaceHandle,
pub rgb: ColorspaceHandle,
pub cmyk: ColorspaceHandle,
pub oi: ColorspaceHandle,
}
impl Default for DefaultColorspaces {
fn default() -> Self {
Self {
refs: 1,
gray: FZ_COLORSPACE_GRAY,
rgb: FZ_COLORSPACE_RGB,
cmyk: FZ_COLORSPACE_CMYK,
oi: 0,
}
}
}
pub static DEFAULT_COLORSPACES: LazyLock<HandleStore<DefaultColorspaces>> =
LazyLock::new(HandleStore::new);
#[derive(Debug, Clone)]
pub struct CalGray {
pub white_point: [f32; 3],
pub black_point: [f32; 3],
pub gamma: f32,
}
impl Default for CalGray {
fn default() -> Self {
Self {
white_point: [0.9505, 1.0, 1.089], black_point: [0.0, 0.0, 0.0],
gamma: 2.2,
}
}
}
#[derive(Debug, Clone)]
pub struct CalRgb {
pub white_point: [f32; 3],
pub black_point: [f32; 3],
pub gamma: [f32; 3],
pub matrix: [f32; 9],
}
impl Default for CalRgb {
fn default() -> Self {
Self {
white_point: [0.9505, 1.0, 1.089], black_point: [0.0, 0.0, 0.0],
gamma: [2.2, 2.2, 2.2],
matrix: [
0.4124564, 0.3575761, 0.1804375, 0.2126729, 0.7151522, 0.0721750, 0.0193339,
0.1191920, 0.9503041,
],
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_lookup_rendering_intent(name: *const c_char) -> i32 {
if name.is_null() {
return RenderingIntent::Perceptual as i32;
}
let name_str = unsafe { CStr::from_ptr(name).to_str().unwrap_or("") };
RenderingIntent::from_name(name_str) as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_rendering_intent_name(ri: i32) -> *const c_char {
match RenderingIntent::from_i32(ri) {
RenderingIntent::Perceptual => c"Perceptual".as_ptr(),
RenderingIntent::RelativeColorimetric => c"RelativeColorimetric".as_ptr(),
RenderingIntent::Saturation => c"Saturation".as_ptr(),
RenderingIntent::AbsoluteColorimetric => c"AbsoluteColorimetric".as_ptr(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_default_color_params() -> ColorParams {
DEFAULT_COLOR_PARAMS
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_color_params(ri: i32, bp: i32, op: i32, opm: i32) -> ColorParams {
ColorParams {
ri: ri as u8,
bp: bp as u8,
op: op as u8,
opm: opm as u8,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_color_params_ri(params: ColorParams) -> i32 {
params.ri as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_color_params_bp(params: ColorParams) -> i32 {
params.bp as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_color_params_op(params: ColorParams) -> i32 {
params.op as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_color_params_opm(params: ColorParams) -> i32 {
params.opm as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_default_colorspaces(_ctx: Handle) -> Handle {
DEFAULT_COLORSPACES.insert(DefaultColorspaces::default())
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_keep_default_colorspaces(_ctx: Handle, default_cs: Handle) -> Handle {
if let Some(cs) = DEFAULT_COLORSPACES.get(default_cs) {
let mut guard = cs.lock().unwrap();
guard.refs += 1;
}
default_cs
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_drop_default_colorspaces(_ctx: Handle, default_cs: Handle) {
if let Some(cs) = DEFAULT_COLORSPACES.get(default_cs) {
let mut guard = cs.lock().unwrap();
guard.refs -= 1;
if guard.refs <= 0 {
drop(guard);
DEFAULT_COLORSPACES.remove(default_cs);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_clone_default_colorspaces(_ctx: Handle, base: Handle) -> Handle {
if let Some(base_cs) = DEFAULT_COLORSPACES.get(base) {
let guard = base_cs.lock().unwrap();
let new_cs = DefaultColorspaces {
refs: 1,
gray: guard.gray,
rgb: guard.rgb,
cmyk: guard.cmyk,
oi: guard.oi,
};
DEFAULT_COLORSPACES.insert(new_cs)
} else {
fz_new_default_colorspaces(_ctx)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_default_gray(_ctx: Handle, default_cs: Handle) -> ColorspaceHandle {
if default_cs == 0 {
return FZ_COLORSPACE_GRAY;
}
if let Some(cs) = DEFAULT_COLORSPACES.get(default_cs) {
cs.lock().unwrap().gray
} else {
FZ_COLORSPACE_GRAY
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_default_rgb(_ctx: Handle, default_cs: Handle) -> ColorspaceHandle {
if default_cs == 0 {
return FZ_COLORSPACE_RGB;
}
if let Some(cs) = DEFAULT_COLORSPACES.get(default_cs) {
cs.lock().unwrap().rgb
} else {
FZ_COLORSPACE_RGB
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_default_cmyk(_ctx: Handle, default_cs: Handle) -> ColorspaceHandle {
if default_cs == 0 {
return FZ_COLORSPACE_CMYK;
}
if let Some(cs) = DEFAULT_COLORSPACES.get(default_cs) {
cs.lock().unwrap().cmyk
} else {
FZ_COLORSPACE_CMYK
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_default_output_intent(_ctx: Handle, default_cs: Handle) -> ColorspaceHandle {
if default_cs == 0 {
return 0;
}
if let Some(cs) = DEFAULT_COLORSPACES.get(default_cs) {
cs.lock().unwrap().oi
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_default_gray(_ctx: Handle, default_cs: Handle, cs: ColorspaceHandle) {
if let Some(dcs) = DEFAULT_COLORSPACES.get(default_cs) {
dcs.lock().unwrap().gray = cs;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_default_rgb(_ctx: Handle, default_cs: Handle, cs: ColorspaceHandle) {
if let Some(dcs) = DEFAULT_COLORSPACES.get(default_cs) {
dcs.lock().unwrap().rgb = cs;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_default_cmyk(_ctx: Handle, default_cs: Handle, cs: ColorspaceHandle) {
if let Some(dcs) = DEFAULT_COLORSPACES.get(default_cs) {
dcs.lock().unwrap().cmyk = cs;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_set_default_output_intent(
_ctx: Handle,
default_cs: Handle,
cs: ColorspaceHandle,
) {
if let Some(dcs) = DEFAULT_COLORSPACES.get(default_cs) {
dcs.lock().unwrap().oi = cs;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_cal_gray_colorspace(
_ctx: Handle,
wp: *const f32,
bp: *const f32,
gamma: f32,
) -> ColorspaceHandle {
let mut cal_gray = CalGray::default();
cal_gray.gamma = gamma;
if !wp.is_null() {
let wp_slice = unsafe { std::slice::from_raw_parts(wp, 3) };
cal_gray.white_point.copy_from_slice(wp_slice);
}
if !bp.is_null() {
let bp_slice = unsafe { std::slice::from_raw_parts(bp, 3) };
cal_gray.black_point.copy_from_slice(bp_slice);
}
FZ_COLORSPACE_GRAY
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_new_cal_rgb_colorspace(
_ctx: Handle,
wp: *const f32,
bp: *const f32,
gamma: *const f32,
matrix: *const f32,
) -> ColorspaceHandle {
let mut cal_rgb = CalRgb::default();
if !wp.is_null() {
let wp_slice = unsafe { std::slice::from_raw_parts(wp, 3) };
cal_rgb.white_point.copy_from_slice(wp_slice);
}
if !bp.is_null() {
let bp_slice = unsafe { std::slice::from_raw_parts(bp, 3) };
cal_rgb.black_point.copy_from_slice(bp_slice);
}
if !gamma.is_null() {
let gamma_slice = unsafe { std::slice::from_raw_parts(gamma, 3) };
cal_rgb.gamma.copy_from_slice(gamma_slice);
}
if !matrix.is_null() {
let matrix_slice = unsafe { std::slice::from_raw_parts(matrix, 9) };
cal_rgb.matrix.copy_from_slice(matrix_slice);
}
FZ_COLORSPACE_RGB
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_is_valid_blend_colorspace(_ctx: Handle, cs: ColorspaceHandle) -> i32 {
match cs {
FZ_COLORSPACE_GRAY | FZ_COLORSPACE_RGB | FZ_COLORSPACE_CMYK => 1,
_ => 0,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_colorspace_digest(_ctx: Handle, _cs: ColorspaceHandle, digest: *mut u8) {
if digest.is_null() {
return;
}
unsafe {
std::ptr::write_bytes(digest, 0, 16);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_convert_color_with_params(
_ctx: Handle,
src_cs: ColorspaceHandle,
src: *const f32,
dst_cs: ColorspaceHandle,
dst: *mut f32,
proof_cs: ColorspaceHandle,
_params: ColorParams,
) {
crate::ffi::colorspace::fz_convert_color(_ctx, src_cs, src, dst_cs, dst, proof_cs);
}
pub const FZ_MAX_COLORS: i32 = 32;
#[unsafe(no_mangle)]
pub extern "C" fn fz_max_colors() -> i32 {
FZ_MAX_COLORS
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rendering_intent_from_i32() {
assert_eq!(RenderingIntent::from_i32(0), RenderingIntent::Perceptual);
assert_eq!(
RenderingIntent::from_i32(1),
RenderingIntent::RelativeColorimetric
);
assert_eq!(RenderingIntent::from_i32(2), RenderingIntent::Saturation);
assert_eq!(
RenderingIntent::from_i32(3),
RenderingIntent::AbsoluteColorimetric
);
assert_eq!(RenderingIntent::from_i32(99), RenderingIntent::Perceptual);
}
#[test]
fn test_rendering_intent_name() {
assert_eq!(RenderingIntent::Perceptual.name(), "Perceptual");
assert_eq!(
RenderingIntent::RelativeColorimetric.name(),
"RelativeColorimetric"
);
}
#[test]
fn test_rendering_intent_from_name() {
assert_eq!(
RenderingIntent::from_name("Perceptual"),
RenderingIntent::Perceptual
);
assert_eq!(
RenderingIntent::from_name("perceptual"),
RenderingIntent::Perceptual
);
assert_eq!(
RenderingIntent::from_name("RelativeColorimetric"),
RenderingIntent::RelativeColorimetric
);
assert_eq!(
RenderingIntent::from_name("Relative"),
RenderingIntent::RelativeColorimetric
);
assert_eq!(
RenderingIntent::from_name("unknown"),
RenderingIntent::Perceptual
);
}
#[test]
fn test_color_params_new() {
let params = ColorParams::new(RenderingIntent::Saturation, true, false, true);
assert_eq!(params.ri, 2);
assert_eq!(params.bp, 1);
assert_eq!(params.op, 0);
assert_eq!(params.opm, 1);
}
#[test]
fn test_color_params_accessors() {
let params = ColorParams::new(RenderingIntent::RelativeColorimetric, true, true, false);
assert_eq!(
params.rendering_intent(),
RenderingIntent::RelativeColorimetric
);
assert!(params.black_point_compensation());
assert!(params.overprint());
assert!(!params.overprint_mode());
}
#[test]
fn test_default_color_params() {
let params = fz_default_color_params();
assert_eq!(params.ri, 0);
assert_eq!(params.bp, 1);
}
#[test]
fn test_lookup_rendering_intent() {
let name = c"Perceptual";
assert_eq!(fz_lookup_rendering_intent(name.as_ptr()), 0);
let name = c"Saturation";
assert_eq!(fz_lookup_rendering_intent(name.as_ptr()), 2);
assert_eq!(fz_lookup_rendering_intent(std::ptr::null()), 0);
}
#[test]
fn test_rendering_intent_name_ffi() {
let name = fz_rendering_intent_name(0);
assert!(!name.is_null());
let name = fz_rendering_intent_name(2);
assert!(!name.is_null());
}
#[test]
fn test_new_color_params() {
let params = fz_new_color_params(1, 1, 0, 1);
assert_eq!(fz_color_params_ri(params), 1);
assert_eq!(fz_color_params_bp(params), 1);
assert_eq!(fz_color_params_op(params), 0);
assert_eq!(fz_color_params_opm(params), 1);
}
#[test]
fn test_default_colorspaces() {
let ctx = 1;
let dcs = fz_new_default_colorspaces(ctx);
assert!(dcs > 0);
assert_eq!(fz_default_gray(ctx, dcs), FZ_COLORSPACE_GRAY);
assert_eq!(fz_default_rgb(ctx, dcs), FZ_COLORSPACE_RGB);
assert_eq!(fz_default_cmyk(ctx, dcs), FZ_COLORSPACE_CMYK);
assert_eq!(fz_default_output_intent(ctx, dcs), 0);
fz_drop_default_colorspaces(ctx, dcs);
}
#[test]
fn test_default_colorspaces_null() {
let ctx = 1;
assert_eq!(fz_default_gray(ctx, 0), FZ_COLORSPACE_GRAY);
assert_eq!(fz_default_rgb(ctx, 0), FZ_COLORSPACE_RGB);
assert_eq!(fz_default_cmyk(ctx, 0), FZ_COLORSPACE_CMYK);
}
#[test]
fn test_set_default_colorspaces() {
let ctx = 1;
let dcs = fz_new_default_colorspaces(ctx);
fz_set_default_gray(ctx, dcs, FZ_COLORSPACE_RGB);
assert_eq!(fz_default_gray(ctx, dcs), FZ_COLORSPACE_RGB);
fz_drop_default_colorspaces(ctx, dcs);
}
#[test]
fn test_clone_default_colorspaces() {
let ctx = 1;
let dcs1 = fz_new_default_colorspaces(ctx);
fz_set_default_gray(ctx, dcs1, FZ_COLORSPACE_RGB);
let dcs2 = fz_clone_default_colorspaces(ctx, dcs1);
assert!(dcs2 > 0);
assert_ne!(dcs1, dcs2);
assert_eq!(fz_default_gray(ctx, dcs2), FZ_COLORSPACE_RGB);
fz_drop_default_colorspaces(ctx, dcs1);
fz_drop_default_colorspaces(ctx, dcs2);
}
#[test]
fn test_keep_drop_default_colorspaces() {
let ctx = 1;
let dcs = fz_new_default_colorspaces(ctx);
let dcs2 = fz_keep_default_colorspaces(ctx, dcs);
assert_eq!(dcs, dcs2);
fz_drop_default_colorspaces(ctx, dcs);
fz_drop_default_colorspaces(ctx, dcs);
}
#[test]
fn test_cal_gray_colorspace() {
let ctx = 1;
let wp = [0.9505f32, 1.0, 1.089];
let bp = [0.0f32, 0.0, 0.0];
let cs = fz_new_cal_gray_colorspace(ctx, wp.as_ptr(), bp.as_ptr(), 2.2);
assert_eq!(cs, FZ_COLORSPACE_GRAY);
}
#[test]
fn test_cal_rgb_colorspace() {
let ctx = 1;
let wp = [0.9505f32, 1.0, 1.089];
let bp = [0.0f32, 0.0, 0.0];
let gamma = [2.2f32, 2.2, 2.2];
let matrix = [
0.4124564f32,
0.3575761,
0.1804375,
0.2126729,
0.7151522,
0.0721750,
0.0193339,
0.1191920,
0.9503041,
];
let cs = fz_new_cal_rgb_colorspace(
ctx,
wp.as_ptr(),
bp.as_ptr(),
gamma.as_ptr(),
matrix.as_ptr(),
);
assert_eq!(cs, FZ_COLORSPACE_RGB);
}
#[test]
fn test_is_valid_blend_colorspace() {
let ctx = 1;
assert_eq!(fz_is_valid_blend_colorspace(ctx, FZ_COLORSPACE_GRAY), 1);
assert_eq!(fz_is_valid_blend_colorspace(ctx, FZ_COLORSPACE_RGB), 1);
assert_eq!(fz_is_valid_blend_colorspace(ctx, FZ_COLORSPACE_CMYK), 1);
assert_eq!(fz_is_valid_blend_colorspace(ctx, 99), 0);
}
#[test]
fn test_colorspace_digest() {
let mut digest = [0xFFu8; 16];
fz_colorspace_digest(1, FZ_COLORSPACE_RGB, digest.as_mut_ptr());
assert_eq!(digest, [0u8; 16]);
}
#[test]
fn test_max_colors() {
assert_eq!(fz_max_colors(), 32);
}
#[test]
fn test_convert_color_with_params() {
let src = [0.5f32];
let mut dst = [0.0f32; 3];
let params = fz_default_color_params();
fz_convert_color_with_params(
1,
FZ_COLORSPACE_GRAY,
src.as_ptr(),
FZ_COLORSPACE_RGB,
dst.as_mut_ptr(),
0,
params,
);
assert!((dst[0] - 0.5).abs() < 0.01);
assert!((dst[1] - 0.5).abs() < 0.01);
assert!((dst[2] - 0.5).abs() < 0.01);
}
}