1 /*
2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <gtest/gtest.h>
18
19 #include <fstream>
20 #include <iostream>
21
22 #include "ultrahdr/editorhelper.h"
23
24 // #define DUMP_OUTPUT
25
26 #ifdef __ANDROID__
27 #define INPUT_IMAGE "/data/local/tmp/raw_p010_image.p010"
28 #else
29 #define INPUT_IMAGE "./data/raw_p010_image.p010"
30 #endif
31
32 #define OUTPUT_P010_IMAGE "output.p010"
33 #define OUTPUT_YUV_IMAGE "output.yuv"
34 #define OUTPUT_RGBA_IMAGE "output.rgb"
35
36 #ifdef DUMP_OUTPUT
writeFile(std::string prefixName,uhdr_raw_image_t * img)37 static bool writeFile(std::string prefixName, uhdr_raw_image_t* img) {
38 char filename[50];
39
40 if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
41 snprintf(filename, sizeof filename, "%s_%d_%s", prefixName.c_str(), img->fmt,
42 OUTPUT_P010_IMAGE);
43 } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420 || img->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
44 snprintf(filename, sizeof filename, "%s_%d_%s", prefixName.c_str(), img->fmt, OUTPUT_YUV_IMAGE);
45 } else if (img->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || img->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
46 img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
47 snprintf(filename, sizeof filename, "%s_%d_%s", prefixName.c_str(), img->fmt,
48 OUTPUT_RGBA_IMAGE);
49 } else {
50 return false;
51 }
52
53 std::ofstream ofd(filename, std::ios::binary);
54 if (ofd.is_open()) {
55 int bpp = 1;
56
57 if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
58 bpp = 2;
59 } else if (img->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
60 img->fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
61 bpp = 4;
62 } else if (img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
63 bpp = 8;
64 }
65
66 const char* data = static_cast<char*>(img->planes[UHDR_PLANE_Y]);
67 size_t stride = img->stride[UHDR_PLANE_Y] * bpp;
68 size_t length = img->w * bpp;
69 for (unsigned i = 0; i < img->h; i++, data += stride) {
70 ofd.write(data, length);
71 }
72
73 if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
74 data = static_cast<char*>(img->planes[UHDR_PLANE_UV]);
75 stride = img->stride[UHDR_PLANE_UV] * bpp;
76 length = img->w * bpp;
77 for (unsigned i = 0; i < img->h / 2; i++, data += stride) {
78 ofd.write(data, length);
79 }
80 } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
81 data = static_cast<char*>(img->planes[UHDR_PLANE_U]);
82 stride = img->stride[UHDR_PLANE_U] * bpp;
83 length = (img->w / 2) * bpp;
84 for (unsigned i = 0; i < img->h / 2; i++, data += stride) {
85 ofd.write(data, length);
86 }
87 data = static_cast<char*>(img->planes[UHDR_PLANE_V]);
88 stride = img->stride[UHDR_PLANE_V] * bpp;
89 length = (img->w / 2) * bpp;
90 for (unsigned i = 0; i < img->h / 2; i++, data += stride) {
91 ofd.write(data, length);
92 }
93 }
94 return true;
95 }
96 std::cerr << "unable to write to file : " << filename << std::endl;
97 return false;
98 }
99 #endif
100
101 namespace ultrahdr {
102
loadFile(const char * filename,uhdr_raw_image_t * handle)103 static bool loadFile(const char* filename, uhdr_raw_image_t* handle) {
104 std::ifstream ifd(filename, std::ios::binary);
105 if (ifd.good()) {
106 if (handle->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
107 const int bpp = 2;
108 ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_Y]), handle->w * handle->h * bpp);
109 ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_UV]),
110 (handle->w / 2) * (handle->h / 2) * bpp * 2);
111 return true;
112 } else if (handle->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
113 ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_Y]), handle->w * handle->h);
114 ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_U]), (handle->w / 2) * (handle->h / 2));
115 ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_V]), (handle->w / 2) * (handle->h / 2));
116 return true;
117 } else if (handle->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
118 handle->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ||
119 handle->fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
120 int bpp = handle->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ? 8 : 4;
121 ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_PACKED]), handle->w * handle->h * bpp);
122 return true;
123 } else if (handle->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
124 ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_Y]), handle->w * handle->h);
125 return true;
126 }
127 return false;
128 }
129 std::cerr << "unable to open file : " << filename << std::endl;
130 return false;
131 }
132
initImageHandle(uhdr_raw_image_t * handle,int width,int height,uhdr_img_fmt_t format)133 void initImageHandle(uhdr_raw_image_t* handle, int width, int height, uhdr_img_fmt_t format) {
134 handle->fmt = format;
135 handle->cg = UHDR_CG_DISPLAY_P3;
136 handle->ct = UHDR_CT_SRGB;
137 handle->range = UHDR_CR_UNSPECIFIED;
138 handle->w = width;
139 handle->h = height;
140 if (handle->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
141 handle->planes[UHDR_PLANE_Y] = malloc(width * height * 2);
142 handle->planes[UHDR_PLANE_UV] = malloc((width / 2) * (height / 2) * 2 * 2);
143 handle->planes[UHDR_PLANE_V] = nullptr;
144 handle->stride[UHDR_PLANE_Y] = width;
145 handle->stride[UHDR_PLANE_UV] = width;
146 handle->stride[UHDR_PLANE_V] = 0;
147 } else if (handle->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
148 handle->planes[UHDR_PLANE_Y] = malloc(width * height);
149 handle->planes[UHDR_PLANE_U] = malloc((width / 2) * (height / 2));
150 handle->planes[UHDR_PLANE_V] = malloc((width / 2) * (height / 2));
151 handle->stride[UHDR_PLANE_Y] = width;
152 handle->stride[UHDR_PLANE_U] = width / 2;
153 handle->stride[UHDR_PLANE_V] = width / 2;
154 } else if (handle->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
155 handle->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ||
156 handle->fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
157 int bpp = handle->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ? 8 : 4;
158 handle->planes[UHDR_PLANE_PACKED] = malloc(width * height * bpp);
159 handle->planes[UHDR_PLANE_U] = nullptr;
160 handle->planes[UHDR_PLANE_V] = nullptr;
161 handle->stride[UHDR_PLANE_PACKED] = width;
162 handle->stride[UHDR_PLANE_U] = 0;
163 handle->stride[UHDR_PLANE_V] = 0;
164 } else if (handle->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
165 handle->planes[UHDR_PLANE_Y] = malloc(width * height);
166 handle->planes[UHDR_PLANE_U] = nullptr;
167 handle->planes[UHDR_PLANE_V] = nullptr;
168 handle->stride[UHDR_PLANE_Y] = width;
169 handle->stride[UHDR_PLANE_U] = 0;
170 handle->stride[UHDR_PLANE_V] = 0;
171 }
172 }
173
compare_planes(void * ref_plane,void * test_plane,int ref_stride,int test_stride,int width,int height,int bpp)174 void compare_planes(void* ref_plane, void* test_plane, int ref_stride, int test_stride, int width,
175 int height, int bpp) {
176 uint8_t* ref = (uint8_t*)ref_plane;
177 uint8_t* test = (uint8_t*)test_plane;
178 const size_t length = width * bpp;
179
180 for (int i = 0; i < height; i++, ref += (ref_stride * bpp), test += (test_stride * bpp)) {
181 ASSERT_EQ(0, memcmp(ref, test, length));
182 }
183 }
184
compareImg(uhdr_raw_image_t * ref,uhdr_raw_image_t * test)185 void compareImg(uhdr_raw_image_t* ref, uhdr_raw_image_t* test) {
186 ASSERT_EQ(ref->fmt, test->fmt);
187 ASSERT_EQ(ref->cg, test->cg);
188 ASSERT_EQ(ref->ct, test->ct);
189 ASSERT_EQ(ref->range, test->range);
190 ASSERT_EQ(ref->w, test->w);
191 ASSERT_EQ(ref->h, test->h);
192 int bpp = 1;
193 if (ref->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
194 bpp = 2;
195 compare_planes(ref->planes[UHDR_PLANE_Y], test->planes[UHDR_PLANE_Y], ref->stride[UHDR_PLANE_Y],
196 test->stride[UHDR_PLANE_Y], ref->w, ref->h, bpp);
197 compare_planes(ref->planes[UHDR_PLANE_UV], test->planes[UHDR_PLANE_UV],
198 ref->stride[UHDR_PLANE_UV], test->stride[UHDR_PLANE_UV], ref->w, ref->h / 2,
199 bpp);
200 } else if (ref->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
201 compare_planes(ref->planes[UHDR_PLANE_Y], test->planes[UHDR_PLANE_Y], ref->stride[UHDR_PLANE_Y],
202 test->stride[UHDR_PLANE_Y], ref->w, ref->h, bpp);
203 compare_planes(ref->planes[UHDR_PLANE_U], test->planes[UHDR_PLANE_U], ref->stride[UHDR_PLANE_U],
204 test->stride[UHDR_PLANE_U], ref->w / 2, ref->h / 2, bpp);
205 compare_planes(ref->planes[UHDR_PLANE_V], test->planes[UHDR_PLANE_V], ref->stride[UHDR_PLANE_V],
206 test->stride[UHDR_PLANE_V], ref->w / 2, ref->h / 2, bpp);
207 } else if (ref->fmt == UHDR_IMG_FMT_32bppRGBA8888 || ref->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
208 ref->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
209 bpp = ref->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ? 8 : 4;
210 compare_planes(ref->planes[UHDR_PLANE_PACKED], test->planes[UHDR_PLANE_PACKED],
211 ref->stride[UHDR_PLANE_PACKED], test->stride[UHDR_PLANE_PACKED], ref->w, ref->h,
212 bpp);
213 } else if (ref->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
214 compare_planes(ref->planes[UHDR_PLANE_Y], test->planes[UHDR_PLANE_Y], ref->stride[UHDR_PLANE_Y],
215 test->stride[UHDR_PLANE_Y], ref->w, ref->h, bpp);
216 }
217 }
218
219 class EditorHelperTest
220 : public ::testing::TestWithParam<std::tuple<std::string, int, int, uhdr_img_fmt_t>> {
221 public:
EditorHelperTest()222 EditorHelperTest()
223 : filename(std::get<0>(GetParam())),
224 width(std::get<1>(GetParam())),
225 height(std::get<2>(GetParam())),
226 fmt(std::get<3>(GetParam())){};
227
~EditorHelperTest()228 ~EditorHelperTest() {
229 int count = sizeof img_a.planes / sizeof img_a.planes[0];
230 for (int i = 0; i < count; i++) {
231 if (img_a.planes[i]) {
232 free(img_a.planes[i]);
233 img_a.planes[i] = nullptr;
234 }
235 }
236 }
237
238 std::string filename;
239 int width;
240 int height;
241 uhdr_img_fmt_t fmt;
242 uhdr_raw_image_t img_a{};
243 };
244
TEST_P(EditorHelperTest,Rotate)245 TEST_P(EditorHelperTest, Rotate) {
246 initImageHandle(&img_a, width, height, fmt);
247 ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
248 ultrahdr::uhdr_rotate_effect_t r90(90), r180(180), r270(270);
249 auto dst = apply_rotate(&r90, &img_a);
250 dst = apply_rotate(&r90, dst.get());
251 dst = apply_rotate(&r180, dst.get());
252 dst = apply_rotate(&r270, dst.get());
253 dst = apply_rotate(&r90, dst.get());
254 dst = apply_rotate(&r90, dst.get());
255 dst = apply_rotate(&r270, dst.get());
256 ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get()))
257 << "failed for resolution " << width << " x " << height << " format: " << fmt;
258 }
259
TEST_P(EditorHelperTest,Mirror)260 TEST_P(EditorHelperTest, Mirror) {
261 initImageHandle(&img_a, width, height, fmt);
262 ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
263 ultrahdr::uhdr_mirror_effect_t mhorz(UHDR_MIRROR_HORIZONTAL), mvert(UHDR_MIRROR_VERTICAL);
264 auto dst = apply_mirror(&mhorz, &img_a);
265 dst = apply_mirror(&mvert, dst.get());
266 dst = apply_mirror(&mhorz, dst.get());
267 dst = apply_mirror(&mvert, dst.get());
268 ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get()))
269 << "failed for resolution " << width << " x " << height << " format: " << fmt;
270 }
271
TEST_P(EditorHelperTest,Crop)272 TEST_P(EditorHelperTest, Crop) {
273 const int left = 16;
274 const int top = 16;
275 const int crop_wd = 32;
276 const int crop_ht = 32;
277
278 if (width < (left + crop_wd) || height <= (top + crop_ht)) {
279 GTEST_SKIP() << "Test skipped as crop attributes are too large for resolution " +
280 std::to_string(width) + " x " + std::to_string(height) +
281 " format: " + std::to_string(fmt);
282 }
283 std::string msg = "failed for resolution " + std::to_string(width) + " x " +
284 std::to_string(height) + " format: " + std::to_string(fmt);
285 initImageHandle(&img_a, width, height, fmt);
286 ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
287 uhdr_raw_image_t img_copy = img_a;
288 apply_crop(&img_copy, left, top, crop_wd, crop_ht);
289
290 ASSERT_EQ(img_a.fmt, img_copy.fmt) << msg;
291 ASSERT_EQ(img_a.cg, img_copy.cg) << msg;
292 ASSERT_EQ(img_a.ct, img_copy.ct) << msg;
293 ASSERT_EQ(img_a.range, img_copy.range) << msg;
294 ASSERT_EQ(img_copy.w, crop_wd) << msg;
295 ASSERT_EQ(img_copy.h, crop_ht) << msg;
296 #ifdef DUMP_OUTPUT
297 if (!writeFile("cropped", &img_copy)) {
298 std::cerr << "unable to write output file" << std::endl;
299 }
300 #endif
301 }
302
TEST_P(EditorHelperTest,Resize)303 TEST_P(EditorHelperTest, Resize) {
304 if ((fmt == UHDR_IMG_FMT_12bppYCbCr420 || UHDR_IMG_FMT_24bppYCbCrP010) &&
305 (((width / 2) % 2 != 0) || ((height / 2) % 2 != 0))) {
306 GTEST_SKIP() << "Test skipped for resolution " + std::to_string(width) + " x " +
307 std::to_string(height) + " format: " + std::to_string(fmt);
308 }
309 std::string msg = "failed for resolution " + std::to_string(width) + " x " +
310 std::to_string(height) + " format: " + std::to_string(fmt);
311 initImageHandle(&img_a, width, height, fmt);
312 ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
313 ultrahdr::uhdr_resize_effect_t resize(width / 2, height / 2);
314 auto dst = apply_resize(&resize, &img_a, width / 2, height / 2);
315
316 ASSERT_EQ(img_a.fmt, dst->fmt) << msg;
317 ASSERT_EQ(img_a.cg, dst->cg) << msg;
318 ASSERT_EQ(img_a.ct, dst->ct) << msg;
319 ASSERT_EQ(img_a.range, dst->range) << msg;
320 ASSERT_EQ(dst->w, width / 2) << msg;
321 ASSERT_EQ(dst->h, height / 2) << msg;
322 #ifdef DUMP_OUTPUT
323 if (!writeFile("resize", dst.get())) {
324 std::cerr << "unable to write output file" << std::endl;
325 }
326 #endif
327 }
328
TEST_P(EditorHelperTest,MultipleEffects)329 TEST_P(EditorHelperTest, MultipleEffects) {
330 std::string msg = "failed for resolution " + std::to_string(width) + " x " +
331 std::to_string(height) + " format: " + std::to_string(fmt);
332 initImageHandle(&img_a, width, height, fmt);
333 ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
334 ultrahdr::uhdr_rotate_effect_t r90(90), r180(180), r270(270);
335 ultrahdr::uhdr_mirror_effect_t mhorz(UHDR_MIRROR_HORIZONTAL), mvert(UHDR_MIRROR_VERTICAL);
336 ultrahdr::uhdr_resize_effect_t resize(width / 2, height / 2);
337 auto dst = apply_mirror(&mhorz, &img_a);
338 dst = apply_rotate(&r180, dst.get());
339 dst = apply_mirror(&mhorz, dst.get());
340 dst = apply_rotate(&r180, dst.get());
341 ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get())) << msg;
342
343 dst = apply_mirror(&mhorz, dst.get());
344 dst = apply_rotate(&r90, dst.get());
345 dst = apply_rotate(&r90, dst.get());
346 dst = apply_mirror(&mvert, dst.get());
347 ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get())) << msg;
348
349 dst = apply_rotate(&r270, dst.get());
350 dst = apply_mirror(&mvert, dst.get());
351 dst = apply_rotate(&r90, dst.get());
352 dst = apply_mirror(&mhorz, dst.get());
353 ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get())) << msg;
354
355 dst = apply_resize(&resize, dst.get(), width * 2, height * 2);
356 ASSERT_EQ(img_a.fmt, dst->fmt) << msg;
357 ASSERT_EQ(img_a.cg, dst->cg) << msg;
358 ASSERT_EQ(img_a.ct, dst->ct) << msg;
359 ASSERT_EQ(img_a.range, dst->range) << msg;
360 ASSERT_EQ(dst->w, width * 2) << msg;
361 ASSERT_EQ(dst->h, height * 2) << msg;
362
363 const int left = 16;
364 const int top = 16;
365 const int crop_wd = 32;
366 const int crop_ht = 32;
367 if (dst->w < (left + crop_wd) || dst->h <= (top + crop_ht)) {
368 GTEST_SKIP() << "Test skipped as crop attributes are too large for resolution " +
369 std::to_string(dst->w) + " x " + std::to_string(dst->h) +
370 " format: " + std::to_string(fmt);
371 }
372 uhdr_raw_image_ext_t* img_copy = dst.get();
373 apply_crop(img_copy, left, top, crop_wd, crop_ht);
374 ASSERT_EQ(dst->fmt, img_copy->fmt) << msg;
375 ASSERT_EQ(dst->cg, img_copy->cg) << msg;
376 ASSERT_EQ(dst->ct, img_copy->ct) << msg;
377 ASSERT_EQ(dst->range, img_copy->range) << msg;
378 ASSERT_EQ(crop_wd, img_copy->w) << msg;
379 ASSERT_EQ(crop_ht, img_copy->h) << msg;
380 }
381
382 INSTANTIATE_TEST_SUITE_P(
383 EditorAPIParameterizedTests, EditorHelperTest,
384 ::testing::Combine(::testing::Values(INPUT_IMAGE), ::testing::Range(2, 80, 2),
385 ::testing::Values(64),
386 ::testing::Values(UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_12bppYCbCr420,
387 UHDR_IMG_FMT_8bppYCbCr400, UHDR_IMG_FMT_32bppRGBA1010102,
388 UHDR_IMG_FMT_64bppRGBAHalfFloat,
389 UHDR_IMG_FMT_32bppRGBA8888)));
390
391 } // namespace ultrahdr
392