• 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 "SkColorSpace_A2B.h"
14 #include "SkColorSpace_XYZ.h"
15 #include "SkColorSpacePriv.h"
16 #include "SkCommandLineFlags.h"
17 #include "SkImageEncoder.h"
18 #include "SkMatrix44.h"
19 #include "SkOSFile.h"
20 
21 #include "sk_tool_utils.h"
22 
23 DEFINE_string(input, "input.png", "A path to the input image or icc profile.");
24 DEFINE_string(gamut_output, "gamut_output.png", "A path to the output gamut image.");
25 DEFINE_string(gamma_output, "gamma_output.png", "A path to the output gamma image.");
26 DEFINE_bool(sRGB_gamut, false, "Draws the sRGB gamut on the gamut visualization.");
27 DEFINE_bool(adobeRGB, false, "Draws the Adobe RGB gamut on the gamut visualization.");
28 DEFINE_bool(sRGB_gamma, false, "Draws the sRGB gamma on all gamma output images.");
29 DEFINE_string(uncorrected, "", "A path to reencode the uncorrected input image.");
30 
31 static const char* kRGBChannelNames[3] = {
32     "Red  ", "Green", "Blue "
33 };
34 
35 static const SkColor kRGBChannelColors[3] = {
36     SkColorSetARGB(164, 255, 32, 32),
37     SkColorSetARGB(164, 32, 255, 32),
38     SkColorSetARGB(164, 32, 32, 255)
39 };
40 
dump_transfer_fn(SkGammaNamed gammaNamed)41 static void dump_transfer_fn(SkGammaNamed gammaNamed) {
42     switch (gammaNamed) {
43         case kSRGB_SkGammaNamed:
44             SkDebugf("Transfer Function: sRGB\n");
45             return;
46         case k2Dot2Curve_SkGammaNamed:
47             SkDebugf("Exponential Transfer Function: Exponent 2.2\n");
48             return;
49         case kLinear_SkGammaNamed:
50             SkDebugf("Transfer Function: Linear\n");
51             return;
52         default:
53             break;
54     }
55 
56 }
57 
dump_transfer_fn(const SkGammas & gammas)58 static void dump_transfer_fn(const SkGammas& gammas) {
59     SkASSERT(gammas.channels() == 3);
60     for (int i = 0; i < gammas.channels(); i++) {
61         if (gammas.isNamed(i)) {
62             switch (gammas.data(i).fNamed) {
63                 case kSRGB_SkGammaNamed:
64                     SkDebugf("%s Transfer Function: sRGB\n", kRGBChannelNames[i]);
65                     return;
66                 case k2Dot2Curve_SkGammaNamed:
67                     SkDebugf("%s Transfer Function: Exponent 2.2\n", kRGBChannelNames[i]);
68                     return;
69                 case kLinear_SkGammaNamed:
70                     SkDebugf("%s Transfer Function: Linear\n", kRGBChannelNames[i]);
71                     return;
72                 default:
73                     SkASSERT(false);
74                     continue;
75             }
76         } else if (gammas.isValue(i)) {
77             SkDebugf("%s Transfer Function: Exponent %.3f\n", kRGBChannelNames[i],
78                      gammas.data(i).fValue);
79         } else if (gammas.isParametric(i)) {
80             const SkColorSpaceTransferFn& fn = gammas.data(i).params(&gammas);
81             SkDebugf("%s Transfer Function: Parametric A = %.3f, B = %.3f, C = %.3f, D = %.3f, "
82                      "E = %.3f, F = %.3f, G = %.3f\n", kRGBChannelNames[i], fn.fA, fn.fB, fn.fC,
83                      fn.fD, fn.fE, fn.fF, fn.fG);
84         } else {
85             SkASSERT(gammas.isTable(i));
86             SkDebugf("%s Transfer Function: Table (%d entries)\n", kRGBChannelNames[i],
87                     gammas.data(i).fTable.fSize);
88         }
89     }
90 }
91 
parametric(const SkColorSpaceTransferFn & fn,float x)92 static inline float parametric(const SkColorSpaceTransferFn& fn, float x) {
93     return x >= fn.fD ? powf(fn.fA*x + fn.fB, fn.fG) + fn.fE
94                       : fn.fC*x + fn.fF;
95 }
96 
draw_transfer_fn(SkCanvas * canvas,SkGammaNamed gammaNamed,const SkGammas * gammas,SkColor color,int col)97 static void draw_transfer_fn(SkCanvas* canvas, SkGammaNamed gammaNamed, const SkGammas* gammas,
98                              SkColor color, int col) {
99     SkColorSpaceTransferFn fn[4];
100     struct TableInfo {
101         const float* fTable;
102         int          fSize;
103     };
104     TableInfo table[4];
105     bool isTable[4] = {false, false, false, false};
106     const int channels = gammas ? gammas->channels() : 1;
107     SkASSERT(channels <= 4);
108     if (kNonStandard_SkGammaNamed != gammaNamed) {
109         dump_transfer_fn(gammaNamed);
110         for (int i = 0; i < channels; ++i) {
111             named_to_parametric(&fn[i], gammaNamed);
112         }
113     } else {
114         SkASSERT(gammas);
115         dump_transfer_fn(*gammas);
116         for (int i = 0; i < channels; ++i) {
117             if (gammas->isTable(i)) {
118                 table[i].fTable = gammas->table(i);
119                 table[i].fSize = gammas->data(i).fTable.fSize;
120                 isTable[i] = true;
121             } else {
122                 switch (gammas->type(i)) {
123                     case SkGammas::Type::kNamed_Type:
124                         named_to_parametric(&fn[i], gammas->data(i).fNamed);
125                         break;
126                     case SkGammas::Type::kValue_Type:
127                         value_to_parametric(&fn[i], gammas->data(i).fValue);
128                         break;
129                     case SkGammas::Type::kParam_Type:
130                         fn[i] = gammas->params(i);
131                         break;
132                     default:
133                         SkASSERT(false);
134                 }
135             }
136         }
137     }
138     SkPaint paint;
139     paint.setStyle(SkPaint::kStroke_Style);
140     paint.setColor(color);
141     paint.setStrokeWidth(2.0f);
142     // note: gamma has positive values going up in this image so this origin is
143     //       the bottom left and we must subtract y instead of adding.
144     const float gap         = 16.0f;
145     const float cellWidth   = 500.0f;
146     const float cellHeight  = 500.0f;
147     const float gammaWidth  = cellWidth - 2 * gap;
148     const float gammaHeight = cellHeight - 2 * gap;
149     // gamma origin point
150     const float ox = gap + cellWidth * col;
151     const float oy = gap + gammaHeight;
152     for (int i = 0; i < channels; ++i) {
153         if (kNonStandard_SkGammaNamed == gammaNamed) {
154             paint.setColor(kRGBChannelColors[i]);
155         } else {
156             paint.setColor(color);
157         }
158         if (isTable[i]) {
159             auto tx = [&table,i](int index) {
160                 return index / (table[i].fSize - 1.0f);
161             };
162             for (int ti = 1; ti < table[i].fSize; ++ti) {
163                 canvas->drawLine(ox + gammaWidth * tx(ti - 1),
164                                  oy - gammaHeight * table[i].fTable[ti - 1],
165                                  ox + gammaWidth * tx(ti),
166                                  oy - gammaHeight * table[i].fTable[ti],
167                                  paint);
168             }
169         } else {
170             const float step = 0.01f;
171             float yPrev = parametric(fn[i], 0.0f);
172             for (float x = step; x <= 1.0f; x += step) {
173                 const float y = parametric(fn[i], x);
174                 canvas->drawLine(ox + gammaWidth * (x - step), oy - gammaHeight * yPrev,
175                                  ox + gammaWidth * x, oy - gammaHeight * y,
176                                  paint);
177                 yPrev = y;
178             }
179         }
180     }
181     paint.setColor(0xFF000000);
182     paint.setStrokeWidth(3.0f);
183     canvas->drawRect({ ox, oy - gammaHeight, ox + gammaWidth, oy }, paint);
184 }
185 
186 /**
187  *  Loads the triangular gamut as a set of three points.
188  */
load_gamut(SkPoint rgb[],const SkMatrix44 & xyz)189 static void load_gamut(SkPoint rgb[], const SkMatrix44& xyz) {
190     // rx = rX / (rX + rY + rZ)
191     // ry = rX / (rX + rY + rZ)
192     // gx, gy, bx, and gy are calulcated similarly.
193     float rSum = xyz.get(0, 0) + xyz.get(1, 0) + xyz.get(2, 0);
194     float gSum = xyz.get(0, 1) + xyz.get(1, 1) + xyz.get(2, 1);
195     float bSum = xyz.get(0, 2) + xyz.get(1, 2) + xyz.get(2, 2);
196     rgb[0].fX = xyz.get(0, 0) / rSum;
197     rgb[0].fY = xyz.get(1, 0) / rSum;
198     rgb[1].fX = xyz.get(0, 1) / gSum;
199     rgb[1].fY = xyz.get(1, 1) / gSum;
200     rgb[2].fX = xyz.get(0, 2) / bSum;
201     rgb[2].fY = xyz.get(1, 2) / bSum;
202 }
203 
204 /**
205  *  Calculates the area of the triangular gamut.
206  */
calculate_area(SkPoint abc[])207 static float calculate_area(SkPoint abc[]) {
208     SkPoint a = abc[0];
209     SkPoint b = abc[1];
210     SkPoint c = abc[2];
211     return 0.5f * SkTAbs(a.fX*b.fY + b.fX*c.fY - a.fX*c.fY - c.fX*b.fY - b.fX*a.fY);
212 }
213 
draw_gamut(SkCanvas * canvas,const SkMatrix44 & xyz,const char * name,SkColor color,bool label)214 static void draw_gamut(SkCanvas* canvas, const SkMatrix44& xyz, const char* name, SkColor color,
215                        bool label) {
216     // Report the XYZ values.
217     SkDebugf("%s\n", name);
218     SkDebugf("       R     G     B\n");
219     SkDebugf("X  %.3f %.3f %.3f\n", xyz.get(0, 0), xyz.get(0, 1), xyz.get(0, 2));
220     SkDebugf("Y  %.3f %.3f %.3f\n", xyz.get(1, 0), xyz.get(1, 1), xyz.get(1, 2));
221     SkDebugf("Z  %.3f %.3f %.3f\n", xyz.get(2, 0), xyz.get(2, 1), xyz.get(2, 2));
222 
223     // Calculate the points in the gamut from the XYZ values.
224     SkPoint rgb[4];
225     load_gamut(rgb, xyz);
226 
227     // Report the area of the gamut.
228     SkDebugf("Area of Gamut: %.3f\n\n", calculate_area(rgb));
229 
230     // Magic constants that help us place the gamut triangles in the appropriate position
231     // on the canvas.
232     const float xScale = 2071.25f;  // Num pixels from 0 to 1 in x
233     const float xOffset = 241.0f;   // Num pixels until start of x-axis
234     const float yScale = 2067.78f;  // Num pixels from 0 to 1 in y
235     const float yOffset = -144.78f; // Num pixels until start of y-axis
236                                     // (negative because y extends beyond image bounds)
237 
238     // Now transform the points so they can be drawn on our canvas.
239     // Note that y increases as we move down the canvas.
240     rgb[0].fX = xOffset + xScale * rgb[0].fX;
241     rgb[0].fY = yOffset + yScale * (1.0f - rgb[0].fY);
242     rgb[1].fX = xOffset + xScale * rgb[1].fX;
243     rgb[1].fY = yOffset + yScale * (1.0f - rgb[1].fY);
244     rgb[2].fX = xOffset + xScale * rgb[2].fX;
245     rgb[2].fY = yOffset + yScale * (1.0f - rgb[2].fY);
246 
247     // Repeat the first point to connect the polygon.
248     rgb[3] = rgb[0];
249     SkPaint paint;
250     paint.setColor(color);
251     paint.setStrokeWidth(6.0f);
252     paint.setTextSize(75.0f);
253     canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, rgb, paint);
254     if (label) {
255         canvas->drawText("R", 1, rgb[0].fX + 5.0f, rgb[0].fY + 75.0f, paint);
256         canvas->drawText("G", 1, rgb[1].fX + 5.0f, rgb[1].fY - 5.0f, paint);
257         canvas->drawText("B", 1, rgb[2].fX - 75.0f, rgb[2].fY - 5.0f, paint);
258     }
259 }
260 
main(int argc,char ** argv)261 int main(int argc, char** argv) {
262     SkCommandLineFlags::SetUsage(
263             "Usage: colorspaceinfo --input <path to input image or icc profile> "
264                                   "--gamma_output <path to output gamma image> "
265                                   "--gamut_output <path to output gamut image>"
266                                   "--sRGB <draw canonical sRGB gamut> "
267                                   "--adobeRGB <draw canonical Adobe RGB gamut> "
268                                   "--uncorrected <path to reencoded, uncorrected input image>\n"
269             "Description: Writes visualizations of the color space to the output image(s)  ."
270                          "Also, if a path is provided, writes uncorrected bytes to an unmarked "
271                          "png, for comparison with the input image.\n");
272     SkCommandLineFlags::Parse(argc, argv);
273     const char* input = FLAGS_input[0];
274     const char* gamut_output = FLAGS_gamut_output[0];
275     const char* gamma_output = FLAGS_gamma_output[0];
276     if (!input || !gamut_output || !gamma_output) {
277         SkCommandLineFlags::PrintUsage();
278         return -1;
279     }
280 
281     sk_sp<SkData> data(SkData::MakeFromFileName(input));
282     if (!data) {
283         SkDebugf("Cannot find input image.\n");
284         return -1;
285     }
286     std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(data));
287     sk_sp<SkColorSpace> colorSpace = nullptr;
288     const bool isImage = (codec != nullptr);
289     if (isImage) {
290         colorSpace = sk_ref_sp(codec->getInfo().colorSpace());
291     } else {
292         colorSpace = SkColorSpace::MakeICC(data->bytes(), data->size());
293     }
294 
295     if (!colorSpace) {
296         SkDebugf("Cannot create codec or icc profile from input file.\n");
297         return -1;
298     }
299 
300     // Load a graph of the CIE XYZ color gamut.
301     SkBitmap gamutCanvasBitmap;
302     if (!GetResourceAsBitmap("gamut.png", &gamutCanvasBitmap)) {
303         SkDebugf("Program failure.\n");
304         return -1;
305     }
306     SkCanvas gamutCanvas(gamutCanvasBitmap);
307 
308     SkBitmap gammaCanvasBitmap;
309     gammaCanvasBitmap.allocN32Pixels(500, 500);
310     SkCanvas gammaCanvas(gammaCanvasBitmap);
311 
312     // Draw the sRGB gamut if requested.
313     if (FLAGS_sRGB_gamut) {
314         sk_sp<SkColorSpace> sRGBSpace = SkColorSpace::MakeSRGB();
315         const SkMatrix44* mat = as_CSB(sRGBSpace)->toXYZD50();
316         SkASSERT(mat);
317         draw_gamut(&gamutCanvas, *mat, "sRGB", 0xFFFF9394, false);
318     }
319 
320     // Draw the Adobe RGB gamut if requested.
321     if (FLAGS_adobeRGB) {
322         sk_sp<SkColorSpace> adobeRGBSpace =
323                 SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kAdobeRGB_Named);
324         const SkMatrix44* mat = as_CSB(adobeRGBSpace)->toXYZD50();
325         SkASSERT(mat);
326         draw_gamut(&gamutCanvas, *mat, "Adobe RGB", 0xFF31a9e1, false);
327     }
328 
329     int gammaCol = 0;
330     if (SkColorSpace_Base::Type::kXYZ == as_CSB(colorSpace)->type()) {
331         const SkMatrix44* mat = as_CSB(colorSpace)->toXYZD50();
332         SkASSERT(mat);
333         auto xyz = static_cast<SkColorSpace_XYZ*>(colorSpace.get());
334         draw_gamut(&gamutCanvas, *mat, input, 0xFF000000, true);
335         if (FLAGS_sRGB_gamma) {
336             draw_transfer_fn(&gammaCanvas, kSRGB_SkGammaNamed, nullptr, 0xFFFF9394, gammaCol);
337         }
338         draw_transfer_fn(&gammaCanvas, xyz->gammaNamed(), xyz->gammas(), 0xFF000000, gammaCol++);
339     } else {
340         SkDebugf("Color space is defined using an A2B tag.  It cannot be represented by "
341                  "a transfer function and to D50 matrix.\n");
342         return -1;
343     }
344 
345     // marker to tell the web-tool the names of all images output
346     SkDebugf("=========\n");
347     auto saveCanvasBitmap = [](const SkBitmap& bitmap, const char *fname) {
348         // Finally, encode the result to the output file.
349         sk_sp<SkData> out = sk_tool_utils::EncodeImageToData(bitmap, SkEncodedImageFormat::kPNG,
350                                                              100);
351         if (!out) {
352             SkDebugf("Failed to encode %s output.\n", fname);
353             return false;
354         }
355         SkFILEWStream stream(fname);
356         if (!stream.write(out->data(), out->size())) {
357             SkDebugf("Failed to write %s output.\n", fname);
358             return false;
359         }
360         // record name of canvas
361         SkDebugf("%s\n", fname);
362         return true;
363     };
364 
365     // only XYZ images have a gamut visualization since the matrix in A2B is not
366     // a gamut adjustment from RGB->XYZ always (or ever)
367     if (SkColorSpace_Base::Type::kXYZ == as_CSB(colorSpace)->type() &&
368         !saveCanvasBitmap(gamutCanvasBitmap, gamut_output)) {
369         return -1;
370     }
371     if (gammaCol > 0 && !saveCanvasBitmap(gammaCanvasBitmap, gamma_output)) {
372         return -1;
373     }
374 
375     if (isImage) {
376         SkDebugf("%s\n", input);
377     }
378     // Also, if requested, decode and reencode the uncorrected input image.
379     if (!FLAGS_uncorrected.isEmpty() && isImage) {
380         SkBitmap bitmap;
381         int width = codec->getInfo().width();
382         int height = codec->getInfo().height();
383         bitmap.allocN32Pixels(width, height, kOpaque_SkAlphaType == codec->getInfo().alphaType());
384         SkImageInfo decodeInfo = SkImageInfo::MakeN32(width, height, kUnpremul_SkAlphaType);
385         if (SkCodec::kSuccess != codec->getPixels(decodeInfo, bitmap.getPixels(),
386                                                   bitmap.rowBytes())) {
387             SkDebugf("Could not decode input image.\n");
388             return -1;
389         }
390         sk_sp<SkData> out = sk_tool_utils::EncodeImageToData(bitmap, SkEncodedImageFormat::kPNG,
391                                                              100);
392         if (!out) {
393             SkDebugf("Failed to encode uncorrected image.\n");
394             return -1;
395         }
396         SkFILEWStream bitmapStream(FLAGS_uncorrected[0]);
397         if (!bitmapStream.write(out->data(), out->size())) {
398             SkDebugf("Failed to write uncorrected image output.\n");
399             return -1;
400         }
401         SkDebugf("%s\n", FLAGS_uncorrected[0]);
402     }
403 
404     return 0;
405 }
406