2022-12-26 08:21:26 -05:00
/*
* Copyright ( c ) 2022 , Nico Weber < thakis @ chromium . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/Endian.h>
# include <LibGfx/ICCProfile.h>
2023-01-01 07:09:38 -05:00
# include <math.h>
2022-12-27 13:37:37 -05:00
# include <time.h>
2022-12-26 08:21:26 -05:00
// V2 spec: https://color.org/specification/ICC.1-2001-04.pdf
// V4 spec: https://color.org/specification/ICC.1-2022-05.pdf
namespace Gfx : : ICC {
namespace {
2022-12-27 13:37:37 -05:00
// ICC V4, 4.2 dateTimeNumber
// "All the dateTimeNumber values in a profile shall be in Coordinated Universal Time [...]."
struct DateTimeNumber {
BigEndian < u16 > year ;
BigEndian < u16 > month ;
BigEndian < u16 > day ;
2023-01-08 06:06:20 -05:00
BigEndian < u16 > hours ;
2022-12-27 13:37:37 -05:00
BigEndian < u16 > minutes ;
BigEndian < u16 > seconds ;
} ;
2023-01-01 07:09:38 -05:00
// ICC V4, 4.6 s15Fixed16Number
using s15Fixed16Number = i32 ;
// ICC V4, 4.14 XYZNumber
struct XYZNumber {
BigEndian < s15Fixed16Number > x ;
BigEndian < s15Fixed16Number > y ;
BigEndian < s15Fixed16Number > z ;
operator XYZ ( ) const
{
return XYZ { x / ( double ) 0x1'0000 , y / ( double ) 0x1'0000 , z / ( double ) 0x1'0000 } ;
}
} ;
2022-12-27 13:37:37 -05:00
ErrorOr < time_t > parse_date_time_number ( DateTimeNumber const & date_time )
{
// ICC V4, 4.2 dateTimeNumber
// "Number of the month (1 to 12)"
if ( date_time . month < 1 | | date_time . month > 12 )
return Error : : from_string_literal ( " ICC::Profile: dateTimeNumber month out of bounds " ) ;
// "Number of the day of the month (1 to 31)"
if ( date_time . day < 1 | | date_time . day > 31 )
return Error : : from_string_literal ( " ICC::Profile: dateTimeNumber day out of bounds " ) ;
// "Number of hours (0 to 23)"
2023-01-08 06:06:20 -05:00
if ( date_time . hours > 23 )
return Error : : from_string_literal ( " ICC::Profile: dateTimeNumber hours out of bounds " ) ;
2022-12-27 13:37:37 -05:00
// "Number of minutes (0 to 59)"
if ( date_time . minutes > 59 )
return Error : : from_string_literal ( " ICC::Profile: dateTimeNumber minutes out of bounds " ) ;
// "Number of seconds (0 to 59)"
// ICC profiles apparently can't be created during leap seconds (seconds would be 60 there, but the spec doesn't allow that).
if ( date_time . seconds > 59 )
return Error : : from_string_literal ( " ICC::Profile: dateTimeNumber seconds out of bounds " ) ;
struct tm tm = { } ;
tm . tm_year = date_time . year - 1900 ;
tm . tm_mon = date_time . month - 1 ;
tm . tm_mday = date_time . day ;
2023-01-08 06:06:20 -05:00
tm . tm_hour = date_time . hours ;
2022-12-27 13:37:37 -05:00
tm . tm_min = date_time . minutes ;
tm . tm_sec = date_time . seconds ;
// timegm() doesn't read tm.tm_isdst, tm.tm_wday, and tm.tm_yday, no need to fill them in.
time_t timestamp = timegm ( & tm ) ;
if ( timestamp = = - 1 )
return Error : : from_string_literal ( " ICC::Profile: dateTimeNumber not representable as timestamp " ) ;
return timestamp ;
}
2022-12-26 08:21:26 -05:00
// ICC V4, 7.2 Profile header
struct ICCHeader {
BigEndian < u32 > profile_size ;
2023-01-18 22:16:10 -05:00
BigEndian < PreferredCMMType > preferred_cmm_type ;
2022-12-26 08:21:26 -05:00
u8 profile_version_major ;
u8 profile_version_minor_bugfix ;
BigEndian < u16 > profile_version_zero ;
2022-12-28 12:58:45 -05:00
BigEndian < DeviceClass > profile_device_class ;
2022-12-30 07:54:58 -05:00
BigEndian < ColorSpace > data_color_space ;
2022-12-30 11:07:38 -05:00
BigEndian < ColorSpace > profile_connection_space ; // "PCS" in the spec.
2022-12-26 08:21:26 -05:00
2022-12-27 13:37:37 -05:00
DateTimeNumber profile_creation_time ;
2022-12-26 08:21:26 -05:00
BigEndian < u32 > profile_file_signature ;
2023-01-07 15:22:15 -05:00
BigEndian < PrimaryPlatform > primary_platform ;
2022-12-26 08:21:26 -05:00
BigEndian < u32 > profile_flags ;
2023-01-18 22:16:10 -05:00
BigEndian < DeviceManufacturer > device_manufacturer ;
BigEndian < DeviceModel > device_model ;
2022-12-26 08:21:26 -05:00
BigEndian < u64 > device_attributes ;
BigEndian < u32 > rendering_intent ;
2023-01-01 07:09:38 -05:00
XYZNumber pcs_illuminant ;
2022-12-26 08:21:26 -05:00
2023-01-18 22:16:10 -05:00
BigEndian < Creator > profile_creator ;
2022-12-26 08:21:26 -05:00
2023-01-05 15:09:31 -05:00
u8 profile_id [ 16 ] ;
2022-12-26 08:21:26 -05:00
u8 reserved [ 28 ] ;
} ;
static_assert ( sizeof ( ICCHeader ) = = 128 ) ;
2023-01-08 07:07:21 -05:00
ErrorOr < u32 > parse_size ( ICCHeader const & header , ReadonlyBytes icc_bytes )
{
// ICC v4, 7.2.2 Profile size field
// "The value in the profile size field shall be the exact size obtained by combining the profile header,
// the tag table, and the tagged element data, including the pad bytes for the last tag."
// Valid files have enough data for profile header and tag table entry count.
if ( header . profile_size < sizeof ( ICCHeader ) + sizeof ( u32 ) )
return Error : : from_string_literal ( " ICC::Profile: Profile size too small " ) ;
if ( header . profile_size > icc_bytes . size ( ) )
return Error : : from_string_literal ( " ICC::Profile: Profile size larger than input data " ) ;
return header . profile_size ;
}
2023-01-06 15:57:45 -05:00
Optional < PreferredCMMType > parse_preferred_cmm_type ( ICCHeader const & header )
{
// ICC v4, 7.2.3 Preferred CMM type field
// "This field may be used to identify the preferred CMM to be used.
// If used, it shall match a CMM type signature registered in the ICC Tag Registry"
// https://www.color.org/signatures2.xalter currently links to
// https://www.color.org/registry/signature/TagRegistry-2021-03.pdf, which contains
// some CMM signatures.
// This requirement is often honored in practice, but not always. For example,
// JPEGs exported in Adobe Lightroom contain profiles that set this to 'Lino',
// which is not present in the "CMM Signatures" table in that PDF.
// "If no preferred CMM is identified, this field shall be set to zero (00000000h)."
2023-01-18 22:16:10 -05:00
if ( header . preferred_cmm_type = = PreferredCMMType { 0 } )
2023-01-06 15:57:45 -05:00
return { } ;
2023-01-18 22:16:10 -05:00
return header . preferred_cmm_type ;
2023-01-06 15:57:45 -05:00
}
2022-12-26 08:21:26 -05:00
ErrorOr < Version > parse_version ( ICCHeader const & header )
{
// ICC v4, 7.2.4 Profile version field
if ( header . profile_version_zero ! = 0 )
return Error : : from_string_literal ( " ICC::Profile: Reserved version bytes not zero " ) ;
return Version ( header . profile_version_major , header . profile_version_minor_bugfix ) ;
}
ErrorOr < DeviceClass > parse_device_class ( ICCHeader const & header )
{
// ICC v4, 7.2.5 Profile/device class field
switch ( header . profile_device_class ) {
2022-12-28 12:58:45 -05:00
case DeviceClass : : InputDevce :
case DeviceClass : : DisplayDevice :
case DeviceClass : : OutputDevice :
case DeviceClass : : DeviceLink :
case DeviceClass : : ColorSpace :
case DeviceClass : : Abstract :
case DeviceClass : : NamedColor :
return header . profile_device_class ;
2022-12-26 08:21:26 -05:00
}
return Error : : from_string_literal ( " ICC::Profile: Invalid device class " ) ;
}
2022-12-30 11:07:38 -05:00
ErrorOr < ColorSpace > parse_color_space ( ColorSpace color_space )
2022-12-30 07:54:58 -05:00
{
2022-12-30 11:07:38 -05:00
// ICC v4, Table 19 — Data colour space signatures
switch ( color_space ) {
2022-12-30 07:54:58 -05:00
case ColorSpace : : nCIEXYZ :
case ColorSpace : : CIELAB :
case ColorSpace : : CIELUV :
case ColorSpace : : YCbCr :
case ColorSpace : : CIEYxy :
case ColorSpace : : RGB :
case ColorSpace : : Gray :
case ColorSpace : : HSV :
case ColorSpace : : HLS :
case ColorSpace : : CMYK :
case ColorSpace : : CMY :
case ColorSpace : : TwoColor :
case ColorSpace : : ThreeColor :
case ColorSpace : : FourColor :
case ColorSpace : : FiveColor :
case ColorSpace : : SixColor :
case ColorSpace : : SevenColor :
case ColorSpace : : EightColor :
case ColorSpace : : NineColor :
case ColorSpace : : TenColor :
case ColorSpace : : ElevenColor :
case ColorSpace : : TwelveColor :
case ColorSpace : : ThirteenColor :
case ColorSpace : : FourteenColor :
case ColorSpace : : FifteenColor :
2022-12-30 11:07:38 -05:00
return color_space ;
2022-12-30 07:54:58 -05:00
}
2022-12-30 11:07:38 -05:00
return Error : : from_string_literal ( " ICC::Profile: Invalid color space " ) ;
}
ErrorOr < ColorSpace > parse_data_color_space ( ICCHeader const & header )
{
// ICC v4, 7.2.6 Data colour space field
return parse_color_space ( header . data_color_space ) ;
}
ErrorOr < ColorSpace > parse_connection_space ( ICCHeader const & header )
{
// ICC v4, 7.2.7 PCS field
// and Annex D
auto space = TRY ( parse_color_space ( header . profile_connection_space ) ) ;
if ( header . profile_device_class ! = DeviceClass : : DeviceLink & & ( space ! = ColorSpace : : PCSXYZ & & space ! = ColorSpace : : PCSLAB ) )
return Error : : from_string_literal ( " ICC::Profile: Invalid profile connection space: Non-PCS space on non-DeviceLink profile " ) ;
return space ;
2022-12-30 07:54:58 -05:00
}
2023-01-04 13:42:37 -05:00
ErrorOr < time_t > parse_creation_date_time ( ICCHeader const & header )
{
// ICC v4, 7.2.8 Date and time field
return parse_date_time_number ( header . profile_creation_time ) ;
}
ErrorOr < void > parse_file_signature ( ICCHeader const & header )
{
// ICC v4, 7.2.9 Profile file signature field
2023-01-06 19:59:03 -05:00
// "The profile file signature field shall contain the value “acsp” (61637370h) as a profile file signature."
2023-01-04 13:42:37 -05:00
if ( header . profile_file_signature ! = 0x61637370 )
return Error : : from_string_literal ( " ICC::Profile: profile file signature not 'acsp' " ) ;
return { } ;
}
2023-01-07 15:22:15 -05:00
ErrorOr < PrimaryPlatform > parse_primary_platform ( ICCHeader const & header )
{
// ICC v4, 7.2.10 Primary platform field
switch ( header . primary_platform ) {
case PrimaryPlatform : : Apple :
case PrimaryPlatform : : Microsoft :
case PrimaryPlatform : : SiliconGraphics :
case PrimaryPlatform : : Sun :
return header . primary_platform ;
}
return Error : : from_string_literal ( " ICC::Profile: Invalid primary platform " ) ;
}
2023-01-06 15:57:45 -05:00
Optional < DeviceManufacturer > parse_device_manufacturer ( ICCHeader const & header )
{
// ICC v4, 7.2.12 Device manufacturer field
// "This field may be used to identify a device manufacturer.
// If used the signature shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org"
// Device manufacturers can be looked up at https://www.color.org/signatureRegistry/index.xalter
// For example: https://www.color.org/signatureRegistry/?entityEntry=APPL-4150504C
// Some icc files use codes not in that registry. For example. D50_XYZ.icc from https://www.color.org/XYZprofiles.xalter
// has its device manufacturer set to 'none', but https://www.color.org/signatureRegistry/?entityEntry=none-6E6F6E65 does not exist.
// "If not used this field shall be set to zero (00000000h)."
2023-01-18 22:16:10 -05:00
if ( header . device_manufacturer = = DeviceManufacturer { 0 } )
2023-01-06 15:57:45 -05:00
return { } ;
2023-01-18 22:16:10 -05:00
return header . device_manufacturer ;
2023-01-06 15:57:45 -05:00
}
Optional < DeviceModel > parse_device_model ( ICCHeader const & header )
{
// ICC v4, 7.2.13 Device model field
// "This field may be used to identify a device model.
// If used the signature shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org"
// Device models can be looked up at https://www.color.org/signatureRegistry/deviceRegistry/index.xalter
// For example: https://www.color.org/signatureRegistry/deviceRegistry/?entityEntry=7FD8-37464438
// Some icc files use codes not in that registry. For example. D50_XYZ.icc from https://www.color.org/XYZprofiles.xalter
// has its device model set to 'none', but https://www.color.org/signatureRegistry/deviceRegistry?entityEntry=none-6E6F6E65 does not exist.
// "If not used this field shall be set to zero (00000000h)."
2023-01-18 22:16:10 -05:00
if ( header . device_model = = DeviceModel { 0 } )
2023-01-06 15:57:45 -05:00
return { } ;
2023-01-18 22:16:10 -05:00
return header . device_model ;
2023-01-06 15:57:45 -05:00
}
2023-01-07 11:04:36 -05:00
ErrorOr < DeviceAttributes > parse_device_attributes ( ICCHeader const & header )
{
// ICC v4, 7.2.14 Device attributes field
// "4 to 31": "Reserved (set to binary zero)"
if ( header . device_attributes & 0xffff'fff0 )
return Error : : from_string_literal ( " ICC::Profile: Device attributes reserved bits not set to 0 " ) ;
return DeviceAttributes { header . device_attributes } ;
}
2022-12-30 08:15:49 -05:00
ErrorOr < RenderingIntent > parse_rendering_intent ( ICCHeader const & header )
{
// ICC v4, 7.2.15 Rendering intent field
switch ( header . rendering_intent ) {
case 0 :
return RenderingIntent : : Perceptual ;
case 1 :
return RenderingIntent : : MediaRelativeColorimetric ;
case 2 :
return RenderingIntent : : Saturation ;
case 3 :
return RenderingIntent : : ICCAbsoluteColorimetric ;
}
return Error : : from_string_literal ( " ICC::Profile: Invalid rendering intent " ) ;
}
2023-01-01 07:09:38 -05:00
ErrorOr < XYZ > parse_pcs_illuminant ( ICCHeader const & header )
{
// ICC v4, 7.2.16 PCS illuminant field
XYZ xyz = ( XYZ ) header . pcs_illuminant ;
/// "The value, when rounded to four decimals, shall be X = 0,9642, Y = 1,0 and Z = 0,8249."
if ( round ( xyz . x * 10'000 ) ! = 9'642 | | round ( xyz . y * 10'000 ) ! = 10'000 | | round ( xyz . z * 10'000 ) ! = 8'249 )
return Error : : from_string_literal ( " ICC::Profile: Invalid pcs illuminant " ) ;
return xyz ;
}
2023-01-04 14:55:16 -05:00
2023-01-06 15:57:45 -05:00
Optional < Creator > parse_profile_creator ( ICCHeader const & header )
{
// ICC v4, 7.2.17 Profile creator field
// "This field may be used to identify the creator of the profile.
// If used the signature should match the signature contained in the device manufacturer section of the ICC signature registry found at www.color.org."
// This is not always true in practice.
// For example, .icc files in /System/ColorSync/Profiles on macOS 12.6 set this to 'appl', which is a CMM signature, not a device signature (that one would be 'APPL').
// "If not used this field shall be set to zero (00000000h)."
2023-01-18 22:16:10 -05:00
if ( header . profile_creator = = Creator { 0 } )
2023-01-06 15:57:45 -05:00
return { } ;
2023-01-18 22:16:10 -05:00
return header . profile_creator ;
2023-01-06 15:57:45 -05:00
}
2023-01-06 12:05:53 -05:00
template < size_t N >
bool all_bytes_are_zero ( const u8 ( & bytes ) [ N ] )
{
for ( u8 byte : bytes ) {
if ( byte ! = 0 )
return false ;
}
return true ;
}
2023-01-06 13:46:18 -05:00
ErrorOr < Optional < Crypto : : Hash : : MD5 : : DigestType > > parse_profile_id ( ICCHeader const & header , ReadonlyBytes icc_bytes )
2023-01-04 14:55:16 -05:00
{
// ICC v4, 7.2.18 Profile ID field
// "A profile ID field value of zero (00h) shall indicate that a profile ID has not been calculated."
2023-01-06 12:05:53 -05:00
if ( all_bytes_are_zero ( header . profile_id ) )
2023-01-06 13:46:18 -05:00
return Optional < Crypto : : Hash : : MD5 : : DigestType > { } ;
2023-01-04 14:55:16 -05:00
2023-01-06 13:40:10 -05:00
Crypto : : Hash : : MD5 : : DigestType id ;
static_assert ( sizeof ( id . data ) = = sizeof ( header . profile_id ) ) ;
memcpy ( id . data , header . profile_id , sizeof ( id . data ) ) ;
2023-01-04 15:34:25 -05:00
2023-01-06 13:46:18 -05:00
auto computed_id = Profile : : compute_id ( icc_bytes ) ;
if ( id ! = computed_id )
return Error : : from_string_literal ( " ICC::Profile: Invalid profile id " ) ;
2023-01-04 15:34:25 -05:00
2023-01-06 13:40:10 -05:00
return id ;
2023-01-04 14:55:16 -05:00
}
2023-01-06 12:06:12 -05:00
ErrorOr < void > parse_reserved ( ICCHeader const & header )
{
// ICC v4, 7.2.19 Reserved field
// "This field of the profile header is reserved for future ICC definition and shall be set to zero."
if ( ! all_bytes_are_zero ( header . reserved ) )
return Error : : from_string_literal ( " ICC::Profile: Reserved header bytes are not zero " ) ;
return { } ;
}
2022-12-26 08:21:26 -05:00
}
2022-12-31 11:45:36 -05:00
StringView device_class_name ( DeviceClass device_class )
2022-12-26 08:21:26 -05:00
{
switch ( device_class ) {
case DeviceClass : : InputDevce :
2022-12-31 11:45:36 -05:00
return " InputDevce " sv ;
2022-12-26 08:21:26 -05:00
case DeviceClass : : DisplayDevice :
2022-12-31 11:45:36 -05:00
return " DisplayDevice " sv ;
2022-12-26 08:21:26 -05:00
case DeviceClass : : OutputDevice :
2022-12-31 11:45:36 -05:00
return " OutputDevice " sv ;
2022-12-26 08:21:26 -05:00
case DeviceClass : : DeviceLink :
2022-12-31 11:45:36 -05:00
return " DeviceLink " sv ;
2022-12-26 08:21:26 -05:00
case DeviceClass : : ColorSpace :
2022-12-31 11:45:36 -05:00
return " ColorSpace " sv ;
2022-12-26 08:21:26 -05:00
case DeviceClass : : Abstract :
2022-12-31 11:45:36 -05:00
return " Abstract " sv ;
2022-12-26 08:21:26 -05:00
case DeviceClass : : NamedColor :
2022-12-31 11:45:36 -05:00
return " NamedColor " sv ;
2022-12-26 08:21:26 -05:00
}
2022-12-31 11:49:57 -05:00
VERIFY_NOT_REACHED ( ) ;
2022-12-26 08:21:26 -05:00
}
2022-12-30 11:07:38 -05:00
StringView data_color_space_name ( ColorSpace color_space )
2022-12-30 07:54:58 -05:00
{
switch ( color_space ) {
case ColorSpace : : nCIEXYZ :
2022-12-31 11:45:36 -05:00
return " nCIEXYZ " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : CIELAB :
2022-12-31 11:45:36 -05:00
return " CIELAB " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : CIELUV :
2022-12-31 11:45:36 -05:00
return " CIELUV " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : YCbCr :
2022-12-31 11:45:36 -05:00
return " YCbCr " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : CIEYxy :
2022-12-31 11:45:36 -05:00
return " CIEYxy " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : RGB :
2022-12-31 11:45:36 -05:00
return " RGB " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : Gray :
2022-12-31 11:45:36 -05:00
return " Gray " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : HSV :
2022-12-31 11:45:36 -05:00
return " HSV " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : HLS :
2022-12-31 11:45:36 -05:00
return " HLS " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : CMYK :
2022-12-31 11:45:36 -05:00
return " CMYK " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : CMY :
2022-12-31 11:45:36 -05:00
return " CMY " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : TwoColor :
2022-12-31 11:45:36 -05:00
return " 2 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : ThreeColor :
2022-12-31 11:45:36 -05:00
return " 3 color (other than XYZ, Lab, Luv, YCbCr, CIEYxy, RGB, HSV, HLS, CMY) " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : FourColor :
2022-12-31 11:45:36 -05:00
return " 4 color (other than CMYK) " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : FiveColor :
2022-12-31 11:45:36 -05:00
return " 5 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : SixColor :
2022-12-31 11:45:36 -05:00
return " 6 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : SevenColor :
2022-12-31 11:45:36 -05:00
return " 7 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : EightColor :
2022-12-31 11:45:36 -05:00
return " 8 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : NineColor :
2022-12-31 11:45:36 -05:00
return " 9 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : TenColor :
2022-12-31 11:45:36 -05:00
return " 10 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : ElevenColor :
2022-12-31 11:45:36 -05:00
return " 11 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : TwelveColor :
2022-12-31 11:45:36 -05:00
return " 12 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : ThirteenColor :
2022-12-31 11:45:36 -05:00
return " 13 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : FourteenColor :
2022-12-31 11:45:36 -05:00
return " 14 color " sv ;
2022-12-30 07:54:58 -05:00
case ColorSpace : : FifteenColor :
2022-12-31 11:45:36 -05:00
return " 15 color " sv ;
2022-12-30 07:54:58 -05:00
}
2022-12-31 11:49:57 -05:00
VERIFY_NOT_REACHED ( ) ;
2022-12-30 07:54:58 -05:00
}
2022-12-30 11:07:38 -05:00
StringView profile_connection_space_name ( ColorSpace color_space )
{
switch ( color_space ) {
case ColorSpace : : PCSXYZ :
return " PCSXYZ " sv ;
case ColorSpace : : PCSLAB :
return " PCSLAB " sv ;
default :
return data_color_space_name ( color_space ) ;
}
}
2023-01-07 15:22:15 -05:00
StringView primary_platform_name ( PrimaryPlatform primary_platform )
{
switch ( primary_platform ) {
case PrimaryPlatform : : Apple :
return " Apple " sv ;
case PrimaryPlatform : : Microsoft :
return " Microsoft " sv ;
case PrimaryPlatform : : SiliconGraphics :
return " Silicon Graphics " sv ;
case PrimaryPlatform : : Sun :
return " Sun " sv ;
}
VERIFY_NOT_REACHED ( ) ;
}
2022-12-31 11:45:36 -05:00
StringView rendering_intent_name ( RenderingIntent rendering_intent )
2022-12-30 08:15:49 -05:00
{
switch ( rendering_intent ) {
case RenderingIntent : : Perceptual :
2022-12-31 11:45:36 -05:00
return " Perceptual " sv ;
2022-12-30 08:15:49 -05:00
case RenderingIntent : : MediaRelativeColorimetric :
2022-12-31 11:45:36 -05:00
return " Media-relative colorimetric " sv ;
2022-12-30 08:15:49 -05:00
case RenderingIntent : : Saturation :
2022-12-31 11:45:36 -05:00
return " Saturation " sv ;
2022-12-30 08:15:49 -05:00
case RenderingIntent : : ICCAbsoluteColorimetric :
2022-12-31 11:45:36 -05:00
return " ICC-absolute colorimetric " sv ;
2022-12-30 08:15:49 -05:00
}
2022-12-31 11:49:57 -05:00
VERIFY_NOT_REACHED ( ) ;
2022-12-30 08:15:49 -05:00
}
2022-12-31 13:50:52 -05:00
Flags : : Flags ( ) = default ;
Flags : : Flags ( u32 bits )
: m_bits ( bits )
{
}
2023-01-06 15:06:30 -05:00
DeviceAttributes : : DeviceAttributes ( ) = default ;
DeviceAttributes : : DeviceAttributes ( u64 bits )
: m_bits ( bits )
{
}
2022-12-26 08:21:26 -05:00
ErrorOr < NonnullRefPtr < Profile > > Profile : : try_load_from_externally_owned_memory ( ReadonlyBytes bytes )
{
auto profile = adopt_ref ( * new Profile ( ) ) ;
if ( bytes . size ( ) < sizeof ( ICCHeader ) )
return Error : : from_string_literal ( " ICC::Profile: Not enough data for header " ) ;
auto header = * bit_cast < ICCHeader const * > ( bytes . data ( ) ) ;
TRY ( parse_file_signature ( header ) ) ;
2023-01-08 07:07:21 -05:00
profile - > m_on_disk_size = TRY ( parse_size ( header , bytes ) ) ;
2023-01-06 15:57:45 -05:00
profile - > m_preferred_cmm_type = parse_preferred_cmm_type ( header ) ;
2022-12-26 08:21:26 -05:00
profile - > m_version = TRY ( parse_version ( header ) ) ;
profile - > m_device_class = TRY ( parse_device_class ( header ) ) ;
2022-12-30 07:54:58 -05:00
profile - > m_data_color_space = TRY ( parse_data_color_space ( header ) ) ;
2022-12-30 11:07:38 -05:00
profile - > m_connection_space = TRY ( parse_connection_space ( header ) ) ;
2022-12-27 13:37:37 -05:00
profile - > m_creation_timestamp = TRY ( parse_creation_date_time ( header ) ) ;
2023-01-07 15:22:15 -05:00
profile - > m_primary_platform = TRY ( parse_primary_platform ( header ) ) ;
2022-12-31 13:50:52 -05:00
profile - > m_flags = Flags { header . profile_flags } ;
2023-01-06 15:57:45 -05:00
profile - > m_device_manufacturer = parse_device_manufacturer ( header ) ;
profile - > m_device_model = parse_device_model ( header ) ;
2023-01-06 15:06:30 -05:00
profile - > m_device_attributes = TRY ( parse_device_attributes ( header ) ) ;
2022-12-30 08:15:49 -05:00
profile - > m_rendering_intent = TRY ( parse_rendering_intent ( header ) ) ;
2023-01-01 07:09:38 -05:00
profile - > m_pcs_illuminant = TRY ( parse_pcs_illuminant ( header ) ) ;
2023-01-06 15:57:45 -05:00
profile - > m_creator = parse_profile_creator ( header ) ;
2023-01-06 13:46:18 -05:00
profile - > m_id = TRY ( parse_profile_id ( header , bytes ) ) ;
2023-01-06 12:06:12 -05:00
TRY ( parse_reserved ( header ) ) ;
2022-12-26 08:21:26 -05:00
2023-01-08 07:07:21 -05:00
bytes = bytes . trim ( header . profile_size ) ;
bytes = bytes . slice ( sizeof ( ICCHeader ) ) ;
// FIXME: Read tag table.
2022-12-26 08:21:26 -05:00
return profile ;
}
2023-01-04 15:34:25 -05:00
Crypto : : Hash : : MD5 : : DigestType Profile : : compute_id ( ReadonlyBytes bytes )
{
// ICC v4, 7.2.18 Profile ID field
2023-01-05 15:10:30 -05:00
// "The Profile ID shall be calculated using the MD5 fingerprinting method as defined in Internet RFC 1321.
// The entire profile, whose length is given by the size field in the header, with the
// profile flags field (bytes 44 to 47, see 7.2.11),
// rendering intent field (bytes 64 to 67, see 7.2.15),
// and profile ID field (bytes 84 to 99)
// in the profile header temporarily set to zeros (00h),
// shall be used to calculate the ID."
2023-01-04 15:34:25 -05:00
const u8 zero [ 16 ] = { } ;
Crypto : : Hash : : MD5 md5 ;
md5 . update ( bytes . slice ( 0 , 44 ) ) ;
md5 . update ( ReadonlyBytes { zero , 4 } ) ; // profile flags field
md5 . update ( bytes . slice ( 48 , 64 - 48 ) ) ;
md5 . update ( ReadonlyBytes { zero , 4 } ) ; // rendering intent field
md5 . update ( bytes . slice ( 68 , 84 - 68 ) ) ;
md5 . update ( ReadonlyBytes { zero , 16 } ) ; // profile ID field
md5 . update ( bytes . slice ( 100 ) ) ;
return md5 . digest ( ) ;
}
2022-12-26 08:21:26 -05:00
}