1 #include <libavcodec/avcodec.h>
2 
3 #include "scale_test.h"
4 #include "video/image_writer.h"
5 #include "video/sws_utils.h"
6 
gen_repack_test_img(int w,int h,int bytes,bool rgb,bool alpha)7 static struct mp_image *gen_repack_test_img(int w, int h, int bytes, bool rgb,
8                                             bool alpha)
9 {
10     struct mp_regular_imgfmt planar_desc = {
11         .component_type = MP_COMPONENT_TYPE_UINT,
12         .component_size = bytes,
13         .forced_csp = rgb ? MP_CSP_RGB : 0,
14         .num_planes = alpha ? 4 : 3,
15         .planes = {
16             {1, {rgb ? 2 : 1}},
17             {1, {rgb ? 3 : 2}},
18             {1, {rgb ? 1 : 3}},
19             {1, {4}},
20         },
21     };
22     int mpfmt = mp_find_regular_imgfmt(&planar_desc);
23     assert(mpfmt);
24     struct mp_image *mpi = mp_image_alloc(mpfmt, w, h);
25     assert(mpi);
26 
27     // Well, I have no idea what makes a good test image. So here's some crap.
28     // This contains bars/tiles of solid colors. For each of R/G/B, it toggles
29     // though 0/100% range, so 2*2*2 = 8 combinations (16 with alpha).
30     int b_h = 16, b_w = 16;
31 
32     for (int y = 0; y < h; y++) {
33         for (int p = 0; p < mpi->num_planes; p++) {
34             void *line = mpi->planes[p] + mpi->stride[p] * (ptrdiff_t)y;
35 
36             for (int x = 0; x < w; x += b_w) {
37                 unsigned i = x / b_w + y / b_h * 2;
38                 int c = ((i >> p) & 1);
39                 if (bytes == 1) {
40                     c *= (1 << 8) - 1;
41                     for (int xs = x; xs < x + b_w; xs++)
42                         ((uint8_t *)line)[xs] = c;
43                 } else if (bytes == 2) {
44                     c *= (1 << 16) - 1;
45                     for (int xs = x; xs < x + b_w; xs++)
46                         ((uint16_t *)line)[xs] = c;
47                 }
48             }
49         }
50     }
51 
52     return mpi;
53 }
54 
dump_image(struct scale_test * stest,const char * name,struct mp_image * img)55 static void dump_image(struct scale_test *stest, const char *name,
56                        struct mp_image *img)
57 {
58     char *path = mp_tprintf(4096, "%s/%s.png", stest->ctx->out_path, name);
59 
60     struct image_writer_opts opts = image_writer_opts_defaults;
61     opts.format = AV_CODEC_ID_PNG;
62 
63     if (!write_image(img, &opts, path, stest->ctx->global, stest->ctx->log)) {
64         MP_FATAL(stest->ctx, "Failed to write '%s'.\n", path);
65         abort();
66     }
67 }
68 
69 // Compare 2 images (same format and size) for exact pixel data match.
70 // Does generally not work with formats that include undefined padding.
71 // Does not work with non-byte aligned formats.
assert_imgs_equal(struct scale_test * stest,FILE * f,struct mp_image * ref,struct mp_image * new)72 static void assert_imgs_equal(struct scale_test *stest, FILE *f,
73                               struct mp_image *ref, struct mp_image *new)
74 {
75     assert(ref->imgfmt == new->imgfmt);
76     assert(ref->w == new->w);
77     assert(ref->h == new->h);
78 
79     assert(ref->fmt.flags & MP_IMGFLAG_BYTE_ALIGNED);
80     assert(ref->fmt.bpp[0]);
81 
82     for (int p = 0; p < ref->num_planes; p++) {
83         for (int y = 0; y < ref->h; y++) {
84             void *line_r = ref->planes[p] + ref->stride[p] * (ptrdiff_t)y;
85             void *line_o = new->planes[p] + new->stride[p] * (ptrdiff_t)y;
86             size_t size = mp_image_plane_bytes(ref, p, 0, new->w);
87 
88             bool ok = memcmp(line_r, line_o, size) == 0;
89             if (!ok) {
90                 stest->fail += 1;
91                 char *fn_a = mp_tprintf(80, "img%d_ref", stest->fail);
92                 char *fn_b = mp_tprintf(80, "img%d_new", stest->fail);
93                 fprintf(f, "Images mismatching, dumping to %s/%s\n", fn_a, fn_b);
94                 dump_image(stest, fn_a, ref);
95                 dump_image(stest, fn_b, new);
96                 return;
97             }
98         }
99     }
100 }
101 
repack_test_run(struct scale_test * stest)102 void repack_test_run(struct scale_test *stest)
103 {
104     char *logname = mp_tprintf(80, "%s.log", stest->test_name);
105     FILE *f = test_open_out(stest->ctx, logname);
106 
107     if (!stest->sws) {
108         init_imgfmts_list();
109 
110         stest->sws = mp_sws_alloc(stest);
111 
112         stest->img_repack_rgb8 = gen_repack_test_img(256, 128, 1, true, false);
113         stest->img_repack_rgba8 = gen_repack_test_img(256, 128, 1, true, true);
114         stest->img_repack_rgb16 = gen_repack_test_img(256, 128, 2, true, false);
115         stest->img_repack_rgba16 = gen_repack_test_img(256, 128, 2, true, true);
116 
117         talloc_steal(stest, stest->img_repack_rgb8);
118         talloc_steal(stest, stest->img_repack_rgba8);
119         talloc_steal(stest, stest->img_repack_rgb16);
120         talloc_steal(stest, stest->img_repack_rgba16);
121     }
122 
123     for (int a = 0; a < num_imgfmts; a++) {
124         int mpfmt = imgfmts[a];
125         struct mp_imgfmt_desc fmtdesc = mp_imgfmt_get_desc(mpfmt);
126         struct mp_regular_imgfmt rdesc;
127         if (!mp_get_regular_imgfmt(&rdesc, mpfmt)) {
128             int ofmt = mp_find_other_endian(mpfmt);
129             if (!mp_get_regular_imgfmt(&rdesc, ofmt))
130                 continue;
131         }
132         if (rdesc.num_planes > 1 || rdesc.forced_csp != MP_CSP_RGB)
133             continue;
134 
135         struct mp_image *test_img = NULL;
136         bool alpha = fmtdesc.flags & MP_IMGFLAG_ALPHA;
137         bool hidepth = rdesc.component_size > 1;
138         if (alpha) {
139             test_img = hidepth ? stest->img_repack_rgba16 : stest->img_repack_rgba8;
140         } else {
141             test_img = hidepth ? stest->img_repack_rgb16 : stest->img_repack_rgb8;
142         }
143 
144         if (test_img->imgfmt == mpfmt)
145             continue;
146 
147         if (!stest->fns->supports_fmts(stest->fns_priv, mpfmt, test_img->imgfmt))
148             continue;
149 
150         if (!mp_sws_supports_formats(stest->sws, mpfmt, test_img->imgfmt))
151             continue;
152 
153         fprintf(f, "%s using %s\n", mp_imgfmt_to_name(mpfmt),
154                 mp_imgfmt_to_name(test_img->imgfmt));
155 
156         struct mp_image *dst = mp_image_alloc(mpfmt, test_img->w, test_img->h);
157         assert(dst);
158 
159         // This tests packing.
160         bool ok = stest->fns->scale(stest->fns_priv, dst, test_img);
161         assert(ok);
162 
163         // Cross-check with swscale in the other direction.
164         // (Mostly so we don't have to worry about padding.)
165         struct mp_image *src2 =
166             mp_image_alloc(test_img->imgfmt, test_img->w, test_img->h);
167         assert(src2);
168         ok = mp_sws_scale(stest->sws, src2, dst) >= 0;
169         assert_imgs_equal(stest, f, test_img, src2);
170 
171         // Assume the other conversion direction also works.
172         assert(stest->fns->supports_fmts(stest->fns_priv, test_img->imgfmt, mpfmt));
173 
174         struct mp_image *back = mp_image_alloc(test_img->imgfmt, dst->w, dst->h);
175         assert(back);
176 
177         // This tests unpacking.
178         ok = stest->fns->scale(stest->fns_priv, back, dst);
179         assert(ok);
180 
181         assert_imgs_equal(stest, f, test_img, back);
182 
183         talloc_free(back);
184         talloc_free(src2);
185         talloc_free(dst);
186     }
187 
188     fclose(f);
189 
190     assert_text_files_equal(stest->ctx, logname, logname,
191                             "This can fail if FFmpeg adds or removes pixfmts.");
192 }
193