1/*
2 * Copyright 2015 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/SkSurface.h"
10#include "include/gpu/GrDirectContext.h"
11#include "src/gpu/GrCaps.h"
12#include "src/gpu/GrDirectContextPriv.h"
13#include "src/gpu/GrImageInfo.h"
14#include "src/gpu/SkGr.h"
15#include "src/gpu/SurfaceContext.h"
16#include "tests/Test.h"
17#include "tests/TestUtils.h"
18
19// using anonymous namespace because these functions are used as template params.
20namespace {
21/** convert 0..1 srgb value to 0..1 linear */
22float srgb_to_linear(float srgb) {
23    if (srgb <= 0.04045f) {
24        return srgb / 12.92f;
25    } else {
26        return powf((srgb + 0.055f) / 1.055f, 2.4f);
27    }
28}
29
30/** convert 0..1 linear value to 0..1 srgb */
31float linear_to_srgb(float linear) {
32    if (linear <= 0.0031308) {
33        return linear * 12.92f;
34    } else {
35        return 1.055f * powf(linear, 1.f / 2.4f) - 0.055f;
36    }
37}
38}  // namespace
39
40/** tests a conversion with an error tolerance */
41template <float (*CONVERT)(float)> static bool check_conversion(uint32_t input, uint32_t output,
42                                                                float error) {
43    // alpha should always be exactly preserved.
44    if ((input & 0xff000000) != (output & 0xff000000)) {
45        return false;
46    }
47
48    for (int c = 0; c < 3; ++c) {
49        uint8_t inputComponent = (uint8_t) ((input & (0xff << (c*8))) >> (c*8));
50        float lower = std::max(0.f, (float) inputComponent - error);
51        float upper = std::min(255.f, (float) inputComponent + error);
52        lower = CONVERT(lower / 255.f);
53        upper = CONVERT(upper / 255.f);
54        SkASSERT(lower >= 0.f && lower <= 255.f);
55        SkASSERT(upper >= 0.f && upper <= 255.f);
56        uint8_t outputComponent = (output & (0xff << (c*8))) >> (c*8);
57        if (outputComponent < SkScalarFloorToInt(lower * 255.f) ||
58            outputComponent > SkScalarCeilToInt(upper * 255.f)) {
59            return false;
60        }
61    }
62    return true;
63}
64
65/** tests a forward and backward conversion with an error tolerance */
66template <float (*FORWARD)(float), float (*BACKWARD)(float)>
67static bool check_double_conversion(uint32_t input, uint32_t output, float error) {
68    // alpha should always be exactly preserved.
69    if ((input & 0xff000000) != (output & 0xff000000)) {
70        return false;
71    }
72
73    for (int c = 0; c < 3; ++c) {
74        uint8_t inputComponent = (uint8_t) ((input & (0xff << (c*8))) >> (c*8));
75        float lower = std::max(0.f, (float) inputComponent - error);
76        float upper = std::min(255.f, (float) inputComponent + error);
77        lower = FORWARD(lower / 255.f);
78        upper = FORWARD(upper / 255.f);
79        SkASSERT(lower >= 0.f && lower <= 255.f);
80        SkASSERT(upper >= 0.f && upper <= 255.f);
81        uint8_t upperComponent = SkScalarCeilToInt(upper * 255.f);
82        uint8_t lowerComponent = SkScalarFloorToInt(lower * 255.f);
83        lower = std::max(0.f, (float) lowerComponent - error);
84        upper = std::min(255.f, (float) upperComponent + error);
85        lower = BACKWARD(lowerComponent / 255.f);
86        upper = BACKWARD(upperComponent / 255.f);
87        SkASSERT(lower >= 0.f && lower <= 255.f);
88        SkASSERT(upper >= 0.f && upper <= 255.f);
89        upperComponent = SkScalarCeilToInt(upper * 255.f);
90        lowerComponent = SkScalarFloorToInt(lower * 255.f);
91
92        uint8_t outputComponent = (output & (0xff << (c*8))) >> (c*8);
93        if (outputComponent < lowerComponent || outputComponent > upperComponent) {
94            return false;
95        }
96    }
97    return true;
98}
99
100static bool check_srgb_to_linear_conversion(uint32_t srgb, uint32_t linear, float error) {
101    return check_conversion<srgb_to_linear>(srgb, linear, error);
102}
103
104static bool check_linear_to_srgb_conversion(uint32_t linear, uint32_t srgb, float error) {
105    return check_conversion<linear_to_srgb>(linear, srgb, error);
106}
107
108static bool check_linear_to_srgb_to_linear_conversion(uint32_t input, uint32_t output, float error) {
109    return check_double_conversion<linear_to_srgb, srgb_to_linear>(input, output, error);
110}
111
112static bool check_srgb_to_linear_to_srgb_conversion(uint32_t input, uint32_t output, float error) {
113    return check_double_conversion<srgb_to_linear, linear_to_srgb>(input, output, error);
114}
115
116static bool check_no_conversion(uint32_t input, uint32_t output, float error) {
117    // This is a bit of a hack to check identity transformations that may lose precision.
118    return check_srgb_to_linear_to_srgb_conversion(input, output, error);
119}
120
121typedef bool (*CheckFn) (uint32_t orig, uint32_t actual, float error);
122
123void read_and_check_pixels(skiatest::Reporter* reporter,
124                           GrDirectContext* dContext,
125                           skgpu::SurfaceContext* sc,
126                           uint32_t* origData,
127                           const SkImageInfo& dstInfo, CheckFn checker, float error,
128                           const char* subtestName) {
129    auto [w, h] = dstInfo.dimensions();
130    GrPixmap readPM = GrPixmap::Allocate(dstInfo);
131    memset(readPM.addr(), 0, sizeof(uint32_t)*w*h);
132
133    if (!sc->readPixels(dContext, readPM, {0, 0})) {
134        ERRORF(reporter, "Could not read pixels for %s.", subtestName);
135        return;
136    }
137
138    for (int j = 0; j < h; ++j) {
139        for (int i = 0; i < w; ++i) {
140            uint32_t orig = origData[j * w + i];
141            uint32_t read = static_cast<uint32_t*>(readPM.addr())[j * w + i];
142
143            if (!checker(orig, read, error)) {
144                ERRORF(reporter, "Original 0x%08x, read back as 0x%08x in %s at %d, %d).", orig,
145                       read, subtestName, i, j);
146                return;
147            }
148        }
149    }
150}
151
152namespace {
153enum class Encoding {
154    kUntagged,
155    kLinear,
156    kSRGB,
157};
158}  // namespace
159
160static sk_sp<SkColorSpace> encoding_as_color_space(Encoding encoding) {
161    switch (encoding) {
162        case Encoding::kUntagged: return nullptr;
163        case Encoding::kLinear:   return SkColorSpace::MakeSRGBLinear();
164        case Encoding::kSRGB:     return SkColorSpace::MakeSRGB();
165    }
166    return nullptr;
167}
168
169static const char* encoding_as_str(Encoding encoding) {
170    switch (encoding) {
171        case Encoding::kUntagged: return "untagged";
172        case Encoding::kLinear:   return "linear";
173        case Encoding::kSRGB:     return "sRGB";
174    }
175    return nullptr;
176}
177
178static constexpr int kW = 255;
179static constexpr int kH = 255;
180
181static std::unique_ptr<uint32_t[]> make_data() {
182    std::unique_ptr<uint32_t[]> data(new uint32_t[kW * kH]);
183    for (int j = 0; j < kH; ++j) {
184        for (int i = 0; i < kW; ++i) {
185            data[j * kW + i] = (0xFF << 24) | (i << 16) | (i << 8) | i;
186        }
187    }
188    return data;
189}
190
191static std::unique_ptr<skgpu::SurfaceContext> make_surface_context(Encoding contextEncoding,
192                                                                   GrRecordingContext* rContext,
193                                                                   skiatest::Reporter* reporter) {
194    GrImageInfo info(GrColorType::kRGBA_8888,
195                     kPremul_SkAlphaType,
196                     encoding_as_color_space(contextEncoding),
197                     kW, kH);
198
199    auto sc = CreateSurfaceContext(rContext,
200                                   info,
201                                   SkBackingFit::kExact,
202                                   kBottomLeft_GrSurfaceOrigin,
203                                   GrRenderable::kYes);
204    if (!sc) {
205        ERRORF(reporter, "Could not create %s surface context.", encoding_as_str(contextEncoding));
206    }
207    return sc;
208}
209
210static void test_write_read(Encoding contextEncoding, Encoding writeEncoding, Encoding readEncoding,
211                            float error, CheckFn check, GrDirectContext* dContext,
212                            skiatest::Reporter* reporter) {
213    auto surfaceContext = make_surface_context(contextEncoding, dContext, reporter);
214    if (!surfaceContext) {
215        return;
216    }
217    auto writeII = SkImageInfo::Make(kW, kH, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
218                                     encoding_as_color_space(writeEncoding));
219    auto data = make_data();
220    GrCPixmap dataPM(writeII, data.get(), kW*sizeof(uint32_t));
221    if (!surfaceContext->writePixels(dContext, dataPM, {0, 0})) {
222        ERRORF(reporter, "Could not write %s to %s surface context.",
223               encoding_as_str(writeEncoding), encoding_as_str(contextEncoding));
224        return;
225    }
226
227    auto readII = SkImageInfo::Make(kW, kH, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
228                                    encoding_as_color_space(readEncoding));
229    SkString testName;
230    testName.printf("write %s data to a %s context and read as %s.", encoding_as_str(writeEncoding),
231                    encoding_as_str(contextEncoding), encoding_as_str(readEncoding));
232    read_and_check_pixels(reporter, dContext, surfaceContext.get(), data.get(), readII, check,
233                          error, testName.c_str());
234}
235
236// Test all combinations of writePixels/readPixels where the surface context/write source/read dst
237// are sRGB, linear, or untagged RGBA_8888.
238DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SRGBReadWritePixels, reporter, ctxInfo) {
239    auto context = ctxInfo.directContext();
240    if (!context->priv().caps()->getDefaultBackendFormat(GrColorType::kRGBA_8888_SRGB,
241                                                         GrRenderable::kNo).isValid()) {
242        return;
243    }
244    // We allow more error on GPUs with lower precision shader variables.
245    float error = context->priv().caps()->shaderCaps()->halfIs32Bits() ? 0.5f : 1.2f;
246    // For the all-sRGB case, we allow a small error only for devices that have
247    // precision variation because the sRGB data gets converted to linear and back in
248    // the shader.
249    float smallError = context->priv().caps()->shaderCaps()->halfIs32Bits() ? 0.0f : 1.f;
250
251    ///////////////////////////////////////////////////////////////////////////////////////////////
252    // Write sRGB data to a sRGB context - no conversion on the write.
253
254    // back to sRGB - no conversion.
255    test_write_read(Encoding::kSRGB, Encoding::kSRGB, Encoding::kSRGB, smallError,
256                    check_no_conversion, context, reporter);
257    // Reading back to untagged should be a pass through with no conversion.
258    test_write_read(Encoding::kSRGB, Encoding::kSRGB, Encoding::kUntagged, error,
259                    check_no_conversion, context, reporter);
260
261    // Converts back to linear
262    test_write_read(Encoding::kSRGB, Encoding::kSRGB, Encoding::kLinear, error,
263                    check_srgb_to_linear_conversion, context, reporter);
264
265    // Untagged source data should be interpreted as sRGB.
266    test_write_read(Encoding::kSRGB, Encoding::kUntagged, Encoding::kSRGB, smallError,
267                    check_no_conversion, context, reporter);
268
269    ///////////////////////////////////////////////////////////////////////////////////////////////
270    // Write linear data to a sRGB context. It gets converted to sRGB on write. The reads
271    // are all the same as the above cases where the original data was untagged.
272    test_write_read(Encoding::kSRGB, Encoding::kLinear, Encoding::kSRGB, error,
273                    check_linear_to_srgb_conversion, context, reporter);
274    // When the dst buffer is untagged there should be no conversion on the read.
275    test_write_read(Encoding::kSRGB, Encoding::kLinear, Encoding::kUntagged, error,
276                    check_linear_to_srgb_conversion, context, reporter);
277    test_write_read(Encoding::kSRGB, Encoding::kLinear, Encoding::kLinear, error,
278                    check_linear_to_srgb_to_linear_conversion, context, reporter);
279
280    ///////////////////////////////////////////////////////////////////////////////////////////////
281    // Write data to an untagged context. The write does no conversion no matter what encoding the
282    // src data has.
283    for (auto writeEncoding : {Encoding::kSRGB, Encoding::kUntagged, Encoding::kLinear}) {
284        // The read from untagged to sRGB also does no conversion.
285        test_write_read(Encoding::kUntagged, writeEncoding, Encoding::kSRGB, error,
286                        check_no_conversion, context, reporter);
287        // Reading untagged back as untagged should do no conversion.
288        test_write_read(Encoding::kUntagged, writeEncoding, Encoding::kUntagged, error,
289                        check_no_conversion, context, reporter);
290        // Reading untagged back as linear does convert (context is source, so treated as sRGB),
291        // dst is tagged.
292        test_write_read(Encoding::kUntagged, writeEncoding, Encoding::kLinear, error,
293                        check_srgb_to_linear_conversion, context, reporter);
294    }
295
296    ///////////////////////////////////////////////////////////////////////////////////////////////
297    // Write sRGB data to a linear context - converts to sRGB on the write.
298
299    // converts back to sRGB on read.
300    test_write_read(Encoding::kLinear, Encoding::kSRGB, Encoding::kSRGB, error,
301                    check_srgb_to_linear_to_srgb_conversion, context, reporter);
302    // Reading untagged data from linear currently does no conversion.
303    test_write_read(Encoding::kLinear, Encoding::kSRGB, Encoding::kUntagged, error,
304                    check_srgb_to_linear_conversion, context, reporter);
305    // Stays linear when read.
306    test_write_read(Encoding::kLinear, Encoding::kSRGB, Encoding::kLinear, error,
307                    check_srgb_to_linear_conversion, context, reporter);
308
309    // Untagged source data should be interpreted as sRGB.
310    test_write_read(Encoding::kLinear, Encoding::kUntagged, Encoding::kSRGB, error,
311                    check_srgb_to_linear_to_srgb_conversion, context, reporter);
312
313    ///////////////////////////////////////////////////////////////////////////////////////////////
314    // Write linear data to a linear context. Does no conversion.
315
316    // Reading to sRGB does a conversion.
317    test_write_read(Encoding::kLinear, Encoding::kLinear, Encoding::kSRGB, error,
318                    check_linear_to_srgb_conversion, context, reporter);
319    // Reading to untagged does no conversion.
320    test_write_read(Encoding::kLinear, Encoding::kLinear, Encoding::kUntagged, error,
321                    check_no_conversion, context, reporter);
322    // Stays linear when read.
323    test_write_read(Encoding::kLinear, Encoding::kLinear, Encoding::kLinear, error,
324                    check_no_conversion, context, reporter);
325}
326