blob: 9a1754e7492d23ee24345f323f7d027956da6aae [file] [log] [blame]
Yuri Wiitala0f308932018-09-13 19:45:221// Copyright 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/viz/common/gl_scaler_test_util.h"
6
7#include <algorithm>
8#include <cmath>
9
Yuri Wiitala8bac8fb2018-10-30 05:36:5810#include "base/files/file_path.h"
Yuri Wiitala0f308932018-09-13 19:45:2211#include "base/logging.h"
Hans Wennborg5bafbb92020-06-18 09:13:5712#include "base/notreached.h"
Yuri Wiitala8bac8fb2018-10-30 05:36:5813#include "base/path_service.h"
14#include "cc/test/pixel_test_utils.h"
15#include "components/viz/test/paths.h"
Yuri Wiitala0f308932018-09-13 19:45:2216#include "third_party/skia/include/core/SkColor.h"
17#include "third_party/skia/include/core/SkImageInfo.h"
18#include "ui/gfx/geometry/rect.h"
19
20namespace viz {
21
22using ColorBar = GLScalerTestUtil::ColorBar;
23
24// static
25SkBitmap GLScalerTestUtil::AllocateRGBABitmap(const gfx::Size& size) {
26 SkBitmap bitmap;
27 bitmap.allocPixels(SkImageInfo::Make(size.width(), size.height(),
28 kRGBA_8888_SkColorType,
29 kUnpremul_SkAlphaType));
30 return bitmap;
31}
32
33// static
34uint8_t GLScalerTestUtil::ToClamped255(float value) {
35 value = std::fma(value, 255.0f, 0.5f /* rounding */);
36 return base::saturated_cast<uint8_t>(value);
37}
38
39// static
40std::vector<ColorBar> GLScalerTestUtil::GetScaledSMPTEColorBars(
41 const gfx::Size& size) {
42 std::vector<ColorBar> rects;
43 const SkScalar scale_x =
44 static_cast<SkScalar>(size.width()) / kSMPTEFullSize.width();
45 const SkScalar scale_y =
46 static_cast<SkScalar>(size.height()) / kSMPTEFullSize.height();
47 for (const auto& cr : kSMPTEColorBars) {
48 const SkRect rect =
49 SkRect{cr.rect.fLeft * scale_x, cr.rect.fTop * scale_y,
50 cr.rect.fRight * scale_x, cr.rect.fBottom * scale_y};
51 rects.push_back(ColorBar{rect.round(), cr.color});
52 }
53 return rects;
54}
55
56// static
57SkBitmap GLScalerTestUtil::CreateSMPTETestImage(const gfx::Size& size) {
58 SkBitmap result = AllocateRGBABitmap(size);
59
60 // Set all pixels to a color that should not exist in the result. Later, a
61 // sanity-check will ensure all pixels have been overwritten.
62 constexpr SkColor kDeadColor = SkColorSetARGB(0xde, 0xad, 0xbe, 0xef);
63 result.eraseColor(kDeadColor);
64
65 // Set the pixels corresponding to each color bar.
66 for (const auto& cr : GetScaledSMPTEColorBars(size)) {
67 result.erase(cr.color, cr.rect);
68 }
69
70 // Validate that every pixel in the result bitmap has been touched by one of
71 // the color bars.
72 for (int y = 0; y < result.height(); ++y) {
73 for (int x = 0; x < result.width(); ++x) {
74 if (result.getColor(x, y) == kDeadColor) {
75 NOTREACHED() << "TEST BUG: Error creating SMPTE test image. Bad size ("
76 << size.ToString() << ")?";
77 return result;
78 }
79 }
80 }
81
82 return result;
83}
84
85// static
86bool GLScalerTestUtil::LooksLikeSMPTETestImage(const SkBitmap& image,
87 const gfx::Size& src_size,
88 const gfx::Rect& src_rect,
89 int fuzzy_pixels,
90 int* max_color_diff) {
91 if (image.width() <= 0 || image.height() <= 0) {
92 return false;
93 }
94
95 const SkScalar offset_x = static_cast<SkScalar>(src_rect.x());
96 const SkScalar offset_y = static_cast<SkScalar>(src_rect.y());
97 const SkScalar scale_x =
98 static_cast<SkScalar>(image.width()) / src_rect.width();
99 const SkScalar scale_y =
100 static_cast<SkScalar>(image.height()) / src_rect.height();
101 int measured_max_diff = 0;
102 for (const auto& cr : GetScaledSMPTEColorBars(src_size)) {
103 const SkIRect offset_rect = cr.rect.makeOffset(-offset_x, -offset_y);
104 const SkIRect rect =
105 SkRect{offset_rect.fLeft * scale_x, offset_rect.fTop * scale_y,
106 offset_rect.fRight * scale_x, offset_rect.fBottom * scale_y}
107 .round()
108 .makeInset(fuzzy_pixels, fuzzy_pixels);
109 for (int y = std::max(0, rect.fTop),
110 y_end = std::min(image.height(), rect.fBottom);
111 y < y_end; ++y) {
112 for (int x = std::max(0, rect.fLeft),
113 x_end = std::min(image.width(), rect.fRight);
114 x < x_end; ++x) {
115 const SkColor actual = image.getColor(x, y);
116 measured_max_diff =
117 std::max({measured_max_diff,
118 std::abs(static_cast<int>(SkColorGetR(cr.color)) -
119 static_cast<int>(SkColorGetR(actual))),
120 std::abs(static_cast<int>(SkColorGetG(cr.color)) -
121 static_cast<int>(SkColorGetG(actual))),
122 std::abs(static_cast<int>(SkColorGetB(cr.color)) -
123 static_cast<int>(SkColorGetB(actual))),
124 std::abs(static_cast<int>(SkColorGetA(cr.color)) -
125 static_cast<int>(SkColorGetA(actual)))});
126 }
127 }
128 }
129
130 if (max_color_diff) {
131 const int threshold = *max_color_diff;
132 *max_color_diff = measured_max_diff;
133 return measured_max_diff <= threshold;
134 }
135 return measured_max_diff == 0;
136}
137
138// static
139SkBitmap GLScalerTestUtil::CreateCyclicalTestImage(
140 const gfx::Size& size,
141 CyclicalPattern pattern,
142 const std::vector<SkColor>& cycle,
143 size_t rotation) {
144 CHECK(!cycle.empty());
145
146 // Map SkColors to RGBA data. Also, applies the cycle |rotation| to simplify
147 // the rest of the code below.
148 std::vector<uint32_t> cycle_as_rgba(cycle.size());
149 for (size_t i = 0; i < cycle.size(); ++i) {
150 const SkColor color = cycle[(i + rotation) % cycle.size()];
151 cycle_as_rgba[i] = ((SkColorGetR(color) << kRedShift) |
152 (SkColorGetG(color) << kGreenShift) |
153 (SkColorGetB(color) << kBlueShift) |
154 (SkColorGetA(color) << kAlphaShift));
155 }
156
157 SkBitmap result = AllocateRGBABitmap(size);
158 switch (pattern) {
159 case HORIZONTAL_STRIPES:
160 for (int y = 0; y < size.height(); ++y) {
161 uint32_t* const pixels = result.getAddr32(0, y);
162 const uint32_t stripe_rgba = cycle_as_rgba[y % cycle_as_rgba.size()];
163 for (int x = 0; x < size.width(); ++x) {
164 pixels[x] = stripe_rgba;
165 }
166 }
167 break;
168
169 case VERTICAL_STRIPES:
170 for (int y = 0; y < size.height(); ++y) {
171 uint32_t* const pixels = result.getAddr32(0, y);
172 for (int x = 0; x < size.width(); ++x) {
173 pixels[x] = cycle_as_rgba[x % cycle_as_rgba.size()];
174 }
175 }
176 break;
177
178 case STAGGERED:
179 for (int y = 0; y < size.height(); ++y) {
180 uint32_t* const pixels = result.getAddr32(0, y);
181 for (int x = 0; x < size.width(); ++x) {
182 pixels[x] = cycle_as_rgba[(x + y) % cycle_as_rgba.size()];
183 }
184 }
185 break;
186 }
187
188 return result;
189}
190
191// static
192gfx::ColorSpace GLScalerTestUtil::DefaultRGBColorSpace() {
193 return gfx::ColorSpace::CreateSRGB();
194}
195
196// static
197gfx::ColorSpace GLScalerTestUtil::DefaultYUVColorSpace() {
198 return gfx::ColorSpace::CreateREC709();
199}
200
201// static
202void GLScalerTestUtil::ConvertBitmapToYUV(SkBitmap* image) {
203 const auto transform = gfx::ColorTransform::NewColorTransform(
204 DefaultRGBColorSpace(), DefaultYUVColorSpace(),
205 gfx::ColorTransform::Intent::INTENT_ABSOLUTE);
206
207 // Loop, transforming one row of pixels at a time.
208 std::vector<gfx::ColorTransform::TriStim> stims(image->width());
209 for (int y = 0; y < image->height(); ++y) {
210 uint32_t* const pixels = image->getAddr32(0, y);
211 for (int x = 0; x < image->width(); ++x) {
212 stims[x].set_x(((pixels[x] >> kRedShift) & 0xff) / 255.0f);
213 stims[x].set_y(((pixels[x] >> kGreenShift) & 0xff) / 255.0f);
214 stims[x].set_z(((pixels[x] >> kBlueShift) & 0xff) / 255.0f);
215 }
216 transform->Transform(stims.data(), stims.size());
217 for (int x = 0; x < image->width(); ++x) {
218 pixels[x] = ((ToClamped255(stims[x].x()) << kRedShift) |
219 (ToClamped255(stims[x].y()) << kGreenShift) |
220 (ToClamped255(stims[x].z()) << kBlueShift) |
221 (((pixels[x] >> kAlphaShift) & 0xff) << kAlphaShift));
222 }
223 }
224}
225
226// static
227void GLScalerTestUtil::SwizzleBitmap(SkBitmap* image) {
228 for (int y = 0; y < image->height(); ++y) {
229 uint32_t* const pixels = image->getAddr32(0, y);
230 for (int x = 0; x < image->width(); ++x) {
231 pixels[x] = ((((pixels[x] >> kBlueShift) & 0xff) << kRedShift) |
232 (((pixels[x] >> kGreenShift) & 0xff) << kGreenShift) |
233 (((pixels[x] >> kRedShift) & 0xff) << kBlueShift) |
234 (((pixels[x] >> kAlphaShift) & 0xff) << kAlphaShift));
235 }
236 }
237}
238
239// static
240SkBitmap GLScalerTestUtil::CreatePackedPlanarBitmap(const SkBitmap& source,
241 int channel) {
242 CHECK_EQ(source.width() % 4, 0);
243 SkBitmap result =
244 AllocateRGBABitmap(gfx::Size(source.width() / 4, source.height()));
245
246 constexpr int kShiftForChannel[4] = {kRedShift, kGreenShift, kBlueShift,
247 kAlphaShift};
248 const int shift = kShiftForChannel[channel];
249 for (int y = 0; y < result.height(); ++y) {
250 const uint32_t* const src = source.getAddr32(0, y);
251 uint32_t* const dst = result.getAddr32(0, y);
252 for (int x = 0; x < result.width(); ++x) {
253 // (src[0..3]) (dst)
254 // RGBA RGBA RGBA RGBA --> RRRR (if channel is 0)
255 dst[x] = ((((src[x * 4 + 0] >> shift) & 0xff) << kRedShift) |
256 (((src[x * 4 + 1] >> shift) & 0xff) << kGreenShift) |
257 (((src[x * 4 + 2] >> shift) & 0xff) << kBlueShift) |
258 (((src[x * 4 + 3] >> shift) & 0xff) << kAlphaShift));
259 }
260 }
261 return result;
262}
263
Yuri Wiitalaf26763f2018-10-30 03:06:46264// static
265void GLScalerTestUtil::UnpackPlanarBitmap(const SkBitmap& plane,
266 int channel,
267 SkBitmap* out) {
268 // The heuristic below auto-adapts to subsampled plane sizes. However, there
269 // are two cricital requirements: 1) |plane| cannot be empty; 2) |plane| must
270 // have a size that cleanly unpacks to |out|'s size.
271 CHECK_GT(plane.width(), 0);
272 CHECK_GT(plane.height(), 0);
273 const int col_sampling_ratio = out->width() / plane.width();
274 CHECK_EQ(out->width() % plane.width(), 0);
275 CHECK_GT(col_sampling_ratio, 0);
276 const int row_sampling_ratio = out->height() / plane.height();
277 CHECK_EQ(out->height() % plane.height(), 0);
278 CHECK_GT(row_sampling_ratio, 0);
279 const int ch_sampling_ratio = col_sampling_ratio / 4;
280 CHECK_GT(ch_sampling_ratio, 0);
281
282 // These determine which single byte in each of |out|'s uint32_t-valued pixels
283 // will be modified.
284 constexpr int kShiftForChannel[4] = {kRedShift, kGreenShift, kBlueShift,
285 kAlphaShift};
286 const int output_shift = kShiftForChannel[channel];
287 const uint32_t output_retain_mask = ~(UINT32_C(0xff) << output_shift);
288
289 // Iterate over the pixels of |out|, sampling each of the 4 components of each
290 // of |plane|'s pixels.
291 for (int y = 0; y < out->height(); ++y) {
292 const uint32_t* const src = plane.getAddr32(0, y / row_sampling_ratio);
293 uint32_t* const dst = out->getAddr32(0, y);
294 for (int x = 0; x < out->width(); ++x) {
295 // Zero-out the existing byte (e.g., if channel==1, then "RGBA" → "R0BA").
296 dst[x] &= output_retain_mask;
297
298 // From |src|, grab one of "XYZW". Then, copy it to the target byte in
299 // |dst| (e.g., if x_src_ch=3, then grab "W" from |src|, and |dst| changes
300 // from "R0BA" to "RWBA").
301 const int x_src = x / col_sampling_ratio;
302 const int x_src_ch = (x / ch_sampling_ratio) % 4;
303 dst[x] |= ((src[x_src] >> kShiftForChannel[x_src_ch]) & 0xff)
304 << output_shift;
305 }
306 }
307}
308
309// static
310SkBitmap GLScalerTestUtil::CreateVerticallyFlippedBitmap(
311 const SkBitmap& source) {
312 SkBitmap bitmap;
313 bitmap.allocPixels(source.info());
314 CHECK_EQ(bitmap.rowBytes(), source.rowBytes());
315 for (int y = 0; y < bitmap.height(); ++y) {
316 const int src_y = bitmap.height() - y - 1;
317 memcpy(bitmap.getAddr32(0, y), source.getAddr32(0, src_y),
318 bitmap.rowBytes());
319 }
320 return bitmap;
321}
322
Yuri Wiitala8bac8fb2018-10-30 05:36:58323// static
324SkBitmap GLScalerTestUtil::LoadPNGTestImage(const std::string& basename) {
325 base::FilePath test_dir;
326 if (!base::PathService::Get(Paths::DIR_TEST_DATA, &test_dir)) {
327 LOG(ERROR) << "Unable to get Paths::DIR_TEST_DATA from base::PathService.";
328 return SkBitmap();
329 }
330 const auto source_file = test_dir.AppendASCII(basename);
331 SkBitmap as_n32;
332 if (!cc::ReadPNGFile(source_file, &as_n32)) {
333 return SkBitmap();
334 }
335 SkBitmap as_rgba =
336 AllocateRGBABitmap(gfx::Size(as_n32.width(), as_n32.height()));
337 if (!as_n32.readPixels(SkPixmap(as_rgba.info(), as_rgba.getAddr(0, 0),
338 as_rgba.rowBytes()))) {
339 return SkBitmap();
340 }
341 return as_rgba;
342}
343
Yuri Wiitala0f308932018-09-13 19:45:22344// The area and color of the bars in a 1920x1080 HD SMPTE color bars test image
345// (https://commons.wikimedia.org/wiki/File:SMPTE_Color_Bars_16x9.svg). The gray
346// linear gradient bar is defined as half solid 0-level black and half solid
347// full-intensity white).
348const ColorBar GLScalerTestUtil::kSMPTEColorBars[30] = {
349 {{0, 0, 240, 630}, SkColorSetRGB(0x66, 0x66, 0x66)},
350 {{240, 0, 445, 630}, SkColorSetRGB(0xbf, 0xbf, 0xbf)},
351 {{445, 0, 651, 630}, SkColorSetRGB(0xbf, 0xbf, 0x00)},
352 {{651, 0, 857, 630}, SkColorSetRGB(0x00, 0xbf, 0xbf)},
353 {{857, 0, 1063, 630}, SkColorSetRGB(0x00, 0xbf, 0x00)},
354 {{1063, 0, 1269, 630}, SkColorSetRGB(0xbf, 0x00, 0xbf)},
355 {{1269, 0, 1475, 630}, SkColorSetRGB(0xbf, 0x00, 0x00)},
356 {{1475, 0, 1680, 630}, SkColorSetRGB(0x00, 0x00, 0xbf)},
357 {{1680, 0, 1920, 630}, SkColorSetRGB(0x66, 0x66, 0x66)},
358 {{0, 630, 240, 720}, SkColorSetRGB(0x00, 0xff, 0xff)},
359 {{240, 630, 445, 720}, SkColorSetRGB(0x00, 0x21, 0x4c)},
360 {{445, 630, 1680, 720}, SkColorSetRGB(0xbf, 0xbf, 0xbf)},
361 {{1680, 630, 1920, 720}, SkColorSetRGB(0x00, 0x00, 0xff)},
362 {{0, 720, 240, 810}, SkColorSetRGB(0xff, 0xff, 0x00)},
363 {{240, 720, 445, 810}, SkColorSetRGB(0x32, 0x00, 0x6a)},
364 {{445, 720, 1063, 810}, SkColorSetRGB(0x00, 0x00, 0x00)},
365 {{1063, 720, 1680, 810}, SkColorSetRGB(0xff, 0xff, 0xff)},
366 {{1680, 720, 1920, 810}, SkColorSetRGB(0xff, 0x00, 0x00)},
367 {{0, 810, 240, 1080}, SkColorSetRGB(0x26, 0x26, 0x26)},
368 {{240, 810, 549, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
369 {{549, 810, 960, 1080}, SkColorSetRGB(0xff, 0xff, 0xff)},
370 {{960, 810, 1131, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
371 {{1131, 810, 1200, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
372 {{1200, 810, 1268, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
373 {{1268, 810, 1337, 1080}, SkColorSetRGB(0x05, 0x05, 0x05)},
374 {{1337, 810, 1405, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
375 {{1405, 810, 1474, 1080}, SkColorSetRGB(0x0a, 0x0a, 0x0a)},
376 {{1474, 810, 1680, 1080}, SkColorSetRGB(0x00, 0x00, 0x00)},
377 {{1680, 810, 1920, 1080}, SkColorSetRGB(0x26, 0x26, 0x26)},
378};
379
380constexpr gfx::Size GLScalerTestUtil::kSMPTEFullSize;
381
382GLScalerTestTextureHelper::GLScalerTestTextureHelper(
383 gpu::gles2::GLES2Interface* gl)
384 : gl_(gl) {
385 CHECK(gl_);
386}
387
388GLScalerTestTextureHelper::~GLScalerTestTextureHelper() {
389 gl_->DeleteTextures(textures_to_delete_.size(), textures_to_delete_.data());
390 textures_to_delete_.clear();
391}
392
393GLuint GLScalerTestTextureHelper::CreateTexture(const gfx::Size& size) {
394 GLuint texture = 0;
395 gl_->GenTextures(1, &texture);
396 gl_->BindTexture(GL_TEXTURE_2D, texture);
397 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
398 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
399 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
400 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
401 gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0,
402 GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
403 gl_->BindTexture(GL_TEXTURE_2D, 0);
404
405 if (texture) {
406 textures_to_delete_.push_back(texture);
407 }
408
409 return texture;
410}
411
412GLuint GLScalerTestTextureHelper::UploadTexture(const SkBitmap& bitmap) {
413 CHECK_EQ(bitmap.colorType(), kRGBA_8888_SkColorType);
414
415 GLuint texture = 0;
416 gl_->GenTextures(1, &texture);
417 gl_->BindTexture(GL_TEXTURE_2D, texture);
418 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
419 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
420 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
421 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
422 gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap.width(), bitmap.height(), 0,
423 GL_RGBA, GL_UNSIGNED_BYTE, bitmap.getAddr32(0, 0));
424 gl_->BindTexture(GL_TEXTURE_2D, 0);
425
426 if (texture) {
427 textures_to_delete_.push_back(texture);
428 }
429
430 return texture;
431}
432
433SkBitmap GLScalerTestTextureHelper::DownloadTexture(GLuint texture,
434 const gfx::Size& size) {
435 GLuint framebuffer = 0;
436 gl_->GenFramebuffers(1, &framebuffer);
437 gl_->BindFramebuffer(GL_FRAMEBUFFER, framebuffer);
438 gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
439 texture, 0);
440 SkBitmap result = GLScalerTestUtil::AllocateRGBABitmap(size);
441 gl_->ReadPixels(0, 0, size.width(), size.height(), GL_RGBA, GL_UNSIGNED_BYTE,
442 result.getAddr32(0, 0));
443 gl_->DeleteFramebuffers(1, &framebuffer);
444 return result;
445}
446
447} // namespace viz