|
@@ -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;
|
|
|
}
|
|
|
}
|
|
|
|