mirror of
https://github.com/wader/fq.git
synced 2024-12-24 13:52:02 +03:00
b08ef00dd1
Replaces []Format with a Group type. A bit more type safe. Breaking change for RegisterFormat, now takes a first argument that is a "single" format group. Lots of naming cleanup. This is also preparation for decode group argument which will enable doing intresting probing, ex a format decoder could know it's decode as part of probe group (html could be probed possibly), or have "arg probe" group for decoder who inspect args to know if they should probe (-d /path/to/schema etc) to enable nice CLI-ergonomics.
183 lines
6.0 KiB
Go
183 lines
6.0 KiB
Go
package icc
|
||
|
||
// http://www.color.org/ICC1-V41.pdf
|
||
// https://www.color.org/icc32.pdf
|
||
|
||
import (
|
||
"github.com/wader/fq/format"
|
||
"github.com/wader/fq/internal/mathex"
|
||
"github.com/wader/fq/pkg/decode"
|
||
"github.com/wader/fq/pkg/interp"
|
||
)
|
||
|
||
func init() {
|
||
interp.RegisterFormat(
|
||
format.IccProfile,
|
||
&decode.Format{
|
||
Description: "International Color Consortium profile",
|
||
DecodeFn: iccProfileDecode,
|
||
})
|
||
}
|
||
|
||
func xyzType(_ int64, d *decode.D) {
|
||
d.FieldFP32("x")
|
||
d.FieldFP32("y")
|
||
d.FieldFP32("z")
|
||
}
|
||
|
||
func textType(_ int64, d *decode.D) {
|
||
d.FieldUTF8NullFixedLen("text", int(d.BitsLeft()/8))
|
||
}
|
||
|
||
func paraType(_ int64, d *decode.D) {
|
||
d.FieldU32("reserved0")
|
||
d.FieldU16("function_type")
|
||
d.FieldU16("reserved1")
|
||
d.FieldRawLen("parameters", d.BitsLeft())
|
||
}
|
||
|
||
func descType(_ int64, d *decode.D) {
|
||
descLen := d.FieldU32("description_length")
|
||
d.FieldUTF8NullFixedLen("description", int(descLen))
|
||
d.FieldU32("language_code")
|
||
localDescLen := d.FieldU32("localizable_description_length")
|
||
d.FieldUTF8NullFixedLen("localizable_description", int(localDescLen))
|
||
d.FieldU16("script_code")
|
||
d.FieldU8("macintosh_description_length")
|
||
d.FieldUTF8NullFixedLen("macintosh_description", 67)
|
||
}
|
||
|
||
func multiLocalizedUnicodeType(tagStart int64, d *decode.D) {
|
||
numberOfNames := d.FieldU32("number_of_names")
|
||
recordSize := d.FieldU32("record_size")
|
||
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")
|
||
d.RangeFn(tagStart+int64(nameOffset)*8, int64(nameLength)*8, func(d *decode.D) {
|
||
d.FieldUTF16BE("value", int(nameLength))
|
||
})
|
||
})
|
||
recordPadding := int64(recordSize) - 2 - 2 - 4 - 4
|
||
if recordPadding > 0 {
|
||
d.FieldRawLen("padding", recordPadding)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
var typeToDecode = map[string]func(tagStart int64, d *decode.D){
|
||
"XYZ ": xyzType,
|
||
"text": textType,
|
||
"para": paraType,
|
||
"desc": descType,
|
||
"mluc": multiLocalizedUnicodeType,
|
||
}
|
||
|
||
func decodeBCDU8(d *decode.D) uint64 {
|
||
n := d.U8()
|
||
return (n>>4)*10 + n&0xf
|
||
}
|
||
|
||
func iccProfileDecode(d *decode.D) any {
|
||
/*
|
||
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)
|
||
|
||
d.FramedFn(int64(size)*8, func(d *decode.D) {
|
||
d.FieldStruct("header", func(d *decode.D) {
|
||
d.FieldU32("size")
|
||
d.FieldUTF8NullFixedLen("cmm_type_signature", 4)
|
||
d.FieldUintFn("version_major", decodeBCDU8)
|
||
d.FieldUintFn("version_minor", decodeBCDU8)
|
||
d.FieldU16("version_reserved")
|
||
d.FieldUTF8NullFixedLen("device_class_signature", 4)
|
||
d.FieldUTF8NullFixedLen("color_space", 4)
|
||
d.FieldUTF8NullFixedLen("connection_space", 4)
|
||
d.FieldStruct("timestamp", func(d *decode.D) {
|
||
d.FieldU16("year")
|
||
d.FieldU16("month")
|
||
d.FieldU16("day")
|
||
d.FieldU16("hours")
|
||
d.FieldU16("minutes")
|
||
d.FieldU16("seconds")
|
||
|
||
})
|
||
d.FieldUTF8NullFixedLen("file_signature", 4)
|
||
d.FieldUTF8NullFixedLen("primary_platform", 4)
|
||
d.FieldU32("flags")
|
||
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)
|
||
d.FieldRawLen("reserved", 28*8, d.BitBufIsZero())
|
||
})
|
||
|
||
d.FieldStruct("tag_table", func(d *decode.D) {
|
||
tagCount := d.FieldU32("count")
|
||
d.FieldArray("table", func(d *decode.D) {
|
||
for i := uint64(0); i < tagCount; i++ {
|
||
d.FieldStruct("element", func(d *decode.D) {
|
||
d.FieldUTF8NullFixedLen("signature", 4)
|
||
offset := d.FieldU32("offset")
|
||
size := d.FieldU32("size")
|
||
|
||
d.RangeFn(int64(offset)*8, int64(size)*8, func(d *decode.D) {
|
||
tagStart := d.Pos()
|
||
typ := d.FieldUTF8NullFixedLen("type", 4)
|
||
d.FieldU32("reserved")
|
||
|
||
if fn, ok := typeToDecode[typ]; ok {
|
||
fn(tagStart, d)
|
||
} else {
|
||
d.FieldRawLen("data", int64(size-4-4)*8)
|
||
}
|
||
})
|
||
|
||
// "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
|
||
// was. instead add alignment after if offset+size does not align and to be sure clamp it if outside buffer.
|
||
alignStart := int64(offset) + int64(size)
|
||
alignBytes := (4 - (int64(offset)+int64(size))%4) % 4
|
||
alignBytes = mathex.Min(d.Len()/8-alignStart, alignBytes)
|
||
if alignBytes != 0 {
|
||
d.RangeFn(alignStart*8, alignBytes*8, func(d *decode.D) {
|
||
d.FieldRawLen("alignment", d.BitsLeft())
|
||
})
|
||
}
|
||
})
|
||
}
|
||
})
|
||
})
|
||
})
|
||
|
||
return nil
|
||
}
|