2020-06-08 03:29:51 +03:00
|
|
|
|
package icc
|
|
|
|
|
|
|
|
|
|
// http://www.color.org/ICC1-V41.pdf
|
|
|
|
|
// https://www.color.org/icc32.pdf
|
|
|
|
|
|
|
|
|
|
import (
|
2021-08-17 13:06:32 +03:00
|
|
|
|
"github.com/wader/fq/format"
|
2022-08-12 16:27:51 +03:00
|
|
|
|
"github.com/wader/fq/internal/mathex"
|
2021-08-17 13:06:32 +03:00
|
|
|
|
"github.com/wader/fq/pkg/decode"
|
2022-07-16 19:39:57 +03:00
|
|
|
|
"github.com/wader/fq/pkg/interp"
|
2020-06-08 03:29:51 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func init() {
|
2022-07-16 19:39:57 +03:00
|
|
|
|
interp.RegisterFormat(decode.Format{
|
2020-06-08 03:29:51 +03:00
|
|
|
|
Name: format.ICC_PROFILE,
|
|
|
|
|
Description: "International Color Consortium profile",
|
|
|
|
|
DecodeFn: iccProfileDecode,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-18 13:22:07 +03:00
|
|
|
|
func xyzType(_ int64, d *decode.D) {
|
2022-04-05 14:57:55 +03:00
|
|
|
|
d.FieldFP32("x")
|
|
|
|
|
d.FieldFP32("y")
|
|
|
|
|
d.FieldFP32("z")
|
2020-06-08 03:29:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-18 13:22:07 +03:00
|
|
|
|
func textType(_ int64, d *decode.D) {
|
2021-11-17 18:13:10 +03:00
|
|
|
|
d.FieldUTF8NullFixedLen("text", int(d.BitsLeft()/8))
|
2020-06-08 03:29:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-18 13:22:07 +03:00
|
|
|
|
func paraType(_ int64, d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
|
d.FieldU32("reserved0")
|
|
|
|
|
d.FieldU16("function_type")
|
|
|
|
|
d.FieldU16("reserved1")
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.FieldRawLen("parameters", d.BitsLeft())
|
2020-06-08 03:29:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-18 13:22:07 +03:00
|
|
|
|
func descType(_ int64, d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
|
descLen := d.FieldU32("description_length")
|
2021-11-17 18:13:10 +03:00
|
|
|
|
d.FieldUTF8NullFixedLen("description", int(descLen))
|
2020-06-08 03:29:51 +03:00
|
|
|
|
d.FieldU32("language_code")
|
|
|
|
|
localDescLen := d.FieldU32("localizable_description_length")
|
2021-11-17 18:13:10 +03:00
|
|
|
|
d.FieldUTF8NullFixedLen("localizable_description", int(localDescLen))
|
2020-06-08 03:29:51 +03:00
|
|
|
|
d.FieldU16("script_code")
|
|
|
|
|
d.FieldU8("macintosh_description_length")
|
2021-11-17 18:13:10 +03:00
|
|
|
|
d.FieldUTF8NullFixedLen("macintosh_description", 67)
|
2020-06-08 03:29:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-18 13:22:07 +03:00
|
|
|
|
func multiLocalizedUnicodeType(tagStart int64, d *decode.D) {
|
|
|
|
|
numberOfNames := d.FieldU32("number_of_names")
|
2021-11-29 13:55:25 +03:00
|
|
|
|
recordSize := d.FieldU32("record_size")
|
2021-11-18 13:22:07 +03:00
|
|
|
|
d.FieldArray("names", func(d *decode.D) {
|
|
|
|
|
for i := uint64(0); i < numberOfNames; i++ {
|
|
|
|
|
d.FieldStruct("name", func(d *decode.D) {
|
|
|
|
|
d.FieldUTF8("language_code", 2)
|
|
|
|
|
d.FieldUTF8("country_code", 2)
|
|
|
|
|
nameLength := d.FieldU32("name_length")
|
|
|
|
|
nameOffset := d.FieldU32("name_offset")
|
2021-11-29 13:55:25 +03:00
|
|
|
|
d.RangeFn(tagStart+int64(nameOffset)*8, int64(nameLength)*8, func(d *decode.D) {
|
2021-11-18 13:22:07 +03:00
|
|
|
|
d.FieldUTF16BE("value", int(nameLength))
|
|
|
|
|
})
|
|
|
|
|
})
|
2021-11-29 13:55:25 +03:00
|
|
|
|
recordPadding := int64(recordSize) - 2 - 2 - 4 - 4
|
|
|
|
|
if recordPadding > 0 {
|
|
|
|
|
d.FieldRawLen("padding", recordPadding)
|
|
|
|
|
}
|
2021-11-18 13:22:07 +03:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var typeToDecode = map[string]func(tagStart int64, d *decode.D){
|
2020-06-08 03:29:51 +03:00
|
|
|
|
"XYZ ": xyzType,
|
|
|
|
|
"text": textType,
|
|
|
|
|
"para": paraType,
|
|
|
|
|
"desc": descType,
|
2021-11-18 13:22:07 +03:00
|
|
|
|
"mluc": multiLocalizedUnicodeType,
|
2020-06-08 03:29:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
|
func decodeBCDU8(d *decode.D) uint64 {
|
2020-06-08 03:29:51 +03:00
|
|
|
|
n := d.U8()
|
|
|
|
|
return (n>>4)*10 + n&0xf
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 19:33:50 +03:00
|
|
|
|
func iccProfileDecode(d *decode.D, _ any) any {
|
2020-06-08 03:29:51 +03:00
|
|
|
|
/*
|
|
|
|
|
0..3 Profile size uInt32Number
|
|
|
|
|
4..7 CMM Type signature see below
|
|
|
|
|
8..11 Profile version number see below
|
|
|
|
|
12..15 Profile/Device Class signature see below
|
|
|
|
|
16..19 Color space of data (possibly a derived space) [i.e. “the canonical input space”] see below
|
|
|
|
|
20..23 Profile Connection Space (PCS) [i.e. “the canonical output space”] see below
|
|
|
|
|
24..35 Date and time this profile was first created dateTimeNumber
|
|
|
|
|
36..39 ‘acsp’ (61637370h) profile file signature
|
|
|
|
|
40..43 Primary Platform signature see below
|
|
|
|
|
44..47 Flags to indicate various options for the CMM such as distributed processing and caching options see below
|
|
|
|
|
48..51 Device manufacturer of the device for which this profile is created see below
|
|
|
|
|
52..55 Device model of the device for which this profile is created see below
|
|
|
|
|
56..63 Device attributes unique to the particular device setup such as media type see below
|
|
|
|
|
64..67 Rendering Intent see below
|
|
|
|
|
68..79 The XYZ values of the illuminant of the Profile Connection Space. This must correspond to D50. It is explained in more detail in A.1. XYZNumber
|
|
|
|
|
80..83 Profile Creator signature see below
|
|
|
|
|
84..99 Profile ID see below
|
|
|
|
|
100..127 28 bytes reserved for future expansion - must be set to zeros
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// TODO: PokeU32()?
|
|
|
|
|
size := d.U32()
|
|
|
|
|
d.SeekRel(-4 * 8)
|
|
|
|
|
|
2022-01-13 20:34:59 +03:00
|
|
|
|
d.FramedFn(int64(size)*8, func(d *decode.D) {
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.FieldStruct("header", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
|
d.FieldU32("size")
|
2021-11-17 18:13:10 +03:00
|
|
|
|
d.FieldUTF8NullFixedLen("cmm_type_signature", 4)
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.FieldUFn("version_major", decodeBCDU8)
|
|
|
|
|
d.FieldUFn("version_minor", decodeBCDU8)
|
2020-06-08 03:29:51 +03:00
|
|
|
|
d.FieldU16("version_reserved")
|
2021-11-17 18:13:10 +03:00
|
|
|
|
d.FieldUTF8NullFixedLen("device_class_signature", 4)
|
|
|
|
|
d.FieldUTF8NullFixedLen("color_space", 4)
|
|
|
|
|
d.FieldUTF8NullFixedLen("connection_space", 4)
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.FieldStruct("timestamp", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
|
d.FieldU16("year")
|
|
|
|
|
d.FieldU16("month")
|
|
|
|
|
d.FieldU16("day")
|
|
|
|
|
d.FieldU16("hours")
|
|
|
|
|
d.FieldU16("minutes")
|
|
|
|
|
d.FieldU16("seconds")
|
|
|
|
|
|
|
|
|
|
})
|
2021-11-17 18:13:10 +03:00
|
|
|
|
d.FieldUTF8NullFixedLen("file_signature", 4)
|
|
|
|
|
d.FieldUTF8NullFixedLen("primary_platform", 4)
|
2020-06-08 03:29:51 +03:00
|
|
|
|
d.FieldU32("flags")
|
2021-11-17 18:13:10 +03:00
|
|
|
|
d.FieldUTF8NullFixedLen("device_manufacturer", 4)
|
|
|
|
|
d.FieldUTF8NullFixedLen("device_model", 4)
|
|
|
|
|
d.FieldUTF8NullFixedLen("device_attribute", 8)
|
|
|
|
|
d.FieldUTF8NullFixedLen("render_intent", 4)
|
|
|
|
|
d.FieldUTF8NullFixedLen("xyz_illuminant", 12)
|
|
|
|
|
d.FieldUTF8NullFixedLen("profile_creator_signature", 4)
|
|
|
|
|
d.FieldUTF8NullFixedLen("profile_id", 16)
|
2021-12-02 00:48:25 +03:00
|
|
|
|
d.FieldRawLen("reserved", 28*8, d.BitBufIsZero())
|
2020-06-08 03:29:51 +03:00
|
|
|
|
})
|
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.FieldStruct("tag_table", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
|
tagCount := d.FieldU32("count")
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.FieldArray("table", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
|
for i := uint64(0); i < tagCount; i++ {
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.FieldStruct("element", func(d *decode.D) {
|
2021-11-17 18:13:10 +03:00
|
|
|
|
d.FieldUTF8NullFixedLen("signature", 4)
|
2020-06-08 03:29:51 +03:00
|
|
|
|
offset := d.FieldU32("offset")
|
|
|
|
|
size := d.FieldU32("size")
|
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.RangeFn(int64(offset)*8, int64(size)*8, func(d *decode.D) {
|
2021-11-18 13:22:07 +03:00
|
|
|
|
tagStart := d.Pos()
|
2021-11-17 18:13:10 +03:00
|
|
|
|
typ := d.FieldUTF8NullFixedLen("type", 4)
|
2020-06-08 03:29:51 +03:00
|
|
|
|
d.FieldU32("reserved")
|
|
|
|
|
|
2021-11-18 13:22:07 +03:00
|
|
|
|
if fn, ok := typeToDecode[typ]; ok {
|
|
|
|
|
fn(tagStart, d)
|
2020-06-08 03:29:51 +03:00
|
|
|
|
} else {
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.FieldRawLen("data", int64(size-4-4)*8)
|
2020-06-08 03:29:51 +03:00
|
|
|
|
}
|
|
|
|
|
})
|
2021-08-19 17:50:38 +03:00
|
|
|
|
|
|
|
|
|
// "All tag data is required to start on a 4-byte boundary (relative to the start of the profile data stream)"
|
|
|
|
|
// we can't add this at the start of the element as we don't know how big the previous element in the stream
|
2021-08-20 12:25:31 +03:00
|
|
|
|
// was. instead add alignment after if offset+size does not align and to be sure clamp it if outside buffer.
|
2022-05-09 14:14:13 +03:00
|
|
|
|
alignStart := int64(offset) + int64(size)
|
|
|
|
|
alignBytes := (4 - (int64(offset)+int64(size))%4) % 4
|
2022-08-12 16:27:51 +03:00
|
|
|
|
alignBytes = mathex.Min(d.Len()/8-alignStart, alignBytes)
|
2022-05-09 14:14:13 +03:00
|
|
|
|
if alignBytes != 0 {
|
|
|
|
|
d.RangeFn(alignStart*8, alignBytes*8, func(d *decode.D) {
|
2021-11-05 17:04:26 +03:00
|
|
|
|
d.FieldRawLen("alignment", d.BitsLeft())
|
|
|
|
|
})
|
2021-08-19 17:50:38 +03:00
|
|
|
|
}
|
2020-06-08 03:29:51 +03:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|