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 <stdint.h>
7 #include <stdio.h>
8 
9 #include <algorithm>
10 #include <utility>
11 #include <vector>
12 
13 #include "gtest/gtest.h"
14 #include "lib/jxl/ac_strategy.h"
15 #include "lib/jxl/base/compiler_specific.h"
16 #include "lib/jxl/common.h"
17 #include "lib/jxl/dec_reconstruct.h"
18 #include "lib/jxl/epf.h"
19 #include "lib/jxl/image.h"
20 #include "lib/jxl/image_bundle.h"
21 #include "lib/jxl/image_ops.h"
22 #include "lib/jxl/image_test_utils.h"
23 #include "lib/jxl/loop_filter.h"
24 #include "lib/jxl/quant_weights.h"
25 #include "lib/jxl/quantizer.h"
26 #include "lib/jxl/test_utils.h"
27 
28 namespace jxl {
29 namespace {
30 
31 const size_t xsize = 16;
32 const size_t ysize = 8;
33 
GenerateFlat(const float background,const float foreground,std::vector<Image3F> * images)34 void GenerateFlat(const float background, const float foreground,
35                   std::vector<Image3F>* images) {
36   for (size_t c = 0; c < Image3F::kNumPlanes; ++c) {
37     Image3F in(xsize, ysize);
38     // Plane c = foreground, all others = background.
39     for (size_t y = 0; y < ysize; ++y) {
40       float* rows[3] = {in.PlaneRow(0, y), in.PlaneRow(1, y),
41                         in.PlaneRow(2, y)};
42       for (size_t x = 0; x < xsize; ++x) {
43         rows[0][x] = rows[1][x] = rows[2][x] = background;
44         rows[c][x] = foreground;
45       }
46     }
47     images->push_back(std::move(in));
48   }
49 }
50 
51 // Single foreground point at any position in any channel
GeneratePoints(const float background,const float foreground,std::vector<Image3F> * images)52 void GeneratePoints(const float background, const float foreground,
53                     std::vector<Image3F>* images) {
54   for (size_t c = 0; c < Image3F::kNumPlanes; ++c) {
55     for (size_t y = 0; y < ysize; ++y) {
56       for (size_t x = 0; x < xsize; ++x) {
57         Image3F in(xsize, ysize);
58         FillImage(background, &in);
59         in.PlaneRow(c, y)[x] = foreground;
60         images->push_back(std::move(in));
61       }
62     }
63   }
64 }
65 
GenerateHorzEdges(const float background,const float foreground,std::vector<Image3F> * images)66 void GenerateHorzEdges(const float background, const float foreground,
67                        std::vector<Image3F>* images) {
68   for (size_t c = 0; c < Image3F::kNumPlanes; ++c) {
69     // Begin of foreground rows
70     for (size_t y = 1; y < ysize; ++y) {
71       Image3F in(xsize, ysize);
72       FillImage(background, &in);
73       for (size_t iy = y; iy < ysize; ++iy) {
74         std::fill(in.PlaneRow(c, iy), in.PlaneRow(c, iy) + xsize, foreground);
75       }
76       images->push_back(std::move(in));
77     }
78   }
79 }
80 
GenerateVertEdges(const float background,const float foreground,std::vector<Image3F> * images)81 void GenerateVertEdges(const float background, const float foreground,
82                        std::vector<Image3F>* images) {
83   for (size_t c = 0; c < Image3F::kNumPlanes; ++c) {
84     // Begin of foreground columns
85     for (size_t x = 1; x < xsize; ++x) {
86       Image3F in(xsize, ysize);
87       FillImage(background, &in);
88       for (size_t iy = 0; iy < ysize; ++iy) {
89         float* JXL_RESTRICT row = in.PlaneRow(c, iy);
90         for (size_t ix = x; ix < xsize; ++ix) {
91           row[ix] = foreground;
92         }
93       }
94       images->push_back(std::move(in));
95     }
96   }
97 }
98 
DumpTestImage(const char * name,const Image3F & img)99 void DumpTestImage(const char* name, const Image3F& img) {
100   fprintf(stderr, "Image %s:\n", name);
101   for (size_t y = 0; y < img.ysize(); ++y) {
102     const float* row_x = img.ConstPlaneRow(0, y);
103     const float* row_y = img.ConstPlaneRow(1, y);
104     const float* row_b = img.ConstPlaneRow(2, y);
105     for (size_t x = 0; x < img.xsize(); ++x) {
106       fprintf(stderr, "%5.1f|%5.1f|%5.1f ", row_x[x], row_y[x], row_b[x]);
107     }
108     fprintf(stderr, "\n");
109   }
110   fprintf(stderr, "\n");
111 }
112 
113 // Ensures input remains unchanged by filter - verifies the edge-preserving
114 // nature of the filter because inputs are piecewise constant.
EnsureUnchanged(const float background,const float foreground,uint32_t epf_iters)115 void EnsureUnchanged(const float background, const float foreground,
116                      uint32_t epf_iters) {
117   std::vector<Image3F> images;
118   GenerateFlat(background, foreground, &images);
119   GeneratePoints(background, foreground, &images);
120   GenerateHorzEdges(background, foreground, &images);
121   GenerateVertEdges(background, foreground, &images);
122 
123   CodecMetadata metadata;
124   JXL_CHECK(metadata.size.Set(xsize, ysize));
125   metadata.m.xyb_encoded = false;
126   FrameHeader frame_header(&metadata);
127   // Ensure no CT is applied
128   frame_header.color_transform = ColorTransform::kNone;
129   LoopFilter& lf = frame_header.loop_filter;
130   lf.gab = false;
131   lf.epf_iters = epf_iters;
132   FrameDimensions frame_dim = frame_header.ToFrameDimensions();
133 
134   jxl::PassesDecoderState state;
135   JXL_CHECK(
136       jxl::InitializePassesSharedState(frame_header, &state.shared_storage));
137   JXL_CHECK(state.Init());
138   JXL_CHECK(state.InitForAC(/*pool=*/nullptr));
139 
140   JXL_CHECK(state.filter_weights.Init(lf, frame_dim));
141   FillImage(-0.5f, &state.filter_weights.sigma);
142 
143   for (size_t idx_image = 0; idx_image < images.size(); ++idx_image) {
144     const Image3F& in = images[idx_image];
145     state.decoded = CopyImage(in);
146 
147     ImageBundle out(&metadata.m);
148     out.SetFromImage(CopyImage(in), ColorEncoding::LinearSRGB());
149     FillImage(-99.f, out.color());  // Initialized with garbage.
150     Image3F padded = PadImageMirror(in, 2 * kBlockDim, 0);
151     // Call with `force_fir` set to true to force to apply filters to all of the
152     // input image.
153     JXL_CHECK(FinalizeFrameDecoding(&out, &state, /*pool=*/nullptr,
154                                     /*force_fir=*/true,
155                                     /*skip_blending=*/true, /*move_ec=*/true));
156 
157 #if JXL_HIGH_PRECISION
158     VerifyRelativeError(in, *out.color(), 1E-3, 1E-4);
159 #else
160     VerifyRelativeError(in, *out.color(), 1E-2, 1E-2);
161 #endif
162     if (testing::Test::HasFatalFailure()) {
163       DumpTestImage("in", in);
164       DumpTestImage("out", *out.color());
165     }
166   }
167 }
168 
169 }  // namespace
170 
171 class AdaptiveReconstructionTest : public testing::TestWithParam<uint32_t> {};
172 
173 JXL_GTEST_INSTANTIATE_TEST_SUITE_P(EPFItersGroup, AdaptiveReconstructionTest,
174                                    testing::Values(1, 2, 3),
175                                    testing::PrintToStringParamName());
176 
TEST_P(AdaptiveReconstructionTest,TestBright)177 TEST_P(AdaptiveReconstructionTest, TestBright) {
178   EnsureUnchanged(1.0f, 128.0f, GetParam());
179 }
TEST_P(AdaptiveReconstructionTest,TestDark)180 TEST_P(AdaptiveReconstructionTest, TestDark) {
181   EnsureUnchanged(128.0f, 1.0f, GetParam());
182 }
183 
184 }  // namespace jxl
185