///|
pub(all) struct Attribute {
name : String
kind : String
value : Bytes
} derive(Debug, Eq)
///|
pub extend Attribute with @debug.Debug::{to_repr}
///|
pub extend Attribute with Eq::{equal, not_equal}
///|
fn reserved(name : String) -> Bool {
match name {
"channels"
| "compression"
| "dataWindow"
| "displayWindow"
| "lineOrder"
| "pixelAspectRatio"
| "screenWindowCenter"
| "screenWindowWidth"
| "tiles"
| "type"
| "name"
| "version"
| "chunkCount"
| "maxSamplesPerPixel" => true
_ => false
}
}
///|
fn Attribute::validate(
self : Attribute,
maximum : Int32,
) -> Unit raise ExrError {
valid_name(self.name)
valid_name(self.kind)
if self.value.length() > maximum {
raise Limit("attribute payload")
}
}
///|
pub fn Attribute::text(
name : String,
text : String,
) -> Attribute raise ExrError {
valid_name(name)
{ name, kind: "string", value: @utf8.encode(text), }
}
///|
pub fn Attribute::as_text(self : Attribute) -> String raise ExrError {
if self.kind != "string" {
raise Invalid("attribute is not string", 0)
}
@utf8.decode(self.value) catch {
_ => raise Invalid("invalid UTF-8 text attribute", 0)
}
}
///|
pub fn Attribute::integer(
name : String,
value : Int32,
) -> Attribute raise ExrError {
valid_name(name)
let b = sink()
b.i32(value)
{ name, kind: "int", value: b.finish(), }
}
///|
pub fn Attribute::as_integer(self : Attribute) -> Int32 raise ExrError {
if self.kind != "int" || self.value.length() != 4 {
raise Invalid("attribute is not int", 0)
}
reader(self.value).i32()
}
///|
pub fn Attribute::real(
name : String,
value : Double,
) -> Attribute raise ExrError {
valid_name(name)
if !finite(value) || Float::from_double(value).is_inf() {
raise Invalid("finite float attribute required", 0)
}
let b = sink()
b.f32(value)
{ name, kind: "float", value: b.finish(), }
}
///|
pub fn Attribute::as_real(self : Attribute) -> Double raise ExrError {
if self.kind != "float" || self.value.length() != 4 {
raise Invalid("attribute is not float", 0)
}
reader(self.value).f32()
}
///|
/// Eight xy coordinates: red, green, blue and white, in that order.
pub fn Attribute::chromaticities(
values : Array[Double],
) -> Attribute raise ExrError {
if values.length() != 8 {
raise Invalid("chromaticities need eight xy values", 0)
}
let b = sink()
for v in values {
if !finite(v) || Float::from_double(v).is_inf() {
raise Invalid("finite chromaticities required", 0)
}
b.f32(v)
}
{ name: "chromaticities", kind: "chromaticities", value: b.finish(), }
}
///|
pub fn Attribute::as_chromaticities(
self : Attribute,
) -> Array[Double] raise ExrError {
if self.kind != "chromaticities" || self.value.length() != 32 {
raise Invalid("chromaticities type/size", 0)
}
let r = reader(self.value)
Array::makei(8, _ => r.f32())
}
///|
fn write_attribute(out : Sink, attr : Attribute) -> Unit {
out.cstring(attr.name)
out.cstring(attr.kind)
out.i32(attr.value.length())
out.raw(attr.value)
}
///|
test "typed metadata helpers retain Unicode text and unknown bytes" {
let a = Attribute::text("comments", "光谱\u0000内嵌")
assert_eq(a.as_text(), "光谱\u0000内嵌")
assert_eq(Attribute::integer("frame", -123).as_integer(), -123)
assert_eq(Attribute::real("exposure", 2.5).as_real(), 2.5)
let coords = [0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]
let c = Attribute::chromaticities(coords).as_chromaticities()
assert_true((c[0] - coords[0]).abs() < 0.000001)
assert_true(
try {
ignore(a.as_integer())
false
} catch {
_ => true
},
)
assert_true(reserved("compression"))
assert_false(reserved("custom.data"))
}