Blender V4.3
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
22#include "CLG_log.h"
23
24#include "MEM_guardedalloc.h"
25
26#include "BLI_blenlib.h"
27#include "BLI_map.hh"
28#include "BLI_threads.h"
29#include "BLI_utildefines.h"
30
31#include "DNA_image_types.h"
32#include "DNA_object_types.h"
33#include "DNA_screen_types.h"
34#include "DNA_space_types.h"
36
37#include "IMB_imbuf.hh"
38#include "IMB_imbuf_types.hh"
39
40#include "BKE_context.hh"
41#include "BKE_image.hh"
42#include "BKE_paint.hh"
43#include "BKE_undo_system.hh"
44
45#include "DEG_depsgraph.hh"
46
47#include "ED_object.hh"
48#include "ED_paint.hh"
49#include "ED_undo.hh"
50#include "ED_util.hh"
51
52#include "WM_api.hh"
53
54static CLG_LogRef LOG = {"ed.image.undo"};
55
56/* -------------------------------------------------------------------- */
60/* This is a non-global static resource,
61 * Maybe it should be exposed as part of the
62 * paint operation, but for now just give a public interface */
64
69
74
77/* -------------------------------------------------------------------- */
92
97 /* Copied from iuser.tile in PaintTile. */
99
101 {
103 }
104 bool operator==(const PaintTileKey &other) const
105 {
106 return x_tile == other.x_tile && y_tile == other.y_tile && image == other.image &&
107 ibuf == other.ibuf && iuser_tile == other.iuser_tile;
108 }
109};
110
111struct PaintTile {
114 /* For 2D image painting the ImageUser uses most of the values.
115 * Even though views and passes are stored they are currently not supported for painting.
116 * For 3D projection painting this only uses a tile & frame number.
117 * The scene pointer must be cleared (or temporarily set it as needed, but leave cleared). */
119 union {
120 float *fp;
122 void *pt;
125 bool valid;
128};
129
130static void ptile_free(PaintTile *ptile)
131{
132 if (ptile->rect.pt) {
133 MEM_freeN(ptile->rect.pt);
134 }
135 if (ptile->mask) {
136 MEM_freeN(ptile->mask);
137 }
138 MEM_freeN(ptile);
139}
140
143
145 {
146 for (PaintTile *ptile : map.values()) {
147 ptile_free(ptile);
148 }
149 }
150};
151
152static void ptile_invalidate_map(PaintTileMap *paint_tile_map)
153{
154 for (PaintTile *ptile : paint_tile_map->map.values()) {
155 ptile->valid = false;
156 }
157}
158
160 Image *image,
161 ImBuf *ibuf,
162 ImageUser *iuser,
163 int x_tile,
164 int y_tile,
165 ushort **r_mask,
166 bool validate)
167{
168 PaintTileKey key;
169 key.ibuf = ibuf;
170 key.image = image;
171 key.iuser_tile = iuser->tile;
172 key.x_tile = x_tile;
173 key.y_tile = y_tile;
174 PaintTile **pptile = paint_tile_map->map.lookup_ptr(key);
175 if (pptile == nullptr) {
176 return nullptr;
177 }
178 PaintTile *ptile = *pptile;
179 if (r_mask) {
180 /* allocate mask if requested. */
181 if (!ptile->mask) {
182 ptile->mask = static_cast<uint16_t *>(
183 MEM_callocN(sizeof(uint16_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), "UndoImageTile.mask"));
184 }
185 *r_mask = ptile->mask;
186 }
187 if (validate) {
188 ptile->valid = true;
189 }
190 return ptile->rect.pt;
191}
192
193/* Set the given buffer data as an owning data of the imbuf's buffer.
194 * Returns the data pointer which was stolen from the imbuf before assignment. */
196{
197 uint8_t *old_buffer_data = IMB_steal_byte_buffer(ibuf);
198 IMB_assign_byte_buffer(ibuf, new_buffer_data, IB_TAKE_OWNERSHIP);
199 return old_buffer_data;
200}
201static float *image_undo_steal_and_assign_float_buffer(ImBuf *ibuf, float *new_buffer_data)
202{
203 float *old_buffer_data = IMB_steal_float_buffer(ibuf);
204 IMB_assign_float_buffer(ibuf, new_buffer_data, IB_TAKE_OWNERSHIP);
205 return old_buffer_data;
206}
207
209 Image *image,
210 ImBuf *ibuf,
211 ImBuf **tmpibuf,
212 ImageUser *iuser,
213 int x_tile,
214 int y_tile,
215 ushort **r_mask,
216 bool **r_valid,
217 bool use_thread_lock,
218 bool find_prev)
219{
220 if (use_thread_lock) {
222 }
223 const bool has_float = (ibuf->float_buffer.data != nullptr);
224
225 /* check if tile is already pushed */
226
227 /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */
228 if (find_prev) {
229 void *data = ED_image_paint_tile_find(
230 paint_tile_map, image, ibuf, iuser, x_tile, y_tile, r_mask, true);
231 if (data) {
232 if (use_thread_lock) {
234 }
235 return data;
236 }
237 }
238
239 if (*tmpibuf == nullptr) {
240 *tmpibuf = imbuf_alloc_temp_tile();
241 }
242
243 PaintTile *ptile = static_cast<PaintTile *>(MEM_callocN(sizeof(PaintTile), "PaintTile"));
244
245 ptile->image = image;
246 ptile->ibuf = ibuf;
247 ptile->iuser = *iuser;
248 ptile->iuser.scene = nullptr;
249
250 ptile->x_tile = x_tile;
251 ptile->y_tile = y_tile;
252
253 /* add mask explicitly here */
254 if (r_mask) {
255 *r_mask = ptile->mask = static_cast<uint16_t *>(
256 MEM_callocN(sizeof(uint16_t) * square_i(ED_IMAGE_UNDO_TILE_SIZE), "PaintTile.mask"));
257 }
258
259 ptile->rect.pt = MEM_callocN((ibuf->float_buffer.data ? sizeof(float[4]) : sizeof(char[4])) *
261 "PaintTile.rect");
262
263 ptile->use_float = has_float;
264 ptile->valid = true;
265
266 if (r_valid) {
267 *r_valid = &ptile->valid;
268 }
269
270 IMB_rectcpy(*tmpibuf,
271 ibuf,
272 0,
273 0,
278
279 if (has_float) {
280 ptile->rect.fp = image_undo_steal_and_assign_float_buffer(*tmpibuf, ptile->rect.fp);
281 }
282 else {
284 }
285
286 PaintTileKey key = {};
287 key.ibuf = ibuf;
288 key.image = image;
289 key.iuser_tile = iuser->tile;
290 key.x_tile = x_tile;
291 key.y_tile = y_tile;
292 PaintTile *existing_tile = nullptr;
293 paint_tile_map->map.add_or_modify(
294 key,
295 [&](PaintTile **pptile) { *pptile = ptile; },
296 [&](PaintTile **pptile) { existing_tile = *pptile; });
297 if (existing_tile) {
298 ptile_free(ptile);
299 ptile = existing_tile;
300 }
301
302 if (use_thread_lock) {
304 }
305 return ptile->rect.pt;
306}
307
308static void ptile_restore_runtime_map(PaintTileMap *paint_tile_map)
309{
310 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
311
312 for (PaintTile *ptile : paint_tile_map->map.values()) {
313 Image *image = ptile->image;
314 ImBuf *ibuf = BKE_image_acquire_ibuf(image, &ptile->iuser, nullptr);
315 const bool has_float = (ibuf->float_buffer.data != nullptr);
316
317 if (has_float) {
318 ptile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, ptile->rect.fp);
319 }
320 else {
321 ptile->rect.byte_ptr = image_undo_steal_and_assign_byte_buffer(tmpibuf,
322 ptile->rect.byte_ptr);
323 }
324
325 /* TODO(sergey): Look into implementing API which does not require such temporary buffer
326 * assignment. */
327 IMB_rectcpy(ibuf,
328 tmpibuf,
329 ptile->x_tile * ED_IMAGE_UNDO_TILE_SIZE,
330 ptile->y_tile * ED_IMAGE_UNDO_TILE_SIZE,
331 0,
332 0,
335
336 if (has_float) {
337 ptile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, ptile->rect.fp);
338 }
339 else {
340 ptile->rect.byte_ptr = image_undo_steal_and_assign_byte_buffer(tmpibuf,
341 ptile->rect.byte_ptr);
342 }
343
344 /* Force OpenGL reload (maybe partial update will operate better?) */
346
347 if (ibuf->float_buffer.data) {
348 ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */
349 }
350 if (ibuf->mipmap[0]) {
351 ibuf->userflags |= IB_MIPMAP_INVALID; /* Force MIP-MAP recreation. */
352 }
354
355 BKE_image_release_ibuf(image, ibuf, nullptr);
356 }
357
358 IMB_freeImBuf(tmpibuf);
359}
360
363/* -------------------------------------------------------------------- */
367static uint32_t index_from_xy(uint32_t tile_x, uint32_t tile_y, const uint32_t tiles_dims[2])
368{
369 BLI_assert(tile_x < tiles_dims[0] && tile_y < tiles_dims[1]);
370 return (tile_y * tiles_dims[0]) + tile_x;
371}
372
374 union {
375 float *fp;
377 void *pt;
379 int users;
380};
381
382static UndoImageTile *utile_alloc(bool has_float)
383{
384 UndoImageTile *utile = static_cast<UndoImageTile *>(
385 MEM_callocN(sizeof(*utile), "ImageUndoTile"));
386 if (has_float) {
387 utile->rect.fp = static_cast<float *>(
388 MEM_mallocN(sizeof(float[4]) * square_i(ED_IMAGE_UNDO_TILE_SIZE), __func__));
389 }
390 else {
391 utile->rect.byte_ptr = static_cast<uint8_t *>(
393 }
394 return utile;
395}
396
398 UndoImageTile *utile, const uint32_t x, const uint32_t y, const ImBuf *ibuf, ImBuf *tmpibuf)
399{
400 const bool has_float = ibuf->float_buffer.data;
401
402 if (has_float) {
403 utile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, utile->rect.fp);
404 }
405 else {
407 }
408
409 /* TODO(sergey): Look into implementing API which does not require such temporary buffer
410 * assignment. */
412
413 if (has_float) {
414 utile->rect.fp = image_undo_steal_and_assign_float_buffer(tmpibuf, utile->rect.fp);
415 }
416 else {
418 }
419}
420
421static void utile_restore(
422 const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf)
423{
424 const bool has_float = ibuf->float_buffer.data;
425 float *prev_rect_float = tmpibuf->float_buffer.data;
426 uint8_t *prev_rect = tmpibuf->byte_buffer.data;
427
428 if (has_float) {
429 tmpibuf->float_buffer.data = utile->rect.fp;
430 }
431 else {
432 tmpibuf->byte_buffer.data = utile->rect.byte_ptr;
433 }
434
435 /* TODO(sergey): Look into implementing API which does not require such temporary buffer
436 * assignment. */
438
439 tmpibuf->float_buffer.data = prev_rect_float;
440 tmpibuf->byte_buffer.data = prev_rect;
441}
442
443static void utile_decref(UndoImageTile *utile)
444{
445 utile->users -= 1;
446 BLI_assert(utile->users >= 0);
447 if (utile->users == 0) {
448 MEM_freeN(utile->rect.pt);
449 MEM_delete(utile);
450 }
451}
452
455/* -------------------------------------------------------------------- */
483
485{
486 UndoImageBuf *ubuf = static_cast<UndoImageBuf *>(MEM_callocN(sizeof(*ubuf), __func__));
487
488 ubuf->image_dims[0] = ibuf->x;
489 ubuf->image_dims[1] = ibuf->y;
490
493
494 ubuf->tiles_len = ubuf->tiles_dims[0] * ubuf->tiles_dims[1];
495 ubuf->tiles = static_cast<UndoImageTile **>(
496 MEM_callocN(sizeof(*ubuf->tiles) * ubuf->tiles_len, __func__));
497
498 STRNCPY(ubuf->ibuf_filepath, ibuf->filepath);
499 ubuf->image_state.source = image->source;
500 ubuf->image_state.use_float = ibuf->float_buffer.data != nullptr;
501
502 return ubuf;
503}
504
505static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf)
506{
507 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
508
509 const bool has_float = ibuf->float_buffer.data;
510 int i = 0;
511 for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) {
512 uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
513 for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) {
514 uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
515
516 BLI_assert(ubuf->tiles[i] == nullptr);
517 UndoImageTile *utile = utile_alloc(has_float);
518 utile->users = 1;
519 utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf);
520 ubuf->tiles[i] = utile;
521
522 i += 1;
523 }
524 }
525
526 BLI_assert(i == ubuf->tiles_len);
527
528 IMB_freeImBuf(tmpibuf);
529}
530
532static void ubuf_ensure_compat_ibuf(const UndoImageBuf *ubuf, ImBuf *ibuf)
533{
534 /* We could have both float and rect buffers,
535 * in this case free the float buffer if it's unused. */
536 if ((ibuf->float_buffer.data != nullptr) && (ubuf->image_state.use_float == false)) {
538 }
539
540 if (ibuf->x == ubuf->image_dims[0] && ibuf->y == ubuf->image_dims[1] &&
541 (ubuf->image_state.use_float ? (void *)ibuf->float_buffer.data :
542 (void *)ibuf->byte_buffer.data))
543 {
544 return;
545 }
546
548 IMB_rect_size_set(ibuf, ubuf->image_dims);
549
550 if (ubuf->image_state.use_float) {
551 imb_addrectfloatImBuf(ibuf, 4);
552 }
553 else {
554 imb_addrectImBuf(ibuf);
555 }
556}
557
558static void ubuf_free(UndoImageBuf *ubuf)
559{
560 UndoImageBuf *ubuf_post = ubuf->post;
561 for (uint i = 0; i < ubuf->tiles_len; i++) {
562 UndoImageTile *utile = ubuf->tiles[i];
563 utile_decref(utile);
564 }
565 MEM_freeN(ubuf->tiles);
566 MEM_freeN(ubuf);
567 if (ubuf_post) {
568 ubuf_free(ubuf_post);
569 }
570}
571
574/* -------------------------------------------------------------------- */
595
596static void uhandle_restore_list(ListBase *undo_handles, bool use_init)
597{
598 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
599
600 LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) {
601 /* Tiles only added to second set of tiles. */
602 Image *image = uh->image_ref.ptr;
603
604 ImBuf *ibuf = BKE_image_acquire_ibuf(image, &uh->iuser, nullptr);
605 if (UNLIKELY(ibuf == nullptr)) {
606 CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2);
607 continue;
608 }
609 bool changed = false;
610 LISTBASE_FOREACH (UndoImageBuf *, ubuf_iter, &uh->buffers) {
611 UndoImageBuf *ubuf = use_init ? ubuf_iter : ubuf_iter->post;
612 ubuf_ensure_compat_ibuf(ubuf, ibuf);
613
614 int i = 0;
615 for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) {
616 uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
617 for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) {
618 uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
619 utile_restore(ubuf->tiles[i], x, y, ibuf, tmpibuf);
620 changed = true;
621 i += 1;
622 }
623 }
624 }
625
626 if (changed) {
627 BKE_image_mark_dirty(image, ibuf);
628 /* TODO(@jbakker): only mark areas that are actually updated to improve performance. */
630
631 if (ibuf->float_buffer.data) {
632 ibuf->userflags |= IB_RECT_INVALID; /* Force recreate of char `rect` */
633 }
634 if (ibuf->mipmap[0]) {
635 ibuf->userflags |= IB_MIPMAP_INVALID; /* Force MIP-MAP recreation. */
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
660/* -------------------------------------------------------------------- */
667 const Image * /*image*/,
668 const char *ibuf_filepath)
669{
670 LISTBASE_FOREACH (UndoImageBuf *, ubuf, &uh->buffers) {
671 if (STREQ(ubuf->ibuf_filepath, ibuf_filepath)) {
672 return ubuf;
673 }
674 }
675 return nullptr;
676}
677
679{
680 BLI_assert(uhandle_lookup_ubuf(uh, image, ibuf->filepath) == nullptr);
681 UndoImageBuf *ubuf = ubuf_from_image_no_tiles(image, ibuf);
682 BLI_addtail(&uh->buffers, ubuf);
683
684 ubuf->post = nullptr;
685
686 return ubuf;
687}
688
690{
691 UndoImageBuf *ubuf = uhandle_lookup_ubuf(uh, image, ibuf->filepath);
692 if (ubuf == nullptr) {
693 ubuf = uhandle_add_ubuf(uh, image, ibuf);
694 }
695 return ubuf;
696}
697
699 const Image *image,
700 int tile_number)
701{
702 LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) {
703 if (STREQ(image->id.name + 2, uh->image_ref.name + 2) && uh->iuser.tile == tile_number) {
704 return uh;
705 }
706 }
707 return nullptr;
708}
709
710static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number)
711{
712 LISTBASE_FOREACH (UndoImageHandle *, uh, undo_handles) {
713 if (image == uh->image_ref.ptr && uh->iuser.tile == tile_number) {
714 return uh;
715 }
716 }
717 return nullptr;
718}
719
720static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image, ImageUser *iuser)
721{
722 BLI_assert(uhandle_lookup(undo_handles, image, iuser->tile) == nullptr);
723 UndoImageHandle *uh = static_cast<UndoImageHandle *>(MEM_callocN(sizeof(*uh), __func__));
724 uh->image_ref.ptr = image;
725 uh->iuser = *iuser;
726 uh->iuser.scene = nullptr;
727 BLI_addtail(undo_handles, uh);
728 return uh;
729}
730
731static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image, ImageUser *iuser)
732{
733 UndoImageHandle *uh = uhandle_lookup(undo_handles, image, iuser->tile);
734 if (uh == nullptr) {
735 uh = uhandle_add(undo_handles, image, iuser);
736 }
737 return uh;
738}
739
742/* -------------------------------------------------------------------- */
761
767 const Image *image,
768 int tile_number,
769 const UndoImageBuf *ubuf)
770{
771 /* Use name lookup because the pointer is cleared for previous steps. */
772 UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image, tile_number);
773 if (uh_prev != nullptr) {
774 UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(uh_prev, image, ubuf->ibuf_filepath);
775 if (ubuf_reference) {
776 ubuf_reference = ubuf_reference->post;
777 if ((ubuf_reference->image_dims[0] == ubuf->image_dims[0]) &&
778 (ubuf_reference->image_dims[1] == ubuf->image_dims[1]))
779 {
780 return ubuf_reference;
781 }
782 }
783 }
784 return nullptr;
785}
786
788{
789 Object *obact = CTX_data_active_object(C);
790
791 ScrArea *area = CTX_wm_area(C);
792 if (area && (area->spacetype == SPACE_IMAGE)) {
793 SpaceImage *sima = (SpaceImage *)area->spacedata.first;
794 if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) {
795 return true;
796 }
797 }
798 else {
799 if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) {
800 return true;
801 }
802 }
803 return false;
804}
805
807{
808 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
809 /* dummy, memory is cleared anyway. */
810 us->is_encode_init = true;
812 us->paint_tile_map = MEM_new<PaintTileMap>(__func__);
813}
814
815static bool image_undosys_step_encode(bContext *C, Main * /*bmain*/, UndoStep *us_p)
816{
817 /* Encoding is done along the way by adding tiles
818 * to the current 'ImageUndoStep' added by encode_init.
819 *
820 * This function ensures there are previous and current states of the image in the undo buffer.
821 */
822 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
823
824 BLI_assert(us->step.data_size == 0);
825
826 if (us->is_encode_init) {
827
828 ImBuf *tmpibuf = imbuf_alloc_temp_tile();
829
830 ImageUndoStep *us_reference = reinterpret_cast<ImageUndoStep *>(
832 while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) {
833 us_reference = reinterpret_cast<ImageUndoStep *>(us_reference->step.prev);
834 }
835
836 /* Initialize undo tiles from paint-tiles (if they exist). */
837 for (PaintTile *ptile : us->paint_tile_map->map.values()) {
838 if (ptile->valid) {
839 UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image, &ptile->iuser);
840 UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf);
841
842 UndoImageTile *utile = static_cast<UndoImageTile *>(
843 MEM_callocN(sizeof(*utile), "UndoImageTile"));
844 utile->users = 1;
845 utile->rect.pt = ptile->rect.pt;
846 ptile->rect.pt = nullptr;
847 const uint tile_index = index_from_xy(ptile->x_tile, ptile->y_tile, ubuf_pre->tiles_dims);
848
849 BLI_assert(ubuf_pre->tiles[tile_index] == nullptr);
850 ubuf_pre->tiles[tile_index] = utile;
851 }
852 ptile_free(ptile);
853 }
854 us->paint_tile_map->map.clear();
855
857 LISTBASE_FOREACH (UndoImageBuf *, ubuf_pre, &uh->buffers) {
858
859 ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, &uh->iuser, nullptr);
860
861 const bool has_float = ibuf->float_buffer.data;
862
863 BLI_assert(ubuf_pre->post == nullptr);
864 ubuf_pre->post = ubuf_from_image_no_tiles(uh->image_ref.ptr, ibuf);
865 UndoImageBuf *ubuf_post = ubuf_pre->post;
866
867 if (ubuf_pre->image_dims[0] != ubuf_post->image_dims[0] ||
868 ubuf_pre->image_dims[1] != ubuf_post->image_dims[1])
869 {
870 ubuf_from_image_all_tiles(ubuf_post, ibuf);
871 }
872 else {
873 /* Search for the previous buffer. */
874 UndoImageBuf *ubuf_reference =
875 (us_reference ? ubuf_lookup_from_reference(
876 us_reference, uh->image_ref.ptr, uh->iuser.tile, ubuf_post) :
877 nullptr);
878
879 int i = 0;
880 for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) {
881 uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
882 for (uint x_tile = 0; x_tile < ubuf_pre->tiles_dims[0]; x_tile += 1) {
883 uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
884
885 if ((ubuf_reference != nullptr) &&
886 ((ubuf_pre->tiles[i] == nullptr) ||
887 /* In this case the paint stroke as has added a tile
888 * which we have a duplicate reference available. */
889 (ubuf_pre->tiles[i]->users == 1)))
890 {
891 if (ubuf_pre->tiles[i] != nullptr) {
892 /* If we have a reference, re-use this single use tile for the post state. */
893 BLI_assert(ubuf_pre->tiles[i]->users == 1);
894 ubuf_post->tiles[i] = ubuf_pre->tiles[i];
895 ubuf_pre->tiles[i] = nullptr;
896 utile_init_from_imbuf(ubuf_post->tiles[i], x, y, ibuf, tmpibuf);
897 }
898 else {
899 BLI_assert(ubuf_post->tiles[i] == nullptr);
900 ubuf_post->tiles[i] = ubuf_reference->tiles[i];
901 ubuf_post->tiles[i]->users += 1;
902 }
903 BLI_assert(ubuf_pre->tiles[i] == nullptr);
904 ubuf_pre->tiles[i] = ubuf_reference->tiles[i];
905 ubuf_pre->tiles[i]->users += 1;
906
907 BLI_assert(ubuf_pre->tiles[i] != nullptr);
908 BLI_assert(ubuf_post->tiles[i] != nullptr);
909 }
910 else {
911 UndoImageTile *utile = utile_alloc(has_float);
912 utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf);
913
914 if (ubuf_pre->tiles[i] != nullptr) {
915 ubuf_post->tiles[i] = utile;
916 utile->users = 1;
917 }
918 else {
919 ubuf_pre->tiles[i] = utile;
920 ubuf_post->tiles[i] = utile;
921 utile->users = 2;
922 }
923 }
924 BLI_assert(ubuf_pre->tiles[i] != nullptr);
925 BLI_assert(ubuf_post->tiles[i] != nullptr);
926 i += 1;
927 }
928 }
929 BLI_assert(i == ubuf_pre->tiles_len);
930 BLI_assert(i == ubuf_post->tiles_len);
931 }
932 BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, nullptr);
933 }
934 }
935
936 IMB_freeImBuf(tmpibuf);
937
938 /* Useful to debug tiles are stored correctly. */
939 if (false) {
940 uhandle_restore_list(&us->handles, false);
941 }
942 }
943 else {
944 BLI_assert(C != nullptr);
945 /* Happens when switching modes. */
948 us->paint_mode = paint_mode;
949 }
950
951 us_p->is_applied = true;
952
953 return true;
954}
955
957{
958 BLI_assert(us->step.is_applied == true);
959 uhandle_restore_list(&us->handles, !is_final);
960 us->step.is_applied = false;
961}
962
964{
965 BLI_assert(us->step.is_applied == false);
966 uhandle_restore_list(&us->handles, false);
967 us->step.is_applied = true;
968}
969
970static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final)
971{
972 /* Walk forward over any applied steps of same type,
973 * then walk back in the next loop, un-applying them. */
974 ImageUndoStep *us_iter = us;
975 while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) {
976 if (us_iter->step.next->is_applied == false) {
977 break;
978 }
979 us_iter = (ImageUndoStep *)us_iter->step.next;
980 }
981 while (us_iter != us || (!is_final && us_iter == us)) {
982 BLI_assert(us_iter->step.type == us->step.type); /* Previous loop ensures this. */
983 image_undosys_step_decode_undo_impl(us_iter, is_final);
984 if (us_iter == us) {
985 break;
986 }
987 us_iter = (ImageUndoStep *)us_iter->step.prev;
988 }
989}
990
992{
993 ImageUndoStep *us_iter = us;
994 while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) {
995 if (us_iter->step.prev->is_applied == true) {
996 break;
997 }
998 us_iter = (ImageUndoStep *)us_iter->step.prev;
999 }
1000 while (us_iter && (us_iter->step.is_applied == false)) {
1002 if (us_iter == us) {
1003 break;
1004 }
1005 us_iter = (ImageUndoStep *)us_iter->step.next;
1006 }
1007}
1008
1010 bContext *C, Main *bmain, UndoStep *us_p, const eUndoStepDir dir, bool is_final)
1011{
1012 /* NOTE: behavior for undo/redo closely matches sculpt undo. */
1013 BLI_assert(dir != STEP_INVALID);
1014
1015 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1016 if (dir == STEP_UNDO) {
1017 image_undosys_step_decode_undo(us, is_final);
1018 }
1019 else if (dir == STEP_REDO) {
1021 }
1022
1023 if (us->paint_mode == PaintMode::Texture3D) {
1025 }
1026
1027 /* Refresh texture slots. */
1029}
1030
1032{
1033 ImageUndoStep *us = (ImageUndoStep *)us_p;
1035
1036 /* Typically this map will have been cleared. */
1037 MEM_delete(us->paint_tile_map);
1038 us->paint_tile_map = nullptr;
1039}
1040
1042 UndoTypeForEachIDRefFn foreach_ID_ref_fn,
1043 void *user_data)
1044{
1045 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1047 foreach_ID_ref_fn(user_data, ((UndoRefID *)&uh->image_ref));
1048 }
1049}
1050
1052{
1053 ut->name = "Image";
1059
1061
1062 /* NOTE: this is actually a confusing case, since it expects a valid context, but only in a
1063 * specific case, see `image_undosys_step_encode` code. We cannot specify
1064 * `UNDOTYPE_FLAG_NEED_CONTEXT_FOR_ENCODE` though, as it can be called with a null context by
1065 * current code. */
1067
1068 ut->step_size = sizeof(ImageUndoStep);
1069}
1070
1073/* -------------------------------------------------------------------- */
1086{
1087 UndoStack *ustack = ED_undo_stack_get();
1088 UndoStep *us_prev = ustack->step_init;
1090 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1091 /* We should always have an undo push started when accessing tiles,
1092 * not doing this means we won't have paint_mode correctly set. */
1093 BLI_assert(us_p == us_prev);
1094 if (us_p != us_prev) {
1095 /* Fallback value until we can be sure this never happens. */
1097 }
1098 return us->paint_tile_map;
1099}
1100
1102{
1103 PaintTileMap *paint_tile_map = reinterpret_cast<ImageUndoStep *>(us)->paint_tile_map;
1104 ptile_restore_runtime_map(paint_tile_map);
1105 ptile_invalidate_map(paint_tile_map);
1106}
1107
1108static ImageUndoStep *image_undo_push_begin(const char *name, PaintMode paint_mode)
1109{
1110 UndoStack *ustack = ED_undo_stack_get();
1111 bContext *C = nullptr; /* special case, we never read from this. */
1113 ImageUndoStep *us = reinterpret_cast<ImageUndoStep *>(us_p);
1115 us->paint_mode = (PaintMode)paint_mode;
1116 return us;
1117}
1118
1119void ED_image_undo_push_begin(const char *name, PaintMode paint_mode)
1120{
1121 image_undo_push_begin(name, paint_mode);
1122}
1123
1125 Image *image,
1126 ImBuf *ibuf,
1127 ImageUser *iuser)
1128{
1130
1131 BLI_assert(BKE_image_get_tile(image, iuser->tile));
1132 UndoImageHandle *uh = uhandle_ensure(&us->handles, image, iuser);
1133 UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf);
1134 BLI_assert(ubuf_pre->post == nullptr);
1135
1136 ImageUndoStep *us_reference = reinterpret_cast<ImageUndoStep *>(
1138 while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) {
1139 us_reference = reinterpret_cast<ImageUndoStep *>(us_reference->step.prev);
1140 }
1141 UndoImageBuf *ubuf_reference = (us_reference ? ubuf_lookup_from_reference(
1142 us_reference, image, iuser->tile, ubuf_pre) :
1143 nullptr);
1144
1145 if (ubuf_reference) {
1146 memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len);
1147 for (uint32_t i = 0; i < ubuf_pre->tiles_len; i++) {
1148 UndoImageTile *utile = ubuf_pre->tiles[i];
1149 utile->users += 1;
1150 }
1151 }
1152 else {
1153 ubuf_from_image_all_tiles(ubuf_pre, ibuf);
1154 }
1155}
1156
1158{
1159 UndoStack *ustack = ED_undo_stack_get();
1160 BKE_undosys_step_push(ustack, nullptr, nullptr);
1163}
1164
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:556
void BKE_image_partial_update_mark_full_update(Image *image)
Mark the whole image to be updated.
PaintMode
Definition BKE_paint.hh:99
PaintMode BKE_paintmode_get_active_from_context(const bContext *C)
Definition paint.cc:506
@ 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:50
#define LISTBASE_FOREACH(type, var, list)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
BLI_INLINE void BLI_listbase_clear(struct ListBase *lb)
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
MINLINE int square_i(int a)
#define STRNCPY(dst, src)
Definition BLI_string.h:593
unsigned short ushort
unsigned int uint
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)
@ 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:108
#define ED_IMAGE_UNDO_TILE_SIZE
Definition ED_paint.hh:107
#define ED_IMAGE_UNDO_TILE_BITS
Definition ED_paint.hh:106
UndoStack * ED_undo_stack_get()
Definition ed_undo.cc:455
void ED_editors_init_for_undo(Main *bmain)
Definition ed_util.cc:64
void imb_freerectfloatImBuf(ImBuf *ibuf)
float * IMB_steal_float_buffer(ImBuf *ibuf)
uint8_t * IMB_steal_byte_buffer(ImBuf *ibuf)
void imb_freerectImbuf_all(ImBuf *ibuf)
void IMB_assign_float_buffer(ImBuf *ibuf, float *buffer_data, ImBufOwnership ownership)
bool imb_addrectImBuf(ImBuf *ibuf, bool initialize_pixels=true)
void IMB_rect_size_set(ImBuf *ibuf, const uint size[2])
Definition rectop.cc:287
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:463
bool imb_addrectfloatImBuf(ImBuf *ibuf, const unsigned int channels, bool initialize_pixels=true)
Contains defines and structs used throughout the imbuf module.
#define IMB_FILEPATH_SIZE
@ IB_TAKE_OWNERSHIP
@ IB_rectfloat
@ IB_rect
@ IB_RECT_INVALID
@ IB_MIPMAP_INVALID
@ IB_DISPLAY_BUFFER_INVALID
Read Guarded memory(de)allocation.
void clear()
Definition BLI_map.hh:989
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:484
ValueIterator values() const
Definition BLI_map.hh:846
auto add_or_modify(const Key &key, const CreateValueF &create_value, const ModifyValueF &modify_value) -> decltype(create_value(nullptr))
Definition BLI_map.hh:457
input_tx image(0, GPU_RGBA16F, Qualifier::WRITE, ImageType::FLOAT_2D, "preview_img") .compute_source("compositor_compute_preview.glsl") .do_static_compilation(true)
struct ImBuf * IMB_allocImBuf(unsigned int, unsigned int, unsigned char, unsigned int)
void IMB_freeImBuf(ImBuf *)
static void utile_decref(UndoImageTile *utile)
static bool image_undosys_step_encode(bContext *C, Main *, UndoStep *us_p)
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:70
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 UndoImageBuf * uhandle_lookup_ubuf(UndoImageHandle *uh, const Image *, const char *ibuf_filepath)
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)
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)
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:87
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 CLG_LogRef LOG
Definition image_undo.cc:54
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:65
static SpinLock paint_tiles_lock
Definition image_undo.cc:63
static void utile_restore(const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf)
const int tile_index
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
bool mode_set_ex(bContext *C, eObjectMode mode, bool use_undo, ReportList *reports)
uint64_t get_default_hash(const T &v)
Definition BLI_hash.hh:219
unsigned short uint16_t
Definition stdint.h:79
unsigned int uint32_t
Definition stdint.h:80
unsigned char uint8_t
Definition stdint.h:78
unsigned __int64 uint64_t
Definition stdint.h:90
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
bool operator==(const PaintTileKey &other) const
Image * image
Definition image_undo.cc:95
uint64_t hash() const
ImBuf * ibuf
Definition image_undo.cc:96
blender::Map< PaintTileKey, PaintTile * > map
uint16_t * mask
Image * image
bool use_float
void * pt
union PaintTile::@497 rect
ImageUser iuser
float * fp
uint8_t * byte_ptr
ImBuf * ibuf
char ibuf_filepath[IMB_FILEPATH_SIZE]
UndoImageBuf * next
UndoImageTile ** tiles
UndoImageBuf * post
UndoImageBuf * prev
uint32_t tiles_dims[2]
uint32_t image_dims[2]
struct UndoImageBuf::@499 image_state
uint32_t tiles_len
ImageUser iuser
UndoRefID_Image image_ref
UndoImageHandle * next
UndoImageHandle * prev
uint8_t * byte_ptr
union UndoImageTile::@498 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
void WM_file_tag_modified()
Definition wm_files.cc:171