This patch contains the work that I did during my week at the Code Quest - adding support for tiled images to Blender. With this patch, images now contain a list of tiles. By default, this just contains one tile, but if the source type is set to Tiled, the user can add additional tiles. When acquiring an ImBuf, the tile to be loaded is specified in the ImageUser. Therefore, code that is not yet aware of tiles will just access the default tile as usual. The filenames of the additional tiles are derived from the original filename according to the UDIM naming scheme - the filename contains an index that is calculated as (1001 + 10*<y coordinate of the tile> + <x coordinate of the tile>), where the x coordinate never goes above 9. Internally, the various tiles are stored in a cache just like sequences. When acquired for the first time, the code will try to load the corresponding file from disk. Alternatively, a new operator can be used to initialize the tile similar to the New Image operator. The following features are supported so far: - Automatic detection and loading of all tiles when opening the first tile (1001) - Saving all tiles - Adding and removing tiles - Filling tiles with generated images - Drawing all tiles in the Image Editor - Viewing a tiled grid even if no image is selected - Rendering tiled images in Eevee - Rendering tiled images in Cycles (in SVM mode) - Automatically skipping loading of unused tiles in Cycles - 2D texture painting (also across tiles) - 3D texture painting (also across tiles, only limitation: individual faces can not cross tile borders) - Assigning custom labels to individual tiles (drawn in the Image Editor instead of the ID) - Different resolutions between tiles There still are some missing features that will be added later (see T72390): - Workbench engine support - Packing/Unpacking support - Baking support - Cycles OSL support - many other Blender features that rely on images Thanks to Brecht for the review and to all who tested the intermediate versions! Differential Revision: https://developer.blender.org/D3509
1087 lines
31 KiB
C
1087 lines
31 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup spimage
|
|
*
|
|
* Overview
|
|
* ========
|
|
*
|
|
* - Each undo step is a #ImageUndoStep
|
|
* - Each #ImageUndoStep stores a list of #UndoImageHandle
|
|
* - Each #UndoImageHandle stores a list of #UndoImageBuf
|
|
* (this is the undo systems equivalent of an #ImBuf).
|
|
* - Each #UndoImageBuf stores an array of #UndoImageTile
|
|
* The tiles are shared between #UndoImageBuf's to avoid duplication.
|
|
*
|
|
* When the undo system manages an image, there will always be a full copy (as a #UndoImageBuf)
|
|
* each new undo step only stores modified tiles.
|
|
*/
|
|
|
|
#include "CLG_log.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_math.h"
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_threads.h"
|
|
|
|
#include "DNA_image_types.h"
|
|
#include "DNA_windowmanager_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_screen_types.h"
|
|
#include "DNA_space_types.h"
|
|
|
|
#include "IMB_imbuf.h"
|
|
#include "IMB_imbuf_types.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_image.h"
|
|
#include "BKE_paint.h"
|
|
#include "BKE_undo_system.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
|
|
#include "ED_paint.h"
|
|
#include "ED_undo.h"
|
|
#include "ED_util.h"
|
|
#include "ED_object.h"
|
|
|
|
#include "GPU_draw.h"
|
|
|
|
#include "WM_api.h"
|
|
|
|
static CLG_LogRef LOG = {"ed.image.undo"};
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Thread Locking
|
|
* \{ */
|
|
|
|
/* This is a non-global static resource,
|
|
* Maybe it should be exposed as part of the
|
|
* paint operation, but for now just give a public interface */
|
|
static SpinLock paint_tiles_lock;
|
|
|
|
void ED_image_paint_tile_lock_init(void)
|
|
{
|
|
BLI_spin_init(&paint_tiles_lock);
|
|
}
|
|
|
|
void ED_image_paint_tile_lock_end(void)
|
|
{
|
|
BLI_spin_end(&paint_tiles_lock);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Paint Tiles
|
|
*
|
|
* Created on demand while painting,
|
|
* use to access the previous state for some paint operations.
|
|
*
|
|
* These buffers are also used for undo when available.
|
|
*
|
|
* \{ */
|
|
|
|
static ImBuf *imbuf_alloc_temp_tile(void)
|
|
{
|
|
return IMB_allocImBuf(
|
|
ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE, 32, IB_rectfloat | IB_rect);
|
|
}
|
|
|
|
typedef struct PaintTile {
|
|
struct PaintTile *next, *prev;
|
|
Image *image;
|
|
ImBuf *ibuf;
|
|
int tile_number;
|
|
union {
|
|
float *fp;
|
|
uint *uint;
|
|
void *pt;
|
|
} rect;
|
|
ushort *mask;
|
|
bool valid;
|
|
bool use_float;
|
|
int x_tile, y_tile;
|
|
} PaintTile;
|
|
|
|
static void ptile_free(PaintTile *ptile)
|
|
{
|
|
if (ptile->rect.pt) {
|
|
MEM_freeN(ptile->rect.pt);
|
|
}
|
|
if (ptile->mask) {
|
|
MEM_freeN(ptile->mask);
|
|
}
|
|
MEM_freeN(ptile);
|
|
}
|
|
|
|
static void ptile_free_list(ListBase *paint_tiles)
|
|
{
|
|
for (PaintTile *ptile = paint_tiles->first, *ptile_next; ptile; ptile = ptile_next) {
|
|
ptile_next = ptile->next;
|
|
ptile_free(ptile);
|
|
}
|
|
BLI_listbase_clear(paint_tiles);
|
|
}
|
|
|
|
static void ptile_invalidate_list(ListBase *paint_tiles)
|
|
{
|
|
for (PaintTile *ptile = paint_tiles->first; ptile; ptile = ptile->next) {
|
|
ptile->valid = false;
|
|
}
|
|
}
|
|
|
|
void *ED_image_paint_tile_find(ListBase *paint_tiles,
|
|
Image *image,
|
|
ImBuf *ibuf,
|
|
int tile_number,
|
|
int x_tile,
|
|
int y_tile,
|
|
ushort **r_mask,
|
|
bool validate)
|
|
{
|
|
for (PaintTile *ptile = paint_tiles->first; ptile; ptile = ptile->next) {
|
|
if (ptile->x_tile == x_tile && ptile->y_tile == y_tile) {
|
|
if (ptile->image == image && ptile->ibuf == ibuf && ptile->tile_number == tile_number) {
|
|
if (r_mask) {
|
|
/* allocate mask if requested. */
|
|
if (!ptile->mask) {
|
|
ptile->mask = MEM_callocN(sizeof(ushort) * SQUARE(ED_IMAGE_UNDO_TILE_SIZE),
|
|
"UndoImageTile.mask");
|
|
}
|
|
*r_mask = ptile->mask;
|
|
}
|
|
if (validate) {
|
|
ptile->valid = true;
|
|
}
|
|
return ptile->rect.pt;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void *ED_image_paint_tile_push(ListBase *paint_tiles,
|
|
Image *image,
|
|
ImBuf *ibuf,
|
|
ImBuf **tmpibuf,
|
|
int tile_number,
|
|
int x_tile,
|
|
int y_tile,
|
|
ushort **r_mask,
|
|
bool **r_valid,
|
|
bool use_thread_lock,
|
|
bool find_prev)
|
|
{
|
|
const bool has_float = (ibuf->rect_float != NULL);
|
|
|
|
/* check if tile is already pushed */
|
|
|
|
/* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */
|
|
if (find_prev) {
|
|
void *data = ED_image_paint_tile_find(
|
|
paint_tiles, image, ibuf, tile_number, x_tile, y_tile, r_mask, true);
|
|
if (data) {
|
|
return data;
|
|
}
|
|
}
|
|
|
|
if (*tmpibuf == NULL) {
|
|
*tmpibuf = imbuf_alloc_temp_tile();
|
|
}
|
|
|
|
PaintTile *ptile = MEM_callocN(sizeof(PaintTile), "PaintTile");
|
|
|
|
ptile->image = image;
|
|
ptile->ibuf = ibuf;
|
|
ptile->tile_number = tile_number;
|
|
|
|
ptile->x_tile = x_tile;
|
|
ptile->y_tile = y_tile;
|
|
|
|
/* add mask explicitly here */
|
|
if (r_mask) {
|
|
*r_mask = ptile->mask = MEM_callocN(sizeof(ushort) * SQUARE(ED_IMAGE_UNDO_TILE_SIZE),
|
|
"PaintTile.mask");
|
|
}
|
|
|
|
ptile->rect.pt = MEM_mapallocN((ibuf->rect_float ? sizeof(float[4]) : sizeof(char[4])) *
|
|
SQUARE(ED_IMAGE_UNDO_TILE_SIZE),
|
|
"PaintTile.rect");
|
|
|
|
ptile->use_float = has_float;
|
|
ptile->valid = true;
|
|
|
|
if (r_valid) {
|
|
*r_valid = &ptile->valid;
|
|
}
|
|
|
|
IMB_rectcpy(*tmpibuf,
|
|
ibuf,
|
|
0,
|
|
0,
|
|
x_tile * ED_IMAGE_UNDO_TILE_SIZE,
|
|
y_tile * ED_IMAGE_UNDO_TILE_SIZE,
|
|
ED_IMAGE_UNDO_TILE_SIZE,
|
|
ED_IMAGE_UNDO_TILE_SIZE);
|
|
|
|
if (has_float) {
|
|
SWAP(float *, ptile->rect.fp, (*tmpibuf)->rect_float);
|
|
}
|
|
else {
|
|
SWAP(uint *, ptile->rect.uint, (*tmpibuf)->rect);
|
|
}
|
|
|
|
if (use_thread_lock) {
|
|
BLI_spin_lock(&paint_tiles_lock);
|
|
}
|
|
BLI_addtail(paint_tiles, ptile);
|
|
|
|
if (use_thread_lock) {
|
|
BLI_spin_unlock(&paint_tiles_lock);
|
|
}
|
|
return ptile->rect.pt;
|
|
}
|
|
|
|
static void ptile_restore_runtime_list(ListBase *paint_tiles)
|
|
{
|
|
ImBuf *tmpibuf = imbuf_alloc_temp_tile();
|
|
|
|
for (PaintTile *ptile = paint_tiles->first; ptile; ptile = ptile->next) {
|
|
Image *image = ptile->image;
|
|
ImageUser iuser;
|
|
BKE_imageuser_default(&iuser);
|
|
iuser.tile = ptile->tile_number;
|
|
ImBuf *ibuf = BKE_image_acquire_ibuf(image, &iuser, NULL);
|
|
const bool has_float = (ibuf->rect_float != NULL);
|
|
|
|
if (has_float) {
|
|
SWAP(float *, ptile->rect.fp, tmpibuf->rect_float);
|
|
}
|
|
else {
|
|
SWAP(uint *, ptile->rect.uint, tmpibuf->rect);
|
|
}
|
|
|
|
IMB_rectcpy(ibuf,
|
|
tmpibuf,
|
|
ptile->x_tile * ED_IMAGE_UNDO_TILE_SIZE,
|
|
ptile->y_tile * ED_IMAGE_UNDO_TILE_SIZE,
|
|
0,
|
|
0,
|
|
ED_IMAGE_UNDO_TILE_SIZE,
|
|
ED_IMAGE_UNDO_TILE_SIZE);
|
|
|
|
if (has_float) {
|
|
SWAP(float *, ptile->rect.fp, tmpibuf->rect_float);
|
|
}
|
|
else {
|
|
SWAP(uint *, ptile->rect.uint, tmpibuf->rect);
|
|
}
|
|
|
|
GPU_free_image(image); /* force OpenGL reload (maybe partial update will operate better?) */
|
|
if (ibuf->rect_float) {
|
|
ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */
|
|
}
|
|
if (ibuf->mipmap[0]) {
|
|
ibuf->userflags |= IB_MIPMAP_INVALID; /* force mip-map recreation. */
|
|
}
|
|
ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
|
|
|
|
BKE_image_release_ibuf(image, ibuf, NULL);
|
|
}
|
|
|
|
IMB_freeImBuf(tmpibuf);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Image Undo Tile
|
|
* \{ */
|
|
|
|
static uint index_from_xy(uint tile_x, uint tile_y, const uint tiles_dims[2])
|
|
{
|
|
BLI_assert(tile_x < tiles_dims[0] && tile_y < tiles_dims[1]);
|
|
return (tile_y * tiles_dims[0]) + tile_x;
|
|
}
|
|
|
|
typedef struct UndoImageTile {
|
|
union {
|
|
float *fp;
|
|
uint *uint;
|
|
void *pt;
|
|
} rect;
|
|
int users;
|
|
} UndoImageTile;
|
|
|
|
static UndoImageTile *utile_alloc(bool has_float)
|
|
{
|
|
UndoImageTile *utile = MEM_callocN(sizeof(*utile), "ImageUndoTile");
|
|
if (has_float) {
|
|
utile->rect.fp = MEM_mallocN(sizeof(float[4]) * SQUARE(ED_IMAGE_UNDO_TILE_SIZE), __func__);
|
|
}
|
|
else {
|
|
utile->rect.uint = MEM_mallocN(sizeof(uint) * SQUARE(ED_IMAGE_UNDO_TILE_SIZE), __func__);
|
|
}
|
|
return utile;
|
|
}
|
|
|
|
static void utile_init_from_imbuf(
|
|
UndoImageTile *utile, const uint x, const uint y, const ImBuf *ibuf, ImBuf *tmpibuf)
|
|
{
|
|
const bool has_float = ibuf->rect_float;
|
|
|
|
if (has_float) {
|
|
SWAP(float *, utile->rect.fp, tmpibuf->rect_float);
|
|
}
|
|
else {
|
|
SWAP(uint *, utile->rect.uint, tmpibuf->rect);
|
|
}
|
|
|
|
IMB_rectcpy(tmpibuf, ibuf, 0, 0, x, y, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE);
|
|
|
|
if (has_float) {
|
|
SWAP(float *, utile->rect.fp, tmpibuf->rect_float);
|
|
}
|
|
else {
|
|
SWAP(uint *, utile->rect.uint, tmpibuf->rect);
|
|
}
|
|
}
|
|
|
|
static void utile_restore(
|
|
const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf)
|
|
{
|
|
const bool has_float = ibuf->rect_float;
|
|
float *prev_rect_float = tmpibuf->rect_float;
|
|
uint *prev_rect = tmpibuf->rect;
|
|
|
|
if (has_float) {
|
|
tmpibuf->rect_float = utile->rect.fp;
|
|
}
|
|
else {
|
|
tmpibuf->rect = utile->rect.uint;
|
|
}
|
|
|
|
IMB_rectcpy(ibuf, tmpibuf, x, y, 0, 0, ED_IMAGE_UNDO_TILE_SIZE, ED_IMAGE_UNDO_TILE_SIZE);
|
|
|
|
tmpibuf->rect_float = prev_rect_float;
|
|
tmpibuf->rect = prev_rect;
|
|
}
|
|
|
|
static void utile_decref(UndoImageTile *utile)
|
|
{
|
|
utile->users -= 1;
|
|
BLI_assert(utile->users >= 0);
|
|
if (utile->users == 0) {
|
|
MEM_freeN(utile->rect.pt);
|
|
MEM_freeN(utile);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Image Undo Buffer
|
|
* \{ */
|
|
|
|
typedef struct UndoImageBuf {
|
|
struct UndoImageBuf *next, *prev;
|
|
|
|
/**
|
|
* The buffer after the undo step has executed.
|
|
*/
|
|
struct UndoImageBuf *post;
|
|
|
|
char ibuf_name[IMB_FILENAME_SIZE];
|
|
|
|
UndoImageTile **tiles;
|
|
|
|
/** Can calculate these from dims, just for convenience. */
|
|
uint tiles_len;
|
|
uint tiles_dims[2];
|
|
|
|
uint image_dims[2];
|
|
|
|
/** Store variables from the image. */
|
|
struct {
|
|
short source;
|
|
bool use_float;
|
|
char gen_type;
|
|
} image_state;
|
|
|
|
} UndoImageBuf;
|
|
|
|
static UndoImageBuf *ubuf_from_image_no_tiles(Image *image, const ImBuf *ibuf)
|
|
{
|
|
UndoImageBuf *ubuf = MEM_callocN(sizeof(*ubuf), __func__);
|
|
|
|
ubuf->image_dims[0] = ibuf->x;
|
|
ubuf->image_dims[1] = ibuf->y;
|
|
|
|
ubuf->tiles_dims[0] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[0]);
|
|
ubuf->tiles_dims[1] = ED_IMAGE_UNDO_TILE_NUMBER(ubuf->image_dims[1]);
|
|
|
|
ubuf->tiles_len = ubuf->tiles_dims[0] * ubuf->tiles_dims[1];
|
|
ubuf->tiles = MEM_callocN(sizeof(*ubuf->tiles) * ubuf->tiles_len, __func__);
|
|
|
|
BLI_strncpy(ubuf->ibuf_name, ibuf->name, sizeof(ubuf->ibuf_name));
|
|
ubuf->image_state.gen_type = image->gen_type;
|
|
ubuf->image_state.source = image->source;
|
|
ubuf->image_state.use_float = ibuf->rect_float != NULL;
|
|
|
|
return ubuf;
|
|
}
|
|
|
|
static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf)
|
|
{
|
|
ImBuf *tmpibuf = imbuf_alloc_temp_tile();
|
|
|
|
const bool has_float = ibuf->rect_float;
|
|
int i = 0;
|
|
for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) {
|
|
uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
|
|
for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) {
|
|
uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
|
|
|
|
BLI_assert(ubuf->tiles[i] == NULL);
|
|
UndoImageTile *utile = utile_alloc(has_float);
|
|
utile->users = 1;
|
|
utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf);
|
|
ubuf->tiles[i] = utile;
|
|
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
BLI_assert(i == ubuf->tiles_len);
|
|
|
|
IMB_freeImBuf(tmpibuf);
|
|
}
|
|
|
|
/** Ensure we can copy the ubuf into the ibuf. */
|
|
static void ubuf_ensure_compat_ibuf(const UndoImageBuf *ubuf, ImBuf *ibuf)
|
|
{
|
|
/* We could have both float and rect buffers,
|
|
* in this case free the float buffer if it's unused. */
|
|
if ((ibuf->rect_float != NULL) && (ubuf->image_state.use_float == false)) {
|
|
imb_freerectfloatImBuf(ibuf);
|
|
}
|
|
|
|
if (ibuf->x == ubuf->image_dims[0] && ibuf->y == ubuf->image_dims[1] &&
|
|
(ubuf->image_state.use_float ? (void *)ibuf->rect_float : (void *)ibuf->rect)) {
|
|
return;
|
|
}
|
|
|
|
imb_freerectImbuf_all(ibuf);
|
|
IMB_rect_size_set(ibuf, ubuf->image_dims);
|
|
|
|
if (ubuf->image_state.use_float) {
|
|
imb_addrectfloatImBuf(ibuf);
|
|
}
|
|
else {
|
|
imb_addrectImBuf(ibuf);
|
|
}
|
|
}
|
|
|
|
static void ubuf_free(UndoImageBuf *ubuf)
|
|
{
|
|
UndoImageBuf *ubuf_post = ubuf->post;
|
|
for (uint i = 0; i < ubuf->tiles_len; i++) {
|
|
UndoImageTile *utile = ubuf->tiles[i];
|
|
utile_decref(utile);
|
|
}
|
|
MEM_freeN(ubuf->tiles);
|
|
MEM_freeN(ubuf);
|
|
if (ubuf_post) {
|
|
ubuf_free(ubuf_post);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Image Undo Handle
|
|
* \{ */
|
|
|
|
typedef struct UndoImageHandle {
|
|
struct UndoImageHandle *next, *prev;
|
|
|
|
/** Each undo handle refers to a single image which may have multiple buffers. */
|
|
UndoRefID_Image image_ref;
|
|
|
|
/** Each tile of a tiled image has its own UndoImageHandle.
|
|
* The tile number of this IUser is used to distinguish them.
|
|
*/
|
|
ImageUser iuser;
|
|
|
|
/**
|
|
* List of #UndoImageBuf's to support multiple buffers per image.
|
|
*/
|
|
ListBase buffers;
|
|
|
|
} UndoImageHandle;
|
|
|
|
static void uhandle_restore_list(ListBase *undo_handles, bool use_init)
|
|
{
|
|
ImBuf *tmpibuf = imbuf_alloc_temp_tile();
|
|
|
|
for (UndoImageHandle *uh = undo_handles->first; uh; uh = uh->next) {
|
|
/* Tiles only added to second set of tiles. */
|
|
Image *image = uh->image_ref.ptr;
|
|
|
|
ImBuf *ibuf = BKE_image_acquire_ibuf(image, &uh->iuser, NULL);
|
|
if (UNLIKELY(ibuf == NULL)) {
|
|
CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2);
|
|
continue;
|
|
}
|
|
bool changed = false;
|
|
for (UndoImageBuf *ubuf_iter = uh->buffers.first; ubuf_iter; ubuf_iter = ubuf_iter->next) {
|
|
UndoImageBuf *ubuf = use_init ? ubuf_iter : ubuf_iter->post;
|
|
ubuf_ensure_compat_ibuf(ubuf, ibuf);
|
|
|
|
int i = 0;
|
|
for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) {
|
|
uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
|
|
for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) {
|
|
uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
|
|
utile_restore(ubuf->tiles[i], x, y, ibuf, tmpibuf);
|
|
changed = true;
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
BKE_image_mark_dirty(image, ibuf);
|
|
GPU_free_image(image); /* force OpenGL reload */
|
|
|
|
if (ibuf->rect_float) {
|
|
ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */
|
|
}
|
|
if (ibuf->mipmap[0]) {
|
|
ibuf->userflags |= IB_MIPMAP_INVALID; /* force mip-map recreation. */
|
|
}
|
|
ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID;
|
|
|
|
DEG_id_tag_update(&image->id, 0);
|
|
}
|
|
BKE_image_release_ibuf(image, ibuf, NULL);
|
|
}
|
|
|
|
IMB_freeImBuf(tmpibuf);
|
|
}
|
|
|
|
static void uhandle_free_list(ListBase *undo_handles)
|
|
{
|
|
LISTBASE_FOREACH_MUTABLE (UndoImageHandle *, uh, undo_handles) {
|
|
LISTBASE_FOREACH_MUTABLE (UndoImageBuf *, ubuf, &uh->buffers) {
|
|
ubuf_free(ubuf);
|
|
}
|
|
MEM_freeN(uh);
|
|
}
|
|
BLI_listbase_clear(undo_handles);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Image Undo Internal Utilities
|
|
* \{ */
|
|
|
|
/** #UndoImageHandle utilities */
|
|
|
|
static UndoImageBuf *uhandle_lookup_ubuf(UndoImageHandle *uh,
|
|
const Image *UNUSED(image),
|
|
const char *ibuf_name)
|
|
{
|
|
for (UndoImageBuf *ubuf = uh->buffers.first; ubuf; ubuf = ubuf->next) {
|
|
if (STREQ(ubuf->ibuf_name, ibuf_name)) {
|
|
return ubuf;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static UndoImageBuf *uhandle_add_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf)
|
|
{
|
|
BLI_assert(uhandle_lookup_ubuf(uh, image, ibuf->name) == NULL);
|
|
UndoImageBuf *ubuf = ubuf_from_image_no_tiles(image, ibuf);
|
|
BLI_addtail(&uh->buffers, ubuf);
|
|
|
|
ubuf->post = NULL;
|
|
|
|
return ubuf;
|
|
}
|
|
|
|
static UndoImageBuf *uhandle_ensure_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf)
|
|
{
|
|
UndoImageBuf *ubuf = uhandle_lookup_ubuf(uh, image, ibuf->name);
|
|
if (ubuf == NULL) {
|
|
ubuf = uhandle_add_ubuf(uh, image, ibuf);
|
|
}
|
|
return ubuf;
|
|
}
|
|
|
|
static UndoImageHandle *uhandle_lookup_by_name(ListBase *undo_handles,
|
|
const Image *image,
|
|
int tile_number)
|
|
{
|
|
for (UndoImageHandle *uh = undo_handles->first; uh; uh = uh->next) {
|
|
if (STREQ(image->id.name + 2, uh->image_ref.name + 2) && uh->iuser.tile == tile_number) {
|
|
return uh;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image, int tile_number)
|
|
{
|
|
for (UndoImageHandle *uh = undo_handles->first; uh; uh = uh->next) {
|
|
if (image == uh->image_ref.ptr && uh->iuser.tile == tile_number) {
|
|
return uh;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image, int tile_number)
|
|
{
|
|
BLI_assert(uhandle_lookup(undo_handles, image, tile_number) == NULL);
|
|
UndoImageHandle *uh = MEM_callocN(sizeof(*uh), __func__);
|
|
uh->image_ref.ptr = image;
|
|
uh->iuser.ok = 1;
|
|
uh->iuser.tile = tile_number;
|
|
BLI_addtail(undo_handles, uh);
|
|
return uh;
|
|
}
|
|
|
|
static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image, int tile_number)
|
|
{
|
|
UndoImageHandle *uh = uhandle_lookup(undo_handles, image, tile_number);
|
|
if (uh == NULL) {
|
|
uh = uhandle_add(undo_handles, image, tile_number);
|
|
}
|
|
return uh;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Implements ED Undo System
|
|
* \{ */
|
|
|
|
typedef struct ImageUndoStep {
|
|
UndoStep step;
|
|
|
|
/** #UndoImageHandle */
|
|
ListBase handles;
|
|
|
|
/**
|
|
* #PaintTile
|
|
* Run-time only data (active during a paint stroke).
|
|
*/
|
|
ListBase paint_tiles;
|
|
|
|
bool is_encode_init;
|
|
ePaintMode paint_mode;
|
|
|
|
} ImageUndoStep;
|
|
|
|
/**
|
|
* Find the previous undo buffer from this one.
|
|
* \note We could look into undo steps even further back.
|
|
*/
|
|
static UndoImageBuf *ubuf_lookup_from_reference(ImageUndoStep *us_prev,
|
|
const Image *image,
|
|
int tile_number,
|
|
const UndoImageBuf *ubuf)
|
|
{
|
|
/* Use name lookup because because the pointer is cleared for previous steps. */
|
|
UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image, tile_number);
|
|
if (uh_prev != NULL) {
|
|
UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(uh_prev, image, ubuf->ibuf_name);
|
|
if (ubuf_reference) {
|
|
ubuf_reference = ubuf_reference->post;
|
|
if ((ubuf_reference->image_dims[0] == ubuf->image_dims[0]) &&
|
|
(ubuf_reference->image_dims[1] == ubuf->image_dims[1])) {
|
|
return ubuf_reference;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool image_undosys_poll(bContext *C)
|
|
{
|
|
Object *obact = CTX_data_active_object(C);
|
|
|
|
ScrArea *sa = CTX_wm_area(C);
|
|
if (sa && (sa->spacetype == SPACE_IMAGE)) {
|
|
SpaceImage *sima = (SpaceImage *)sa->spacedata.first;
|
|
if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) {
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void image_undosys_step_encode_init(struct bContext *UNUSED(C), UndoStep *us_p)
|
|
{
|
|
ImageUndoStep *us = (ImageUndoStep *)us_p;
|
|
/* dummy, memory is cleared anyway. */
|
|
us->is_encode_init = true;
|
|
BLI_listbase_clear(&us->handles);
|
|
BLI_listbase_clear(&us->paint_tiles);
|
|
}
|
|
|
|
static bool image_undosys_step_encode(struct bContext *C,
|
|
struct Main *UNUSED(bmain),
|
|
UndoStep *us_p)
|
|
{
|
|
/* Encoding is done along the way by adding tiles
|
|
* to the current 'ImageUndoStep' added by encode_init.
|
|
*
|
|
* This function ensures there are previous and current states of the image in the undo buffer.
|
|
*/
|
|
ImageUndoStep *us = (ImageUndoStep *)us_p;
|
|
|
|
BLI_assert(us->step.data_size == 0);
|
|
|
|
if (us->is_encode_init) {
|
|
|
|
ImBuf *tmpibuf = imbuf_alloc_temp_tile();
|
|
|
|
ImageUndoStep *us_reference = (ImageUndoStep *)ED_undo_stack_get()->step_active;
|
|
while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) {
|
|
us_reference = (ImageUndoStep *)us_reference->step.prev;
|
|
}
|
|
|
|
/* Initialize undo tiles from ptiles (if they exist). */
|
|
for (PaintTile *ptile = us->paint_tiles.first, *ptile_next; ptile; ptile = ptile_next) {
|
|
if (ptile->valid) {
|
|
UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image, ptile->tile_number);
|
|
UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf);
|
|
|
|
UndoImageTile *utile = MEM_callocN(sizeof(*utile), "UndoImageTile");
|
|
utile->users = 1;
|
|
utile->rect.pt = ptile->rect.pt;
|
|
ptile->rect.pt = NULL;
|
|
const uint tile_index = index_from_xy(ptile->x_tile, ptile->y_tile, ubuf_pre->tiles_dims);
|
|
|
|
BLI_assert(ubuf_pre->tiles[tile_index] == NULL);
|
|
ubuf_pre->tiles[tile_index] = utile;
|
|
}
|
|
ptile_next = ptile->next;
|
|
ptile_free(ptile);
|
|
}
|
|
BLI_listbase_clear(&us->paint_tiles);
|
|
|
|
for (UndoImageHandle *uh = us->handles.first; uh; uh = uh->next) {
|
|
for (UndoImageBuf *ubuf_pre = uh->buffers.first; ubuf_pre; ubuf_pre = ubuf_pre->next) {
|
|
|
|
ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, &uh->iuser, NULL);
|
|
|
|
const bool has_float = ibuf->rect_float;
|
|
|
|
BLI_assert(ubuf_pre->post == NULL);
|
|
ubuf_pre->post = ubuf_from_image_no_tiles(uh->image_ref.ptr, ibuf);
|
|
UndoImageBuf *ubuf_post = ubuf_pre->post;
|
|
|
|
if (ubuf_pre->image_dims[0] != ubuf_post->image_dims[0] ||
|
|
ubuf_pre->image_dims[1] != ubuf_post->image_dims[1]) {
|
|
ubuf_from_image_all_tiles(ubuf_post, ibuf);
|
|
}
|
|
else {
|
|
/* Search for the previous buffer. */
|
|
UndoImageBuf *ubuf_reference =
|
|
(us_reference ? ubuf_lookup_from_reference(
|
|
us_reference, uh->image_ref.ptr, uh->iuser.tile, ubuf_post) :
|
|
NULL);
|
|
|
|
int i = 0;
|
|
for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) {
|
|
uint y = y_tile << ED_IMAGE_UNDO_TILE_BITS;
|
|
for (uint x_tile = 0; x_tile < ubuf_pre->tiles_dims[0]; x_tile += 1) {
|
|
uint x = x_tile << ED_IMAGE_UNDO_TILE_BITS;
|
|
|
|
if ((ubuf_reference != NULL) && ((ubuf_pre->tiles[i] == NULL) ||
|
|
/* In this case the paint stroke as has added a tile
|
|
* which we have a duplicate reference available. */
|
|
(ubuf_pre->tiles[i]->users == 1))) {
|
|
if (ubuf_pre->tiles[i] != NULL) {
|
|
/* If we have a reference, re-use this single use tile for the post state. */
|
|
BLI_assert(ubuf_pre->tiles[i]->users == 1);
|
|
ubuf_post->tiles[i] = ubuf_pre->tiles[i];
|
|
ubuf_pre->tiles[i] = NULL;
|
|
utile_init_from_imbuf(ubuf_post->tiles[i], x, y, ibuf, tmpibuf);
|
|
}
|
|
else {
|
|
BLI_assert(ubuf_post->tiles[i] == NULL);
|
|
ubuf_post->tiles[i] = ubuf_reference->tiles[i];
|
|
ubuf_post->tiles[i]->users += 1;
|
|
}
|
|
BLI_assert(ubuf_pre->tiles[i] == NULL);
|
|
ubuf_pre->tiles[i] = ubuf_reference->tiles[i];
|
|
ubuf_pre->tiles[i]->users += 1;
|
|
|
|
BLI_assert(ubuf_pre->tiles[i] != NULL);
|
|
BLI_assert(ubuf_post->tiles[i] != NULL);
|
|
}
|
|
else {
|
|
UndoImageTile *utile = utile_alloc(has_float);
|
|
utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf);
|
|
|
|
if (ubuf_pre->tiles[i] != NULL) {
|
|
ubuf_post->tiles[i] = utile;
|
|
utile->users = 1;
|
|
}
|
|
else {
|
|
ubuf_pre->tiles[i] = utile;
|
|
ubuf_post->tiles[i] = utile;
|
|
utile->users = 2;
|
|
}
|
|
}
|
|
BLI_assert(ubuf_pre->tiles[i] != NULL);
|
|
BLI_assert(ubuf_post->tiles[i] != NULL);
|
|
i += 1;
|
|
}
|
|
}
|
|
BLI_assert(i == ubuf_pre->tiles_len);
|
|
BLI_assert(i == ubuf_post->tiles_len);
|
|
}
|
|
BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, NULL);
|
|
}
|
|
}
|
|
|
|
IMB_freeImBuf(tmpibuf);
|
|
|
|
/* Useful to debug tiles are stored correctly. */
|
|
if (false) {
|
|
uhandle_restore_list(&us->handles, false);
|
|
}
|
|
}
|
|
else {
|
|
/* Happens when switching modes. */
|
|
ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C);
|
|
BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D));
|
|
us->paint_mode = paint_mode;
|
|
}
|
|
|
|
us_p->is_applied = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void image_undosys_step_decode_undo_impl(ImageUndoStep *us, bool is_final)
|
|
{
|
|
BLI_assert(us->step.is_applied == true);
|
|
uhandle_restore_list(&us->handles, !is_final);
|
|
us->step.is_applied = false;
|
|
}
|
|
|
|
static void image_undosys_step_decode_redo_impl(ImageUndoStep *us)
|
|
{
|
|
BLI_assert(us->step.is_applied == false);
|
|
uhandle_restore_list(&us->handles, false);
|
|
us->step.is_applied = true;
|
|
}
|
|
|
|
static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final)
|
|
{
|
|
ImageUndoStep *us_iter = us;
|
|
while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) {
|
|
if (us_iter->step.next->is_applied == false) {
|
|
break;
|
|
}
|
|
us_iter = (ImageUndoStep *)us_iter->step.next;
|
|
}
|
|
while (us_iter != us || (!is_final && us_iter == us)) {
|
|
|
|
image_undosys_step_decode_undo_impl(us_iter, is_final);
|
|
if (us_iter == us) {
|
|
break;
|
|
}
|
|
us_iter = (ImageUndoStep *)us_iter->step.prev;
|
|
}
|
|
}
|
|
|
|
static void image_undosys_step_decode_redo(ImageUndoStep *us)
|
|
{
|
|
ImageUndoStep *us_iter = us;
|
|
while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) {
|
|
if (us_iter->step.prev->is_applied == true) {
|
|
break;
|
|
}
|
|
us_iter = (ImageUndoStep *)us_iter->step.prev;
|
|
}
|
|
while (us_iter && (us_iter->step.is_applied == false)) {
|
|
image_undosys_step_decode_redo_impl(us_iter);
|
|
if (us_iter == us) {
|
|
break;
|
|
}
|
|
us_iter = (ImageUndoStep *)us_iter->step.next;
|
|
}
|
|
}
|
|
|
|
static void image_undosys_step_decode(
|
|
struct bContext *C, struct Main *bmain, UndoStep *us_p, int dir, bool is_final)
|
|
{
|
|
ImageUndoStep *us = (ImageUndoStep *)us_p;
|
|
if (dir < 0) {
|
|
image_undosys_step_decode_undo(us, is_final);
|
|
}
|
|
else {
|
|
image_undosys_step_decode_redo(us);
|
|
}
|
|
|
|
if (us->paint_mode == PAINT_MODE_TEXTURE_3D) {
|
|
ED_object_mode_set(C, OB_MODE_TEXTURE_PAINT);
|
|
}
|
|
|
|
/* Refresh texture slots. */
|
|
ED_editors_init_for_undo(bmain);
|
|
}
|
|
|
|
static void image_undosys_step_free(UndoStep *us_p)
|
|
{
|
|
ImageUndoStep *us = (ImageUndoStep *)us_p;
|
|
uhandle_free_list(&us->handles);
|
|
|
|
/* Typically this list will have been cleared. */
|
|
ptile_free_list(&us->paint_tiles);
|
|
}
|
|
|
|
static void image_undosys_foreach_ID_ref(UndoStep *us_p,
|
|
UndoTypeForEachIDRefFn foreach_ID_ref_fn,
|
|
void *user_data)
|
|
{
|
|
ImageUndoStep *us = (ImageUndoStep *)us_p;
|
|
for (UndoImageHandle *uh = us->handles.first; uh; uh = uh->next) {
|
|
foreach_ID_ref_fn(user_data, ((UndoRefID *)&uh->image_ref));
|
|
}
|
|
}
|
|
|
|
/* Export for ED_undo_sys. */
|
|
void ED_image_undosys_type(UndoType *ut)
|
|
{
|
|
ut->name = "Image";
|
|
ut->poll = image_undosys_poll;
|
|
ut->step_encode_init = image_undosys_step_encode_init;
|
|
ut->step_encode = image_undosys_step_encode;
|
|
ut->step_decode = image_undosys_step_decode;
|
|
ut->step_free = image_undosys_step_free;
|
|
|
|
ut->step_foreach_ID_ref = image_undosys_foreach_ID_ref;
|
|
|
|
ut->use_context = true;
|
|
|
|
ut->step_size = sizeof(ImageUndoStep);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Utilities
|
|
* \{ */
|
|
|
|
ListBase *ED_image_paint_tile_list_get(void)
|
|
{
|
|
UndoStack *ustack = ED_undo_stack_get();
|
|
UndoStep *us_prev = ustack->step_init;
|
|
UndoStep *us_p = BKE_undosys_stack_init_or_active_with_type(ustack, BKE_UNDOSYS_TYPE_IMAGE);
|
|
ImageUndoStep *us = (ImageUndoStep *)us_p;
|
|
/* We should always have an undo push started when accessing tiles,
|
|
* not doing this means we won't have paint_mode correctly set. */
|
|
BLI_assert(us_p == us_prev);
|
|
if (us_p != us_prev) {
|
|
/* Fallback value until we can be sure this never happens. */
|
|
us->paint_mode = PAINT_MODE_TEXTURE_2D;
|
|
}
|
|
return &us->paint_tiles;
|
|
}
|
|
|
|
/* restore painting image to previous state. Used for anchored and drag-dot style brushes*/
|
|
void ED_image_undo_restore(UndoStep *us)
|
|
{
|
|
ListBase *paint_tiles = &((ImageUndoStep *)us)->paint_tiles;
|
|
ptile_restore_runtime_list(paint_tiles);
|
|
ptile_invalidate_list(paint_tiles);
|
|
}
|
|
|
|
static ImageUndoStep *image_undo_push_begin(const char *name, int paint_mode)
|
|
{
|
|
UndoStack *ustack = ED_undo_stack_get();
|
|
bContext *C = NULL; /* special case, we never read from this. */
|
|
UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, name, BKE_UNDOSYS_TYPE_IMAGE);
|
|
ImageUndoStep *us = (ImageUndoStep *)us_p;
|
|
BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D));
|
|
us->paint_mode = paint_mode;
|
|
return us;
|
|
}
|
|
|
|
void ED_image_undo_push_begin(const char *name, int paint_mode)
|
|
{
|
|
image_undo_push_begin(name, paint_mode);
|
|
}
|
|
|
|
void ED_image_undo_push_begin_with_image(const char *name,
|
|
Image *image,
|
|
ImBuf *ibuf,
|
|
int tile_number)
|
|
{
|
|
ImageUndoStep *us = image_undo_push_begin(name, PAINT_MODE_TEXTURE_2D);
|
|
|
|
BLI_assert(BKE_image_get_tile(image, tile_number));
|
|
UndoImageHandle *uh = uhandle_ensure(&us->handles, image, tile_number);
|
|
UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf);
|
|
BLI_assert(ubuf_pre->post == NULL);
|
|
|
|
ImageUndoStep *us_reference = (ImageUndoStep *)ED_undo_stack_get()->step_active;
|
|
while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) {
|
|
us_reference = (ImageUndoStep *)us_reference->step.prev;
|
|
}
|
|
UndoImageBuf *ubuf_reference = (us_reference ? ubuf_lookup_from_reference(
|
|
us_reference, image, tile_number, ubuf_pre) :
|
|
NULL);
|
|
|
|
if (ubuf_reference) {
|
|
memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len);
|
|
for (uint i = 0; i < ubuf_pre->tiles_len; i++) {
|
|
UndoImageTile *utile = ubuf_pre->tiles[i];
|
|
utile->users += 1;
|
|
}
|
|
}
|
|
else {
|
|
ubuf_from_image_all_tiles(ubuf_pre, ibuf);
|
|
}
|
|
}
|
|
|
|
void ED_image_undo_push_end(void)
|
|
{
|
|
UndoStack *ustack = ED_undo_stack_get();
|
|
BKE_undosys_step_push(ustack, NULL, NULL);
|
|
WM_file_tag_modified();
|
|
}
|
|
|
|
/** \} */
|