Blender V5.0
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_math_base.h"
29#include "BLI_string.h"
30#include "BLI_threads.h"
31#include "BLI_utildefines.h"
32
33#include "DNA_image_types.h"
34#include "DNA_object_types.h"
35#include "DNA_screen_types.h"
36#include "DNA_space_types.h"
37#include "DNA_userdef_types.h"
39
40#include "IMB_imbuf.hh"
41#include "IMB_imbuf_types.hh"
42
43#include "BKE_context.hh"
44#include "BKE_image.hh"
45#include "BKE_paint.hh"
46#include "BKE_paint_types.hh"
47#include "BKE_undo_system.hh"
48
49#include "DEG_depsgraph.hh"
50
51#include "ED_object.hh"
52#include "ED_paint.hh"
53#include "ED_undo.hh"
54#include "ED_util.hh"
55
56#include "WM_api.hh"
57
58static CLG_LogRef LOG = {"undo.image"};
59
60/* -------------------------------------------------------------------- */
63
64/* This is a non-global static resource,
65 * Maybe it should be exposed as part of the
66 * paint operation, but for now just give a public interface */
68
73
78
80
81/* -------------------------------------------------------------------- */
90
96
101 /* Copied from iuser.tile in PaintTile. */
103
105 {
107 }
108 bool operator==(const PaintTileKey &other) const
109 {
110 return x_tile == other.x_tile && y_tile == other.y_tile && image == other.image &&
111 ibuf == other.ibuf && iuser_tile == other.iuser_tile;
112 }
113};
114
115struct PaintTile {
118 /* For 2D image painting the ImageUser uses most of the values.
119 * Even though views and passes are stored they are currently not supported for painting.
120 * For 3D projection painting this only uses a tile & frame number.
121 * The scene pointer must be cleared (or temporarily set it as needed, but leave cleared). */
123 union {
124 float *fp;
125 uint8_t *byte_ptr;
126 void *pt;
128 uint16_t *mask;
129 bool valid;
132};
133
134static void ptile_free(PaintTile *ptile)
135{
136 if (ptile->rect.pt) {
137 MEM_freeN(ptile->rect.pt);
138 }
139 if (ptile->mask) {
140 MEM_freeN(ptile->mask);
141 }
142 MEM_freeN(ptile);
143}
144
147
149 {
150 for (PaintTile *ptile : map.values()) {
151 ptile_free(ptile);
152 }
153 }
154};
155
156static void ptile_invalidate_map(PaintTileMap *paint_tile_map)
157{
158 for (PaintTile *ptile : paint_tile_map->map.values()) {
159 ptile->valid = false;
160 }
161}
162
164 Image *image,
165 ImBuf *ibuf,
166 ImageUser *iuser,
167 int x_tile,
168 int y_tile,
169 ushort **r_mask,
170 bool validate)
171{
172 PaintTileKey key;
173 key.ibuf = ibuf;
174 key.image = image;
175 key.iuser_tile = iuser->tile;
176 key.x_tile = x_tile;
177 key.y_tile = y_tile;
178 PaintTile **pptile = paint_tile_map->map.lookup_ptr(key);
179 if (pptile == nullptr) {
180 return nullptr;
181 }
182 PaintTile *ptile = *pptile;
183 if (r_mask) {
184 /* allocate mask if requested. */
185 if (!ptile->mask) {
187 "UndoImageTile.mask");
188 }
189 *r_mask = ptile->mask;
190 }
191 if (validate) {
192 ptile->valid = true;
193 }
194 return ptile->rect.pt;
195}
196
197/* Set the given buffer data as an owning data of the imbuf's buffer.
198 * Returns the data pointer which was stolen from the imbuf before assignment. */
199static uint8_t *image_undo_steal_and_assign_byte_buffer(ImBuf *ibuf, uint8_t *new_buffer_data)
200{
201 uint8_t *old_buffer_data = IMB_steal_byte_buffer(ibuf);
202 IMB_assign_byte_buffer(ibuf, new_buffer_data, IB_TAKE_OWNERSHIP);
203 return old_buffer_data;
204}
205static float *image_undo_steal_and_assign_float_buffer(ImBuf *ibuf, float *new_buffer_data)
206{
207 float *old_buffer_data = IMB_steal_float_buffer(ibuf);
208 IMB_assign_float_buffer(ibuf, new_buffer_data, IB_TAKE_OWNERSHIP);
209 return old_buffer_data;
210}
211
213 Image *image,
214 ImBuf *ibuf,
215 ImBuf **tmpibuf,
216 ImageUser *iuser,
217 int x_tile,
218 int y_tile,
219 ushort **r_mask,
220 bool **r_valid,
221 bool use_thread_lock,
222 bool find_prev)
223{
224 if (use_thread_lock) {
226 }
227 const bool has_float = (ibuf->float_buffer.data != nullptr);
228
229 /* check if tile is already pushed */
230
231 /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */
232 if (find_prev) {
234 paint_tile_map, image, ibuf, iuser, x_tile, y_tile, r_mask, true);
235 if (data) {
236 if (use_thread_lock) {
238 }
239 return data;
240 }
241 }
242
243 if (*tmpibuf == nullptr) {
244 *tmpibuf = imbuf_alloc_temp_tile();
245 }
246
247 PaintTile *ptile = MEM_callocN<PaintTile>("PaintTile");
248
249 ptile->image = image;
250 ptile->ibuf = ibuf;
251 ptile->iuser = *iuser;
252 ptile->iuser.scene = nullptr;
253
254 ptile->x_tile = x_tile;
255 ptile->y_tile = y_tile;
256
257 /* add mask explicitly here */
258 if (r_mask) {
260 "PaintTile.mask");
261 }
262
263 ptile->rect.pt = MEM_callocN((ibuf->float_buffer.data ? sizeof(float[4]) : sizeof(char[4])) *
265 "PaintTile.rect");
266
267 ptile->use_float = has_float;
268 ptile->valid = true;
269
270 if (r_valid) {
271 *r_valid = &ptile->valid;
272 }
273
274 IMB_rectcpy(*tmpibuf,
275 ibuf,
276 0,
277 0,
282
283 if (has_float) {
284 ptile->rect.fp = image_undo_steal_and_assign_float_buffer(*tmpibuf, ptile->rect.fp);
285 }
286 else {
288 }
289
290 PaintTileKey key = {};
291 key.ibuf = ibuf;
292 key.image = image;
293 key.iuser_tile = iuser->tile;
294 key.x_tile = x_tile;
295 key.y_tile = y_tile;
296 PaintTile *existing_tile = nullptr;
297 paint_tile_map->map.add_or_modify(
298 key,
299 [&](PaintTile **pptile) { *pptile = ptile; },
300 [&](PaintTile **pptile) { existing_tile = *pptile; });
301 if (existing_tile) {
302 ptile_free(ptile);
303 ptile = existing_tile;
304 }
305
306 if (use_thread_lock) {
308 }
309 return ptile->rect.pt;
310}
311
312static void ptile_restore_runtime_map(PaintTileMap *paint_tile_map)
313{
314 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
315
316 for (PaintTile *ptile : paint_tile_map->map.values()) {
317 Image *image = ptile->image;
318 ImBuf *ibuf = BKE_image_acquire_ibuf(image, &ptile->iuser, nullptr);
319 const bool has_float = (ibuf->float_buffer.data != nullptr);
320
321 if (has_float) {
322 ptile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, ptile->rect.fp);
323 }
324 else {
325 ptile->rect.byte_ptr = image_undo_steal_and_assign_byte_buffer(tmpibuf,
326 ptile->rect.byte_ptr);
327 }
328
329 /* TODO(sergey): Look into implementing API which does not require such temporary buffer
330 * assignment. */
331 IMB_rectcpy(ibuf,
332 tmpibuf,
333 ptile->x_tile * ED_IMAGE_UNDO_TILE_SIZE,
334 ptile->y_tile * ED_IMAGE_UNDO_TILE_SIZE,
335 0,
336 0,
339
340 if (has_float) {
341 ptile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, ptile->rect.fp);
342 }
343 else {
344 ptile->rect.byte_ptr = image_undo_steal_and_assign_byte_buffer(tmpibuf,
345 ptile->rect.byte_ptr);
346 }
347
348 /* Force OpenGL reload (maybe partial update will operate better?) */
350
351 if (ibuf->float_buffer.data) {
352 ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */
353 }
355
356 BKE_image_release_ibuf(image, ibuf, nullptr);
357 }
358
359 IMB_freeImBuf(tmpibuf);
360}
361
363
364/* -------------------------------------------------------------------- */
367
368static uint32_t index_from_xy(uint32_t tile_x, uint32_t tile_y, const uint32_t tiles_dims[2])
369{
370 BLI_assert(tile_x < tiles_dims[0] && tile_y < tiles_dims[1]);
371 return (tile_y * tiles_dims[0]) + tile_x;
372}
373
375 union {
376 float *fp;
377 uint8_t *byte_ptr;
378 void *pt;
380 int users;
381};
382
383static UndoImageTile *utile_alloc(bool has_float)
384{
385 UndoImageTile *utile = static_cast<UndoImageTile *>(
386 MEM_callocN(sizeof(*utile), "ImageUndoTile"));
387 if (has_float) {
388 utile->rect.fp = static_cast<float *>(
389 MEM_mallocN(sizeof(float[4]) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__));
390 }
391 else {
392 utile->rect.byte_ptr = static_cast<uint8_t *>(
393 MEM_mallocN(sizeof(uint32_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__));
394 }
395 return utile;
396}
397
399 UndoImageTile *utile, const uint32_t x, const uint32_t y, const ImBuf *ibuf, ImBuf *tmpibuf)
400{
401 const bool has_float = ibuf->float_buffer.data;
402
403 if (has_float) {
404 utile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, utile->rect.fp);
405 }
406 else {
408 }
409
410 /* TODO(sergey): Look into implementing API which does not require such temporary buffer
411 * assignment. */
413
414 if (has_float) {
415 utile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, utile->rect.fp);
416 }
417 else {
419 }
420}
421
422static void utile_restore(
423 const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf)
424{
425 const bool has_float = ibuf->float_buffer.data;
426 float *prev_rect_float = tmpibuf->float_buffer.data;
427 uint8_t *prev_rect = tmpibuf->byte_buffer.data;
428
429 if (has_float) {
430 tmpibuf->float_buffer.data = utile->rect.fp;
431 }
432 else {
433 tmpibuf->byte_buffer.data = utile->rect.byte_ptr;
434 }
435
436 /* TODO(sergey): Look into implementing API which does not require such temporary buffer
437 * assignment. */
439
440 tmpibuf->float_buffer.data = prev_rect_float;
441 tmpibuf->byte_buffer.data = prev_rect;
442}
443
444static void utile_decref(UndoImageTile *utile)
445{
446 utile->users -= 1;
447 BLI_assert(utile->users >= 0);
448 if (utile->users == 0) {
449 MEM_freeN(utile->rect.pt);
450 MEM_delete(utile);
451 }
452}
453
455
456/* -------------------------------------------------------------------- */
459
462
467
470
472
474 uint32_t tiles_len;
475 uint32_t tiles_dims[2];
476
477 uint32_t image_dims[2];
478
480 struct {
481 short source;
484};
485
487{
488 UndoImageBuf *ubuf = MEM_callocN<UndoImageBuf>(__func__);
489
490 ubuf->image_dims[0] = ibuf->x;
491 ubuf->image_dims[1] = ibuf->y;
492
495
496 ubuf->tiles_len = ubuf->tiles_dims[0] * ubuf->tiles_dims[1];
497 ubuf->tiles = static_cast<UndoImageTile **>(
498 MEM_callocN(sizeof(*ubuf->tiles) * ubuf->tiles_len, __func__));
499
500 STRNCPY(ubuf->ibuf_filepath, ibuf->filepath);
501 ubuf->ibuf_fileframe = ibuf->fileframe;
502 ubuf->image_state.source = image->source;
503 ubuf->image_state.use_float = ibuf->float_buffer.data != nullptr;
504
505 return ubuf;
506}
507
508static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf)
509{
510 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
511
512 const bool has_float = ibuf->float_buffer.data;
513 int i = 0;
514 for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) {
515 uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
516 for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) {
517 uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
518
519 BLI_assert(ubuf->tiles[i] == nullptr);
520 UndoImageTile *utile = utile_alloc(has_float);
521 utile->users = 1;
522 utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf);
523 ubuf->tiles[i] = utile;
524
525 i += 1;
526 }
527 }
528
529 BLI_assert(i == ubuf->tiles_len);
530
531 IMB_freeImBuf(tmpibuf);
532}
533
535static void ubuf_ensure_compat_ibuf(const UndoImageBuf *ubuf, ImBuf *ibuf)
536{
537 /* We could have both float and rect buffers,
538 * in this case free the float buffer if it's unused. */
539 if ((ibuf->float_buffer.data != nullptr) && (ubuf->image_state.use_float == false)) {
541 }
542
543 if (ibuf->x == ubuf->image_dims[0] && ibuf->y == ubuf->image_dims[1] &&
544 (ubuf->image_state.use_float ? (void *)ibuf->float_buffer.data :
545 (void *)ibuf->byte_buffer.data))
546 {
547 return;
548 }
549
550 IMB_free_all_data(ibuf);
551 IMB_rect_size_set(ibuf, ubuf->image_dims);
552
553 if (ubuf->image_state.use_float) {
554 IMB_alloc_float_pixels(ibuf, 4);
555 }
556 else {
558 }
559}
560
561static void ubuf_free(UndoImageBuf *ubuf)
562{
563 UndoImageBuf *ubuf_post = ubuf->post;
564 for (uint i = 0; i < ubuf->tiles_len; i++) {
565 UndoImageTile *utile = ubuf->tiles[i];
566 utile_decref(utile);
567 }
568 MEM_freeN(ubuf->tiles);
569 MEM_freeN(ubuf);
570 if (ubuf_post) {
571 ubuf_free(ubuf_post);
572 }
573}
574
576
577/* -------------------------------------------------------------------- */
580
598
599static void uhandle_restore_list(ListBase *undo_handles, bool use_init)
600{
601 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
602
603 LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) {
604 /* Tiles only added to second set of tiles. */
605 Image *image = uh->image_ref.ptr;
606
607 ImBuf *ibuf = BKE_image_acquire_ibuf(image, &uh->iuser, nullptr);
608 if (UNLIKELY(ibuf == nullptr)) {
609 CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2);
610 continue;
611 }
612 bool changed = false;
613 LISTBASE_FOREACH (UndoImageBuf *, ubuf_iter, &uh->buffers) {
614 UndoImageBuf *ubuf = use_init ? ubuf_iter : ubuf_iter->post;
615 ubuf_ensure_compat_ibuf(ubuf, ibuf);
616
617 int i = 0;
618 for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) {
619 uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
620 for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) {
621 uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
622 utile_restore(ubuf->tiles[i], x, y, ibuf, tmpibuf);
623 changed = true;
624 i += 1;
625 }
626 }
627 }
628
629 if (changed) {
630 BKE_image_mark_dirty(image, ibuf);
631 /* TODO(@jbakker): only mark areas that are actually updated to improve performance. */
633
634 if (ibuf->float_buffer.data) {
635 ibuf->userflags |= IB_RECT_INVALID; /* Force recreate of char `rect` */
636 }
638
639 DEG_id_tag_update(&image->id, 0);
640 }
641 BKE_image_release_ibuf(image, ibuf, nullptr);
642 }
643
644 IMB_freeImBuf(tmpibuf);
645}
646
647static void uhandle_free_list(ListBase *undo_handles)
648{
649 LISTBASE_FOREACH_MUTABLE (UndoImageHandle *, uh, undo_handles) {
650 LISTBASE_FOREACH_MUTABLE (UndoImageBuf *, ubuf, &uh->buffers) {
651 ubuf_free(ubuf);
652 }
653 MEM_freeN(uh);
654 }
655 BLI_listbase_clear(undo_handles);
656}
657
659
660/* -------------------------------------------------------------------- */
663
665
667 const Image * /*image*/,
668 const char *ibuf_filepath,
669 const int ibuf_fileframe)
670{
671 LISTBASE_FOREACH (UndoImageBuf *, ubuf, &uh->buffers) {
672 if (STREQ(ubuf->ibuf_filepath, ibuf_filepath) && ubuf->ibuf_fileframe == ibuf_fileframe) {
673 return ubuf;
674 }
675 }
676 return nullptr;
677}
678
680{
681 BLI_assert(uhandle_lookup_ubuf(uh, image, ibuf->filepath, ibuf->fileframe) == nullptr);
682 UndoImageBuf *ubuf = ubuf_from_image_no_tiles(image, ibuf);
683 BLI_addtail(&uh->buffers, ubuf);
684
685 ubuf->post = nullptr;
686
687 return ubuf;
688}
689
691{
692 UndoImageBuf *ubuf = uhandle_lookup_ubuf(uh, image, ibuf->filepath, ibuf->fileframe);
693 if (ubuf == nullptr) {
694 ubuf = uhandle_add_ubuf(uh, image, ibuf);
695 }
696 return ubuf;
697}
698
700 const Image *image,
701 int tile_number)
702{
703 LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) {
704 if (STREQ(image->id.name + 2, uh->image_ref.name + 2) && uh->iuser.tile == tile_number) {
705 return uh;
706 }
707 }
708 return nullptr;
709}
710
711static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number)
712{
713 LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) {
714 if (image == uh->image_ref.ptr && uh->iuser.tile == tile_number) {
715 return uh;
716 }
717 }
718 return nullptr;
719}
720
721static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image, ImageUser *iuser)
722{
723 BLI_assert(uhandle_lookup(undo_handles, image, iuser->tile) == nullptr);
725 uh->image_ref.ptr = image;
726 uh->iuser = *iuser;
727 uh->iuser.scene = nullptr;
728 BLI_addtail(undo_handles, uh);
729 return uh;
730}
731
732static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image, ImageUser *iuser)
733{
734 UndoImageHandle *uh = uhandle_lookup(undo_handles, image, iuser->tile);
735 if (uh == nullptr) {
736 uh = uhandle_add(undo_handles, image, iuser);
737 }
738 return uh;
739}
740
742
743/* -------------------------------------------------------------------- */
746
762
768 const Image *image,
769 int tile_number,
770 const UndoImageBuf *ubuf)
771{
772 /* Use name lookup because the pointer is cleared for previous steps. */
773 UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image, tile_number);
774 if (uh_prev != nullptr) {
775 UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(
776 uh_prev, image, ubuf->ibuf_filepath, ubuf->ibuf_fileframe);
777 if (ubuf_reference) {
778 ubuf_reference = ubuf_reference->post;
779 if ((ubuf_reference->image_dims[0] == ubuf->image_dims[0]) &&
780 (ubuf_reference->image_dims[1] == ubuf->image_dims[1]))
781 {
782 return ubuf_reference;
783 }
784 }
785 }
786 return nullptr;
787}
788
790{
792
793 ScrArea *area = CTX_wm_area(C);
794 if (area && (area->spacetype == SPACE_IMAGE)) {
795 SpaceImage *sima = (SpaceImage *)area->spacedata.first;
796 if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) {
797 return true;
798 }
799 }
800 else {
801 if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) {
802 return true;
803 }
804 }
805 return false;
806}
807
809{
810 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
811 /* dummy, memory is cleared anyway. */
812 us->is_encode_init = true;
814 us->paint_tile_map = MEM_new<PaintTileMap>(__func__);
815}
816
817static bool image_undosys_step_encode(bContext *C, Main * /*bmain*/, UndoStep *us_p)
818{
819 /* Encoding is done along the way by adding tiles
820 * to the current 'ImageUndoStep' added by encode_init.
821 *
822 * This function ensures there are previous and current states of the image in the undo buffer.
823 */
824 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
825
826 BLI_assert(us->step.data_size == 0);
827
828 if (us->is_encode_init) {
829
830 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
831
832 ImageUndoStep *us_reference = reinterpret_cast<ImageUndoStep *>(
834 while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) {
835 us_reference = reinterpret_cast<ImageUndoStep *>(us_reference->step.prev);
836 }
837
838 /* Initialize undo tiles from paint-tiles (if they exist). */
839 for (PaintTile *ptile : us->paint_tile_map->map.values()) {
840 if (ptile->valid) {
841 UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image, &ptile->iuser);
842 UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf);
843
844 UndoImageTile *utile = static_cast<UndoImageTile *>(
845 MEM_callocN(sizeof(*utile), "UndoImageTile"));
846 utile->users = 1;
847 utile->rect.pt = ptile->rect.pt;
848 ptile->rect.pt = nullptr;
849 const uint tile_index = index_from_xy(ptile->x_tile, ptile->y_tile, ubuf_pre->tiles_dims);
850
851 BLI_assert(ubuf_pre->tiles[tile_index] == nullptr);
852 ubuf_pre->tiles[tile_index] = utile;
853 }
854 ptile_free(ptile);
855 }
856 us->paint_tile_map->map.clear();
857
859 LISTBASE_FOREACH (UndoImageBuf *, ubuf_pre, &uh->buffers) {
860
861 ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, &uh->iuser, nullptr);
862
863 const bool has_float = ibuf->float_buffer.data;
864
865 BLI_assert(ubuf_pre->post == nullptr);
866 ubuf_pre->post = ubuf_from_image_no_tiles(uh->image_ref.ptr, ibuf);
867 UndoImageBuf *ubuf_post = ubuf_pre->post;
868
869 if (ubuf_pre->image_dims[0] != ubuf_post->image_dims[0] ||
870 ubuf_pre->image_dims[1] != ubuf_post->image_dims[1])
871 {
872 ubuf_from_image_all_tiles(ubuf_post, ibuf);
873 }
874 else {
875 /* Search for the previous buffer. */
876 UndoImageBuf *ubuf_reference =
877 (us_reference ? ubuf_lookup_from_reference(
878 us_reference, uh->image_ref.ptr, uh->iuser.tile, ubuf_post) :
879 nullptr);
880
881 int i = 0;
882 for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) {
883 uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
884 for (uint x_tile = 0; x_tile < ubuf_pre->tiles_dims[0]; x_tile += 1) {
885 uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
886
887 if ((ubuf_reference != nullptr) &&
888 ((ubuf_pre->tiles[i] == nullptr) ||
889 /* In this case the paint stroke as has added a tile
890 * which we have a duplicate reference available. */
891 (ubuf_pre->tiles[i]->users == 1)))
892 {
893 if (ubuf_pre->tiles[i] != nullptr) {
894 /* If we have a reference, re-use this single use tile for the post state. */
895 BLI_assert(ubuf_pre->tiles[i]->users == 1);
896 ubuf_post->tiles[i] = ubuf_pre->tiles[i];
897 ubuf_pre->tiles[i] = nullptr;
898 utile_init_from_imbuf(ubuf_post->tiles[i], x, y, ibuf, tmpibuf);
899 }
900 else {
901 BLI_assert(ubuf_post->tiles[i] == nullptr);
902 ubuf_post->tiles[i] = ubuf_reference->tiles[i];
903 ubuf_post->tiles[i]->users += 1;
904 }
905 BLI_assert(ubuf_pre->tiles[i] == nullptr);
906 ubuf_pre->tiles[i] = ubuf_reference->tiles[i];
907 ubuf_pre->tiles[i]->users += 1;
908
909 BLI_assert(ubuf_pre->tiles[i] != nullptr);
910 BLI_assert(ubuf_post->tiles[i] != nullptr);
911 }
912 else {
913 UndoImageTile *utile = utile_alloc(has_float);
914 utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf);
915
916 if (ubuf_pre->tiles[i] != nullptr) {
917 ubuf_post->tiles[i] = utile;
918 utile->users = 1;
919 }
920 else {
921 ubuf_pre->tiles[i] = utile;
922 ubuf_post->tiles[i] = utile;
923 utile->users = 2;
924 }
925 }
926 BLI_assert(ubuf_pre->tiles[i] != nullptr);
927 BLI_assert(ubuf_post->tiles[i] != nullptr);
928 i += 1;
929 }
930 }
931 BLI_assert(i == ubuf_pre->tiles_len);
932 BLI_assert(i == ubuf_post->tiles_len);
933 }
934 BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, nullptr);
935 }
936 }
937
938 IMB_freeImBuf(tmpibuf);
939
940 /* Useful to debug tiles are stored correctly. */
941 if (false) {
942 uhandle_restore_list(&us->handles, false);
943 }
944 }
945 else {
946 BLI_assert(C != nullptr);
947 /* Happens when switching modes. */
950 us->paint_mode = paint_mode;
951 }
952
953 us_p->is_applied = true;
954
955 return true;
956}
957
959{
960 BLI_assert(us->step.is_applied == true);
961 uhandle_restore_list(&us->handles, !is_final);
962 us->step.is_applied = false;
963}
964
966{
967 BLI_assert(us->step.is_applied == false);
968 uhandle_restore_list(&us->handles, false);
969 us->step.is_applied = true;
970}
971
972static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final)
973{
974 /* Walk forward over any applied steps of same type,
975 * then walk back in the next loop, un-applying them. */
976 ImageUndoStep *us_iter = us;
977 while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) {
978 if (us_iter->step.next->is_applied == false) {
979 break;
980 }
981 us_iter = (ImageUndoStep *)us_iter->step.next;
982 }
983 while (us_iter != us || (!is_final && us_iter == us)) {
984 BLI_assert(us_iter->step.type == us->step.type); /* Previous loop ensures this. */
985 image_undosys_step_decode_undo_impl(us_iter, is_final);
986 if (us_iter == us) {
987 break;
988 }
989 us_iter = (ImageUndoStep *)us_iter->step.prev;
990 }
991}
992
994{
995 ImageUndoStep *us_iter = us;
996 while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) {
997 if (us_iter->step.prev->is_applied == true) {
998 break;
999 }
1000 us_iter = (ImageUndoStep *)us_iter->step.prev;
1001 }
1002 while (us_iter && (us_iter->step.is_applied == false)) {
1004 if (us_iter == us) {
1005 break;
1006 }
1007 us_iter = (ImageUndoStep *)us_iter->step.next;
1008 }
1009}
1010
1012 bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir dir, bool is_final)
1013{
1014 /* NOTE: behavior for undo/redo closely matches sculpt undo. */
1015 BLI_assert(dir != STEP_INVALID);
1016
1017 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1018 if (dir == STEP_UNDO) {
1019 image_undosys_step_decode_undo(us, is_final);
1020 }
1021 else if (dir == STEP_REDO) {
1023 }
1024
1025 if (us->paint_mode == PaintMode::Texture3D) {
1027 }
1028
1029 /* Refresh texture slots. */
1031}
1032
1034{
1035 ImageUndoStep *us = (ImageUndoStep *)us_p;
1037
1038 /* Typically this map will have been cleared. */
1039 MEM_delete(us->paint_tile_map);
1040 us->paint_tile_map = nullptr;
1041}
1042
1044 UndoTypeForEachIDRefFn foreach_ID_ref_fn,
1045 void *user_data)
1046{
1047 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1049 foreach_ID_ref_fn(user_data, ((UndoRefID *)&uh->image_ref));
1050 }
1051}
1052
1054{
1055 ut->name = "Image";
1061
1063
1064 /* NOTE: this is actually a confusing case, since it expects a valid context, but only in a
1065 * specific case, see `image_undosys_step_encode` code. We cannot specify
1066 * `UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE` though, as it can be called with a null context by
1067 * current code. */
1069
1070 ut->step_size = sizeof(ImageUndoStep);
1071}
1072
1074
1075/* -------------------------------------------------------------------- */
1086
1088{
1089 UndoStack *ustack = ED_undo_stack_get();
1090 UndoStep *us_prev = ustack->step_init;
1092 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1093 /* We should always have an undo push started when accessing tiles,
1094 * not doing this means we won't have paint_mode correctly set. */
1095 BLI_assert(us_p == us_prev);
1096 if (us_p != us_prev) {
1097 /* Fallback value until we can be sure this never happens. */
1099 }
1100 return us->paint_tile_map;
1101}
1102
1104{
1105 PaintTileMap *paint_tile_map = reinterpret_cast<ImageUndoStep *>(us)->paint_tile_map;
1106 ptile_restore_runtime_map(paint_tile_map);
1107 ptile_invalidate_map(paint_tile_map);
1108}
1109
1110static ImageUndoStep *image_undo_push_begin(const char *name, PaintMode paint_mode)
1111{
1112 UndoStack *ustack = ED_undo_stack_get();
1113 bContext *C = nullptr; /* special case, we never read from this. */
1115 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1117 us->paint_mode = paint_mode;
1118 return us;
1119}
1120
1121void ED_image_undo_push_begin(const char *name, PaintMode paint_mode)
1122{
1123 image_undo_push_begin(name, paint_mode);
1124}
1125
1127 Image *image,
1128 ImBuf *ibuf,
1129 ImageUser *iuser)
1130{
1132
1133 ED_image_undo_push(image, ibuf, iuser, us);
1134}
1135
1137 Image *image,
1138 ImageUser *iuser)
1139{
1141
1142 LISTBASE_FOREACH (ImageTile *, current_tile, &image->tiles) {
1143 iuser->tile = current_tile->tile_number;
1144 ImBuf *ibuf = BKE_image_acquire_ibuf(image, iuser, nullptr);
1145
1146 ED_image_undo_push(image, ibuf, iuser, us);
1147
1148 // Release the image buffer to avoid leaking memory
1149 BKE_image_release_ibuf(image, ibuf, nullptr);
1150 }
1151}
1152
1153void ED_image_undo_push(Image *image, ImBuf *ibuf, ImageUser *iuser, ImageUndoStep *us)
1154{
1155 BLI_assert(BKE_image_get_tile(image, iuser->tile));
1156 UndoImageHandle *uh = uhandle_ensure(&us->handles, image, iuser);
1157 UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf);
1158 BLI_assert(ubuf_pre->post == nullptr);
1159
1160 ImageUndoStep *us_reference = reinterpret_cast<ImageUndoStep *>(
1162 while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) {
1163 us_reference = reinterpret_cast<ImageUndoStep *>(us_reference->step.prev);
1164 }
1165 UndoImageBuf *ubuf_reference = (us_reference ? ubuf_lookup_from_reference(
1166 us_reference, image, iuser->tile, ubuf_pre) :
1167 nullptr);
1168
1169 if (ubuf_reference) {
1170 memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len);
1171 for (uint32_t i = 0; i < ubuf_pre->tiles_len; i++) {
1172 UndoImageTile *utile = ubuf_pre->tiles[i];
1173 utile->users += 1;
1174 }
1175 }
1176 else {
1177 ubuf_from_image_all_tiles(ubuf_pre, ibuf);
1178 }
1179}
1180
1182{
1183 UndoStack *ustack = ED_undo_stack_get();
1184 BKE_undosys_step_push(ustack, nullptr, nullptr);
1187}
1188
ScrArea * CTX_wm_area(const bContext *C)
Object * CTX_data_active_object(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:581
void BKE_image_partial_update_mark_full_update(Image *image)
Mark the whole image to be updated.
PaintMode BKE_paintmode_get_active_from_context(const bContext *C)
Definition paint.cc:505
PaintMode
@ 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:693
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:188
void DEG_id_tag_update(ID *id, unsigned int flags)
@ OB_MODE_TEXTURE_PAINT
Object is a sort of wrapper for general info.
@ 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:442
void ED_editors_init_for_undo(Main *bmain)
Definition ed_util.cc:61
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:288
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:444
#define IMB_FILEPATH_SIZE
@ IB_RECT_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:74
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:91
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:69
static SpinLock paint_tiles_lock
Definition image_undo.cc:67
static void utile_restore(const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf)
const int tile_index
#define LOG(level)
Definition log.h:97
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
const char * name
char name[258]
Definition DNA_ID.h:432
char filepath[IMB_FILEPATH_SIZE]
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
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:99
uint64_t hash() const
blender::Map< PaintTileKey, PaintTile * > map
uint16_t * mask
Image * image
bool use_float
union PaintTile::@275364016240307256050210375017352137171317126370 rect
void * pt
ImageUser iuser
float * fp
uint8_t * byte_ptr
ImBuf * ibuf
ListBase spacedata
char ibuf_filepath[IMB_FILEPATH_SIZE]
struct UndoImageBuf::@263271160035357327315251267115250107172320334243 image_state
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::@040210201047004104306316350105014000227155375126 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:177