1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "src/decoder/endpoint_codec.h"
16 #include "src/decoder/quantization.h"
17
18 #include <algorithm>
19 #include <array>
20 #include <numeric>
21 #include <utility>
22
23 namespace astc_codec {
24
25 namespace {
26
27 template<typename T>
Clamp(T value,T min,T max)28 T Clamp(T value, T min, T max) {
29 return value < min ? min : (value > max ? max : value);
30 }
31
32 // This is the 'blue_contract' function defined in Section C.2.14 of the ASTC
33 // specification.
34 template<typename ArrayType>
BlueContract(ArrayType * const cptr)35 void BlueContract(ArrayType* const cptr) {
36 ArrayType& c = *cptr;
37 c[0] = (c[0] + c[2]) >> 1;
38 c[1] = (c[1] + c[2]) >> 1;
39 }
40
41 // Returns the inverse of values in BlueContract, subjected to the constraint
42 // that the new values are stored in the range [0, 255].
43 template<typename ArrayType>
InvertBlueContract(const ArrayType & c)44 ArrayType InvertBlueContract(const ArrayType& c) {
45 ArrayType result = c;
46 result[0] = Clamp(2 * c[0] - c[2], 0, 255);
47 result[1] = Clamp(2 * c[1] - c[2], 0, 255);
48 return result;
49 }
50
51 // This is the 'bit_transfer_signed' function defined in Section C.2.14 of the
52 // ASTC specification.
BitTransferSigned(int * const a,int * const b)53 void BitTransferSigned(int* const a, int* const b) {
54 *b >>= 1;
55 *b |= *a & 0x80;
56 *a >>= 1;
57 *a &= 0x3F;
58 if ((*a & 0x20) != 0) {
59 *a -= 0x40;
60 }
61 }
62
63 // Takes two values, |a| in the range [-32, 31], and |b| in the range [0, 255],
64 // and returns the two values in [0, 255] that will reconstruct |a| and |b| when
65 // passed to the BitTransferSigned function.
InvertBitTransferSigned(int * const a,int * const b)66 void InvertBitTransferSigned(int* const a, int* const b) {
67 assert(*a >= -32); assert(*a < 32);
68 assert(*b >= 0); assert(*b < 256);
69
70 if (*a < 0) {
71 *a += 0x40;
72 }
73 *a <<= 1;
74 *a |= (*b & 0x80);
75 *b <<= 1;
76 *b &= 0xff;
77 }
78
StripAlpha(const RgbaColor & c)79 RgbColor StripAlpha(const RgbaColor& c) {
80 return RgbColor {{ c[0], c[1], c[2] }};
81 }
82
83 template<typename ContainerType>
Quantize(ContainerType * const c,size_t max_value)84 void Quantize(ContainerType* const c, size_t max_value) {
85 for (auto& x : *c) {
86 x = QuantizeCEValueToRange(x, max_value);
87 }
88 }
89
90 template<typename ArrayType>
QuantizeColor(const ArrayType & c,size_t max_value)91 ArrayType QuantizeColor(const ArrayType& c, size_t max_value) {
92 ArrayType result = c;
93 Quantize(&result, max_value);
94 return result;
95 }
96
97 template<typename ContainerType>
Unquantize(ContainerType * const c,size_t max_value)98 void Unquantize(ContainerType* const c, size_t max_value) {
99 for (auto& x : *c) {
100 x = UnquantizeCEValueFromRange(x, max_value);
101 }
102 }
103
104 template<typename ArrayType>
UnquantizeColor(const ArrayType & c,size_t max_value)105 ArrayType UnquantizeColor(const ArrayType& c, size_t max_value) {
106 ArrayType result = c;
107 Unquantize(&result, max_value);
108 return result;
109 }
110
111 // Returns the average of the three RGB channels.
112 template<typename ContainerType>
AverageRGB(const ContainerType & c)113 const int AverageRGB(const ContainerType& c) {
114 // Each channel can be in the range [0, 255], and we need to divide by three.
115 // However, we want to round the error properly. Both (x + 1) / 3 and
116 // (x + 2) / 3 are relatively imprecise when it comes to rounding, so instead
117 // we increase the precision by multiplying our numerator by some arbitrary
118 // number. Here, we choose 256 to get 8 additional bits and maintain
119 // performance since it turns into a shift rather than a multiply. Our
120 // denominator then becomes 3 * 256 = 768.
121 return (std::accumulate(c.begin(), c.begin() + 3, 0) * 256 + 384) / 768;
122 }
123
124 // Returns the sum of squared differences between each element of |a| and |b|,
125 // which are assumed to contain the same number of elements.
126 template<typename ContainerType>
SquaredError(const ContainerType & a,const ContainerType & b,size_t num_channels=std::tuple_size<ContainerType>::value)127 const typename ContainerType::value_type SquaredError(
128 const ContainerType& a, const ContainerType& b,
129 size_t num_channels = std::tuple_size<ContainerType>::value) {
130 using ValueTy = typename ContainerType::value_type;
131 static_assert(std::is_signed<ValueTy>::value,
132 "Value type assumed to be signed to avoid branch below.");
133 ValueTy result = ValueTy(0);
134 for (int i = 0; i < num_channels; ++i) {
135 ValueTy error = a[i] - b[i];
136 result += error * error;
137 }
138 return result;
139 }
140
MaxValuesForModes(ColorEndpointMode mode_a,ColorEndpointMode mode_b)141 constexpr int MaxValuesForModes(ColorEndpointMode mode_a,
142 ColorEndpointMode mode_b) {
143 return (NumColorValuesForEndpointMode(mode_a) >
144 NumColorValuesForEndpointMode(mode_b))
145 ? NumColorValuesForEndpointMode(mode_a)
146 : NumColorValuesForEndpointMode(mode_b);
147 }
148
149 // This function takes the two colors in |endpoint_low| and |endpoint_high| and
150 // encodes them into |vals| according to the ASTC spec in section C.2.14. It
151 // assumes that the two colors are close enough to grayscale that the encoding
152 // should use the ColorEndpointMode kLDRLumaBaseOffset or kLDRLumaDirect. Which
153 // one is chosen depends on which produces smaller error for the given
154 // quantization value stored in |max_value|
EncodeColorsLuma(const RgbaColor & endpoint_low,const RgbaColor & endpoint_high,int max_value,ColorEndpointMode * const astc_mode,std::vector<int> * const vals)155 bool EncodeColorsLuma(const RgbaColor& endpoint_low,
156 const RgbaColor& endpoint_high,
157 int max_value, ColorEndpointMode* const astc_mode,
158 std::vector<int>* const vals) {
159 assert(vals->size() ==
160 NumValuesForEncodingMode(EndpointEncodingMode::kDirectLuma));
161 int avg1 = AverageRGB(endpoint_low);
162 int avg2 = AverageRGB(endpoint_high);
163
164 // For the offset mode, L1 is strictly greater than L2, so if we are using
165 // it to encode the color values, we need to swap the weights and
166 // endpoints so that the larger of the two is the second endpoint.
167 bool needs_weight_swap = false;
168 if (avg1 > avg2) {
169 needs_weight_swap = true;
170 std::swap(avg1, avg2);
171 }
172 assert(avg1 <= avg2);
173
174 // Now, the first endpoint is based on the low-order six bits of the first
175 // value, and the high order two bits of the second value. The low order
176 // six bits of the second value are used as the (strictly positive) offset
177 // from the first value.
178 const int offset = std::min(avg2 - avg1, 0x3F);
179 const int quant_off_low =
180 QuantizeCEValueToRange((avg1 & 0x3F) << 2, max_value);
181 const int quant_off_high =
182 QuantizeCEValueToRange((avg1 & 0xC0) | offset, max_value);
183
184 const int quant_low = QuantizeCEValueToRange(avg1, max_value);
185 const int quant_high = QuantizeCEValueToRange(avg2, max_value);
186
187 RgbaColor unquant_off_low, unquant_off_high;
188 RgbaColor unquant_low, unquant_high;
189
190 (*vals)[0] = quant_off_low;
191 (*vals)[1] = quant_off_high;
192 DecodeColorsForMode(
193 *vals, max_value, ColorEndpointMode::kLDRLumaBaseOffset,
194 &unquant_off_low, &unquant_off_high);
195
196 (*vals)[0] = quant_low;
197 (*vals)[1] = quant_high;
198 DecodeColorsForMode(*vals, max_value, ColorEndpointMode::kLDRLumaDirect,
199 &unquant_low, &unquant_high);
200
201 const auto calculate_error =
202 [needs_weight_swap, &endpoint_low, &endpoint_high]
203 (const RgbaColor& low, const RgbaColor& high) {
204 int error = 0;
205 if (needs_weight_swap) {
206 error += SquaredError(low, endpoint_high);
207 error += SquaredError(high, endpoint_low);
208 } else {
209 error += SquaredError(low, endpoint_low);
210 error += SquaredError(high, endpoint_high);
211 }
212 return error;
213 };
214
215 const int direct_error = calculate_error(unquant_low, unquant_high);
216 const int off_error = calculate_error(unquant_off_low, unquant_off_high);
217
218 if (direct_error <= off_error) {
219 (*vals)[0] = quant_low;
220 (*vals)[1] = quant_high;
221 *astc_mode = ColorEndpointMode::kLDRLumaDirect;
222 } else {
223 (*vals)[0] = quant_off_low;
224 (*vals)[1] = quant_off_high;
225 *astc_mode = ColorEndpointMode::kLDRLumaBaseOffset;
226 }
227
228 return needs_weight_swap;
229 }
230
231 class QuantizedEndpointPair {
232 public:
QuantizedEndpointPair(const RgbaColor & c_low,const RgbaColor & c_high,int max_value)233 QuantizedEndpointPair(const RgbaColor& c_low, const RgbaColor& c_high,
234 int max_value)
235 : orig_low_(c_low),
236 orig_high_(c_high),
237 quant_low_(QuantizeColor(c_low, max_value)),
238 quant_high_(QuantizeColor(c_high, max_value)),
239 unquant_low_(UnquantizeColor(quant_low_, max_value)),
240 unquant_high_(UnquantizeColor(quant_high_, max_value)) { }
241
QuantizedLow() const242 const RgbaColor& QuantizedLow() const { return quant_low_; }
QuantizedHigh() const243 const RgbaColor& QuantizedHigh() const { return quant_high_; }
244
UnquantizedLow() const245 const RgbaColor& UnquantizedLow() const { return unquant_low_; }
UnquantizedHigh() const246 const RgbaColor& UnquantizedHigh() const { return unquant_high_; }
247
OriginalLow() const248 const RgbaColor& OriginalLow() const { return orig_low_; }
OriginalHigh() const249 const RgbaColor& OriginalHigh() const { return orig_high_; }
250
251 private:
252 RgbaColor orig_low_;
253 RgbaColor orig_high_;
254
255 RgbaColor quant_low_;
256 RgbaColor quant_high_;
257
258 RgbaColor unquant_low_;
259 RgbaColor unquant_high_;
260 };
261
262 class CEEncodingOption {
263 public:
CEEncodingOption()264 CEEncodingOption() { }
CEEncodingOption(int squared_error,const QuantizedEndpointPair * quantized_endpoints,bool swap_endpoints,bool blue_contract,bool use_offset_mode)265 CEEncodingOption(
266 int squared_error, const QuantizedEndpointPair* quantized_endpoints,
267 bool swap_endpoints, bool blue_contract, bool use_offset_mode)
268 : squared_error_(squared_error),
269 quantized_endpoints_(quantized_endpoints),
270 swap_endpoints_(swap_endpoints),
271 blue_contract_(blue_contract),
272 use_offset_mode_(use_offset_mode) { }
273
274 // Returns true if able to generate valid |astc_mode| and |vals|. In some
275 // instances, such as if the endpoints reprsent a base/offset pair, we may not
276 // be able to guarantee blue-contract encoding due to how the base/offset pair
277 // are represented and the specifics of the decoding procedure. Similarly,
278 // some direct RGBA encodings also may not be able to emit blue-contract modes
279 // due to an unlucky combination of channels. In these instances, this
280 // function will return false, and all pointers will remain unmodified.
Pack(bool with_alpha,ColorEndpointMode * const astc_mode,std::vector<int> * const vals,bool * const needs_weight_swap) const281 bool Pack(bool with_alpha, ColorEndpointMode* const astc_mode,
282 std::vector<int>* const vals, bool* const needs_weight_swap) const {
283 auto unquantized_low = quantized_endpoints_->UnquantizedLow();
284 auto unquantized_high = quantized_endpoints_->UnquantizedHigh();
285
286 // In offset mode, we do BitTransferSigned before analyzing the values
287 // of the endpoints in order to determine whether or not we're going to
288 // be using blue-contract mode.
289 if (use_offset_mode_) {
290 for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
291 BitTransferSigned(&unquantized_high[i], &unquantized_low[i]);
292 }
293 }
294
295 // Define variables as outlined in the ASTC spec C.2.14 for the RGB[A]
296 // direct and base-offset modes
297 int s0 = 0, s1 = 0;
298 for (int i = 0; i < 3; ++i) {
299 s0 += unquantized_low[i];
300 s1 += unquantized_high[i];
301 }
302
303 // Can we guarantee a blue-contract mode if we want it? In other words,
304 // if we swap which endpoint is high and which endpoint is low, can we
305 // guarantee that we will hit the corresponding decode path?
306 bool swap_vals = false;
307 if (use_offset_mode_) {
308 if (blue_contract_) {
309 swap_vals = s1 >= 0;
310 } else {
311 swap_vals = s1 < 0;
312 }
313
314 // In offset mode, we have two different measurements that swap the
315 // endpoints prior to encoding, so we don't need to swap them here.
316 // If we need to swap them to guarantee a blue-contract mode, then
317 // abort and wait until we get the other error measurement.
318 if (swap_vals) {
319 return false;
320 }
321 } else {
322 if (blue_contract_) {
323 // If we want a blue_contract path, but s1 == s0, then swapping the
324 // values will have no effect.
325 if (s1 == s0) {
326 return false;
327 }
328
329 swap_vals = s1 > s0;
330 // If we're encoding blue contract mode directly, then we implicitly
331 // swap the endpoints during decode, meaning that we need to take
332 // note of that here.
333 *needs_weight_swap = !(*needs_weight_swap);
334 } else {
335 swap_vals = s1 < s0;
336 }
337 }
338
339 const auto* quantized_low = &(quantized_endpoints_->QuantizedLow());
340 const auto* quantized_high = &(quantized_endpoints_->QuantizedHigh());
341
342 if (swap_vals) {
343 assert(!use_offset_mode_);
344 std::swap(quantized_low, quantized_high);
345 *needs_weight_swap = !(*needs_weight_swap);
346 }
347
348 (*vals)[0] = quantized_low->at(0);
349 (*vals)[1] = quantized_high->at(0);
350 (*vals)[2] = quantized_low->at(1);
351 (*vals)[3] = quantized_high->at(1);
352 (*vals)[4] = quantized_low->at(2);
353 (*vals)[5] = quantized_high->at(2);
354
355 if (use_offset_mode_) {
356 *astc_mode = ColorEndpointMode::kLDRRGBBaseOffset;
357 } else {
358 *astc_mode = ColorEndpointMode::kLDRRGBDirect;
359 }
360
361 if (with_alpha) {
362 (*vals)[6] = quantized_low->at(3);
363 (*vals)[7] = quantized_high->at(3);
364
365 if (use_offset_mode_) {
366 *astc_mode = ColorEndpointMode::kLDRRGBABaseOffset;
367 } else {
368 *astc_mode = ColorEndpointMode::kLDRRGBADirect;
369 }
370 }
371
372 // If we swapped them to measure, then they need to be swapped after
373 // decoding
374 if (swap_endpoints_) {
375 *needs_weight_swap = !(*needs_weight_swap);
376 }
377
378 return true;
379 }
380
BlueContract() const381 bool BlueContract() const { return blue_contract_; }
Error() const382 int Error() const { return squared_error_; }
383
384 private:
385 int squared_error_;
386 const QuantizedEndpointPair* quantized_endpoints_;
387 bool swap_endpoints_;
388 bool blue_contract_;
389 bool use_offset_mode_;
390 };
391
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)392 bool EncodeColorsRGBA(const RgbaColor& endpoint_low_rgba,
393 const RgbaColor& endpoint_high_rgba,
394 int max_value, bool with_alpha,
395 ColorEndpointMode* const astc_mode,
396 std::vector<int>* const vals) {
397 const int num_channels = with_alpha ? std::tuple_size<RgbaColor>::value : 3;
398 // The difficulty of encoding into this mode is determining whether or
399 // not we'd like to use the 'blue contract' function to reconstruct
400 // the endpoints and whether or not we'll be more accurate by using the
401 // base/offset color modes instead of quantizing the color channels
402 // directly. With that in mind, we:
403 // 1. Generate the inverted values for blue-contract and offset modes.
404 // 2. Quantize all of the different endpoints.
405 // 3. Unquantize each sets and decide which one gives least error
406 // 4. Encode the values correspondingly.
407
408 // 1. Generate the inverted values for blue-contract and offset modes.
409 const auto inv_bc_low = InvertBlueContract(endpoint_low_rgba);
410 const auto inv_bc_high = InvertBlueContract(endpoint_high_rgba);
411
412 RgbaColor direct_base, direct_offset;
413 for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
414 direct_base[i] = endpoint_low_rgba[i];
415 direct_offset[i] =
416 Clamp(endpoint_high_rgba[i] - endpoint_low_rgba[i], -32, 31);
417 InvertBitTransferSigned(&direct_offset[i], &direct_base[i]);
418 }
419
420 RgbaColor inv_bc_base, inv_bc_offset;
421 for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
422 // Remember, for blue-contract'd offset modes, the base is compared
423 // against the second endpoint and not the first.
424 inv_bc_base[i] = inv_bc_high[i];
425 inv_bc_offset[i] = Clamp(inv_bc_low[i] - inv_bc_high[i], -32, 31);
426 InvertBitTransferSigned(&inv_bc_offset[i], &inv_bc_base[i]);
427 }
428
429 // The order of the endpoints for offset modes may determine how well they
430 // approximate the given endpoints. It may be that the quantization value
431 // produces more accurate values for the base than the offset or
432 // vice/versa. For this reason, we need to generate quantized versions of
433 // the endpoints as if they were swapped to see if we get better error
434 // out of it.
435
436 RgbaColor direct_base_swapped, direct_offset_swapped;
437 for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
438 direct_base_swapped[i] = endpoint_high_rgba[i];
439 direct_offset_swapped[i] =
440 Clamp(endpoint_low_rgba[i] - endpoint_high_rgba[i], -32, 31);
441 InvertBitTransferSigned(&direct_offset_swapped[i], &direct_base_swapped[i]);
442 }
443
444 RgbaColor inv_bc_base_swapped, inv_bc_offset_swapped;
445 for (int i = 0; i < std::tuple_size<RgbaColor>::value; ++i) {
446 // Remember, for blue-contract'd offset modes, the base is compared
447 // against the second endpoint and not the first. Hence, the swapped
448 // version will compare the base against the first endpoint.
449 inv_bc_base_swapped[i] = inv_bc_low[i];
450 inv_bc_offset_swapped[i] = Clamp(inv_bc_high[i] - inv_bc_low[i], -32, 31);
451 InvertBitTransferSigned(&inv_bc_offset_swapped[i], &inv_bc_base_swapped[i]);
452 }
453
454 // 2. Quantize the endpoints directly.
455 const QuantizedEndpointPair direct_quantized(
456 endpoint_low_rgba, endpoint_high_rgba, max_value);
457 const QuantizedEndpointPair bc_quantized(
458 inv_bc_low, inv_bc_high, max_value);
459
460 const QuantizedEndpointPair offset_quantized(
461 direct_base, direct_offset, max_value);
462 const QuantizedEndpointPair bc_offset_quantized(
463 inv_bc_base, inv_bc_offset, max_value);
464
465 const QuantizedEndpointPair offset_swapped_quantized(
466 direct_base_swapped, direct_offset_swapped, max_value);
467 const QuantizedEndpointPair bc_offset_swapped_quantized(
468 inv_bc_base_swapped, inv_bc_offset_swapped, max_value);
469
470 // 3. Unquantize each set and decide which one gives least error.
471 std::array<CEEncodingOption, 6> errors;
472 auto errors_itr = errors.begin();
473
474 // 3.1 regular unquantized error
475 {
476 const auto rgba_low = direct_quantized.UnquantizedLow();
477 const auto rgba_high = direct_quantized.UnquantizedHigh();
478
479 const int sq_rgb_error =
480 SquaredError(rgba_low, endpoint_low_rgba, num_channels) +
481 SquaredError(rgba_high, endpoint_high_rgba, num_channels);
482
483 const bool swap_endpoints = false;
484 const bool blue_contract = false;
485 const bool offset_mode = false;
486 *(errors_itr++) = CEEncodingOption(
487 sq_rgb_error, &direct_quantized,
488 swap_endpoints, blue_contract, offset_mode);
489 }
490
491 // 3.2 Compute blue-contract'd error.
492 {
493 auto bc_low = bc_quantized.UnquantizedLow();
494 auto bc_high = bc_quantized.UnquantizedHigh();
495 BlueContract(&bc_low);
496 BlueContract(&bc_high);
497
498 const int sq_bc_error =
499 SquaredError(bc_low, endpoint_low_rgba, num_channels) +
500 SquaredError(bc_high, endpoint_high_rgba, num_channels);
501
502 const bool swap_endpoints = false;
503 const bool blue_contract = true;
504 const bool offset_mode = false;
505 *(errors_itr++) = CEEncodingOption(
506 sq_bc_error, &bc_quantized,
507 swap_endpoints, blue_contract, offset_mode);
508 }
509
510 // 3.3 Compute base/offset unquantized error.
511 const auto compute_base_offset_error =
512 [num_channels, &errors_itr, &endpoint_low_rgba, &endpoint_high_rgba]
513 (const QuantizedEndpointPair& pair, bool swapped) {
514 auto base = pair.UnquantizedLow();
515 auto offset = pair.UnquantizedHigh();
516
517 for (int i = 0; i < num_channels; ++i) {
518 BitTransferSigned(&offset[i], &base[i]);
519 offset[i] = Clamp(base[i] + offset[i], 0, 255);
520 }
521
522 int base_offset_error = 0;
523 // If we swapped the endpoints going in, then without blue contract
524 // we should be comparing the base against the high endpoint.
525 if (swapped) {
526 base_offset_error =
527 SquaredError(base, endpoint_high_rgba, num_channels) +
528 SquaredError(offset, endpoint_low_rgba, num_channels);
529 } else {
530 base_offset_error =
531 SquaredError(base, endpoint_low_rgba, num_channels) +
532 SquaredError(offset, endpoint_high_rgba, num_channels);
533 }
534
535 const bool blue_contract = false;
536 const bool offset_mode = true;
537 *(errors_itr++) = CEEncodingOption(
538 base_offset_error, &pair, swapped, blue_contract, offset_mode);
539 };
540
541 compute_base_offset_error(offset_quantized, false);
542
543 // 3.4 Compute base/offset blue-contract error.
544 const auto compute_base_offset_blue_contract_error =
545 [num_channels, &errors_itr, &endpoint_low_rgba, &endpoint_high_rgba]
546 (const QuantizedEndpointPair& pair, bool swapped) {
547 auto base = pair.UnquantizedLow();
548 auto offset = pair.UnquantizedHigh();
549
550 for (int i = 0; i < num_channels; ++i) {
551 BitTransferSigned(&offset[i], &base[i]);
552 offset[i] = Clamp(base[i] + offset[i], 0, 255);
553 }
554
555 BlueContract(&base);
556 BlueContract(&offset);
557
558 int sq_bc_error = 0;
559 // Remember, for blue-contract'd offset modes, the base is compared
560 // against the second endpoint and not the first. So, we compare
561 // against the first if we swapped the endpoints going in.
562 if (swapped) {
563 sq_bc_error =
564 SquaredError(base, endpoint_low_rgba, num_channels) +
565 SquaredError(offset, endpoint_high_rgba, num_channels);
566 } else {
567 sq_bc_error =
568 SquaredError(base, endpoint_high_rgba, num_channels) +
569 SquaredError(offset, endpoint_low_rgba, num_channels);
570 }
571
572 const bool blue_contract = true;
573 const bool offset_mode = true;
574 *(errors_itr++) = CEEncodingOption(sq_bc_error, &pair,
575 swapped, blue_contract, offset_mode);
576 };
577
578 compute_base_offset_blue_contract_error(bc_offset_quantized, false);
579
580 // 3.5 Compute swapped base/offset error.
581 compute_base_offset_error(offset_swapped_quantized, true);
582
583 // 3.6 Compute swapped base/offset blue-contract error.
584 compute_base_offset_blue_contract_error(
585 bc_offset_swapped_quantized, true);
586
587 std::sort(errors.begin(), errors.end(),
588 [](const CEEncodingOption& a, const CEEncodingOption& b) {
589 return a.Error() < b.Error();
590 });
591
592 // 4. Encode the values correspondingly.
593 // For this part, we go through each measurement in order of increasing
594 // error. Based on the properties of each measurement, we decide how to
595 // best encode the quantized endpoints that produced that error value. If
596 // for some reason we cannot encode that metric, then we skip it and move
597 // to the next one.
598 for (const auto& measurement : errors) {
599 bool needs_weight_swap = false;
600 if (measurement.Pack(with_alpha, astc_mode, vals, &needs_weight_swap)) {
601 // Make sure that if we ask for a blue-contract mode that we get it *and*
602 // if we don't ask for it then we don't get it.
603 assert(!(measurement.BlueContract() ^
604 UsesBlueContract(max_value, *astc_mode, *vals)));
605
606 // We encoded what we got.
607 return needs_weight_swap;
608 }
609 }
610
611 assert(false && "Shouldn't have reached this point -- some combination of "
612 "endpoints should be possible to encode!");
613 return false;
614 }
615
616 } // namespace
617
618 ////////////////////////////////////////////////////////////////////////////////
619
UsesBlueContract(int max_value,ColorEndpointMode mode,const std::vector<int> & vals)620 bool UsesBlueContract(int max_value, ColorEndpointMode mode,
621 const std::vector<int>& vals) {
622 assert(vals.size() >= NumColorValuesForEndpointMode(mode));
623
624 switch (mode) {
625 case ColorEndpointMode::kLDRRGBDirect:
626 case ColorEndpointMode::kLDRRGBADirect: {
627 constexpr int kNumVals = MaxValuesForModes(
628 ColorEndpointMode::kLDRRGBDirect, ColorEndpointMode::kLDRRGBADirect);
629 std::array<int, kNumVals> v {};
630 std::copy(vals.begin(), vals.end(), v.begin());
631 Unquantize(&v, max_value);
632
633 const int s0 = v[0] + v[2] + v[4];
634 const int s1 = v[1] + v[3] + v[5];
635
636 return s0 > s1;
637 }
638
639 case ColorEndpointMode::kLDRRGBBaseOffset:
640 case ColorEndpointMode::kLDRRGBABaseOffset: {
641 constexpr int kNumVals = MaxValuesForModes(
642 ColorEndpointMode::kLDRRGBBaseOffset,
643 ColorEndpointMode::kLDRRGBABaseOffset);
644 std::array<int, kNumVals> v {};
645 std::copy(vals.begin(), vals.end(), v.begin());
646 Unquantize(&v, max_value);
647
648 BitTransferSigned(&v[1], &v[0]);
649 BitTransferSigned(&v[3], &v[2]);
650 BitTransferSigned(&v[5], &v[4]);
651
652 return v[1] + v[3] + v[5] < 0;
653 }
654
655 default:
656 return false;
657 }
658 }
659
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)660 bool EncodeColorsForMode(
661 const RgbaColor& endpoint_low_rgba, const RgbaColor& endpoint_high_rgba,
662 int max_value, EndpointEncodingMode encoding_mode,
663 ColorEndpointMode* const astc_mode, std::vector<int>* const vals) {
664 bool needs_weight_swap = false;
665 vals->resize(NumValuesForEncodingMode(encoding_mode));
666
667 switch (encoding_mode) {
668 case EndpointEncodingMode::kDirectLuma:
669 return EncodeColorsLuma(
670 endpoint_low_rgba, endpoint_high_rgba, max_value, astc_mode, vals);
671
672 case EndpointEncodingMode::kDirectLumaAlpha: {
673 // TODO(google): See if luma-alpha base-offset is better
674 const int avg1 = AverageRGB(endpoint_low_rgba);
675 const int avg2 = AverageRGB(endpoint_high_rgba);
676
677 (*vals)[0] = QuantizeCEValueToRange(avg1, max_value);
678 (*vals)[1] = QuantizeCEValueToRange(avg2, max_value);
679 (*vals)[2] = QuantizeCEValueToRange(endpoint_low_rgba[3], max_value);
680 (*vals)[3] = QuantizeCEValueToRange(endpoint_high_rgba[3], max_value);
681 *astc_mode = ColorEndpointMode::kLDRLumaAlphaDirect;
682 }
683 break;
684
685 case EndpointEncodingMode::kBaseScaleRGB:
686 case EndpointEncodingMode::kBaseScaleRGBA: {
687 RgbaColor base = endpoint_high_rgba;
688 RgbaColor scaled = endpoint_low_rgba;
689
690 // Similar to luma base-offset, the scaled value is strictly less than
691 // the base value here according to the decode procedure. In this case,
692 // if the base is larger than the scale then we need to swap.
693 int num_channels_ge = 0;
694 for (int i = 0; i < 3; ++i) {
695 num_channels_ge +=
696 static_cast<int>(endpoint_high_rgba[i] >= endpoint_low_rgba[i]);
697 }
698
699 if (num_channels_ge < 2) {
700 needs_weight_swap = true;
701 std::swap(base, scaled);
702 }
703
704 // Since the second endpoint is just a direct copy of the RGB values, we
705 // can start by quantizing them.
706 const auto q_base = QuantizeColor(base, max_value);
707 const auto uq_base = UnquantizeColor(q_base, max_value);
708
709 // The first endpoint (scaled) is defined by piecewise multiplying the
710 // second endpoint (base) by the scale factor and then dividing by 256.
711 // This means that the inverse operation is to first piecewise multiply
712 // the first endpoint by 256 and then divide by the unquantized second
713 // endpoint. We take the average of each of each of these scale values as
714 // our final scale value.
715 // TODO(google): Is this the best way to determine the scale factor?
716 int num_samples = 0;
717 int scale_sum = 0;
718 for (int i = 0; i < 3; ++i) {
719 int x = uq_base[i];
720 if (x != 0) {
721 ++num_samples;
722 scale_sum += (scaled[i] * 256) / x;
723 }
724 }
725
726 (*vals)[0] = q_base[0];
727 (*vals)[1] = q_base[1];
728 (*vals)[2] = q_base[2];
729 if (num_samples > 0) {
730 const int avg_scale = Clamp(scale_sum / num_samples, 0, 255);
731 (*vals)[3] = QuantizeCEValueToRange(avg_scale, max_value);
732 } else {
733 // In this case, all of the base values are zero, so we can use whatever
734 // we want as the scale -- it won't affect the outcome.
735 (*vals)[3] = max_value;
736 }
737 *astc_mode = ColorEndpointMode::kLDRRGBBaseScale;
738
739 if (encoding_mode == EndpointEncodingMode::kBaseScaleRGBA) {
740 (*vals)[4] = QuantizeCEValueToRange(scaled[3], max_value);
741 (*vals)[5] = QuantizeCEValueToRange(base[3], max_value);
742 *astc_mode = ColorEndpointMode::kLDRRGBBaseScaleTwoA;
743 }
744 }
745 break;
746
747 case EndpointEncodingMode::kDirectRGB:
748 case EndpointEncodingMode::kDirectRGBA:
749 return EncodeColorsRGBA(
750 endpoint_low_rgba, endpoint_high_rgba, max_value,
751 encoding_mode == EndpointEncodingMode::kDirectRGBA, astc_mode, vals);
752
753 default:
754 assert(false && "Unimplemented color encoding.");
755 }
756
757 return needs_weight_swap;
758 }
759
760 // These decoding procedures follow the code outlined in Section C.2.14 of
761 // the ASTC specification.
DecodeColorsForMode(const std::vector<int> & vals,int max_value,ColorEndpointMode mode,RgbaColor * const endpoint_low_rgba,RgbaColor * const endpoint_high_rgba)762 void DecodeColorsForMode(const std::vector<int>& vals,
763 int max_value, ColorEndpointMode mode,
764 RgbaColor* const endpoint_low_rgba,
765 RgbaColor* const endpoint_high_rgba) {
766 assert(vals.size() >= NumColorValuesForEndpointMode(mode));
767 switch (mode) {
768 case ColorEndpointMode::kLDRLumaDirect: {
769 const int l0 = UnquantizeCEValueFromRange(vals[0], max_value);
770 const int l1 = UnquantizeCEValueFromRange(vals[1], max_value);
771
772 *endpoint_low_rgba = {{ l0, l0, l0, 255 }};
773 *endpoint_high_rgba = {{ l1, l1, l1, 255 }};
774 }
775 break;
776
777 case ColorEndpointMode::kLDRLumaBaseOffset: {
778 const int v0 = UnquantizeCEValueFromRange(vals[0], max_value);
779 const int v1 = UnquantizeCEValueFromRange(vals[1], max_value);
780
781 const int l0 = (v0 >> 2) | (v1 & 0xC0);
782 const int l1 = std::min(l0 + (v1 & 0x3F), 0xFF);
783
784 *endpoint_low_rgba = {{ l0, l0, l0, 255 }};
785 *endpoint_high_rgba = {{ l1, l1, l1, 255 }};
786 }
787 break;
788
789 case ColorEndpointMode::kLDRLumaAlphaDirect: {
790 constexpr int kNumVals =
791 NumColorValuesForEndpointMode(ColorEndpointMode::kLDRLumaAlphaDirect);
792
793 std::array<int, kNumVals> v;
794 std::copy(vals.begin(), vals.end(), v.begin());
795 Unquantize(&v, max_value);
796
797 *endpoint_low_rgba = {{ v[0], v[0], v[0], v[2] }};
798 *endpoint_high_rgba = {{ v[1], v[1], v[1], v[3] }};
799 }
800 break;
801
802 case ColorEndpointMode::kLDRLumaAlphaBaseOffset: {
803 constexpr int kNumVals = NumColorValuesForEndpointMode(
804 ColorEndpointMode::kLDRLumaAlphaBaseOffset);
805
806 std::array<int, kNumVals> v;
807 std::copy(vals.begin(), vals.end(), v.begin());
808 Unquantize(&v, max_value);
809
810 BitTransferSigned(&v[1], &v[0]);
811 BitTransferSigned(&v[3], &v[2]);
812
813 *endpoint_low_rgba = {{ v[0], v[0], v[0], v[2] }};
814 const int high_luma = v[0] + v[1];
815 *endpoint_high_rgba = {{ high_luma, high_luma, high_luma, v[2] + v[3] }};
816
817 for (auto& c : *endpoint_low_rgba) { c = Clamp(c, 0, 255); }
818 for (auto& c : *endpoint_high_rgba) { c = Clamp(c, 0, 255); }
819 }
820 break;
821
822 case ColorEndpointMode::kLDRRGBBaseScale: {
823 constexpr int kNumVals =
824 NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBBaseScale);
825
826 std::array<int, kNumVals> v;
827 std::copy(vals.begin(), vals.end(), v.begin());
828 Unquantize(&v, max_value);
829
830 *endpoint_high_rgba = {{ v[0], v[1], v[2], 255 }};
831 for (int i = 0; i < 3; ++i) {
832 const int x = endpoint_high_rgba->at(i);
833 endpoint_low_rgba->at(i) = (x * v[3]) >> 8;
834 }
835 endpoint_low_rgba->at(3) = 255;
836 }
837 break;
838
839 case ColorEndpointMode::kLDRRGBDirect: {
840 constexpr int kNumVals =
841 NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBDirect);
842
843 std::array<int, kNumVals> v;
844 std::copy(vals.begin(), vals.end(), v.begin());
845 Unquantize(&v, max_value);
846
847 const int s0 = v[0] + v[2] + v[4];
848 const int s1 = v[1] + v[3] + v[5];
849
850 *endpoint_low_rgba = {{ v[0], v[2], v[4], 255 }};
851 *endpoint_high_rgba = {{ v[1], v[3], v[5], 255 }};
852
853 if (s1 < s0) {
854 std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
855 BlueContract(endpoint_low_rgba);
856 BlueContract(endpoint_high_rgba);
857 }
858 }
859 break;
860
861 case ColorEndpointMode::kLDRRGBBaseOffset: {
862 constexpr int kNumVals =
863 NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBBaseOffset);
864
865 std::array<int, kNumVals> v;
866 std::copy(vals.begin(), vals.end(), v.begin());
867 Unquantize(&v, max_value);
868
869 BitTransferSigned(&v[1], &v[0]);
870 BitTransferSigned(&v[3], &v[2]);
871 BitTransferSigned(&v[5], &v[4]);
872
873 *endpoint_low_rgba = {{ v[0], v[2], v[4], 255 }};
874 *endpoint_high_rgba = {{ v[0] + v[1], v[2] + v[3], v[4] + v[5], 255 }};
875
876 if (v[1] + v[3] + v[5] < 0) {
877 std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
878 BlueContract(endpoint_low_rgba);
879 BlueContract(endpoint_high_rgba);
880 }
881
882 for (auto& c : *endpoint_low_rgba) { c = Clamp(c, 0, 255); }
883 for (auto& c : *endpoint_high_rgba) { c = Clamp(c, 0, 255); }
884 }
885 break;
886
887 case ColorEndpointMode::kLDRRGBBaseScaleTwoA: {
888 constexpr int kNumVals = NumColorValuesForEndpointMode(
889 ColorEndpointMode::kLDRRGBBaseScaleTwoA);
890
891 std::array<int, kNumVals> v;
892 std::copy(vals.begin(), vals.end(), v.begin());
893 Unquantize(&v, max_value);
894
895 // Base
896 *endpoint_low_rgba = *endpoint_high_rgba = {{ v[0], v[1], v[2], 255 }};
897
898 // Scale
899 for (int i = 0; i < 3; ++i) {
900 auto& x = endpoint_low_rgba->at(i);
901 x = (x * v[3]) >> 8;
902 }
903
904 // Two A
905 endpoint_low_rgba->at(3) = v[4];
906 endpoint_high_rgba->at(3) = v[5];
907 }
908 break;
909
910 case ColorEndpointMode::kLDRRGBADirect: {
911 constexpr int kNumVals =
912 NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBADirect);
913
914 std::array<int, kNumVals> v;
915 std::copy(vals.begin(), vals.end(), v.begin());
916 Unquantize(&v, max_value);
917
918 const int s0 = v[0] + v[2] + v[4];
919 const int s1 = v[1] + v[3] + v[5];
920
921 *endpoint_low_rgba = {{ v[0], v[2], v[4], v[6] }};
922 *endpoint_high_rgba = {{ v[1], v[3], v[5], v[7] }};
923
924 if (s1 < s0) {
925 std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
926 BlueContract(endpoint_low_rgba);
927 BlueContract(endpoint_high_rgba);
928 }
929 }
930 break;
931
932 case ColorEndpointMode::kLDRRGBABaseOffset: {
933 constexpr int kNumVals =
934 NumColorValuesForEndpointMode(ColorEndpointMode::kLDRRGBABaseOffset);
935
936 std::array<int, kNumVals> v;
937 std::copy(vals.begin(), vals.end(), v.begin());
938 Unquantize(&v, max_value);
939
940 BitTransferSigned(&v[1], &v[0]);
941 BitTransferSigned(&v[3], &v[2]);
942 BitTransferSigned(&v[5], &v[4]);
943 BitTransferSigned(&v[7], &v[6]);
944
945 *endpoint_low_rgba = {{ v[0], v[2], v[4], v[6] }};
946 *endpoint_high_rgba = {{
947 v[0] + v[1], v[2] + v[3], v[4] + v[5], v[6] + v[7] }};
948
949 if (v[1] + v[3] + v[5] < 0) {
950 std::swap(*endpoint_low_rgba, *endpoint_high_rgba);
951 BlueContract(endpoint_low_rgba);
952 BlueContract(endpoint_high_rgba);
953 }
954
955 for (auto& c : *endpoint_low_rgba) { c = Clamp(c, 0, 255); }
956 for (auto& c : *endpoint_high_rgba) { c = Clamp(c, 0, 255); }
957 }
958 break;
959
960 default:
961 // Unimplemented color encoding.
962 // TODO(google): Is this the correct error handling?
963 *endpoint_high_rgba = *endpoint_low_rgba = {{ 0, 0, 0, 0 }};
964 }
965 }
966
967 } // namespace astc_codec
968