• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
30 DEFINE_string(input, "input.png", "A path to the input image (or icc profile with --icc).");
31 DEFINE_string(output, ".", "A path to the output image directory.");
32 DEFINE_bool(icc, false, "Indicates that the input is an icc profile.");
33 DEFINE_bool(sRGB_gamut, false, "Draws the sRGB gamut on the gamut visualization.");
34 DEFINE_bool(adobeRGB, false, "Draws the Adobe RGB gamut on the gamut visualization.");
35 DEFINE_bool(sRGB_gamma, false, "Draws the sRGB gamma on all gamma output images.");
36 DEFINE_string(uncorrected, "", "A path to reencode the uncorrected input image.");
37 
38 
39 //-------------------------------------------------------------------------------------------------
40 //------------------------------------ Gamma visualizations ---------------------------------------
41 
42 static const char* kRGBChannelNames[3] = {
43     "Red  ",
44     "Green",
45     "Blue "
46 };
47 static const SkColor kRGBChannelColors[3] = {
48     SkColorSetARGB(128, 255, 0, 0),
49     SkColorSetARGB(128, 0, 255, 0),
50     SkColorSetARGB(128, 0, 0, 255)
51 };
52 
53 static const char* kGrayChannelNames[1] = { "Gray"};
54 static const SkColor kGrayChannelColors[1] = { SkColorSetRGB(128, 128, 128) };
55 
56 static const char* kCMYKChannelNames[4] = {
57     "Cyan   ",
58     "Magenta",
59     "Yellow ",
60     "Black  "
61 };
62 static 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 
69 static const char*const*const kChannelNames[4] = {
70     kGrayChannelNames,
71     kRGBChannelNames,
72     kRGBChannelNames,
73     kCMYKChannelNames
74 };
75 static const SkColor*const kChannelColors[4] = {
76     kGrayChannelColors,
77     kRGBChannelColors,
78     kRGBChannelColors,
79     kCMYKChannelColors
80 };
81 
dump_transfer_fn(SkGammaNamed gammaNamed)82 static 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 
99 static constexpr int kGammaImageWidth = 500;
100 static constexpr int kGammaImageHeight = 500;
101 
dump_transfer_fn(const SkGammas & gammas)102 static 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 
parametric(const SkColorSpaceTransferFn & fn,float x)136 static 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 
draw_transfer_fn(SkCanvas * canvas,SkGammaNamed gammaNamed,const SkGammas * gammas,SkColor color)141 static 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 ----------------------------------------
dump_clut(const SkColorLookUpTable & clut)230 static 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 
238 constexpr int kClutGap = 8;
239 constexpr float kClutCanvasSize = 2000;
240 
usedGridPoints(const SkColorLookUpTable & clut,int dimension)241 static 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
cut_rows(const SkColorLookUpTable & clut,int dimOrder[4])247 static 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
cut_cols(const SkColorLookUpTable & clut,int dimOrder[4])253 static 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
cut_size(const SkColorLookUpTable & clut,int dimOrder[4])259 static 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 
clut_interp(const SkColorLookUpTable & clut,float out[3],const float in[4])269 static 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 
draw_clut(SkCanvas * canvas,const SkColorLookUpTable & clut,int dimOrder[4])290 static 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 ---------------------------------------
dump_matrix(const SkMatrix44 & m)331 static 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  */
load_gamut(SkPoint rgb[],const SkMatrix44 & xyz)344 static 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  */
calculate_area(SkPoint abc[])362 static 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 
draw_gamut(SkCanvas * canvas,const SkMatrix44 & xyz,const char * name,SkColor color,bool label)369 static 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 ---------------------------------------------
transparentBitmap(int width,int height)419 static 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 
426 class OutputCanvas {
427 public:
OutputCanvas(SkBitmap && bitmap)428     OutputCanvas(SkBitmap&& bitmap)
429         :fBitmap(bitmap)
430         ,fCanvas(fBitmap)
431     {}
432 
save(std::vector<std::string> * output,const std::string & filename)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 
canvas()451     SkCanvas* canvas() { return &fCanvas; }
452 
453 private:
454     SkBitmap fBitmap;
455     SkCanvas fCanvas;
456 };
457 
main(int argc,char ** argv)458 int 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