blob: 280dc772992b090338652cf321caf59f730cadce [file] [log] [blame]
// Copyright 2025 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.
// -----------------------------------------------------------------------------
//
// Transform definition for WebP Lossless.
//
// Authors: Vincent Rabaud (vrabaud@google.com)
#include "src/common/lossless/transforms.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include "src/common/color_precision.h"
#include "src/dsp/math.h"
#include "src/utils/ans.h"
#include "src/wp2/base.h"
#include "src/wp2/format_constants.h"
namespace WP2L {
// A segment of integers, defining the min/max of a channel.
struct Segment {
Segment operator+(const Segment& other) const {
return Segment{min + other.min, max + other.max};
}
Segment operator-(const Segment& other) const {
return Segment{min - other.max, max - other.min};
}
Segment operator*(int32_t v) const {
return v >= 0 ? Segment{min * v, max * v} : Segment{max * v, min * v};
}
Segment operator/(int32_t v) const {
return v > 0 ? Segment{min / v, max / v} : Segment{max / v, min / v};
}
Segment operator>>(int32_t v) const { return Segment{min >> v, max >> v}; }
Segment operator|(const Segment& other) const {
return Segment{std::min(min, other.min), std::max(max, other.max)};
}
// This is the only operation that can fail, if the two intervals do not
// intersect.
WP2_NO_DISCARD
bool IntersectAndUpdate(const Segment& other) {
if (other.min > max || other.max < min) return false;
min = std::max(min, other.min);
max = std::min(max, other.max);
return true;
}
int32_t min, max;
};
} // namespace WP2L
namespace WP2 {
template <>
inline WP2L::Segment RightShiftRound<WP2L::Segment>(WP2L::Segment v,
uint32_t shift) {
return {RightShiftRound(v.min, shift), RightShiftRound(v.max, shift)};
}
} // namespace WP2
namespace WP2L {
WP2Status GetARGBRanges(
const std::array<TransformHeader, kPossibleTransformCombinationSize>&
headers,
WP2SampleFormat format, std::array<int32_t, 4>& minima_range,
std::array<int32_t, 4>& maxima_range, uint32_t num_transforms) {
Segment segments[4];
// Define the original range according to the pixel format.
const int32_t num_bits_alpha = ::WP2::kAlphaBits;
const int32_t num_bits_non_alpha = static_cast<int32_t>(WP2Formatbpc(format));
for (uint32_t c = 0; c < 4; ++c) {
segments[c] = {0, static_cast<int32_t>(WP2::FormatMax(format, c))};
}
assert(segments[0].max == WP2::kAlphaMax); // WP2_A***_64 is unsupported.
bool do_clamp = true;
for (uint32_t i = 0; i < num_transforms; ++i) {
const TransformHeader& header = headers[i];
if (header.type == TransformType::kNum) break;
switch (header.type) {
case TransformType::kPredictor:
case TransformType::kPredictorWSub:
// The predictors are convex or clamped in the current bounds. Only the
// black predictor can give results out of those bounds.
// The ranges are updated to be the ranges of the residuals.
for (uint32_t c = 0; c < 4; ++c) {
segments[c] =
(segments[c] + segments[c]) | (segments[c] - segments[c]);
// For alpha and a value of 0, because of the predictor
// (kAlphaMax,0,0,0), the residual can be -WP2::kAlphaMax.
if (c == 0) {
segments[0].min = std::min(segments[0].min,
-static_cast<int32_t>(WP2::kAlphaMax));
}
if (do_clamp) {
const uint32_t num_bits =
(c == 0) ? num_bits_alpha : num_bits_non_alpha;
const int32_t abs_value_max =
(num_bits == 8) ? kMaxAbsResidual8 : kMaxAbsResidual10;
static_assert(2 * kMaxAbsResidual8 + 1 <= ::WP2::kANSMaxRange,
"invalid kMaxAbsResidual8 value");
static_assert(2 * kMaxAbsResidual10 + 1 <= ::WP2::kANSMaxRange,
"invalid kMaxAbsResidual10 value");
if (!segments[c].IntersectAndUpdate(
Segment{-abs_value_max, abs_value_max})) {
return WP2_STATUS_INVALID_PARAMETER;
}
if (segments[c].min > segments[c].max) {
// Should not happen.
assert(false);
return WP2_STATUS_INVALID_PARAMETER;
}
}
}
break;
case TransformType::kCrossColor: {
auto update_min_max = [&segments](uint32_t c0, uint32_t c1,
int32_t coeff) {
// The color transforms adds rounded(channel * coeff >>
// kColorTransformBits). Extend the interval with all possible deltas.
const Segment delta_pos =
WP2::RightShiftRound(segments[c1] * coeff, kColorTransformBits);
const Segment delta_neg =
WP2::RightShiftRound(segments[c1] * -coeff, kColorTransformBits);
segments[c0] =
(segments[c0] + delta_pos) | (segments[c0] - delta_pos) |
(segments[c0] + delta_neg) | (segments[c0] - delta_neg);
};
// First, red is modified by green.
update_min_max(/*c0=*/1, /*c1=*/2, kGreenToRedMax);
// Then blue by green and red.
update_min_max(/*c0=*/3, /*c1=*/2, kGreenToBlueMax);
update_min_max(/*c0=*/3, /*c1=*/1, kRedToBlueMax);
break;
}
case TransformType::kCrossColorGlobal: {
const uint32_t y_index = header.cc_global_y_index;
const uint32_t uv_index = header.cc_global_uv_index;
Segment r = segments[1], g = segments[2], b = segments[3];
int16_t alpha1, alpha2;
if (y_index == 1) {
alpha1 = alpha2 = 0;
} else if (y_index == 2) {
alpha1 = 4;
alpha2 = 0;
} else if (y_index == 3) {
alpha1 = 0;
alpha2 = 4;
} else if (y_index == 4) {
alpha1 = 2;
alpha2 = 0;
} else if (y_index == 5) {
alpha1 = 0;
alpha2 = 2;
} else if (y_index == 6) {
alpha1 = 2;
alpha2 = 2;
} else if (y_index == 7) {
alpha1 = alpha2 = 1;
} else if (y_index == 8) {
alpha1 = 2;
alpha2 = 1;
} else {
alpha1 = 1;
alpha2 = 2;
}
const int16_t eps = uv_index <= 3 ? 0 : uv_index <= 9 ? 1 : 2;
if (uv_index == 2 || uv_index == 5) {
std::swap(r, g);
} else if (uv_index == 3 || uv_index == 6 || uv_index == 11) {
std::swap(b, g);
} else if (uv_index == 7 || uv_index == 12) {
std::swap(r, b);
} else if (uv_index == 8) {
std::swap(r, b);
std::swap(g, r);
} else if (uv_index == 9) {
std::swap(r, g);
std::swap(b, r);
}
const Segment v = r - g;
const Segment uu = b - g;
const Segment u = uu - ((v * eps) >> 2);
const Segment y = g + ((v * alpha1 + uu * alpha2) >> 2);
segments[1] = y;
segments[2] = u;
segments[3] = v;
break;
}
case TransformType::kSubtractGreen:
// green is subtracted from red and blue
segments[1] = segments[1] - segments[2];
segments[3] = segments[3] - segments[2];
break;
case TransformType::kColorIndexing:
assert(i == 0);
// With color indexing, values can go up to the number of colors so do
// not clamp anymore after that.
do_clamp = false;
// A is set to its maximum value.
segments[0] = {::WP2::kAlphaMax, ::WP2::kAlphaMax};
// R and B are set to 0.
segments[1] = segments[3] = {0, 0};
// Only the color index in [0, num_colors-1] is stored in G.
segments[2] = {0, static_cast<int32_t>(header.indexing_num_colors) - 1};
break;
case TransformType::kNormalizeChannels: {
for (uint32_t c = 0; c < 4; ++c) {
segments[c] = {
0, static_cast<int32_t>(header.normalize_channels_max[c])};
}
break;
}
case TransformType::kNum:
assert(false);
}
}
// Make sure we fit in the ANS ranges [0,(1 << WP2::kANSMaxRangeBits)-1].
constexpr int32_t kMaxRange = 1 << WP2::kANSMaxRangeBits;
for (uint32_t c = 0; c < 4; ++c) {
// Assign to the output.
if (!segments[c].IntersectAndUpdate(
Segment{-kMaxRange / 2, kMaxRange / 2 - 1})) {
return WP2_STATUS_INVALID_PARAMETER;
}
minima_range[c] = segments[c].min;
maxima_range[c] = segments[c].max;
assert(minima_range[c] <= maxima_range[c]);
}
return WP2_STATUS_OK;
}
} // namespace WP2L