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