• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/decoder/logical_astc_block.h"
16 #include "src/decoder/test/image_utils.h"
17 
18 #include <gtest/gtest.h>
19 #include <gmock/gmock.h>
20 
21 #include <fstream>
22 #include <string>
23 
24 namespace astc_codec {
25 
26 namespace {
27 
28 using ::testing::Eq;
29 using ::testing::ElementsAre;
30 using ::testing::TestWithParam;
31 using ::testing::ValuesIn;
32 
LoadGoldenImageWithAlpha(std::string basename)33 ImageBuffer LoadGoldenImageWithAlpha(std::string basename) {
34   const std::string filename = std::string("src/decoder/testdata/") + basename + ".bmp";
35   ImageBuffer result;
36   LoadGoldenBmp(filename, &result);
37   EXPECT_EQ(result.BytesPerPixel(), 4);
38   return result;
39 }
40 
LoadGoldenImage(std::string basename)41 ImageBuffer LoadGoldenImage(std::string basename) {
42   const std::string filename = std::string("src/decoder/testdata/") + basename + ".bmp";
43   ImageBuffer result;
44   LoadGoldenBmp(filename, &result);
45   EXPECT_EQ(result.BytesPerPixel(), 3);
46   return result;
47 }
48 
49 struct ImageTestParams {
50   std::string image_name;
51   bool has_alpha;
52   Footprint footprint;
53   int width;
54   int height;
55 };
56 
PrintTo(const ImageTestParams & params,std::ostream * os)57 static void PrintTo(const ImageTestParams& params, std::ostream* os) {
58     *os << "ImageTestParams(" << params.image_name << ", "
59         << params.width << "x" << params.height << ", "
60         << (params.has_alpha ? "RGBA" : "RGB") << ", "
61         << "footprint " << params.footprint.Width() << "x"
62         << params.footprint.Height() << ")";
63 }
64 
65 class LogicalASTCBlockTest : public TestWithParam<ImageTestParams> { };
66 
67 // Test to make sure that reading out color values from blocks is not
68 // terribly wrong. To do so, we compress an image and then decompress it
69 // using our logical blocks and the library. The difference between the
70 // decoded images should be minimal.
TEST_P(LogicalASTCBlockTest,ImageWithFootprint)71 TEST_P(LogicalASTCBlockTest, ImageWithFootprint) {
72   const auto& params = GetParam();
73   const std::string astc = LoadASTCFile(params.image_name);
74 
75   ImageBuffer our_decoded_image;
76   our_decoded_image.Allocate(params.width, params.height, params.has_alpha ? 4 : 3);
77 
78   const int block_width = params.footprint.Width();
79   const int block_height = params.footprint.Height();
80 
81   base::UInt128 block;
82   for (int i = 0; i < astc.size(); i += 16) {
83     const int block_index = i / 16;
84     const int blocks_wide =
85         (params.width + block_width - 1) / block_width;
86     const int block_x = block_index % blocks_wide;
87     const int block_y = block_index / blocks_wide;
88     memcpy(&block, astc.data() + i, sizeof(block));
89 
90     PhysicalASTCBlock physical_block(block);
91     if (physical_block.IsVoidExtent()) {
92       auto ve = UnpackVoidExtent(physical_block);
93       ASSERT_TRUE(ve) << "ASTC encoder produced invalid block!";
94     } else {
95       auto ib = UnpackIntermediateBlock(physical_block);
96       ASSERT_TRUE(ib) << "ASTC encoder produced invalid block!";
97     }
98 
99     // Make sure that the library doesn't produce incorrect ASTC blocks.
100     // This is covered in more depth in other tests in
101     // intermediate_astc_block_test and physical_astc_block_test
102     auto lb = UnpackLogicalBlock(params.footprint, physical_block);
103     ASSERT_TRUE(lb) << "ASTC encoder produced invalid block!";
104 
105     LogicalASTCBlock logical_block = lb.value();
106     const size_t color_size = params.has_alpha ? 4 : 3;
107 
108     for (int y = 0; y < block_height; ++y) {
109       for (int x = 0; x < block_width; ++x) {
110         const int px = block_width * block_x + x;
111         const int py = block_height * block_y + y;
112 
113         // Skip out of bounds.
114         if (px >= params.width || py >= params.height) {
115           continue;
116         }
117 
118         uint8_t* pixel = our_decoded_image(px, py);
119         const RgbaColor decoded_color = logical_block.ColorAt(x, y);
120         ASSERT_LE(color_size, decoded_color.size());
121 
122         for (int c = 0; c < color_size; ++c) {
123           // All of the pixels should also be 8-bit values.
124           ASSERT_GE(decoded_color[c], 0);
125           ASSERT_LT(decoded_color[c], 256);
126           pixel[c] = decoded_color[c];
127         }
128       }
129     }
130   }
131 
132   // Check that the decoded image is *very* similar to the library decoding
133   // of an ASTC texture. They may not be exact due to differences in how we
134   // convert a 16-bit float to an 8-bit integer.
135   ImageBuffer decoded_image = params.has_alpha ? LoadGoldenImageWithAlpha(params.image_name) : LoadGoldenImage(params.image_name);
136   CompareSumOfSquaredDifferences(decoded_image, our_decoded_image, 1.0);
137 }
138 
139 // Test to make sure that a simple gradient image can be compressed and decoded
140 // by our logical block representation. This should work with every footprint.
GetSyntheticImageTestParams()141 std::vector<ImageTestParams> GetSyntheticImageTestParams() {
142   return {
143     // image_name         alpha    astc footprint          width  height
144     { "footprint_4x4",    false,   Footprint::Get4x4(),    32,    32 },
145     { "footprint_5x4",    false,   Footprint::Get5x4(),    32,    32 },
146     { "footprint_5x5",    false,   Footprint::Get5x5(),    32,    32 },
147     { "footprint_6x5",    false,   Footprint::Get6x5(),    32,    32 },
148     { "footprint_6x6",    false,   Footprint::Get6x6(),    32,    32 },
149     { "footprint_8x5",    false,   Footprint::Get8x5(),    32,    32 },
150     { "footprint_8x6",    false,   Footprint::Get8x6(),    32,    32 },
151     { "footprint_10x5",   false,   Footprint::Get10x5(),   32,    32 },
152     { "footprint_10x6",   false,   Footprint::Get10x6(),   32,    32 },
153     { "footprint_8x8",    false,   Footprint::Get8x8(),    32,    32 },
154     { "footprint_10x8",   false,   Footprint::Get10x8(),   32,    32 },
155     { "footprint_10x10",  false,   Footprint::Get10x10(),  32,    32 },
156     { "footprint_12x10",  false,   Footprint::Get12x10(),  32,    32 },
157     { "footprint_12x12",  false,   Footprint::Get12x12(),  32,    32 },
158   };
159 }
160 
161 INSTANTIATE_TEST_CASE_P(Synthetic, LogicalASTCBlockTest,
162                         ValuesIn(GetSyntheticImageTestParams()));
163 
164 // Test to make sure that reading out color values from blocks in a real-world
165 // image isn't terribly wrong, either.
GetRealWorldImageTestParams()166 std::vector<ImageTestParams> GetRealWorldImageTestParams() {
167   return {
168     // image_name   alpha  astc footprint         width   height
169     { "rgb_4x4",    false, Footprint::Get4x4(),   224,    288 },
170     { "rgb_6x6",    false, Footprint::Get6x6(),   224,    288 },
171     { "rgb_8x8",    false, Footprint::Get8x8(),   224,    288 },
172     { "rgb_12x12",  false, Footprint::Get12x12(), 224,    288 },
173     { "rgb_5x4",    false, Footprint::Get5x4(),   224,    288 }
174   };
175 }
176 
177 INSTANTIATE_TEST_CASE_P(RealWorld, LogicalASTCBlockTest,
178                         ValuesIn(GetRealWorldImageTestParams()));
179 
180 // Test to make sure that reading out color values from blocks in a real-world
181 // image isn't terribly wrong, either.
GetTransparentImageTestParams()182 std::vector<ImageTestParams> GetTransparentImageTestParams() {
183   return {
184     // image_name          alpha  astc footprint       width    height
185     { "atlas_small_4x4",   true,  Footprint::Get4x4(), 256,     256 },
186     { "atlas_small_5x5",   true,  Footprint::Get5x5(), 256,     256 },
187     { "atlas_small_6x6",   true,  Footprint::Get6x6(), 256,     256 },
188     { "atlas_small_8x8",   true,  Footprint::Get8x8(), 256,     256 },
189   };
190 }
191 
192 INSTANTIATE_TEST_CASE_P(Transparent, LogicalASTCBlockTest,
193                         ValuesIn(GetTransparentImageTestParams()));
194 
195 // Test to make sure that if we set our endpoints then it's reflected in our
196 // color selection
TEST(LogicalASTCBlockTest,SetEndpoints)197 TEST(LogicalASTCBlockTest, SetEndpoints) {
198   LogicalASTCBlock logical_block(Footprint::Get8x8());
199 
200   // Setup a weight checkerboard
201   for (int j = 0; j < 8; ++j) {
202     for (int i = 0; i < 8; ++i) {
203       if (((i ^ j) & 1) == 1) {
204         logical_block.SetWeightAt(i, j, 0);
205       } else {
206         logical_block.SetWeightAt(i, j, 64);
207       }
208     }
209   }
210 
211   // Now set the colors to something ridiculous
212   logical_block.SetEndpoints({{ 123, 45, 67, 89 }}, {{ 101, 121, 31, 41 }}, 0);
213 
214   // For each pixel, we expect it to mirror the endpoints in a checkerboard
215   // pattern
216   for (int j = 0; j < 8; ++j) {
217     for (int i = 0; i < 8; ++i) {
218       if (((i ^ j) & 1) == 1) {
219         EXPECT_THAT(logical_block.ColorAt(i, j), ElementsAre(123, 45, 67, 89));
220       } else {
221         EXPECT_THAT(logical_block.ColorAt(i, j), ElementsAre(101, 121, 31, 41));
222       }
223     }
224   }
225 }
226 
227 // Test whether or not setting weight values under different circumstances is
228 // supported and reflected in the query functions.
TEST(LogicalASTCBlockTest,SetWeightVals)229 TEST(LogicalASTCBlockTest, SetWeightVals) {
230   LogicalASTCBlock logical_block(Footprint::Get4x4());
231 
232   EXPECT_THAT(logical_block.GetFootprint(), Eq(Footprint::Get4x4()));
233 
234   // Not a dual plane by default
235   EXPECT_FALSE(logical_block.IsDualPlane());
236   logical_block.SetWeightAt(2, 3, 2);
237 
238   // Set the dual plane
239   logical_block.SetDualPlaneChannel(0);
240   EXPECT_TRUE(logical_block.IsDualPlane());
241 
242   // This shouldn't have reset our weight
243   const LogicalASTCBlock other_block = logical_block;
244   EXPECT_THAT(other_block.WeightAt(2, 3), Eq(2));
245   EXPECT_THAT(other_block.DualPlaneWeightAt(0, 2, 3), Eq(2));
246 
247   // If we set the dual plane weight, it shouldn't change the original weight
248   // value or the other channels
249   logical_block.SetDualPlaneWeightAt(0, 2, 3, 1);
250   EXPECT_THAT(logical_block.WeightAt(2, 3), Eq(2));
251   EXPECT_THAT(logical_block.DualPlaneWeightAt(0, 2, 3), Eq(1));
252   for (int i = 1; i < 4; ++i) {
253     EXPECT_THAT(logical_block.DualPlaneWeightAt(i, 2, 3), Eq(2));
254   }
255 
256   // Remove the dual plane
257   logical_block.SetDualPlaneChannel(-1);
258   EXPECT_FALSE(logical_block.IsDualPlane());
259 
260   // Now the original dual plane weight should be reset back to the others. Note
261   // that we have to call DualPlaneWeightAt from a const logical block since
262   // returning a reference to a weight that doesn't exist is illegal.
263   const LogicalASTCBlock other_block2 = logical_block;
264   EXPECT_THAT(logical_block.WeightAt(2, 3), Eq(2));
265   for (int i = 0; i < 4; ++i) {
266     EXPECT_EQ(logical_block.WeightAt(2, 3),
267               other_block2.DualPlaneWeightAt(i, 2, 3));
268   }
269 }
270 
271 }  // namespace
272 
273 }  // namespace astc_codec
274