1/* 2 * Copyright 2016 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "Resources.h" 9 10#include "SkBitmap.h" 11#include "SkCanvas.h" 12#include "SkCodec.h" 13#include "SkColorSpacePriv.h" 14#include "SkColorSpace_A2B.h" 15#include "SkColorSpace_XYZ.h" 16#include "SkCommandLineFlags.h" 17#include "SkICCPriv.h" 18#include "SkImageEncoder.h" 19#include "SkMatrix44.h" 20#include "SkOSFile.h" 21#include "SkRasterPipeline.h" 22#include "../src/jumper/SkJumper.h" 23 24#include "sk_tool_utils.h" 25 26#include <sstream> 27#include <string> 28#include <vector> 29 30DEFINE_string(input, "input.png", "A path to the input image (or icc profile with --icc)."); 31DEFINE_string(output, ".", "A path to the output image directory."); 32DEFINE_bool(icc, false, "Indicates that the input is an icc profile."); 33DEFINE_bool(sRGB_gamut, false, "Draws the sRGB gamut on the gamut visualization."); 34DEFINE_bool(adobeRGB, false, "Draws the Adobe RGB gamut on the gamut visualization."); 35DEFINE_bool(sRGB_gamma, false, "Draws the sRGB gamma on all gamma output images."); 36DEFINE_string(uncorrected, "", "A path to reencode the uncorrected input image."); 37 38 39//------------------------------------------------------------------------------------------------- 40//------------------------------------ Gamma visualizations --------------------------------------- 41 42static const char* kRGBChannelNames[3] = { 43 "Red ", 44 "Green", 45 "Blue " 46}; 47static const SkColor kRGBChannelColors[3] = { 48 SkColorSetARGB(128, 255, 0, 0), 49 SkColorSetARGB(128, 0, 255, 0), 50 SkColorSetARGB(128, 0, 0, 255) 51}; 52 53static const char* kGrayChannelNames[1] = { "Gray"}; 54static const SkColor kGrayChannelColors[1] = { SkColorSetRGB(128, 128, 128) }; 55 56static const char* kCMYKChannelNames[4] = { 57 "Cyan ", 58 "Magenta", 59 "Yellow ", 60 "Black " 61}; 62static const SkColor kCMYKChannelColors[4] = { 63 SkColorSetARGB(128, 0, 255, 255), 64 SkColorSetARGB(128, 255, 0, 255), 65 SkColorSetARGB(128, 255, 255, 0), 66 SkColorSetARGB(128, 16, 16, 16) 67}; 68 69static const char*const*const kChannelNames[4] = { 70 kGrayChannelNames, 71 kRGBChannelNames, 72 kRGBChannelNames, 73 kCMYKChannelNames 74}; 75static const SkColor*const kChannelColors[4] = { 76 kGrayChannelColors, 77 kRGBChannelColors, 78 kRGBChannelColors, 79 kCMYKChannelColors 80}; 81 82static void dump_transfer_fn(SkGammaNamed gammaNamed) { 83 switch (gammaNamed) { 84 case kSRGB_SkGammaNamed: 85 SkDebugf("Transfer Function: sRGB\n"); 86 return; 87 case k2Dot2Curve_SkGammaNamed: 88 SkDebugf("Exponential Transfer Function: Exponent 2.2\n"); 89 return; 90 case kLinear_SkGammaNamed: 91 SkDebugf("Transfer Function: Linear\n"); 92 return; 93 default: 94 break; 95 } 96 97} 98 99static constexpr int kGammaImageWidth = 500; 100static constexpr int kGammaImageHeight = 500; 101 102static void dump_transfer_fn(const SkGammas& gammas) { 103 SkASSERT(gammas.channels() <= 4); 104 const char*const*const channels = kChannelNames[gammas.channels() - 1]; 105 for (int i = 0; i < gammas.channels(); i++) { 106 if (gammas.isNamed(i)) { 107 switch (gammas.data(i).fNamed) { 108 case kSRGB_SkGammaNamed: 109 SkDebugf("%s Transfer Function: sRGB\n", channels[i]); 110 return; 111 case k2Dot2Curve_SkGammaNamed: 112 SkDebugf("%s Transfer Function: Exponent 2.2\n", channels[i]); 113 return; 114 case kLinear_SkGammaNamed: 115 SkDebugf("%s Transfer Function: Linear\n", channels[i]); 116 return; 117 default: 118 SkASSERT(false); 119 continue; 120 } 121 } else if (gammas.isValue(i)) { 122 SkDebugf("%s Transfer Function: Exponent %.3f\n", channels[i], gammas.data(i).fValue); 123 } else if (gammas.isParametric(i)) { 124 const SkColorSpaceTransferFn& fn = gammas.data(i).params(&gammas); 125 SkDebugf("%s Transfer Function: Parametric A = %.3f, B = %.3f, C = %.3f, D = %.3f, " 126 "E = %.3f, F = %.3f, G = %.3f\n", channels[i], fn.fA, fn.fB, fn.fC, fn.fD, 127 fn.fE, fn.fF, fn.fG); 128 } else { 129 SkASSERT(gammas.isTable(i)); 130 SkDebugf("%s Transfer Function: Table (%d entries)\n", channels[i], 131 gammas.data(i).fTable.fSize); 132 } 133 } 134} 135 136static inline float parametric(const SkColorSpaceTransferFn& fn, float x) { 137 return x >= fn.fD ? powf(fn.fA*x + fn.fB, fn.fG) + fn.fE 138 : fn.fC*x + fn.fF; 139} 140 141static void draw_transfer_fn(SkCanvas* canvas, SkGammaNamed gammaNamed, const SkGammas* gammas, 142 SkColor color) { 143 SkColorSpaceTransferFn fn[4]; 144 struct TableInfo { 145 const float* fTable; 146 int fSize; 147 }; 148 TableInfo table[4]; 149 bool isTable[4] = {false, false, false, false}; 150 const int channels = gammas ? gammas->channels() : 1; 151 SkASSERT(channels <= 4); 152 if (kNonStandard_SkGammaNamed != gammaNamed) { 153 dump_transfer_fn(gammaNamed); 154 for (int i = 0; i < channels; ++i) { 155 named_to_parametric(&fn[i], gammaNamed); 156 } 157 } else { 158 SkASSERT(gammas); 159 dump_transfer_fn(*gammas); 160 for (int i = 0; i < channels; ++i) { 161 if (gammas->isTable(i)) { 162 table[i].fTable = gammas->table(i); 163 table[i].fSize = gammas->data(i).fTable.fSize; 164 isTable[i] = true; 165 } else { 166 switch (gammas->type(i)) { 167 case SkGammas::Type::kNamed_Type: 168 named_to_parametric(&fn[i], gammas->data(i).fNamed); 169 break; 170 case SkGammas::Type::kValue_Type: 171 value_to_parametric(&fn[i], gammas->data(i).fValue); 172 break; 173 case SkGammas::Type::kParam_Type: 174 fn[i] = gammas->params(i); 175 break; 176 default: 177 SkASSERT(false); 178 } 179 } 180 } 181 } 182 SkPaint paint; 183 paint.setStyle(SkPaint::kStroke_Style); 184 paint.setColor(color); 185 paint.setStrokeWidth(2.0f); 186 // note: gamma has positive values going up in this image so this origin is 187 // the bottom left and we must subtract y instead of adding. 188 const float gap = 16.0f; 189 const float gammaWidth = kGammaImageWidth - 2 * gap; 190 const float gammaHeight = kGammaImageHeight - 2 * gap; 191 // gamma origin point 192 const float ox = gap; 193 const float oy = gap + gammaHeight; 194 for (int i = 0; i < channels; ++i) { 195 if (kNonStandard_SkGammaNamed == gammaNamed) { 196 paint.setColor(kChannelColors[channels - 1][i]); 197 } else { 198 paint.setColor(color); 199 } 200 if (isTable[i]) { 201 auto tx = [&table,i](int index) { 202 return index / (table[i].fSize - 1.0f); 203 }; 204 for (int ti = 1; ti < table[i].fSize; ++ti) { 205 canvas->drawLine(ox + gammaWidth * tx(ti - 1), 206 oy - gammaHeight * table[i].fTable[ti - 1], 207 ox + gammaWidth * tx(ti), 208 oy - gammaHeight * table[i].fTable[ti], 209 paint); 210 } 211 } else { 212 const float step = 0.01f; 213 float yPrev = parametric(fn[i], 0.0f); 214 for (float x = step; x <= 1.0f; x += step) { 215 const float y = parametric(fn[i], x); 216 canvas->drawLine(ox + gammaWidth * (x - step), oy - gammaHeight * yPrev, 217 ox + gammaWidth * x, oy - gammaHeight * y, 218 paint); 219 yPrev = y; 220 } 221 } 222 } 223 paint.setColor(0xFF000000); 224 paint.setStrokeWidth(3.0f); 225 canvas->drawRect({ ox, oy - gammaHeight, ox + gammaWidth, oy }, paint); 226} 227 228//------------------------------------------------------------------------------------------------- 229//------------------------------------ CLUT visualizations ---------------------------------------- 230static void dump_clut(const SkColorLookUpTable& clut) { 231 SkDebugf("CLUT: "); 232 for (int i = 0; i < clut.inputChannels(); ++i) { 233 SkDebugf("[%d]", clut.gridPoints(i)); 234 } 235 SkDebugf(" -> [%d]\n", clut.outputChannels()); 236} 237 238constexpr int kClutGap = 8; 239constexpr float kClutCanvasSize = 2000; 240 241static inline int usedGridPoints(const SkColorLookUpTable& clut, int dimension) { 242 const int gp = clut.gridPoints(dimension); 243 return gp <= 16 ? gp : 16; 244} 245 246// how many rows of cross-section cuts to display 247static inline int cut_rows(const SkColorLookUpTable& clut, int dimOrder[4]) { 248 // and vertical ones for the 4th dimension (if applicable) 249 return clut.inputChannels() >= 4 ? usedGridPoints(clut, dimOrder[3]) : 1; 250} 251 252// how many columns of cross-section cuts to display 253static inline int cut_cols(const SkColorLookUpTable& clut, int dimOrder[4]) { 254 // do horizontal cuts for the 3rd dimension (if applicable) 255 return clut.inputChannels() >= 3 ? usedGridPoints(clut, dimOrder[2]) : 1; 256} 257 258// gets the width/height to use for cross-sections of a CLUT 259static int cut_size(const SkColorLookUpTable& clut, int dimOrder[4]) { 260 const int rows = cut_rows(clut, dimOrder); 261 const int cols = cut_cols(clut, dimOrder); 262 // make sure the cross-section CLUT cuts are square still by using the 263 // smallest of the width/height, then adjust the gaps between accordingly 264 const int cutWidth = (kClutCanvasSize - kClutGap * (1 + cols)) / cols; 265 const int cutHeight = (kClutCanvasSize - kClutGap * (1 + rows)) / rows; 266 return cutWidth < cutHeight ? cutWidth : cutHeight; 267} 268 269static void clut_interp(const SkColorLookUpTable& clut, float out[3], const float in[4]) { 270 // This is kind of a toy implementation. 271 // You generally wouldn't want to do this 1 pixel at a time. 272 273 SkJumper_ColorLookupTableCtx ctx; 274 ctx.table = clut.table(); 275 for (int i = 0; i < clut.inputChannels(); i++) { 276 ctx.limits[i] = clut.gridPoints(i); 277 } 278 279 SkSTArenaAlloc<256> alloc; 280 SkRasterPipeline p(&alloc); 281 p.append_constant_color(&alloc, in); 282 p.append(clut.inputChannels() == 3 ? SkRasterPipeline::clut_3D 283 : SkRasterPipeline::clut_4D, &ctx); 284 p.append(SkRasterPipeline::clamp_0); 285 p.append(SkRasterPipeline::clamp_1); 286 p.append(SkRasterPipeline::store_f32, &out); 287 p.run(0,0, 1,1); 288} 289 290static void draw_clut(SkCanvas* canvas, const SkColorLookUpTable& clut, int dimOrder[4]) { 291 dump_clut(clut); 292 293 const int cutSize = cut_size(clut, dimOrder); 294 const int rows = cut_rows(clut, dimOrder); 295 const int cols = cut_cols(clut, dimOrder); 296 const int cutHorizGap = (kClutCanvasSize - cutSize * cols) / (1 + cols); 297 const int cutVertGap = (kClutCanvasSize - cutSize * rows) / (1 + rows); 298 299 SkPaint paint; 300 for (int row = 0; row < rows; ++row) { 301 for (int col = 0; col < cols; ++col) { 302 // make sure to move at least one pixel, but otherwise move per-gridpoint 303 const float xStep = 1.0f / (SkTMin(cutSize, clut.gridPoints(dimOrder[0])) - 1); 304 const float yStep = 1.0f / (SkTMin(cutSize, clut.gridPoints(dimOrder[1])) - 1); 305 const float ox = clut.inputChannels() >= 3 ? (1 + col) * cutHorizGap + col * cutSize 306 : kClutGap; 307 const float oy = clut.inputChannels() >= 4 ? (1 + row) * cutVertGap + row * cutSize 308 : kClutGap; 309 // for each cross-section cut, draw a bunch of squares whose colour is the top-left's 310 // colour in the CLUT (usually this will just draw the gridpoints) 311 for (float x = 0.0f; x < 1.0f; x += xStep) { 312 for (float y = 0.0f; y < 1.0f; y += yStep) { 313 const float z = col / (cols - 1.0f); 314 const float w = row / (rows - 1.0f); 315 const float input[4] = {x, y, z, w}; 316 float output[3]; 317 clut_interp(clut, output, input); 318 paint.setColor(SkColorSetRGB(255*output[0], 255*output[1], 255*output[2])); 319 canvas->drawRect(SkRect::MakeLTRB(ox + cutSize * x, oy + cutSize * y, 320 ox + cutSize * (x + xStep), 321 oy + cutSize * (y + yStep)), paint); 322 } 323 } 324 } 325 } 326} 327 328 329//------------------------------------------------------------------------------------------------- 330//------------------------------------ Gamut visualizations --------------------------------------- 331static void dump_matrix(const SkMatrix44& m) { 332 for (int r = 0; r < 4; ++r) { 333 SkDebugf("|"); 334 for (int c = 0; c < 4; ++c) { 335 SkDebugf(" %f ", m.get(r, c)); 336 } 337 SkDebugf("|\n"); 338 } 339} 340 341/** 342 * Loads the triangular gamut as a set of three points. 343 */ 344static void load_gamut(SkPoint rgb[], const SkMatrix44& xyz) { 345 // rx = rX / (rX + rY + rZ) 346 // ry = rX / (rX + rY + rZ) 347 // gx, gy, bx, and gy are calulcated similarly. 348 float rSum = xyz.get(0, 0) + xyz.get(1, 0) + xyz.get(2, 0); 349 float gSum = xyz.get(0, 1) + xyz.get(1, 1) + xyz.get(2, 1); 350 float bSum = xyz.get(0, 2) + xyz.get(1, 2) + xyz.get(2, 2); 351 rgb[0].fX = xyz.get(0, 0) / rSum; 352 rgb[0].fY = xyz.get(1, 0) / rSum; 353 rgb[1].fX = xyz.get(0, 1) / gSum; 354 rgb[1].fY = xyz.get(1, 1) / gSum; 355 rgb[2].fX = xyz.get(0, 2) / bSum; 356 rgb[2].fY = xyz.get(1, 2) / bSum; 357} 358 359/** 360 * Calculates the area of the triangular gamut. 361 */ 362static float calculate_area(SkPoint abc[]) { 363 SkPoint a = abc[0]; 364 SkPoint b = abc[1]; 365 SkPoint c = abc[2]; 366 return 0.5f * SkTAbs(a.fX*b.fY + b.fX*c.fY - a.fX*c.fY - c.fX*b.fY - b.fX*a.fY); 367} 368 369static void draw_gamut(SkCanvas* canvas, const SkMatrix44& xyz, const char* name, SkColor color, 370 bool label) { 371 // Report the XYZ values. 372 SkDebugf("%s\n", name); 373 SkDebugf(" R G B\n"); 374 SkDebugf("X %.3f %.3f %.3f\n", xyz.get(0, 0), xyz.get(0, 1), xyz.get(0, 2)); 375 SkDebugf("Y %.3f %.3f %.3f\n", xyz.get(1, 0), xyz.get(1, 1), xyz.get(1, 2)); 376 SkDebugf("Z %.3f %.3f %.3f\n", xyz.get(2, 0), xyz.get(2, 1), xyz.get(2, 2)); 377 378 // Calculate the points in the gamut from the XYZ values. 379 SkPoint rgb[4]; 380 load_gamut(rgb, xyz); 381 382 // Report the area of the gamut. 383 SkDebugf("Area of Gamut: %.3f\n\n", calculate_area(rgb)); 384 385 // Magic constants that help us place the gamut triangles in the appropriate position 386 // on the canvas. 387 const float xScale = 2071.25f; // Num pixels from 0 to 1 in x 388 const float xOffset = 241.0f; // Num pixels until start of x-axis 389 const float yScale = 2067.78f; // Num pixels from 0 to 1 in y 390 const float yOffset = -144.78f; // Num pixels until start of y-axis 391 // (negative because y extends beyond image bounds) 392 393 // Now transform the points so they can be drawn on our canvas. 394 // Note that y increases as we move down the canvas. 395 rgb[0].fX = xOffset + xScale * rgb[0].fX; 396 rgb[0].fY = yOffset + yScale * (1.0f - rgb[0].fY); 397 rgb[1].fX = xOffset + xScale * rgb[1].fX; 398 rgb[1].fY = yOffset + yScale * (1.0f - rgb[1].fY); 399 rgb[2].fX = xOffset + xScale * rgb[2].fX; 400 rgb[2].fY = yOffset + yScale * (1.0f - rgb[2].fY); 401 402 // Repeat the first point to connect the polygon. 403 rgb[3] = rgb[0]; 404 SkPaint paint; 405 paint.setColor(color); 406 paint.setStrokeWidth(6.0f); 407 paint.setTextSize(75.0f); 408 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, rgb, paint); 409 if (label) { 410 canvas->drawString("R", rgb[0].fX + 5.0f, rgb[0].fY + 75.0f, paint); 411 canvas->drawString("G", rgb[1].fX + 5.0f, rgb[1].fY - 5.0f, paint); 412 canvas->drawString("B", rgb[2].fX - 75.0f, rgb[2].fY - 5.0f, paint); 413 } 414} 415 416 417//------------------------------------------------------------------------------------------------- 418//----------------------------------------- Main code --------------------------------------------- 419static SkBitmap transparentBitmap(int width, int height) { 420 SkBitmap bitmap; 421 bitmap.allocN32Pixels(width, height); 422 bitmap.eraseColor(SkColorSetARGB(0, 0, 0, 0)); 423 return bitmap; 424} 425 426class OutputCanvas { 427public: 428 OutputCanvas(SkBitmap&& bitmap) 429 :fBitmap(bitmap) 430 ,fCanvas(fBitmap) 431 {} 432 433 bool save(std::vector<std::string>* output, const std::string& filename) { 434 // Finally, encode the result to the output file. 435 sk_sp<SkData> out = sk_tool_utils::EncodeImageToData(fBitmap, SkEncodedImageFormat::kPNG, 436 100); 437 if (!out) { 438 SkDebugf("Failed to encode %s output.\n", filename.c_str()); 439 return false; 440 } 441 SkFILEWStream stream(filename.c_str()); 442 if (!stream.write(out->data(), out->size())) { 443 SkDebugf("Failed to write %s output.\n", filename.c_str()); 444 return false; 445 } 446 // record name of canvas 447 output->push_back(filename); 448 return true; 449 } 450 451 SkCanvas* canvas() { return &fCanvas; } 452 453private: 454 SkBitmap fBitmap; 455 SkCanvas fCanvas; 456}; 457 458int main(int argc, char** argv) { 459 SkCommandLineFlags::SetUsage( 460 "Usage: colorspaceinfo --input <path to input image (or icc profile with --icc)> " 461 "--output <directory to output images> " 462 "--icc <indicates that the input is an icc profile>" 463 "--sRGB_gamut <draw canonical sRGB gamut> " 464 "--adobeRGB <draw canonical Adobe RGB gamut> " 465 "--sRGB_gamma <draw sRGB gamma> " 466 "--uncorrected <path to reencoded, uncorrected input image>\n" 467 "Description: Writes visualizations of the color space to the output image(s) ." 468 "Also, if a path is provided, writes uncorrected bytes to an unmarked " 469 "png, for comparison with the input image.\n"); 470 SkCommandLineFlags::Parse(argc, argv); 471 const char* input = FLAGS_input[0]; 472 const char* output = FLAGS_output[0]; 473 if (!input || !output) { 474 SkCommandLineFlags::PrintUsage(); 475 return -1; 476 } 477 478 sk_sp<SkData> data(SkData::MakeFromFileName(input)); 479 if (!data) { 480 SkDebugf("Cannot find input image.\n"); 481 return -1; 482 } 483 484 std::unique_ptr<SkCodec> codec = nullptr; 485 sk_sp<SkColorSpace> colorSpace = nullptr; 486 if (FLAGS_icc) { 487 colorSpace = SkColorSpace::MakeICC(data->bytes(), data->size()); 488 } else { 489 codec = SkCodec::MakeFromData(data); 490 colorSpace = sk_ref_sp(codec->getInfo().colorSpace()); 491 SkDebugf("SkCodec would naturally decode as colorType=%s\n", 492 sk_tool_utils::colortype_name(codec->getInfo().colorType())); 493 } 494 495 if (!colorSpace) { 496 SkDebugf("Cannot create codec or icc profile from input file.\n"); 497 return -1; 498 } 499 500 { 501 SkColorSpaceTransferFn colorSpaceTransferFn; 502 SkMatrix44 toXYZD50(SkMatrix44::kIdentity_Constructor); 503 if (colorSpace->isNumericalTransferFn(&colorSpaceTransferFn) && 504 colorSpace->toXYZD50(&toXYZD50)) { 505 SkString description = SkICCGetColorProfileTag(colorSpaceTransferFn, toXYZD50); 506 SkDebugf("Color Profile Description: \"%s\"\n", description.c_str()); 507 } 508 } 509 510 // TODO: command line tweaking of this order 511 int dimOrder[4] = {0, 1, 2, 3}; 512 513 std::vector<std::string> outputFilenames; 514 515 auto createOutputFilename = [output](const char* category, int index) -> std::string { 516 std::stringstream ss; 517 ss << output << '/' << category << '_' << index << ".png"; 518 return ss.str(); 519 }; 520 521 if (colorSpace->toXYZD50()) { 522 SkDebugf("XYZ/TRC color space\n"); 523 524 // Load a graph of the CIE XYZ color gamut. 525 SkBitmap gamutCanvasBitmap; 526 if (!GetResourceAsBitmap("images/gamut.png", &gamutCanvasBitmap)) { 527 SkDebugf("Program failure (could not load gamut.png).\n"); 528 return -1; 529 } 530 OutputCanvas gamutCanvas(std::move(gamutCanvasBitmap)); 531 // Draw the sRGB gamut if requested. 532 if (FLAGS_sRGB_gamut) { 533 sk_sp<SkColorSpace> sRGBSpace = SkColorSpace::MakeSRGB(); 534 const SkMatrix44* mat = sRGBSpace->toXYZD50(); 535 SkASSERT(mat); 536 draw_gamut(gamutCanvas.canvas(), *mat, "sRGB", 0xFFFF9394, false); 537 } 538 539 // Draw the Adobe RGB gamut if requested. 540 if (FLAGS_adobeRGB) { 541 sk_sp<SkColorSpace> adobeRGBSpace = SkColorSpace::MakeRGB( 542 SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kAdobeRGB_Gamut); 543 const SkMatrix44* mat = adobeRGBSpace->toXYZD50(); 544 SkASSERT(mat); 545 draw_gamut(gamutCanvas.canvas(), *mat, "Adobe RGB", 0xFF31a9e1, false); 546 } 547 const SkMatrix44* mat = colorSpace->toXYZD50(); 548 SkASSERT(mat); 549 auto xyz = static_cast<SkColorSpace_XYZ*>(colorSpace.get()); 550 draw_gamut(gamutCanvas.canvas(), *mat, input, 0xFF000000, true); 551 if (!gamutCanvas.save(&outputFilenames, createOutputFilename("gamut", 0))) { 552 return -1; 553 } 554 555 OutputCanvas gammaCanvas(transparentBitmap(kGammaImageWidth, kGammaImageHeight)); 556 if (FLAGS_sRGB_gamma) { 557 draw_transfer_fn(gammaCanvas.canvas(), kSRGB_SkGammaNamed, nullptr, 0xFFFF9394); 558 } 559 draw_transfer_fn(gammaCanvas.canvas(), colorSpace->gammaNamed(), xyz->gammas(), 0xFF000000); 560 if (!gammaCanvas.save(&outputFilenames, createOutputFilename("gamma", 0))) { 561 return -1; 562 } 563 } else { 564 SkDebugf("A2B color space"); 565 SkColorSpace_A2B* a2b = static_cast<SkColorSpace_A2B*>(colorSpace.get()); 566 SkDebugf("Conversion type: "); 567 switch (a2b->iccType()) { 568 case SkColorSpace::kRGB_Type: 569 SkDebugf("RGB"); 570 break; 571 case SkColorSpace::kCMYK_Type: 572 SkDebugf("CMYK"); 573 break; 574 case SkColorSpace::kGray_Type: 575 SkDebugf("Gray"); 576 break; 577 default: 578 SkASSERT(false); 579 break; 580 581 } 582 SkDebugf(" -> "); 583 switch (a2b->pcs()) { 584 case SkColorSpace_A2B::PCS::kXYZ: 585 SkDebugf("XYZ\n"); 586 break; 587 case SkColorSpace_A2B::PCS::kLAB: 588 SkDebugf("LAB\n"); 589 break; 590 } 591 int clutCount = 0; 592 int gammaCount = 0; 593 for (int i = 0; i < a2b->count(); ++i) { 594 const SkColorSpace_A2B::Element& e = a2b->element(i); 595 switch (e.type()) { 596 case SkColorSpace_A2B::Element::Type::kGammaNamed: { 597 OutputCanvas gammaCanvas(transparentBitmap(kGammaImageWidth, 598 kGammaImageHeight)); 599 if (FLAGS_sRGB_gamma) { 600 draw_transfer_fn(gammaCanvas.canvas(), kSRGB_SkGammaNamed, nullptr, 601 0xFFFF9394); 602 } 603 draw_transfer_fn(gammaCanvas.canvas(), e.gammaNamed(), nullptr, 604 0xFF000000); 605 if (!gammaCanvas.save(&outputFilenames, 606 createOutputFilename("gamma", gammaCount++))) { 607 return -1; 608 } 609 } 610 break; 611 case SkColorSpace_A2B::Element::Type::kGammas: { 612 OutputCanvas gammaCanvas(transparentBitmap(kGammaImageWidth, 613 kGammaImageHeight)); 614 if (FLAGS_sRGB_gamma) { 615 draw_transfer_fn(gammaCanvas.canvas(), kSRGB_SkGammaNamed, nullptr, 616 0xFFFF9394); 617 } 618 draw_transfer_fn(gammaCanvas.canvas(), kNonStandard_SkGammaNamed, 619 &e.gammas(), 0xFF000000); 620 if (!gammaCanvas.save(&outputFilenames, 621 createOutputFilename("gamma", gammaCount++))) { 622 return -1; 623 } 624 } 625 break; 626 case SkColorSpace_A2B::Element::Type::kCLUT: { 627 const SkColorLookUpTable& clut = e.colorLUT(); 628 const int cutSize = cut_size(clut, dimOrder); 629 const int clutWidth = clut.inputChannels() >= 3 ? kClutCanvasSize 630 : 2 * kClutGap + cutSize; 631 const int clutHeight = clut.inputChannels() >= 4 ? kClutCanvasSize 632 : 2 * kClutGap + cutSize; 633 OutputCanvas clutCanvas(transparentBitmap(clutWidth, clutHeight)); 634 draw_clut(clutCanvas.canvas(), e.colorLUT(), dimOrder); 635 if (!clutCanvas.save(&outputFilenames, 636 createOutputFilename("clut", clutCount++))) { 637 return -1; 638 } 639 } 640 break; 641 case SkColorSpace_A2B::Element::Type::kMatrix: 642 dump_matrix(e.matrix()); 643 break; 644 } 645 } 646 } 647 648 // marker to tell the web-tool the names of all images output 649 SkDebugf("=========\n"); 650 for (const std::string& filename : outputFilenames) { 651 SkDebugf("%s\n", filename.c_str()); 652 } 653 if (!FLAGS_icc) { 654 SkDebugf("%s\n", input); 655 } 656 // Also, if requested, decode and reencode the uncorrected input image. 657 if (!FLAGS_uncorrected.isEmpty() && !FLAGS_icc) { 658 SkBitmap bitmap; 659 int width = codec->getInfo().width(); 660 int height = codec->getInfo().height(); 661 bitmap.allocN32Pixels(width, height, kOpaque_SkAlphaType == codec->getInfo().alphaType()); 662 SkImageInfo decodeInfo = SkImageInfo::MakeN32(width, height, kUnpremul_SkAlphaType); 663 if (SkCodec::kSuccess != codec->getPixels(decodeInfo, bitmap.getPixels(), 664 bitmap.rowBytes())) { 665 SkDebugf("Could not decode input image.\n"); 666 return -1; 667 } 668 sk_sp<SkData> out = sk_tool_utils::EncodeImageToData(bitmap, SkEncodedImageFormat::kPNG, 669 100); 670 if (!out) { 671 SkDebugf("Failed to encode uncorrected image.\n"); 672 return -1; 673 } 674 SkFILEWStream bitmapStream(FLAGS_uncorrected[0]); 675 if (!bitmapStream.write(out->data(), out->size())) { 676 SkDebugf("Failed to write uncorrected image output.\n"); 677 return -1; 678 } 679 SkDebugf("%s\n", FLAGS_uncorrected[0]); 680 } 681 682 return 0; 683} 684