blob: 7e57b1801de3fbfe9f292ad9aa9d4d4243f83d11 [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.
// -----------------------------------------------------------------------------
//
// Image transforms and color space conversion methods for lossless decoder.
//
// Authors: Vikas Arora (vikaas.arora@gmail.com)
// Jyrki Alakuijala (jyrki@google.com)
// Urvang Joshi (urvang@google.com)
#include "src/dsp/lossless/decl_dsp.h"
#include "src/common/constants.h"
#include "src/dec/lossless/losslessi_dec.h"
#include "src/dsp/dsp.h"
//------------------------------------------------------------------------------
// Predictors
// Sum of each component, mod 256.
static WP2_UBSAN_IGNORE_UNSIGNED_OVERFLOW inline void AddPixels(
const int16_t* const a, bool has_alpha, const int16_t* const b,
uint16_t mask, int16_t* const out) {
// TODO(vrabaud) remove 0xff alpha wrap around.
out[0] = has_alpha ? ((a[0] + b[0]) & 0xff) : WP2::kAlphaMax;
for (uint32_t i = 1; i < 4; ++i) out[i] = (a[i] + b[i]) & mask;
}
static void PredictorAdd0_C(const int16_t* in, bool has_alpha, const int16_t*,
uint32_t num_pixels, int16_t min_value,
int16_t max_value, int16_t mask, int16_t* out) {
int16_t pred[4];
WP2L::Predictors_C[0](/*left=*/nullptr, /*top=*/nullptr, min_value, max_value,
pred);
for (const int16_t* in_end = in + 4 * num_pixels; in < in_end;
in += 4, out += 4) {
AddPixels(in, has_alpha, pred, mask, out);
}
}
static void PredictorAdd1_C(const int16_t* in, bool has_alpha, const int16_t*,
uint32_t num_pixels, int16_t, int16_t, int16_t mask,
int16_t* out) {
for (const int16_t* in_end = in + 4 * num_pixels; in < in_end;
in += 4, out += 4) {
AddPixels(in, has_alpha, &out[-4], mask, out);
}
}
// Macros used to create a batch predictor that iteratively uses a
// one-pixel predictor.
// The predictor is added to the output pixel (which
// is therefore considered as a residual) to get the final prediction.
#define GENERATE_PREDICTOR_ADD(PREDICTOR, PREDICTOR_ADD) \
static void PREDICTOR_ADD(const int16_t* in, bool has_alpha, \
const int16_t* upper, uint32_t num_pixels, \
int16_t min_value, int16_t max_value, \
int16_t mask, int16_t* out) { \
int16_t pred[4]; \
assert(upper != nullptr); \
for (const int16_t* const out_end = out + 4 * num_pixels; out < out_end; \
out += 4, in += 4, upper += 4) { \
(PREDICTOR)(out - 4, upper, min_value, max_value, pred); \
AddPixels(in, has_alpha, pred, mask, out); \
} \
}
namespace WP2L {
GENERATE_PREDICTOR_ADD(Predictors_C[2], PredictorAdd2_C)
GENERATE_PREDICTOR_ADD(Predictors_C[3], PredictorAdd3_C)
GENERATE_PREDICTOR_ADD(Predictors_C[4], PredictorAdd4_C)
GENERATE_PREDICTOR_ADD(Predictors_C[5], PredictorAdd5_C)
GENERATE_PREDICTOR_ADD(Predictors_C[6], PredictorAdd6_C)
GENERATE_PREDICTOR_ADD(Predictors_C[7], PredictorAdd7_C)
GENERATE_PREDICTOR_ADD(Predictors_C[8], PredictorAdd8_C)
GENERATE_PREDICTOR_ADD(Predictors_C[9], PredictorAdd9_C)
GENERATE_PREDICTOR_ADD(Predictors_C[10], PredictorAdd10_C)
GENERATE_PREDICTOR_ADD(Predictors_C[11], PredictorAdd11_C)
GENERATE_PREDICTOR_ADD(Predictors_C[12], PredictorAdd12_C)
GENERATE_PREDICTOR_ADD(Predictors_C[13], PredictorAdd13_C)
GENERATE_PREDICTOR_ADD(Predictors_C[14], PredictorAdd14_C)
//------------------------------------------------------------------------------
// Inverse transforms.
// Inverse prediction.
static void PredictorInverseTransform_C(const Transform* const transform,
int y_start, int y_end,
int16_t min_value, int16_t max_value,
int16_t mask, const int16_t* in,
bool has_alpha, int16_t* out) {
const int width = transform->width_;
if (y_start == 0) { // First Row follows the L (mode=1) mode.
PredictorAdd0_C(in, has_alpha, nullptr, 1, min_value, max_value, mask, out);
PredictorAdd1_C(in + 4, has_alpha, nullptr, width - 1, min_value, max_value,
mask, out + 4);
in += 4 * width;
out += 4 * width;
++y_start;
}
{
int y = y_start;
const int tile_width = 1 << transform->bits_;
const int tile_mask = tile_width - 1;
const int tiles_per_row = SubSampleSize(width, transform->bits_);
const int16_t* pred_mode_base =
transform->data_.data() + 4 * (y >> transform->bits_) * tiles_per_row;
while (y < y_end) {
const int16_t* pred_mode_src = pred_mode_base;
int x = 1;
// First pixel follows the T (mode=2) mode.
PredictorAdd2_C(in, has_alpha, out - 4 * width, 1, min_value, max_value,
mask, out);
// .. the rest:
while (x < width) {
const PredictorAddSubFunc pred_func = PredictorsAdd[pred_mode_src[2]];
int x_end = (x & ~tile_mask) + tile_width;
if (x_end > width) x_end = width;
pred_func(in + 4 * x, has_alpha, out + 4 * x - 4 * width, x_end - x,
min_value, max_value, mask, out + 4 * x);
x = x_end;
pred_mode_src += 4;
}
in += 4 * width;
out += 4 * width;
++y;
// Use the same mask, since tiles are squares.
if ((y & tile_mask) == 0) {
pred_mode_base += 4 * tiles_per_row;
}
}
}
}
// Add green to blue and red channels (i.e. perform the inverse transform of
// 'subtract green').
void AddGreenToBlueAndRed_C(const uint16_t* src, uint32_t num_pixels,
uint32_t channel_bits, uint16_t* dst) {
const uint16_t mask = (uint16_t)((1u << channel_bits) - 1);
for (const uint16_t* src_end = src + 4 * num_pixels; src < src_end;
src += 4, dst += 4) {
const uint16_t green = src[2];
dst[0] = src[0];
dst[1] = (src[1] + green) & mask;
dst[2] = src[2];
dst[3] = (src[3] + green) & mask;
}
}
static inline int32_t ColorTransformDelta(int16_t color_pred,
uint16_t max_value_half,
uint16_t color) {
const int color_int =
(color >= max_value_half) ? (int)color - 2 * max_value_half : (int)color;
return (((int32_t)color_pred * color_int) >> 5);
}
static inline void ColorCodeToMultipliers(const int16_t* const color_code,
Multipliers* const m) {
m->green_to_red = color_code[3];
m->green_to_blue = color_code[2];
m->red_to_blue = color_code[1];
}
static void TransformColorInverse_C(const Multipliers* const m,
const uint16_t* src, uint32_t num_pixels,
uint32_t num_bits, uint16_t* dst) {
const uint16_t max_value = (1u << num_bits) - 1;
const uint16_t max_value_half = (max_value + 1) / 2;
for (const uint16_t* src_end = src + 4 * num_pixels; src < src_end;
src += 4, dst += 4) {
const uint16_t red = src[1];
const uint16_t green = src[2];
int new_red = red;
int new_blue = src[3];
new_red += ColorTransformDelta(m->green_to_red, max_value_half, green);
new_red &= max_value;
new_blue += ColorTransformDelta(m->green_to_blue, max_value_half, green);
new_blue += ColorTransformDelta(m->red_to_blue, max_value_half, new_red);
new_blue &= max_value;
dst[0] = src[0];
dst[1] = (uint16_t)new_red;
dst[2] = src[2];
dst[3] = (uint16_t)new_blue;
}
}
// Color space inverse transform.
static void ColorSpaceInverseTransform_C(const Transform* const transform,
int y_start, int y_end,
uint32_t num_bits, const uint16_t* src,
uint16_t* dst) {
const uint32_t width = transform->width_;
const int tile_width = 1 << transform->bits_;
const int mask = tile_width - 1;
const int safe_width = width & ~mask;
const int remaining_width = width - safe_width;
const int tiles_per_row = SubSampleSize(width, transform->bits_);
int y = y_start;
const int16_t* pred_row =
transform->data_.data() + 4 * (y >> transform->bits_) * tiles_per_row;
while (y < y_end) {
const int16_t* pred = pred_row;
Multipliers m = { 0, 0, 0 };
const uint16_t* const src_safe_end = src + 4 * safe_width;
const uint16_t* const src_end = src + 4 * width;
while (src < src_safe_end) {
ColorCodeToMultipliers(pred, &m);
TransformColorInverse(&m, src, tile_width, num_bits, dst);
src += 4 * tile_width;
dst += 4 * tile_width;
pred += 4;
}
if (src < src_end) { // Left-overs using C-version.
ColorCodeToMultipliers(pred, &m);
TransformColorInverse(&m, src, remaining_width, num_bits, dst);
src += 4 * remaining_width;
dst += 4 * remaining_width;
pred += 4;
}
++y;
if ((y & mask) == 0) pred_row += 4 * tiles_per_row;
}
}
static void MapARGB_C(const uint16_t* src, const int16_t* const color_map,
uint32_t color_map_size, uint32_t y_start, uint32_t y_end,
uint32_t width, uint16_t* dst) {
for (const uint16_t* const src_end = src + 4 * (y_end - y_start) * width;
src < src_end; src += 4, dst += 4) {
if (src[2] >= color_map_size) assert(false);
// TODO(vrabaud) use the following once switched to int16_t;
// ColorCopy(&color_map[4 * src[2]], dst);
std::copy(&color_map[4 * src[2]], &color_map[4 * src[2]] + 4, dst);
}
}
static void ColorIndexInverseTransform_C(const Transform* const transform,
uint32_t y_start, uint32_t y_end,
const uint16_t* const src,
uint16_t* const dst) {
const uint32_t width = transform->width_;
MapColor(src, /*color_map=*/transform->data_.data(),
/*color_map_size=*/transform->data_.size() / 4, y_start, y_end,
width, dst);
}
void InverseTransform(const Transform* const transform, uint32_t row_start,
uint32_t row_end, uint32_t channel_bits,
const uint16_t* const in, bool has_alpha,
uint16_t* const out) {
const uint32_t width = transform->width_;
assert(row_start < row_end);
assert(row_end <= transform->height_);
switch (transform->type_) {
case SUBTRACT_GREEN:
AddGreenToBlueAndRed(in, (row_end - row_start) * width, channel_bits,
out);
break;
case PREDICTOR_TRANSFORM: {
const int16_t min_value = 0;
const int16_t max_value = (1 << channel_bits) - 1;
const int16_t mask = (1 << channel_bits) - 1;
PredictorInverseTransform_C(transform, row_start, row_end, min_value,
max_value, mask, (int16_t*)in, has_alpha,
(int16_t*)out);
if (row_end != transform->height_) {
// The last predicted row in this iteration will be the top-pred row
// for the first row in next iteration.
std::copy(out + 4 * (row_end - row_start - 1) * width,
out + 4 * (row_end - row_start) * width, out - 4 * width);
}
break;
}
case CROSS_COLOR_TRANSFORM:
ColorSpaceInverseTransform_C(transform, row_start, row_end, channel_bits,
in, out);
break;
case GROUP4:
case COLOR_INDEXING_TRANSFORM:
ColorIndexInverseTransform_C(transform, row_start, row_end, in, out);
break;
default:
assert(false);
}
}
//------------------------------------------------------------------------------
ProcessDecBlueAndRedFunc AddGreenToBlueAndRed;
PredictorAddSubFunc PredictorsAdd[kNumPredictors];
// exposed plain-C implementations
PredictorAddSubFunc PredictorsAdd_C[kNumPredictors];
TransformColorInverseFunc TransformColorInverse;
MapARGBFunc MapColor;
static volatile WP2CPUInfo lossless_last_cpuinfo_used =
(WP2CPUInfo)&lossless_last_cpuinfo_used;
WP2_TSAN_IGNORE_FUNCTION void DecLDspInit() {
if (lossless_last_cpuinfo_used == WP2GetCPUInfo) return;
DspInit();
COPY_PREDICTOR_ARRAY(PredictorAdd, PredictorsAdd)
COPY_PREDICTOR_ARRAY(PredictorAdd, PredictorsAdd_C)
AddGreenToBlueAndRed = AddGreenToBlueAndRed_C;
TransformColorInverse = TransformColorInverse_C;
MapColor = MapARGB_C;
// If defined, use CPUInfo() to overwrite some pointers with faster versions.
if (WP2GetCPUInfo != NULL) {
// TODO(skal): SSE2, etc.
}
lossless_last_cpuinfo_used = WP2GetCPUInfo;
}
} // namespace WP2L