1 /*
2 * Copyright 2018 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 "include/core/SkCanvas.h"
9 #include "include/core/SkData.h"
10 #include "include/core/SkImage.h"
11 #include "include/core/SkStream.h"
12 #include "include/core/SkSurface.h"
13 #include "include/third_party/skcms/skcms.h"
14 #include "src/core/SkColorSpacePriv.h"
15 
write_png(const char * path,sk_sp<SkImage> img)16 static void write_png(const char* path, sk_sp<SkImage> img) {
17     sk_sp<SkData>  png = img->encodeToData();
18     SkFILEWStream(path).write(png->data(), png->size());
19 }
20 
main(int argc,char ** argv)21 int main(int argc, char** argv) {
22     const char* source_path = argc > 1 ? argv[1] : nullptr;
23     if (!source_path) {
24         SkDebugf("Please pass an image or profile to convert"
25                  " as the first argument to this program.\n");
26         return 1;
27     }
28 
29     const char* dst_profile_path = argc > 2 ? argv[2] : nullptr;
30     skcms_ICCProfile dst_profile = *skcms_sRGB_profile();
31     sk_sp<SkData> dst_blob;
32     if (dst_profile_path) {
33         dst_blob = SkData::MakeFromFileName(dst_profile_path);
34         if (!skcms_Parse(dst_blob->data(), dst_blob->size(), &dst_profile)) {
35             SkDebugf("Can't parse %s as an ICC profile.\n", dst_profile_path);
36             return 1;
37         }
38     }
39 
40     auto blob = SkData::MakeFromFileName(source_path);
41 
42     skcms_ICCProfile src_profile;
43     if (skcms_Parse(blob->data(), blob->size(), &src_profile)) {
44         // Transform white, black, primaries, and primary complements.
45         float src[] = {
46            0,0,0,
47            1,1,1,
48 
49            1,0,0,
50            0,1,0,
51            0,0,1,
52 
53            0,1,1,
54            1,0,1,
55            1,1,0,
56         };
57         float dst[24] = {0};
58 
59         if (!skcms_Transform(
60                     src, skcms_PixelFormat_RGB_fff, skcms_AlphaFormat_Unpremul, &src_profile,
61                     dst, skcms_PixelFormat_RGB_fff, skcms_AlphaFormat_Unpremul, &dst_profile,
62                     8)) {
63             SkDebugf("Cannot transform.\n");
64             return 1;
65         }
66         for (int i = 0; i < 8; i++) {
67             SkDebugf("(%g, %g, %g) --> (%+.4f, %+.4f, %+.4f)\n",
68                      src[3*i+0], src[3*i+1], src[3*i+2],
69                      dst[3*i+0], dst[3*i+1], dst[3*i+2]);
70         }
71         return 0;
72     }
73 
74     sk_sp<SkImage> image = SkImage::MakeFromEncoded(blob);
75     if (!image) {
76         SkDebugf("Couldn't decode %s as an SkImage or an ICC profile.\n", source_path);
77         return 1;
78     }
79 
80     image = image->makeRasterImage();
81     if (!image) {
82         SkDebugf("Converting to raster image failed.\n");
83         return 1;
84     }
85 
86     SkPixmap pixmap;
87     if (!image->peekPixels(&pixmap)) {
88         SkDebugf("We really should be able to peek raster pixels.\n");
89         return 1;
90     }
91 
92     sk_sp<SkColorSpace> dst_cs = SkColorSpace::Make(dst_profile);
93     if (!dst_cs) {
94         SkDebugf("We can't convert to this destination profile as-is. Coercing it.\n");
95         if (skcms_MakeUsableAsDestinationWithSingleCurve(&dst_profile)) {
96             dst_cs = SkColorSpace::Make(dst_profile);
97         }
98         if (!dst_cs) {
99             SkDebugf("We can't convert to this destination profile at all.\n");
100             return 1;
101         }
102     }
103 
104     { // transform with skcms
105         SkColorSpace* src_cs = image->colorSpace() ? image->colorSpace()
106                                                    : sk_srgb_singleton();
107         src_cs->toProfile(&src_profile);
108 
109         skcms_PixelFormat fmt;
110         switch (pixmap.colorType()) {
111             case kRGBA_8888_SkColorType: fmt = skcms_PixelFormat_RGBA_8888; break;
112             case kBGRA_8888_SkColorType: fmt = skcms_PixelFormat_BGRA_8888; break;
113             default:
114                 SkDebugf("color type %d not yet supported, imgcvt.cpp needs an update.\n",
115                          pixmap.colorType());
116                 return 1;
117         }
118 
119         if (pixmap.alphaType() == kUnpremul_SkAlphaType) {
120             SkDebugf("not premul, that's weird.\n");
121             return 1;
122         }
123         auto alpha = skcms_AlphaFormat_PremulAsEncoded;
124 
125         if (pixmap.rowBytes() != (size_t)pixmap.width() * pixmap.info().bytesPerPixel()) {
126             SkDebugf("not a tight pixmap, need a loop here\n");
127             return 1;
128         }
129 
130         if (!skcms_Transform(pixmap.addr(),          fmt,alpha, &src_profile,
131                              pixmap.writable_addr(), fmt,alpha, &dst_profile,
132                              pixmap.width() * pixmap.height())) {
133             SkDebugf("skcms_Transform() failed\n");
134             return 1;
135         }
136         pixmap.setColorSpace(dst_cs);
137 
138         write_png("transformed-skcms.png", SkImage::MakeRasterCopy(pixmap));
139     }
140 
141     { // transform with writePixels()
142         sk_sp<SkSurface> surface = SkSurface::MakeRaster(pixmap.info().makeColorSpace(dst_cs));
143         if (!surface) {
144             SkDebugf("couldn't create a surface\n");
145             return 1;
146         }
147 
148         surface->writePixels(pixmap, 0,0);
149 
150         write_png("transformed-writepixels.png", surface->makeImageSnapshot());
151     }
152 
153     { // transform by drawing
154         sk_sp<SkSurface> surface = SkSurface::MakeRaster(pixmap.info().makeColorSpace(dst_cs));
155         if (!surface) {
156             SkDebugf("couldn't create a surface\n");
157             return 1;
158         }
159 
160         surface->getCanvas()->drawImage(image, 0,0);
161 
162         write_png("transformed-draw.png", surface->makeImageSnapshot());
163     }
164 
165     return 0;
166 }
167