blob: 20bcec1b7819f9dcda432093e8e70d241c6bef1d [file] [log] [blame]
// Copyright 2020 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.
// -----------------------------------------------------------------------------
//
// implementation of the 1990 CCITT Group4 spec encoder.
// https://en.wikipedia.org/wiki/Group_4_compression
//
// Author: Vincent Rabaud (vrabaud@google.com)
#include <algorithm>
#include <cassert>
#include <cstdint>
#include "src/common/constants.h"
#include "src/common/lossless/color_cache.h"
#include "src/common/symbols.h"
#include "src/dec/lossless/losslessi_dec.h"
#include "src/dec/symbols_dec.h"
#include "src/utils/ans_utils.h"
#include "src/utils/plane.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
#include "src/wp2/format_constants.h"
namespace WP2L {
static void SetColor(int16_t color, uint32_t x, int16_t* const dst) {
// Write to the image to the green channel (channel 2).
dst[4 * x + 0] = dst[4 * x + 1] = dst[4 * x + 3] = 0;
dst[4 * x + 2] = color;
}
static uint16_t GetColor(const int16_t* const argb, int x,
int16_t fallback = 0) {
if (x < 0) return fallback;
// Get the color from the green channel.
return argb[x * 4 + 2];
}
static uint16_t ChangeColor(uint32_t num_colors, uint16_t current_color,
uint16_t expected_new_color,
MoveToFrontCache* const mtf,
WP2::SymbolReader* const sr) {
assert(current_color != expected_new_color);
if (num_colors == 2) return !current_color;
const bool color_change = sr->Read(kSymbolG4ColorChange, "color_change");
if (!color_change) {
mtf->MoveToFront(expected_new_color);
return expected_new_color;
}
uint32_t new_color_idx = sr->Read(kSymbolG4NewColor, "color_change");
const uint32_t current_color_idx = mtf->GetIndex(current_color);
const uint32_t expected_new_color_idx = mtf->GetIndex(expected_new_color);
if (new_color_idx >= std::min(expected_new_color_idx, current_color_idx)) {
++new_color_idx;
}
if (new_color_idx >= std::max(expected_new_color_idx, current_color_idx)) {
++new_color_idx;
}
const uint32_t new_color = mtf->GetColor(new_color_idx);
mtf->MoveToFront(new_color);
return new_color;
}
// Implementation taken from https://www.itu.int/rec/T-REC-T.6-198811-I/en.
// See the above for diagrams showing the meaning of a0/a1/b1/b2.
WP2Status Decoder::DecodeGroup4(uint32_t width, uint32_t last_row,
WP2::Planef* const bits_per_pixel,
int16_t** src_out) {
assert(num_colors_ >= 2);
WP2::ANSDebugPrefix prefix(dec_, "group4");
WP2::SymbolReader* const sr = &hdr_.sr_;
int16_t* const data = pixels_.data();
int16_t* src = data + 4 * last_pixel_;
const int16_t first_color = group4_first_color_;
const bool get_cost = (bits_per_pixel != nullptr || config_.info != nullptr);
for (uint32_t y = last_pixel_ / width; y < last_row; ++y) {
const int16_t* prev = (y > 0) ? &data[(y - 1) * width * 4] : nullptr;
// Define the virtual pixel before the first column.
int16_t current_color = first_color;
mtf_.MoveToFront(current_color);
int a0 = -1;
uint32_t x = 0;
while (x < width) {
float cost = 0.f;
const uint32_t start_x = x;
float* cost_ptr = get_cost ? &cost : nullptr;
const int16_t a0_color = current_color;
// Find b1: next changing pixel of color different from a0_color
// on the previous line.
int b1 = a0 + 1;
if (y == 0) {
b1 = width;
} else {
while (b1 < (int)width &&
!(GetColor(prev, b1) != GetColor(prev, b1 - 1, first_color) &&
GetColor(prev, b1) != a0_color)) {
++b1;
}
}
const uint64_t b1_color =
(b1 < (int)width) ? GetColor(prev, b1) : !a0_color;
// Find b2: next changing pixel after b1 on the previous line.
uint32_t b2 = b1 + 1;
if (y == 0) {
b2 = width;
} else {
while (b2 < width && GetColor(prev, b2) == GetColor(prev, b1)) ++b2;
}
const uint16_t b2_color =
(b2 < width) ? GetColor(prev, b2, a0_color) : a0_color;
mtf_.MoveToFront(b1_color);
mtf_.MoveToFront(b2_color);
const uint32_t a0b1_dist = b1 - a0;
const uint32_t a0a1_max = b2 - a0;
// Horizontal mode allowed if all the possible pixel locations aren't
// covered by the vertical mode.
const bool horizontal_mode_allowed =
(a0b1_dist > kGroup4Window + 1) ||
(a0b1_dist + kGroup4Window < a0a1_max);
// Pass mode allowed if it's not at the right edge of the tile.
const bool pass_mode_allowed = (b2 + 1) <= width;
const uint32_t max_mode = horizontal_mode_allowed + pass_mode_allowed;
Group4Mode mode = Group4Mode::kVertical;
if (max_mode == 2) {
mode = (Group4Mode)sr->Read(kSymbolG4Type, "mode", cost_ptr);
} else if (max_mode == 1) {
int32_t mode_int;
WP2_CHECK_STATUS(sr->ReadWithMax(kSymbolG4Type, /*cluster=*/0, max_mode,
"mode", &mode_int, cost_ptr));
mode = (mode_int == 0) ? Group4Mode::kVertical
: horizontal_mode_allowed ? Group4Mode::kHorizontal
: Group4Mode::kPass;
}
switch (mode) {
case Group4Mode::kPass:
// b2 can be equal to width so make sure we also do not get past the
// end of the row.
while (x < width && x <= b2) SetColor(a0_color, x++, src);
a0 = b2;
break;
case Group4Mode::kHorizontal: {
const char* const str[2] = {"hdist_bg_minus1", "hdist_fg_minus1"};
int32_t a0a1_minus1;
WP2_CHECK_STATUS(sr->ReadWithMax(
kSymbolG4HorizontalDist, a0_color != first_color, a0a1_max - 1,
str[a0_color != first_color], &a0a1_minus1, cost_ptr));
const uint32_t a1 = a0 + a0a1_minus1 + 1;
while (x < a1) SetColor(a0_color, x++, src);
if (x < width) {
const int a1a2_max = (int)width - a1;
int32_t a1a2_minus1;
WP2_CHECK_STATUS(sr->ReadWithMax(
kSymbolG4HorizontalDist, a0_color == first_color, a1a2_max - 1,
str[a0_color == first_color], &a1a2_minus1, cost_ptr));
const uint32_t a2 = a1 + a1a2_minus1 + 1;
assert(a2 <= width);
const int16_t a1_color =
ChangeColor(num_colors_, a0_color, b1_color, &mtf_, sr);
current_color = a1_color;
while (x < a2) SetColor(a1_color, x++, src);
if (x < width) {
const uint16_t expected_a2_color =
(b2_color != a1_color) ? b2_color : a0_color;
const int16_t a2_color = ChangeColor(
num_colors_, a1_color, expected_a2_color, &mtf_, sr);
current_color = a2_color;
SetColor(a2_color, x++, src);
}
a0 = a2;
}
break;
}
case Group4Mode::kVertical: {
// min_dist could also be optimized upon (e.g. when at the beginning
// of the line) but that would change the statistics as some values
// would be offset. E.g., if all values are the same, they would be
// shifted by a non-constant min_dist which would result in different
// values.
const int min_dist = -(int)kGroup4Window;
const int max_dist = (int)std::min(b1 + kGroup4Window, width) - b1;
int32_t a1b1;
WP2_CHECK_STATUS(sr->ReadWithMax(kSymbolG4VerticalDist, /*cluster=*/0,
max_dist - min_dist, "vdist", &a1b1,
cost_ptr));
const uint32_t a1 = b1 + a1b1 + min_dist;
// We need to keep x < width as we do not optimize upon min_dist.
while (x < std::min(width, a1)) SetColor(a0_color, x++, src);
if (x < width) {
const int16_t a1_color =
ChangeColor(num_colors_, a0_color, b1_color, &mtf_, sr);
current_color = a1_color;
SetColor(a1_color, x++, src);
}
a0 = a1;
break;
}
}
if (get_cost) {
WP2_CHECK_REDUCED_STATUS(RegisterSymbolForVDebug(
(int)mode, y * width + start_x, /*distance=*/0u,
/*length=*/x - start_x, cost, config_.info));
if (bits_per_pixel != nullptr) {
const float bits = cost / (x - start_x);
bits_per_pixel->Fill({start_x, y, x - start_x, 1}, bits);
}
}
}
src += 4 * width;
WP2_CHECK_STATUS(dec_->GetStatus());
WP2_CHECK_STATUS(ProcessRows(y));
}
if (src_out != nullptr) *src_out = src;
return WP2_STATUS_OK;
}
} // namespace WP2L