mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 11:07:45 +00:00
ICC: Add method to convert a color to the profile connection space
Only implemented for matrix profiles so far. This API won't be fast enough to color manage images, but let's get something working before getting something fast.
This commit is contained in:
parent
e76d2238bb
commit
af453b246a
3 changed files with 145 additions and 0 deletions
|
@ -1356,4 +1356,122 @@ Crypto::Hash::MD5::DigestType Profile::compute_id(ReadonlyBytes bytes)
|
||||||
return md5.digest();
|
return md5.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TagSignature tag_for_rendering_intent(RenderingIntent rendering_intent)
|
||||||
|
{
|
||||||
|
// ICCv4, Table 25 — Profile type/profile tag and defined rendering intents
|
||||||
|
// This function assumes a profile class of InputDevice, DisplayDevice, OutputDevice, or ColorSpace.
|
||||||
|
switch (rendering_intent) {
|
||||||
|
case RenderingIntent::Perceptual:
|
||||||
|
return AToB0Tag;
|
||||||
|
case RenderingIntent::MediaRelativeColorimetric:
|
||||||
|
case RenderingIntent::ICCAbsoluteColorimetric:
|
||||||
|
return AToB1Tag;
|
||||||
|
case RenderingIntent::Saturation:
|
||||||
|
return AToB2Tag;
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorOr<FloatVector3> Profile::to_pcs(ReadonlyBytes color)
|
||||||
|
{
|
||||||
|
if (color.size() != number_of_components_in_color_space(data_color_space()))
|
||||||
|
return Error::from_string_literal("ICC::Profile: input color doesn't match color space size");
|
||||||
|
|
||||||
|
auto has_tag = [&](auto tag) { return m_tag_table.contains(tag); };
|
||||||
|
auto has_all_tags = [&]<class T>(T tags) { return all_of(tags, has_tag); };
|
||||||
|
|
||||||
|
switch (device_class()) {
|
||||||
|
case DeviceClass::InputDevice:
|
||||||
|
case DeviceClass::DisplayDevice:
|
||||||
|
case DeviceClass::OutputDevice:
|
||||||
|
case DeviceClass::ColorSpace: {
|
||||||
|
// ICC v4, 8.10 Precedence order of tag usage
|
||||||
|
// "There are several methods of colour transformation that can function within a single CMM. If data for more than
|
||||||
|
// one method are included in the same profile, the following selection algorithm shall be used by the software
|
||||||
|
// implementation."
|
||||||
|
// ICC v4, 8.10.2 Input, display, output, or colour space profile types
|
||||||
|
// "a) Use the BToD0Tag, BToD1Tag, BToD2Tag, BToD3Tag, DToB0Tag, DToB1Tag, DToB2Tag, or
|
||||||
|
// DToB3Tag designated for the rendering intent if the tag is present, except where this tag is not needed or
|
||||||
|
// supported by the CMM (if a particular processing element within the tag is not supported the tag is not
|
||||||
|
// supported)."
|
||||||
|
// FIXME: Implement multiProcessElementsType one day.
|
||||||
|
|
||||||
|
// "b) Use the BToA0Tag, BToA1Tag, BToA2Tag, AToB0Tag, AToB1Tag, or AToB2Tag designated for the
|
||||||
|
// rendering intent if present, when the tag in a) is not used."
|
||||||
|
if (has_tag(tag_for_rendering_intent(rendering_intent()))) {
|
||||||
|
// FIXME
|
||||||
|
return Error::from_string_literal("ICC::Profile::to_pcs: AToB0Tag handling not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
// "c) Use the BToA0Tag or AToB0Tag if present, when the tags in a) and b) are not used."
|
||||||
|
// AToB0Tag is for the conversion _to_ PCS (BToA0Tag is for conversion _from_ PCS, so not needed in this function).
|
||||||
|
if (has_tag(AToB0Tag)) {
|
||||||
|
// FIXME
|
||||||
|
return Error::from_string_literal("ICC::Profile::to_pcs: AToB0Tag handling not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
// "d) Use TRCs (redTRCTag, greenTRCTag, blueTRCTag, or grayTRCTag) and colorants
|
||||||
|
// (redMatrixColumnTag, greenMatrixColumnTag, blueMatrixColumnTag) when tags in a), b), and c) are not
|
||||||
|
// used."
|
||||||
|
if (data_color_space() == ColorSpace::Gray) {
|
||||||
|
// ICC v4, F.2 grayTRCTag
|
||||||
|
// FIXME
|
||||||
|
return Error::from_string_literal("ICC::Profile::to_pcs: Gray handling not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Per ICC v4, A.1 General, this should also handle HLS, HSV, YCbCr.
|
||||||
|
if (data_color_space() == ColorSpace::RGB) {
|
||||||
|
if (!has_all_tags(Array { redMatrixColumnTag, greenMatrixColumnTag, blueMatrixColumnTag, redTRCTag, greenTRCTag, blueTRCTag }))
|
||||||
|
return Error::from_string_literal("ICC::Profile::to_pcs: RGB color space but neither LUT-based nor matrix-based tags present");
|
||||||
|
VERIFY(color.size() == 3); // True because of color.size() check further up.
|
||||||
|
|
||||||
|
// ICC v4, F.3 Three-component matrix-based profiles
|
||||||
|
// "linear_r = redTRC[device_r]
|
||||||
|
// linear_g = greenTRC[device_g]
|
||||||
|
// linear_b = blueTRC[device_b]
|
||||||
|
// [connection_X] = [redMatrixColumn_X greenMatrixColumn_X blueMatrixColumn_X] [ linear_r ]
|
||||||
|
// [connection_Y] = [redMatrixColumn_Y greenMatrixColumn_Y blueMatrixColumn_Y] * [ linear_g ]
|
||||||
|
// [connection_Z] = [redMatrixColumn_Z greenMatrixColumn_Z blueMatrixColumn_Z] [ linear_b ]"
|
||||||
|
auto evaluate_curve = [this](TagSignature curve_tag, float f) {
|
||||||
|
auto const& trc = *m_tag_table.get(curve_tag).value();
|
||||||
|
VERIFY(trc.type() == CurveTagData::Type || trc.type() == ParametricCurveTagData::Type);
|
||||||
|
if (trc.type() == CurveTagData::Type)
|
||||||
|
return static_cast<CurveTagData const&>(trc).evaluate(f);
|
||||||
|
return static_cast<ParametricCurveTagData const&>(trc).evaluate(f);
|
||||||
|
};
|
||||||
|
|
||||||
|
float linear_r = evaluate_curve(redTRCTag, color[0] / 255.f);
|
||||||
|
float linear_g = evaluate_curve(greenTRCTag, color[1] / 255.f);
|
||||||
|
float linear_b = evaluate_curve(blueTRCTag, color[2] / 255.f);
|
||||||
|
|
||||||
|
auto const& redMatrixColumn = red_matrix_column();
|
||||||
|
auto const& greenMatrixColumn = green_matrix_column();
|
||||||
|
auto const& blueMatrixColumn = blue_matrix_column();
|
||||||
|
|
||||||
|
float X = redMatrixColumn.x * linear_r + greenMatrixColumn.x * linear_g + blueMatrixColumn.x * linear_b;
|
||||||
|
float Y = redMatrixColumn.y * linear_r + greenMatrixColumn.y * linear_g + blueMatrixColumn.y * linear_b;
|
||||||
|
float Z = redMatrixColumn.z * linear_r + greenMatrixColumn.z * linear_g + blueMatrixColumn.z * linear_b;
|
||||||
|
|
||||||
|
return FloatVector3 { X, Y, Z };
|
||||||
|
}
|
||||||
|
|
||||||
|
return Error::from_string_literal("ICC::Profile::to_pcs: What happened?!");
|
||||||
|
}
|
||||||
|
|
||||||
|
case DeviceClass::DeviceLink:
|
||||||
|
case DeviceClass::Abstract:
|
||||||
|
// ICC v4, 8.10.3 DeviceLink or Abstract profile types
|
||||||
|
// FIXME
|
||||||
|
return Error::from_string_literal("ICC::Profile::to_pcs: conversion for DeviceLink and Abstract not implemented");
|
||||||
|
|
||||||
|
case DeviceClass::NamedColor:
|
||||||
|
return Error::from_string_literal("ICC::Profile::to_pcs: to_pcs with NamedColor profile does not make sense");
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
XYZ const& Profile::red_matrix_column() const { return xyz_data(redMatrixColumnTag); }
|
||||||
|
XYZ const& Profile::green_matrix_column() const { return xyz_data(greenMatrixColumnTag); }
|
||||||
|
XYZ const& Profile::blue_matrix_column() const { return xyz_data(blueMatrixColumnTag); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <LibCrypto/Hash/MD5.h>
|
#include <LibCrypto/Hash/MD5.h>
|
||||||
#include <LibGfx/ICC/DistinctFourCC.h>
|
#include <LibGfx/ICC/DistinctFourCC.h>
|
||||||
#include <LibGfx/ICC/TagTypes.h>
|
#include <LibGfx/ICC/TagTypes.h>
|
||||||
|
#include <LibGfx/Vector3.h>
|
||||||
|
|
||||||
namespace Gfx::ICC {
|
namespace Gfx::ICC {
|
||||||
|
|
||||||
|
@ -257,6 +258,19 @@ public:
|
||||||
bool is_v2() const { return version().major_version() == 2; }
|
bool is_v2() const { return version().major_version() == 2; }
|
||||||
bool is_v4() const { return version().major_version() == 4; }
|
bool is_v4() const { return version().major_version() == 4; }
|
||||||
|
|
||||||
|
// FIXME: The color conversion stuff should be in some other class.
|
||||||
|
|
||||||
|
// Converts an 8-bits-per-channel color to the profile connection space.
|
||||||
|
// The color's number of channels must match number_of_components_in_color_space(data_color_space()).
|
||||||
|
// Do not call for DeviceLink or NamedColor profiles. (XXX others?)
|
||||||
|
// Call connection_space() to find out the space the result is in.
|
||||||
|
ErrorOr<FloatVector3> to_pcs(ReadonlyBytes);
|
||||||
|
|
||||||
|
// Only call these if you know that this is an RGB matrix-based profile.
|
||||||
|
XYZ const& red_matrix_column() const;
|
||||||
|
XYZ const& green_matrix_column() const;
|
||||||
|
XYZ const& blue_matrix_column() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Profile(ProfileHeader const& header, OrderedHashMap<TagSignature, NonnullRefPtr<TagData>> tag_table)
|
Profile(ProfileHeader const& header, OrderedHashMap<TagSignature, NonnullRefPtr<TagData>> tag_table)
|
||||||
: m_header(header)
|
: m_header(header)
|
||||||
|
@ -264,6 +278,13 @@ private:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XYZ const& xyz_data(TagSignature tag) const
|
||||||
|
{
|
||||||
|
auto const& data = *m_tag_table.get(tag).value();
|
||||||
|
VERIFY(data.type() == XYZTagData::Type);
|
||||||
|
return static_cast<XYZTagData const&>(data).xyz();
|
||||||
|
}
|
||||||
|
|
||||||
ErrorOr<void> check_required_tags();
|
ErrorOr<void> check_required_tags();
|
||||||
ErrorOr<void> check_tag_types();
|
ErrorOr<void> check_tag_types();
|
||||||
|
|
||||||
|
|
|
@ -890,6 +890,12 @@ public:
|
||||||
|
|
||||||
Vector<XYZ, 1> const& xyzs() const { return m_xyzs; }
|
Vector<XYZ, 1> const& xyzs() const { return m_xyzs; }
|
||||||
|
|
||||||
|
XYZ const& xyz() const
|
||||||
|
{
|
||||||
|
VERIFY(m_xyzs.size() == 1);
|
||||||
|
return m_xyzs[0];
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Vector<XYZ, 1> m_xyzs;
|
Vector<XYZ, 1> m_xyzs;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue