blob: 4b24479437859258cdfdb85c9b0a2f37b02ece2a [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.
// -----------------------------------------------------------------------------
//
// Orientation, rotation functions
//
// Author: Yannis Guyon (yguyon@google.com)
#include "src/utils/orientation.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <utility>
#include "src/dsp/dsp.h"
#include "src/utils/plane.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
#include "src/wp2/format_constants.h"
namespace WP2 {
//------------------------------------------------------------------------------
uint32_t OrientateWidth(Orientation orientation, uint32_t width,
uint32_t height) {
return (orientation == Orientation::k90Clockwise ||
orientation == Orientation::k270Clockwise ||
orientation == Orientation::kFlipBotLeftToTopRgt ||
orientation == Orientation::kFlipTopLeftToBotRgt)
? height
: width;
}
uint32_t OrientateHeight(Orientation orientation, uint32_t width,
uint32_t height) {
return (orientation == Orientation::k90Clockwise ||
orientation == Orientation::k270Clockwise ||
orientation == Orientation::kFlipBotLeftToTopRgt ||
orientation == Orientation::kFlipTopLeftToBotRgt)
? width
: height;
}
//------------------------------------------------------------------------------
uint32_t OrientatePointX(Orientation orientation, uint32_t old_width,
uint32_t old_height, uint32_t x, uint32_t y) {
assert(x < old_width && y < old_height);
if (orientation == Orientation::k90Clockwise ||
orientation == Orientation::kFlipTopLeftToBotRgt) {
return old_height - y - 1u;
} else if (orientation == Orientation::k180 ||
orientation == Orientation::kFlipLeftToRight) {
return old_width - x - 1u;
} else if (orientation == Orientation::k270Clockwise ||
orientation == Orientation::kFlipBotLeftToTopRgt) {
return y;
}
return x;
}
uint32_t OrientatePointY(Orientation orientation, uint32_t old_width,
uint32_t old_height, uint32_t x, uint32_t y) {
assert(x < old_width && y < old_height);
if (orientation == Orientation::k90Clockwise ||
orientation == Orientation::kFlipBotLeftToTopRgt) {
return x;
} else if (orientation == Orientation::k180 ||
orientation == Orientation::kFlipTopToBottom) {
return old_height - y - 1u;
} else if (orientation == Orientation::k270Clockwise ||
orientation == Orientation::kFlipTopLeftToBotRgt) {
return old_width - x - 1u;
}
return y;
}
//------------------------------------------------------------------------------
Rectangle OrientateRectangle(Orientation orientation, uint32_t old_width,
uint32_t old_height, const Rectangle& rect) {
assert(rect.x + rect.width <= old_width &&
rect.y + rect.height <= old_height);
Rectangle rotated_rect(
OrientatePointX(orientation, old_width, old_height, rect.x, rect.y),
OrientatePointY(orientation, old_width, old_height, rect.x, rect.y),
OrientateWidth(orientation, rect.width, rect.height),
OrientateHeight(orientation, rect.width, rect.height));
// x, y represents top left, not any corner.
if (orientation == Orientation::k90Clockwise ||
orientation == Orientation::k180 ||
orientation == Orientation::kFlipLeftToRight ||
orientation == Orientation::kFlipTopLeftToBotRgt) {
if (rotated_rect.width > 1) rotated_rect.x -= rotated_rect.width - 1;
}
if (orientation == Orientation::k180 ||
orientation == Orientation::k270Clockwise ||
orientation == Orientation::kFlipTopToBottom ||
orientation == Orientation::kFlipTopLeftToBotRgt) {
if (rotated_rect.height > 1) rotated_rect.y -= rotated_rect.height - 1;
}
return rotated_rect;
}
//------------------------------------------------------------------------------
WP2Status OrientateBuffer(Orientation orientation, ArgbBuffer* const buffer) {
WP2_CHECK_OK(buffer != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!buffer->IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
if (orientation == Orientation::k90Clockwise ||
orientation == Orientation::k270Clockwise ||
orientation == Orientation::kFlipBotLeftToTopRgt ||
orientation == Orientation::kFlipTopLeftToBotRgt) {
// Note: Rotating or mirroring a non-square image in place is not trivial;
// using a temporary array instead.
// TODO(yguyon): rotate directly during decoding
ArgbBuffer tmp(buffer->format());
WP2_CHECK_STATUS(tmp.Resize(buffer->height(), buffer->width()));
WP2_CHECK_STATUS(OrientateSubBuffer(
orientation, *buffer, buffer->AsRect(), &tmp));
if (buffer->IsView()) {
WP2_CHECK_STATUS(buffer->CopyFrom(tmp));
} else {
*buffer = std::move(tmp);
}
} else if (orientation == Orientation::k180) {
// Swap first half of all pixels in scan-order with opposites.
const uint32_t pixel_depth = WP2FormatBpp(buffer->format());
uint8_t* row = buffer->GetRow8(0);
uint8_t* opposite_row = buffer->GetRow8(buffer->height() - 1) +
(buffer->width() - 1) * pixel_depth;
while (row < opposite_row) {
uint8_t* pixel = row;
uint8_t* opposite_pixel = opposite_row;
for (uint32_t x = 0; x < buffer->width() && pixel < opposite_pixel; ++x) {
for (uint32_t c = 0; c < pixel_depth; ++c) {
std::swap(pixel[c], opposite_pixel[c]);
}
pixel += pixel_depth;
opposite_pixel -= pixel_depth;
}
row += buffer->stride();
opposite_row -= buffer->stride();
}
} else if (orientation == Orientation::kFlipLeftToRight) {
// Reverse each line.
const uint32_t pixel_depth = WP2FormatBpp(buffer->format());
uint8_t* row = buffer->GetRow8(0);
for (uint32_t y = 0; y < buffer->height(); ++y, row += buffer->stride()) {
uint8_t* pixel = row;
uint8_t* opposite_pixel = row + (buffer->width() - 1) * pixel_depth;
while (pixel < opposite_pixel) {
for (uint32_t c = 0; c < pixel_depth; ++c) {
std::swap(pixel[c], opposite_pixel[c]);
}
pixel += pixel_depth;
opposite_pixel -= pixel_depth;
}
}
} else if (orientation == Orientation::kFlipTopToBottom) {
// Swap each line with its opposite.
const uint32_t pixel_depth = WP2FormatBpp(buffer->format());
uint8_t* row = buffer->GetRow8(0);
uint8_t* opposite_row = buffer->GetRow8(buffer->height() - 1);
while (row < opposite_row) {
std::swap_ranges(row, row + buffer->width() * pixel_depth, opposite_row);
row += buffer->stride();
opposite_row -= buffer->stride();
}
}
return WP2_STATUS_OK;
}
WP2Status OrientateBuffer(Orientation orientation, const ArgbBuffer& src,
ArgbBuffer* const dst) {
WP2_CHECK_OK(dst != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!src.IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_STATUS(
dst->Resize(OrientateWidth(orientation, src.width(), src.height()),
OrientateHeight(orientation, src.width(), src.height())));
WP2_CHECK_STATUS(OrientateSubBuffer(orientation, src, src.AsRect(), dst));
return WP2_STATUS_OK;
}
WP2Status OrientateSubBuffer(Orientation orientation, const ArgbBuffer& src,
const Rectangle& rect_in_src,
ArgbBuffer* const dst) {
WP2_CHECK_OK(dst != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!src.IsEmpty() && !dst->IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(src.format() == dst->format() || src.format() == WP2_Argb_32 ||
src.format() == WP2_ARGB_32 ||
dst->format() == WP2_Argb_32 || dst->format() == WP2_ARGB_32,
WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK((rect_in_src.x + rect_in_src.width <= src.width()) &&
(rect_in_src.y + rect_in_src.height <= src.height()),
WP2_STATUS_INVALID_PARAMETER);
if (rect_in_src.width == 0 || rect_in_src.height == 0) return WP2_STATUS_OK;
const uint32_t dst_x = OrientatePointX(orientation, src.width(), src.height(),
rect_in_src.x, rect_in_src.y);
const uint32_t dst_y = OrientatePointY(orientation, src.width(), src.height(),
rect_in_src.x, rect_in_src.y);
const uint32_t dst_width =
OrientateWidth(orientation, rect_in_src.width, rect_in_src.height);
const uint32_t dst_height =
OrientateHeight(orientation, rect_in_src.width, rect_in_src.height);
WP2_CHECK_OK(dst_width <= dst->width(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(dst_height <= dst->height(), WP2_STATUS_INVALID_PARAMETER);
// Both 32- and 38-bit are possible for input or output.
const uint32_t src_pixel_depth = WP2FormatBpp(src.format());
const uint32_t dst_pixel_depth = WP2FormatBpp(dst->format());
const uint8_t* src_row = (const uint8_t*)src.GetRow(rect_in_src.y) +
rect_in_src.x * src_pixel_depth;
uint8_t* dst_line = (uint8_t*)dst->GetRow(dst_y) + dst_x * dst_pixel_depth;
int32_t dst_next_line;
int32_t dst_next_pixel;
if (orientation == Orientation::kOriginal) {
dst_next_line = (int32_t)dst->stride();
dst_next_pixel = (int32_t)dst_pixel_depth;
} else if (orientation == Orientation::k90Clockwise) {
dst_next_line = -(int32_t)dst_pixel_depth;
dst_next_pixel = (int32_t)dst->stride();
} else if (orientation == Orientation::k180) {
dst_next_line = -(int32_t)dst->stride();
dst_next_pixel = -(int32_t)dst_pixel_depth;
} else if (orientation == Orientation::k270Clockwise) {
dst_next_line = (int32_t)dst_pixel_depth;
dst_next_pixel = -(int32_t)dst->stride();
} else if (orientation == Orientation::kFlipLeftToRight) {
dst_next_line = (int32_t)dst->stride();
dst_next_pixel = -(int32_t)dst_pixel_depth;
} else if (orientation == Orientation::kFlipBotLeftToTopRgt) {
dst_next_line = (int32_t)dst_pixel_depth;
dst_next_pixel = (int32_t)dst->stride();
} else if (orientation == Orientation::kFlipTopToBottom) {
dst_next_line = -(int32_t)dst->stride();
dst_next_pixel = (int32_t)dst_pixel_depth;
} else {
assert(orientation == Orientation::kFlipTopLeftToBotRgt);
dst_next_line = -(int32_t)dst_pixel_depth;
dst_next_pixel = -(int32_t)dst->stride();
}
if (src.format() == dst->format()) {
for (uint32_t y = 0; y < rect_in_src.height; ++y) {
const uint8_t* src_pixel = src_row;
uint8_t* dst_pixel = dst_line;
for (uint32_t x = 0; x < rect_in_src.width; ++x) {
std::copy_n(src_pixel, src_pixel_depth, dst_pixel);
src_pixel += src_pixel_depth;
dst_pixel += dst_next_pixel;
}
src_row += src.stride();
dst_line += dst_next_line;
}
} else {
WP2ArgbConverterInit();
const WP2ArgbConverterF convert =
WP2ConversionFunction(src.format(), dst->format());
for (uint32_t y = 0; y < rect_in_src.height; ++y) {
const uint8_t* src_pixel = src_row;
uint8_t* dst_pixel = dst_line;
for (uint32_t x = 0; x < rect_in_src.width; ++x) {
// Not optimized but the orientation stuff should be removed eventually.
convert(src_pixel, /*width=*/1u, dst_pixel);
src_pixel += src_pixel_depth;
dst_pixel += dst_next_pixel;
}
src_row += src.stride();
dst_line += dst_next_line;
}
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status OrientateBuffer(Orientation orientation, Plane16* const buffer) {
WP2_CHECK_OK(buffer != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!buffer->IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
if (orientation == Orientation::k90Clockwise ||
orientation == Orientation::k270Clockwise ||
orientation == Orientation::kFlipBotLeftToTopRgt ||
orientation == Orientation::kFlipTopLeftToBotRgt) {
// Note: Rotating or mirroring a non-square image in place is not trivial;
// using a temporary array instead.
Plane16 tmp;
WP2_CHECK_STATUS(tmp.Resize(buffer->h_, buffer->w_));
WP2_CHECK_STATUS(OrientateSubBuffer(orientation, *buffer,
buffer->AsRect(), &tmp));
if (buffer->IsView()) {
WP2_CHECK_STATUS(buffer->Copy(tmp, /*resize_if_needed=*/false));
} else {
using std::swap;
swap(*buffer, tmp);
}
} else if (orientation == Orientation::k180) {
// Swap first half with opposites.
int16_t* row = &buffer->At(0, 0);
int16_t* opposite_row = &buffer->At(buffer->w_ - 1, buffer->h_ - 1);
while (row < opposite_row) {
int16_t* pixel = row;
int16_t* opposite_pixel = opposite_row;
for (uint32_t x = 0; x < buffer->w_ && pixel < opposite_pixel; ++x) {
std::swap(*pixel, *opposite_pixel);
pixel += 1;
opposite_pixel -= 1;
}
row += buffer->Step();
opposite_row -= buffer->Step();
}
} else if (orientation == Orientation::kFlipLeftToRight) {
// Reverse each line.
int16_t* row = buffer->Row(0);
for (uint32_t y = 0; y < buffer->h_; ++y, row += buffer->step_) {
std::reverse(row, row + buffer->w_);
}
} else if (orientation == Orientation::kFlipTopToBottom) {
// Swap each line with its opposite.
int16_t* row = buffer->Row(0);
int16_t* opposite_row = buffer->Row(buffer->h_ - 1);
while (row < opposite_row) {
std::swap_ranges(row, row + buffer->w_, opposite_row);
row += buffer->step_;
opposite_row -= buffer->step_;
}
}
return WP2_STATUS_OK;
}
WP2Status OrientateBuffer(Orientation orientation, const Plane16& src,
Plane16* const dst) {
WP2_CHECK_OK(dst != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!src.IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
const uint32_t dst_width = OrientateWidth(orientation, src.w_, src.h_);
const uint32_t dst_height = OrientateHeight(orientation, src.w_, src.h_);
if (!dst->IsView()) WP2_CHECK_STATUS(dst->Resize(dst_width, dst_height));
WP2_CHECK_STATUS(OrientateSubBuffer(orientation, src, src.AsRect(), dst));
return WP2_STATUS_OK;
}
WP2Status OrientateSubBuffer(Orientation orientation, const Plane16& src,
const Rectangle& rect_in_src, Plane16* const dst) {
WP2_CHECK_OK(dst != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!src.IsEmpty() && !dst->IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK((rect_in_src.x + rect_in_src.width <= src.w_) &&
(rect_in_src.y + rect_in_src.height <= src.h_),
WP2_STATUS_INVALID_PARAMETER);
if (rect_in_src.width == 0 || rect_in_src.height == 0) return WP2_STATUS_OK;
const uint32_t dst_x = OrientatePointX(orientation, src.w_, src.h_,
rect_in_src.x, rect_in_src.y);
const uint32_t dst_y = OrientatePointY(orientation, src.w_, src.h_,
rect_in_src.x, rect_in_src.y);
const uint32_t dst_width =
OrientateWidth(orientation, rect_in_src.width, rect_in_src.height);
const uint32_t dst_height =
OrientateHeight(orientation, rect_in_src.width, rect_in_src.height);
WP2_CHECK_OK(dst_width <= dst->w_, WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(dst_height <= dst->h_, WP2_STATUS_INVALID_PARAMETER);
const int16_t* src_row = &src.At(rect_in_src.x, rect_in_src.y);
int16_t* dst_line = &dst->At(dst_x, dst_y);
int32_t dst_next_line;
int32_t dst_next_pixel;
if (orientation == Orientation::kOriginal) {
dst_next_line = (int32_t)dst->Step();
dst_next_pixel = 1;
} else if (orientation == Orientation::k90Clockwise) {
dst_next_line = -1;
dst_next_pixel = (int32_t)dst->Step();
} else if (orientation == Orientation::k180) {
dst_next_line = -(int32_t)dst->Step();
dst_next_pixel = -1;
} else if (orientation == Orientation::k270Clockwise) {
dst_next_line = 1;
dst_next_pixel = -(int32_t)dst->Step();
} else if (orientation == Orientation::kFlipLeftToRight) {
dst_next_line = (int32_t)dst->Step();
dst_next_pixel = -1;
} else if (orientation == Orientation::kFlipBotLeftToTopRgt) {
dst_next_line = 1;
dst_next_pixel = (int32_t)dst->Step();
} else if (orientation == Orientation::kFlipTopToBottom) {
dst_next_line = -(int32_t)dst->Step();
dst_next_pixel = 1;
} else {
assert(orientation == Orientation::kFlipTopLeftToBotRgt);
dst_next_line = -1;
dst_next_pixel = -(int32_t)dst->Step();
}
for (uint32_t y = 0; y < rect_in_src.height; ++y) {
const int16_t* src_pixel = src_row;
int16_t* dst_pixel = dst_line;
for (uint32_t x = 0; x < rect_in_src.width; ++x) {
*dst_pixel = *src_pixel;
src_pixel += 1;
dst_pixel += dst_next_pixel;
}
src_row += src.Step();
dst_line += dst_next_line;
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
WP2Status OrientateBuffer(Orientation orientation, YUVPlane* const buffer) {
WP2_CHECK_OK(buffer != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(!buffer->IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (channel == kAChannel && buffer->GetChannel(channel).IsEmpty()) continue;
WP2_CHECK_STATUS(
OrientateBuffer(orientation, &buffer->GetChannel(channel)));
}
return WP2_STATUS_OK;
}
WP2Status OrientateBuffer(Orientation orientation, const YUVPlane& src,
YUVPlane* const dst) {
WP2_CHECK_OK(!src.IsEmpty(), WP2_STATUS_INVALID_PARAMETER);
WP2_CHECK_OK(dst != nullptr, WP2_STATUS_NULL_PARAMETER);
for (Channel channel : {kYChannel, kUChannel, kVChannel, kAChannel}) {
if (channel == kAChannel && src.GetChannel(channel).IsEmpty()) {
dst->GetChannel(channel).Clear();
continue;
}
WP2_CHECK_STATUS(OrientateBuffer(orientation, src.GetChannel(channel),
&dst->GetChannel(channel)));
}
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
Orientation GetInverseOrientation(Orientation orientation) {
static constexpr Orientation kInverseOrientation[8] = {
Orientation::kOriginal,
Orientation::kFlipLeftToRight,
Orientation::k180,
Orientation::kFlipTopToBottom,
Orientation::kFlipBotLeftToTopRgt,
Orientation::k270Clockwise,
Orientation::kFlipTopLeftToBotRgt,
Orientation::k90Clockwise
};
return kInverseOrientation[(uint32_t)orientation];
}
//------------------------------------------------------------------------------
} // namespace WP2