diff options
author | Rémi Verschelde <rverschelde@gmail.com> | 2022-11-22 21:18:58 +0100 |
---|---|---|
committer | Rémi Verschelde <rverschelde@gmail.com> | 2022-11-22 21:18:58 +0100 |
commit | 65a45eb7e3df0d23bb761d622d21d8a6465e29d0 (patch) | |
tree | 002c5c6195c4c330a32b87144c3336925a722843 /core/io | |
parent | 44edd5b8fdef6f1a2840ba434f19bfd2f59036a1 (diff) | |
parent | 100b83971fbb31f0761ce189d79c3497f314f8cc (diff) |
Merge pull request #64284 from kleonc/image-rotate90-in-place
`Image` Fix `rotate_90`/`rotate_180` methods
Diffstat (limited to 'core/io')
-rw-r--r-- | core/io/image.cpp | 156 |
1 files changed, 103 insertions, 53 deletions
diff --git a/core/io/image.cpp b/core/io/image.cpp index 65addaf964..21146dd80c 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -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; + + 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; + } - _put_pixelb(width - x - 1, y, pixel_size, w, src); - _put_pixelb(x, y, pixel_size, w, dst); + // 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; } - } - } - // Transpose. + // Save the in-cycle pixel with the smallest index (`i`). + memcpy(single_pixel_buffer, data_ptr + i * pixel_size, pixel_size); - 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); + // 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++; - _put_pixelb(y, x, pixel_size, w, src); - _put_pixelb(x, y, pixel_size, w, dst); + 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; } + +#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; } } |