aboutsummaryrefslogtreecommitdiff
path: root/src/decoder/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/decoder/test')
-rw-r--r--src/decoder/test/astc_fuzzer.cc36
-rw-r--r--src/decoder/test/codec_test.cc181
-rw-r--r--src/decoder/test/endpoint_codec_test.cc464
-rw-r--r--src/decoder/test/footprint_test.cc97
-rw-r--r--src/decoder/test/image_utils.h217
-rw-r--r--src/decoder/test/integer_sequence_codec_test.cc337
-rw-r--r--src/decoder/test/intermediate_astc_block_test.cc453
-rw-r--r--src/decoder/test/logical_astc_block_test.cc273
-rw-r--r--src/decoder/test/partition_test.cc263
-rw-r--r--src/decoder/test/physical_astc_block_test.cc361
-rw-r--r--src/decoder/test/quantization_test.cc288
-rw-r--r--src/decoder/test/weight_infill_test.cc69
12 files changed, 3039 insertions, 0 deletions
diff --git a/src/decoder/test/astc_fuzzer.cc b/src/decoder/test/astc_fuzzer.cc
new file mode 100644
index 0000000..f152675
--- /dev/null
+++ b/src/decoder/test/astc_fuzzer.cc
@@ -0,0 +1,36 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ASTC fuzzing wrapper to help with fuzz testing.
+
+#include "src/decoder/codec.h"
+
+#include <benchmark/benchmark.h>
+
+#include <vector>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::string error;
+ std::unique_ptr<astc_codec::ASTCFile> file =
+ astc_codec::ASTCFile::LoadFromMemory(reinterpret_cast<const char*>(data),
+ size, &error);
+ if (file) {
+ std::vector<uint8_t> out_buffer(file->GetWidth() * file->GetHeight() * 4);
+ bool result = astc_codec::DecompressToImage(
+ *file, out_buffer.data(), out_buffer.size(), file->GetWidth() * 4);
+ benchmark::DoNotOptimize(result);
+ }
+
+ return 0;
+}
diff --git a/src/decoder/test/codec_test.cc b/src/decoder/test/codec_test.cc
new file mode 100644
index 0000000..936eed3
--- /dev/null
+++ b/src/decoder/test/codec_test.cc
@@ -0,0 +1,181 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/codec.h"
+#include "include/astc-codec/astc-codec.h"
+#include "src/decoder/test/image_utils.h"
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+namespace astc_codec {
+
+static void PrintTo(FootprintType footprint, std::ostream* os) {
+ switch (footprint) {
+ case FootprintType::k4x4: *os << "FootprintType::k4x4"; break;
+ case FootprintType::k5x4: *os << "FootprintType::k5x4"; break;
+ case FootprintType::k5x5: *os << "FootprintType::k5x5"; break;
+ case FootprintType::k6x5: *os << "FootprintType::k6x5"; break;
+ case FootprintType::k6x6: *os << "FootprintType::k6x6"; break;
+ case FootprintType::k8x5: *os << "FootprintType::k8x5"; break;
+ case FootprintType::k8x6: *os << "FootprintType::k8x6"; break;
+ case FootprintType::k10x5: *os << "FootprintType::k10x5"; break;
+ case FootprintType::k10x6: *os << "FootprintType::k10x6"; break;
+ case FootprintType::k8x8: *os << "FootprintType::k8x8"; break;
+ case FootprintType::k10x8: *os << "FootprintType::k10x8"; break;
+ case FootprintType::k10x10: *os << "FootprintType::k10x10"; break;
+ case FootprintType::k12x10: *os << "FootprintType::k12x10"; break;
+ case FootprintType::k12x12: *os << "FootprintType::k12x12"; break;
+ default:
+ *os << "<Unexpected FootprintType "
+ << static_cast<uint32_t>(footprint) << ">";
+ }
+}
+
+namespace {
+
+using ::testing::TestWithParam;
+using ::testing::ValuesIn;
+
+ImageBuffer LoadGoldenImageWithAlpha(std::string basename) {
+ const std::string filename =
+ std::string("src/decoder/testdata/") + basename + ".bmp";
+ ImageBuffer result;
+ LoadGoldenBmp(filename, &result);
+ EXPECT_EQ(result.BytesPerPixel(), 4);
+ return result;
+}
+
+struct ImageTestParams {
+ std::string image_name;
+ FootprintType footprint;
+ size_t width;
+ size_t height;
+};
+
+static void PrintTo(const ImageTestParams& params, std::ostream* os) {
+ *os << "ImageTestParams(" << params.image_name << ", " << params.width << "x"
+ << params.height << ", ";
+ PrintTo(params.footprint, os);
+ *os << ")";
+}
+
+TEST(CodecTest, InvalidInput) {
+ const size_t valid_width = 16;
+ const size_t valid_height = 16;
+ const size_t valid_stride = valid_width * 4;
+
+ const std::vector<uint8_t> data(256);
+ std::vector<uint8_t> output(valid_width * valid_height * 4);
+
+ // Invalid footprint.
+ EXPECT_FALSE(ASTCDecompressToRGBA(
+ data.data(), data.size(), valid_width, valid_height,
+ FootprintType::kCount, output.data(), output.size(), valid_stride));
+
+ // Fail for 0 width or height.
+ EXPECT_FALSE(ASTCDecompressToRGBA(data.data(), data.size(), 0, valid_height,
+ FootprintType::k4x4, output.data(),
+ output.size(), valid_stride));
+ EXPECT_FALSE(ASTCDecompressToRGBA(data.data(), data.size(), valid_width, 0,
+ FootprintType::k4x4, output.data(),
+ output.size(), valid_stride));
+
+ // Fail for data size that's not a multiple of block size.
+ EXPECT_FALSE(ASTCDecompressToRGBA(
+ data.data(), data.size() - 1, valid_width, valid_height,
+ FootprintType::k4x4, output.data(), output.size(), valid_stride));
+ // Fail for data size that doesn't match the block count.
+ EXPECT_FALSE(ASTCDecompressToRGBA(
+ data.data(), data.size() - 16, valid_width, valid_height,
+ FootprintType::k4x4, output.data(), output.size(), valid_stride));
+
+ // Fail for invalid stride.
+ EXPECT_FALSE(ASTCDecompressToRGBA(
+ data.data(), data.size(), valid_width, valid_height, FootprintType::k4x4,
+ output.data(), output.size(), valid_stride - 1));
+
+ // Fail for invalid output size.
+ EXPECT_FALSE(ASTCDecompressToRGBA(
+ data.data(), data.size(), valid_width, valid_height, FootprintType::k4x4,
+ output.data(), output.size() - 1, valid_stride));
+}
+
+class CodecTest : public TestWithParam<ImageTestParams> {};
+
+TEST_P(CodecTest, PublicAPI) {
+ const auto& params = GetParam();
+ const std::string astc = LoadASTCFile(params.image_name);
+
+ ImageBuffer our_decoded_image;
+ our_decoded_image.Allocate(params.width, params.height, 4);
+ ASSERT_TRUE(ASTCDecompressToRGBA(
+ reinterpret_cast<const uint8_t*>(astc.data()), astc.size(), params.width,
+ params.height, params.footprint, our_decoded_image.Data().data(),
+ our_decoded_image.DataSize(), our_decoded_image.Stride()));
+
+ // Check that the decoded image is *very* similar to the library decoding
+ // of an ASTC texture. They may not be exact due to differences in how we
+ // convert a 16-bit float to an 8-bit integer.
+ ImageBuffer decoded_image = LoadGoldenImageWithAlpha(params.image_name);
+ CompareSumOfSquaredDifferences(decoded_image, our_decoded_image, 1.0);
+}
+
+TEST_P(CodecTest, DecompressToImage) {
+ const auto& params = GetParam();
+
+ std::string error;
+ std::unique_ptr<ASTCFile> image_file = ASTCFile::LoadFile(
+ std::string("src/decoder/testdata/") + params.image_name + ".astc",
+ &error);
+ ASSERT_TRUE(image_file) << "Failed to load " << params.image_name << ": "
+ << error;
+
+ ASSERT_TRUE(image_file->GetFootprint());
+ EXPECT_EQ(params.footprint, image_file->GetFootprint().value().Type());
+
+ ImageBuffer our_decoded_image;
+ our_decoded_image.Allocate(image_file->GetWidth(), image_file->GetHeight(),
+ 4);
+
+ ASSERT_TRUE(DecompressToImage(*image_file, our_decoded_image.Data().data(),
+ our_decoded_image.DataSize(),
+ our_decoded_image.Stride()));
+
+ // Check that the decoded image is *very* similar to the library decoding
+ // of an ASTC texture. They may not be exact due to differences in how we
+ // convert a 16-bit float to an 8-bit integer.
+ ImageBuffer decoded_image = LoadGoldenImageWithAlpha(params.image_name);
+ CompareSumOfSquaredDifferences(decoded_image, our_decoded_image, 1.0);
+}
+
+// Test to make sure that reading out color values from blocks in a real-world
+// image isn't terribly wrong, either.
+std::vector<ImageTestParams> GetTransparentImageTestParams() {
+ return {
+ // image_name astc footprint width height
+ { "atlas_small_4x4", FootprintType::k4x4, 256, 256 },
+ { "atlas_small_5x5", FootprintType::k5x5, 256, 256 },
+ { "atlas_small_6x6", FootprintType::k6x6, 256, 256 },
+ { "atlas_small_8x8", FootprintType::k8x8, 256, 256 },
+ };
+}
+
+INSTANTIATE_TEST_CASE_P(Transparent, CodecTest,
+ ValuesIn(GetTransparentImageTestParams()));
+
+} // namespace
+
+} // namespace astc_codec
diff --git a/src/decoder/test/endpoint_codec_test.cc b/src/decoder/test/endpoint_codec_test.cc
new file mode 100644
index 0000000..f2fef54
--- /dev/null
+++ b/src/decoder/test/endpoint_codec_test.cc
@@ -0,0 +1,464 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/endpoint_codec.h"
+#include "src/decoder/intermediate_astc_block.h"
+#include "src/decoder/test/image_utils.h"
+
+#include <random>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+namespace astc_codec {
+
+namespace {
+
+using ::testing::AllOf;
+using ::testing::AnyOf;
+using ::testing::Each;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::Le;
+using ::testing::Ne;
+using ::testing::Optional;
+using ::testing::Pointwise;
+using ::testing::SizeIs;
+using ::testing::Pair;
+
+constexpr std::array<EndpointEncodingMode, 6> kEndpointEncodingModes = {{
+ EndpointEncodingMode::kDirectLuma,
+ EndpointEncodingMode::kDirectLumaAlpha,
+ EndpointEncodingMode::kBaseScaleRGB,
+ EndpointEncodingMode::kBaseScaleRGBA,
+ EndpointEncodingMode::kDirectRGB,
+ EndpointEncodingMode::kDirectRGBA }};
+
+const std::array<std::pair<RgbaColor, RgbaColor>, 3> kBlueContractPairs = {{
+ std::make_pair(RgbaColor{{ 22, 18, 30, 59 }},
+ RgbaColor{{ 162, 148, 155, 59 }}),
+ std::make_pair(RgbaColor{{ 22, 30, 27, 36 }},
+ RgbaColor{{ 228, 221, 207, 36 }}),
+ std::make_pair(RgbaColor{{ 54, 60, 55, 255 }},
+ RgbaColor{{ 23, 30, 27, 255 }})
+ }};
+
+// Used to directly initialize std::pairs of colors with initializer lists
+// e.g. MakeColors({{ r, g, b, a }}, {{ r, g, b, a }});
+std::pair<RgbaColor, RgbaColor> MakeColors(RgbaColor&& a, RgbaColor&& b) {
+ return std::make_pair(a, b);
+}
+
+// Returns |high| and |low| as they would be decoded using the quantization
+// factor |quant| for the ColorEndpointMode |mode|.
+std::pair<RgbaColor, RgbaColor> TestColors(
+ RgbaColor low, RgbaColor high, int quant, EndpointEncodingMode mode) {
+ ColorEndpointMode astc_mode;
+ std::vector<int> encoded;
+ const bool needs_swap =
+ EncodeColorsForMode(low, high, quant, mode, &astc_mode, &encoded);
+
+ RgbaColor decoded_low, decoded_high;
+ DecodeColorsForMode(encoded, quant, astc_mode, &decoded_low, &decoded_high);
+
+ if (needs_swap) {
+ return std::make_pair(decoded_high, decoded_low);
+ } else {
+ return std::make_pair(decoded_low, decoded_high);
+ }
+}
+
+// Returns true if the argument tuple entries only differ by at most x.
+MATCHER_P(IsCloseTo, x, "") {
+ const auto& a = ::testing::get<0>(arg);
+ const auto& b = ::testing::get<1>(arg);
+ return (a > b) ? ((a - b) <= x) : ((b - a) <= x);
+}
+
+// Test to make sure that the range of values that we get as they are
+// quantized remains within what we pass as |quant|.
+TEST(EndpointCodecTest, QuantRanges) {
+ const RgbaColor low {{ 0, 0, 0, 0 }};
+ const RgbaColor high {{ 255, 255, 255, 255 }};
+
+ std::vector<int> result;
+ for (const auto& mode : kEndpointEncodingModes) {
+ for (int i = 5; i < 256; ++i) {
+ ColorEndpointMode astc_mode;
+ const bool needs_swap =
+ EncodeColorsForMode(low, high, i, mode, &astc_mode, &result);
+ EXPECT_EQ(result.size(), NumValuesForEncodingMode(mode)) << i;
+ EXPECT_EQ(result.size(), NumColorValuesForEndpointMode(astc_mode)) << i;
+
+ // ASTC mode shouldn't use base/offset when endpoints are so far apart.
+ EXPECT_THAT(astc_mode, Ne(ColorEndpointMode::kLDRRGBBaseOffset));
+ EXPECT_THAT(astc_mode, Ne(ColorEndpointMode::kLDRRGBABaseOffset));
+
+ EXPECT_THAT(result, Each(AllOf(Ge(0), Le(i))))
+ << "Mode: " << static_cast<int>(mode);
+ // We don't care if we need to swap the weights in this test
+ EXPECT_TRUE(needs_swap || !needs_swap);
+ }
+ }
+}
+
+// Test to make sure that each mode that directly encodes colors can effectively
+// encode both black and white
+TEST(EndpointCodecTest, ExtremeDirectEncodings) {
+ const RgbaColor kWhite {{ 255, 255, 255, 255 }};
+ const RgbaColor kBlack {{ 0, 0, 0, 255 }};
+
+ std::vector<int> encoded;
+ for (const auto& mode : kEndpointEncodingModes) {
+ for (int i = 5; i < 256; ++i) {
+ const auto expected = std::make_pair(kWhite, kBlack);
+ EXPECT_EQ(TestColors(kWhite, kBlack, i, mode), expected)
+ << "Range: " << i << ", Mode: " << static_cast<int>(mode);
+ }
+ }
+}
+
+// According to the spec, this is used for colors close to gray. The values
+// chosen here were according to the spec.
+TEST(EndpointCodecTest, UsesBlueContract) {
+ std::vector<int> vals = { 132, 127, 116, 112, 183, 180, 31, 22 };
+ EXPECT_TRUE(UsesBlueContract(255, ColorEndpointMode::kLDRRGBDirect, vals));
+ EXPECT_TRUE(UsesBlueContract(255, ColorEndpointMode::kLDRRGBADirect, vals));
+
+ // For the offset modes the only way to trigger the blue contract mode is if
+ // we force the subtraction in the decoding procedure (See section C.2.14 of
+ // the spec), so we need to set the 7th bit to 1 for all of the odd-numbered
+ // values
+ vals[1] &= 0xBF;
+ vals[3] &= 0xBF;
+ vals[5] &= 0xBF;
+ vals[7] &= 0xBF;
+
+ EXPECT_FALSE(
+ UsesBlueContract(255, ColorEndpointMode::kLDRRGBBaseOffset, vals));
+ EXPECT_FALSE(
+ UsesBlueContract(255, ColorEndpointMode::kLDRRGBABaseOffset, vals));
+
+ vals[1] |= 0x40;
+ vals[3] |= 0x40;
+ vals[5] |= 0x40;
+ vals[7] |= 0x40;
+
+ EXPECT_TRUE(
+ UsesBlueContract(255, ColorEndpointMode::kLDRRGBBaseOffset, vals));
+ EXPECT_TRUE(
+ UsesBlueContract(255, ColorEndpointMode::kLDRRGBABaseOffset, vals));
+
+ // All other LDR endpoint modes should return no blue contract
+ for (int max_val : { 255, 127, 11 }) {
+ for (auto mode : { ColorEndpointMode::kLDRLumaDirect,
+ ColorEndpointMode::kLDRLumaBaseOffset,
+ ColorEndpointMode::kLDRLumaAlphaDirect,
+ ColorEndpointMode::kLDRLumaAlphaBaseOffset,
+ ColorEndpointMode::kLDRRGBBaseScale,
+ ColorEndpointMode::kLDRRGBBaseScaleTwoA }) {
+ EXPECT_FALSE(UsesBlueContract(max_val, mode, vals));
+ }
+ }
+}
+
+// Make sure that encoding and decoding for the direct luminance mode works.
+TEST(EndpointCodecTest, LumaDirect) {
+ const auto mode = EndpointEncodingMode::kDirectLuma;
+
+ // With a 255 quantizer, all greys should be exact.
+ for (int i = 0; i < 255; ++i) {
+ for (int j = 0; j < 255; ++j) {
+ EXPECT_EQ(TestColors({{ i, i, i, 255 }}, {{ j, j, j, 255 }}, 255, mode),
+ MakeColors({{ i, i, i, 255 }}, {{ j, j, j, 255 }}));
+ }
+ }
+
+ // If we have almost grey, then they should encode to grey.
+ EXPECT_EQ(TestColors({{ 247, 248, 246, 255 }}, {{ 2, 3, 1, 255 }}, 255, mode),
+ MakeColors({{ 247, 247, 247, 255 }}, {{ 2, 2, 2, 255 }}));
+
+ EXPECT_EQ(TestColors({{ 80, 80, 50, 255 }}, {{ 99, 255, 6, 255 }}, 255, mode),
+ MakeColors({{ 70, 70, 70, 255 }}, {{ 120, 120, 120, 255 }}));
+
+ // If we have almost greys and a really small quantizer, it should be white
+ // and black (literally).
+ EXPECT_EQ(TestColors({{ 247, 248, 246, 255 }}, {{ 2, 3, 1, 255 }}, 15, mode),
+ MakeColors({{ 255, 255, 255, 255 }}, {{ 0, 0, 0, 255 }}));
+
+ // The average of 64, 127, and 192 is 127.666..., so it should round to
+ // 130 instead of 125.
+ EXPECT_EQ(TestColors({{ 64, 127, 192, 255 }}, {{ 0, 0, 0, 255 }}, 63, mode),
+ MakeColors({{ 130, 130, 130, 255 }}, {{ 0, 0, 0, 255 }}));
+
+ // If we have almost grey, then they should encode to grey -- similar to
+ // direct encoding since the encoded colors differ by < 63.
+ EXPECT_EQ(TestColors({{ 80, 80, 50, 255 }}, {{ 99, 255, 6, 255 }}, 255, mode),
+ MakeColors({{ 70, 70, 70, 255 }}, {{ 120, 120, 120, 255 }}));
+
+ // Low precision colors should still encode pretty well with base/offset.
+ EXPECT_EQ(TestColors({{ 35, 36, 38, 255 }}, {{ 42, 43, 40, 255 }}, 47, mode),
+ MakeColors({{ 38, 38, 38, 255 }}, {{ 43, 43, 43, 255 }}));
+
+ EXPECT_EQ(TestColors({{ 39, 42, 40, 255 }}, {{ 18, 20, 21, 255 }}, 39, mode),
+ MakeColors({{ 39, 39, 39, 255 }}, {{ 19, 19, 19, 255 }}));
+}
+
+// Test encoding and decoding for the base-offset luminance mode.
+TEST(EndpointCodecTest, LumaAlphaDirect) {
+ const auto mode = EndpointEncodingMode::kDirectLumaAlpha;
+
+ // With a 255 quantizer, all greys should be exact.
+ for (int i = 0; i < 255; ++i) {
+ for (int j = 0; j < 255; ++j) {
+ EXPECT_EQ(TestColors({{ i, i, i, j }}, {{ j, j, j, i }}, 255, mode),
+ MakeColors({{ i, i, i, j }}, {{ j, j, j, i }}));
+ }
+ }
+
+ // If we have almost grey, then they should encode to grey.
+ EXPECT_EQ(TestColors({{ 247, 248, 246, 250 }}, {{ 2, 3, 1, 172 }}, 255, mode),
+ MakeColors({{ 247, 247, 247, 250 }}, {{ 2, 2, 2, 172 }}));
+
+ EXPECT_EQ(TestColors({{ 80, 80, 50, 0 }}, {{ 99, 255, 6, 255 }}, 255, mode),
+ MakeColors({{ 70, 70, 70, 0 }}, {{ 120, 120, 120, 255 }}));
+
+ // If we have almost greys and a really small quantizer, it should be white
+ // and black (literally).
+ EXPECT_EQ(TestColors({{ 247, 248, 246, 253 }}, {{ 2, 3, 1, 3 }}, 15, mode),
+ MakeColors({{ 255, 255, 255, 255 }}, {{ 0, 0, 0, 0 }}));
+
+ // The average of 64, 127, and 192 is 127.666..., so it should round to
+ // 130 instead of 125. The alpha in this case is independent.
+ EXPECT_EQ(TestColors({{ 64, 127, 192, 127 }}, {{ 0, 0, 0, 20 }}, 63, mode),
+ MakeColors({{ 130, 130, 130, 125 }}, {{ 0, 0, 0, 20 }}));
+}
+
+// Test encoding for the direct RGB mode.
+TEST(EndpointCodecTest, RGBDirect) {
+ const auto mode = EndpointEncodingMode::kDirectRGB;
+
+ // Colors should be encoded exactly with a 255 quantizer.
+ std::mt19937 random(0xdeadbeef);
+ std::uniform_int_distribution<int> byte_distribution(0, 255);
+
+ for (int i = 0; i < 100; ++i) {
+ RgbaColor low, high;
+ for (auto& x : high) { x = byte_distribution(random); }
+ for (auto& x : low) { x = byte_distribution(random); }
+ high[3] = low[3] = 255; // RGB Direct mode has opaque alpha.
+
+ EXPECT_EQ(TestColors(low, high, 255, mode), std::make_pair(low, high))
+ << "Random iter: " << i;
+ }
+
+ // For each of the following tests, order of endpoints shouldn't have any
+ // bearing on the quantization properties, so we should be able to switch
+ // endpoints as we see fit and have them generate the same flipped encoded
+ // pairs.
+
+ EXPECT_EQ(TestColors({{ 64, 127, 192, 255 }}, {{ 0, 0, 0, 255 }}, 63, mode),
+ MakeColors({{ 65, 125, 190, 255 }}, {{ 0, 0, 0, 255 }}));
+
+ EXPECT_EQ(TestColors({{ 0, 0, 0, 255 }}, {{ 64, 127, 192, 255 }}, 63, mode),
+ MakeColors({{ 0, 0, 0, 255 }}, {{ 65, 125, 190, 255 }}));
+
+ EXPECT_EQ(TestColors({{ 1, 2, 94, 255 }}, {{ 168, 255, 13, 255 }}, 7, mode),
+ MakeColors({{ 0, 0, 109, 255 }}, {{ 182, 255, 0, 255 }}));
+
+ // Colors close to grey will likely need a blue contract.
+ EXPECT_EQ(TestColors(kBlueContractPairs[0].first,
+ kBlueContractPairs[0].second, 31, mode),
+ MakeColors({{ 24, 20, 33, 255 }}, {{ 160, 148, 156, 255 }}));
+
+ EXPECT_EQ(TestColors(kBlueContractPairs[0].second,
+ kBlueContractPairs[0].first, 31, mode),
+ MakeColors({{ 160, 148, 156, 255 }}, {{ 24, 20, 33, 255 }}));
+
+ EXPECT_EQ(TestColors(kBlueContractPairs[1].first,
+ kBlueContractPairs[1].second, 7, mode),
+ MakeColors({{ 18, 36, 36, 255 }}, {{ 237, 219, 219, 255 }}));
+
+ EXPECT_EQ(TestColors(kBlueContractPairs[1].second,
+ kBlueContractPairs[1].first, 7, mode),
+ MakeColors({{ 237, 219, 219, 255 }}, {{ 18, 36, 36, 255 }}));
+
+ // Colors close to grey (and each other) will likely need a blue contract AND
+ // use the offset mode for encoding
+ EXPECT_EQ(TestColors(kBlueContractPairs[2].first,
+ kBlueContractPairs[2].second, 31, mode),
+ MakeColors({{ 53, 59, 53, 255 }}, {{ 24, 30, 26, 255 }}));
+
+ EXPECT_EQ(TestColors(kBlueContractPairs[2].second,
+ kBlueContractPairs[2].first, 31, mode),
+ MakeColors({{ 24, 30, 26, 255 }}, {{ 53, 59, 53, 255 }}));
+
+ // Colors close to each other, but not to grey will likely only use the offset
+ // mode and not the blue-contract modes.
+ EXPECT_EQ(TestColors({{ 22, 148, 30, 59 }}, {{ 162, 18, 155, 59 }}, 31, mode),
+ MakeColors({{ 24, 148, 33, 255 }}, {{ 165, 16, 156, 255 }}));
+
+ EXPECT_EQ(TestColors({{ 162, 18, 155, 59 }}, {{ 22, 148, 30, 59 }}, 31, mode),
+ MakeColors({{ 165, 16, 156, 255 }}, {{ 24, 148, 33, 255 }}));
+}
+
+// Make sure that certain endpoint pairs result in the blue-contract path as
+// we'd expect, such that we can make sure that we're hitting all of the encode
+// paths.
+TEST(EndpointCodecTest, RGBDirectMakesBlueContract) {
+ constexpr int kEndpointRange = 31;
+ for (const auto& endpoint_pair : kBlueContractPairs) {
+ ColorEndpointMode astc_mode;
+ std::vector<int> vals;
+ bool needs_swap = EncodeColorsForMode(
+ endpoint_pair.first, endpoint_pair.second,
+ kEndpointRange, EndpointEncodingMode::kDirectRGB, &astc_mode, &vals);
+ (void)(needs_swap); // Don't really care.
+
+ EXPECT_TRUE(UsesBlueContract(kEndpointRange, astc_mode, vals));
+ }
+}
+
+// Make sure that encoding and decoding for the RGB base-scale mode works.
+TEST(EndpointCodecTest, RGBBaseScale) {
+ const auto mode = EndpointEncodingMode::kBaseScaleRGB;
+ const auto close_to = [](RgbaColor c, int x) {
+ return Pointwise(IsCloseTo(x), c);
+ };
+
+ // Identical colors should be encoded with a 255 scale factor. Since ASTC
+ // decodes the scaled color by doing (x * s) >> 8, the decoded color will be
+ // multiplied by 255/256. This might cause rounding errors sometimes, so we
+ // check that every channel only deviates by 1.
+ std::mt19937 random(0xdeadbeef);
+ std::uniform_int_distribution<int> byte_distribution(0, 255);
+
+ for (int i = 0; i < 100; ++i) {
+ RgbaColor color{{byte_distribution(random), byte_distribution(random),
+ byte_distribution(random), 255}};
+ const auto test_result = TestColors(color, color, 255, mode);
+ EXPECT_THAT(test_result, Pair(close_to(color, 1), close_to(color, 1)));
+ }
+
+ // Make sure that if we want to scale by e.g. 1/4 then we can do that exactly:
+ const RgbaColor low = {{ 20, 4, 40, 255 }};
+ const RgbaColor high = {{ 80, 16, 160, 255 }};
+ EXPECT_THAT(TestColors(low, high, 255, mode),
+ Pair(close_to(low, 0), close_to(high, 0)));
+
+ // And if we quantize it, then we get roughly the same thing. The scale factor
+ // should be representable with most quantization levels. The problem is that
+ // if we're off on the 'high' color, then we will be off on the 'low' color.
+ EXPECT_THAT(TestColors(low, high, 127, mode),
+ Pair(close_to(low, 1), close_to(high, 1)));
+
+ EXPECT_THAT(TestColors(low, high, 63, mode),
+ Pair(close_to(low, 1), close_to(high, 2)));
+
+ EXPECT_THAT(TestColors(low, high, 31, mode),
+ Pair(close_to(low, 1), close_to(high, 4)));
+
+ EXPECT_THAT(TestColors(low, high, 15, mode),
+ Pair(close_to(low, 2), close_to(high, 8)));
+}
+
+// Make sure that encoding and decoding for the RGB base-offset mode works.
+// Since we don't have a decoder, this is currently only a test that should work
+// based on reasoning about what's written in the spec.
+// TODO(krajcevski): Write an encoder.
+TEST(EndpointCodecTest, RGBBaseOffset) {
+ const auto test_colors = [](const RgbaColor& low, const RgbaColor& high) {
+ const RgbaColor diff = {{ high[0] - low[0], high[1] - low[1],
+ high[2] - low[2], high[3] - low[3] }};
+
+ std::vector<int> vals;
+ for (int i = 0; i < 3; ++i) {
+ // If the base is "large", then it grabs it's most significant bit from
+ // the offset value. Hence, we need to save it here.
+ const bool is_large = low[i] >= 128;
+ vals.push_back((low[i] * 2) & 0xFF);
+ vals.push_back(diff[i] * 2);
+
+ // Give the "large" bases their bits back.
+ if (is_large) {
+ vals.back() |= 0x80;
+ }
+ }
+
+ RgbaColor dec_low, dec_high;
+ DecodeColorsForMode(vals, 255, ColorEndpointMode::kLDRRGBBaseOffset,
+ &dec_low, &dec_high);
+
+ EXPECT_THAT(std::make_pair(dec_low, dec_high), Pair(Eq(low), Eq(high)));
+ };
+
+ // Test the "direct encoding" path.
+ test_colors({{ 80, 16, 112, 255 }}, {{ 87, 18, 132, 255 }});
+ test_colors({{ 80, 74, 82, 255 }}, {{ 90, 92, 110, 255 }});
+ test_colors({{ 0, 0, 0, 255 }}, {{ 2, 2, 2, 255 }});
+
+ // Identical endpoints should always encode exactly, provided they satisfy the
+ // requirements for the base encoding.
+ std::mt19937 random(0xdeadbeef);
+ std::uniform_int_distribution<int> byte_distribution(0, 255);
+ for (int i = 0; i < 100; ++i) {
+ RgbaColor color{{byte_distribution(random), byte_distribution(random),
+ byte_distribution(random), 255}};
+ if ((color[0] | color[1] | color[2]) & 1) {
+ continue;
+ }
+ test_colors(color, color);
+ }
+
+ // TODO(google): Test the "blue contract" path.
+}
+
+// Make sure that we can decode colors that are given to us straight out of the
+// ASTC codec.
+TEST(EndpointCodecTest, DecodeCheckerboard) {
+ const RgbaColor kWhite {{ 255, 255, 255, 255 }};
+ const RgbaColor kBlack {{ 0, 0, 0, 255 }};
+
+ const std::string astc = LoadASTCFile("checkerboard");
+ for (int i = 0; i < astc.size(); i += 16) {
+ base::UInt128 block;
+ memcpy(&block, &astc[i], sizeof(block));
+
+ const auto intermediate = UnpackIntermediateBlock(PhysicalASTCBlock(block));
+ ASSERT_TRUE(intermediate) << "Block is void extent???";
+
+ const auto block_data = &intermediate.value();
+ ASSERT_THAT(block_data->endpoints, SizeIs(Eq(1)));
+
+ const int color_range = EndpointRangeForBlock(*block_data);
+ const auto& endpoints = block_data->endpoints[0];
+
+ RgbaColor low, high;
+ DecodeColorsForMode(endpoints.colors, color_range, endpoints.mode,
+ &low, &high);
+
+ // Expect that the endpoints are black and white, but either order.
+ EXPECT_THAT(std::make_pair(low, high),
+ AnyOf(
+ Pair(Eq(kWhite), Eq(kBlack)),
+ Pair(Eq(kBlack), Eq(kWhite))));
+ }
+}
+
+} // namespace
+
+} // namespace astc_codec
diff --git a/src/decoder/test/footprint_test.cc b/src/decoder/test/footprint_test.cc
new file mode 100644
index 0000000..6aef98a
--- /dev/null
+++ b/src/decoder/test/footprint_test.cc
@@ -0,0 +1,97 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/footprint.h"
+
+#include <array>
+#include <tuple>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+namespace astc_codec {
+
+namespace {
+
+TEST(FootprintTest, ParseAstcFootprintString) {
+ using ASTCTestPair = std::pair<std::string, Footprint>;
+ const std::array<ASTCTestPair, Footprint::NumValidFootprints()>
+ valid_footprints {{
+ std::make_pair("4x4", Footprint::Get4x4()),
+ std::make_pair("5x4", Footprint::Get5x4()),
+ std::make_pair("5x5", Footprint::Get5x5()),
+ std::make_pair("6x5", Footprint::Get6x5()),
+ std::make_pair("6x6", Footprint::Get6x6()),
+ std::make_pair("8x5", Footprint::Get8x5()),
+ std::make_pair("8x6", Footprint::Get8x6()),
+ std::make_pair("8x8", Footprint::Get8x8()),
+ std::make_pair("10x5", Footprint::Get10x5()),
+ std::make_pair("10x6", Footprint::Get10x6()),
+ std::make_pair("10x8", Footprint::Get10x8()),
+ std::make_pair("10x10", Footprint::Get10x10()),
+ std::make_pair("12x10", Footprint::Get12x10()),
+ std::make_pair("12x12", Footprint::Get12x12())
+ }};
+
+ for (const auto& test : valid_footprints) {
+ base::Optional<Footprint> footprint = Footprint::Parse(test.first.c_str());
+ EXPECT_TRUE(footprint);
+ EXPECT_EQ(test.second, footprint.value());
+ }
+
+ EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("")), "");
+ EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("3")), "");
+ EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("x")), "");
+ // Validly formed but out-of-bounds dimensions do not assert, otherwise
+ // malformed ASTC files could crash the decoder in debug builds.
+ EXPECT_FALSE(Footprint::Parse("9999999999x10"));
+ EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("ax8")), "");
+ EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("2x3x4")), "");
+ EXPECT_DEBUG_DEATH(EXPECT_FALSE(Footprint::Parse("-3x4")), "");
+ EXPECT_FALSE(Footprint::Parse("10x4"));
+}
+
+TEST(FootprintTest, Bitrates) {
+ EXPECT_NEAR(Footprint::Get4x4().Bitrate(), 8.f, 0.01f);
+ EXPECT_NEAR(Footprint::Get5x4().Bitrate(), 6.4f, 0.01f);
+ EXPECT_NEAR(Footprint::Get5x5().Bitrate(), 5.12f, 0.01f);
+ EXPECT_NEAR(Footprint::Get6x5().Bitrate(), 4.27f, 0.01f);
+ EXPECT_NEAR(Footprint::Get6x6().Bitrate(), 3.56f, 0.01f);
+ EXPECT_NEAR(Footprint::Get8x5().Bitrate(), 3.20f, 0.01f);
+ EXPECT_NEAR(Footprint::Get8x6().Bitrate(), 2.67f, 0.01f);
+ EXPECT_NEAR(Footprint::Get8x8().Bitrate(), 2.00f, 0.01f);
+ EXPECT_NEAR(Footprint::Get10x5().Bitrate(), 2.56f, 0.01f);
+ EXPECT_NEAR(Footprint::Get10x6().Bitrate(), 2.13f, 0.01f);
+ EXPECT_NEAR(Footprint::Get10x8().Bitrate(), 1.60f, 0.01f);
+ EXPECT_NEAR(Footprint::Get10x10().Bitrate(), 1.28f, 0.01f);
+ EXPECT_NEAR(Footprint::Get12x10().Bitrate(), 1.07f, 0.01f);
+ EXPECT_NEAR(Footprint::Get12x12().Bitrate(), 0.89f, 0.01f);
+}
+
+TEST(FootprintTest, StorageRequirements) {
+ auto footprint = Footprint::Get10x8();
+ EXPECT_EQ(footprint.Width(), 10);
+ EXPECT_EQ(footprint.Height(), 8);
+
+ // If we have 8x8 blocks, then we have 64*16 = 1024 bytes.
+ EXPECT_EQ(footprint.StorageRequirements(80, 64), 1024);
+
+ // If our block is a little smaller this still counts because we need to
+ // cover a partial block with a fully encoded one.
+ EXPECT_EQ(footprint.StorageRequirements(79, 63), 1024);
+}
+
+} // namespace
+
+} // namespace astc_codec
diff --git a/src/decoder/test/image_utils.h b/src/decoder/test/image_utils.h
new file mode 100644
index 0000000..718696e
--- /dev/null
+++ b/src/decoder/test/image_utils.h
@@ -0,0 +1,217 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <vector>
+
+static constexpr size_t kMaxVectorOutput = 128;
+
+class ImageBuffer {
+ public:
+ static constexpr size_t Align = 4;
+
+ void Allocate(size_t width, size_t height, size_t bytes_per_pixel) {
+ width_ = width;
+ height_ = height;
+ bytes_per_pixel_ = bytes_per_pixel;
+ stride_ = AlignBytes(width * bytes_per_pixel);
+ data_.resize(stride_ * height);
+ }
+
+ uint8_t* operator()(size_t x, size_t y) {
+ assert(x < width_ && y < height_);
+ return &data_[y * Stride() + x * bytes_per_pixel_];
+ }
+
+ size_t Stride() const { return stride_; }
+ size_t BytesPerPixel() const { return bytes_per_pixel_; }
+
+ std::vector<uint8_t>& Data() { return data_; }
+ const std::vector<uint8_t>& Data() const { return data_; }
+ size_t DataSize() const { return data_.size(); }
+
+ private:
+ size_t AlignBytes(size_t bytes) const {
+ return (bytes + (Align - 1)) / Align * Align;
+ }
+
+ size_t width_ = 0;
+ size_t height_ = 0;
+ size_t stride_ = 0;
+ size_t bytes_per_pixel_ = 0;
+ std::vector<uint8_t> data_;
+};
+
+namespace std {
+static void PrintTo(const vector<uint8_t>& vec, ostream* os) {
+ ios::fmtflags origFlags(os->flags());
+
+ *os << '{';
+ size_t count = 0;
+ for (vector<uint8_t>::const_iterator it = vec.begin(); it != vec.end();
+ ++it, ++count) {
+ if (count > 0) {
+ *os << ", ";
+ }
+
+ if (count == kMaxVectorOutput) {
+ *os << "... ";
+ break;
+ }
+
+ if ((count % 16) == 0) {
+ *os << "\n";
+ }
+
+ if (*it == 0) {
+ *os << " ";
+ } else {
+ *os << "0x" << std::hex << std::uppercase << std::setw(2)
+ << std::setfill('0') << int(*it) << std::dec;
+ }
+ }
+
+ *os << '}';
+
+ os->flags(origFlags);
+}
+} // namespace std
+
+static std::string LoadFile(const std::string& path) {
+ std::ifstream is(path, std::ios::binary);
+ EXPECT_TRUE(is) << "Failed to load file " << path;
+ if (!is) {
+ return "";
+ }
+
+ std::ostringstream ss;
+ ss << is.rdbuf();
+ return ss.str();
+}
+
+static std::string LoadASTCFile(const std::string& basename) {
+ const std::string filename =
+ std::string("src/decoder/testdata/") + basename + ".astc";
+
+ std::string result = LoadFile(filename);
+ // Don't parse the header here, we already know what kind of astc encoding it
+ // is.
+ if (result.size() < 16) {
+ return "";
+ } else {
+ return result.substr(16);
+ }
+}
+
+void LoadGoldenBmp(const std::string& path, ImageBuffer* result) {
+ constexpr size_t kBmpHeaderSize = 54;
+
+ SCOPED_TRACE(testing::Message() << "LoadGoldenBmp " << path);
+
+ const std::string data = LoadFile(path);
+ ASSERT_FALSE(data.empty()) << "Failed to open golden image: " << path;
+
+ ASSERT_GE(data.size(), kBmpHeaderSize);
+ ASSERT_EQ('B', data[0]);
+ ASSERT_EQ('M', data[1]);
+
+ uint32_t dataPos = *reinterpret_cast<const uint32_t*>(&data[0x0A]);
+ uint32_t imageSize = *reinterpret_cast<const uint32_t*>(&data[0x22]);
+ const uint16_t bitsPerPixel = *reinterpret_cast<const uint16_t*>(&data[0x1C]);
+ int width = *reinterpret_cast<const int*>(&data[0x12]);
+ int height = *reinterpret_cast<const int*>(&data[0x16]);
+
+ SCOPED_TRACE(testing::Message()
+ << "dataPos=" << dataPos << ", imageSize=" << imageSize
+ << ", bitsPerPixel=" << bitsPerPixel << ", width=" << width
+ << ", height=" << height);
+
+ if (height < 0) {
+ height = -height;
+ }
+
+ if (imageSize == 0) {
+ imageSize = width * height * 3;
+ }
+
+ if (dataPos < kBmpHeaderSize) {
+ dataPos = kBmpHeaderSize;
+ }
+
+ ASSERT_TRUE(bitsPerPixel == 24 || bitsPerPixel == 32)
+ << "BMP bits per pixel mismatch, expected 24 or 32";
+
+ result->Allocate(width, height, bitsPerPixel == 24 ? 3 : 4);
+ ASSERT_LE(imageSize, result->DataSize());
+
+ std::vector<uint8_t>& resultData = result->Data();
+ const size_t stride = result->Stride();
+
+ // Copy the data row-by-row to make sure that stride is right.
+ for (size_t row = 0; row < static_cast<size_t>(height); ++row) {
+ memcpy(&resultData[row * stride], &data[dataPos + row * stride],
+ width * bitsPerPixel / 8);
+ }
+
+ if (bitsPerPixel == 32) {
+ // Swizzle the data from ABGR to ARGB.
+ for (size_t row = 0; row < static_cast<size_t>(height); ++row) {
+ uint8_t* rowData = resultData.data() + row * stride;
+
+ for (size_t i = 3; i < stride; i += 4) {
+ const uint8_t b = rowData[i - 3];
+ rowData[i - 3] = rowData[i - 1];
+ rowData[i - 1] = b;
+ }
+ }
+ } else {
+ // Swizzle the data from BGR to RGB.
+ for (size_t row = 0; row < static_cast<size_t>(height); ++row) {
+ uint8_t* rowData = resultData.data() + row * stride;
+
+ for (size_t i = 2; i < stride; i += 3) {
+ const uint8_t tmp = rowData[i - 2];
+ rowData[i - 2] = rowData[i];
+ rowData[i] = tmp;
+ }
+ }
+ }
+}
+
+static void CompareSumOfSquaredDifferences(const ImageBuffer& golden,
+ const ImageBuffer& image,
+ double threshold) {
+ ASSERT_EQ(golden.DataSize(), image.DataSize());
+ ASSERT_EQ(golden.Stride(), image.Stride());
+ ASSERT_EQ(golden.BytesPerPixel(), image.BytesPerPixel());
+
+ const std::vector<uint8_t>& image_data = image.Data();
+ const std::vector<uint8_t>& golden_data = golden.Data();
+
+ double sum = 0.0;
+ for (size_t i = 0; i < image_data.size(); ++i) {
+ const double diff = static_cast<double>(image_data[i]) - golden_data[i];
+ sum += diff * diff;
+ }
+
+ EXPECT_LE(sum, threshold * image_data.size())
+ << "Per pixel " << (sum / image_data.size())
+ << ", expected <= " << threshold;
+ if (sum > threshold * image_data.size()) {
+ // Fall back to comparison which will dump first chunk of vector.
+ EXPECT_EQ(golden_data, image_data);
+ }
+}
diff --git a/src/decoder/test/integer_sequence_codec_test.cc b/src/decoder/test/integer_sequence_codec_test.cc
new file mode 100644
index 0000000..b66ff2b
--- /dev/null
+++ b/src/decoder/test/integer_sequence_codec_test.cc
@@ -0,0 +1,337 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/integer_sequence_codec.h"
+#include "src/base/uint128.h"
+
+#include <random>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using astc_codec::base::UInt128;
+using astc_codec::base::BitStream;
+using astc_codec::IntegerSequenceCodec;
+using astc_codec::IntegerSequenceEncoder;
+using astc_codec::IntegerSequenceDecoder;
+
+namespace {
+
+// Make sure that the counts returned for a specific range match what's
+// expected. In particular, make sure that it fits with Table C.2.7
+TEST(ASTCIntegerSequenceCodecTest, TestGetCountsForRange) {
+ std::array<int, 3> kExpectedCounts[31] = {
+ {{ 0, 0, 1 }}, // 1
+ {{ 1, 0, 0 }}, // 2
+ {{ 0, 0, 2 }}, // 3
+ {{ 0, 1, 0 }}, // 4
+ {{ 1, 0, 1 }}, // 5
+ {{ 0, 0, 3 }}, // 6
+ {{ 0, 0, 3 }}, // 7
+ {{ 0, 1, 1 }}, // 8
+ {{ 0, 1, 1 }}, // 9
+ {{ 1, 0, 2 }}, // 10
+ {{ 1, 0, 2 }}, // 11
+ {{ 0, 0, 4 }}, // 12
+ {{ 0, 0, 4 }}, // 13
+ {{ 0, 0, 4 }}, // 14
+ {{ 0, 0, 4 }}, // 15
+ {{ 0, 1, 2 }}, // 16
+ {{ 0, 1, 2 }}, // 17
+ {{ 0, 1, 2 }}, // 18
+ {{ 0, 1, 2 }}, // 19
+ {{ 1, 0, 3 }}, // 20
+ {{ 1, 0, 3 }}, // 21
+ {{ 1, 0, 3 }}, // 22
+ {{ 1, 0, 3 }}, // 23
+ {{ 0, 0, 5 }}, // 24
+ {{ 0, 0, 5 }}, // 25
+ {{ 0, 0, 5 }}, // 26
+ {{ 0, 0, 5 }}, // 27
+ {{ 0, 0, 5 }}, // 28
+ {{ 0, 0, 5 }}, // 29
+ {{ 0, 0, 5 }}, // 30
+ {{ 0, 0, 5 }}, // 31
+ };
+
+ int t, q, b;
+ for (int i = 1; i < 32; ++i) {
+ IntegerSequenceCodec::GetCountsForRange(i, &t, &q, &b);
+ EXPECT_EQ(t, kExpectedCounts[i - 1][0]);
+ EXPECT_EQ(q, kExpectedCounts[i - 1][1]);
+ EXPECT_EQ(b, kExpectedCounts[i - 1][2]);
+ }
+
+ ASSERT_DEBUG_DEATH(IntegerSequenceCodec::GetCountsForRange(0, &t, &q, &b), "");
+ ASSERT_DEBUG_DEATH(
+ IntegerSequenceCodec::GetCountsForRange(256, &t, &q, &b), "");
+
+ IntegerSequenceCodec::GetCountsForRange(1, &t, &q, &b);
+ EXPECT_EQ(t, 0);
+ EXPECT_EQ(q, 0);
+ EXPECT_EQ(b, 1);
+}
+
+// Test to make sure that we're calculating the number of bits needed to
+// encode a given number of values based on the range of the values.
+TEST(ASTCIntegerSequenceCodecTest, TestNumBitsForCounts) {
+ int trits = 0;
+ int quints = 0;
+ int bits = 0;
+
+ // A range of one should have single bits, so n 1-bit values should be n bits.
+ trits = 0;
+ quints = 0;
+ bits = 1;
+ for (int i = 0; i < 64; ++i) {
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCount(i, trits, quints, bits), i);
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(i, 1), i);
+ }
+
+ // Similarly, N two-bit values should be 2n bits...
+ trits = 0;
+ quints = 0;
+ bits = 2;
+ for (int i = 0; i < 64; ++i) {
+ int bit_counts = IntegerSequenceCodec::GetBitCount(i, trits, quints, bits);
+ EXPECT_EQ(bit_counts, 2 * i);
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(i, 3), 2 * i);
+ }
+
+ // Trits are a bit more complicated -- there are five trits in a block, so
+ // if we encode 15 values with 3 bits each in trits, we'd get three blocks,
+ // each with eight bits of trits.
+ trits = 1;
+ quints = 0;
+ bits = 3;
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCount(15, trits, quints, bits),
+ 8 * 3 + 15 * 3);
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(15, 23),
+ IntegerSequenceCodec::GetBitCount(15, trits, quints, bits));
+
+ // However, if instead we encode 13 values, we don't need to use the remaining
+ // two values, so we only need bits as they will be encoded. As it turns out,
+ // this means we can avoid three bits in the final block (one for the high
+ // order trit encoding and two for one of the values), resulting in 47 bits.
+ trits = 1;
+ quints = 0;
+ bits = 2;
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCount(13, trits, quints, bits), 47);
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(13, 11),
+ IntegerSequenceCodec::GetBitCount(13, trits, quints, bits));
+
+ // Quints have a similar property -- if we encode six values using a quint and
+ // four bits, then we have two quint blocks each with three values and a seven
+ // bit encoded quint triplet...
+ trits = 0;
+ quints = 1;
+ bits = 4;
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCount(6, trits, quints, bits),
+ 7 * 2 + 6 * 4);
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCountForRange(6, 79),
+ IntegerSequenceCodec::GetBitCount(6, trits, quints, bits));
+
+ // If we have fewer values than blocks we can again avoid about 2 + nbits
+ // bits...
+ trits = 0;
+ quints = 1;
+ bits = 3;
+ EXPECT_EQ(IntegerSequenceCodec::GetBitCount(7, trits, quints, bits),
+ /* first two quint blocks */ 7 * 2 +
+ /* first two blocks of bits */ 6 * 3 +
+ /* last quint block without the high order four bits */ 3 +
+ /* last block with one set of three bits */ 3);
+}
+
+// Tests that the encoder knows how to encode values of the form 5*2^k.
+TEST(ASTCIntegerSequenceCodecTest, TestQuintCodec) {
+ // In this case, k = 4
+
+ // Setup bit src/sink
+ BitStream<UInt128> bit_sink;
+
+ const int kValueRange = 79;
+ IntegerSequenceEncoder enc(kValueRange);
+ enc.AddValue(3);
+ enc.AddValue(79);
+ enc.AddValue(37);
+ enc.Encode(&bit_sink);
+
+ // quint: 1000101 m0: 0011 m1: 1111 m2: 0101
+ // 100 0100 0111 1101 0010
+ // interleaved 10m200m1101m0
+ // should be 100 1010 0111 1101 0011 = 0x4A7D3
+ EXPECT_EQ(bit_sink.Bits(), 19);
+
+ uint64_t encoded = 0;
+ bit_sink.GetBits(19, &encoded);
+ EXPECT_EQ(encoded, 0x4A7D3);
+
+ // Now check that decoding it works as well
+ BitStream<UInt128> bit_src(encoded, 19);
+
+ IntegerSequenceDecoder dec(kValueRange);
+ auto decoded_vals = dec.Decode(3, &bit_src);
+ ASSERT_EQ(decoded_vals.size(), 3);
+ EXPECT_EQ(decoded_vals[0], 3);
+ EXPECT_EQ(decoded_vals[1], 79);
+ EXPECT_EQ(decoded_vals[2], 37);
+}
+
+// Tests that the encoder knows how to encode values of the form 3*2^k.
+TEST(ASTCIntegerSequenceCodecTest, TestTritCodec) {
+ uint64_t encoded = 0;
+
+ // Setup bit src/sink
+ BitStream<UInt128> bit_sink(encoded, 0);
+
+ const int kValueRange = 11;
+ IntegerSequenceEncoder enc(kValueRange);
+ enc.AddValue(7);
+ enc.AddValue(5);
+ enc.AddValue(3);
+ enc.AddValue(6);
+ enc.AddValue(10);
+ enc.Encode(&bit_sink);
+
+ EXPECT_EQ(bit_sink.Bits(), 18);
+
+ bit_sink.GetBits(18, &encoded);
+ EXPECT_EQ(encoded, 0x37357);
+
+ // Now check that decoding it works as well
+ BitStream<UInt128> bit_src(encoded, 19);
+
+ IntegerSequenceDecoder dec(kValueRange);
+ auto decoded_vals = dec.Decode(5, &bit_src);
+ ASSERT_EQ(decoded_vals.size(), 5);
+ EXPECT_EQ(decoded_vals[0], 7);
+ EXPECT_EQ(decoded_vals[1], 5);
+ EXPECT_EQ(decoded_vals[2], 3);
+ EXPECT_EQ(decoded_vals[3], 6);
+ EXPECT_EQ(decoded_vals[4], 10);
+}
+
+// Test a specific quint encoding/decoding. This test makes sure that the way we
+// encode and decode integer sequences matches what we should expect out of the
+// reference ASTC encoder.
+TEST(ASTCIntegerSequenceCodecTest, TestDecodeThenEncode) {
+ std::vector<int> vals = {{ 16, 18, 17, 4, 7, 14, 10, 0 }};
+ const uint64_t kValEncoding = 0x2b9c83dc;
+
+ BitStream<UInt128> bit_src(kValEncoding, 64);
+ IntegerSequenceDecoder dec(19);
+ auto decoded_vals = dec.Decode(8, &bit_src);
+ ASSERT_EQ(decoded_vals.size(), vals.size());
+ for (size_t i = 0; i < decoded_vals.size(); ++i) {
+ EXPECT_EQ(decoded_vals[i], vals[i]);
+ }
+
+ // Setup bit src/sink
+ BitStream<UInt128> bit_sink;
+ IntegerSequenceEncoder enc(19);
+ for (const auto& v : vals) {
+ enc.AddValue(v);
+ }
+ enc.Encode(&bit_sink);
+ EXPECT_EQ(bit_sink.Bits(), 35);
+
+ uint64_t encoded = 0;
+ EXPECT_TRUE(bit_sink.GetBits(35, &encoded));
+ EXPECT_EQ(encoded, kValEncoding)
+ << std::hex << encoded << " -- " << kValEncoding;
+}
+
+// Same as the previous test, except it uses a trit encoding rather than a
+// quint encoding.
+TEST(ASTCIntegerSequenceCodecTest, TestDecodeThenEncodeTrits) {
+ std::vector<int> vals = {{ 6, 0, 0, 2, 0, 0, 0, 0, 8, 0, 0, 0, 0, 8, 8, 0 }};
+ const uint64_t kValEncoding = 0x0004c0100001006ULL;
+
+ BitStream<UInt128> bit_src(kValEncoding, 64);
+ IntegerSequenceDecoder dec(11);
+ auto decoded_vals = dec.Decode(vals.size(), &bit_src);
+ ASSERT_EQ(decoded_vals.size(), vals.size());
+ for (size_t i = 0; i < decoded_vals.size(); ++i) {
+ EXPECT_EQ(decoded_vals[i], vals[i]);
+ }
+
+ // Setup bit src/sink
+ BitStream<UInt128> bit_sink;
+ IntegerSequenceEncoder enc(11);
+ for (const auto& v : vals) {
+ enc.AddValue(v);
+ }
+ enc.Encode(&bit_sink);
+ EXPECT_EQ(bit_sink.Bits(), 58);
+
+ uint64_t encoded = 0;
+ EXPECT_TRUE(bit_sink.GetBits(58, &encoded));
+ EXPECT_EQ(encoded, kValEncoding)
+ << std::hex << encoded << " -- " << kValEncoding;
+}
+
+// Generate a random sequence of integer codings with different ranges to test
+// the reciprocability of our codec (encoded sequences should be able to
+// decoded)
+TEST(ASTCIntegerSequenceCodecTest, TestRandomReciprocation) {
+ std::mt19937 mt(0xbad7357);
+ std::uniform_int_distribution<int> rand(0, 255);
+
+ for (int test = 0; test < 1600; ++test) {
+ // Generate a random number of values and a random range
+ int num_vals = 4 + rand(mt) % 44; // Up to 48 weights in a grid
+ int range = 1 + rand(mt) % 63;
+
+ // If this produces a bit pattern larger than our buffer, then ignore
+ // it... we already know what our bounds are for the integer sequences
+ int num_bits = IntegerSequenceCodec::GetBitCountForRange(num_vals, range);
+ if (num_bits >= 64) {
+ continue;
+ }
+
+ std::vector<int> generated_vals(num_vals);
+ for (auto& val : generated_vals) {
+ val = rand(mt) % (range + 1);
+ }
+
+ // Encode the values using the
+ BitStream<UInt128> bit_sink;
+
+ // Add them to the encoder
+ IntegerSequenceEncoder enc(range);
+ for (int v : generated_vals) {
+ enc.AddValue(v);
+ }
+ enc.Encode(&bit_sink);
+
+ uint64_t encoded = 0;
+ bit_sink.GetBits(bit_sink.Bits(), &encoded);
+ ASSERT_GE(encoded, 0);
+ EXPECT_LT(encoded, 1ULL << num_bits);
+
+ BitStream<UInt128> bit_src(encoded, 64);
+
+ IntegerSequenceDecoder dec(range);
+ auto decoded_vals = dec.Decode(num_vals, &bit_src);
+
+ ASSERT_EQ(decoded_vals.size(), generated_vals.size());
+ for (size_t i = 0; i < decoded_vals.size(); ++i) {
+ EXPECT_EQ(decoded_vals[i], generated_vals[i]);
+ }
+ }
+}
+
+} // namespace
diff --git a/src/decoder/test/intermediate_astc_block_test.cc b/src/decoder/test/intermediate_astc_block_test.cc
new file mode 100644
index 0000000..69935ef
--- /dev/null
+++ b/src/decoder/test/intermediate_astc_block_test.cc
@@ -0,0 +1,453 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/intermediate_astc_block.h"
+#include "src/decoder/test/image_utils.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <string>
+
+namespace astc_codec {
+
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Optional;
+using ::testing::SizeIs;
+using ::testing::TestWithParam;
+using ::testing::ValuesIn;
+
+// Test to make sure that unpacking an error block returns false.
+TEST(IntermediateASTCBlockTest, TestUnpackError) {
+ const PhysicalASTCBlock kErrorBlock(base::UInt128(0));
+ EXPECT_FALSE(UnpackVoidExtent(kErrorBlock));
+ EXPECT_FALSE(UnpackIntermediateBlock(kErrorBlock));
+}
+
+// Test to make sure that if we don't populate our weight data in the
+// intermediate block than the resulting color range should error due to the
+// mismatch.
+TEST(IntermediateASTCBlockTest, TestEndpointRangeErrorOnNotSettingWeights) {
+ IntermediateBlockData data;
+ data.weight_range = 15;
+ for (auto& ep : data.endpoints) {
+ ep.mode = ColorEndpointMode::kLDRRGBDirect;
+ }
+ data.weight_grid_dim_x = 6;
+ data.weight_grid_dim_y = 6;
+ EXPECT_EQ(-1, EndpointRangeForBlock(data));
+
+ base::UInt128 dummy;
+ auto err_str = Pack(data, &dummy);
+ EXPECT_TRUE(err_str.hasValue());
+ EXPECT_THAT(err_str.value(), HasSubstr("Incorrect number of weights"));
+}
+
+// Test to make sure that if we run out of bits, then we should say so.
+TEST(IntermediateASTCBlockTest, TestEndpointRangeErrorOnNotEnoughBits) {
+ IntermediateBlockData data;
+ data.weight_range = 1;
+ data.partition_id = 0;
+ data.endpoints.resize(3);
+ for (auto& ep : data.endpoints) {
+ ep.mode = ColorEndpointMode::kLDRRGBDirect;
+ }
+ data.weight_grid_dim_x = 8;
+ data.weight_grid_dim_y = 8;
+ EXPECT_EQ(-2, EndpointRangeForBlock(data));
+
+ // Resize the weights to get past the error that they do not match the grid
+ // dimensions.
+ data.weights.resize(64);
+
+ base::UInt128 dummy;
+ auto err_str = Pack(data, &dummy);
+ EXPECT_TRUE(err_str.hasValue());
+ EXPECT_THAT(err_str.value(), HasSubstr("illegal color range"));
+}
+
+// Test to make sure that as we increase the number of weights, we decrease the
+// allowable range of colors
+TEST(IntermediateASTCBlockTest, TestEndpointRangeForBlock) {
+ IntermediateBlockData data;
+ data.weight_range = 2;
+ data.endpoints.resize(2);
+ data.dual_plane_channel.clear();
+ for (auto& ep : data.endpoints) {
+ ep.mode = ColorEndpointMode::kLDRRGBDirect;
+ }
+
+ // Weight params control how many weights are present in a block
+ struct WeightParams {
+ int width;
+ int height;
+
+ // We should sort based on number of weights for these params
+ int NumWeights() const { return width * height; }
+ bool operator<(const WeightParams& other) const {
+ return NumWeights() < other.NumWeights();
+ }
+ };
+
+ std::vector<WeightParams> weight_params;
+ for (int y = 2; y < 8; ++y) {
+ for (int x = 2; x < 8; ++x) {
+ weight_params.emplace_back(WeightParams{x, y});
+ }
+ }
+
+ // Sort weights from fewest to largest such that the allowable color range
+ // should be monotonically decreasing
+ std::sort(weight_params.begin(), weight_params.end());
+
+ // Keep track of the largest available color range and measure that it
+ // decreases as we add more weights to our block
+ int last_color_range = 255;
+ for (const auto& params : weight_params) {
+ data.weight_grid_dim_x = params.width;
+ data.weight_grid_dim_y = params.height;
+
+ const int color_range = EndpointRangeForBlock(data);
+ EXPECT_LE(color_range, last_color_range);
+ last_color_range = std::min(color_range, last_color_range);
+ }
+
+ // Make sure that we actually changed it at some point.
+ EXPECT_LT(last_color_range, 255);
+}
+
+// Test to make sure that unpacking an legitimate ASTC block returns the encoded
+// values that we expect.
+TEST(IntermediateASTCBlockTest, TestUnpackNonVoidExtentBlock) {
+ PhysicalASTCBlock blk(0x0000000001FE000173ULL);
+ auto b = UnpackIntermediateBlock(blk);
+ ASSERT_TRUE(b);
+
+ const auto& data = b.value();
+ EXPECT_EQ(data.weight_grid_dim_x, 6);
+ EXPECT_EQ(data.weight_grid_dim_y, 5);
+ EXPECT_EQ(data.weight_range, 7);
+
+ EXPECT_FALSE(data.partition_id);
+ EXPECT_FALSE(data.dual_plane_channel);
+
+ ASSERT_EQ(data.weights.size(), 30);
+ for (auto weight : data.weights) {
+ EXPECT_EQ(weight, 0);
+ }
+
+ ASSERT_EQ(data.endpoints.size(), 1);
+ for (const auto& ep_data : data.endpoints) {
+ EXPECT_EQ(ep_data.mode, ColorEndpointMode::kLDRLumaDirect);
+ ASSERT_EQ(ep_data.colors.size(), 2);
+ EXPECT_EQ(ep_data.colors[0], 0);
+ EXPECT_EQ(ep_data.colors[1], 255);
+ }
+}
+
+// Make sure that we can pack blocks that aren't void extent blocks. (In other
+// words, can we actually deal with intermediate ASTC data).
+TEST(IntermediateASTCBlockTest, TestPackNonVoidExtentBlock) {
+ IntermediateBlockData data;
+
+ data.weight_grid_dim_x = 6;
+ data.weight_grid_dim_y = 5;
+ data.weight_range = 7;
+
+ data.partition_id = {};
+ data.dual_plane_channel = {};
+
+ data.weights.resize(30);
+ for (auto& weight : data.weights) {
+ weight = 0;
+ }
+
+ data.endpoints.resize(1);
+ for (auto& ep_data : data.endpoints) {
+ ep_data.mode = ColorEndpointMode::kLDRLumaDirect;
+ ep_data.colors.resize(2);
+ ep_data.colors[0] = 0;
+ ep_data.colors[1] = 255;
+ }
+
+ base::UInt128 packed;
+ auto error_str = Pack(data, &packed);
+ ASSERT_FALSE(error_str) << (error_str ? error_str.value() : std::string(""));
+ EXPECT_EQ(packed, 0x0000000001FE000173ULL);
+}
+
+// Make sure that we can unpack void extent blocks
+TEST(IntermediateASTCBlockTest, TestUnpackVoidExtentBlock) {
+ PhysicalASTCBlock void_extent_block(0xFFFFFFFFFFFFFDFCULL);
+
+ auto b = UnpackVoidExtent(void_extent_block);
+ ASSERT_TRUE(b);
+
+ const auto& data = b.value();
+ EXPECT_EQ(data.r, 0);
+ EXPECT_EQ(data.g, 0);
+ EXPECT_EQ(data.b, 0);
+ EXPECT_EQ(data.a, 0);
+ for (const auto& coord : data.coords) {
+ EXPECT_EQ(coord, (1 << 13) - 1);
+ }
+
+ base::UInt128 more_interesting(0xdeadbeefdeadbeefULL, 0xFFF8003FFE000DFCULL);
+ b = UnpackVoidExtent(PhysicalASTCBlock(more_interesting));
+ ASSERT_TRUE(b);
+
+ const auto& other_data = b.value();
+ EXPECT_EQ(other_data.r, 0xbeef);
+ EXPECT_EQ(other_data.g, 0xdead);
+ EXPECT_EQ(other_data.b, 0xbeef);
+ EXPECT_EQ(other_data.a, 0xdead);
+ EXPECT_EQ(other_data.coords[0], 0);
+ EXPECT_EQ(other_data.coords[1], 8191);
+ EXPECT_EQ(other_data.coords[2], 0);
+ EXPECT_EQ(other_data.coords[3], 8191);
+}
+
+// Make sure that we can pack void extent blocks and void extent data.
+TEST(IntermediateASTCBlockTest, TestPackVoidExtentBlock) {
+ VoidExtentData data;
+ data.r = 0;
+ data.g = 0;
+ data.b = 0;
+ data.a = 0;
+ for (auto& coord : data.coords) {
+ coord = (1 << 13) - 1;
+ }
+
+ base::UInt128 packed;
+ auto error_str = Pack(data, &packed);
+ ASSERT_FALSE(error_str) << (error_str ? error_str.value() : std::string(""));
+ EXPECT_EQ(packed, 0xFFFFFFFFFFFFFDFCULL);
+
+ data.r = 0xbeef;
+ data.g = 0xdead;
+ data.b = 0xbeef;
+ data.a = 0xdead;
+ data.coords[0] = 0;
+ data.coords[1] = 8191;
+ data.coords[2] = 0;
+ data.coords[3] = 8191;
+
+ error_str = Pack(data, &packed);
+ ASSERT_FALSE(error_str) << (error_str ? error_str.value() : std::string(""));
+ EXPECT_EQ(packed,
+ base::UInt128(0xdeadbeefdeadbeefULL, 0xFFF8003FFE000DFCULL));
+}
+
+// Make sure that the color endpoint mode is properly repacked. This test case
+// was created as a bug during testing.
+TEST(IntermediateASTCBlockTest, TestPackUnpackWithSameCEM) {
+ base::UInt128 orig(0xe8e8eaea20000980ULL, 0x20000200cb73f045ULL);
+
+ auto b = UnpackIntermediateBlock(PhysicalASTCBlock(orig));
+ ASSERT_TRUE(b);
+
+ base::UInt128 repacked;
+ auto err_str = Pack(b.value(), &repacked);
+ ASSERT_FALSE(err_str) << (err_str ? err_str.value() : std::string(""));
+
+ EXPECT_EQ(repacked, orig);
+
+ // Test case #2
+ orig = base::UInt128(0x3300c30700cb01c5ULL, 0x0573907b8c0f6879ULL);
+ b = UnpackIntermediateBlock(PhysicalASTCBlock(orig));
+ ASSERT_TRUE(b);
+
+ err_str = Pack(b.value(), &repacked);
+ ASSERT_FALSE(err_str) << (err_str ? err_str.value() : std::string(""));
+ EXPECT_EQ(repacked, orig);
+}
+
+// Test that we can encode/decode a block that uses a very large gap
+// between weight and endpoint data.
+TEST(IntermediateASTCBlockTest, TestPackingWithLargeGap) {
+ // We can construct this block by doing the following:
+ // -- choose a block mode that only gives 24 weight bits
+ // -- choose the smallest endpoint mode: grayscale direct
+ // -- make sure there are no partitions
+ const base::UInt128 orig(0xBEDEAD0000000000ULL, 0x0000000001FE032EULL);
+ const auto b = UnpackIntermediateBlock(PhysicalASTCBlock(orig));
+ ASSERT_TRUE(b);
+
+ const auto& data = b.value();
+ EXPECT_EQ(data.weight_grid_dim_x, 2);
+ EXPECT_EQ(data.weight_grid_dim_y, 3);
+ EXPECT_EQ(data.weight_range, 15);
+
+ EXPECT_FALSE(data.partition_id);
+ EXPECT_FALSE(data.dual_plane_channel);
+
+ ASSERT_EQ(data.endpoints.size(), 1);
+ EXPECT_EQ(data.endpoints.at(0).mode, ColorEndpointMode::kLDRLumaDirect);
+
+ ASSERT_EQ(data.endpoints.at(0).colors.size(), 2);
+ EXPECT_EQ(data.endpoints.at(0).colors.at(0), 255);
+ EXPECT_EQ(data.endpoints.at(0).colors.at(1), 0);
+
+ // Now encode it again
+ base::UInt128 repacked;
+ const auto err_str = Pack(b.value(), &repacked);
+ EXPECT_EQ(orig, repacked) << (err_str ? err_str.value() : std::string(""));
+}
+
+// Take a block that is encoded using direct luma with full byte values and see
+// if we properly set the endpoint range.
+TEST(IntermediateASTCBlockTest, TestEndpointRange) {
+ PhysicalASTCBlock blk(0x0000000001FE000173ULL);
+ EXPECT_THAT(blk.ColorValuesRange(), Optional(Eq(255)));
+
+ auto b = UnpackIntermediateBlock(blk);
+ ASSERT_TRUE(b);
+
+ const auto& data = b.value();
+ ASSERT_THAT(data.endpoints, SizeIs(1));
+ EXPECT_THAT(data.endpoints[0].mode, Eq(ColorEndpointMode::kLDRLumaDirect));
+ EXPECT_THAT(data.endpoints[0].colors, ElementsAre(0, 255));
+ EXPECT_THAT(data.endpoint_range, Optional(Eq(255)));
+}
+
+struct ImageTestParams {
+ std::string image_name;
+ int checkered_dim;
+};
+
+static void PrintTo(const ImageTestParams& params, std::ostream* os) {
+ *os << "ImageTestParams(" << params.image_name << ")";
+}
+
+class IntermediateASTCBlockTest : public TestWithParam<ImageTestParams> { };
+
+// Test whether or not a real-world ASTC implementation can be unpacked and
+// then repacked into the same implementation. In conjunction with the other
+// tests, we make sure that we can recreate ASTC blocks that we have previously
+// unpacked.
+TEST_P(IntermediateASTCBlockTest, TestPackUnpack) {
+ const auto& params = GetParam();
+ const int astc_dim = 8;
+ const int img_dim = params.checkered_dim * astc_dim;
+ const std::string astc = LoadASTCFile(params.image_name);
+
+ // Make sure that unpacking and repacking all of the blocks works...
+ const int kNumASTCBlocks = (img_dim / astc_dim) * (img_dim / astc_dim);
+ for (int i = 0; i < kNumASTCBlocks; ++i) {
+ base::UInt128 block_bits;
+ memcpy(&block_bits, astc.data() + PhysicalASTCBlock::kSizeInBytes * i,
+ PhysicalASTCBlock::kSizeInBytes);
+
+ const PhysicalASTCBlock block(block_bits);
+
+ base::UInt128 repacked;
+ if (block.IsVoidExtent()) {
+ auto b = UnpackVoidExtent(block);
+ ASSERT_TRUE(b);
+
+ auto err_str = Pack(b.value(), &repacked);
+ ASSERT_FALSE(err_str) << (err_str ? err_str.value() : std::string(""));
+ } else {
+ auto b = UnpackIntermediateBlock(block);
+ ASSERT_TRUE(b);
+
+ // Check to see that we properly set the endpoint range when we decoded
+ // the block.
+ auto& block_data = b.value();
+ EXPECT_EQ(block_data.endpoint_range, block.ColorValuesRange());
+
+ // Reset the endpoint range here to see if we correctly reconstruct it
+ // below
+ block_data.endpoint_range = {};
+
+ auto err_str = Pack(b.value(), &repacked);
+ ASSERT_FALSE(err_str) << (err_str ? err_str.value() : std::string(""));
+ }
+
+ // You would expect the following line to be enough:
+ // EXPECT_EQ(repacked, block.GetBlockBits())
+ // ... except that the ASTC encoder makes some interesting decisions
+ // about how to encode the same logical bits. One example is that
+ // sometimes if all partitions share an endpoint mode, the encoded
+ // block will not use the shared CEM mode, and rather list each
+ // partition's mode explicitly. For that reason, we just need to make as
+ // close of an approximation as possible that we decode to the same
+ // physical values.
+
+ PhysicalASTCBlock pb(repacked);
+ ASSERT_FALSE(pb.IsIllegalEncoding());
+
+ base::UInt128 pb_color_mask =
+ (base::UInt128(1) << pb.NumColorBits().value()) - 1;
+ base::UInt128 pb_color_bits =
+ pb.GetBlockBits() >> pb.ColorStartBit().value();
+ pb_color_bits &= pb_color_mask;
+
+ base::UInt128 b_color_mask =
+ (base::UInt128(1) << pb.NumColorBits().value()) - 1;
+ base::UInt128 b_color_bits =
+ block.GetBlockBits() >> block.ColorStartBit().value();
+ b_color_bits &= b_color_mask;
+
+ EXPECT_EQ(pb_color_mask, b_color_mask);
+ EXPECT_EQ(pb_color_bits, b_color_bits);
+
+ EXPECT_EQ(pb.IsVoidExtent(), block.IsVoidExtent());
+ EXPECT_EQ(pb.VoidExtentCoords(), block.VoidExtentCoords());
+
+ EXPECT_EQ(pb.WeightGridDims(), block.WeightGridDims());
+ EXPECT_EQ(pb.WeightRange(), block.WeightRange());
+ EXPECT_EQ(pb.NumWeightBits(), block.NumWeightBits());
+ EXPECT_EQ(pb.WeightStartBit(), block.WeightStartBit());
+
+ EXPECT_EQ(pb.IsDualPlane(), block.IsDualPlane());
+ EXPECT_EQ(pb.DualPlaneChannel(), block.DualPlaneChannel());
+
+ EXPECT_EQ(pb.NumPartitions(), block.NumPartitions());
+ EXPECT_EQ(pb.PartitionID(), block.PartitionID());
+
+ EXPECT_EQ(pb.NumColorValues(), block.NumColorValues());
+ EXPECT_EQ(pb.ColorValuesRange(), block.ColorValuesRange());
+
+ for (int j = 0; j < pb.NumPartitions().valueOr(0); ++j) {
+ EXPECT_EQ(pb.GetEndpointMode(j), block.GetEndpointMode(j));
+ }
+ }
+}
+
+std::vector<ImageTestParams> GetImageTestParams() {
+ return {
+ // image_name checkered_dim
+ { "checkered_4", 4 },
+ { "checkered_5", 5 },
+ { "checkered_6", 6 },
+ { "checkered_7", 7 },
+ { "checkered_8", 8 },
+ { "checkered_9", 9 },
+ { "checkered_10", 10 },
+ { "checkered_11", 11 },
+ { "checkered_12", 12 },
+ };
+}
+
+INSTANTIATE_TEST_CASE_P(Checkered, IntermediateASTCBlockTest,
+ ValuesIn(GetImageTestParams()));
+
+} // namespace
+
+} // namespace astc_codec
diff --git a/src/decoder/test/logical_astc_block_test.cc b/src/decoder/test/logical_astc_block_test.cc
new file mode 100644
index 0000000..ed85f3f
--- /dev/null
+++ b/src/decoder/test/logical_astc_block_test.cc
@@ -0,0 +1,273 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/logical_astc_block.h"
+#include "src/decoder/test/image_utils.h"
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include <fstream>
+#include <string>
+
+namespace astc_codec {
+
+namespace {
+
+using ::testing::Eq;
+using ::testing::ElementsAre;
+using ::testing::TestWithParam;
+using ::testing::ValuesIn;
+
+ImageBuffer LoadGoldenImageWithAlpha(std::string basename) {
+ const std::string filename = std::string("src/decoder/testdata/") + basename + ".bmp";
+ ImageBuffer result;
+ LoadGoldenBmp(filename, &result);
+ EXPECT_EQ(result.BytesPerPixel(), 4);
+ return result;
+}
+
+ImageBuffer LoadGoldenImage(std::string basename) {
+ const std::string filename = std::string("src/decoder/testdata/") + basename + ".bmp";
+ ImageBuffer result;
+ LoadGoldenBmp(filename, &result);
+ EXPECT_EQ(result.BytesPerPixel(), 3);
+ return result;
+}
+
+struct ImageTestParams {
+ std::string image_name;
+ bool has_alpha;
+ Footprint footprint;
+ int width;
+ int height;
+};
+
+static void PrintTo(const ImageTestParams& params, std::ostream* os) {
+ *os << "ImageTestParams(" << params.image_name << ", "
+ << params.width << "x" << params.height << ", "
+ << (params.has_alpha ? "RGBA" : "RGB") << ", "
+ << "footprint " << params.footprint.Width() << "x"
+ << params.footprint.Height() << ")";
+}
+
+class LogicalASTCBlockTest : public TestWithParam<ImageTestParams> { };
+
+// Test to make sure that reading out color values from blocks is not
+// terribly wrong. To do so, we compress an image and then decompress it
+// using our logical blocks and the library. The difference between the
+// decoded images should be minimal.
+TEST_P(LogicalASTCBlockTest, ImageWithFootprint) {
+ const auto& params = GetParam();
+ const std::string astc = LoadASTCFile(params.image_name);
+
+ ImageBuffer our_decoded_image;
+ our_decoded_image.Allocate(params.width, params.height, params.has_alpha ? 4 : 3);
+
+ const int block_width = params.footprint.Width();
+ const int block_height = params.footprint.Height();
+
+ base::UInt128 block;
+ for (int i = 0; i < astc.size(); i += 16) {
+ const int block_index = i / 16;
+ const int blocks_wide =
+ (params.width + block_width - 1) / block_width;
+ const int block_x = block_index % blocks_wide;
+ const int block_y = block_index / blocks_wide;
+ memcpy(&block, astc.data() + i, sizeof(block));
+
+ PhysicalASTCBlock physical_block(block);
+ if (physical_block.IsVoidExtent()) {
+ auto ve = UnpackVoidExtent(physical_block);
+ ASSERT_TRUE(ve) << "ASTC encoder produced invalid block!";
+ } else {
+ auto ib = UnpackIntermediateBlock(physical_block);
+ ASSERT_TRUE(ib) << "ASTC encoder produced invalid block!";
+ }
+
+ // Make sure that the library doesn't produce incorrect ASTC blocks.
+ // This is covered in more depth in other tests in
+ // intermediate_astc_block_test and physical_astc_block_test
+ auto lb = UnpackLogicalBlock(params.footprint, physical_block);
+ ASSERT_TRUE(lb) << "ASTC encoder produced invalid block!";
+
+ LogicalASTCBlock logical_block = lb.value();
+ const size_t color_size = params.has_alpha ? 4 : 3;
+
+ for (int y = 0; y < block_height; ++y) {
+ for (int x = 0; x < block_width; ++x) {
+ const int px = block_width * block_x + x;
+ const int py = block_height * block_y + y;
+
+ // Skip out of bounds.
+ if (px >= params.width || py >= params.height) {
+ continue;
+ }
+
+ uint8_t* pixel = our_decoded_image(px, py);
+ const RgbaColor decoded_color = logical_block.ColorAt(x, y);
+ ASSERT_LE(color_size, decoded_color.size());
+
+ for (int c = 0; c < color_size; ++c) {
+ // All of the pixels should also be 8-bit values.
+ ASSERT_GE(decoded_color[c], 0);
+ ASSERT_LT(decoded_color[c], 256);
+ pixel[c] = decoded_color[c];
+ }
+ }
+ }
+ }
+
+ // Check that the decoded image is *very* similar to the library decoding
+ // of an ASTC texture. They may not be exact due to differences in how we
+ // convert a 16-bit float to an 8-bit integer.
+ ImageBuffer decoded_image = params.has_alpha ? LoadGoldenImageWithAlpha(params.image_name) : LoadGoldenImage(params.image_name);
+ CompareSumOfSquaredDifferences(decoded_image, our_decoded_image, 1.0);
+}
+
+// Test to make sure that a simple gradient image can be compressed and decoded
+// by our logical block representation. This should work with every footprint.
+std::vector<ImageTestParams> GetSyntheticImageTestParams() {
+ return {
+ // image_name alpha astc footprint width height
+ { "footprint_4x4", false, Footprint::Get4x4(), 32, 32 },
+ { "footprint_5x4", false, Footprint::Get5x4(), 32, 32 },
+ { "footprint_5x5", false, Footprint::Get5x5(), 32, 32 },
+ { "footprint_6x5", false, Footprint::Get6x5(), 32, 32 },
+ { "footprint_6x6", false, Footprint::Get6x6(), 32, 32 },
+ { "footprint_8x5", false, Footprint::Get8x5(), 32, 32 },
+ { "footprint_8x6", false, Footprint::Get8x6(), 32, 32 },
+ { "footprint_10x5", false, Footprint::Get10x5(), 32, 32 },
+ { "footprint_10x6", false, Footprint::Get10x6(), 32, 32 },
+ { "footprint_8x8", false, Footprint::Get8x8(), 32, 32 },
+ { "footprint_10x8", false, Footprint::Get10x8(), 32, 32 },
+ { "footprint_10x10", false, Footprint::Get10x10(), 32, 32 },
+ { "footprint_12x10", false, Footprint::Get12x10(), 32, 32 },
+ { "footprint_12x12", false, Footprint::Get12x12(), 32, 32 },
+ };
+}
+
+INSTANTIATE_TEST_CASE_P(Synthetic, LogicalASTCBlockTest,
+ ValuesIn(GetSyntheticImageTestParams()));
+
+// Test to make sure that reading out color values from blocks in a real-world
+// image isn't terribly wrong, either.
+std::vector<ImageTestParams> GetRealWorldImageTestParams() {
+ return {
+ // image_name alpha astc footprint width height
+ { "rgb_4x4", false, Footprint::Get4x4(), 224, 288 },
+ { "rgb_6x6", false, Footprint::Get6x6(), 224, 288 },
+ { "rgb_8x8", false, Footprint::Get8x8(), 224, 288 },
+ { "rgb_12x12", false, Footprint::Get12x12(), 224, 288 },
+ { "rgb_5x4", false, Footprint::Get5x4(), 224, 288 }
+ };
+}
+
+INSTANTIATE_TEST_CASE_P(RealWorld, LogicalASTCBlockTest,
+ ValuesIn(GetRealWorldImageTestParams()));
+
+// Test to make sure that reading out color values from blocks in a real-world
+// image isn't terribly wrong, either.
+std::vector<ImageTestParams> GetTransparentImageTestParams() {
+ return {
+ // image_name alpha astc footprint width height
+ { "atlas_small_4x4", true, Footprint::Get4x4(), 256, 256 },
+ { "atlas_small_5x5", true, Footprint::Get5x5(), 256, 256 },
+ { "atlas_small_6x6", true, Footprint::Get6x6(), 256, 256 },
+ { "atlas_small_8x8", true, Footprint::Get8x8(), 256, 256 },
+ };
+}
+
+INSTANTIATE_TEST_CASE_P(Transparent, LogicalASTCBlockTest,
+ ValuesIn(GetTransparentImageTestParams()));
+
+// Test to make sure that if we set our endpoints then it's reflected in our
+// color selection
+TEST(LogicalASTCBlockTest, SetEndpoints) {
+ LogicalASTCBlock logical_block(Footprint::Get8x8());
+
+ // Setup a weight checkerboard
+ for (int j = 0; j < 8; ++j) {
+ for (int i = 0; i < 8; ++i) {
+ if (((i ^ j) & 1) == 1) {
+ logical_block.SetWeightAt(i, j, 0);
+ } else {
+ logical_block.SetWeightAt(i, j, 64);
+ }
+ }
+ }
+
+ // Now set the colors to something ridiculous
+ logical_block.SetEndpoints({{ 123, 45, 67, 89 }}, {{ 101, 121, 31, 41 }}, 0);
+
+ // For each pixel, we expect it to mirror the endpoints in a checkerboard
+ // pattern
+ for (int j = 0; j < 8; ++j) {
+ for (int i = 0; i < 8; ++i) {
+ if (((i ^ j) & 1) == 1) {
+ EXPECT_THAT(logical_block.ColorAt(i, j), ElementsAre(123, 45, 67, 89));
+ } else {
+ EXPECT_THAT(logical_block.ColorAt(i, j), ElementsAre(101, 121, 31, 41));
+ }
+ }
+ }
+}
+
+// Test whether or not setting weight values under different circumstances is
+// supported and reflected in the query functions.
+TEST(LogicalASTCBlockTest, SetWeightVals) {
+ LogicalASTCBlock logical_block(Footprint::Get4x4());
+
+ EXPECT_THAT(logical_block.GetFootprint(), Eq(Footprint::Get4x4()));
+
+ // Not a dual plane by default
+ EXPECT_FALSE(logical_block.IsDualPlane());
+ logical_block.SetWeightAt(2, 3, 2);
+
+ // Set the dual plane
+ logical_block.SetDualPlaneChannel(0);
+ EXPECT_TRUE(logical_block.IsDualPlane());
+
+ // This shouldn't have reset our weight
+ const LogicalASTCBlock other_block = logical_block;
+ EXPECT_THAT(other_block.WeightAt(2, 3), Eq(2));
+ EXPECT_THAT(other_block.DualPlaneWeightAt(0, 2, 3), Eq(2));
+
+ // If we set the dual plane weight, it shouldn't change the original weight
+ // value or the other channels
+ logical_block.SetDualPlaneWeightAt(0, 2, 3, 1);
+ EXPECT_THAT(logical_block.WeightAt(2, 3), Eq(2));
+ EXPECT_THAT(logical_block.DualPlaneWeightAt(0, 2, 3), Eq(1));
+ for (int i = 1; i < 4; ++i) {
+ EXPECT_THAT(logical_block.DualPlaneWeightAt(i, 2, 3), Eq(2));
+ }
+
+ // Remove the dual plane
+ logical_block.SetDualPlaneChannel(-1);
+ EXPECT_FALSE(logical_block.IsDualPlane());
+
+ // Now the original dual plane weight should be reset back to the others. Note
+ // that we have to call DualPlaneWeightAt from a const logical block since
+ // returning a reference to a weight that doesn't exist is illegal.
+ const LogicalASTCBlock other_block2 = logical_block;
+ EXPECT_THAT(logical_block.WeightAt(2, 3), Eq(2));
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_EQ(logical_block.WeightAt(2, 3),
+ other_block2.DualPlaneWeightAt(i, 2, 3));
+ }
+}
+
+} // namespace
+
+} // namespace astc_codec
diff --git a/src/decoder/test/partition_test.cc b/src/decoder/test/partition_test.cc
new file mode 100644
index 0000000..63adfb5
--- /dev/null
+++ b/src/decoder/test/partition_test.cc
@@ -0,0 +1,263 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/partition.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <array>
+#include <random>
+#include <string>
+#include <vector>
+
+namespace {
+
+using ::testing::ElementsAreArray;
+using ::testing::Eq;
+using ::testing::Le;
+using ::testing::Not;
+
+using astc_codec::Footprint;
+using astc_codec::Partition;
+using astc_codec::PartitionMetric;
+using astc_codec::GetASTCPartition;
+using astc_codec::FindClosestASTCPartition;
+
+// Test to make sure that a simple difference between two partitions where
+// most of the values are the same returns what we expect.
+TEST(PartitionTest, TestSimplePartitionMetric) {
+ Partition a = {Footprint::Get6x6(), /* num_parts = */ 2,
+ /* partition_id = */ {}, /* assignment = */ {}};
+ Partition b = a;
+
+ a.assignment = {
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1,
+ };
+
+ b.assignment = {
+ 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ };
+
+ const int dist = PartitionMetric(a, b);
+ EXPECT_EQ(dist, 2);
+}
+
+// Test to make sure that if one partition is a subset of another that we still
+// return the proper difference against the subset of the larger one.
+TEST(PartitionDeathTest, TestPartitionMetric) {
+ Partition a = {Footprint::Get4x4(), /* num_parts = */ 2,
+ /* partition_id = */ {}, /* assignment = */ {}};
+ Partition b = {Footprint::Get6x6(), /* num_parts = */ 2,
+ /* partition_id = */ {}, /* assignment = */ {}};
+
+ a.assignment = {{
+ 1, 1, 1, 1,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 1,
+ }};
+
+ b.assignment = {{
+ 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 1, 0,
+ 0, 0, 1, 1, 0, 0,
+ }};
+
+ EXPECT_DEATH(PartitionMetric(a, b), "");
+}
+
+// Test to make sure that even if we have different numbers of subsets for each
+// partition, that the returned value is what we'd expect.
+TEST(PartitionTest, TestDiffPartsPartitionMetric) {
+ Partition a = {Footprint::Get4x4(), /* num_parts = */ 2,
+ /* partition_id = */ {}, /* assignment = */ {}};
+ Partition b = {Footprint::Get4x4(), /* num_parts = */ 3,
+ /* partition_id = */ {}, /* assignment = */ {}};
+
+ a.assignment = {{
+ 2, 2, 2, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 1,
+ }};
+
+ b.assignment = {{
+ 1, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0
+ }};
+
+ const int dist = PartitionMetric(a, b);
+ EXPECT_EQ(dist, 3);
+}
+
+// An additional sanity check test that makes sure that we're not always mapping
+// zero to zero in our tests.
+TEST(PartitionTest, TestDiffMappingPartitionMetric) {
+ Partition a = {Footprint::Get4x4(), /* num_parts = */ 2,
+ /* partition_id = */ {}, /* assignment = */ {}};
+ Partition b = {Footprint::Get4x4(), /* num_parts = */ 3,
+ /* partition_id = */ {}, /* assignment = */ {}};
+
+ a.assignment = {{
+ 0, 1, 2, 2,
+ 2, 2, 2, 2,
+ 2, 2, 2, 2,
+ 2, 2, 2, 2,
+ }};
+
+ b.assignment = {{
+ 1, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ }};
+
+ const int dist = PartitionMetric(a, b);
+ EXPECT_EQ(dist, 1);
+}
+
+// Finally, if we grab an ASTC partition and modify it a tad, the closest
+// partition should still be the same ASTC partition.
+TEST(PartitionTest, TestFindingASTCPartition) {
+ const Partition astc = GetASTCPartition(Footprint::Get12x12(), 3, 0x3CB);
+ Partition almost_astc = astc;
+ almost_astc.assignment[0]++;
+
+ const Partition& closest_astc = FindClosestASTCPartition(almost_astc);
+ EXPECT_EQ(astc, closest_astc);
+}
+
+// Test a partition that was obtained from the reference ASTC encoder. We should
+// be able to match it exactly
+TEST(PartitionTest, TestSpecificPartition) {
+ const Partition astc = GetASTCPartition(Footprint::Get10x6(), 3, 557);
+ EXPECT_THAT(astc.assignment, ElementsAreArray(std::array<int, 60> {{
+ 0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
+ 0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
+ 0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
+ 0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
+ 0, 0, 0, 0, 1, 1, 1, 2, 2, 2,
+ 0, 0, 0, 0, 1, 1, 1, 2, 2, 2 }}));
+}
+
+// Make sure that when we match against this specific partition, it'll return a
+// partition with the same number of subsets
+TEST(PartitionTest, EstimatedPartitionSubsets) {
+ Partition partition = {
+ /* footprint = */ Footprint::Get6x6(),
+ /* num_parts = */ 2,
+ /* partition_id = */ {},
+ /* assignment = */ {
+ 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1
+ }};
+
+ const Partition astc = FindClosestASTCPartition(partition);
+ EXPECT_THAT(astc.num_parts, Eq(partition.num_parts));
+}
+
+// Make sure that regardless of what partition we match against, it'll return a
+// partition with at most a fewer number of subsets
+TEST(PartitionTest, EstimatedPartitionFewerSubsets) {
+ std::mt19937 random(0xdeadbeef);
+ auto randUniform = [&random](int max) {
+ std::uniform_int_distribution<> dist(0, max - 1);
+ return dist(random);
+ };
+
+ constexpr int kNumFootprints = Footprint::NumValidFootprints();
+ const auto kFootprints = std::array<Footprint, kNumFootprints> {{
+ Footprint::Get4x4(),
+ Footprint::Get5x4(),
+ Footprint::Get5x5(),
+ Footprint::Get6x5(),
+ Footprint::Get6x6(),
+ Footprint::Get8x5(),
+ Footprint::Get8x6(),
+ Footprint::Get8x8(),
+ Footprint::Get10x5(),
+ Footprint::Get10x6(),
+ Footprint::Get10x8(),
+ Footprint::Get10x10(),
+ Footprint::Get12x10(),
+ Footprint::Get12x12()
+ }};
+
+ constexpr int kNumTests = 200;
+ for (int i = 0; i < kNumTests; ++i) {
+ const auto& footprint = kFootprints[randUniform(kNumFootprints)];
+ const int num_parts = 2 + randUniform(3);
+ Partition partition = {
+ footprint,
+ num_parts,
+ /* partition_id = */ {},
+ /* assignment = */ std::vector<int>(footprint.NumPixels(), 0)};
+
+ for (auto& p : partition.assignment) {
+ p = randUniform(num_parts);
+ }
+
+ const Partition astc = FindClosestASTCPartition(partition);
+ EXPECT_THAT(astc.num_parts, Le(partition.num_parts))
+ << "Test #" << i << ": "
+ << "Selected partition with ID " << astc.partition_id.value();
+ }
+}
+
+// Make sure that we generate unique partitions that are close to the
+// candidates.
+TEST(PartitionTest, UniquePartitionResults) {
+ Partition partition = {
+ /* footprint = */ Footprint::Get6x6(),
+ /* num_parts = */ 2,
+ /* partition_id = */ {},
+ /* assignment = */ {
+ 0, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1
+ }};
+
+ const auto parts = FindKClosestASTCPartitions(partition, 2);
+ EXPECT_THAT(*parts[0], Not(Eq(*parts[1])));
+}
+
+// TODO(google): Verify somehow that the assignment generated from
+// GetASTCPartition actually matches what's in the spec. The selection
+// function was more or less copy/pasted though so it's unclear how to
+// measure that against e.g. the ASTC encoder.
+
+} // namespace
diff --git a/src/decoder/test/physical_astc_block_test.cc b/src/decoder/test/physical_astc_block_test.cc
new file mode 100644
index 0000000..8eafe46
--- /dev/null
+++ b/src/decoder/test/physical_astc_block_test.cc
@@ -0,0 +1,361 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/physical_astc_block.h"
+#include "src/base/uint128.h"
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using astc_codec::PhysicalASTCBlock;
+using astc_codec::ColorEndpointMode;
+using astc_codec::base::UInt128;
+
+namespace {
+
+static const PhysicalASTCBlock kErrorBlock(UInt128(0));
+
+// Test to make sure that each of the constructors work and that
+// they produce the same block encodings, since the ASTC blocks
+// are little-endian
+TEST(PhysicalASTCBlockTest, TestConstructors) {
+ // Little-endian reading of bytes
+ PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
+ PhysicalASTCBlock blk2(
+ std::string("\x73\x01\x00\xFE\x01\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16));
+ EXPECT_EQ(blk1.GetBlockBits(), blk2.GetBlockBits());
+}
+
+// Test to see if we properly decode the maximum value that a weight
+// can take in an ASTC block based on the block mode encoding. We test
+// against a valid case and various error cases
+TEST(PhysicalASTCBlockTest, TestWeightRange) {
+ PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
+ auto weight_range = blk1.WeightRange();
+ ASSERT_TRUE(weight_range);
+ EXPECT_EQ(weight_range.value(), 7);
+
+ // If we flip the high bit then we should have a range of 31,
+ // although then we have too many bits and this should error.
+ PhysicalASTCBlock blk2(0x0000000001FE000373ULL);
+ EXPECT_FALSE(blk2.WeightRange());
+
+ // One bit per weight -- range of 1
+ PhysicalASTCBlock non_shared_cem(0x4000000000800D44ULL);
+ weight_range = non_shared_cem.WeightRange();
+ ASSERT_TRUE(weight_range);
+ EXPECT_EQ(weight_range.value(), 1);
+
+ // Error blocks have no weight range
+ EXPECT_FALSE(kErrorBlock.WeightRange());
+}
+
+// Test to see if we properly decode the weight grid width and height
+// in an ASTC block based on the block mode encoding. We test against
+// a valid case and various error cases
+TEST(PhysicalASTCBlockTest, TestWeightDims) {
+ PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
+ auto weight_dims = blk1.WeightGridDims();
+ EXPECT_TRUE(weight_dims);
+ EXPECT_EQ(weight_dims.value()[0], 6);
+ EXPECT_EQ(weight_dims.value()[1], 5);
+
+ // If we flip the high bit then we should have a range of 31,
+ // although then we have too many bits for the weight grid
+ // and this should error.
+ PhysicalASTCBlock blk2(0x0000000001FE000373ULL);
+ EXPECT_FALSE(blk2.WeightGridDims());
+ EXPECT_EQ(blk2.IsIllegalEncoding().value(),
+ "Too many bits required for weight grid");
+
+ // Dual plane block with 3x5 weight dims
+ PhysicalASTCBlock blk3(0x0000000001FE0005FFULL);
+ weight_dims = blk3.WeightGridDims();
+ ASSERT_TRUE(weight_dims);
+ EXPECT_EQ(weight_dims->at(0), 3);
+ EXPECT_EQ(weight_dims->at(1), 5);
+
+ // Error blocks shouldn't have any weight dims
+ EXPECT_FALSE(kErrorBlock.WeightGridDims());
+
+ PhysicalASTCBlock non_shared_cem(0x4000000000800D44ULL);
+ weight_dims = non_shared_cem.WeightGridDims();
+ ASSERT_TRUE(weight_dims);
+ EXPECT_EQ(weight_dims->at(0), 8);
+ EXPECT_EQ(weight_dims->at(1), 8);
+}
+
+// Test to see whether or not the presence of a dual-plane bit
+// is decoded properly. Error encodings are tested to *not* return
+// that they have dual planes.
+TEST(PhysicalASTCBlockTest, TestDualPlane) {
+ PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
+ EXPECT_FALSE(blk1.IsDualPlane());
+ EXPECT_FALSE(kErrorBlock.IsDualPlane());
+
+ // If we flip the dual plane bit, we will have too many bits
+ // for the weight grid and this should error
+ PhysicalASTCBlock blk2(0x0000000001FE000573ULL);
+ EXPECT_FALSE(blk2.IsDualPlane());
+ EXPECT_FALSE(blk2.WeightGridDims());
+ EXPECT_EQ(blk2.IsIllegalEncoding().value(),
+ "Too many bits required for weight grid");
+
+ // A dual plane with 3x5 weight grid should be supported
+ PhysicalASTCBlock blk3(0x0000000001FE0005FFULL);
+ EXPECT_TRUE(blk3.IsDualPlane());
+
+ // If we use the wrong block mode, then a valid block
+ // shouldn't have any dual plane
+ PhysicalASTCBlock blk4(0x0000000001FE000108ULL);
+ EXPECT_FALSE(blk4.IsDualPlane());
+ EXPECT_FALSE(blk4.IsIllegalEncoding());
+}
+
+// Make sure that we properly calculate the number of bits used to encode
+// the weight grid. Given error encodings or void extent blocks, this number
+// should be zero
+TEST(PhysicalASTCBlockTest, TestNumWeightBits) {
+ // 6x5 single-plane weight grid with 3-bit weights
+ // should have 90 bits for the weights.
+ PhysicalASTCBlock blk1(0x0000000001FE000173ULL);
+ EXPECT_EQ(90, blk1.NumWeightBits());
+
+ // Error block has no weight bits
+ EXPECT_FALSE(kErrorBlock.NumWeightBits());
+
+ // Void extent blocks have no weight bits
+ EXPECT_FALSE(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).NumWeightBits());
+
+ // If we flip the dual plane bit, we will have too many bits
+ // for the weight grid and this should error and return no bits
+ PhysicalASTCBlock blk2(0x0000000001FE000573ULL);
+ EXPECT_FALSE(blk2.NumWeightBits());
+
+ // 3x5 dual-plane weight grid with 3-bit weights
+ // should have 90 bits for the weights.
+ PhysicalASTCBlock blk3(0x0000000001FE0005FFULL);
+ EXPECT_EQ(90, blk3.NumWeightBits());
+}
+
+// Test to make sure that our weight bits start where we expect them to.
+// In other words, make sure that the calculation based on the block mode for
+// where the weight bits start is accurate.
+TEST(PhysicalASTCBlockTest, TestStartWeightBit) {
+ EXPECT_EQ(PhysicalASTCBlock(0x4000000000800D44ULL).WeightStartBit(), 64);
+
+ // Error blocks have no weight start bit
+ EXPECT_FALSE(kErrorBlock.WeightStartBit());
+
+ // Void extent blocks have no weight start bit
+ EXPECT_FALSE(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).WeightStartBit());
+}
+
+// Test to make sure that we catch various different reasons for error encoding
+// of ASTC blocks, but also that certain encodings aren't errors.
+TEST(PhysicalASTCBlockTest, TestErrorBlocks) {
+ // Various valid block modes
+ EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).IsIllegalEncoding());
+ EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE0005FFULL).IsIllegalEncoding());
+ EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000108ULL).IsIllegalEncoding());
+
+ // This is an error because it uses an invalid block mode
+ EXPECT_EQ(kErrorBlock.IsIllegalEncoding().value(), "Reserved block mode");
+
+ // This is an error because we have too many weight bits
+ PhysicalASTCBlock err_blk(0x0000000001FE000573ULL);
+ EXPECT_EQ(err_blk.IsIllegalEncoding().value(),
+ "Too many bits required for weight grid");
+
+ // This is an error because we have too many weights
+ PhysicalASTCBlock err_blk2 = PhysicalASTCBlock(0x0000000001FE0005A8ULL);
+ EXPECT_EQ(err_blk2.IsIllegalEncoding().value(), "Too many weights specified");
+
+ PhysicalASTCBlock err_blk3 = PhysicalASTCBlock(0x0000000001FE000588ULL);
+ EXPECT_EQ(err_blk3.IsIllegalEncoding().value(), "Too many weights specified");
+
+ // This is an error because we have too few weights
+ PhysicalASTCBlock err_blk4 = PhysicalASTCBlock(0x0000000001FE00002ULL);
+ EXPECT_EQ(err_blk4.IsIllegalEncoding().value(),
+ "Too few bits required for weight grid");
+
+ // Four partitions, dual plane -- should be error
+ // 2x2 weight grid, 3 bits per weight
+ PhysicalASTCBlock dual_plane_four_parts(0x000000000000001D1FULL);
+ EXPECT_FALSE(dual_plane_four_parts.NumPartitions());
+ EXPECT_EQ(dual_plane_four_parts.IsIllegalEncoding().value(),
+ "Both four partitions and dual plane specified");
+}
+
+// Test to make sure that we properly identify and can manipulate void-extent
+// blocks. These are ASTC blocks that only define a single color for the entire
+// block.
+TEST(PhysicalASTCBlockTest, TestVoidExtentBlocks) {
+ // Various valid block modes that aren't void extent blocks
+ EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).IsVoidExtent());
+ EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE0005FFULL).IsVoidExtent());
+ EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000108ULL).IsVoidExtent());
+
+ // Error block is not a void extent block
+ EXPECT_FALSE(kErrorBlock.IsVoidExtent());
+
+ // Void extent block is void extent block...
+ UInt128 void_extent_encoding(0, 0xFFF8003FFE000DFCULL);
+ EXPECT_FALSE(PhysicalASTCBlock(void_extent_encoding).IsIllegalEncoding());
+ EXPECT_TRUE(PhysicalASTCBlock(void_extent_encoding).IsVoidExtent());
+
+ // If we modify the high 64 bits it shouldn't change anything
+ void_extent_encoding |= UInt128(0xdeadbeefdeadbeef, 0);
+ EXPECT_FALSE(PhysicalASTCBlock(void_extent_encoding).IsIllegalEncoding());
+ EXPECT_TRUE(PhysicalASTCBlock(void_extent_encoding).IsVoidExtent());
+}
+
+TEST(PhysicalASTCBlockTest, TestVoidExtentCoordinates) {
+ // The void extent block should have texture coordinates from 0-8191
+ auto coords = PhysicalASTCBlock(0xFFF8003FFE000DFCULL).VoidExtentCoords();
+ EXPECT_EQ(coords->at(0), 0);
+ EXPECT_EQ(coords->at(1), 8191);
+ EXPECT_EQ(coords->at(2), 0);
+ EXPECT_EQ(coords->at(3), 8191);
+
+ // If we set the coords to all 1's then it's still a void extent
+ // block, but there aren't any void extent coords.
+ EXPECT_FALSE(PhysicalASTCBlock(0xFFFFFFFFFFFFFDFCULL).IsIllegalEncoding());
+ EXPECT_TRUE(PhysicalASTCBlock(0xFFFFFFFFFFFFFDFCULL).IsVoidExtent());
+ EXPECT_FALSE(PhysicalASTCBlock(0xFFFFFFFFFFFFFDFCULL).VoidExtentCoords());
+
+ // If we set the void extent coords to something where the coords are
+ // >= each other, then the encoding is illegal.
+ EXPECT_TRUE(PhysicalASTCBlock(0x0008004002001DFCULL).IsIllegalEncoding());
+ EXPECT_TRUE(PhysicalASTCBlock(0x0007FFC001FFFDFCULL).IsIllegalEncoding());
+}
+
+// Test to see if we can properly identify the number of partitions in a block
+// In particular -- we need to make sure we properly identify single and
+// multi-partition blocks, but also that void extent and error blocks don't
+// return valid numbers of partitions
+TEST(PhysicalASTCBlockTest, TestNumPartitions) {
+ // Various valid block modes, but all single partition
+ EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).NumPartitions(), 1);
+ EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE0005FFULL).NumPartitions(), 1);
+ EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000108ULL).NumPartitions(), 1);
+
+ // Two to four partitions don't have enough bits for color.
+ EXPECT_FALSE(PhysicalASTCBlock(0x000000000000000973ULL).NumPartitions());
+ EXPECT_FALSE(PhysicalASTCBlock(0x000000000000001173ULL).NumPartitions());
+ EXPECT_FALSE(PhysicalASTCBlock(0x000000000000001973ULL).NumPartitions());
+
+ // Test against having more than one partition
+ PhysicalASTCBlock non_shared_cem(0x4000000000800D44ULL);
+ EXPECT_EQ(non_shared_cem.NumPartitions(), 2);
+}
+
+// Test the color endpoint modes specified for how the endpoints are encoded.
+// In particular, test that shared color endpoint modes work for multi-partition
+// blocks and that non-shared color endpoint modes also work.
+TEST(PhysicalASTCBlockTest, TestColorEndpointModes) {
+ // Four partitions -- one shared CEM
+ const auto blk1 = PhysicalASTCBlock(0x000000000000001961ULL);
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_EQ(blk1.GetEndpointMode(i), ColorEndpointMode::kLDRLumaDirect);
+ }
+
+ // Void extent blocks have no endpoint modes
+ EXPECT_FALSE(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).GetEndpointMode(0));
+
+ // Test out of range partitions
+ EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).GetEndpointMode(1));
+ EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).GetEndpointMode(-1));
+ EXPECT_FALSE(PhysicalASTCBlock(0x0000000001FE000173ULL).GetEndpointMode(100));
+
+ // Error blocks have no endpoint modes
+ EXPECT_FALSE(kErrorBlock.GetEndpointMode(0));
+
+ // Test non-shared CEMs
+ PhysicalASTCBlock non_shared_cem(0x4000000000800D44ULL);
+ EXPECT_EQ(non_shared_cem.GetEndpointMode(0),
+ ColorEndpointMode::kLDRLumaDirect);
+ EXPECT_EQ(non_shared_cem.GetEndpointMode(1),
+ ColorEndpointMode::kLDRLumaBaseOffset);
+}
+
+// Make sure that if we have more than one partition then we have proper
+// partition IDs (these determine which pixels correspond to which partition)
+TEST(PhysicalASTCBlockTest, TestPartitionID) {
+ // Valid partitions
+ EXPECT_EQ(PhysicalASTCBlock(0x4000000000FFED44ULL).PartitionID(), 0x3FF);
+ EXPECT_EQ(PhysicalASTCBlock(0x4000000000AAAD44ULL).PartitionID(), 0x155);
+
+ // Error blocks have no partition IDs
+ EXPECT_FALSE(kErrorBlock.PartitionID());
+
+ // Void extent blocks have no endpoint modes
+ EXPECT_FALSE(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).PartitionID());
+}
+
+// Make sure that we're properly attributing the number of bits associated with
+// the encoded color values.
+TEST(PhysicalASTCBlockTest, TestNumColorBits) {
+ // If we're using a direct luma channel, then the number of color bits is 16
+ EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).NumColorValues(), 2);
+ EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).NumColorBits(), 16);
+
+ // Error blocks have nothing
+ EXPECT_FALSE(kErrorBlock.NumColorValues());
+ EXPECT_FALSE(kErrorBlock.NumColorBits());
+
+ // Void extent blocks have four color values and 64 bits of color
+ EXPECT_EQ(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).NumColorValues(), 4);
+ EXPECT_EQ(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).NumColorBits(), 64);
+}
+
+// Make sure that we're properly decoding the range of values that each of the
+// encoded color values can take
+TEST(PhysicalASTCBlockTest, TestColorValuesRange) {
+ // If we're using a direct luma channel, then we use two color values up to
+ // a full byte each.
+ EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).ColorValuesRange(), 255);
+
+ // Error blocks have nothing
+ EXPECT_FALSE(kErrorBlock.ColorValuesRange());
+
+ // Void extent blocks have four color values and 64 bits of color, so the
+ // color range for each is sixteen bits.
+ EXPECT_EQ(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).ColorValuesRange(),
+ (1 << 16) - 1);
+}
+
+// Test that we know where the color data starts. This is different mostly
+// depending on whether or not the block is single-partition or void extent.
+TEST(PhysicalASTCBlockTest, TestColorStartBits) {
+ // Void extent blocks start at bit 64
+ EXPECT_EQ(PhysicalASTCBlock(0xFFF8003FFE000DFCULL).ColorStartBit(), 64);
+
+ // Error blocks don't start anywhere
+ EXPECT_FALSE(kErrorBlock.ColorStartBit());
+
+ // Single partition blocks start at bit 17
+ EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000173ULL).ColorStartBit(), 17);
+ EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE0005FFULL).ColorStartBit(), 17);
+ EXPECT_EQ(PhysicalASTCBlock(0x0000000001FE000108ULL).ColorStartBit(), 17);
+
+ // Multi-partition blocks start at bit 29
+ EXPECT_EQ(PhysicalASTCBlock(0x4000000000FFED44ULL).ColorStartBit(), 29);
+ EXPECT_EQ(PhysicalASTCBlock(0x4000000000AAAD44ULL).ColorStartBit(), 29);
+}
+
+} // namespace
diff --git a/src/decoder/test/quantization_test.cc b/src/decoder/test/quantization_test.cc
new file mode 100644
index 0000000..f882876
--- /dev/null
+++ b/src/decoder/test/quantization_test.cc
@@ -0,0 +1,288 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/quantization.h"
+#include "src/decoder/integer_sequence_codec.h"
+
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace astc_codec {
+
+namespace {
+
+// Make sure that we never exceed the maximum range that we pass in.
+TEST(QuantizationTest, TestQuantizeMaxRange) {
+ for (int i = kEndpointRangeMinValue; i < 256; ++i) {
+ EXPECT_LE(QuantizeCEValueToRange(255, i), i);
+ }
+
+ for (int i = 1; i < kWeightRangeMaxValue; ++i) {
+ EXPECT_LE(QuantizeWeightToRange(64, i), i);
+ }
+}
+
+// Make sure that whenever we unquantize and requantize a value we get back
+// what we started with.
+TEST(QuantizationTest, TestReversibility) {
+ for (auto itr = ISERangeBegin(); itr != ISERangeEnd(); itr++) {
+ const int range = *itr;
+ if (range <= kWeightRangeMaxValue) {
+ for (int j = 0; j <= range; ++j) {
+ const int q = UnquantizeWeightFromRange(j, range);
+ EXPECT_EQ(QuantizeWeightToRange(q, range), j);
+ }
+ }
+
+ if (range >= kEndpointRangeMinValue) {
+ for (int j = 0; j <= range; ++j) {
+ const int q = UnquantizeCEValueFromRange(j, range);
+ EXPECT_EQ(QuantizeCEValueToRange(q, range), j);
+ }
+ }
+ }
+}
+
+// Make sure that whenever we quantize a non-maximal value it gets sent to the
+// proper range
+TEST(QuantizationTest, TestQuantizationRange) {
+ for (auto itr = ISERangeBegin(); itr != ISERangeEnd(); itr++) {
+ const int range = *itr;
+ if (range >= kEndpointRangeMinValue) {
+ EXPECT_LE(QuantizeCEValueToRange(0, range), range);
+ EXPECT_LE(QuantizeCEValueToRange(4, range), range);
+ EXPECT_LE(QuantizeCEValueToRange(15, range), range);
+ EXPECT_LE(QuantizeCEValueToRange(22, range), range);
+ EXPECT_LE(QuantizeCEValueToRange(66, range), range);
+ EXPECT_LE(QuantizeCEValueToRange(91, range), range);
+ EXPECT_LE(QuantizeCEValueToRange(126, range), range);
+ }
+
+ if (range <= kWeightRangeMaxValue) {
+ EXPECT_LE(QuantizeWeightToRange(0, range), range);
+ EXPECT_LE(QuantizeWeightToRange(4, range), range);
+ EXPECT_LE(QuantizeWeightToRange(15, range), range);
+ EXPECT_LE(QuantizeWeightToRange(22, range), range);
+ }
+ }
+}
+
+// Make sure that whenever we unquantize a value it remains within [0, 255]
+TEST(QuantizationTest, TestUnquantizationRange) {
+ EXPECT_LT(UnquantizeCEValueFromRange(2, 7), 256);
+ EXPECT_LT(UnquantizeCEValueFromRange(7, 7), 256);
+ EXPECT_LT(UnquantizeCEValueFromRange(39, 63), 256);
+ EXPECT_LT(UnquantizeCEValueFromRange(66, 79), 256);
+ EXPECT_LT(UnquantizeCEValueFromRange(91, 191), 256);
+ EXPECT_LT(UnquantizeCEValueFromRange(126, 255), 256);
+ EXPECT_LT(UnquantizeCEValueFromRange(255, 255), 256);
+
+ EXPECT_LE(UnquantizeWeightFromRange(0, 1), 64);
+ EXPECT_LE(UnquantizeWeightFromRange(2, 7), 64);
+ EXPECT_LE(UnquantizeWeightFromRange(7, 7), 64);
+ EXPECT_LE(UnquantizeWeightFromRange(29, 31), 64);
+}
+
+// When we quantize a value, it should use the largest quantization range that
+// does not exceed the desired range.
+TEST(QuantizationTest, TestUpperBoundRanges) {
+ auto expected_range_itr = ISERangeBegin();
+ for (int desired_range = 1; desired_range < 256; ++desired_range) {
+ if (desired_range == *(expected_range_itr + 1)) {
+ ++expected_range_itr;
+ }
+ const int expected_range = *expected_range_itr;
+ ASSERT_LE(expected_range, desired_range);
+
+ if (desired_range >= kEndpointRangeMinValue) {
+ EXPECT_EQ(QuantizeCEValueToRange(0, desired_range),
+ QuantizeCEValueToRange(0, expected_range));
+
+ EXPECT_EQ(QuantizeCEValueToRange(208, desired_range),
+ QuantizeCEValueToRange(208, expected_range));
+
+ EXPECT_EQ(QuantizeCEValueToRange(173, desired_range),
+ QuantizeCEValueToRange(173, expected_range));
+
+ EXPECT_EQ(QuantizeCEValueToRange(13, desired_range),
+ QuantizeCEValueToRange(13, expected_range));
+
+ EXPECT_EQ(QuantizeCEValueToRange(255, desired_range),
+ QuantizeCEValueToRange(255, expected_range));
+ }
+
+ if (desired_range <= kWeightRangeMaxValue) {
+ EXPECT_EQ(QuantizeWeightToRange(0, desired_range),
+ QuantizeWeightToRange(0, expected_range));
+
+ EXPECT_EQ(QuantizeWeightToRange(63, desired_range),
+ QuantizeWeightToRange(63, expected_range));
+
+ EXPECT_EQ(QuantizeWeightToRange(12, desired_range),
+ QuantizeWeightToRange(12, expected_range));
+
+ EXPECT_EQ(QuantizeWeightToRange(23, desired_range),
+ QuantizeWeightToRange(23, expected_range));
+ }
+ }
+
+ // Make sure that we covered all the possible ranges
+ ASSERT_EQ(std::next(expected_range_itr), ISERangeEnd());
+}
+
+// Make sure that quantizing to the largest range is the identity function.
+TEST(QuantizationTest, TestIdentity) {
+ for (int i = 0; i < 256; ++i) {
+ EXPECT_EQ(QuantizeCEValueToRange(i, 255), i);
+ }
+
+ // Note: This doesn't apply to weights since there's a weird hack to convert
+ // values from [0, 31] to [0, 64].
+}
+
+// Make sure that bit quantization is monotonic with respect to the input,
+// since quantizing and dequantizing bits is a matter of truncation and bit
+// replication
+TEST(QuantizationTest, TestMonotonicBitPacking) {
+ for (int num_bits = 3; num_bits < 8; ++num_bits) {
+ const int range = (1 << num_bits) - 1;
+ int last_quant_val = -1;
+ for (int i = 0; i < 256; ++i) {
+ const int quant_val = QuantizeCEValueToRange(i, range);
+ EXPECT_LE(last_quant_val, quant_val);
+ last_quant_val = quant_val;
+ }
+
+ // Also expect the last quantization val to be equal to the range
+ EXPECT_EQ(last_quant_val, range);
+
+ if (range <= kWeightRangeMaxValue) {
+ last_quant_val = -1;
+ for (int i = 0; i <= 64; ++i) {
+ const int quant_val = QuantizeWeightToRange(i, range);
+ EXPECT_LE(last_quant_val, quant_val);
+ last_quant_val = quant_val;
+ }
+ EXPECT_EQ(last_quant_val, range);
+ }
+ }
+}
+
+// Make sure that bit quantization reflects that quantized values below the bit
+// replication threshold get mapped to zero
+TEST(QuantizationTest, TestSmallBitPacking) {
+ for (int num_bits = 1; num_bits <= 8; ++num_bits) {
+ const int range = (1 << num_bits) - 1;
+
+ // The largest number that should map to zero is one less than half of the
+ // smallest representation w.r.t. range. For example: if we have a range
+ // of 7, it means that we have 3 total bits abc for quantized values. If we
+ // unquantize to 8 bits, it means that our resulting value will be abcabcab.
+ // Hence, we map 000 to 0 and 001 to 0b00100100 = 36. The earliest value
+ // that should not map to zero with three bits is therefore 0b00001111 = 15.
+ // This ends up being (1 << (8 - 3 - 1)) - 1. We don't use 0b00011111 = 31
+ // because this would "round up" to 1 during quantization. This value is not
+ // necessarily the largest, but it is the largest that we can *guarantee*
+ // should map to zero.
+
+ if (range >= kEndpointRangeMinValue) {
+ constexpr int cev_bits = 8;
+ const int half_max_quant_bits = std::max(0, cev_bits - num_bits - 1);
+ const int largest_cev_to_zero = (1 << half_max_quant_bits) - 1;
+ EXPECT_EQ(QuantizeCEValueToRange(largest_cev_to_zero, range), 0)
+ << " Largest CEV to zero: " << largest_cev_to_zero
+ << " Range: " << range;
+ }
+
+ if (range <= kWeightRangeMaxValue) {
+ constexpr int weight_bits = 6;
+ const int half_max_quant_bits = std::max(0, weight_bits - num_bits - 1);
+ const int largest_weight_to_zero = (1 << half_max_quant_bits) - 1;
+ EXPECT_EQ(QuantizeWeightToRange(largest_weight_to_zero, range), 0)
+ << " Largest weight to zero: " << largest_weight_to_zero
+ << " Range: " << range;
+ }
+ }
+}
+
+// Test specific quint and trit weight encodings with values that were obtained
+// using the reference ASTC codec.
+TEST(QuantizationTest, TestSpecificQuintTritPackings) {
+ std::vector<int> vals = { 4, 6, 4, 6, 7, 5, 7, 5 };
+ std::vector<int> quantized;
+
+ // Test a quint packing
+ std::transform(
+ vals.begin(), vals.end(), std::back_inserter(quantized),
+ std::bind(UnquantizeWeightFromRange, std::placeholders::_1, 9));
+ const std::vector<int> quintExpected = {14, 21, 14, 21, 43, 50, 43, 50 };
+ EXPECT_EQ(quantized, quintExpected);
+
+ // Test a trit packing
+ std::transform(
+ vals.begin(), vals.end(), quantized.begin(),
+ std::bind(UnquantizeWeightFromRange, std::placeholders::_1, 11));
+ const std::vector<int> tritExpected = { 5, 23, 5, 23, 41, 59, 41, 59 };
+ EXPECT_EQ(quantized, tritExpected);
+}
+
+// Make sure that we properly die when we pass in values below the minimum
+// allowed ranges for our quantization intervals.
+TEST(QuantizationDeathTest, TestInvalidMinRange) {
+ for (int i = 0; i < kEndpointRangeMinValue; ++i) {
+ EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(0, i), "");
+ EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(0, i), "");
+ }
+
+ EXPECT_DEBUG_DEATH(QuantizeWeightToRange(0, 0), "");
+ EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(0, 0), "");
+}
+
+// Make sure that we properly die when we pass in bogus values.
+TEST(QuantizationDeathTest, TestOutOfRange) {
+ EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(-1, 10), "");
+ EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(256, 7), "");
+ EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(10000, 17), "");
+
+ EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(-1, 10), "");
+ EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(8, 7), "");
+ EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(-1000, 17), "");
+
+ EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(0, -7), "");
+ EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(0, -17), "");
+
+ EXPECT_DEBUG_DEATH(QuantizeCEValueToRange(0, 257), "");
+ EXPECT_DEBUG_DEATH(UnquantizeCEValueFromRange(0, 256), "");
+
+ EXPECT_DEBUG_DEATH(QuantizeWeightToRange(-1, 10), "");
+ EXPECT_DEBUG_DEATH(QuantizeWeightToRange(256, 7), "");
+ EXPECT_DEBUG_DEATH(QuantizeWeightToRange(10000, 17), "");
+
+ EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(-1, 10), "");
+ EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(8, 7), "");
+ EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(-1000, 17), "");
+
+ EXPECT_DEBUG_DEATH(QuantizeWeightToRange(0, -7), "");
+ EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(0, -17), "");
+
+ EXPECT_DEBUG_DEATH(QuantizeWeightToRange(0, 32), "");
+ EXPECT_DEBUG_DEATH(UnquantizeWeightFromRange(0, 64), "");
+}
+
+} // namespace
+
+} // namespace astc_codec
diff --git a/src/decoder/test/weight_infill_test.cc b/src/decoder/test/weight_infill_test.cc
new file mode 100644
index 0000000..79c7745
--- /dev/null
+++ b/src/decoder/test/weight_infill_test.cc
@@ -0,0 +1,69 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/decoder/weight_infill.h"
+#include "src/decoder/footprint.h"
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+namespace astc_codec {
+
+namespace {
+
+// Make sure that the physical size of the bit representations for certain
+// dimensions of weight grids matches our expectations
+TEST(ASTCWeightInfillTest, TestGetBitCount) {
+ // Bit encodings
+ EXPECT_EQ(32, CountBitsForWeights(4, 4, 3));
+ EXPECT_EQ(48, CountBitsForWeights(4, 4, 7));
+ EXPECT_EQ(24, CountBitsForWeights(2, 4, 7));
+ EXPECT_EQ(8, CountBitsForWeights(2, 4, 1));
+
+ // Trit encodings
+ EXPECT_EQ(32, CountBitsForWeights(4, 5, 2));
+ EXPECT_EQ(26, CountBitsForWeights(4, 4, 2));
+ EXPECT_EQ(52, CountBitsForWeights(4, 5, 5));
+ EXPECT_EQ(42, CountBitsForWeights(4, 4, 5));
+
+ // Quint encodings
+ EXPECT_EQ(21, CountBitsForWeights(3, 3, 4));
+ EXPECT_EQ(38, CountBitsForWeights(4, 4, 4));
+ EXPECT_EQ(49, CountBitsForWeights(3, 7, 4));
+ EXPECT_EQ(52, CountBitsForWeights(4, 3, 19));
+ EXPECT_EQ(70, CountBitsForWeights(4, 4, 19));
+}
+
+// Make sure that we bilerp our weights properly
+TEST(ASTCWeightInfillTest, TestInfillBilerp) {
+ std::vector<int> weights = InfillWeights(
+ {{ 1, 3, 5, 3, 5, 7, 5, 7, 9 }}, Footprint::Get5x5(), 3, 3);
+
+ std::vector<int> expected_weights = {
+ 1, 2, 3, 4, 5,
+ 2, 3, 4, 5, 6,
+ 3, 4, 5, 6, 7,
+ 4, 5, 6, 7, 8,
+ 5, 6, 7, 8, 9 };
+
+ ASSERT_EQ(weights.size(), expected_weights.size());
+ for (int i = 0; i < weights.size(); ++i) {
+ EXPECT_EQ(weights[i], expected_weights[i]);
+ }
+}
+
+} // namespace
+
+} // namespace astc_codec