blob: 9e9ed9631493245d8c2dae2cc2f0c6e628e137b8 [file] [log] [blame]
msarettaabe15e2016-05-13 07:32:59 -07001/*
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"
msarett7802c3d2016-09-28 11:15:27 -070013#include "SkColorSpace_Base.h"
msarettaabe15e2016-05-13 07:32:59 -070014#include "SkCommandLineFlags.h"
15#include "SkForceLinking.h"
16#include "SkImageEncoder.h"
17#include "SkMatrix44.h"
18#include "SkOSFile.h"
19
20__SK_FORCE_IMAGE_DECODER_LINKING;
21
msarett97205a42016-05-27 14:01:02 -070022DEFINE_string(input, "input.png", "A path to the input image.");
23DEFINE_string(output, "output.png", "A path to the output image.");
24DEFINE_bool(sRGB, false, "Draws the sRGB gamut.");
25DEFINE_bool(adobeRGB, false, "Draws the Adobe RGB gamut.");
26DEFINE_string(uncorrected, "", "A path to reencode the uncorrected input image.");
msarettaabe15e2016-05-13 07:32:59 -070027
28/**
29 * Loads the triangular gamut as a set of three points.
30 */
31static void load_gamut(SkPoint rgb[], const SkMatrix44& xyz) {
32 // rx = rX / (rX + rY + rZ)
33 // ry = rX / (rX + rY + rZ)
34 // gx, gy, bx, and gy are calulcated similarly.
brianosmande68d6c2016-09-09 10:36:17 -070035 float rSum = xyz.get(0, 0) + xyz.get(1, 0) + xyz.get(2, 0);
36 float gSum = xyz.get(0, 1) + xyz.get(1, 1) + xyz.get(2, 1);
37 float bSum = xyz.get(0, 2) + xyz.get(1, 2) + xyz.get(2, 2);
msarettaabe15e2016-05-13 07:32:59 -070038 rgb[0].fX = xyz.get(0, 0) / rSum;
brianosmande68d6c2016-09-09 10:36:17 -070039 rgb[0].fY = xyz.get(1, 0) / rSum;
40 rgb[1].fX = xyz.get(0, 1) / gSum;
msarettaabe15e2016-05-13 07:32:59 -070041 rgb[1].fY = xyz.get(1, 1) / gSum;
brianosmande68d6c2016-09-09 10:36:17 -070042 rgb[2].fX = xyz.get(0, 2) / bSum;
43 rgb[2].fY = xyz.get(1, 2) / bSum;
msarettaabe15e2016-05-13 07:32:59 -070044}
45
46/**
47 * Calculates the area of the triangular gamut.
48 */
msarett97205a42016-05-27 14:01:02 -070049static float calculate_area(SkPoint abc[]) {
msarettaabe15e2016-05-13 07:32:59 -070050 SkPoint a = abc[0];
51 SkPoint b = abc[1];
52 SkPoint c = abc[2];
53 return 0.5f * SkTAbs(a.fX*b.fY + b.fX*c.fY - a.fX*c.fY - c.fX*b.fY - b.fX*a.fY);
54}
55
msarett97205a42016-05-27 14:01:02 -070056static void draw_gamut(SkCanvas* canvas, const SkMatrix44& xyz, const char* name, SkColor color,
57 bool label) {
58 // Report the XYZ values.
59 SkDebugf("%s\n", name);
brianosmande68d6c2016-09-09 10:36:17 -070060 SkDebugf(" R G B\n");
61 SkDebugf("X %.3f %.3f %.3f\n", xyz.get(0, 0), xyz.get(0, 1), xyz.get(0, 2));
62 SkDebugf("Y %.3f %.3f %.3f\n", xyz.get(1, 0), xyz.get(1, 1), xyz.get(1, 2));
63 SkDebugf("Z %.3f %.3f %.3f\n", xyz.get(2, 0), xyz.get(2, 1), xyz.get(2, 2));
msarett97205a42016-05-27 14:01:02 -070064
65 // Calculate the points in the gamut from the XYZ values.
66 SkPoint rgb[4];
67 load_gamut(rgb, xyz);
68
69 // Report the area of the gamut.
70 SkDebugf("Area of Gamut: %.3f\n\n", calculate_area(rgb));
71
72 // Magic constants that help us place the gamut triangles in the appropriate position
73 // on the canvas.
74 const float xScale = 2071.25f; // Num pixels from 0 to 1 in x
75 const float xOffset = 241.0f; // Num pixels until start of x-axis
76 const float yScale = 2067.78f; // Num pixels from 0 to 1 in y
77 const float yOffset = -144.78f; // Num pixels until start of y-axis
78 // (negative because y extends beyond image bounds)
79
80 // Now transform the points so they can be drawn on our canvas.
81 // Note that y increases as we move down the canvas.
82 rgb[0].fX = xOffset + xScale * rgb[0].fX;
83 rgb[0].fY = yOffset + yScale * (1.0f - rgb[0].fY);
84 rgb[1].fX = xOffset + xScale * rgb[1].fX;
85 rgb[1].fY = yOffset + yScale * (1.0f - rgb[1].fY);
86 rgb[2].fX = xOffset + xScale * rgb[2].fX;
87 rgb[2].fY = yOffset + yScale * (1.0f - rgb[2].fY);
88
89 // Repeat the first point to connect the polygon.
90 rgb[3] = rgb[0];
91 SkPaint paint;
92 paint.setColor(color);
93 paint.setStrokeWidth(6.0f);
94 paint.setTextSize(75.0f);
95 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, rgb, paint);
96 if (label) {
97 canvas->drawText("R", 1, rgb[0].fX + 5.0f, rgb[0].fY + 75.0f, paint);
98 canvas->drawText("G", 1, rgb[1].fX + 5.0f, rgb[1].fY - 5.0f, paint);
99 canvas->drawText("B", 1, rgb[2].fX - 75.0f, rgb[2].fY - 5.0f, paint);
100 }
101}
102
msarettaabe15e2016-05-13 07:32:59 -0700103int main(int argc, char** argv) {
104 SkCommandLineFlags::SetUsage(
msarett97205a42016-05-27 14:01:02 -0700105 "Usage: visualize_color_gamut --input <path to input image> "
106 "--output <path to output image> "
107 "--sRGB <draw canonical sRGB gamut> "
108 "--adobeRGB <draw canonical Adobe RGB gamut> "
109 "--uncorrected <path to reencoded, uncorrected "
110 " input image>\n"
111 "Description: Writes a visualization of the color gamut to the output image ."
112 "Also, if a path is provided, writes uncorrected bytes to an unmarked "
113 "png, for comparison with the input image.\n");
msarettaabe15e2016-05-13 07:32:59 -0700114 SkCommandLineFlags::Parse(argc, argv);
115 const char* input = FLAGS_input[0];
116 const char* output = FLAGS_output[0];
117 if (!input || !output) {
118 SkCommandLineFlags::PrintUsage();
119 return -1;
120 }
121
bungeman38d909e2016-08-02 14:40:46 -0700122 sk_sp<SkData> data(SkData::MakeFromFileName(input));
msarettaabe15e2016-05-13 07:32:59 -0700123 if (!data) {
124 SkDebugf("Cannot find input image.\n");
125 return -1;
126 }
reed42943c82016-09-12 12:01:44 -0700127 SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(data));
msarettaabe15e2016-05-13 07:32:59 -0700128 if (!codec) {
129 SkDebugf("Invalid input image.\n");
130 return -1;
131 }
132
133 // Load a graph of the CIE XYZ color gamut.
msarett97205a42016-05-27 14:01:02 -0700134 SkBitmap gamut;
135 if (!GetResourceAsBitmap("gamut.png", &gamut)) {
msarettaabe15e2016-05-13 07:32:59 -0700136 SkDebugf("Program failure.\n");
137 return -1;
138 }
msarett97205a42016-05-27 14:01:02 -0700139 SkCanvas canvas(gamut);
msarettaabe15e2016-05-13 07:32:59 -0700140
msarett97205a42016-05-27 14:01:02 -0700141 // Draw the sRGB gamut if requested.
142 if (FLAGS_sRGB) {
Brian Osman526972e2016-10-24 09:24:02 -0400143 sk_sp<SkColorSpace> sRGBSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
raftias94888332016-10-18 10:02:51 -0700144 const SkMatrix44* mat = as_CSB(sRGBSpace)->toXYZD50();
145 SkASSERT(mat);
146 draw_gamut(&canvas, *mat, "sRGB", 0xFFFF9394, false);
msarett97205a42016-05-27 14:01:02 -0700147 }
148
149 // Draw the Adobe RGB gamut if requested.
150 if (FLAGS_adobeRGB) {
Brian Osman526972e2016-10-24 09:24:02 -0400151 sk_sp<SkColorSpace> adobeRGBSpace = SkColorSpace::MakeNamed(SkColorSpace::kAdobeRGB_Named);
raftias94888332016-10-18 10:02:51 -0700152 const SkMatrix44* mat = as_CSB(adobeRGBSpace)->toXYZD50();
153 SkASSERT(mat);
154 draw_gamut(&canvas, *mat, "Adobe RGB", 0xFF31a9e1, false);
msarett97205a42016-05-27 14:01:02 -0700155 }
156
157 // Draw gamut for the input image.
msarett530c8442016-07-21 11:57:49 -0700158 sk_sp<SkColorSpace> colorSpace = sk_ref_sp(codec->getInfo().colorSpace());
msarettaabe15e2016-05-13 07:32:59 -0700159 if (!colorSpace) {
160 SkDebugf("Image had no embedded color space information. Defaulting to sRGB.\n");
Brian Osman526972e2016-10-24 09:24:02 -0400161 colorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named);
msarettaabe15e2016-05-13 07:32:59 -0700162 }
raftias94888332016-10-18 10:02:51 -0700163 const SkMatrix44* mat = as_CSB(colorSpace)->toXYZD50();
164 SkASSERT(mat);
165 draw_gamut(&canvas, *mat, input, 0xFF000000, true);
msarettaabe15e2016-05-13 07:32:59 -0700166
msarett97205a42016-05-27 14:01:02 -0700167 // Finally, encode the result to the output file.
bungemanffae30d2016-08-03 13:32:32 -0700168 sk_sp<SkData> out(SkImageEncoder::EncodeData(gamut, SkImageEncoder::kPNG_Type, 100));
msarettaabe15e2016-05-13 07:32:59 -0700169 if (!out) {
msarett97205a42016-05-27 14:01:02 -0700170 SkDebugf("Failed to encode gamut output.\n");
msarettaabe15e2016-05-13 07:32:59 -0700171 return -1;
172 }
173 SkFILEWStream stream(output);
174 bool result = stream.write(out->data(), out->size());
175 if (!result) {
msarett97205a42016-05-27 14:01:02 -0700176 SkDebugf("Failed to write gamut output.\n");
msarettaabe15e2016-05-13 07:32:59 -0700177 return -1;
178 }
179
msarett97205a42016-05-27 14:01:02 -0700180 // Also, if requested, decode and reencode the uncorrected input image.
181 if (!FLAGS_uncorrected.isEmpty()) {
182 SkBitmap bitmap;
183 int width = codec->getInfo().width();
184 int height = codec->getInfo().height();
185 SkAlphaType alphaType = codec->getInfo().alphaType();
186 bitmap.allocN32Pixels(width, height, kOpaque_SkAlphaType == alphaType);
187 SkImageInfo decodeInfo = SkImageInfo::MakeN32(width, height, alphaType);
188 if (SkCodec::kSuccess != codec->getPixels(decodeInfo, bitmap.getPixels(),
189 bitmap.rowBytes())) {
190 SkDebugf("Could not decode input image.\n");
191 return -1;
192 }
193 out.reset(SkImageEncoder::EncodeData(bitmap, SkImageEncoder::kPNG_Type, 100));
194 if (!out) {
195 SkDebugf("Failed to encode uncorrected image.\n");
196 return -1;
197 }
198 SkFILEWStream bitmapStream(FLAGS_uncorrected[0]);
199 result = bitmapStream.write(out->data(), out->size());
200 if (!result) {
201 SkDebugf("Failed to write uncorrected image output.\n");
202 return -1;
203 }
204 }
205
msarettaabe15e2016-05-13 07:32:59 -0700206 return 0;
207}