// Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "src/decoder/logical_astc_block.h" #include "src/decoder/endpoint_codec.h" #include "src/decoder/footprint.h" #include "src/decoder/integer_sequence_codec.h" #include "src/decoder/quantization.h" #include "src/decoder/weight_infill.h" namespace astc_codec { namespace { Partition GenerateSinglePartition(Footprint footprint) { return Partition { footprint, /* num_parts = */ 1, /* partition_id = */ 0, std::vector(footprint.NumPixels(), 0) }; } static std::vector DecodeEndpoints(const IntermediateBlockData& block) { const int endpoint_range = block.endpoint_range ? block.endpoint_range.value() : EndpointRangeForBlock(block); assert(endpoint_range > 0); std::vector endpoints; for (const auto& eps : block.endpoints) { RgbaColor decmp_one_rgba, decmp_two_rgba; DecodeColorsForMode(eps.colors, endpoint_range, eps.mode, &decmp_one_rgba, &decmp_two_rgba); endpoints.emplace_back(decmp_one_rgba, decmp_two_rgba); } return endpoints; } static std::vector DecodeEndpoints(const VoidExtentData& block) { EndpointPair eps; eps.first[0] = eps.second[0] = (block.r * 255) / 65535; eps.first[1] = eps.second[1] = (block.g * 255) / 65535; eps.first[2] = eps.second[2] = (block.b * 255) / 65535; eps.first[3] = eps.second[3] = (block.a * 255) / 65535; std::vector endpoints; endpoints.emplace_back(eps); return endpoints; } Partition ComputePartition(const Footprint& footprint, const IntermediateBlockData& block) { if (block.partition_id) { const int part_id = block.partition_id.value(); const size_t num_parts = block.endpoints.size(); return GetASTCPartition(footprint, num_parts, part_id); } else { return GenerateSinglePartition(footprint); } } Partition ComputePartition(const Footprint& footprint, const VoidExtentData&) { return GenerateSinglePartition(footprint); } } // namespace //////////////////////////////////////////////////////////////////////////////// LogicalASTCBlock::LogicalASTCBlock(const Footprint& footprint) : endpoints_(1), weights_(footprint.NumPixels(), 0), partition_(GenerateSinglePartition(footprint)) { } LogicalASTCBlock::LogicalASTCBlock(const Footprint& footprint, const IntermediateBlockData& block) : endpoints_(DecodeEndpoints(block)), partition_(ComputePartition(footprint, block)) { CalculateWeights(footprint, block); } LogicalASTCBlock::LogicalASTCBlock(const Footprint& footprint, const VoidExtentData& block) : endpoints_(DecodeEndpoints(block)), partition_(ComputePartition(footprint, block)) { CalculateWeights(footprint, block); } void LogicalASTCBlock::CalculateWeights(const Footprint& footprint, const IntermediateBlockData& block) { const int grid_size_x = block.weight_grid_dim_x; const int grid_size_y = block.weight_grid_dim_y; const int weight_grid_size = grid_size_x * grid_size_y; // Either we have a dual plane and we have twice as many weights as // specified or we don't assert(block.dual_plane_channel ? block.weights.size() == weight_grid_size * 2 : block.weights.size() == weight_grid_size); std::vector unquantized; unquantized.reserve(weight_grid_size); // According to C.2.16, if we have dual-plane weights, then we have two // weights per texel -- one adjacent to the other. Hence, we have to skip // some when we decode the separate weight values. const int weight_frequency = (block.dual_plane_channel) ? 2 : 1; const int weight_range = block.weight_range; for (int i = 0; i < weight_grid_size; ++i) { const int weight = block.weights[i * weight_frequency]; unquantized.push_back(UnquantizeWeightFromRange(weight, weight_range)); } weights_ = InfillWeights(unquantized, footprint, grid_size_x, grid_size_y); if (block.dual_plane_channel) { SetDualPlaneChannel(block.dual_plane_channel.value()); for (int i = 0; i < weight_grid_size; ++i) { const int weight = block.weights[i * weight_frequency + 1]; unquantized[i] = UnquantizeWeightFromRange(weight, weight_range); } dual_plane_->weights = InfillWeights(unquantized, footprint, grid_size_x, grid_size_y); } } void LogicalASTCBlock::CalculateWeights(const Footprint& footprint, const VoidExtentData&) { weights_ = std::vector(footprint.NumPixels(), 0); } void LogicalASTCBlock::SetWeightAt(int x, int y, int weight) { assert(weight >= 0); assert(weight <= 64); weights_.at(y * GetFootprint().Width() + x) = weight; } int LogicalASTCBlock::WeightAt(int x, int y) const { return weights_.at(y * GetFootprint().Width() + x); } void LogicalASTCBlock::SetDualPlaneWeightAt(int channel, int x, int y, int weight) { assert(weight >= 0); assert(weight <= 64); // If it's not a dual plane, then this has no logical meaning assert(IsDualPlane()); // If it is a dual plane and the passed channel matches the query, then we // return the specialized weights if (dual_plane_->channel == channel) { dual_plane_->weights.at(y * GetFootprint().Width() + x) = weight; } else { // If the channel is not the special channel, then return the general weight SetWeightAt(x, y, weight); } } int LogicalASTCBlock::DualPlaneWeightAt(int channel, int x, int y) const { // If it's not a dual plane, then we just return the weight for all channels if (!IsDualPlane()) { // TODO(google): Log warning, Requesting dual-plane channel for a non // dual-plane block! return WeightAt(x, y); } // If it is a dual plane and the passed channel matches the query, then we // return the specialized weights if (dual_plane_->channel == channel) { return dual_plane_->weights.at(y * GetFootprint().Width() + x); } // If the channel is not the special channel, then return the general weight return WeightAt(x, y); } void LogicalASTCBlock::SetDualPlaneChannel(int channel) { if (channel < 0) { dual_plane_.clear(); } else if (dual_plane_) { dual_plane_->channel = channel; } else { dual_plane_ = DualPlaneData {channel, weights_}; } } RgbaColor LogicalASTCBlock::ColorAt(int x, int y) const { const auto footprint = GetFootprint(); assert(x >= 0); assert(x < footprint.Width()); assert(y >= 0); assert(y < footprint.Height()); const int texel_idx = y * footprint.Width() + x; const int part = partition_.assignment[texel_idx]; const auto& endpoints = endpoints_[part]; RgbaColor result; for (int channel = 0; channel < 4; ++channel) { const int weight = (dual_plane_ && dual_plane_->channel == channel) ? dual_plane_->weights[texel_idx] : weights_[texel_idx]; const int p0 = endpoints.first[channel]; const int p1 = endpoints.second[channel]; assert(p0 >= 0); assert(p0 < 256); assert(p1 >= 0); assert(p1 < 256); // According to C.2.19 const int c0 = (p0 << 8) | p0; const int c1 = (p1 << 8) | p1; const int c = (c0 * (64 - weight) + c1 * weight + 32) / 64; // TODO(google): Handle conversion to sRGB or FP16 per C.2.19. const int quantized = ((c * 255) + 32767) / 65536; assert(quantized < 256); result[channel] = quantized; } return result; } void LogicalASTCBlock::SetPartition(const Partition& p) { assert(p.footprint == partition_.footprint && "New partitions may not be for a different footprint"); partition_ = p; endpoints_.resize(p.num_parts); } void LogicalASTCBlock::SetEndpoints(const EndpointPair& eps, int subset) { assert(subset < partition_.num_parts); assert(subset < endpoints_.size()); endpoints_[subset] = eps; } base::Optional UnpackLogicalBlock( const Footprint& footprint, const PhysicalASTCBlock& pb) { if (pb.IsVoidExtent()) { base::Optional ve = UnpackVoidExtent(pb); if (!ve) { return {}; } return LogicalASTCBlock(footprint, ve.value()); } else { base::Optional ib = UnpackIntermediateBlock(pb); if (!ib) { return {}; } return LogicalASTCBlock(footprint, ib.value()); } } } // namespace astc_codec