libyuv/source/rotate.cc
Frank Barchard f72a48bf02 [libyuv] Optimize RotatePlane_16 SIMD and compact 32-bit transposes
Implement SIMD 16-bit plane rotation (TransposePlane_16,
RotatePlane90_16, RotatePlane180_16, RotatePlane270_16) used by
I010Rotate, I210Rotate, and I410Rotate, keeping tile sizes compact
with full-register writes and zero cross-lane shuffles:
- AVX2: 8x16 tile in ymm0..ymm15 with _Any tail (low-end x86 SIMD)
- AVX-512BW: 8x32 tile in zmm0..zmm15 with k1/k2 right/bottom masks
- NEON (ARM64/ARM32): 8x16 tile in v0..v23 / q0..q11 with _Any tail
- SME: 8xVL tile using ZA16 horizontal/vertical array slices
- RVV: 8xVL tile using widening unpacks with hardware vl tail
- WASM SIMD: 8x8 tile in v128 with _Any tail

Also update 32-bit ARGBRotate x86 transposes to compact vertical
in-lane tiles with full-register writes and zero cross-lane shuffles:
- TransposeWxH_32_AVX2: 4x8 tile in ymm0..ymm7 with vpmaskmovd tails
- TransposeWxH_32_AVX512BW: 4x16 tile in zmm0..zmm7 with k1/k2 tails

Add RotatePlane16_SplitMergeUV_Opt and RotatePlane16_SplitMergeUV_Odd
unit tests comparing 16-bit rotation against SplitUVPlane +
RotatePlane + MergeUVPlane across 0/90/180/270 degrees.

Performance on 1275x1650 (1000 iterations, before -> after):
CPU                     90 degree               180 degree
Coral RVV (Verilator)   7306 -> 1415c (5.16x)   2274 -> 719c (3.16x)
AMD Rome (Zen 2)        821 -> 215ms (3.82x)    259 -> 181ms (1.44x)
AMD Genoa (Zen 4)       483 -> 129ms (3.73x)    149 -> 111ms (1.34x)
AMD Turin (Zen 5)       321 -> 101ms (3.18x)    109 -> 78ms (1.40x)
AMD Milan (Zen 3)       505 -> 189ms (2.67x)    199 -> 118ms (1.69x)
Intel Skylake-X         1076 -> 447ms (2.41x)   353 -> 317ms (1.11x)
WASM SIMD (d8)          415 -> 203ms (2.04x)    370 -> 106ms (3.49x)
Intel Emerald Rapids    735 -> 422ms (1.74x)    238 -> 233ms (1.02x)
Intel Haswell           1120 -> 648ms (1.73x)   438 -> 393ms (1.11x)
Intel Ice Lake          774 -> 474ms (1.63x)    279 -> 290ms (0.96x)
ARM Neoverse-N1         864 -> 544ms (1.59x)    404 -> 266ms (1.52x)
Ampere Siryn            862 -> 576ms (1.50x)    568 -> 329ms (1.72x)
Google Axion (Maple)    554 -> 379ms (1.46x)    199 -> 142ms (1.41x)
Intel Sapphire Rapids   1126 -> 772ms (1.46x)   301 -> 288ms (1.04x)
Intel Granite Rapids    730 -> 565ms (1.29x)    280 -> 280ms (1.00x)
NVIDIA Grace            462 -> 365ms (1.26x)    192 -> 126ms (1.52x)
ARM Cortex-A53          28290 -> 24386ms (1.16x) 7178 -> 5075ms (1.41x)
Google Axion 2 (Tamar)  523 -> 453ms (1.15x)    217 -> 112ms (1.93x)

Test: libyuv_unittest --gtest_filter=*Rotate*
Bug: b/42280942
Change-Id: I91ba6cac22185b5d294fcb98344fdb8a23cc94ff
2026-10-12 03:27:41 -07:00

1419 lines
46 KiB
C++

/*
* Copyright 2011 The LibYuv Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "libyuv/rotate.h"
#include <assert.h>
#include <limits.h>
#include "libyuv/convert.h"
#include "libyuv/cpu_id.h"
#include "libyuv/planar_functions.h"
#include "libyuv/rotate_row.h"
#include "libyuv/row.h"
#ifdef __cplusplus
namespace libyuv {
extern "C" {
#endif
LIBYUV_API
void TransposePlane(const uint8_t* src,
int src_stride,
uint8_t* dst,
int dst_stride,
int width,
int height) {
int i = height;
#if defined(HAS_TRANSPOSEWXH_SME)
void (*TransposeWxH)(const uint8_t* src, int src_stride, uint8_t* dst,
int dst_stride, int width, int height) = NULL;
#endif
#if defined(HAS_TRANSPOSEWX16_AVX2) || defined(HAS_TRANSPOSEWX16_AVX512BW) || \
defined(HAS_TRANSPOSEWX16_WASMSIMD)
void (*TransposeWx16)(const uint8_t* src, int src_stride, uint8_t* dst,
int dst_stride, int width) = NULL;
#elif defined(HAS_TRANSPOSEWX16_LSX) || defined(HAS_TRANSPOSEWX16_NEON)
void (*TransposeWx16)(const uint8_t* src, int src_stride, uint8_t* dst,
int dst_stride, int width) = TransposeWx16_C;
#endif
#if !defined(HAS_TRANSPOSEWX16_LSX) && !defined(HAS_TRANSPOSEWX16_NEON)
void (*TransposeWx8)(const uint8_t* src, int src_stride, uint8_t* dst,
int dst_stride, int width) = TransposeWx8_C;
#endif
#if defined(HAS_TRANSPOSEWX8_NEON)
if (TestCpuFlag(kCpuHasNEON)) {
TransposeWx8 = TransposeWx8_Any_NEON;
if (IS_ALIGNED(width, 8)) {
TransposeWx8 = TransposeWx8_NEON;
}
}
#endif
#if defined(HAS_TRANSPOSEWX16_NEON)
if (TestCpuFlag(kCpuHasNEON)) {
TransposeWx16 = TransposeWx16_Any_NEON;
if (IS_ALIGNED(width, 16)) {
TransposeWx16 = TransposeWx16_NEON;
}
}
#endif
#if defined(HAS_TRANSPOSEWXH_SME)
if (TestCpuFlag(kCpuHasSME)) {
TransposeWxH = TransposeWxH_SME;
}
#endif
#if defined(HAS_TRANSPOSEWX8_SSSE3)
if (TestCpuFlag(kCpuHasSSSE3)) {
TransposeWx8 = TransposeWx8_Any_SSSE3;
if (IS_ALIGNED(width, 16)) {
TransposeWx8 = TransposeWx8_SSSE3;
}
}
#endif
#if defined(HAS_TRANSPOSEWX16_AVX2)
if (TestCpuFlag(kCpuHasAVX2)) {
TransposeWx16 = TransposeWx16_Any_AVX2;
if (IS_ALIGNED(width, 16)) {
TransposeWx16 = TransposeWx16_AVX2;
}
}
#endif
#if defined(HAS_TRANSPOSEWX16_AVX512BW)
if (TestCpuFlag(kCpuHasAVX512BW)) {
TransposeWx16 = TransposeWx16_AVX512BW;
}
#endif
#if defined(HAS_TRANSPOSEWX16_LSX)
if (TestCpuFlag(kCpuHasLSX)) {
TransposeWx16 = TransposeWx16_Any_LSX;
if (IS_ALIGNED(width, 16)) {
TransposeWx16 = TransposeWx16_LSX;
}
}
#endif
#if defined(HAS_TRANSPOSEWX8_WASMSIMD)
if (TestCpuFlag(kCpuHasWASMSIMD)) {
TransposeWx8 = TransposeWx8_Any_WASMSIMD;
if (IS_ALIGNED(width, 16)) {
TransposeWx8 = TransposeWx8_WASMSIMD;
}
}
#endif
#if defined(HAS_TRANSPOSEWX16_WASMSIMD)
if (TestCpuFlag(kCpuHasWASMSIMD)) {
TransposeWx16 = TransposeWx16_Any_WASMSIMD;
if (IS_ALIGNED(width, 16)) {
TransposeWx16 = TransposeWx16_WASMSIMD;
}
}
#endif
#if defined(HAS_TRANSPOSEWXH_SME)
if (TransposeWxH) {
TransposeWxH(src, src_stride, dst, dst_stride, width, height);
return;
}
#endif
#if defined(HAS_TRANSPOSEWX16_AVX2) || defined(HAS_TRANSPOSEWX16_AVX512BW) || \
defined(HAS_TRANSPOSEWX16_LSX) || defined(HAS_TRANSPOSEWX16_NEON) || \
defined(HAS_TRANSPOSEWX16_WASMSIMD)
// Work across the source in 16x16 tiles
if (TransposeWx16) {
while (i >= 16) {
TransposeWx16(src, src_stride, dst, dst_stride, width);
src += 16 * src_stride; // Go down 16 rows.
dst += 16; // Move over 16 columns.
i -= 16;
}
}
#endif
#if !defined(HAS_TRANSPOSEWX16_LSX) && !defined(HAS_TRANSPOSEWX16_NEON)
// Work across the source in 8x8 tiles
while (i >= 8) {
TransposeWx8(src, src_stride, dst, dst_stride, width);
src += 8 * src_stride; // Go down 8 rows.
dst += 8; // Move over 8 columns.
i -= 8;
}
#endif
if (i > 0) {
TransposeWxH_C(src, src_stride, dst, dst_stride, width, i);
}
}
LIBYUV_API
void RotatePlane90(const uint8_t* src,
int src_stride,
uint8_t* dst,
int dst_stride,
int width,
int height) {
// Rotate by 90 is a transpose with the source read
// from bottom to top. So set the source pointer to the end
// of the buffer and flip the sign of the source stride.
src += (ptrdiff_t)src_stride * (height - 1);
src_stride = -src_stride;
TransposePlane(src, src_stride, dst, dst_stride, width, height);
}
LIBYUV_API
void RotatePlane270(const uint8_t* src,
int src_stride,
uint8_t* dst,
int dst_stride,
int width,
int height) {
// Rotate by 270 is a transpose with the destination written
// from bottom to top. So set the destination pointer to the end
// of the buffer and flip the sign of the destination stride.
dst += (ptrdiff_t)dst_stride * (width - 1);
dst_stride = -dst_stride;
TransposePlane(src, src_stride, dst, dst_stride, width, height);
}
LIBYUV_API
void RotatePlane180(const uint8_t* src,
int src_stride,
uint8_t* dst,
int dst_stride,
int width,
int height) {
// Swap top and bottom row and mirror the content. Uses a temporary row.
align_buffer_64(row, width);
assert(row);
if (!row)
return;
const uint8_t* src_bot = src + (ptrdiff_t)src_stride * (height - 1);
uint8_t* dst_bot = dst + (ptrdiff_t)dst_stride * (height - 1);
int half_height = (height + 1) >> 1;
int y;
void (*MirrorRow)(const uint8_t* src, uint8_t* dst, int width) = MirrorRow_C;
void (*CopyRow)(const uint8_t* src, uint8_t* dst, int width) = CopyRow_C;
#if defined(HAS_MIRRORROW_NEON)
if (TestCpuFlag(kCpuHasNEON)) {
MirrorRow = MirrorRow_Any_NEON;
if (IS_ALIGNED(width, 32)) {
MirrorRow = MirrorRow_NEON;
}
}
#endif
#if defined(HAS_MIRRORROW_SSSE3)
if (TestCpuFlag(kCpuHasSSSE3)) {
MirrorRow = MirrorRow_Any_SSSE3;
if (IS_ALIGNED(width, 16)) {
MirrorRow = MirrorRow_SSSE3;
}
}
#endif
#if defined(HAS_MIRRORROW_AVX2)
if (TestCpuFlag(kCpuHasAVX2)) {
MirrorRow = MirrorRow_Any_AVX2;
if (IS_ALIGNED(width, 32)) {
MirrorRow = MirrorRow_AVX2;
}
}
#endif
#if defined(HAS_MIRRORROW_LSX)
if (TestCpuFlag(kCpuHasLSX)) {
MirrorRow = MirrorRow_Any_LSX;
if (IS_ALIGNED(width, 32)) {
MirrorRow = MirrorRow_LSX;
}
}
#endif
#if defined(HAS_MIRRORROW_LASX)
if (TestCpuFlag(kCpuHasLASX)) {
MirrorRow = MirrorRow_Any_LASX;
if (IS_ALIGNED(width, 64)) {
MirrorRow = MirrorRow_LASX;
}
}
#endif
#if defined(HAS_MIRRORROW_WASMSIMD)
if (TestCpuFlag(kCpuHasWASMSIMD)) {
MirrorRow = MirrorRow_Any_WASMSIMD;
if (IS_ALIGNED(width, 16)) {
MirrorRow = MirrorRow_WASMSIMD;
}
}
#endif
#if defined(HAS_COPYROW_SSE2)
if (TestCpuFlag(kCpuHasSSE2)) {
CopyRow = IS_ALIGNED(width, 32) ? CopyRow_SSE2 : CopyRow_Any_SSE2;
}
#endif
#if defined(HAS_COPYROW_AVX)
if (TestCpuFlag(kCpuHasAVX)) {
CopyRow = IS_ALIGNED(width, 64) ? CopyRow_AVX : CopyRow_Any_AVX;
}
#endif
#if defined(HAS_COPYROW_AVX512BW)
if (TestCpuFlag(kCpuHasAVX512BW)) {
CopyRow = IS_ALIGNED(width, 128) ? CopyRow_AVX512BW : CopyRow_Any_AVX512BW;
}
#endif
#if defined(HAS_COPYROW_ERMS)
if (TestCpuFlag(kCpuHasERMS)) {
CopyRow = CopyRow_ERMS;
}
#endif
#if defined(HAS_COPYROW_NEON)
if (TestCpuFlag(kCpuHasNEON)) {
CopyRow = IS_ALIGNED(width, 32) ? CopyRow_NEON : CopyRow_Any_NEON;
}
#endif
#if defined(HAS_COPYROW_SVE2)
if (TestCpuFlag(kCpuHasSVE2)) {
CopyRow = CopyRow_SVE2;
}
#endif
#if defined(HAS_COPYROW_SME)
if (TestCpuFlag(kCpuHasSME)) {
CopyRow = CopyRow_SME;
}
#endif
#if defined(HAS_COPYROW_RVV)
if (TestCpuFlag(kCpuHasRVV)) {
CopyRow = CopyRow_RVV;
}
#endif
#if defined(HAS_COPYROW_LSX)
if (TestCpuFlag(kCpuHasLSX)) {
CopyRow = IS_ALIGNED(width, 32) ? CopyRow_LSX : CopyRow_Any_LSX;
}
#endif
#if defined(HAS_COPYROW_LASX)
if (TestCpuFlag(kCpuHasLASX)) {
CopyRow = IS_ALIGNED(width, 64) ? CopyRow_LASX : CopyRow_Any_LASX;
}
#endif
// Odd height will harmlessly mirror the middle row twice.
for (y = 0; y < half_height; ++y) {
CopyRow(src, row, width); // Copy top row into buffer
MirrorRow(src_bot, dst, width); // Mirror bottom row into top row
MirrorRow(row, dst_bot, width); // Mirror buffer into bottom row
src += src_stride;
dst += dst_stride;
src_bot -= src_stride;
dst_bot -= dst_stride;
}
free_aligned_buffer_64(row);
}
LIBYUV_API
void SplitTransposeUV(const uint8_t* src,
int src_stride,
uint8_t* dst_a,
int dst_stride_a,
uint8_t* dst_b,
int dst_stride_b,
int width,
int height) {
int i = height;
#if defined(HAS_TRANSPOSEUVWXH_SME)
void (*TransposeUVWxH)(const uint8_t* src, int src_stride, uint8_t* dst_a,
int dst_stride_a, uint8_t* dst_b, int dst_stride_b,
int width, int height) = TransposeUVWxH_C;
#endif
#if defined(HAS_TRANSPOSEUVWX16_AVX2) || \
defined(HAS_TRANSPOSEUVWX16_AVX512BW) || \
defined(HAS_TRANSPOSEUVWX16_WASMSIMD)
void (*TransposeUVWx16)(const uint8_t* src, int src_stride, uint8_t* dst_a,
int dst_stride_a, uint8_t* dst_b, int dst_stride_b,
int width) = NULL;
#elif defined(HAS_TRANSPOSEUVWX16_LSX)
void (*TransposeUVWx16)(const uint8_t* src, int src_stride, uint8_t* dst_a,
int dst_stride_a, uint8_t* dst_b, int dst_stride_b,
int width) = TransposeUVWx16_C;
#endif
#if !defined(HAS_TRANSPOSEUVWX16_LSX)
void (*TransposeUVWx8)(const uint8_t* src, int src_stride, uint8_t* dst_a,
int dst_stride_a, uint8_t* dst_b, int dst_stride_b,
int width) = TransposeUVWx8_C;
#endif
#if defined(HAS_TRANSPOSEUVWX16_LSX)
if (TestCpuFlag(kCpuHasLSX)) {
TransposeUVWx16 = TransposeUVWx16_Any_LSX;
if (IS_ALIGNED(width, 8)) {
TransposeUVWx16 = TransposeUVWx16_LSX;
}
}
#endif
#if defined(HAS_TRANSPOSEUVWX8_NEON)
if (TestCpuFlag(kCpuHasNEON)) {
TransposeUVWx8 = TransposeUVWx8_Any_NEON;
if (IS_ALIGNED(width, 8)) {
TransposeUVWx8 = TransposeUVWx8_NEON;
}
}
#endif
#if defined(HAS_TRANSPOSEUVWXH_SME)
if (TestCpuFlag(kCpuHasSME)) {
TransposeUVWxH = TransposeUVWxH_SME;
}
#endif
#if defined(HAS_TRANSPOSEUVWX8_SSE2)
if (TestCpuFlag(kCpuHasSSE2)) {
TransposeUVWx8 = TransposeUVWx8_Any_SSE2;
if (IS_ALIGNED(width, 8)) {
TransposeUVWx8 = TransposeUVWx8_SSE2;
}
}
#endif
#if defined(HAS_TRANSPOSEUVWX16_AVX2)
if (TestCpuFlag(kCpuHasAVX2)) {
TransposeUVWx16 = TransposeUVWx16_Any_AVX2;
if (IS_ALIGNED(width, 8)) {
TransposeUVWx16 = TransposeUVWx16_AVX2;
}
}
#endif
#if defined(HAS_TRANSPOSEUVWX16_AVX512BW)
if (TestCpuFlag(kCpuHasAVX512BW)) {
TransposeUVWx16 = TransposeUVWx16_AVX512BW;
}
#endif
#if defined(HAS_TRANSPOSEUVWX8_WASMSIMD)
if (TestCpuFlag(kCpuHasWASMSIMD)) {
TransposeUVWx8 = TransposeUVWx8_Any_WASMSIMD;
if (IS_ALIGNED(width, 8)) {
TransposeUVWx8 = TransposeUVWx8_WASMSIMD;
}
}
#endif
#if defined(HAS_TRANSPOSEUVWX16_WASMSIMD)
if (TestCpuFlag(kCpuHasWASMSIMD)) {
TransposeUVWx16 = TransposeUVWx16_Any_WASMSIMD;
if (IS_ALIGNED(width, 8)) {
TransposeUVWx16 = TransposeUVWx16_WASMSIMD;
}
}
#endif
#if defined(HAS_TRANSPOSEUVWXH_SME)
if (TestCpuFlag(kCpuHasSME)) {
TransposeUVWxH(src, src_stride, dst_a, dst_stride_a, dst_b, dst_stride_b,
width, i);
return;
}
#endif
#if defined(HAS_TRANSPOSEUVWX16_AVX2) || \
defined(HAS_TRANSPOSEUVWX16_AVX512BW) || \
defined(HAS_TRANSPOSEUVWX16_LSX) || defined(HAS_TRANSPOSEUVWX16_WASMSIMD)
// Work through the source in 16x16 tiles.
if (TransposeUVWx16) {
while (i >= 16) {
TransposeUVWx16(src, src_stride, dst_a, dst_stride_a, dst_b, dst_stride_b,
width);
src += 16 * src_stride; // Go down 16 rows.
dst_a += 16; // Move over 16 columns.
dst_b += 16; // Move over 16 columns.
i -= 16;
}
}
#endif
#if !defined(HAS_TRANSPOSEUVWX16_LSX)
// Work through the source in 8x8 tiles.
while (i >= 8) {
TransposeUVWx8(src, src_stride, dst_a, dst_stride_a, dst_b, dst_stride_b,
width);
src += 8 * src_stride; // Go down 8 rows.
dst_a += 8; // Move over 8 columns.
dst_b += 8; // Move over 8 columns.
i -= 8;
}
#endif
if (i > 0) {
TransposeUVWxH_C(src, src_stride, dst_a, dst_stride_a, dst_b, dst_stride_b,
width, i);
}
}
LIBYUV_API
void SplitRotateUV90(const uint8_t* src,
int src_stride,
uint8_t* dst_a,
int dst_stride_a,
uint8_t* dst_b,
int dst_stride_b,
int width,
int height) {
src += (ptrdiff_t)src_stride * (height - 1);
src_stride = -src_stride;
SplitTransposeUV(src, src_stride, dst_a, dst_stride_a, dst_b, dst_stride_b,
width, height);
}
LIBYUV_API
void SplitRotateUV270(const uint8_t* src,
int src_stride,
uint8_t* dst_a,
int dst_stride_a,
uint8_t* dst_b,
int dst_stride_b,
int width,
int height) {
dst_a += dst_stride_a * (width - 1);
dst_b += dst_stride_b * (width - 1);
dst_stride_a = -dst_stride_a;
dst_stride_b = -dst_stride_b;
SplitTransposeUV(src, src_stride, dst_a, dst_stride_a, dst_b, dst_stride_b,
width, height);
}
// Rotate 180 is a horizontal and vertical flip.
LIBYUV_API
void SplitRotateUV180(const uint8_t* src,
int src_stride,
uint8_t* dst_a,
int dst_stride_a,
uint8_t* dst_b,
int dst_stride_b,
int width,
int height) {
int i;
void (*MirrorSplitUVRow)(const uint8_t* src, uint8_t* dst_u, uint8_t* dst_v,
int width) = MirrorSplitUVRow_C;
#if defined(HAS_MIRRORSPLITUVROW_NEON)
if (TestCpuFlag(kCpuHasNEON) && IS_ALIGNED(width, 16)) {
MirrorSplitUVRow = MirrorSplitUVRow_NEON;
}
#endif
#if defined(HAS_MIRRORSPLITUVROW_AVX2)
if (TestCpuFlag(kCpuHasAVX2) && IS_ALIGNED(width, 32)) {
MirrorSplitUVRow = MirrorSplitUVRow_AVX2;
}
#endif
#if defined(HAS_MIRRORSPLITUVROW_AVX512BW)
if (TestCpuFlag(kCpuHasAVX512BW) && IS_ALIGNED(width, 32)) {
MirrorSplitUVRow = MirrorSplitUVRow_AVX512BW;
}
#endif
#if defined(HAS_MIRRORSPLITUVROW_LSX)
if (TestCpuFlag(kCpuHasLSX) && IS_ALIGNED(width, 32)) {
MirrorSplitUVRow = MirrorSplitUVRow_LSX;
}
#endif
#if defined(HAS_MIRRORSPLITUVROW_WASMSIMD)
if (TestCpuFlag(kCpuHasWASMSIMD) && IS_ALIGNED(width, 16)) {
MirrorSplitUVRow = MirrorSplitUVRow_WASMSIMD;
}
#endif
dst_a += dst_stride_a * (height - 1);
dst_b += dst_stride_b * (height - 1);
for (i = 0; i < height; ++i) {
MirrorSplitUVRow(src, dst_a, dst_b, width);
src += src_stride;
dst_a -= dst_stride_a;
dst_b -= dst_stride_b;
}
}
// Rotate UV and split into planar.
// width and height expected to be half size for NV12
LIBYUV_API
int SplitRotateUV(const uint8_t* src_uv,
int src_stride_uv,
uint8_t* dst_u,
int dst_stride_u,
uint8_t* dst_v,
int dst_stride_v,
int width,
int height,
enum RotationMode mode) {
if (!src_uv || width <= 0 || height == 0 || height == INT_MIN || !dst_u ||
!dst_v) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
src_uv = src_uv + (ptrdiff_t)(height - 1) * src_stride_uv;
src_stride_uv = -src_stride_uv;
}
switch (mode) {
case kRotate0:
SplitUVPlane(src_uv, src_stride_uv, dst_u, dst_stride_u, dst_v,
dst_stride_v, width, height);
return 0;
case kRotate90:
SplitRotateUV90(src_uv, src_stride_uv, dst_u, dst_stride_u, dst_v,
dst_stride_v, width, height);
return 0;
case kRotate270:
SplitRotateUV270(src_uv, src_stride_uv, dst_u, dst_stride_u, dst_v,
dst_stride_v, width, height);
return 0;
case kRotate180:
SplitRotateUV180(src_uv, src_stride_uv, dst_u, dst_stride_u, dst_v,
dst_stride_v, width, height);
return 0;
default:
break;
}
return -1;
}
LIBYUV_API
int RotatePlane(const uint8_t* src,
int src_stride,
uint8_t* dst,
int dst_stride,
int width,
int height,
enum RotationMode mode) {
if (!src || width <= 0 || height == 0 || height == INT_MIN || !dst) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
src = src + (ptrdiff_t)(height - 1) * src_stride;
src_stride = -src_stride;
}
switch (mode) {
case kRotate0:
// copy frame
CopyPlane(src, src_stride, dst, dst_stride, width, height);
return 0;
case kRotate90:
RotatePlane90(src, src_stride, dst, dst_stride, width, height);
return 0;
case kRotate270:
RotatePlane270(src, src_stride, dst, dst_stride, width, height);
return 0;
case kRotate180:
RotatePlane180(src, src_stride, dst, dst_stride, width, height);
return 0;
default:
break;
}
return -1;
}
static void TransposePlane_16(const uint16_t* src,
int src_stride,
uint16_t* dst,
int dst_stride,
int width,
int height) {
int i = height;
#if defined(HAS_TRANSPOSEWXH_16_SME) || defined(HAS_TRANSPOSEWXH_16_RVV) || \
defined(HAS_TRANSPOSEWXH_16_AVX512BW)
void (*TransposeWxH_16)(const uint16_t* src, int src_stride, uint16_t* dst,
int dst_stride, int width, int height) = NULL;
#endif
#if defined(HAS_TRANSPOSEWX16_16_AVX2) || defined(HAS_TRANSPOSEWX16_16_LASX)
void (*TransposeWx16_16)(const uint16_t* src, int src_stride, uint16_t* dst,
int dst_stride, int width) = NULL;
#endif
void (*TransposeWx8_16)(const uint16_t* src, int src_stride, uint16_t* dst,
int dst_stride, int width) = TransposeWx8_16_C;
#if defined(HAS_TRANSPOSEWX8_16_NEON)
if (TestCpuFlag(kCpuHasNEON)) {
TransposeWx8_16 = TransposeWx8_16_Any_NEON;
if (IS_ALIGNED(width, 8)) {
TransposeWx8_16 = TransposeWx8_16_NEON;
}
}
#endif
#if defined(HAS_TRANSPOSEWX8_16_SVE2)
if (TestCpuFlag(kCpuHasSVE2)) {
TransposeWx8_16 = TransposeWx8_16_SVE2;
}
#endif
#if defined(HAS_TRANSPOSEWXH_16_SME)
if (TestCpuFlag(kCpuHasSME)) {
TransposeWxH_16 = TransposeWxH_16_SME;
}
#endif
#if defined(HAS_TRANSPOSEWXH_16_RVV)
if (TestCpuFlag(kCpuHasRVV)) {
TransposeWxH_16 = TransposeWxH_16_RVV;
}
#endif
#if defined(HAS_TRANSPOSEWX16_16_AVX2)
if (TestCpuFlag(kCpuHasAVX2)) {
TransposeWx16_16 = TransposeWx16_16_Any_AVX2;
if (IS_ALIGNED(width, 8)) {
TransposeWx16_16 = TransposeWx16_16_AVX2;
}
}
#endif
#if defined(HAS_TRANSPOSEWXH_16_AVX512BW)
if (TestCpuFlag(kCpuHasAVX512BW)) {
TransposeWxH_16 = TransposeWxH_16_AVX512BW;
}
#endif
#if defined(HAS_TRANSPOSEWX8_16_LSX)
if (TestCpuFlag(kCpuHasLSX)) {
TransposeWx8_16 = TransposeWx8_16_Any_LSX;
if (IS_ALIGNED(width, 8)) {
TransposeWx8_16 = TransposeWx8_16_LSX;
}
}
#endif
#if defined(HAS_TRANSPOSEWX16_16_LASX)
if (TestCpuFlag(kCpuHasLASX)) {
TransposeWx16_16 = TransposeWx16_16_Any_LASX;
if (IS_ALIGNED(width, 8)) {
TransposeWx16_16 = TransposeWx16_16_LASX;
}
}
#endif
#if defined(HAS_TRANSPOSEWX8_16_WASMSIMD)
if (TestCpuFlag(kCpuHasWASMSIMD)) {
TransposeWx8_16 = TransposeWx8_16_Any_WASMSIMD;
if (IS_ALIGNED(width, 8)) {
TransposeWx8_16 = TransposeWx8_16_WASMSIMD;
}
}
#endif
#if defined(HAS_TRANSPOSEWXH_16_SME) || defined(HAS_TRANSPOSEWXH_16_RVV) || \
defined(HAS_TRANSPOSEWXH_16_AVX512BW)
if (TransposeWxH_16) {
TransposeWxH_16(src, src_stride, dst, dst_stride, width, height);
return;
}
#endif
#if defined(HAS_TRANSPOSEWX16_16_AVX2) || defined(HAS_TRANSPOSEWX16_16_LASX)
if (TransposeWx16_16) {
while (i >= 16) {
TransposeWx16_16(src, src_stride, dst, dst_stride, width);
src += 16 * (ptrdiff_t)src_stride;
dst += 16;
i -= 16;
}
}
#endif
// Work across the source in 8x8 tiles
while (i >= 8) {
TransposeWx8_16(src, src_stride, dst, dst_stride, width);
src += 8 * (ptrdiff_t)src_stride; // Go down 8 rows.
dst += 8; // Move over 8 columns.
i -= 8;
}
if (i > 0) {
TransposeWxH_16_C(src, src_stride, dst, dst_stride, width, i);
}
}
static void RotatePlane90_16(const uint16_t* src,
int src_stride,
uint16_t* dst,
int dst_stride,
int width,
int height) {
// Rotate by 90 is a transpose with the source read
// from bottom to top. So set the source pointer to the end
// of the buffer and flip the sign of the source stride.
src += (ptrdiff_t)src_stride * (height - 1);
src_stride = -src_stride;
TransposePlane_16(src, src_stride, dst, dst_stride, width, height);
}
static void RotatePlane270_16(const uint16_t* src,
int src_stride,
uint16_t* dst,
int dst_stride,
int width,
int height) {
// Rotate by 270 is a transpose with the destination written
// from bottom to top. So set the destination pointer to the end
// of the buffer and flip the sign of the destination stride.
dst += (ptrdiff_t)dst_stride * (width - 1);
dst_stride = -dst_stride;
TransposePlane_16(src, src_stride, dst, dst_stride, width, height);
}
static void RotatePlane180_16(const uint16_t* src,
int src_stride,
uint16_t* dst,
int dst_stride,
int width,
int height) {
// Rotate by 180 is a vertical flip (negative height) and horizontal mirror.
MirrorUVPlane((const uint8_t*)src, src_stride * 2, (uint8_t*)dst,
dst_stride * 2, width, -height);
}
LIBYUV_API
int RotatePlane_16(const uint16_t* src,
int src_stride,
uint16_t* dst,
int dst_stride,
int width,
int height,
enum RotationMode mode) {
if (!src || width <= 0 || height == 0 || height == INT_MIN || !dst) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
src = src + (ptrdiff_t)(height - 1) * src_stride;
src_stride = -src_stride;
}
switch (mode) {
case kRotate0:
// copy frame
CopyPlane_16(src, src_stride, dst, dst_stride, width, height);
return 0;
case kRotate90:
RotatePlane90_16(src, src_stride, dst, dst_stride, width, height);
return 0;
case kRotate270:
RotatePlane270_16(src, src_stride, dst, dst_stride, width, height);
return 0;
case kRotate180:
RotatePlane180_16(src, src_stride, dst, dst_stride, width, height);
return 0;
default:
break;
}
return -1;
}
LIBYUV_API
int I420Rotate(const uint8_t* src_y,
int src_stride_y,
const uint8_t* src_u,
int src_stride_u,
const uint8_t* src_v,
int src_stride_v,
uint8_t* dst_y,
int dst_stride_y,
uint8_t* dst_u,
int dst_stride_u,
uint8_t* dst_v,
int dst_stride_v,
int width,
int height,
enum RotationMode mode) {
int halfwidth = (width + 1) >> 1;
int halfheight = (height + 1) >> 1;
if ((!src_y && dst_y) || !src_u || !src_v || width <= 0 || height == 0 ||
height == INT_MIN || !dst_y || !dst_u || !dst_v) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
halfheight = (height + 1) >> 1;
src_y = src_y + (ptrdiff_t)(height - 1) * src_stride_y;
src_u = src_u + (ptrdiff_t)(halfheight - 1) * src_stride_u;
src_v = src_v + (ptrdiff_t)(halfheight - 1) * src_stride_v;
src_stride_y = -src_stride_y;
src_stride_u = -src_stride_u;
src_stride_v = -src_stride_v;
}
switch (mode) {
case kRotate0:
// copy frame
return I420Copy(src_y, src_stride_y, src_u, src_stride_u, src_v,
src_stride_v, dst_y, dst_stride_y, dst_u, dst_stride_u,
dst_v, dst_stride_v, width, height);
case kRotate90:
RotatePlane90(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
RotatePlane90(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth,
halfheight);
RotatePlane90(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth,
halfheight);
return 0;
case kRotate270:
RotatePlane270(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
RotatePlane270(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth,
halfheight);
RotatePlane270(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth,
halfheight);
return 0;
case kRotate180:
RotatePlane180(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
RotatePlane180(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth,
halfheight);
RotatePlane180(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth,
halfheight);
return 0;
default:
break;
}
return -1;
}
// I422 has half width x full height UV planes, so rotate by 90 and 270
// require scaling to maintain 422 subsampling.
LIBYUV_API
int I422Rotate(const uint8_t* src_y,
int src_stride_y,
const uint8_t* src_u,
int src_stride_u,
const uint8_t* src_v,
int src_stride_v,
uint8_t* dst_y,
int dst_stride_y,
uint8_t* dst_u,
int dst_stride_u,
uint8_t* dst_v,
int dst_stride_v,
int width,
int height,
enum RotationMode mode) {
int halfwidth = (width + 1) >> 1;
int halfheight = (height + 1) >> 1;
int r;
if (!src_y || !src_u || !src_v || width <= 0 || height == 0 ||
height == INT_MIN || !dst_y || !dst_u || !dst_v) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
src_y = src_y + (ptrdiff_t)(height - 1) * src_stride_y;
src_u = src_u + (ptrdiff_t)(height - 1) * src_stride_u;
src_v = src_v + (ptrdiff_t)(height - 1) * src_stride_v;
src_stride_y = -src_stride_y;
src_stride_u = -src_stride_u;
src_stride_v = -src_stride_v;
}
switch (mode) {
case kRotate0:
// Copy frame
CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
CopyPlane(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth, height);
CopyPlane(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth, height);
return 0;
// Note on temporary Y plane for UV.
// Rotation of UV first fits within the Y destination plane rows.
// Y plane is width x height
// Y plane rotated is height x width
// UV plane is (width / 2) x height
// UV plane rotated is height x (width / 2)
// UV plane rotated+scaled is (height / 2) x width.
// UV plane rotated is a temporary that fits within the Y plane rotated.
case kRotate90:
RotatePlane90(src_u, src_stride_u, dst_y, dst_stride_y, halfwidth,
height);
r = ScalePlane(dst_y, dst_stride_y, height, halfwidth, dst_u,
dst_stride_u, halfheight, width, kFilterBilinear);
if (r != 0) {
return r;
}
RotatePlane90(src_v, src_stride_v, dst_y, dst_stride_y, halfwidth,
height);
r = ScalePlane(dst_y, dst_stride_y, height, halfwidth, dst_v,
dst_stride_v, halfheight, width, kFilterLinear);
if (r != 0) {
return r;
}
RotatePlane90(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
return 0;
case kRotate270:
RotatePlane270(src_u, src_stride_u, dst_y, dst_stride_y, halfwidth,
height);
r = ScalePlane(dst_y, dst_stride_y, height, halfwidth, dst_u,
dst_stride_u, halfheight, width, kFilterBilinear);
if (r != 0) {
return r;
}
RotatePlane270(src_v, src_stride_v, dst_y, dst_stride_y, halfwidth,
height);
r = ScalePlane(dst_y, dst_stride_y, height, halfwidth, dst_v,
dst_stride_v, halfheight, width, kFilterLinear);
if (r != 0) {
return r;
}
RotatePlane270(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
return 0;
case kRotate180:
RotatePlane180(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
RotatePlane180(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth,
height);
RotatePlane180(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth,
height);
return 0;
default:
break;
}
return -1;
}
LIBYUV_API
int I444Rotate(const uint8_t* src_y,
int src_stride_y,
const uint8_t* src_u,
int src_stride_u,
const uint8_t* src_v,
int src_stride_v,
uint8_t* dst_y,
int dst_stride_y,
uint8_t* dst_u,
int dst_stride_u,
uint8_t* dst_v,
int dst_stride_v,
int width,
int height,
enum RotationMode mode) {
if (!src_y || !src_u || !src_v || width <= 0 || height == 0 ||
height == INT_MIN || !dst_y || !dst_u || !dst_v) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
src_y = src_y + (ptrdiff_t)(height - 1) * src_stride_y;
src_u = src_u + (ptrdiff_t)(height - 1) * src_stride_u;
src_v = src_v + (ptrdiff_t)(height - 1) * src_stride_v;
src_stride_y = -src_stride_y;
src_stride_u = -src_stride_u;
src_stride_v = -src_stride_v;
}
switch (mode) {
case kRotate0:
// copy frame
CopyPlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
CopyPlane(src_u, src_stride_u, dst_u, dst_stride_u, width, height);
CopyPlane(src_v, src_stride_v, dst_v, dst_stride_v, width, height);
return 0;
case kRotate90:
RotatePlane90(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
RotatePlane90(src_u, src_stride_u, dst_u, dst_stride_u, width, height);
RotatePlane90(src_v, src_stride_v, dst_v, dst_stride_v, width, height);
return 0;
case kRotate270:
RotatePlane270(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
RotatePlane270(src_u, src_stride_u, dst_u, dst_stride_u, width, height);
RotatePlane270(src_v, src_stride_v, dst_v, dst_stride_v, width, height);
return 0;
case kRotate180:
RotatePlane180(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
RotatePlane180(src_u, src_stride_u, dst_u, dst_stride_u, width, height);
RotatePlane180(src_v, src_stride_v, dst_v, dst_stride_v, width, height);
return 0;
default:
break;
}
return -1;
}
LIBYUV_API
int NV12ToI420Rotate(const uint8_t* src_y,
int src_stride_y,
const uint8_t* src_uv,
int src_stride_uv,
uint8_t* dst_y,
int dst_stride_y,
uint8_t* dst_u,
int dst_stride_u,
uint8_t* dst_v,
int dst_stride_v,
int width,
int height,
enum RotationMode mode) {
int halfwidth = (width + 1) >> 1;
int halfheight = (height + 1) >> 1;
if (!src_y || !src_uv || width <= 0 || height == 0 || height == INT_MIN ||
!dst_y || !dst_u || !dst_v) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
halfheight = (height + 1) >> 1;
src_y = src_y + (ptrdiff_t)(height - 1) * src_stride_y;
src_uv = src_uv + (ptrdiff_t)(halfheight - 1) * src_stride_uv;
src_stride_y = -src_stride_y;
src_stride_uv = -src_stride_uv;
}
switch (mode) {
case kRotate0:
// copy frame
return NV12ToI420(src_y, src_stride_y, src_uv, src_stride_uv, dst_y,
dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v,
width, height);
case kRotate90:
RotatePlane90(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
SplitRotateUV90(src_uv, src_stride_uv, dst_u, dst_stride_u, dst_v,
dst_stride_v, halfwidth, halfheight);
return 0;
case kRotate270:
RotatePlane270(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
SplitRotateUV270(src_uv, src_stride_uv, dst_u, dst_stride_u, dst_v,
dst_stride_v, halfwidth, halfheight);
return 0;
case kRotate180:
RotatePlane180(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
SplitRotateUV180(src_uv, src_stride_uv, dst_u, dst_stride_u, dst_v,
dst_stride_v, halfwidth, halfheight);
return 0;
default:
break;
}
return -1;
}
static void SplitPixels(const uint8_t* src_u,
int src_pixel_stride_uv,
uint8_t* dst_u,
int width) {
int i;
for (i = 0; i < width; ++i) {
*dst_u = *src_u;
++dst_u;
src_u += src_pixel_stride_uv;
}
}
// Convert Android420 to I420 with Rotate
LIBYUV_API
int Android420ToI420Rotate(const uint8_t* src_y,
int src_stride_y,
const uint8_t* src_u,
int src_stride_u,
const uint8_t* src_v,
int src_stride_v,
int src_pixel_stride_uv,
uint8_t* dst_y,
int dst_stride_y,
uint8_t* dst_u,
int dst_stride_u,
uint8_t* dst_v,
int dst_stride_v,
int width,
int height,
enum RotationMode rotation) {
int y;
const ptrdiff_t vu_off = src_v - src_u;
int halfwidth = (width + 1) >> 1;
int halfheight = (height + 1) >> 1;
if ((!src_y && dst_y) || !src_u || !src_v || !dst_u || !dst_v || width <= 0 ||
height == 0 || height == INT_MIN) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
halfheight = (height + 1) >> 1;
src_y = src_y + (ptrdiff_t)(height - 1) * src_stride_y;
src_u = src_u + (ptrdiff_t)(halfheight - 1) * src_stride_u;
src_v = src_v + (ptrdiff_t)(halfheight - 1) * src_stride_v;
src_stride_y = -src_stride_y;
src_stride_u = -src_stride_u;
src_stride_v = -src_stride_v;
}
if (dst_y) {
RotatePlane(src_y, src_stride_y, dst_y, dst_stride_y, width, height,
rotation);
}
// Copy UV planes - I420
if (src_pixel_stride_uv == 1) {
RotatePlane(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth, halfheight,
rotation);
RotatePlane(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth, halfheight,
rotation);
return 0;
}
// Split UV planes - NV21
if (src_pixel_stride_uv == 2 && vu_off == -1 &&
src_stride_u == src_stride_v) {
SplitRotateUV(src_v, src_stride_v, dst_v, dst_stride_v, dst_u, dst_stride_u,
halfwidth, halfheight, rotation);
return 0;
}
// Split UV planes - NV12
if (src_pixel_stride_uv == 2 && vu_off == 1 && src_stride_u == src_stride_v) {
SplitRotateUV(src_u, src_stride_u, dst_u, dst_stride_u, dst_v, dst_stride_v,
halfwidth, halfheight, rotation);
return 0;
}
if (rotation == 0) {
for (y = 0; y < halfheight; ++y) {
SplitPixels(src_u, src_pixel_stride_uv, dst_u, halfwidth);
SplitPixels(src_v, src_pixel_stride_uv, dst_v, halfwidth);
src_u += src_stride_u;
src_v += src_stride_v;
dst_u += dst_stride_u;
dst_v += dst_stride_v;
}
return 0;
}
// unsupported type and/or rotation.
return -1;
}
LIBYUV_API
int I010Rotate(const uint16_t* src_y,
int src_stride_y,
const uint16_t* src_u,
int src_stride_u,
const uint16_t* src_v,
int src_stride_v,
uint16_t* dst_y,
int dst_stride_y,
uint16_t* dst_u,
int dst_stride_u,
uint16_t* dst_v,
int dst_stride_v,
int width,
int height,
enum RotationMode mode) {
int halfwidth = (width + 1) >> 1;
int halfheight = (height + 1) >> 1;
if (!src_y || !src_u || !src_v || width <= 0 || height == 0 ||
height == INT_MIN || !dst_y || !dst_u || !dst_v || dst_stride_y < 0) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
src_y = src_y + (ptrdiff_t)(height - 1) * src_stride_y;
src_u = src_u + (ptrdiff_t)(height - 1) * src_stride_u;
src_v = src_v + (ptrdiff_t)(height - 1) * src_stride_v;
src_stride_y = -src_stride_y;
src_stride_u = -src_stride_u;
src_stride_v = -src_stride_v;
}
switch (mode) {
case kRotate0:
// copy frame
return I010Copy(src_y, src_stride_y, src_u, src_stride_u, src_v,
src_stride_v, dst_y, dst_stride_y, dst_u, dst_stride_u,
dst_v, dst_stride_v, width, height);
case kRotate90:
RotatePlane90_16(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
RotatePlane90_16(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth,
halfheight);
RotatePlane90_16(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth,
halfheight);
return 0;
case kRotate270:
RotatePlane270_16(src_y, src_stride_y, dst_y, dst_stride_y, width,
height);
RotatePlane270_16(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth,
halfheight);
RotatePlane270_16(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth,
halfheight);
return 0;
case kRotate180:
RotatePlane180_16(src_y, src_stride_y, dst_y, dst_stride_y, width,
height);
RotatePlane180_16(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth,
halfheight);
RotatePlane180_16(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth,
halfheight);
return 0;
default:
break;
}
return -1;
}
// I210 has half width x full height UV planes, so rotate by 90 and 270
// require scaling to maintain 422 subsampling.
LIBYUV_API
int I210Rotate(const uint16_t* src_y,
int src_stride_y,
const uint16_t* src_u,
int src_stride_u,
const uint16_t* src_v,
int src_stride_v,
uint16_t* dst_y,
int dst_stride_y,
uint16_t* dst_u,
int dst_stride_u,
uint16_t* dst_v,
int dst_stride_v,
int width,
int height,
enum RotationMode mode) {
int halfwidth = (width + 1) >> 1;
int halfheight = (height + 1) >> 1;
int r;
if (!src_y || !src_u || !src_v || width <= 0 || height == 0 ||
height == INT_MIN || !dst_y || !dst_u || !dst_v) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
src_y = src_y + (ptrdiff_t)(height - 1) * src_stride_y;
src_u = src_u + (ptrdiff_t)(height - 1) * src_stride_u;
src_v = src_v + (ptrdiff_t)(height - 1) * src_stride_v;
src_stride_y = -src_stride_y;
src_stride_u = -src_stride_u;
src_stride_v = -src_stride_v;
}
switch (mode) {
case kRotate0:
// Copy frame
CopyPlane_16(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
CopyPlane_16(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth, height);
CopyPlane_16(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth, height);
return 0;
// Note on temporary Y plane for UV.
// Rotation of UV first fits within the Y destination plane rows.
// Y plane is width x height
// Y plane rotated is height x width
// UV plane is (width / 2) x height
// UV plane rotated is height x (width / 2)
// UV plane rotated+scaled is (height / 2) x width.
// UV plane rotated is a temporary that fits within the Y plane rotated.
case kRotate90:
RotatePlane90_16(src_u, src_stride_u, dst_y, dst_stride_y, halfwidth,
height);
r = ScalePlane_16(dst_y, dst_stride_y, height, halfwidth, dst_u,
dst_stride_u, halfheight, width, kFilterBilinear);
if (r != 0) {
return r;
}
RotatePlane90_16(src_v, src_stride_v, dst_y, dst_stride_y, halfwidth,
height);
r = ScalePlane_16(dst_y, dst_stride_y, height, halfwidth, dst_v,
dst_stride_v, halfheight, width, kFilterLinear);
if (r != 0) {
return r;
}
RotatePlane90_16(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
return 0;
case kRotate270:
RotatePlane270_16(src_u, src_stride_u, dst_y, dst_stride_y, halfwidth,
height);
r = ScalePlane_16(dst_y, dst_stride_y, height, halfwidth, dst_u,
dst_stride_u, halfheight, width, kFilterBilinear);
if (r != 0) {
return r;
}
RotatePlane270_16(src_v, src_stride_v, dst_y, dst_stride_y, halfwidth,
height);
r = ScalePlane_16(dst_y, dst_stride_y, height, halfwidth, dst_v,
dst_stride_v, halfheight, width, kFilterLinear);
if (r != 0) {
return r;
}
RotatePlane270_16(src_y, src_stride_y, dst_y, dst_stride_y, width,
height);
return 0;
case kRotate180:
RotatePlane180_16(src_y, src_stride_y, dst_y, dst_stride_y, width,
height);
RotatePlane180_16(src_u, src_stride_u, dst_u, dst_stride_u, halfwidth,
height);
RotatePlane180_16(src_v, src_stride_v, dst_v, dst_stride_v, halfwidth,
height);
return 0;
default:
break;
}
return -1;
}
LIBYUV_API
int I410Rotate(const uint16_t* src_y,
int src_stride_y,
const uint16_t* src_u,
int src_stride_u,
const uint16_t* src_v,
int src_stride_v,
uint16_t* dst_y,
int dst_stride_y,
uint16_t* dst_u,
int dst_stride_u,
uint16_t* dst_v,
int dst_stride_v,
int width,
int height,
enum RotationMode mode) {
if (!src_y || !src_u || !src_v || width <= 0 || height == 0 ||
height == INT_MIN || !dst_y || !dst_u || !dst_v || dst_stride_y < 0) {
return -1;
}
// Negative height means invert the image.
if (height < 0) {
height = -height;
src_y = src_y + (ptrdiff_t)(height - 1) * src_stride_y;
src_u = src_u + (ptrdiff_t)(height - 1) * src_stride_u;
src_v = src_v + (ptrdiff_t)(height - 1) * src_stride_v;
src_stride_y = -src_stride_y;
src_stride_u = -src_stride_u;
src_stride_v = -src_stride_v;
}
switch (mode) {
case kRotate0:
// copy frame
CopyPlane_16(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
CopyPlane_16(src_u, src_stride_u, dst_u, dst_stride_u, width, height);
CopyPlane_16(src_v, src_stride_v, dst_v, dst_stride_v, width, height);
return 0;
case kRotate90:
RotatePlane90_16(src_y, src_stride_y, dst_y, dst_stride_y, width, height);
RotatePlane90_16(src_u, src_stride_u, dst_u, dst_stride_u, width, height);
RotatePlane90_16(src_v, src_stride_v, dst_v, dst_stride_v, width, height);
return 0;
case kRotate270:
RotatePlane270_16(src_y, src_stride_y, dst_y, dst_stride_y, width,
height);
RotatePlane270_16(src_u, src_stride_u, dst_u, dst_stride_u, width,
height);
RotatePlane270_16(src_v, src_stride_v, dst_v, dst_stride_v, width,
height);
return 0;
case kRotate180:
RotatePlane180_16(src_y, src_stride_y, dst_y, dst_stride_y, width,
height);
RotatePlane180_16(src_u, src_stride_u, dst_u, dst_stride_u, width,
height);
RotatePlane180_16(src_v, src_stride_v, dst_v, dst_stride_v, width,
height);
return 0;
default:
break;
}
return -1;
}
#ifdef __cplusplus
} // extern "C"
} // namespace libyuv
#endif