Blender V4.5
image_undo.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
21
22#include "CLG_log.h"
23
24#include "MEM_guardedalloc.h"
25
26#include "BLI_listbase.h"
27#include "BLI_map.hh"
28#include "BLI_string.h"
29#include "BLI_threads.h"
30#include "BLI_utildefines.h"
31
32#include "DNA_image_types.h"
33#include "DNA_object_types.h"
34#include "DNA_screen_types.h"
35#include "DNA_space_types.h"
36#include "DNA_userdef_types.h"
38
39#include "IMB_imbuf.hh"
40#include "IMB_imbuf_types.hh"
41
42#include "BKE_context.hh"
43#include "BKE_image.hh"
44#include "BKE_paint.hh"
45#include "BKE_undo_system.hh"
46
47#include "DEG_depsgraph.hh"
48
49#include "ED_object.hh"
50#include "ED_paint.hh"
51#include "ED_undo.hh"
52#include "ED_util.hh"
53
54#include "WM_api.hh"
55
56static CLG_LogRef LOG = {"ed.image.undo"};
57
58/* -------------------------------------------------------------------- */
61
62/* This is a non-global static resource,
63 * Maybe it should be exposed as part of the
64 * paint operation, but for now just give a public interface */
66
71
76
78
79/* -------------------------------------------------------------------- */
88
94
99 /* Copied from iuser.tile in PaintTile. */
101
103 {
105 }
106 bool operator==(const PaintTileKey &other) const
107 {
108 return x_tile == other.x_tile && y_tile == other.y_tile && image == other.image &&
109 ibuf == other.ibuf && iuser_tile == other.iuser_tile;
110 }
111};
112
113struct PaintTile {
116 /* For 2D image painting the ImageUser uses most of the values.
117 * Even though views and passes are stored they are currently not supported for painting.
118 * For 3D projection painting this only uses a tile & frame number.
119 * The scene pointer must be cleared (or temporarily set it as needed, but leave cleared). */
121 union {
122 float *fp;
123 uint8_t *byte_ptr;
124 void *pt;
126 uint16_t *mask;
127 bool valid;
130};
131
132static void ptile_free(PaintTile *ptile)
133{
134 if (ptile->rect.pt) {
135 MEM_freeN(ptile->rect.pt);
136 }
137 if (ptile->mask) {
138 MEM_freeN(ptile->mask);
139 }
140 MEM_freeN(ptile);
141}
142
145
147 {
148 for (PaintTile *ptile : map.values()) {
149 ptile_free(ptile);
150 }
151 }
152};
153
154static void ptile_invalidate_map(PaintTileMap *paint_tile_map)
155{
156 for (PaintTile *ptile : paint_tile_map->map.values()) {
157 ptile->valid = false;
158 }
159}
160
162 Image *image,
163 ImBuf *ibuf,
164 ImageUser *iuser,
165 int x_tile,
166 int y_tile,
167 ushort **r_mask,
168 bool validate)
169{
170 PaintTileKey key;
171 key.ibuf = ibuf;
172 key.image = image;
173 key.iuser_tile = iuser->tile;
174 key.x_tile = x_tile;
175 key.y_tile = y_tile;
176 PaintTile **pptile = paint_tile_map->map.lookup_ptr(key);
177 if (pptile == nullptr) {
178 return nullptr;
179 }
180 PaintTile *ptile = *pptile;
181 if (r_mask) {
182 /* allocate mask if requested. */
183 if (!ptile->mask) {
185 "UndoImageTile.mask");
186 }
187 *r_mask = ptile->mask;
188 }
189 if (validate) {
190 ptile->valid = true;
191 }
192 return ptile->rect.pt;
193}
194
195/* Set the given buffer data as an owning data of the imbuf's buffer.
196 * Returns the data pointer which was stolen from the imbuf before assignment. */
197static uint8_t *image_undo_steal_and_assign_byte_buffer(ImBuf *ibuf, uint8_t *new_buffer_data)
198{
199 uint8_t *old_buffer_data = IMB_steal_byte_buffer(ibuf);
200 IMB_assign_byte_buffer(ibuf, new_buffer_data, IB_TAKE_OWNERSHIP);
201 return old_buffer_data;
202}
203static float *image_undo_steal_and_assign_float_buffer(ImBuf *ibuf, float *new_buffer_data)
204{
205 float *old_buffer_data = IMB_steal_float_buffer(ibuf);
206 IMB_assign_float_buffer(ibuf, new_buffer_data, IB_TAKE_OWNERSHIP);
207 return old_buffer_data;
208}
209
211 Image *image,
212 ImBuf *ibuf,
213 ImBuf **tmpibuf,
214 ImageUser *iuser,
215 int x_tile,
216 int y_tile,
217 ushort **r_mask,
218 bool **r_valid,
219 bool use_thread_lock,
220 bool find_prev)
221{
222 if (use_thread_lock) {
224 }
225 const bool has_float = (ibuf->float_buffer.data != nullptr);
226
227 /* check if tile is already pushed */
228
229 /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */
230 if (find_prev) {
232 paint_tile_map, image, ibuf, iuser, x_tile, y_tile, r_mask, true);
233 if (data) {
234 if (use_thread_lock) {
236 }
237 return data;
238 }
239 }
240
241 if (*tmpibuf == nullptr) {
242 *tmpibuf = imbuf_alloc_temp_tile();
243 }
244
245 PaintTile *ptile = MEM_callocN<PaintTile>("PaintTile");
246
247 ptile->image = image;
248 ptile->ibuf = ibuf;
249 ptile->iuser = *iuser;
250 ptile->iuser.scene = nullptr;
251
252 ptile->x_tile = x_tile;
253 ptile->y_tile = y_tile;
254
255 /* add mask explicitly here */
256 if (r_mask) {
258 "PaintTile.mask");
259 }
260
261 ptile->rect.pt = MEM_callocN((ibuf->float_buffer.data ? sizeof(float[4]) : sizeof(char[4])) *
263 "PaintTile.rect");
264
265 ptile->use_float = has_float;
266 ptile->valid = true;
267
268 if (r_valid) {
269 *r_valid = &ptile->valid;
270 }
271
272 IMB_rectcpy(*tmpibuf,
273 ibuf,
274 0,
275 0,
280
281 if (has_float) {
282 ptile->rect.fp = image_undo_steal_and_assign_float_buffer(*tmpibuf, ptile->rect.fp);
283 }
284 else {
286 }
287
288 PaintTileKey key = {};
289 key.ibuf = ibuf;
290 key.image = image;
291 key.iuser_tile = iuser->tile;
292 key.x_tile = x_tile;
293 key.y_tile = y_tile;
294 PaintTile *existing_tile = nullptr;
295 paint_tile_map->map.add_or_modify(
296 key,
297 [&](PaintTile **pptile) { *pptile = ptile; },
298 [&](PaintTile **pptile) { existing_tile = *pptile; });
299 if (existing_tile) {
300 ptile_free(ptile);
301 ptile = existing_tile;
302 }
303
304 if (use_thread_lock) {
306 }
307 return ptile->rect.pt;
308}
309
310static void ptile_restore_runtime_map(PaintTileMap *paint_tile_map)
311{
312 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
313
314 for (PaintTile *ptile : paint_tile_map->map.values()) {
315 Image *image = ptile->image;
316 ImBuf *ibuf = BKE_image_acquire_ibuf(image, &ptile->iuser, nullptr);
317 const bool has_float = (ibuf->float_buffer.data != nullptr);
318
319 if (has_float) {
320 ptile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, ptile->rect.fp);
321 }
322 else {
323 ptile->rect.byte_ptr = image_undo_steal_and_assign_byte_buffer(tmpibuf,
324 ptile->rect.byte_ptr);
325 }
326
327 /* TODO(sergey): Look into implementing API which does not require such temporary buffer
328 * assignment. */
329 IMB_rectcpy(ibuf,
330 tmpibuf,
331 ptile->x_tile * ED_IMAGE_UNDO_TILE_SIZE,
332 ptile->y_tile * ED_IMAGE_UNDO_TILE_SIZE,
333 0,
334 0,
337
338 if (has_float) {
339 ptile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, ptile->rect.fp);
340 }
341 else {
342 ptile->rect.byte_ptr = image_undo_steal_and_assign_byte_buffer(tmpibuf,
343 ptile->rect.byte_ptr);
344 }
345
346 /* Force OpenGL reload (maybe partial update will operate better?) */
348
349 if (ibuf->float_buffer.data) {
350 ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */
351 }
352 if (ibuf->mipmap[0]) {
353 ibuf->userflags |= IB_MIPMAP_INVALID; /* Force MIP-MAP recreation. */
354 }
356
357 BKE_image_release_ibuf(image, ibuf, nullptr);
358 }
359
360 IMB_freeImBuf(tmpibuf);
361}
362
364
365/* -------------------------------------------------------------------- */
368
369static uint32_t index_from_xy(uint32_t tile_x, uint32_t tile_y, const uint32_t tiles_dims[2])
370{
371 BLI_assert(tile_x < tiles_dims[0] && tile_y < tiles_dims[1]);
372 return (tile_y * tiles_dims[0]) + tile_x;
373}
374
376 union {
377 float *fp;
378 uint8_t *byte_ptr;
379 void *pt;
381 int users;
382};
383
384static UndoImageTile *utile_alloc(bool has_float)
385{
386 UndoImageTile *utile = static_cast<UndoImageTile *>(
387 MEM_callocN(sizeof(*utile), "ImageUndoTile"));
388 if (has_float) {
389 utile->rect.fp = static_cast<float *>(
390 MEM_mallocN(sizeof(float[4]) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__));
391 }
392 else {
393 utile->rect.byte_ptr = static_cast<uint8_t *>(
394 MEM_mallocN(sizeof(uint32_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__));
395 }
396 return utile;
397}
398
400 UndoImageTile *utile, const uint32_t x, const uint32_t y, const ImBuf *ibuf, ImBuf *tmpibuf)
401{
402 const bool has_float = ibuf->float_buffer.data;
403
404 if (has_float) {
405 utile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, utile->rect.fp);
406 }
407 else {
409 }
410
411 /* TODO(sergey): Look into implementing API which does not require such temporary buffer
412 * assignment. */
414
415 if (has_float) {
416 utile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, utile->rect.fp);
417 }
418 else {
420 }
421}
422
423static void utile_restore(
424 const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf)
425{
426 const bool has_float = ibuf->float_buffer.data;
427 float *prev_rect_float = tmpibuf->float_buffer.data;
428 uint8_t *prev_rect = tmpibuf->byte_buffer.data;
429
430 if (has_float) {
431 tmpibuf->float_buffer.data = utile->rect.fp;
432 }
433 else {
434 tmpibuf->byte_buffer.data = utile->rect.byte_ptr;
435 }
436
437 /* TODO(sergey): Look into implementing API which does not require such temporary buffer
438 * assignment. */
440
441 tmpibuf->float_buffer.data = prev_rect_float;
442 tmpibuf->byte_buffer.data = prev_rect;
443}
444
445static void utile_decref(UndoImageTile *utile)
446{
447 utile->users -= 1;
448 BLI_assert(utile->users >= 0);
449 if (utile->users == 0) {
450 MEM_freeN(utile->rect.pt);
451 MEM_delete(utile);
452 }
453}
454
456
457/* -------------------------------------------------------------------- */
460
463
468
471
473
475 uint32_t tiles_len;
476 uint32_t tiles_dims[2];
477
478 uint32_t image_dims[2];
479
481 struct {
482 short source;
485};
486
488{
489 UndoImageBuf *ubuf = MEM_callocN<UndoImageBuf>(__func__);
490
491 ubuf->image_dims[0] = ibuf->x;
492 ubuf->image_dims[1] = ibuf->y;
493
496
497 ubuf->tiles_len = ubuf->tiles_dims[0] * ubuf->tiles_dims[1];
498 ubuf->tiles = static_cast<UndoImageTile **>(
499 MEM_callocN(sizeof(*ubuf->tiles) * ubuf->tiles_len, __func__));
500
501 STRNCPY(ubuf->ibuf_filepath, ibuf->filepath);
502 ubuf->ibuf_fileframe = ibuf->fileframe;
503 ubuf->image_state.source = image->source;
504 ubuf->image_state.use_float = ibuf->float_buffer.data != nullptr;
505
506 return ubuf;
507}
508
509static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf)
510{
511 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
512
513 const bool has_float = ibuf->float_buffer.data;
514 int i = 0;
515 for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) {
516 uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
517 for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) {
518 uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
519
520 BLI_assert(ubuf->tiles[i] == nullptr);
521 UndoImageTile *utile = utile_alloc(has_float);
522 utile->users = 1;
523 utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf);
524 ubuf->tiles[i] = utile;
525
526 i += 1;
527 }
528 }
529
530 BLI_assert(i == ubuf->tiles_len);
531
532 IMB_freeImBuf(tmpibuf);
533}
534
536static void ubuf_ensure_compat_ibuf(const UndoImageBuf *ubuf, ImBuf *ibuf)
537{
538 /* We could have both float and rect buffers,
539 * in this case free the float buffer if it's unused. */
540 if ((ibuf->float_buffer.data != nullptr) && (ubuf->image_state.use_float == false)) {
542 }
543
544 if (ibuf->x == ubuf->image_dims[0] && ibuf->y == ubuf->image_dims[1] &&
545 (ubuf->image_state.use_float ? (void *)ibuf->float_buffer.data :
546 (void *)ibuf->byte_buffer.data))
547 {
548 return;
549 }
550
551 IMB_free_all_data(ibuf);
552 IMB_rect_size_set(ibuf, ubuf->image_dims);
553
554 if (ubuf->image_state.use_float) {
555 IMB_alloc_float_pixels(ibuf, 4);
556 }
557 else {
559 }
560}
561
562static void ubuf_free(UndoImageBuf *ubuf)
563{
564 UndoImageBuf *ubuf_post = ubuf->post;
565 for (uint i = 0; i < ubuf->tiles_len; i++) {
566 UndoImageTile *utile = ubuf->tiles[i];
567 utile_decref(utile);
568 }
569 MEM_freeN(ubuf->tiles);
570 MEM_freeN(ubuf);
571 if (ubuf_post) {
572 ubuf_free(ubuf_post);
573 }
574}
575
577
578/* -------------------------------------------------------------------- */
581
599
600static void uhandle_restore_list(ListBase *undo_handles, bool use_init)
601{
602 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
603
604 LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) {
605 /* Tiles only added to second set of tiles. */
606 Image *image = uh->image_ref.ptr;
607
608 ImBuf *ibuf = BKE_image_acquire_ibuf(image, &uh->iuser, nullptr);
609 if (UNLIKELY(ibuf == nullptr)) {
610 CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2);
611 continue;
612 }
613 bool changed = false;
614 LISTBASE_FOREACH (UndoImageBuf *, ubuf_iter, &uh->buffers) {
615 UndoImageBuf *ubuf = use_init ? ubuf_iter : ubuf_iter->post;
616 ubuf_ensure_compat_ibuf(ubuf, ibuf);
617
618 int i = 0;
619 for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) {
620 uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
621 for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) {
622 uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
623 utile_restore(ubuf->tiles[i], x, y, ibuf, tmpibuf);
624 changed = true;
625 i += 1;
626 }
627 }
628 }
629
630 if (changed) {
631 BKE_image_mark_dirty(image, ibuf);
632 /* TODO(@jbakker): only mark areas that are actually updated to improve performance. */
634
635 if (ibuf->float_buffer.data) {
636 ibuf->userflags |= IB_RECT_INVALID; /* Force recreate of char `rect` */
637 }
638 if (ibuf->mipmap[0]) {
639 ibuf->userflags |= IB_MIPMAP_INVALID; /* Force MIP-MAP recreation. */
640 }
642
643 DEG_id_tag_update(&image->id, 0);
644 }
645 BKE_image_release_ibuf(image, ibuf, nullptr);
646 }
647
648 IMB_freeImBuf(tmpibuf);
649}
650
651static void uhandle_free_list(ListBase *undo_handles)
652{
653 LISTBASE_FOREACH_MUTABLE (UndoImageHandle *, uh, undo_handles) {
654 LISTBASE_FOREACH_MUTABLE (UndoImageBuf *, ubuf, &uh->buffers) {
655 ubuf_free(ubuf);
656 }
657 MEM_freeN(uh);
658 }
659 BLI_listbase_clear(undo_handles);
660}
661
663
664/* -------------------------------------------------------------------- */
667
669
671 const Image * /*image*/,
672 const char *ibuf_filepath,
673 const int ibuf_fileframe)
674{
675 LISTBASE_FOREACH (UndoImageBuf *, ubuf, &uh->buffers) {
676 if (STREQ(ubuf->ibuf_filepath, ibuf_filepath) && ubuf->ibuf_fileframe == ibuf_fileframe) {
677 return ubuf;
678 }
679 }
680 return nullptr;
681}
682
684{
685 BLI_assert(uhandle_lookup_ubuf(uh, image, ibuf->filepath, ibuf->fileframe) == nullptr);
686 UndoImageBuf *ubuf = ubuf_from_image_no_tiles(image, ibuf);
687 BLI_addtail(&uh->buffers, ubuf);
688
689 ubuf->post = nullptr;
690
691 return ubuf;
692}
693
695{
696 UndoImageBuf *ubuf = uhandle_lookup_ubuf(uh, image, ibuf->filepath, ibuf->fileframe);
697 if (ubuf == nullptr) {
698 ubuf = uhandle_add_ubuf(uh, image, ibuf);
699 }
700 return ubuf;
701}
702
704 const Image *image,
705 int tile_number)
706{
707 LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) {
708 if (STREQ(image->id.name + 2, uh->image_ref.name + 2) && uh->iuser.tile == tile_number) {
709 return uh;
710 }
711 }
712 return nullptr;
713}
714
715static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number)
716{
717 LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) {
718 if (image == uh->image_ref.ptr && uh->iuser.tile == tile_number) {
719 return uh;
720 }
721 }
722 return nullptr;
723}
724
725static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image, ImageUser *iuser)
726{
727 BLI_assert(uhandle_lookup(undo_handles, image, iuser->tile) == nullptr);
729 uh->image_ref.ptr = image;
730 uh->iuser = *iuser;
731 uh->iuser.scene = nullptr;
732 BLI_addtail(undo_handles, uh);
733 return uh;
734}
735
736static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image, ImageUser *iuser)
737{
738 UndoImageHandle *uh = uhandle_lookup(undo_handles, image, iuser->tile);
739 if (uh == nullptr) {
740 uh = uhandle_add(undo_handles, image, iuser);
741 }
742 return uh;
743}
744
746
747/* -------------------------------------------------------------------- */
750
766
772 const Image *image,
773 int tile_number,
774 const UndoImageBuf *ubuf)
775{
776 /* Use name lookup because the pointer is cleared for previous steps. */
777 UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image, tile_number);
778 if (uh_prev != nullptr) {
779 UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(
780 uh_prev, image, ubuf->ibuf_filepath, ubuf->ibuf_fileframe);
781 if (ubuf_reference) {
782 ubuf_reference = ubuf_reference->post;
783 if ((ubuf_reference->image_dims[0] == ubuf->image_dims[0]) &&
784 (ubuf_reference->image_dims[1] == ubuf->image_dims[1]))
785 {
786 return ubuf_reference;
787 }
788 }
789 }
790 return nullptr;
791}
792
794{
796
797 ScrArea *area = CTX_wm_area(C);
798 if (area && (area->spacetype == SPACE_IMAGE)) {
799 SpaceImage *sima = (SpaceImage *)area->spacedata.first;
800 if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) {
801 return true;
802 }
803 }
804 else {
805 if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) {
806 return true;
807 }
808 }
809 return false;
810}
811
813{
814 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
815 /* dummy, memory is cleared anyway. */
816 us->is_encode_init = true;
818 us->paint_tile_map = MEM_new<PaintTileMap>(__func__);
819}
820
821static bool image_undosys_step_encode(bContext *C, Main * /*bmain*/, UndoStep *us_p)
822{
823 /* Encoding is done along the way by adding tiles
824 * to the current 'ImageUndoStep' added by encode_init.
825 *
826 * This function ensures there are previous and current states of the image in the undo buffer.
827 */
828 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
829
830 BLI_assert(us->step.data_size == 0);
831
832 if (us->is_encode_init) {
833
834 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
835
836 ImageUndoStep *us_reference = reinterpret_cast<ImageUndoStep *>(
838 while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) {
839 us_reference = reinterpret_cast<ImageUndoStep *>(us_reference->step.prev);
840 }
841
842 /* Initialize undo tiles from paint-tiles (if they exist). */
843 for (PaintTile *ptile : us->paint_tile_map->map.values()) {
844 if (ptile->valid) {
845 UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image, &ptile->iuser);
846 UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf);
847
848 UndoImageTile *utile = static_cast<UndoImageTile *>(
849 MEM_callocN(sizeof(*utile), "UndoImageTile"));
850 utile->users = 1;
851 utile->rect.pt = ptile->rect.pt;
852 ptile->rect.pt = nullptr;
853 const uint tile_index = index_from_xy(ptile->x_tile, ptile->y_tile, ubuf_pre->tiles_dims);
854
855 BLI_assert(ubuf_pre->tiles[tile_index] == nullptr);
856 ubuf_pre->tiles[tile_index] = utile;
857 }
858 ptile_free(ptile);
859 }
860 us->paint_tile_map->map.clear();
861
863 LISTBASE_FOREACH (UndoImageBuf *, ubuf_pre, &uh->buffers) {
864
865 ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, &uh->iuser, nullptr);
866
867 const bool has_float = ibuf->float_buffer.data;
868
869 BLI_assert(ubuf_pre->post == nullptr);
870 ubuf_pre->post = ubuf_from_image_no_tiles(uh->image_ref.ptr, ibuf);
871 UndoImageBuf *ubuf_post = ubuf_pre->post;
872
873 if (ubuf_pre->image_dims[0] != ubuf_post->image_dims[0] ||
874 ubuf_pre->image_dims[1] != ubuf_post->image_dims[1])
875 {
876 ubuf_from_image_all_tiles(ubuf_post, ibuf);
877 }
878 else {
879 /* Search for the previous buffer. */
880 UndoImageBuf *ubuf_reference =
881 (us_reference ? ubuf_lookup_from_reference(
882 us_reference, uh->image_ref.ptr, uh->iuser.tile, ubuf_post) :
883 nullptr);
884
885 int i = 0;
886 for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) {
887 uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
888 for (uint x_tile = 0; x_tile < ubuf_pre->tiles_dims[0]; x_tile += 1) {
889 uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
890
891 if ((ubuf_reference != nullptr) &&
892 ((ubuf_pre->tiles[i] == nullptr) ||
893 /* In this case the paint stroke as has added a tile
894 * which we have a duplicate reference available. */
895 (ubuf_pre->tiles[i]->users == 1)))
896 {
897 if (ubuf_pre->tiles[i] != nullptr) {
898 /* If we have a reference, re-use this single use tile for the post state. */
899 BLI_assert(ubuf_pre->tiles[i]->users == 1);
900 ubuf_post->tiles[i] = ubuf_pre->tiles[i];
901 ubuf_pre->tiles[i] = nullptr;
902 utile_init_from_imbuf(ubuf_post->tiles[i], x, y, ibuf, tmpibuf);
903 }
904 else {
905 BLI_assert(ubuf_post->tiles[i] == nullptr);
906 ubuf_post->tiles[i] = ubuf_reference->tiles[i];
907 ubuf_post->tiles[i]->users += 1;
908 }
909 BLI_assert(ubuf_pre->tiles[i] == nullptr);
910 ubuf_pre->tiles[i] = ubuf_reference->tiles[i];
911 ubuf_pre->tiles[i]->users += 1;
912
913 BLI_assert(ubuf_pre->tiles[i] != nullptr);
914 BLI_assert(ubuf_post->tiles[i] != nullptr);
915 }
916 else {
917 UndoImageTile *utile = utile_alloc(has_float);
918 utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf);
919
920 if (ubuf_pre->tiles[i] != nullptr) {
921 ubuf_post->tiles[i] = utile;
922 utile->users = 1;
923 }
924 else {
925 ubuf_pre->tiles[i] = utile;
926 ubuf_post->tiles[i] = utile;
927 utile->users = 2;
928 }
929 }
930 BLI_assert(ubuf_pre->tiles[i] != nullptr);
931 BLI_assert(ubuf_post->tiles[i] != nullptr);
932 i += 1;
933 }
934 }
935 BLI_assert(i == ubuf_pre->tiles_len);
936 BLI_assert(i == ubuf_post->tiles_len);
937 }
938 BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, nullptr);
939 }
940 }
941
942 IMB_freeImBuf(tmpibuf);
943
944 /* Useful to debug tiles are stored correctly. */
945 if (false) {
946 uhandle_restore_list(&us->handles, false);
947 }
948 }
949 else {
950 BLI_assert(C != nullptr);
951 /* Happens when switching modes. */
954 us->paint_mode = paint_mode;
955 }
956
957 us_p->is_applied = true;
958
959 return true;
960}
961
963{
964 BLI_assert(us->step.is_applied == true);
965 uhandle_restore_list(&us->handles, !is_final);
966 us->step.is_applied = false;
967}
968
970{
971 BLI_assert(us->step.is_applied == false);
972 uhandle_restore_list(&us->handles, false);
973 us->step.is_applied = true;
974}
975
976static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final)
977{
978 /* Walk forward over any applied steps of same type,
979 * then walk back in the next loop, un-applying them. */
980 ImageUndoStep *us_iter = us;
981 while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) {
982 if (us_iter->step.next->is_applied == false) {
983 break;
984 }
985 us_iter = (ImageUndoStep *)us_iter->step.next;
986 }
987 while (us_iter != us || (!is_final && us_iter == us)) {
988 BLI_assert(us_iter->step.type == us->step.type); /* Previous loop ensures this. */
989 image_undosys_step_decode_undo_impl(us_iter, is_final);
990 if (us_iter == us) {
991 break;
992 }
993 us_iter = (ImageUndoStep *)us_iter->step.prev;
994 }
995}
996
998{
999 ImageUndoStep *us_iter = us;
1000 while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) {
1001 if (us_iter->step.prev->is_applied == true) {
1002 break;
1003 }
1004 us_iter = (ImageUndoStep *)us_iter->step.prev;
1005 }
1006 while (us_iter && (us_iter->step.is_applied == false)) {
1008 if (us_iter == us) {
1009 break;
1010 }
1011 us_iter = (ImageUndoStep *)us_iter->step.next;
1012 }
1013}
1014
1016 bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir dir, bool is_final)
1017{
1018 /* NOTE: behavior for undo/redo closely matches sculpt undo. */
1019 BLI_assert(dir != STEP_INVALID);
1020
1021 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1022 if (dir == STEP_UNDO) {
1023 image_undosys_step_decode_undo(us, is_final);
1024 }
1025 else if (dir == STEP_REDO) {
1027 }
1028
1029 if (us->paint_mode == PaintMode::Texture3D) {
1031 }
1032
1033 /* Ideally, we shouldn't have to tag the object as needing to be recalculated if using this paint
1034 * mode, however, because the image isn't connected as part of the shader nodes, the draw code
1035 * is unaware of the corresponding image tag. See #150957 for more details. */
1036 const Scene *scene = CTX_data_scene(C);
1037 Object *object = CTX_data_active_object(C);
1038 if (object && object->type == OB_MESH && scene &&
1040 {
1042 }
1043
1044 /* Refresh texture slots. */
1046}
1047
1049{
1050 ImageUndoStep *us = (ImageUndoStep *)us_p;
1052
1053 /* Typically this map will have been cleared. */
1054 MEM_delete(us->paint_tile_map);
1055 us->paint_tile_map = nullptr;
1056}
1057
1059 UndoTypeForEachIDRefFn foreach_ID_ref_fn,
1060 void *user_data)
1061{
1062 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1064 foreach_ID_ref_fn(user_data, ((UndoRefID *)&uh->image_ref));
1065 }
1066}
1067
1069{
1070 ut->name = "Image";
1076
1078
1079 /* NOTE: this is actually a confusing case, since it expects a valid context, but only in a
1080 * specific case, see `image_undosys_step_encode` code. We cannot specify
1081 * `UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE` though, as it can be called with a null context by
1082 * current code. */
1084
1085 ut->step_size = sizeof(ImageUndoStep);
1086}
1087
1089
1090/* -------------------------------------------------------------------- */
1101
1103{
1104 UndoStack *ustack = ED_undo_stack_get();
1105 UndoStep *us_prev = ustack->step_init;
1107 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1108 /* We should always have an undo push started when accessing tiles,
1109 * not doing this means we won't have paint_mode correctly set. */
1110 BLI_assert(us_p == us_prev);
1111 if (us_p != us_prev) {
1112 /* Fallback value until we can be sure this never happens. */
1114 }
1115 return us->paint_tile_map;
1116}
1117
1119{
1120 PaintTileMap *paint_tile_map = reinterpret_cast<ImageUndoStep *>(us)->paint_tile_map;
1121 ptile_restore_runtime_map(paint_tile_map);
1122 ptile_invalidate_map(paint_tile_map);
1123}
1124
1125static ImageUndoStep *image_undo_push_begin(const char *name, PaintMode paint_mode)
1126{
1127 UndoStack *ustack = ED_undo_stack_get();
1128 bContext *C = nullptr; /* special case, we never read from this. */
1130 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1132 us->paint_mode = paint_mode;
1133 return us;
1134}
1135
1136void ED_image_undo_push_begin(const char *name, PaintMode paint_mode)
1137{
1138 image_undo_push_begin(name, paint_mode);
1139}
1140
1142 Image *image,
1143 ImBuf *ibuf,
1144 ImageUser *iuser)
1145{
1147
1148 ED_image_undo_push(image, ibuf, iuser, us);
1149}
1150
1152 Image *image,
1153 ImageUser *iuser)
1154{
1156
1157 LISTBASE_FOREACH (ImageTile *, current_tile, &image->tiles) {
1158 iuser->tile = current_tile->tile_number;
1159 ImBuf *ibuf = BKE_image_acquire_ibuf(image, iuser, nullptr);
1160
1161 ED_image_undo_push(image, ibuf, iuser, us);
1162
1163 // Release the image buffer to avoid leaking memory
1164 BKE_image_release_ibuf(image, ibuf, nullptr);
1165 }
1166}
1167
1168void ED_image_undo_push(Image *image, ImBuf *ibuf, ImageUser *iuser, ImageUndoStep *us)
1169{
1170 BLI_assert(BKE_image_get_tile(image, iuser->tile));
1171 UndoImageHandle *uh = uhandle_ensure(&us->handles, image, iuser);
1172 UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf);
1173 BLI_assert(ubuf_pre->post == nullptr);
1174
1175 ImageUndoStep *us_reference = reinterpret_cast<ImageUndoStep *>(
1177 while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) {
1178 us_reference = reinterpret_cast<ImageUndoStep *>(us_reference->step.prev);
1179 }
1180 UndoImageBuf *ubuf_reference = (us_reference ? ubuf_lookup_from_reference(
1181 us_reference, image, iuser->tile, ubuf_pre) :
1182 nullptr);
1183
1184 if (ubuf_reference) {
1185 memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len);
1186 for (uint32_t i = 0; i < ubuf_pre->tiles_len; i++) {
1187 UndoImageTile *utile = ubuf_pre->tiles[i];
1188 utile->users += 1;
1189 }
1190 }
1191 else {
1192 ubuf_from_image_all_tiles(ubuf_pre, ibuf);
1193 }
1194}
1195
1197{
1198 UndoStack *ustack = ED_undo_stack_get();
1199 BKE_undosys_step_push(ustack, nullptr, nullptr);
1202}
1203
ScrArea * CTX_wm_area(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
ImBuf * BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
void BKE_image_mark_dirty(Image *image, ImBuf *ibuf)
void BKE_image_release_ibuf(Image *ima, ImBuf *ibuf, void *lock)
void BKE_image_free_gputextures(Image *ima)
Definition image_gpu.cc:570
void BKE_image_partial_update_mark_full_update(Image *image)
Mark the whole image to be updated.
PaintMode
Definition BKE_paint.hh:93
PaintMode BKE_paintmode_get_active_from_context(const bContext *C)
Definition paint.cc:496
@ UNDOTYPE_FLAG_DECODE_ACTIVE_STEP
void(*)(void *user_data, UndoRefID *id_ref) UndoTypeForEachIDRefFn
eUndoPushReturn BKE_undosys_step_push(UndoStack *ustack, bContext *C, const char *name)
eUndoStepDir
@ STEP_INVALID
@ STEP_UNDO
@ STEP_REDO
UndoStep * BKE_undosys_step_push_init_with_type(UndoStack *ustack, bContext *C, const char *name, const UndoType *ut)
#define BKE_undosys_stack_limit_steps_and_memory_defaults(ustack)
UndoStep * BKE_undosys_stack_init_or_active_with_type(UndoStack *ustack, const UndoType *ut)
const UndoType * BKE_UNDOSYS_TYPE_IMAGE
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE void BLI_listbase_clear(ListBase *lb)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
MINLINE int square_i(int a)
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
unsigned int uint
unsigned short ushort
pthread_spinlock_t SpinLock
void BLI_spin_init(SpinLock *spin)
Definition threads.cc:391
void BLI_spin_unlock(SpinLock *spin)
Definition threads.cc:430
void BLI_spin_lock(SpinLock *spin)
Definition threads.cc:405
void BLI_spin_end(SpinLock *spin)
Definition threads.cc:445
#define UNLIKELY(x)
#define ELEM(...)
#define STREQ(a, b)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:182
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_SHADING
Definition DNA_ID.h:1002
@ OB_MODE_TEXTURE_PAINT
Object is a sort of wrapper for general info.
@ OB_MESH
#define IMAGEPAINT_MODE_IMAGE
@ SPACE_IMAGE
@ SI_MODE_PAINT
#define ED_IMAGE_UNDO_TILE_NUMBER(size)
Definition ED_paint.hh:115
#define ED_IMAGE_UNDO_TILE_SIZE
Definition ED_paint.hh:114
#define ED_IMAGE_UNDO_TILE_BITS
Definition ED_paint.hh:113
UndoStack * ED_undo_stack_get()
Definition ed_undo.cc:441
void ED_editors_init_for_undo(Main *bmain)
Definition ed_util.cc:58
float * IMB_steal_float_buffer(ImBuf *ibuf)
uint8_t * IMB_steal_byte_buffer(ImBuf *ibuf)
void IMB_assign_float_buffer(ImBuf *ibuf, float *buffer_data, ImBufOwnership ownership)
bool IMB_alloc_byte_pixels(ImBuf *ibuf, bool initialize_pixels=true)
void IMB_free_all_data(ImBuf *ibuf)
void IMB_freeImBuf(ImBuf *ibuf)
ImBuf * IMB_allocImBuf(unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
void IMB_free_float_pixels(ImBuf *ibuf)
void IMB_rect_size_set(ImBuf *ibuf, const uint size[2])
Definition rectop.cc:289
bool IMB_alloc_float_pixels(ImBuf *ibuf, const unsigned int channels, bool initialize_pixels=true)
void IMB_assign_byte_buffer(ImBuf *ibuf, uint8_t *buffer_data, ImBufOwnership ownership)
void IMB_rectcpy(ImBuf *dbuf, const ImBuf *sbuf, int destx, int desty, int srcx, int srcy, int width, int height)
Definition rectop.cc:445
#define IMB_FILEPATH_SIZE
@ IB_RECT_INVALID
@ IB_MIPMAP_INVALID
@ IB_DISPLAY_BUFFER_INVALID
@ IB_TAKE_OWNERSHIP
@ IB_float_data
@ IB_byte_data
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
BMesh const char void * data
unsigned long long int uint64_t
void clear()
Definition BLI_map.hh:1038
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:508
ValueIterator values() const &
Definition BLI_map.hh:884
auto add_or_modify(const Key &key, const CreateValueF &create_value, const ModifyValueF &modify_value) -> decltype(create_value(nullptr))
Definition BLI_map.hh:481
static void utile_decref(UndoImageTile *utile)
static bool image_undosys_step_encode(bContext *C, Main *, UndoStep *us_p)
static UndoImageBuf * uhandle_lookup_ubuf(UndoImageHandle *uh, const Image *, const char *ibuf_filepath, const int ibuf_fileframe)
static void uhandle_restore_list(ListBase *undo_handles, bool use_init)
static void image_undosys_step_encode_init(bContext *, UndoStep *us_p)
static void ptile_free(PaintTile *ptile)
static ImageUndoStep * image_undo_push_begin(const char *name, PaintMode paint_mode)
static UndoImageTile * utile_alloc(bool has_float)
void ED_image_paint_tile_lock_end()
Definition image_undo.cc:72
void ED_image_undo_push_begin(const char *name, PaintMode paint_mode)
static void ptile_invalidate_map(PaintTileMap *paint_tile_map)
static float * image_undo_steal_and_assign_float_buffer(ImBuf *ibuf, float *new_buffer_data)
static UndoImageBuf * uhandle_ensure_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf)
static UndoImageHandle * uhandle_lookup_by_name(ListBase *undo_handles, const Image *image, int tile_number)
void ED_image_undo_push_begin_with_image(const char *name, Image *image, ImBuf *ibuf, ImageUser *iuser)
void ED_image_undo_push_begin_with_image_all_udims(const char *name, Image *image, ImageUser *iuser)
static void uhandle_free_list(ListBase *undo_handles)
static void utile_init_from_imbuf(UndoImageTile *utile, const uint32_t x, const uint32_t y, const ImBuf *ibuf, ImBuf *tmpibuf)
static void ptile_restore_runtime_map(PaintTileMap *paint_tile_map)
void ED_image_undosys_type(UndoType *ut)
void * ED_image_paint_tile_find(PaintTileMap *paint_tile_map, Image *image, ImBuf *ibuf, ImageUser *iuser, int x_tile, int y_tile, ushort **r_mask, bool validate)
static void image_undosys_step_decode(bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir dir, bool is_final)
static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final)
void ED_image_undo_push(Image *image, ImBuf *ibuf, ImageUser *iuser, ImageUndoStep *us)
static UndoImageBuf * ubuf_lookup_from_reference(ImageUndoStep *us_prev, const Image *image, int tile_number, const UndoImageBuf *ubuf)
static ImBuf * imbuf_alloc_temp_tile()
Definition image_undo.cc:89
void ED_image_undo_push_end()
static void image_undosys_foreach_ID_ref(UndoStep *us_p, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data)
static void image_undosys_step_decode_redo(ImageUndoStep *us)
void * ED_image_paint_tile_push(PaintTileMap *paint_tile_map, Image *image, ImBuf *ibuf, ImBuf **tmpibuf, ImageUser *iuser, int x_tile, int y_tile, ushort **r_mask, bool **r_valid, bool use_thread_lock, bool find_prev)
static uint8_t * image_undo_steal_and_assign_byte_buffer(ImBuf *ibuf, uint8_t *new_buffer_data)
PaintTileMap * ED_image_paint_tile_map_get()
static UndoImageBuf * ubuf_from_image_no_tiles(Image *image, const ImBuf *ibuf)
static void ubuf_ensure_compat_ibuf(const UndoImageBuf *ubuf, ImBuf *ibuf)
static UndoImageHandle * uhandle_ensure(ListBase *undo_handles, Image *image, ImageUser *iuser)
static void image_undosys_step_free(UndoStep *us_p)
static void image_undosys_step_decode_redo_impl(ImageUndoStep *us)
static bool image_undosys_poll(bContext *C)
static void image_undosys_step_decode_undo_impl(ImageUndoStep *us, bool is_final)
static uint32_t index_from_xy(uint32_t tile_x, uint32_t tile_y, const uint32_t tiles_dims[2])
static UndoImageHandle * uhandle_add(ListBase *undo_handles, Image *image, ImageUser *iuser)
static UndoImageBuf * uhandle_add_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf)
void ED_image_undo_restore(UndoStep *us)
static void ubuf_free(UndoImageBuf *ubuf)
static UndoImageHandle * uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number)
static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf)
void ED_image_paint_tile_lock_init()
Definition image_undo.cc:67
static SpinLock paint_tiles_lock
Definition image_undo.cc:65
static void utile_restore(const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf)
const int tile_index
#define LOG(severity)
Definition log.h:32
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
bool mode_set_ex(bContext *C, eObjectMode mode, bool use_undo, ReportList *reports)
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
char name[66]
Definition DNA_ID.h:415
char filepath[IMB_FILEPATH_SIZE]
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
ImBuf * mipmap[IMB_MIPMAP_LEVELS]
ListBase handles
UndoStep step
PaintMode paint_mode
PaintTileMap * paint_tile_map
struct Scene * scene
ListBase tiles
short source
void * first
bool operator==(const PaintTileKey &other) const
Image * image
Definition image_undo.cc:97
uint64_t hash() const
ImBuf * ibuf
Definition image_undo.cc:98
blender::Map< PaintTileKey, PaintTile * > map
uint16_t * mask
Image * image
bool use_float
void * pt
ImageUser iuser
float * fp
union PaintTile::@237370211305151134143255373203063211214040125353 rect
uint8_t * byte_ptr
ImBuf * ibuf
struct ToolSettings * toolsettings
ListBase spacedata
struct ImagePaintSettings imapaint
struct UndoImageBuf::@336115201270115314156211340064372176067024076115 image_state
char ibuf_filepath[IMB_FILEPATH_SIZE]
UndoImageBuf * next
UndoImageTile ** tiles
UndoImageBuf * post
UndoImageBuf * prev
uint32_t tiles_dims[2]
uint32_t image_dims[2]
uint32_t tiles_len
ImageUser iuser
UndoRefID_Image image_ref
UndoImageHandle * next
UndoImageHandle * prev
uint8_t * byte_ptr
union UndoImageTile::@025276325040357304216277157040215360242274146141 rect
UndoStep * step_init
UndoStep * step_active
size_t data_size
UndoStep * prev
UndoStep * next
const UndoType * type
void(* step_encode_init)(bContext *C, UndoStep *us)
void(* step_foreach_ID_ref)(UndoStep *us, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data)
const char * name
void(* step_free)(UndoStep *us)
bool(* poll)(struct bContext *C)
void(* step_decode)(bContext *C, Main *bmain, UndoStep *us, eUndoStepDir dir, bool is_final)
bool(* step_encode)(bContext *C, Main *bmain, UndoStep *us)
void * BKE_image_get_tile
Definition stubs.c:36
i
Definition text_draw.cc:230
void WM_file_tag_modified()
Definition wm_files.cc:174