| // 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 |