Imported Upstream version 0.9.0
[platform/upstream/libjxl.git] / lib / jxl / test_image.cc
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
5
6 #include "lib/jxl/test_image.h"
7
8 #include <jxl/encode.h>
9
10 #include <algorithm>
11 #include <cstring>
12 #include <utility>
13
14 #include "lib/extras/dec/color_description.h"
15 #include "lib/extras/dec/color_hints.h"
16 #include "lib/extras/dec/decode.h"
17 #include "lib/jxl/base/byte_order.h"
18 #include "lib/jxl/base/random.h"
19 #include "lib/jxl/base/span.h"
20 #include "lib/jxl/base/status.h"
21 #include "lib/jxl/color_encoding_internal.h"
22
23 namespace jxl {
24 namespace test {
25
26 namespace {
27
28 void StoreValue(float val, size_t bits_per_sample, JxlPixelFormat format,
29                 uint8_t** out) {
30   const float mul = (1u << bits_per_sample) - 1;
31   if (format.data_type == JXL_TYPE_UINT8) {
32     **out = val * mul;
33   } else if (format.data_type == JXL_TYPE_UINT16) {
34     uint16_t uval = val * mul;
35     if (SwapEndianness(format.endianness)) {
36       uval = JXL_BSWAP16(uval);
37     }
38     memcpy(*out, &uval, 2);
39   } else if (format.data_type == JXL_TYPE_FLOAT) {
40     // TODO(szabadka) Add support for custom bits / exponent bits floats.
41     if (SwapEndianness(format.endianness)) {
42       val = BSwapFloat(val);
43     }
44     memcpy(*out, &val, 4);
45   } else {
46     // TODO(szabadka) Add support for FLOAT16.
47   }
48   *out += extras::PackedImage::BitsPerChannel(format.data_type) / 8;
49 }
50
51 void FillPackedImage(size_t bits_per_sample, uint16_t seed,
52                      extras::PackedImage* image) {
53   const size_t xsize = image->xsize;
54   const size_t ysize = image->ysize;
55   const JxlPixelFormat format = image->format;
56
57   // Cause more significant image difference for successive seeds.
58   Rng generator(seed);
59
60   // Returns random integer in interval [0, max_value)
61   auto rngu = [&generator](size_t max_value) -> size_t {
62     return generator.UniformU(0, max_value);
63   };
64
65   // Returns random float in interval [0.0, max_value)
66   auto rngf = [&generator](float max_value) {
67     return generator.UniformF(0.0f, max_value);
68   };
69
70   // Dark background gradient color
71   float r0 = rngf(0.5f);
72   float g0 = rngf(0.5f);
73   float b0 = rngf(0.5f);
74   float a0 = rngf(0.5f);
75   float r1 = rngf(0.5f);
76   float g1 = rngf(0.5f);
77   float b1 = rngf(0.5f);
78   float a1 = rngf(0.5f);
79
80   // Circle with different color
81   size_t circle_x = rngu(xsize);
82   size_t circle_y = rngu(ysize);
83   size_t circle_r = rngu(std::min(xsize, ysize));
84
85   // Rectangle with random noise
86   size_t rect_x0 = rngu(xsize);
87   size_t rect_y0 = rngu(ysize);
88   size_t rect_x1 = rngu(xsize);
89   size_t rect_y1 = rngu(ysize);
90   if (rect_x1 < rect_x0) std::swap(rect_x0, rect_y1);
91   if (rect_y1 < rect_y0) std::swap(rect_y0, rect_y1);
92
93   // Create pixel content to test, actual content does not matter as long as it
94   // can be compared after roundtrip.
95   uint8_t* out = reinterpret_cast<uint8_t*>(image->pixels());
96   const float imul16 = 1.0f / 65536.0f;
97   for (size_t y = 0; y < ysize; y++) {
98     for (size_t x = 0; x < xsize; x++) {
99       float r = r0 * (ysize - y - 1) / ysize + r1 * y / ysize;
100       float g = g0 * (ysize - y - 1) / ysize + g1 * y / ysize;
101       float b = b0 * (ysize - y - 1) / ysize + b1 * y / ysize;
102       float a = a0 * (ysize - y - 1) / ysize + a1 * y / ysize;
103       // put some shape in there for visual debugging
104       if ((x - circle_x) * (x - circle_x) + (y - circle_y) * (y - circle_y) <
105           circle_r * circle_r) {
106         r = std::min(1.0f, ((65535 - x * y) ^ seed) * imul16);
107         g = std::min(1.0f, ((x << 8) + y + seed) * imul16);
108         b = std::min(1.0f, ((y << 8) + x * seed) * imul16);
109         a = std::min(1.0f, (32768 + x * 256 - y) * imul16);
110       } else if (x > rect_x0 && x < rect_x1 && y > rect_y0 && y < rect_y1) {
111         r = rngf(1.0f);
112         g = rngf(1.0f);
113         b = rngf(1.0f);
114         a = rngf(1.0f);
115       }
116       if (format.num_channels == 1) {
117         StoreValue(g, bits_per_sample, format, &out);
118       } else if (format.num_channels == 2) {
119         StoreValue(g, bits_per_sample, format, &out);
120         StoreValue(a, bits_per_sample, format, &out);
121       } else if (format.num_channels == 3) {
122         StoreValue(r, bits_per_sample, format, &out);
123         StoreValue(g, bits_per_sample, format, &out);
124         StoreValue(b, bits_per_sample, format, &out);
125       } else if (format.num_channels == 4) {
126         StoreValue(r, bits_per_sample, format, &out);
127         StoreValue(g, bits_per_sample, format, &out);
128         StoreValue(b, bits_per_sample, format, &out);
129         StoreValue(a, bits_per_sample, format, &out);
130       }
131     }
132   }
133 }
134
135 }  // namespace
136
137 std::vector<uint8_t> GetSomeTestImage(size_t xsize, size_t ysize,
138                                       size_t num_channels, uint16_t seed) {
139   // Cause more significant image difference for successive seeds.
140   Rng generator(seed);
141
142   // Returns random integer in interval [0, max_value)
143   auto rng = [&generator](size_t max_value) -> size_t {
144     return generator.UniformU(0, max_value);
145   };
146
147   // Dark background gradient color
148   uint16_t r0 = rng(32768);
149   uint16_t g0 = rng(32768);
150   uint16_t b0 = rng(32768);
151   uint16_t a0 = rng(32768);
152   uint16_t r1 = rng(32768);
153   uint16_t g1 = rng(32768);
154   uint16_t b1 = rng(32768);
155   uint16_t a1 = rng(32768);
156
157   // Circle with different color
158   size_t circle_x = rng(xsize);
159   size_t circle_y = rng(ysize);
160   size_t circle_r = rng(std::min(xsize, ysize));
161
162   // Rectangle with random noise
163   size_t rect_x0 = rng(xsize);
164   size_t rect_y0 = rng(ysize);
165   size_t rect_x1 = rng(xsize);
166   size_t rect_y1 = rng(ysize);
167   if (rect_x1 < rect_x0) std::swap(rect_x0, rect_y1);
168   if (rect_y1 < rect_y0) std::swap(rect_y0, rect_y1);
169
170   size_t num_pixels = xsize * ysize;
171   // 16 bits per channel, big endian, 4 channels
172   std::vector<uint8_t> pixels(num_pixels * num_channels * 2);
173   // Create pixel content to test, actual content does not matter as long as it
174   // can be compared after roundtrip.
175   for (size_t y = 0; y < ysize; y++) {
176     for (size_t x = 0; x < xsize; x++) {
177       uint16_t r = r0 * (ysize - y - 1) / ysize + r1 * y / ysize;
178       uint16_t g = g0 * (ysize - y - 1) / ysize + g1 * y / ysize;
179       uint16_t b = b0 * (ysize - y - 1) / ysize + b1 * y / ysize;
180       uint16_t a = a0 * (ysize - y - 1) / ysize + a1 * y / ysize;
181       // put some shape in there for visual debugging
182       if ((x - circle_x) * (x - circle_x) + (y - circle_y) * (y - circle_y) <
183           circle_r * circle_r) {
184         r = (65535 - x * y) ^ seed;
185         g = (x << 8) + y + seed;
186         b = (y << 8) + x * seed;
187         a = 32768 + x * 256 - y;
188       } else if (x > rect_x0 && x < rect_x1 && y > rect_y0 && y < rect_y1) {
189         r = rng(65536);
190         g = rng(65536);
191         b = rng(65536);
192         a = rng(65536);
193       }
194       size_t i = (y * xsize + x) * 2 * num_channels;
195       pixels[i + 0] = (r >> 8);
196       pixels[i + 1] = (r & 255);
197       if (num_channels >= 2) {
198         // This may store what is called 'g' in the alpha channel of a 2-channel
199         // image, but that's ok since the content is arbitrary
200         pixels[i + 2] = (g >> 8);
201         pixels[i + 3] = (g & 255);
202       }
203       if (num_channels >= 3) {
204         pixels[i + 4] = (b >> 8);
205         pixels[i + 5] = (b & 255);
206       }
207       if (num_channels >= 4) {
208         pixels[i + 6] = (a >> 8);
209         pixels[i + 7] = (a & 255);
210       }
211     }
212   }
213   return pixels;
214 }
215
216 TestImage::TestImage() {
217   SetChannels(3);
218   SetAllBitDepths(8);
219   SetColorEncoding("RGB_D65_SRG_Rel_SRG");
220 }
221
222 TestImage& TestImage::DecodeFromBytes(const std::vector<uint8_t>& bytes) {
223   ColorEncoding c_enc;
224   JXL_CHECK(c_enc.FromExternal(ppf_.color_encoding));
225   extras::ColorHints color_hints;
226   color_hints.Add("color_space", Description(c_enc));
227   JXL_CHECK(extras::DecodeBytes(Bytes(bytes), color_hints, &ppf_));
228   return *this;
229 }
230
231 TestImage& TestImage::ClearMetadata() {
232   ppf_.metadata = extras::PackedMetadata();
233   return *this;
234 }
235
236 TestImage& TestImage::SetDimensions(size_t xsize, size_t ysize) {
237   if (xsize <= ppf_.info.xsize && ysize <= ppf_.info.ysize) {
238     for (auto& frame : ppf_.frames) {
239       CropLayerInfo(xsize, ysize, &frame.frame_info.layer_info);
240       CropImage(xsize, ysize, &frame.color);
241       for (auto& ec : frame.extra_channels) {
242         CropImage(xsize, ysize, &ec);
243       }
244     }
245   } else {
246     JXL_CHECK(ppf_.info.xsize == 0 && ppf_.info.ysize == 0);
247   }
248   ppf_.info.xsize = xsize;
249   ppf_.info.ysize = ysize;
250   return *this;
251 }
252
253 TestImage& TestImage::SetChannels(size_t num_channels) {
254   JXL_CHECK(ppf_.frames.empty());
255   JXL_CHECK(!ppf_.preview_frame);
256   ppf_.info.num_color_channels = num_channels < 3 ? 1 : 3;
257   ppf_.info.num_extra_channels = num_channels - ppf_.info.num_color_channels;
258   if (ppf_.info.num_extra_channels > 0 && ppf_.info.alpha_bits == 0) {
259     ppf_.info.alpha_bits = ppf_.info.bits_per_sample;
260     ppf_.info.alpha_exponent_bits = ppf_.info.exponent_bits_per_sample;
261   }
262   ppf_.extra_channels_info.clear();
263   for (size_t i = 1; i < ppf_.info.num_extra_channels; ++i) {
264     extras::PackedExtraChannel ec;
265     ec.index = i;
266     JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &ec.ec_info);
267     if (ec.ec_info.bits_per_sample == 0) {
268       ec.ec_info.bits_per_sample = ppf_.info.bits_per_sample;
269       ec.ec_info.exponent_bits_per_sample = ppf_.info.exponent_bits_per_sample;
270     }
271     ppf_.extra_channels_info.emplace_back(std::move(ec));
272   }
273   format_.num_channels = std::min(static_cast<size_t>(4), num_channels);
274   if (ppf_.info.num_color_channels == 1 &&
275       ppf_.color_encoding.color_space != JXL_COLOR_SPACE_GRAY) {
276     SetColorEncoding("Gra_D65_Rel_SRG");
277   }
278   return *this;
279 }
280
281 // Sets the same bit depth on color, alpha and all extra channels.
282 TestImage& TestImage::SetAllBitDepths(uint32_t bits_per_sample,
283                                       uint32_t exponent_bits_per_sample) {
284   ppf_.info.bits_per_sample = bits_per_sample;
285   ppf_.info.exponent_bits_per_sample = exponent_bits_per_sample;
286   if (ppf_.info.num_extra_channels > 0) {
287     ppf_.info.alpha_bits = bits_per_sample;
288     ppf_.info.alpha_exponent_bits = exponent_bits_per_sample;
289   }
290   for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) {
291     extras::PackedExtraChannel& ec = ppf_.extra_channels_info[i];
292     ec.ec_info.bits_per_sample = bits_per_sample;
293     ec.ec_info.exponent_bits_per_sample = exponent_bits_per_sample;
294   }
295   format_.data_type = DefaultDataType(ppf_.info);
296   return *this;
297 }
298
299 TestImage& TestImage::SetDataType(JxlDataType data_type) {
300   format_.data_type = data_type;
301   return *this;
302 }
303
304 TestImage& TestImage::SetEndianness(JxlEndianness endianness) {
305   format_.endianness = endianness;
306   return *this;
307 }
308
309 TestImage& TestImage::SetColorEncoding(const std::string& description) {
310   JXL_CHECK(ParseDescription(description, &ppf_.color_encoding));
311   ColorEncoding c_enc;
312   JXL_CHECK(c_enc.FromExternal(ppf_.color_encoding));
313   IccBytes icc = c_enc.ICC();
314   JXL_CHECK(!icc.empty());
315   ppf_.icc.assign(icc.begin(), icc.end());
316   return *this;
317 }
318
319 TestImage& TestImage::CoalesceGIFAnimationWithAlpha() {
320   extras::PackedFrame canvas = ppf_.frames[0].Copy();
321   JXL_CHECK(canvas.color.format.num_channels == 3);
322   JXL_CHECK(canvas.color.format.data_type == JXL_TYPE_UINT8);
323   JXL_CHECK(canvas.extra_channels.size() == 1);
324   for (size_t i = 1; i < ppf_.frames.size(); i++) {
325     const extras::PackedFrame& frame = ppf_.frames[i];
326     JXL_CHECK(frame.extra_channels.size() == 1);
327     const JxlLayerInfo& layer_info = frame.frame_info.layer_info;
328     extras::PackedFrame rendered = canvas.Copy();
329     uint8_t* pixels_rendered =
330         reinterpret_cast<uint8_t*>(rendered.color.pixels());
331     const uint8_t* pixels_frame =
332         reinterpret_cast<const uint8_t*>(frame.color.pixels());
333     uint8_t* alpha_rendered =
334         reinterpret_cast<uint8_t*>(rendered.extra_channels[0].pixels());
335     const uint8_t* alpha_frame =
336         reinterpret_cast<const uint8_t*>(frame.extra_channels[0].pixels());
337     for (size_t y = 0; y < frame.color.ysize; y++) {
338       for (size_t x = 0; x < frame.color.xsize; x++) {
339         size_t idx_frame = y * frame.color.xsize + x;
340         size_t idx_rendered = ((layer_info.crop_y0 + y) * rendered.color.xsize +
341                                (layer_info.crop_x0 + x));
342         if (alpha_frame[idx_frame] != 0) {
343           memcpy(&pixels_rendered[idx_rendered * 3],
344                  &pixels_frame[idx_frame * 3], 3);
345           alpha_rendered[idx_rendered] = alpha_frame[idx_frame];
346         }
347       }
348     }
349     if (layer_info.save_as_reference != 0) {
350       canvas = rendered.Copy();
351     }
352     ppf_.frames[i] = std::move(rendered);
353   }
354   return *this;
355 }
356
357 TestImage::Frame::Frame(TestImage* parent, bool is_preview, size_t index)
358     : parent_(parent), is_preview_(is_preview), index_(index) {}
359
360 void TestImage::Frame::ZeroFill() {
361   memset(frame().color.pixels(), 0, frame().color.pixels_size);
362   for (auto& ec : frame().extra_channels) {
363     memset(ec.pixels(), 0, ec.pixels_size);
364   }
365 }
366
367 void TestImage::Frame::RandomFill(uint16_t seed) {
368   FillPackedImage(ppf().info.bits_per_sample, seed, &frame().color);
369   for (size_t i = 0; i < ppf().extra_channels_info.size(); ++i) {
370     FillPackedImage(ppf().extra_channels_info[i].ec_info.bits_per_sample,
371                     seed + 1 + i, &frame().extra_channels[i]);
372   }
373 }
374
375 void TestImage::Frame::SetValue(size_t y, size_t x, size_t c, float val) {
376   const extras::PackedImage& color = frame().color;
377   JxlPixelFormat format = color.format;
378   JXL_CHECK(y < ppf().info.ysize);
379   JXL_CHECK(x < ppf().info.xsize);
380   JXL_CHECK(c < format.num_channels);
381   size_t pwidth = extras::PackedImage::BitsPerChannel(format.data_type) / 8;
382   size_t idx = ((y * color.xsize + x) * format.num_channels + c) * pwidth;
383   uint8_t* pixels = reinterpret_cast<uint8_t*>(frame().color.pixels());
384   uint8_t* p = pixels + idx;
385   StoreValue(val, ppf().info.bits_per_sample, frame().color.format, &p);
386 }
387
388 TestImage::Frame TestImage::AddFrame() {
389   size_t index = ppf_.frames.size();
390   extras::PackedFrame frame(ppf_.info.xsize, ppf_.info.ysize, format_);
391   for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) {
392     JxlPixelFormat ec_format = {1, format_.data_type, format_.endianness, 0};
393     extras::PackedImage image(ppf_.info.xsize, ppf_.info.ysize, ec_format);
394     frame.extra_channels.emplace_back(std::move(image));
395   }
396   ppf_.frames.emplace_back(std::move(frame));
397   return Frame(this, false, index);
398 }
399
400 TestImage::Frame TestImage::AddPreview(size_t xsize, size_t ysize) {
401   extras::PackedFrame frame(xsize, ysize, format_);
402   for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) {
403     JxlPixelFormat ec_format = {1, format_.data_type, format_.endianness, 0};
404     extras::PackedImage image(xsize, ysize, ec_format);
405     frame.extra_channels.emplace_back(std::move(image));
406   }
407   ppf_.preview_frame = make_unique<extras::PackedFrame>(std::move(frame));
408   return Frame(this, true, 0);
409 }
410
411 void TestImage::CropLayerInfo(size_t xsize, size_t ysize, JxlLayerInfo* info) {
412   if (info->crop_x0 < static_cast<ssize_t>(xsize)) {
413     info->xsize = std::min<size_t>(info->xsize, xsize - info->crop_x0);
414   } else {
415     info->xsize = 0;
416   }
417   if (info->crop_y0 < static_cast<ssize_t>(ysize)) {
418     info->ysize = std::min<size_t>(info->ysize, ysize - info->crop_y0);
419   } else {
420     info->ysize = 0;
421   }
422 }
423
424 void TestImage::CropImage(size_t xsize, size_t ysize,
425                           extras::PackedImage* image) {
426   size_t new_stride = (image->stride / image->xsize) * xsize;
427   uint8_t* buf = reinterpret_cast<uint8_t*>(image->pixels());
428   for (size_t y = 0; y < ysize; ++y) {
429     memmove(&buf[y * new_stride], &buf[y * image->stride], new_stride);
430   }
431   image->xsize = xsize;
432   image->ysize = ysize;
433   image->stride = new_stride;
434   image->pixels_size = ysize * new_stride;
435 }
436
437 JxlDataType TestImage::DefaultDataType(const JxlBasicInfo& info) {
438   if (info.bits_per_sample == 16 && info.exponent_bits_per_sample == 5) {
439     return JXL_TYPE_FLOAT16;
440   } else if (info.exponent_bits_per_sample > 0 || info.bits_per_sample > 16) {
441     return JXL_TYPE_FLOAT;
442   } else if (info.bits_per_sample > 8) {
443     return JXL_TYPE_UINT16;
444   } else {
445     return JXL_TYPE_UINT8;
446   }
447 }
448
449 }  // namespace test
450 }  // namespace jxl