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

View File

@ -1341,78 +1341,126 @@ void Image::crop(int p_width, int p_height) {
void Image::rotate_90(ClockDirection p_direction) { 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(!_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(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
ERR_FAIL_COND_MSG(height <= 1, "The Image height specified (" + itos(height) + " pixels) must be greater than 1 pixels."); ERR_FAIL_COND_MSG(height <= 0, "The Image height specified (" + itos(height) + " pixels) must be greater than 0 pixels.");
int saved_width = height;
int saved_height = width;
if (width != height) {
int n = MAX(width, height);
resize(n, n, INTERPOLATE_NEAREST);
}
bool used_mipmaps = has_mipmaps(); bool used_mipmaps = has_mipmaps();
if (used_mipmaps) { if (used_mipmaps) {
clear_mipmaps(); clear_mipmaps();
} }
// In-place 90 degrees rotation by following the permutation cycles.
{ {
uint8_t *w = data.ptrw(); // Explanation by example (clockwise):
uint8_t src[16]; //
uint8_t dst[16]; // 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); uint32_t pixel_size = get_format_pixel_size(format);
// Flip. uint8_t single_pixel_buffer[16];
if (p_direction == CLOCKWISE) { #define PREV_INDEX_IN_CYCLE(index) (p_direction == CLOCKWISE) ? ((h - 1 - (index % h)) * w + (index / h)) : ((index % h) * w + (w - 1 - (index / h)))
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);
_put_pixelb(x, height - y - 1, pixel_size, w, src); if (w == h) { // Square case, 4-length cycles only (plus irrelevant thus skipped 1-length cycle in the middle for odd-sized squares).
_put_pixelb(x, y, pixel_size, w, dst); 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 { } else { // Rectangular case (w != h), kinda unpredictable cycles.
for (int y = 0; y < height; y++) { int permuted_pixels_count = 0;
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);
_put_pixelb(width - x - 1, y, pixel_size, w, src); for (int i = 0; i < size; i++) {
_put_pixelb(x, y, pixel_size, w, dst); 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. #undef PREV_INDEX_IN_CYCLE
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);
}
}
}
} }
if (saved_width != saved_height) { if (used_mipmaps) {
resize(saved_width, saved_height, INTERPOLATE_NEAREST);
} else if (used_mipmaps) {
generate_mipmaps(); generate_mipmaps();
} }
} }
void Image::rotate_180() { void Image::rotate_180() {
ERR_FAIL_COND_MSG(!_can_modify(format), "Cannot rotate in compressed or custom image formats."); 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(width <= 0, "The Image width specified (" + itos(width) + " pixels) must be greater than 0 pixels.");
ERR_FAIL_COND_MSG(height <= 1, "The Image height specified (" + itos(height) + " pixels) must be greater than 1 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(); bool used_mipmaps = has_mipmaps();
if (used_mipmaps) { if (used_mipmaps) {
@ -1420,19 +1468,21 @@ void Image::rotate_180() {
} }
{ {
uint8_t *w = data.ptrw(); uint8_t *data_ptr = data.ptrw();
uint8_t src[16];
uint8_t dst[16];
uint32_t pixel_size = get_format_pixel_size(format); uint32_t pixel_size = get_format_pixel_size(format);
for (int y = 0; y < height / 2; y++) { uint8_t single_pixel_buffer[16];
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);
_put_pixelb(width - x - 1, height - y - 1, pixel_size, w, src); uint8_t *from_begin_ptr = data_ptr;
_put_pixelb(x, y, pixel_size, w, dst); 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;
} }
} }