`Image` Fix `rotate_90`/`rotate_180` methods

This commit is contained in:
kleonc 2022-08-11 18:45:50 +02:00
parent 11abffbf12
commit 100b83971f
1 changed files with 106 additions and 56 deletions

View File

@ -1341,78 +1341,126 @@ void Image::crop(int p_width, int p_height) {
void Image::rotate_90(ClockDirection p_direction) {
ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot rotate in compressed or custom image formats.");
ERR_FAIL_COND_MSG(width <= 1, "The Image width specified (" + itos(width) + " pixels) must be greater than 1 pixels.");
ERR_FAIL_COND_MSG(height <= 1, "The Image height specified (" + itos(height) + " pixels) must be greater than 1 pixels.");
int saved_width = height;
int saved_height = width;
if (width != height) {
int n = MAX(width, height);
resize(n, n, INTERPOLATE_NEAREST);
}
ERR_FAIL_COND_MSG(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
ERR_FAIL_COND_MSG(height <= 0, "The Image height specified (" + itos(height) + " pixels) must be greater than 0 pixels.");
bool used_mipmaps = has_mipmaps();
if (used_mipmaps) {
clear_mipmaps();
}
// In-place 90 degrees rotation by following the permutation cycles.
{
uint8_t *w = data.ptrw();
uint8_t src[16];
uint8_t dst[16];
// Explanation by example (clockwise):
//
// abc da
// def -> eb
// fc
//
// In memory:
// 012345 012345
// abcdef -> daebfc
//
// Permutation cycles:
// (0 --a--> 1 --b--> 3 --d--> 0)
// (2 --c--> 5 --f--> 4 --e--> 2)
//
// Applying cycles (backwards):
// 0->s s=a (store)
// 3->0 abcdef -> dbcdef
// 1->3 dbcdef -> dbcbef
// s->1 dbcbef -> dacbef
//
// 2->s s=c
// 4->2 dacbef -> daebef
// 5->4 daebef -> daebff
// s->5 daebff -> daebfc
const int w = width;
const int h = height;
const int size = w * h;
uint8_t *data_ptr = data.ptrw();
uint32_t pixel_size = get_format_pixel_size(format);
// Flip.
uint8_t single_pixel_buffer[16];
if (p_direction == CLOCKWISE) {
for (int y = 0; y < height / 2; y++) {
for (int x = 0; x < width; x++) {
_get_pixelb(x, y, pixel_size, w, src);
_get_pixelb(x, height - y - 1, pixel_size, w, dst);
#define PREV_INDEX_IN_CYCLE(index) (p_direction == CLOCKWISE) ? ((h - 1 - (index % h)) * w + (index / h)) : ((index % h) * w + (w - 1 - (index / h)))
_put_pixelb(x, height - y - 1, pixel_size, w, src);
_put_pixelb(x, y, pixel_size, w, dst);
if (w == h) { // Square case, 4-length cycles only (plus irrelevant thus skipped 1-length cycle in the middle for odd-sized squares).
for (int y = 0; y < h / 2; y++) {
for (int x = 0; x < (w + 1) / 2; x++) {
int current = y * w + x;
memcpy(single_pixel_buffer, data_ptr + current * pixel_size, pixel_size);
for (int i = 0; i < 3; i++) {
int prev = PREV_INDEX_IN_CYCLE(current);
memcpy(data_ptr + current * pixel_size, data_ptr + prev * pixel_size, pixel_size);
current = prev;
}
memcpy(data_ptr + current * pixel_size, single_pixel_buffer, pixel_size);
}
}
} else {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width / 2; x++) {
_get_pixelb(x, y, pixel_size, w, src);
_get_pixelb(width - x - 1, y, pixel_size, w, dst);
} else { // Rectangular case (w != h), kinda unpredictable cycles.
int permuted_pixels_count = 0;
_put_pixelb(width - x - 1, y, pixel_size, w, src);
_put_pixelb(x, y, pixel_size, w, dst);
for (int i = 0; i < size; i++) {
int prev = PREV_INDEX_IN_CYCLE(i);
if (prev == i) {
// 1-length cycle, pixel remains at the same index.
permuted_pixels_count++;
continue;
}
// Check whether we already processed this cycle.
// We iterate over it and if we'll find an index smaller than `i` then we already
// processed this cycle because we always start at the smallest index in the cycle.
// TODO: Improve this naive approach, can be done better.
while (prev > i) {
prev = PREV_INDEX_IN_CYCLE(prev);
}
if (prev < i) {
continue;
}
// Save the in-cycle pixel with the smallest index (`i`).
memcpy(single_pixel_buffer, data_ptr + i * pixel_size, pixel_size);
// Overwrite pixels one by one by the preceding pixel in the cycle.
int current = i;
prev = PREV_INDEX_IN_CYCLE(current);
while (prev != i) {
memcpy(data_ptr + current * pixel_size, data_ptr + prev * pixel_size, pixel_size);
permuted_pixels_count++;
current = prev;
prev = PREV_INDEX_IN_CYCLE(current);
};
// Overwrite the remaining pixel in the cycle by the saved pixel with the smallest index.
memcpy(data_ptr + current * pixel_size, single_pixel_buffer, pixel_size);
permuted_pixels_count++;
if (permuted_pixels_count == size) {
break;
}
}
width = h;
height = w;
}
// Transpose.
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (x < y) {
_get_pixelb(x, y, pixel_size, w, src);
_get_pixelb(y, x, pixel_size, w, dst);
_put_pixelb(y, x, pixel_size, w, src);
_put_pixelb(x, y, pixel_size, w, dst);
}
}
}
#undef PREV_INDEX_IN_CYCLE
}
if (saved_width != saved_height) {
resize(saved_width, saved_height, INTERPOLATE_NEAREST);
} else if (used_mipmaps) {
if (used_mipmaps) {
generate_mipmaps();
}
}
void Image::rotate_180() {
ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot rotate in compressed or custom image formats.");
ERR_FAIL_COND_MSG(width <= 1, "The Image width specified (" + itos(width) + " pixels) must be greater than 1 pixels.");
ERR_FAIL_COND_MSG(height <= 1, "The Image height specified (" + itos(height) + " pixels) must be greater than 1 pixels.");
ERR_FAIL_COND_MSG(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
ERR_FAIL_COND_MSG(height <= 0, "The Image height specified (" + itos(height) + " pixels) must be greater than 0 pixels.");
bool used_mipmaps = has_mipmaps();
if (used_mipmaps) {
@ -1420,19 +1468,21 @@ void Image::rotate_180() {
}
{
uint8_t *w = data.ptrw();
uint8_t src[16];
uint8_t dst[16];
uint8_t *data_ptr = data.ptrw();
uint32_t pixel_size = get_format_pixel_size(format);
for (int y = 0; y < height / 2; y++) {
for (int x = 0; x < width; x++) {
_get_pixelb(x, y, pixel_size, w, src);
_get_pixelb(width - x - 1, height - y - 1, pixel_size, w, dst);
uint8_t single_pixel_buffer[16];
_put_pixelb(width - x - 1, height - y - 1, pixel_size, w, src);
_put_pixelb(x, y, pixel_size, w, dst);
}
uint8_t *from_begin_ptr = data_ptr;
uint8_t *from_end_ptr = data_ptr + (width * height - 1) * pixel_size;
while (from_begin_ptr < from_end_ptr) {
memcpy(single_pixel_buffer, from_begin_ptr, pixel_size);
memcpy(from_begin_ptr, from_end_ptr, pixel_size);
memcpy(from_end_ptr, single_pixel_buffer, pixel_size);
from_begin_ptr += pixel_size;
from_end_ptr -= pixel_size;
}
}