]> git.sesse.net Git - casparcg/blob - unit-test/image_mixer_test.cpp
Fixed bug where audio filter did not use the input channel layout of a stream
[casparcg] / unit-test / image_mixer_test.cpp
1 /*
2 * Copyright (c) 2011 Sveriges Television AB <info@casparcg.com>
3 *
4 * This file is part of CasparCG (www.casparcg.com).
5 *
6 * CasparCG is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * CasparCG is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with CasparCG. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Helge Norberg, helge.norberg@svt.se
20 */
21
22 #include "stdafx.h"
23
24 #include <gtest/gtest.h>
25
26 #include <core/mixer/image/image_mixer.h>
27 #include <core/frame/audio_channel_layout.h>
28 #include <core/frame/pixel_format.h>
29 #include <core/frame/frame_transform.h>
30 #include <core/frame/draw_frame.h>
31
32 #include <accelerator/ogl/image/image_mixer.h>
33 #include <accelerator/ogl/util/device.h>
34
35 #ifdef _MSC_VER
36 #include <accelerator/cpu/image/image_mixer.h>
37 #endif
38
39 #include <boost/assign.hpp>
40
41 #include <cstdint>
42
43 namespace caspar { namespace core {
44
45 spl::shared_ptr<accelerator::ogl::device> ogl_device()
46 {
47         static auto device = spl::make_shared<accelerator::ogl::device>();
48
49         return device;
50 }
51
52 template <typename T>
53 spl::shared_ptr<core::image_mixer> create_mixer();
54
55 template <>
56 spl::shared_ptr<core::image_mixer> create_mixer<accelerator::ogl::image_mixer>()
57 {
58         return spl::make_shared<accelerator::ogl::image_mixer>(
59                         ogl_device(),
60                         false, // blend modes not wanted
61                         false, // straight alpha not wanted
62                         0);
63 }
64
65 struct dummy_ogl_with_blend_modes {};
66 template <>
67 spl::shared_ptr<core::image_mixer> create_mixer<dummy_ogl_with_blend_modes>()
68 {
69         return spl::make_shared<accelerator::ogl::image_mixer>(
70                         ogl_device(),
71                         true,  // blend modes wanted
72                         false, // straight alpha not wanted
73                         0);
74 }
75
76 #ifdef _MSC_VER
77 template <>
78 spl::shared_ptr<core::image_mixer> create_mixer<accelerator::cpu::image_mixer>()
79 {
80         return spl::make_shared<accelerator::cpu::image_mixer>(0);
81 }
82 #endif
83
84 void assert_pixel_eq(uint8_t r, uint8_t g, uint8_t b, uint8_t a, const uint8_t* pos, int tolerance = 1)
85 {
86         ASSERT_NEAR(b, *pos, tolerance);
87         ASSERT_NEAR(g, *(pos + 1), tolerance);
88         ASSERT_NEAR(r, *(pos + 2), tolerance);
89         ASSERT_NEAR(a, *(pos + 3), tolerance);
90 }
91
92 template<typename Range>
93 void assert_all_pixels_eq(uint8_t r, uint8_t g, uint8_t b, uint8_t a, const Range& range, int tolerance = 1)
94 {
95         for (auto iter = range.begin(); iter != range.end(); iter += 4)
96                 assert_pixel_eq(r, g, b, a, iter, tolerance);
97 }
98
99 void set_pixel(core::mutable_frame& frame, int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
100 {
101         auto pos = frame.image_data().data() + (y * frame.pixel_format_desc().planes[0].width + x) * 4;
102
103         *pos = b;
104         *++pos = g;
105         *++pos = r;
106         *++pos = a;
107 }
108
109 template <typename T>
110 class MixerTest : public ::testing::Test
111 {
112 protected:
113         spl::shared_ptr<core::image_mixer> mixer;
114 public:
115         MixerTest() : mixer(create_mixer<T>()) { }
116
117         core::mutable_frame create_frame(int width, int height)
118         {
119                 core::pixel_format_desc desc(core::pixel_format::bgra);
120                 desc.planes.push_back(core::pixel_format_desc::plane(width, height, 4));
121                 return mixer->create_frame(this, desc, core::audio_channel_layout::invalid());
122         }
123
124         core::mutable_frame create_single_color_frame(
125                         uint8_t r, uint8_t g, uint8_t b, uint8_t a, int width, int height)
126         {
127                 auto frame = create_frame(width, height);
128
129                 for (int y = 0; y < height; ++y)
130                         for (int x = 0; x < width; ++x)
131                                 set_pixel(frame, x, y, r, g, b, a);
132
133                 return frame;
134         }
135
136         void add_layer(core::draw_frame frame, core::blend_mode blend_mode = core::blend_mode::normal)
137         {
138                 frame.transform().image_transform.layer_depth = 1;
139                 frame.accept(*mixer);
140         }
141
142         array<const uint8_t> get_result(int width, int height)
143         {
144                 core::video_format_desc desc(core::video_format::x576p2500);
145                 desc.width = width;
146                 desc.height = height;
147                 desc.size = width * height * 4;
148
149                 return (*mixer)(desc, false).get();
150         }
151 };
152
153 template <typename T>
154 class MixerTestEveryImpl : public MixerTest<T>
155 {
156 };
157
158 template <typename T>
159 class MixerTestOgl : public MixerTest<T> // Only use-cases supported by ogl mixer
160 {
161 };
162
163 template <typename T>
164 class MixerTestOglWithBlendModes : public MixerTest<T> // Only use-cases supported by ogl mixer
165 {
166 };
167
168 #ifdef _MSC_VER
169 typedef testing::Types<
170                 accelerator::ogl::image_mixer,
171                 accelerator::cpu::image_mixer,
172                 dummy_ogl_with_blend_modes> all_mixer_implementations;
173 #else
174 typedef testing::Types<
175                 accelerator::ogl::image_mixer,
176                 dummy_ogl_with_blend_modes> all_mixer_implementations;
177 #endif
178
179 typedef testing::Types<
180                 accelerator::ogl::image_mixer,
181                 dummy_ogl_with_blend_modes> gpu_mixer_implementations;
182
183 TYPED_TEST_CASE(MixerTestEveryImpl, all_mixer_implementations);
184 TYPED_TEST_CASE(MixerTestOgl, gpu_mixer_implementations);
185 TYPED_TEST_CASE(MixerTestOglWithBlendModes, testing::Types<dummy_ogl_with_blend_modes>);
186
187 // Tests for use cases that should work on both CPU and GPU mixer
188 // --------------------------------------------------------------
189
190 TYPED_TEST(MixerTestEveryImpl, PassthroughSingleLayer)
191 {
192         core::draw_frame frame(this->create_single_color_frame(255, 255, 255, 255, 16, 16));
193         this->add_layer(frame);
194         assert_all_pixels_eq(255, 255, 255, 255, this->get_result(16, 16));
195 }
196
197 TYPED_TEST(MixerTestEveryImpl, OpaqueTopLayer)
198 {
199         core::draw_frame red_under(this->create_single_color_frame(255, 0, 0, 255, 16, 16));
200         core::draw_frame green_over(this->create_single_color_frame(0, 255, 0, 255, 16, 16));
201         this->add_layer(red_under);
202         this->add_layer(green_over);
203         assert_all_pixels_eq(0, 255, 0, 255, this->get_result(16, 16));
204 }
205
206 TYPED_TEST(MixerTestEveryImpl, HalfAlpha)
207 {
208         core::draw_frame red_under(this->create_single_color_frame(255, 0, 0, 255, 16, 16));
209         core::draw_frame half_green_over(this->create_single_color_frame(0, 127, 0, 127, 16, 16));
210         this->add_layer(red_under);
211         this->add_layer(half_green_over);
212         assert_all_pixels_eq(127, 127, 0, 255, this->get_result(16, 16));
213
214         this->add_layer(half_green_over);
215         assert_all_pixels_eq(0, 127, 0, 127, this->get_result(16, 16));
216 }
217
218 // Tests for use cases that currently *only* works on GPU mixer
219 // ------------------------------------------------------------
220
221 TYPED_TEST(MixerTestOgl, HalfBrightness)
222 {
223         core::draw_frame frame(this->create_single_color_frame(255, 255, 255, 255, 1, 1));
224         frame.transform().image_transform.brightness = 0.5;
225         this->add_layer(frame);
226         assert_all_pixels_eq(127, 127, 127, 255, this->get_result(1, 1));
227 }
228
229 TYPED_TEST(MixerTestOgl, HalfOpacity)
230 {
231         core::draw_frame red_under(this->create_single_color_frame(255, 0, 0, 255, 1, 1));
232         core::draw_frame green_over(this->create_single_color_frame(0, 255, 0, 255, 1, 1));
233         green_over.transform().image_transform.opacity = 0.5;
234         this->add_layer(red_under);
235         this->add_layer(green_over);
236         assert_all_pixels_eq(127, 127, 0, 255, this->get_result(1, 1));
237
238         this->add_layer(green_over);
239         assert_all_pixels_eq(0, 127, 0, 127, this->get_result(1, 1));
240 }
241
242 TYPED_TEST(MixerTestOgl, MakeGrayscaleWithSaturation)
243 {
244         core::draw_frame frame(this->create_single_color_frame(255, 0, 0, 255, 1, 1));
245         frame.transform().image_transform.saturation = 0.0;
246         this->add_layer(frame);
247         auto result = this->get_result(1, 1);
248         
249         ASSERT_EQ(result.data()[0], result.data()[1]);
250         ASSERT_EQ(result.data()[1], result.data()[2]);
251 }
252
253 TYPED_TEST(MixerTestOgl, TransformFillScale)
254 {
255         core::draw_frame frame(this->create_single_color_frame(255, 255, 255, 255, 1, 1));
256         frame.transform().image_transform.fill_translation[0] = 0.5;
257         frame.transform().image_transform.fill_translation[1] = 0.5;
258         frame.transform().image_transform.fill_scale[0] = 0.5;
259         frame.transform().image_transform.fill_scale[1] = 0.5;
260         this->add_layer(frame);
261         auto res = this->get_result(2, 2);
262         std::vector<std::uint8_t> result(res.begin(), res.end());
263
264         // bottom right corner
265         ASSERT_EQ(boost::assign::list_of<uint8_t>
266                         (0)(0)(0)(0) (0)(0)(0)(0)
267                         (0)(0)(0)(0) (255)(255)(255)(255), result);
268
269         frame.transform().image_transform.fill_translation[0] = 0;
270         this->add_layer(frame);
271         res = this->get_result(2, 2);
272         result = std::vector<std::uint8_t>(res.begin(), res.end());
273
274         // bottom left corner
275         ASSERT_EQ(boost::assign::list_of<uint8_t>
276                         (0)(0)(0)(0)         (0)(0)(0)(0)
277                         (255)(255)(255)(255) (0)(0)(0)(0), result);
278 }
279
280 TYPED_TEST(MixerTestOgl, LevelsBugGammaMinLevelsMismatch)
281 {
282         auto src_frame = this->create_frame(2, 1);
283         set_pixel(src_frame, 0, 0, 16, 16, 16, 255);
284         set_pixel(src_frame, 1, 0, 235, 235, 235, 255);
285
286         core::draw_frame frame(std::move(src_frame));
287         frame.transform().image_transform.levels.min_input = 16.0 / 255.0;
288         frame.transform().image_transform.levels.max_input = 235.0 / 255.0;
289         this->add_layer(frame);
290
291         auto result = this->get_result(2, 1);
292         assert_pixel_eq(0, 0, 0, 255, result.data(), 0);
293         assert_pixel_eq(255, 255, 255, 255, result.data() + 4, 0);
294 }
295
296 // Tests for use cases that only works on GPU mixer with blend-modes enabled
297 // -------------------------------------------------------------------------
298
299 }}