blob: 7302f4acdda9ea18db666810768e17f8b4fa4e34 [file] [log] [blame]
// Copyright 2019 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.
// -----------------------------------------------------------------------------
//
// Preview decoder
//
// Author: Yannis Guyon (yguyon@google.com)
#include "./preview_dec.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include "src/common/color_precision.h"
#include "src/common/preview/preview.h"
#include "src/utils/ans.h"
#include "src/utils/ans_utils.h"
#include "src/utils/data_source.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
namespace WP2 {
//------------------------------------------------------------------------------
namespace {
int32_t ReadAValue(ANSDec* const dec, ValueStats* const stats, bool is_positive,
WP2_OPT_LABEL) {
if (dec->ReadABit(&stats->zero, label)) return 0u;
int32_t abs_value = 1;
for (uint32_t i = 0, j = 1; j <= AYCoCg19b::kYCoCgMax; j <<= 1) {
assert(i < AYCoCg19b::kYCoCgBitDepth);
if (dec->ReadABit(&stats->bits[i++], label)) abs_value += j;
}
if (!is_positive && dec->ReadABit(&stats->sign, label)) return -abs_value;
return abs_value;
}
} // namespace
WP2Status PreviewData::DecodePalette(ANSDec* const dec) {
ANSDebugPrefix prefix(dec, "palette");
const uint32_t num_colors =
dec->ReadRange(kPreviewMinNumColors, kPreviewMaxNumColors, "num_colors");
const bool has_alpha =
(bool)dec->ReadBit(kPreviewOpaqueProba, kPreviewTotalProba, "has_alpha");
WP2_CHECK_ALLOC_OK(palette_.resize(num_colors));
AYCoCg19b pred = kPreviewStartingColorPrediction;
ANSBinSymbol alpha(2, 2);
ValueStats stat_yco, stat_cg;
for (AYCoCg19b& color : palette_) {
color.a =
!has_alpha || (bool)(dec->ReadABit(&alpha, "residual_alpha") ^ pred.a);
color.y =
(uint8_t)(pred.y + ReadAValue(dec, &stat_yco, false, "residual_y"));
color.co =
(uint8_t)(pred.co + ReadAValue(dec, &stat_yco, false, "residual_co"));
color.cg =
(uint8_t)(pred.cg + ReadAValue(dec, &stat_cg, true, "residual_cg"));
pred = color;
}
return dec->GetStatus();
}
//------------------------------------------------------------------------------
uint16_t PreviewData::DecodeColorIndex(ANSDec* const dec,
ANSBinSymbol* const stats,
uint16_t* const prediction) {
if (dec->ReadABit(stats, "color_index_match")) {
// TODO(yguyon): Map color index better (if only unused colors left..)
const uint16_t coded_idx =
dec->ReadRValue(palette_.size() - 1, "residual_color_index");
*prediction = (coded_idx <= *prediction) ? coded_idx : coded_idx + 1;
}
return *prediction;
}
WP2Status PreviewData::DecodeVertices(ANSDec* const dec) {
uint32_t num_grid_points_left = grid_width_ * grid_height_ - 4u;
const uint32_t min_num_vertices = std::max(
std::max((uint32_t)palette_.size(), 4u) - 4u, kPreviewMinNumVertices);
const uint32_t max_num_vertices =
std::min(kPreviewMaxNumVertices, num_grid_points_left);
ANSDebugPrefix prefix(dec, "vertices");
const uint32_t num_vertices =
dec->ReadRange(min_num_vertices, max_num_vertices, "num_vertices");
WP2_CHECK_ALLOC_OK(vertices_.resize(num_vertices));
// Decode corners.
ANSBinSymbol stat_idx(2, 2);
uint16_t predictor = 0;
for (auto& c : corners_) c = DecodeColorIndex(dec, &stat_idx, &predictor);
if (num_vertices == 0) return WP2_STATUS_OK;
uint32_t vertex_index = 0;
// Decode other points.
for (uint16_t y = 0; y < grid_height_; ++y) {
for (uint16_t x = 0; x < grid_width_; ++x) {
if (IsCorner(x, y)) continue;
const uint32_t num_vertices_left = vertices_.size() - vertex_index;
if (dec->ReadBit(num_grid_points_left - num_vertices_left,
num_grid_points_left, "position_match")) {
VertexIndexedColor& vertex = vertices_[vertex_index];
vertex.x = x;
vertex.y = y;
vertex.color_index = DecodeColorIndex(dec, &stat_idx, &predictor);
if (++vertex_index == vertices_.size()) return dec->GetStatus();
}
--num_grid_points_left;
}
}
// TODO(yguyon): Remove the possibility of BITSTREAM_ERROR
return WP2_STATUS_BITSTREAM_ERROR; // Vertices outside the grid.
}
//------------------------------------------------------------------------------
void PreviewData::Reset() {
grid_width_ = 0;
grid_height_ = 0;
use_noise_ = false;
vertices_.clear();
palette_.clear();
corners_[0] = corners_[1] = corners_[2] = corners_[3] = 0;
}
WP2Status PreviewData::Decode(ANSDec* const dec) {
Reset();
ANSDebugPrefix prefix(dec, "preview");
grid_width_ =
dec->ReadRange(kPreviewMinGridSize, kPreviewMaxGridSize, "grid_dim");
grid_height_ =
dec->ReadRange(kPreviewMinGridSize, kPreviewMaxGridSize, "grid_dim");
use_noise_ =
(bool)dec->ReadBit(kPreviewNoiseProba, kPreviewTotalProba, "use_noise");
WP2_CHECK_STATUS(DecodePalette(dec));
WP2_CHECK_STATUS(DecodeVertices(dec));
WP2_CHECK_STATUS(dec->GetStatus());
return WP2_STATUS_OK;
}
WP2Status PreviewData::Decode(const uint8_t data[], uint32_t data_size) {
ExternalDataSource source(data, data_size);
ANSDec dec(&source);
WP2Status status = Decode(&dec);
if (status == WP2_STATUS_NOT_ENOUGH_DATA ||
dec.GetNumUsedBytes() != data_size) {
status = WP2_STATUS_BITSTREAM_ERROR;
}
WP2_CHECK_STATUS(status);
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status DecodePreview(const uint8_t* const data, uint32_t data_size,
ArgbBuffer* const output_buffer) {
if (output_buffer != nullptr) {
WP2_CHECK_OK(!output_buffer->IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(output_buffer->format() == WP2_Argb_32,
WP2_STATUS_INVALID_COLORSPACE);
PreviewData preview;
WP2_CHECK_STATUS(preview.Decode(data, data_size));
Triangulation triangulation;
WP2_CHECK_STATUS(triangulation.Create(preview));
uint32_t num_covered_pixels = 0;
WP2_CHECK_STATUS(triangulation.FillTriangles(preview, output_buffer,
&num_covered_pixels));
// Make sure all 'canvas' pixels were drawn (fast good enough test).
// TODO(yguyon): Make sure each pixel is drawn exactly once.
assert(num_covered_pixels >=
output_buffer->width() * output_buffer->height());
// TODO(skal): use_noise_
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
} // namespace WP2