aboutsummaryrefslogtreecommitdiff
path: root/src/decoder/endpoint_codec.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/decoder/endpoint_codec.cc')
-rw-r--r--src/decoder/endpoint_codec.cc967
1 files changed, 967 insertions, 0 deletions
diff --git a/src/decoder/endpoint_codec.cc b/src/decoder/endpoint_codec.cc
new file mode 100644
index 0000000..1513d15
--- /dev/null
+++ b/src/decoder/endpoint_codec.cc
@@ -0,0 +1,967 @@
+// 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/quantization.h"
+
+#include <algorithm>
+#include <array>
+#include <numeric>
+#include <utility>
+
+namespace astc_codec {
+
+namespace {
+
+template<typename T>
+T Clamp(T value, T min, T max) {
+ return value < min ? min : (value > max ? max : value);
+}
+
+// This is the 'blue_contract' function defined in Section C.2.14 of the ASTC
+// specification.
+template<typename ArrayType>
+void BlueContract(ArrayType* const cptr) {
+ ArrayType& c = *cptr;
+ c[0] = (c[0] + c[2]) >> 1;
+ c[1] = (c[1] + c[2]) >> 1;
+}
+
+// Returns the inverse of values in BlueContract, subjected to the constraint
+// that the new values are stored in the range [0, 255].
+template<typename ArrayType>
+ArrayType InvertBlueContract(const ArrayType& c) {
+ ArrayType result = c;
+ result[0] = Clamp(2 * c[0] - c[2], 0, 255);
+ result[1] = Clamp(2 * c[1] - c[2], 0, 255);
+ return result;
+}
+
+// This is the 'bit_transfer_signed' function defined in Section C.2.14 of the
+// ASTC specification.
+void BitTransferSigned(int* const a, int* const b) {
+ *b >>= 1;
+ *b |= *a & 0x80;
+ *a >>= 1;
+ *a &= 0x3F;
+ if ((*a & 0x20) != 0) {
+ *a -= 0x40;
+ }
+}
+
+// Takes two values, |a| in the range [-32, 31], and |b| in the range [0, 255],
+// and returns the two values in [0, 255] that will reconstruct |a| and |b| when
+// passed to the BitTransferSigned function.
+void InvertBitTransferSigned(int* const a, int* const b) {
+ assert(*a >= -32); assert(*a < 32);
+ assert(*b >= 0); assert(*b < 256);
+
+ if (*a < 0) {
+ *a += 0x40;
+ }
+ *a <<= 1;
+ *a |= (*b & 0x80);
+ *b <<= 1;
+ *b &= 0xff;
+}
+
+RgbColor StripAlpha(const RgbaColor& c) {
+ return RgbColor {{ c[0], c[1], c[2] }};
+}
+
+template<typename ContainerType>
+void Quantize(ContainerType* const c, size_t max_value) {
+ for (auto& x : *c) {
+ x = QuantizeCEValueToRange(x, max_value);
+ }
+}
+
+template<typename ArrayType>
+ArrayType QuantizeColor(const ArrayType& c, size_t max_value) {
+ ArrayType result = c;
+ Quantize(&result, max_value);
+ return result;
+}
+
+template<typename ContainerType>
+void Unquantize(ContainerType* const c, size_t max_value) {
+ for (auto& x : *c) {
+ x = UnquantizeCEValueFromRange(x, max_value);
+ }
+}
+
+template<typename ArrayType>
+ArrayType UnquantizeColor(const ArrayType& c, size_t max_value) {
+ ArrayType result = c;
+ Unquantize(&result, max_value);
+ return result;
+}
+
+// Returns the average of the three RGB channels.
+template<typename ContainerType>
+const int AverageRGB(const ContainerType& c) {
+ // Each channel can be in the range [0, 255], and we need to divide by three.
+ // However, we want to round the error properly. Both (x + 1) / 3 and
+ // (x + 2) / 3 are relatively imprecise when it comes to rounding, so instead
+ // we increase the precision by multiplying our numerator by some arbitrary
+ // number. Here, we choose 256 to get 8 additional bits and maintain
+ // performance since it turns into a shift rather than a multiply. Our
+ // denominator then becomes 3 * 256 = 768.
+ return (std::accumulate(c.begin(), c.begin() + 3, 0) * 256 + 384) / 768;
+}
+
+// Returns the sum of squared differences between each element of |a| and |b|,
+// which are assumed to contain the same number of elements.
+template<typename ContainerType>
+const typename ContainerType::value_type SquaredError(
+ const ContainerType& a, const ContainerType& b,
+ size_t num_channels = std::tuple_size<ContainerType>::value) {
+ using ValueTy = typename ContainerType::value_type;
+ static_assert(std::is_signed<ValueTy>::value,
+ "Value type assumed to be signed to avoid branch below.");
+ ValueTy result = ValueTy(0);
+ for (int i = 0; i < num_channels; ++i) {
+ ValueTy error = a[i] - b[i];
+ result += error * error;
+ }
+ return result;
+}
+
+constexpr int MaxValuesForModes(ColorEndpointMode mode_a,
+ ColorEndpointMode mode_b) {
+ return (NumColorValuesForEndpointMode(mode_a) >
+ NumColorValuesForEndpointMode(mode_b))
+ ? NumColorValuesForEndpointMode(mode_a)
+ : NumColorValuesForEndpointMode(mode_b);
+}
+
+// This function takes the two colors in |endpoint_low| and |endpoint_high| and
+// encodes them into |vals| according to the ASTC spec in section C.2.14. It
+// assumes that the two colors are close enough to grayscale that the encoding
+// should use the ColorEndpointMode kLDRLumaBaseOffset or kLDRLumaDirect. Which
+// one is chosen depends on which produces smaller error for the given
+// quantization value stored in |max_value|
+bool EncodeColorsLuma(const RgbaColor& endpoint_low,
+ const RgbaColor& endpoint_high,
+ int max_value, ColorEndpointMode* const astc_mode,
+ std::vector<int>* const vals) {
+ assert(vals->size() ==
+ NumValuesForEncodingMode(EndpointEncodingMode::kDirectLuma));
+ int avg1 = AverageRGB(endpoint_low);
+ int avg2 = AverageRGB(endpoint_high);
+
+ // For the offset mode, L1 is strictly greater than L2, so if we are using
+ // it to encode the color values, we need to swap the weights and
+ // endpoints so that the larger of the two is the second endpoint.
+ bool needs_weight_swap = false;
+ if (avg1 > avg2) {
+ needs_weight_swap = true;
+ std::swap(avg1, avg2);
+ }
+ assert(avg1 <= avg2);
+
+ // Now, the first endpoint is based on the low-order six bits of the first
+ // value, and the high order two bits of the second value. The low order
+ // six bits of the second value are used as the (strictly positive) offset
+ // from the first value.
+ const int offset = std::min(avg2 - avg1, 0x3F);
+ const int quant_off_low =
+ QuantizeCEValueToRange((avg1 & 0x3F) << 2, max_value);
+ const int quant_off_high =
+ QuantizeCEValueToRange((avg1 & 0xC0) | offset, max_value);
+
+ const int quant_low = QuantizeCEValueToRange(avg1, max_value);
+ const int quant_high = QuantizeCEValueToRange(avg2, max_value);
+
+ RgbaColor unquant_off_low, unquant_off_high;
+ RgbaColor unquant_low, unquant_high;
+
+ (*vals)[0] = quant_off_low;
+ (*vals)[1] = quant_off_high;
+ DecodeColorsForMode(
+ *vals, max_value, ColorEndpointMode::kLDRLumaBaseOffset,
+ &unquant_off_low, &unquant_off_high);
+
+ (*vals)[0] = quant_low;
+ (*vals)[1] = quant_high;
+ DecodeColorsForMode(*vals, max_value, ColorEndpointMode::kLDRLumaDirect,
+ &unquant_low, &unquant_high);
+
+ const auto calculate_error =
+ [needs_weight_swap, &endpoint_low, &endpoint_high]
+ (const RgbaColor& low, const RgbaColor& high) {
+ int error = 0;
+ if (needs_weight_swap) {
+ error += SquaredError(low, endpoint_high);
+ error += SquaredError(high, endpoint_low);
+ } else {
+ error += SquaredError(low, endpoint_low);
+ error += SquaredError(high, endpoint_high);
+ }
+ return error;
+ };
+
+ const int direct_error = calculate_error(unquant_low, unquant_high);
+ const int off_error = calculate_error(unquant_off_low, unquant_off_high);
+
+ if (direct_error <= off_error) {
+ (*vals)[0] = quant_low;
+ (*vals)[1] = quant_high;
+ *astc_mode = ColorEndpointMode::kLDRLumaDirect;
+ } else {
+ (*vals)[0] = quant_off_low;
+ (*vals)[1] = quant_off_high;
+ *astc_mode = ColorEndpointMode::kLDRLumaBaseOffset;
+ }
+
+ return needs_weight_swap;
+}
+
+class QuantizedEndpointPair {
+ public:
+ QuantizedEndpointPair(const RgbaColor& c_low, const RgbaColor& c_high,
+ int max_value)
+ : orig_low_(c_low),
+ orig_high_(c_high),
+ quant_low_(QuantizeColor(c_low, max_value)),
+ quant_high_(QuantizeColor(c_high, max_value)),
+ unquant_low_(UnquantizeColor(quant_low_, max_value)),
+ unquant_high_(UnquantizeColor(quant_high_, max_value)) { }
+
+ const RgbaColor& QuantizedLow() const { return quant_low_; }
+ const RgbaColor& QuantizedHigh() const { return quant_high_; }
+
+ const RgbaColor& UnquantizedLow() const { return unquant_low_; }
+ const RgbaColor& UnquantizedHigh() const { return unquant_high_; }
+
+ const RgbaColor& OriginalLow() const { return orig_low_; }
+ const RgbaColor& OriginalHigh() const { return orig_high_; }
+
+ private:
+ RgbaColor orig_low_;
+ RgbaColor orig_high_;
+
+ RgbaColor quant_low_;
+ RgbaColor quant_high_;
+
+ RgbaColor unquant_low_;
+ RgbaColor unquant_high_;
+};
+
+class CEEncodingOption {
+ public:
+ CEEncodingOption() { }
+ CEEncodingOption(
+ int squared_error, const QuantizedEndpointPair* quantized_endpoints,
+ bool swap_endpoints, bool blue_contract, bool use_offset_mode)
+ : squared_error_(squared_error),
+ quantized_endpoints_(quantized_endpoints),
+ swap_endpoints_(swap_endpoints),
+ blue_contract_(blue_contract),
+ use_offset_mode_(use_offset_mode) { }
+
+ // Returns true if able to generate valid |astc_mode| and |vals|. In some
+ // instances, such as if the endpoints reprsent a base/offset pair, we may not
+ // be able to guarantee blue-contract encoding due to how the base/offset pair
+ // are represented and the specifics of the decoding procedure. Similarly,
+ // some direct RGBA encodings also may not be able to emit blue-contract modes
+ // due to an unlucky combination of channels. In these instances, this
+ // function will return false, and all pointers will remain unmodified.
+ bool Pack(bool with_alpha, ColorEndpointMode* const astc_mode,
+ std::vector<int>* const vals, bool* const needs_weight_swap) const {
+ auto unquantized_low = quantized_endpoints_->UnquantizedLow();
+ auto unquantized_high = quantized_endpoints_->UnquantizedHigh();
+
+ // In offset mode, we do BitTransferSigned before analyzing the values
+ // of the endpoints in order to determine whether or not we're going to
+ // be using blue-contract mode.
+ if (use_offset_mode_) {
+ for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
+ BitTransferSigned(&unquantized_high[i], &unquantized_low[i]);
+ }
+ }
+
+ // Define variables as outlined in the ASTC spec C.2.14 for the RGB[A]
+ // direct and base-offset modes
+ int s0 = 0, s1 = 0;
+ for (int i = 0; i < 3; ++i) {
+ s0 += unquantized_low[i];
+ s1 += unquantized_high[i];
+ }
+
+ // Can we guarantee a blue-contract mode if we want it? In other words,
+ // if we swap which endpoint is high and which endpoint is low, can we
+ // guarantee that we will hit the corresponding decode path?
+ bool swap_vals = false;
+ if (use_offset_mode_) {
+ if (blue_contract_) {
+ swap_vals = s1 >= 0;
+ } else {
+ swap_vals = s1 < 0;
+ }
+
+ // In offset mode, we have two different measurements that swap the
+ // endpoints prior to encoding, so we don't need to swap them here.
+ // If we need to swap them to guarantee a blue-contract mode, then
+ // abort and wait until we get the other error measurement.
+ if (swap_vals) {
+ return false;
+ }
+ } else {
+ if (blue_contract_) {
+ // If we want a blue_contract path, but s1 == s0, then swapping the
+ // values will have no effect.
+ if (s1 == s0) {
+ return false;
+ }
+
+ swap_vals = s1 > s0;
+ // If we're encoding blue contract mode directly, then we implicitly
+ // swap the endpoints during decode, meaning that we need to take
+ // note of that here.
+ *needs_weight_swap = !(*needs_weight_swap);
+ } else {
+ swap_vals = s1 < s0;
+ }
+ }
+
+ const auto* quantized_low = &(quantized_endpoints_->QuantizedLow());
+ const auto* quantized_high = &(quantized_endpoints_->QuantizedHigh());
+
+ if (swap_vals) {
+ assert(!use_offset_mode_);
+ std::swap(quantized_low, quantized_high);
+ *needs_weight_swap = !(*needs_weight_swap);
+ }
+
+ (*vals)[0] = quantized_low->at(0);
+ (*vals)[1] = quantized_high->at(0);
+ (*vals)[2] = quantized_low->at(1);
+ (*vals)[3] = quantized_high->at(1);
+ (*vals)[4] = quantized_low->at(2);
+ (*vals)[5] = quantized_high->at(2);
+
+ if (use_offset_mode_) {
+ *astc_mode = ColorEndpointMode::kLDRRGBBaseOffset;
+ } else {
+ *astc_mode = ColorEndpointMode::kLDRRGBDirect;
+ }
+
+ if (with_alpha) {
+ (*vals)[6] = quantized_low->at(3);
+ (*vals)[7] = quantized_high->at(3);
+
+ if (use_offset_mode_) {
+ *astc_mode = ColorEndpointMode::kLDRRGBABaseOffset;
+ } else {
+ *astc_mode = ColorEndpointMode::kLDRRGBADirect;
+ }
+ }
+
+ // If we swapped them to measure, then they need to be swapped after
+ // decoding
+ if (swap_endpoints_) {
+ *needs_weight_swap = !(*needs_weight_swap);
+ }
+
+ return true;
+ }
+
+ bool BlueContract() const { return blue_contract_; }
+ int Error() const { return squared_error_; }
+
+ private:
+ int squared_error_;
+ const QuantizedEndpointPair* quantized_endpoints_;
+ bool swap_endpoints_;
+ bool blue_contract_;
+ bool use_offset_mode_;
+};
+
+bool EncodeColorsRGBA(const RgbaColor& endpoint_low_rgba,
+ const RgbaColor& endpoint_high_rgba,
+ int max_value, bool with_alpha,
+ ColorEndpointMode* const astc_mode,
+ std::vector<int>* const vals) {
+ const int num_channels = with_alpha ? std::tuple_size<RgbaColor>::value : 3;
+ // The difficulty of encoding into this mode is determining whether or
+ // not we'd like to use the 'blue contract' function to reconstruct
+ // the endpoints and whether or not we'll be more accurate by using the
+ // base/offset color modes instead of quantizing the color channels
+ // directly. With that in mind, we:
+ // 1. Generate the inverted values for blue-contract and offset modes.
+ // 2. Quantize all of the different endpoints.
+ // 3. Unquantize each sets and decide which one gives least error
+ // 4. Encode the values correspondingly.
+
+ // 1. Generate the inverted values for blue-contract and offset modes.
+ const auto inv_bc_low = InvertBlueContract(endpoint_low_rgba);
+ const auto inv_bc_high = InvertBlueContract(endpoint_high_rgba);
+
+ RgbaColor direct_base, direct_offset;
+ for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
+ direct_base[i] = endpoint_low_rgba[i];
+ direct_offset[i] =
+ Clamp(endpoint_high_rgba[i] - endpoint_low_rgba[i], -32, 31);
+ InvertBitTransferSigned(&direct_offset[i], &direct_base[i]);
+ }
+
+ RgbaColor inv_bc_base, inv_bc_offset;
+ for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
+ // Remember, for blue-contract'd offset modes, the base is compared
+ // against the second endpoint and not the first.
+ inv_bc_base[i] = inv_bc_high[i];
+ inv_bc_offset[i] = Clamp(inv_bc_low[i] - inv_bc_high[i], -32, 31);
+ InvertBitTransferSigned(&inv_bc_offset[i], &inv_bc_base[i]);
+ }
+
+ // The order of the endpoints for offset modes may determine how well they
+ // approximate the given endpoints. It may be that the quantization value
+ // produces more accurate values for the base than the offset or
+ // vice/versa. For this reason, we need to generate quantized versions of
+ // the endpoints as if they were swapped to see if we get better error
+ // out of it.
+
+ RgbaColor direct_base_swapped, direct_offset_swapped;
+ for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
+ direct_base_swapped[i] = endpoint_high_rgba[i];
+ direct_offset_swapped[i] =
+ Clamp(endpoint_low_rgba[i] - endpoint_high_rgba[i], -32, 31);
+ InvertBitTransferSigned(&direct_offset_swapped[i], &direct_base_swapped[i]);
+ }
+
+ RgbaColor inv_bc_base_swapped, inv_bc_offset_swapped;
+ for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
+ // Remember, for blue-contract'd offset modes, the base is compared
+ // against the second endpoint and not the first. Hence, the swapped
+ // version will compare the base against the first endpoint.
+ inv_bc_base_swapped[i] = inv_bc_low[i];
+ inv_bc_offset_swapped[i] = Clamp(inv_bc_high[i] - inv_bc_low[i], -32, 31);
+ InvertBitTransferSigned(&inv_bc_offset_swapped[i], &inv_bc_base_swapped[i]);
+ }
+
+ // 2. Quantize the endpoints directly.
+ const QuantizedEndpointPair direct_quantized(
+ endpoint_low_rgba, endpoint_high_rgba, max_value);
+ const QuantizedEndpointPair bc_quantized(
+ inv_bc_low, inv_bc_high, max_value);
+
+ const QuantizedEndpointPair offset_quantized(
+ direct_base, direct_offset, max_value);
+ const QuantizedEndpointPair bc_offset_quantized(
+ inv_bc_base, inv_bc_offset, max_value);
+
+ const QuantizedEndpointPair offset_swapped_quantized(
+ direct_base_swapped, direct_offset_swapped, max_value);
+ const QuantizedEndpointPair bc_offset_swapped_quantized(
+ inv_bc_base_swapped, inv_bc_offset_swapped, max_value);
+
+ // 3. Unquantize each set and decide which one gives least error.
+ std::array<CEEncodingOption, 6> errors;
+ auto errors_itr = errors.begin();
+
+ // 3.1 regular unquantized error
+ {
+ const auto rgba_low = direct_quantized.UnquantizedLow();
+ const auto rgba_high = direct_quantized.UnquantizedHigh();
+
+ const int sq_rgb_error =
+ SquaredError(rgba_low, endpoint_low_rgba, num_channels) +
+ SquaredError(rgba_high, endpoint_high_rgba, num_channels);
+
+ const bool swap_endpoints = false;
+ const bool blue_contract = false;
+ const bool offset_mode = false;
+ *(errors_itr++) = CEEncodingOption(
+ sq_rgb_error, &direct_quantized,
+ swap_endpoints, blue_contract, offset_mode);
+ }
+
+ // 3.2 Compute blue-contract'd error.
+ {
+ auto bc_low = bc_quantized.UnquantizedLow();
+ auto bc_high = bc_quantized.UnquantizedHigh();
+ BlueContract(&bc_low);
+ BlueContract(&bc_high);
+
+ const int sq_bc_error =
+ SquaredError(bc_low, endpoint_low_rgba, num_channels) +
+ SquaredError(bc_high, endpoint_high_rgba, num_channels);
+
+ const bool swap_endpoints = false;
+ const bool blue_contract = true;
+ const bool offset_mode = false;
+ *(errors_itr++) = CEEncodingOption(
+ sq_bc_error, &bc_quantized,
+ swap_endpoints, blue_contract, offset_mode);
+ }
+
+ // 3.3 Compute base/offset unquantized error.
+ const auto compute_base_offset_error =
+ [num_channels, &errors_itr, &endpoint_low_rgba, &endpoint_high_rgba]
+ (const QuantizedEndpointPair& pair, bool swapped) {
+ auto base = pair.UnquantizedLow();
+ auto offset = pair.UnquantizedHigh();
+
+ for (int i = 0; i < num_channels; ++i) {
+ BitTransferSigned(&offset[i], &base[i]);
+ offset[i] = Clamp(base[i] + offset[i], 0, 255);
+ }
+
+ int base_offset_error = 0;
+ // If we swapped the endpoints going in, then without blue contract
+ // we should be comparing the base against the high endpoint.
+ if (swapped) {
+ base_offset_error =
+ SquaredError(base, endpoint_high_rgba, num_channels) +
+ SquaredError(offset, endpoint_low_rgba, num_channels);
+ } else {
+ base_offset_error =
+ SquaredError(base, endpoint_low_rgba, num_channels) +
+ SquaredError(offset, endpoint_high_rgba, num_channels);
+ }
+
+ const bool blue_contract = false;
+ const bool offset_mode = true;
+ *(errors_itr++) = CEEncodingOption(
+ base_offset_error, &pair, swapped, blue_contract, offset_mode);
+ };
+
+ compute_base_offset_error(offset_quantized, false);
+
+ // 3.4 Compute base/offset blue-contract error.
+ const auto compute_base_offset_blue_contract_error =
+ [num_channels, &errors_itr, &endpoint_low_rgba, &endpoint_high_rgba]
+ (const QuantizedEndpointPair& pair, bool swapped) {
+ auto base = pair.UnquantizedLow();
+ auto offset = pair.UnquantizedHigh();
+
+ for (int i = 0; i < num_channels; ++i) {
+ BitTransferSigned(&offset[i], &base[i]);
+ offset[i] = Clamp(base[i] + offset[i], 0, 255);
+ }
+
+ BlueContract(&base);
+ BlueContract(&offset);
+
+ int sq_bc_error = 0;
+ // Remember, for blue-contract'd offset modes, the base is compared
+ // against the second endpoint and not the first. So, we compare
+ // against the first if we swapped the endpoints going in.
+ if (swapped) {
+ sq_bc_error =
+ SquaredError(base, endpoint_low_rgba, num_channels) +
+ SquaredError(offset, endpoint_high_rgba, num_channels);
+ } else {
+ sq_bc_error =
+ SquaredError(base, endpoint_high_rgba, num_channels) +
+ SquaredError(offset, endpoint_low_rgba, num_channels);
+ }
+
+ const bool blue_contract = true;
+ const bool offset_mode = true;
+ *(errors_itr++) = CEEncodingOption(sq_bc_error, &pair,
+ swapped, blue_contract, offset_mode);
+ };
+
+ compute_base_offset_blue_contract_error(bc_offset_quantized, false);
+
+ // 3.5 Compute swapped base/offset error.
+ compute_base_offset_error(offset_swapped_quantized, true);
+
+ // 3.6 Compute swapped base/offset blue-contract error.
+ compute_base_offset_blue_contract_error(
+ bc_offset_swapped_quantized, true);
+
+ std::sort(errors.begin(), errors.end(),
+ [](const CEEncodingOption& a, const CEEncodingOption& b) {
+ return a.Error() < b.Error();
+ });
+
+ // 4. Encode the values correspondingly.
+ // For this part, we go through each measurement in order of increasing
+ // error. Based on the properties of each measurement, we decide how to
+ // best encode the quantized endpoints that produced that error value. If
+ // for some reason we cannot encode that metric, then we skip it and move
+ // to the next one.
+ for (const auto& measurement : errors) {
+ bool needs_weight_swap = false;
+ if (measurement.Pack(with_alpha, astc_mode, vals, &needs_weight_swap)) {
+ // Make sure that if we ask for a blue-contract mode that we get it *and*
+ // if we don't ask for it then we don't get it.
+ assert(!(measurement.BlueContract() ^
+ UsesBlueContract(max_value, *astc_mode, *vals)));
+
+ // We encoded what we got.
+ return needs_weight_swap;
+ }
+ }
+
+ assert(false && "Shouldn't have reached this point -- some combination of "
+ "endpoints should be possible to encode!");
+ return false;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool UsesBlueContract(int max_value, ColorEndpointMode mode,
+ const std::vector<int>& vals) {
+ assert(vals.size() >= NumColorValuesForEndpointMode(mode));
+
+ switch (mode) {
+ case ColorEndpointMode::kLDRRGBDirect:
+ case ColorEndpointMode::kLDRRGBADirect: {
+ constexpr int kNumVals = MaxValuesForModes(
+ ColorEndpointMode::kLDRRGBDirect, ColorEndpointMode::kLDRRGBADirect);
+ std::array<int, kNumVals> v {};
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ const int s0 = v[0] + v[2] + v[4];
+ const int s1 = v[1] + v[3] + v[5];
+
+ return s0 > s1;
+ }
+
+ case ColorEndpointMode::kLDRRGBBaseOffset:
+ case ColorEndpointMode::kLDRRGBABaseOffset: {
+ constexpr int kNumVals = MaxValuesForModes(
+ ColorEndpointMode::kLDRRGBBaseOffset,
+ ColorEndpointMode::kLDRRGBABaseOffset);
+ std::array<int, kNumVals> v {};
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ BitTransferSigned(&v[1], &v[0]);
+ BitTransferSigned(&v[3], &v[2]);
+ BitTransferSigned(&v[5], &v[4]);
+
+ return v[1] + v[3] + v[5] < 0;
+ }
+
+ default:
+ return false;
+ }
+}
+
+bool EncodeColorsForMode(
+ const RgbaColor& endpoint_low_rgba, const RgbaColor& endpoint_high_rgba,
+ int max_value, EndpointEncodingMode encoding_mode,
+ ColorEndpointMode* const astc_mode, std::vector<int>* const vals) {
+ bool needs_weight_swap = false;
+ vals->resize(NumValuesForEncodingMode(encoding_mode));
+
+ switch (encoding_mode) {
+ case EndpointEncodingMode::kDirectLuma:
+ return EncodeColorsLuma(
+ endpoint_low_rgba, endpoint_high_rgba, max_value, astc_mode, vals);
+
+ case EndpointEncodingMode::kDirectLumaAlpha: {
+ // TODO(google): See if luma-alpha base-offset is better
+ const int avg1 = AverageRGB(endpoint_low_rgba);
+ const int avg2 = AverageRGB(endpoint_high_rgba);
+
+ (*vals)[0] = QuantizeCEValueToRange(avg1, max_value);
+ (*vals)[1] = QuantizeCEValueToRange(avg2, max_value);
+ (*vals)[2] = QuantizeCEValueToRange(endpoint_low_rgba[3], max_value);
+ (*vals)[3] = QuantizeCEValueToRange(endpoint_high_rgba[3], max_value);
+ *astc_mode = ColorEndpointMode::kLDRLumaAlphaDirect;
+ }
+ break;
+
+ case EndpointEncodingMode::kBaseScaleRGB:
+ case EndpointEncodingMode::kBaseScaleRGBA: {
+ RgbaColor base = endpoint_high_rgba;
+ RgbaColor scaled = endpoint_low_rgba;
+
+ // Similar to luma base-offset, the scaled value is strictly less than
+ // the base value here according to the decode procedure. In this case,
+ // if the base is larger than the scale then we need to swap.
+ int num_channels_ge = 0;
+ for (int i = 0; i < 3; ++i) {
+ num_channels_ge +=
+ static_cast<int>(endpoint_high_rgba[i] >= endpoint_low_rgba[i]);
+ }
+
+ if (num_channels_ge < 2) {
+ needs_weight_swap = true;
+ std::swap(base, scaled);
+ }
+
+ // Since the second endpoint is just a direct copy of the RGB values, we
+ // can start by quantizing them.
+ const auto q_base = QuantizeColor(base, max_value);
+ const auto uq_base = UnquantizeColor(q_base, max_value);
+
+ // The first endpoint (scaled) is defined by piecewise multiplying the
+ // second endpoint (base) by the scale factor and then dividing by 256.
+ // This means that the inverse operation is to first piecewise multiply
+ // the first endpoint by 256 and then divide by the unquantized second
+ // endpoint. We take the average of each of each of these scale values as
+ // our final scale value.
+ // TODO(google): Is this the best way to determine the scale factor?
+ int num_samples = 0;
+ int scale_sum = 0;
+ for (int i = 0; i < 3; ++i) {
+ int x = uq_base[i];
+ if (x != 0) {
+ ++num_samples;
+ scale_sum += (scaled[i] * 256) / x;
+ }
+ }
+
+ (*vals)[0] = q_base[0];
+ (*vals)[1] = q_base[1];
+ (*vals)[2] = q_base[2];
+ if (num_samples > 0) {
+ const int avg_scale = Clamp(scale_sum / num_samples, 0, 255);
+ (*vals)[3] = QuantizeCEValueToRange(avg_scale, max_value);
+ } else {
+ // In this case, all of the base values are zero, so we can use whatever
+ // we want as the scale -- it won't affect the outcome.
+ (*vals)[3] = max_value;
+ }
+ *astc_mode = ColorEndpointMode::kLDRRGBBaseScale;
+
+ if (encoding_mode == EndpointEncodingMode::kBaseScaleRGBA) {
+ (*vals)[4] = QuantizeCEValueToRange(scaled[3], max_value);
+ (*vals)[5] = QuantizeCEValueToRange(base[3], max_value);
+ *astc_mode = ColorEndpointMode::kLDRRGBBaseScaleTwoA;
+ }
+ }
+ break;
+
+ case EndpointEncodingMode::kDirectRGB:
+ case EndpointEncodingMode::kDirectRGBA:
+ return EncodeColorsRGBA(
+ endpoint_low_rgba, endpoint_high_rgba, max_value,
+ encoding_mode == EndpointEncodingMode::kDirectRGBA, astc_mode, vals);
+
+ default:
+ assert(false && "Unimplemented color encoding.");
+ }
+
+ return needs_weight_swap;
+}
+
+// These decoding procedures follow the code outlined in Section C.2.14 of
+// the ASTC specification.
+void DecodeColorsForMode(const std::vector<int>& vals,
+ int max_value, ColorEndpointMode mode,
+ RgbaColor* const endpoint_low_rgba,
+ RgbaColor* const endpoint_high_rgba) {
+ assert(vals.size() >= NumColorValuesForEndpointMode(mode));
+ switch (mode) {
+ case ColorEndpointMode::kLDRLumaDirect: {
+ const int l0 = UnquantizeCEValueFromRange(vals[0], max_value);
+ const int l1 = UnquantizeCEValueFromRange(vals[1], max_value);
+
+ *endpoint_low_rgba = {{ l0, l0, l0, 255 }};
+ *endpoint_high_rgba = {{ l1, l1, l1, 255 }};
+ }
+ break;
+
+ case ColorEndpointMode::kLDRLumaBaseOffset: {
+ const int v0 = UnquantizeCEValueFromRange(vals[0], max_value);
+ const int v1 = UnquantizeCEValueFromRange(vals[1], max_value);
+
+ const int l0 = (v0 >> 2) | (v1 & 0xC0);
+ const int l1 = std::min(l0 + (v1 & 0x3F), 0xFF);
+
+ *endpoint_low_rgba = {{ l0, l0, l0, 255 }};
+ *endpoint_high_rgba = {{ l1, l1, l1, 255 }};
+ }
+ break;
+
+ case ColorEndpointMode::kLDRLumaAlphaDirect: {
+ constexpr int kNumVals =
+ NumColorValuesForEndpointMode(ColorEndpointMode::kLDRLumaAlphaDirect);
+
+ std::array<int, kNumVals> v;
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ *endpoint_low_rgba = {{ v[0], v[0], v[0], v[2] }};
+ *endpoint_high_rgba = {{ v[1], v[1], v[1], v[3] }};
+ }
+ break;
+
+ case ColorEndpointMode::kLDRLumaAlphaBaseOffset: {
+ constexpr int kNumVals = NumColorValuesForEndpointMode(
+ ColorEndpointMode::kLDRLumaAlphaBaseOffset);
+
+ std::array<int, kNumVals> v;
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ BitTransferSigned(&v[1], &v[0]);
+ BitTransferSigned(&v[3], &v[2]);
+
+ *endpoint_low_rgba = {{ v[0], v[0], v[0], v[2] }};
+ const int high_luma = v[0] + v[1];
+ *endpoint_high_rgba = {{ high_luma, high_luma, high_luma, v[2] + v[3] }};
+
+ for (auto& c : *endpoint_low_rgba) { c = Clamp(c, 0, 255); }
+ for (auto& c : *endpoint_high_rgba) { c = Clamp(c, 0, 255); }
+ }
+ break;
+
+ case ColorEndpointMode::kLDRRGBBaseScale: {
+ constexpr int kNumVals =
+ NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBBaseScale);
+
+ std::array<int, kNumVals> v;
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ *endpoint_high_rgba = {{ v[0], v[1], v[2], 255 }};
+ for (int i = 0; i < 3; ++i) {
+ const int x = endpoint_high_rgba->at(i);
+ endpoint_low_rgba->at(i) = (x * v[3]) >> 8;
+ }
+ endpoint_low_rgba->at(3) = 255;
+ }
+ break;
+
+ case ColorEndpointMode::kLDRRGBDirect: {
+ constexpr int kNumVals =
+ NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBDirect);
+
+ std::array<int, kNumVals> v;
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ const int s0 = v[0] + v[2] + v[4];
+ const int s1 = v[1] + v[3] + v[5];
+
+ *endpoint_low_rgba = {{ v[0], v[2], v[4], 255 }};
+ *endpoint_high_rgba = {{ v[1], v[3], v[5], 255 }};
+
+ if (s1 < s0) {
+ std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
+ BlueContract(endpoint_low_rgba);
+ BlueContract(endpoint_high_rgba);
+ }
+ }
+ break;
+
+ case ColorEndpointMode::kLDRRGBBaseOffset: {
+ constexpr int kNumVals =
+ NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBBaseOffset);
+
+ std::array<int, kNumVals> v;
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ BitTransferSigned(&v[1], &v[0]);
+ BitTransferSigned(&v[3], &v[2]);
+ BitTransferSigned(&v[5], &v[4]);
+
+ *endpoint_low_rgba = {{ v[0], v[2], v[4], 255 }};
+ *endpoint_high_rgba = {{ v[0] + v[1], v[2] + v[3], v[4] + v[5], 255 }};
+
+ if (v[1] + v[3] + v[5] < 0) {
+ std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
+ BlueContract(endpoint_low_rgba);
+ BlueContract(endpoint_high_rgba);
+ }
+
+ for (auto& c : *endpoint_low_rgba) { c = Clamp(c, 0, 255); }
+ for (auto& c : *endpoint_high_rgba) { c = Clamp(c, 0, 255); }
+ }
+ break;
+
+ case ColorEndpointMode::kLDRRGBBaseScaleTwoA: {
+ constexpr int kNumVals = NumColorValuesForEndpointMode(
+ ColorEndpointMode::kLDRRGBBaseScaleTwoA);
+
+ std::array<int, kNumVals> v;
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ // Base
+ *endpoint_low_rgba = *endpoint_high_rgba = {{ v[0], v[1], v[2], 255 }};
+
+ // Scale
+ for (int i = 0; i < 3; ++i) {
+ auto& x = endpoint_low_rgba->at(i);
+ x = (x * v[3]) >> 8;
+ }
+
+ // Two A
+ endpoint_low_rgba->at(3) = v[4];
+ endpoint_high_rgba->at(3) = v[5];
+ }
+ break;
+
+ case ColorEndpointMode::kLDRRGBADirect: {
+ constexpr int kNumVals =
+ NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBADirect);
+
+ std::array<int, kNumVals> v;
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ const int s0 = v[0] + v[2] + v[4];
+ const int s1 = v[1] + v[3] + v[5];
+
+ *endpoint_low_rgba = {{ v[0], v[2], v[4], v[6] }};
+ *endpoint_high_rgba = {{ v[1], v[3], v[5], v[7] }};
+
+ if (s1 < s0) {
+ std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
+ BlueContract(endpoint_low_rgba);
+ BlueContract(endpoint_high_rgba);
+ }
+ }
+ break;
+
+ case ColorEndpointMode::kLDRRGBABaseOffset: {
+ constexpr int kNumVals =
+ NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBABaseOffset);
+
+ std::array<int, kNumVals> v;
+ std::copy(vals.begin(), vals.end(), v.begin());
+ Unquantize(&v, max_value);
+
+ BitTransferSigned(&v[1], &v[0]);
+ BitTransferSigned(&v[3], &v[2]);
+ BitTransferSigned(&v[5], &v[4]);
+ BitTransferSigned(&v[7], &v[6]);
+
+ *endpoint_low_rgba = {{ v[0], v[2], v[4], v[6] }};
+ *endpoint_high_rgba = {{
+ v[0] + v[1], v[2] + v[3], v[4] + v[5], v[6] + v[7] }};
+
+ if (v[1] + v[3] + v[5] < 0) {
+ std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
+ BlueContract(endpoint_low_rgba);
+ BlueContract(endpoint_high_rgba);
+ }
+
+ for (auto& c : *endpoint_low_rgba) { c = Clamp(c, 0, 255); }
+ for (auto& c : *endpoint_high_rgba) { c = Clamp(c, 0, 255); }
+ }
+ break;
+
+ default:
+ // Unimplemented color encoding.
+ // TODO(google): Is this the correct error handling?
+ *endpoint_high_rgba = *endpoint_low_rgba = {{ 0, 0, 0, 0 }};
+ }
+}
+
+} // namespace astc_codec