Serenity Operating System
at master 341 lines 12 kB view raw
1/* 2 * Copyright (c) 2021-2022, Matthew Olsson <mattco@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibPDF/ColorSpace.h> 8#include <LibPDF/CommonNames.h> 9#include <LibPDF/Document.h> 10#include <LibPDF/ObjectDerivatives.h> 11 12namespace PDF { 13 14#define ENUMERATE(name, ever_needs_parameters) \ 15 ColorSpaceFamily ColorSpaceFamily::name { #name, ever_needs_parameters }; 16ENUMERATE_COLOR_SPACE_FAMILIES(ENUMERATE); 17#undef ENUMERATE 18 19PDFErrorOr<ColorSpaceFamily> ColorSpaceFamily::get(DeprecatedFlyString const& family_name) 20{ 21#define ENUMERATE(f_name, ever_needs_parameters) \ 22 if (family_name == f_name.name()) { \ 23 return ColorSpaceFamily::f_name; \ 24 } 25 ENUMERATE_COLOR_SPACE_FAMILIES(ENUMERATE) 26#undef ENUMERATE 27 return Error(Error::Type::MalformedPDF, DeprecatedString::formatted("Unknown ColorSpace family {}", family_name)); 28} 29 30PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(DeprecatedFlyString const& name) 31{ 32 // Simple color spaces with no parameters, which can be specified directly 33 if (name == CommonNames::DeviceGray) 34 return DeviceGrayColorSpace::the(); 35 if (name == CommonNames::DeviceRGB) 36 return DeviceRGBColorSpace::the(); 37 if (name == CommonNames::DeviceCMYK) 38 return DeviceCMYKColorSpace::the(); 39 if (name == CommonNames::Pattern) 40 TODO(); 41 VERIFY_NOT_REACHED(); 42} 43 44PDFErrorOr<NonnullRefPtr<ColorSpace>> ColorSpace::create(Document* document, NonnullRefPtr<ArrayObject> color_space_array) 45{ 46 auto color_space_name = TRY(color_space_array->get_name_at(document, 0))->name(); 47 48 Vector<Value> parameters; 49 parameters.ensure_capacity(color_space_array->size() - 1); 50 for (size_t i = 1; i < color_space_array->size(); i++) 51 parameters.unchecked_append(color_space_array->at(i)); 52 53 if (color_space_name == CommonNames::CalRGB) 54 return TRY(CalRGBColorSpace::create(document, move(parameters))); 55 56 if (color_space_name == CommonNames::ICCBased) 57 return TRY(ICCBasedColorSpace::create(document, move(parameters))); 58 59 dbgln("Unknown color space: {}", color_space_name); 60 TODO(); 61} 62 63NonnullRefPtr<DeviceGrayColorSpace> DeviceGrayColorSpace::the() 64{ 65 static auto instance = adopt_ref(*new DeviceGrayColorSpace()); 66 return instance; 67} 68 69Color DeviceGrayColorSpace::color(Vector<Value> const& arguments) const 70{ 71 VERIFY(arguments.size() == 1); 72 auto gray = static_cast<u8>(arguments[0].to_float() * 255.0f); 73 return Color(gray, gray, gray); 74} 75 76Vector<float> DeviceGrayColorSpace::default_decode() const 77{ 78 return { 0.0f, 1.0f }; 79} 80 81NonnullRefPtr<DeviceRGBColorSpace> DeviceRGBColorSpace::the() 82{ 83 static auto instance = adopt_ref(*new DeviceRGBColorSpace()); 84 return instance; 85} 86 87Color DeviceRGBColorSpace::color(Vector<Value> const& arguments) const 88{ 89 VERIFY(arguments.size() == 3); 90 auto r = static_cast<u8>(arguments[0].to_float() * 255.0f); 91 auto g = static_cast<u8>(arguments[1].to_float() * 255.0f); 92 auto b = static_cast<u8>(arguments[2].to_float() * 255.0f); 93 return Color(r, g, b); 94} 95 96Vector<float> DeviceRGBColorSpace::default_decode() const 97{ 98 return { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }; 99} 100 101NonnullRefPtr<DeviceCMYKColorSpace> DeviceCMYKColorSpace::the() 102{ 103 static auto instance = adopt_ref(*new DeviceCMYKColorSpace()); 104 return instance; 105} 106 107Color DeviceCMYKColorSpace::color(Vector<Value> const& arguments) const 108{ 109 VERIFY(arguments.size() == 4); 110 auto c = arguments[0].to_float(); 111 auto m = arguments[1].to_float(); 112 auto y = arguments[2].to_float(); 113 auto k = arguments[3].to_float(); 114 return Color::from_cmyk(c, m, y, k); 115} 116 117Vector<float> DeviceCMYKColorSpace::default_decode() const 118{ 119 return { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }; 120} 121 122PDFErrorOr<NonnullRefPtr<CalRGBColorSpace>> CalRGBColorSpace::create(Document* document, Vector<Value>&& parameters) 123{ 124 if (parameters.size() != 1) 125 return Error { Error::Type::MalformedPDF, "RGB color space expects one parameter" }; 126 127 auto param = parameters[0]; 128 if (!param.has<NonnullRefPtr<Object>>() || !param.get<NonnullRefPtr<Object>>()->is<DictObject>()) 129 return Error { Error::Type::MalformedPDF, "RGB color space expects a dict parameter" }; 130 131 auto dict = param.get<NonnullRefPtr<Object>>()->cast<DictObject>(); 132 if (!dict->contains(CommonNames::WhitePoint)) 133 return Error { Error::Type::MalformedPDF, "RGB color space expects a Whitepoint key" }; 134 135 auto white_point_array = TRY(dict->get_array(document, CommonNames::WhitePoint)); 136 if (white_point_array->size() != 3) 137 return Error { Error::Type::MalformedPDF, "RGB color space expects 3 Whitepoint parameters" }; 138 139 auto color_space = adopt_ref(*new CalRGBColorSpace()); 140 141 color_space->m_whitepoint[0] = white_point_array->at(0).to_float(); 142 color_space->m_whitepoint[1] = white_point_array->at(1).to_float(); 143 color_space->m_whitepoint[2] = white_point_array->at(2).to_float(); 144 145 if (color_space->m_whitepoint[1] != 1.0f) 146 return Error { Error::Type::MalformedPDF, "RGB color space expects 2nd Whitepoint to be 1.0" }; 147 148 if (dict->contains(CommonNames::BlackPoint)) { 149 auto black_point_array = TRY(dict->get_array(document, CommonNames::BlackPoint)); 150 if (black_point_array->size() == 3) { 151 color_space->m_blackpoint[0] = black_point_array->at(0).to_float(); 152 color_space->m_blackpoint[1] = black_point_array->at(1).to_float(); 153 color_space->m_blackpoint[2] = black_point_array->at(2).to_float(); 154 } 155 } 156 157 if (dict->contains(CommonNames::Gamma)) { 158 auto gamma_array = TRY(dict->get_array(document, CommonNames::Gamma)); 159 if (gamma_array->size() == 3) { 160 color_space->m_gamma[0] = gamma_array->at(0).to_float(); 161 color_space->m_gamma[1] = gamma_array->at(1).to_float(); 162 color_space->m_gamma[2] = gamma_array->at(2).to_float(); 163 } 164 } 165 166 if (dict->contains(CommonNames::Matrix)) { 167 auto matrix_array = TRY(dict->get_array(document, CommonNames::Matrix)); 168 if (matrix_array->size() == 3) { 169 color_space->m_matrix[0] = matrix_array->at(0).to_float(); 170 color_space->m_matrix[1] = matrix_array->at(1).to_float(); 171 color_space->m_matrix[2] = matrix_array->at(2).to_float(); 172 color_space->m_matrix[3] = matrix_array->at(3).to_float(); 173 color_space->m_matrix[4] = matrix_array->at(4).to_float(); 174 color_space->m_matrix[5] = matrix_array->at(5).to_float(); 175 color_space->m_matrix[6] = matrix_array->at(6).to_float(); 176 color_space->m_matrix[7] = matrix_array->at(7).to_float(); 177 color_space->m_matrix[8] = matrix_array->at(8).to_float(); 178 } 179 } 180 181 return color_space; 182} 183 184constexpr Array<float, 3> matrix_multiply(Array<float, 9> a, Array<float, 3> b) 185{ 186 return Array<float, 3> { 187 a[0] * b[0] + a[1] * b[1] + a[2] * b[2], 188 a[3] * b[0] + a[4] * b[1] + a[5] * b[2], 189 a[6] * b[0] + a[7] * b[1] + a[8] * b[2] 190 }; 191} 192 193// Converts to a flat XYZ space with white point = (1, 1, 1) 194// Step 2 of https://www.adobe.com/content/dam/acom/en/devnet/photoshop/sdk/AdobeBPC.pdf 195constexpr Array<float, 3> flatten_and_normalize_whitepoint(Array<float, 3> whitepoint, Array<float, 3> xyz) 196{ 197 VERIFY(whitepoint[1] == 1.0f); 198 199 return { 200 (1.0f / whitepoint[0]) * xyz[0], 201 xyz[1], 202 (1.0f / whitepoint[2]) * xyz[2], 203 }; 204} 205 206constexpr float decode_l(float input) 207{ 208 constexpr float decode_l_scaling_constant = 0.00110705646f; // (((8 + 16) / 116) ^ 3) / 8 209 210 if (input < 0.0f) 211 return -decode_l(-input); 212 if (input >= 0.0f && input <= 8.0f) 213 return input * decode_l_scaling_constant; 214 return powf(((input + 16.0f) / 116.0f), 3.0f); 215} 216 217constexpr Array<float, 3> scale_black_point(Array<float, 3> blackpoint, Array<float, 3> xyz) 218{ 219 auto y_dst = decode_l(0); // DestinationBlackPoint is just [0, 0, 0] 220 auto y_src = decode_l(blackpoint[0]); 221 auto scale = (1 - y_dst) / (1 - y_src); 222 auto offset = 1 - scale; 223 224 return { 225 xyz[0] * scale + offset, 226 xyz[1] * scale + offset, 227 xyz[2] * scale + offset, 228 }; 229} 230 231// https://en.wikipedia.org/wiki/Illuminant_D65 232constexpr Array<float, 3> convert_to_d65(Array<float, 3> whitepoint, Array<float, 3> xyz) 233{ 234 constexpr float d65x = 0.95047f; 235 constexpr float d65y = 1.0f; 236 constexpr float d65z = 1.08883f; 237 238 return { 239 (xyz[0] * d65x) / whitepoint[0], 240 (xyz[1] * d65y) / whitepoint[1], 241 (xyz[2] * d65z) / whitepoint[2], 242 }; 243} 244 245// https://en.wikipedia.org/wiki/SRGB 246constexpr Array<float, 3> convert_to_srgb(Array<float, 3> xyz) 247{ 248 // See the sRGB D65 [M]^-1 matrix in the following page 249 // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 250 constexpr Array<float, 9> conversion_matrix = { 251 3.2404542, 252 -1.5371385, 253 -0.4985314, 254 -0.969266, 255 1.8760108, 256 0.0415560, 257 0.0556434, 258 -0.2040259, 259 1.0572252, 260 }; 261 262 return matrix_multiply(conversion_matrix, xyz); 263} 264 265Color CalRGBColorSpace::color(Vector<Value> const& arguments) const 266{ 267 VERIFY(arguments.size() == 3); 268 auto a = clamp(arguments[0].to_float(), 0.0f, 1.0f); 269 auto b = clamp(arguments[1].to_float(), 0.0f, 1.0f); 270 auto c = clamp(arguments[2].to_float(), 0.0f, 1.0f); 271 272 auto agr = powf(a, m_gamma[0]); 273 auto bgg = powf(b, m_gamma[1]); 274 auto cgb = powf(c, m_gamma[2]); 275 276 auto x = m_matrix[0] * agr + m_matrix[3] * bgg + m_matrix[6] * cgb; 277 auto y = m_matrix[1] * agr + m_matrix[4] * bgg + m_matrix[7] * cgb; 278 auto z = m_matrix[2] * agr + m_matrix[5] * bgg + m_matrix[8] * cgb; 279 280 auto flattened_xyz = flatten_and_normalize_whitepoint(m_whitepoint, { x, y, z }); 281 auto scaled_black_point_xyz = scale_black_point(m_blackpoint, flattened_xyz); 282 auto d65_normalized = convert_to_d65(m_whitepoint, scaled_black_point_xyz); 283 auto srgb = convert_to_srgb(d65_normalized); 284 285 auto red = static_cast<u8>(srgb[0] * 255.0f); 286 auto green = static_cast<u8>(srgb[1] * 255.0f); 287 auto blue = static_cast<u8>(srgb[2] * 255.0f); 288 289 return Color(red, green, blue); 290} 291 292Vector<float> CalRGBColorSpace::default_decode() const 293{ 294 return { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }; 295} 296 297PDFErrorOr<NonnullRefPtr<ColorSpace>> ICCBasedColorSpace::create(Document* document, Vector<Value>&& parameters) 298{ 299 if (parameters.is_empty()) 300 return Error { Error::Type::MalformedPDF, "ICCBased color space expected one parameter" }; 301 302 auto param = TRY(document->resolve(parameters[0])); 303 if (!param.has<NonnullRefPtr<Object>>() || !param.get<NonnullRefPtr<Object>>()->is<StreamObject>()) 304 return Error { Error::Type::MalformedPDF, "ICCBased color space expects a stream parameter" }; 305 306 auto dict = param.get<NonnullRefPtr<Object>>()->cast<StreamObject>()->dict(); 307 308 DeprecatedFlyString name; 309 if (!dict->contains(CommonNames::Alternate)) { 310 auto number_of_components = dict->get_value(CommonNames::N).to_int(); 311 if (number_of_components == 1) 312 name = CommonNames::DeviceGray; 313 else if (number_of_components == 3) 314 name = CommonNames::DeviceRGB; 315 else if (number_of_components == 4) 316 name = CommonNames::DeviceCMYK; 317 else 318 VERIFY_NOT_REACHED(); 319 return ColorSpace::create(name); 320 } 321 322 auto alternate_color_space_object = MUST(dict->get_object(document, CommonNames::Alternate)); 323 if (alternate_color_space_object->is<NameObject>()) { 324 return ColorSpace::create(alternate_color_space_object->cast<NameObject>()->name()); 325 } 326 327 dbgln("Alternate color spaces in array format not supported yet "); 328 TODO(); 329} 330 331Color ICCBasedColorSpace::color(Vector<Value> const&) const 332{ 333 VERIFY_NOT_REACHED(); 334} 335 336Vector<float> ICCBasedColorSpace::default_decode() const 337{ 338 VERIFY_NOT_REACHED(); 339} 340 341}