#include "c4d_general.h" #include "c4d_shader.h" #include "c4d_memory.h" #include "c4d_bitmapfilter.h" #define DEBUG 0 #define USE_TILE_CACHE 1 #define MAX_COMPONENTS 8 // maximum number of components per pixel #include "pixelfetchutil.h" struct TILE_LRU { TILE_LRU* prev; TILE_LRU* next; Int32 grid_x; Int32 grid_y; TILE_LRU** grid_entry; BM_TILE* data; // data }; struct PIXEL_ROOT { BM_REF bm; // needed for accessing tiles Int32 color_space; UInt32 px_format; Int32 _pixel_size; // bytes per pixel Int32 _channels; // chper pixel Int32 tile_flags; // flags for clipping/texture wrapping #if USE_TILE_CACHE #if DEBUG Int32 no_tile_unloads; // # of times tiles had to be kicked out of the cache #endif TILE_LRU dummy_first; TILE_LRU dummy_last; Int32 tile_shifts; Int32 tile_mask; Int32 tile_wh; Int32 no_tiles; // number of currently cached tiles Int32 no_max_tiles; // maximum number of tiles that should be cached UInt32 grid_width; // # of tiles along the x axis (must be UInt32!) UInt32 grid_height; // # of tiles along the y axis (must be UInt32!) TILE_LRU* tile_grid[VARIABLE_ARRAY_SIZE]; #else BM_TILE* tile; #endif }; //---------------------------------------------------------------------------------------- // Create a new pixel fetcher object // Function result: pixel fetcher reference // bm: bitmap reference // tile_flags: TILE_REPEAT_BORDER or TILE_REPEAT_TILING // no_cached_tiles: 0: use default settings > 0: maximum number of cached tiles //---------------------------------------------------------------------------------------- PIXEL_REF new_pixel_fetcher(BM_REF bm, Int32 tile_flags, Int32 no_cached_tiles) { Int32 tile_shifts; Int32 tile_wh; Int32 grid_w; Int32 grid_h; Int32 grid_size; PIXEL_REF pf; tile_shifts = 7; // tile size is 128 * 128 tile_wh = 128; grid_w = (bm->image_rect.x2 + tile_wh - 1) / tile_wh; // width of the lookup table in tiles grid_h = (bm->image_rect.y2 + tile_wh - 1) / tile_wh; // height of the lookup table in tiles grid_size = grid_w * grid_h * sizeof(TILE_LRU*); pf = (PIXEL_REF) NewMemClear(UChar, sizeof(PIXEL_ROOT) + grid_size); if (pf) { pf->bm = bm; pf->color_space = bm->image_color_space; pf->px_format = bm->image_px_format; pf->_pixel_size = get_PX_BITS(bm->image_px_format) >> 3; pf->_channels = get_PX_CMPNTS(bm->image_px_format); pf->tile_flags = tile_flags; #if USE_TILE_CACHE #if DEBUG pf->no_tile_unloads = 0; #endif pf->dummy_first.prev = 0; // indicates that this ist first dummy entry pf->dummy_first.next = &pf->dummy_last; pf->dummy_first.grid_x = LIMIT::MIN; pf->dummy_first.grid_y = LIMIT::MIN; pf->dummy_first.grid_entry = 0; pf->dummy_first.data = 0; pf->dummy_last.prev = &pf->dummy_first; pf->dummy_last.next = 0; // indicates that this is the final dummy entry pf->dummy_last.grid_x = LIMIT::MIN; pf->dummy_last.grid_y = LIMIT::MIN; pf->dummy_last.grid_entry = 0; pf->dummy_last.data = 0; pf->tile_shifts = tile_shifts; // tile size is 128 * 128 pf->tile_wh = tile_wh; pf->tile_mask = tile_wh - 1; pf->no_tiles = 0; // currently no tiles allocated pf->no_max_tiles = (bm->image_rect.x2 - bm->image_rect.x1 + tile_wh - 1) >> tile_shifts; pf->no_max_tiles <<= 1; if (no_cached_tiles > 0) pf->no_max_tiles = no_cached_tiles; pf->grid_width = grid_w; pf->grid_height = grid_h; ClearMem(pf->tile_grid, grid_size, 0); // empty lookup table #else pf->tile = nullptr; #endif } return pf; } //---------------------------------------------------------------------------------------- // Delete a pixel fetcher object // Function result: - // pf: pixel fetcher object //---------------------------------------------------------------------------------------- void delete_pixel_fetcher(PIXEL_REF pf) { if (pf) { #if USE_TILE_CACHE TILE_LRU* lru_tile; lru_tile = pf->dummy_first.next; while (lru_tile->next) // loop till the last dummy entry is reached { TILE_LRU* tmp; tmp = lru_tile; lru_tile = lru_tile->next; BfBitmapTileDetach(pf->bm, tmp->data, false); // free the tile data DeleteMem(tmp); // free lru entry } #else if (pf->tile) BfBitmapTileDetach(pf->bm, pf->tile, false); #endif DeleteMem(pf); } } #if USE_TILE_CACHE //---------------------------------------------------------------------------------------- // Return a pixel's address // Function result: pixel address or 0 // pf: pixel fetcher object // x, y: coordinates in the image's coordinate space //---------------------------------------------------------------------------------------- inline static UChar* get_pixel_addr(PIXEL_REF pf, Int32 x, Int32 y) { UInt32 grid_x; UInt32 grid_y; TILE_LRU* lru_tile; BM_TILE* tile; grid_x = ((UInt32) x) >> pf->tile_shifts; grid_y = ((UInt32) y) >> pf->tile_shifts; lru_tile = pf->dummy_first.next; // the last recently used tile if ((grid_x != (UInt32) lru_tile->grid_x) || (grid_y != (UInt32) lru_tile->grid_y)) // are we not accessing the last recently used tile? { if ((grid_x >= pf->grid_width) || (grid_y >= pf->grid_height)) { RECT32* r; r = &pf->bm->image_rect; if (pf->tile_flags & TILE_REPEAT_TILING) // wrap around at the border { x -= r->x1; y -= r->y1; x %= r->x2 - r->x1; // note: the modulo result is negative, if x was < 0 y %= r->y2 - r->y1; if (x < 0) x += r->x2; else x += r->x1; if (y < 0) y += r->y2; else y += r->y1; } else // TILE_REPEAT_BORDER { if (x < pf->bm->image_rect.x1) x = pf->bm->image_rect.x1; if (x >= pf->bm->image_rect.x2) x = pf->bm->image_rect.x2 - 1; if (y < pf->bm->image_rect.y1) y = pf->bm->image_rect.y1; if (y >= pf->bm->image_rect.y2) y = pf->bm->image_rect.y2 - 1; } grid_x = ((UInt32) x) >> pf->tile_shifts; grid_y = ((UInt32) y) >> pf->tile_shifts; } lru_tile = pf->tile_grid[(grid_y * pf->grid_width) + grid_x]; if (lru_tile == 0) // no tile for this area? { RECT32 tile_rect; tile_rect.x1 = grid_x << pf->tile_shifts; tile_rect.y1 = grid_y << pf->tile_shifts; tile_rect.x2 = (grid_x + 1) << pf->tile_shifts; tile_rect.y2 = (grid_y + 1) << pf->tile_shifts; if (pf->no_tiles >= pf->no_max_tiles) // free the least important tile? { pf->no_tiles--; lru_tile = pf->dummy_last.prev; lru_tile->prev->next = lru_tile->next; // unlink lru_tile->next->prev = lru_tile->prev; *lru_tile->grid_entry = 0; // delete the entry in the lookup grid BfBitmapTileDetach(pf->bm, lru_tile->data, false); // free the tile data #if DEBUG pf->no_tile_unloads++; #endif } else // add a new lru entry { lru_tile = NewMemClear(TILE_LRU, 1); if (lru_tile == 0) return 0; } lru_tile->data = BfBitmapTileGet(pf->bm, &tile_rect, 0, 0, TILE_BM_READ_ONLY, pf->tile_flags); if (lru_tile->data == 0) // unable to access the tile? { DeleteMem(lru_tile); return 0; // Houston, we have a problem } pf->no_tiles++; lru_tile->next = pf->dummy_first.next; // insert in the chain as the first lru_tile->prev = &pf->dummy_first; lru_tile->prev->next = lru_tile; lru_tile->next->prev = lru_tile; lru_tile->grid_x = grid_x; // save that grid position for a quick lru check at the top of the function lru_tile->grid_y = grid_y; lru_tile->grid_entry = &pf->tile_grid[(grid_y * pf->grid_width) + grid_x]; *lru_tile->grid_entry = lru_tile; // add entry in the lookup grid } if (lru_tile != pf->dummy_first.next) // change the lru order? { lru_tile->next->prev = lru_tile->prev; // unlink from the chain lru_tile->prev->next = lru_tile->next; pf->dummy_first.next->prev = lru_tile; // link with the previously used tile lru_tile->next = pf->dummy_first.next; lru_tile->prev = &pf->dummy_first; // link with the dummy root pf->dummy_first.next = lru_tile; } } tile = lru_tile->data; x -= tile->xmin; y -= tile->ymin; return (UChar*) tile->addr + (tile->width * y) + (pf->_pixel_size * x); } //---------------------------------------------------------------------------------------- // Return a pixel // Function result: true: everything is fine false: can't access the pixel // pf: pixel fetcher object // x: discrete x coordinate // y: discrete y coordinate // dst: used to return the pixel //---------------------------------------------------------------------------------------- #define T PIX_C Bool pf_get_pixel(PIXEL_REF pf, Int32 x, Int32 y, T* dst) { T* p; p = (T*)get_pixel_addr(pf, x, y); if (p) { Int32 i; for (i = pf->_channels; i; i--) *dst++ = *p++; return true; } return false; } #undef T #define T PIX_W Bool pf_get_pixel(PIXEL_REF pf, Int32 x, Int32 y, T* dst) { T* p; p = (T*)get_pixel_addr(pf, x, y); if (p) { Int32 i; for (i = pf->_channels; i; i--) *dst++ = *p++; return true; } return false; } #undef T #define T PIX_F Bool pf_get_pixel(PIXEL_REF pf, Int32 x, Int32 y, T* dst) { T* p; p = (T*)get_pixel_addr(pf, x, y); if (p) { Int32 i; for (i = pf->_channels; i; i--) *dst++ = *p++; return true; } return false; } #undef T //---------------------------------------------------------------------------------------- // Return a pixel's address // Function result: pixel address or 0 // pf: pixel fetcher object // x: discrete x coordinate // y: discrete y coordinate //---------------------------------------------------------------------------------------- UChar* pf_get_pixel_addr(PIXEL_REF pf, Int32 x, Int32 y) { return get_pixel_addr(pf, x, y); } #else template Bool pf_get_pixel(PIXEL_REF pf, Int32 x, Int32 y, T* pixel) { T* p; Int32 i; BM_TILE* tile; tile = pf->tile; if ((tile == 0) || (x < tile->xmin) || (x >= tile->xmax) || (y < tile->ymin) || (y >= tile->ymax)) { BM_REF bm; RECT32 tile_rect; Int32 col; Int32 row; bm = pf->bm; if (x < bm->image_rect.x1) x = bm->image_rect.x1; if (x >= bm->image_rect.x2) x = bm->image_rect.x2 - 1; if (y < bm->image_rect.y1) y = bm->image_rect.y1; if (y >= bm->image_rect.y2) y = bm->image_rect.y2 - 1; if ((tile == 0) || (x < tile->xmin) || (x >= tile->xmax) || (y < tile->ymin) || (y >= tile->ymax)) { col = x / bm->preferred_tile_width; row = y / bm->preferred_tile_height; if (pf->tile) BfBitmapTileDetach(bm, pf->tile, false); tile_rect = bm->image_rect; tile = BfBitmapTileGet(bm, &tile_rect, 0, 0, TILE_BM_READ_ONLY, 0); if (tile == 0) return false; pf->tile = tile; } } p = (T*) tile->addr; p += tile->width * (y - tile->ymin); p += pf->_channels * (x - tile->xmin); for (i = pf->_channels; i; i--) *pixel++ = *p++; return true; } UChar* pf_get_pixel_addr(PIXEL_REF pf, Int32 x, Int32 y) { UChar* p; BM_TILE* tile; tile = pf->tile; if ((tile == 0) || (x < tile->xmin) || (x >= tile->xmax) || (y < tile->ymin) || (y >= tile->ymax)) { BM_REF bm; RECT32 tile_rect; Int32 col; Int32 row; bm = pf->bm; if (x < bm->image_rect.x1) x = bm->image_rect.x1; if (x >= bm->image_rect.x2) x = bm->image_rect.x2 - 1; if (y < bm->image_rect.y1) y = bm->image_rect.y1; if (y >= bm->image_rect.y2) y = bm->image_rect.y2 - 1; if ((tile == 0) || (x < tile->xmin) || (x >= tile->xmax) || (y < tile->ymin) || (y >= tile->ymax)) { col = x / bm->preferred_tile_width; row = y / bm->preferred_tile_height; if (pf->tile) BfBitmapTileDetach(bm, pf->tile, false); tile_rect = bm->image_rect; tile = BfBitmapTileGet(bm, &tile_rect, 0, 0, TILE_BM_READ_ONLY, 0); if (tile == 0) return 0; pf->tile = tile; } } p = (UChar*) tile->addr; p += tile->width * (y - tile->ymin); p += pf->_pixel_size * (x - tile->xmin); return p; } #endif //---------------------------------------------------------------------------------------- // Return a bilinear filtered pixel // Function result: true: everything is fine false: can't access the pixel // pf: pixel fetcher object // fx: continous x coordinate (.5 is pixel center) // fy: continous y coordinate (.5 is pixel center) // dst: used to return the pixel //---------------------------------------------------------------------------------------- Bool pf_get_pixel_bilinear(PIXEL_REF pf, Float fx, Float fy, PIX_C* dst) { PIX_C pbuf[4][MAX_COMPONENTS]; Int32 ix; Int32 iy; fx -= 0.5; fy -= 0.5; ix = (Int32) floor(fx); iy = (Int32) floor(fy); if (pf_get_pixel(pf, ix, iy, pbuf[0])) { if (pf_get_pixel(pf, ix + 1, iy, pbuf[1])) { if (pf_get_pixel(pf, ix, iy + 1, pbuf[2])) { if (pf_get_pixel(pf, ix + 1, iy + 1, pbuf[3])) { Float values[4]; Int32 i; if (pf->color_space & CSPACE_ALPHA_FLAG) { Float alpha[4]; Float new_alpha; values[0] = (Float) pbuf[0][0]; values[1] = (Float) pbuf[1][0]; values[2] = (Float) pbuf[2][0]; values[3] = (Float) pbuf[3][0]; alpha[0] = values[0] / 255.0; alpha[1] = values[1] / 255.0; alpha[2] = values[2] / 255.0; alpha[3] = values[3] / 255.0; new_alpha = bilinear_double(fx, fy, values); *dst++ = (UChar) (new_alpha + 0.5); new_alpha /= 255.0; if (new_alpha == 0.0) new_alpha = 1.0; for (i = 1; i < pf->_channels; i++) { Float v; values[0] = (Float) pbuf[0][i] * alpha[0]; values[1] = (Float) pbuf[1][i] * alpha[1]; values[2] = (Float) pbuf[2][i] * alpha[2]; values[3] = (Float) pbuf[3][i] * alpha[3]; v = bilinear_double(fx, fy, values); v /= new_alpha; if (v < 0.0) v = 0.0; if (v > 255.0) v = 255.0; *dst++ = (UChar) (v + 0.5); } } else { for (i = 0; i < pf->_channels; i++) { values[0] = (Float) pbuf[0][i]; values[1] = (Float) pbuf[1][i]; values[2] = (Float) pbuf[2][i]; values[3] = (Float) pbuf[3][i]; *dst++ = (UChar) (bilinear_double(fx, fy, values) + 0.5); } } return true; } } } } return false; } Bool pf_get_pixel_bilinear(PIXEL_REF pf, Float fx, Float fy, PIX_W* dst) { PIX_W pbuf[4][MAX_COMPONENTS]; Int32 ix; Int32 iy; fx -= 0.5; fy -= 0.5; ix = (Int32) floor(fx); iy = (Int32) floor(fy); if (pf_get_pixel(pf, ix, iy, pbuf[0])) { if (pf_get_pixel(pf, ix + 1, iy, pbuf[1])) { if (pf_get_pixel(pf, ix, iy + 1, pbuf[2])) { if (pf_get_pixel(pf, ix + 1, iy + 1, pbuf[3])) { Float values[4]; Int32 i; if (pf->color_space & CSPACE_ALPHA_FLAG) { Float alpha[4]; Float new_alpha; values[0] = (Float) pbuf[0][0]; values[1] = (Float) pbuf[1][0]; values[2] = (Float) pbuf[2][0]; values[3] = (Float) pbuf[3][0]; alpha[0] = values[0] / 65535.0; alpha[1] = values[1] / 65535.0; alpha[2] = values[2] / 65535.0; alpha[3] = values[3] / 65535.0; new_alpha = bilinear_double(fx, fy, values); *dst++ = (PIX_W) (new_alpha + 0.5); new_alpha /= 65535.0; if (new_alpha == 0.0) new_alpha = 1.0; for (i = 1; i < pf->_channels; i++) { Float v; values[0] = (Float) pbuf[0][i] * alpha[0]; values[1] = (Float) pbuf[1][i] * alpha[1]; values[2] = (Float) pbuf[2][i] * alpha[2]; values[3] = (Float) pbuf[3][i] * alpha[3]; v = bilinear_double(fx, fy, values); v /= new_alpha; if (v < 0.0) v = 0.0; if (v > 65535.0) v = 65535.0; *dst++ = (PIX_W) (v + 0.5); } } else { for (i = 0; i < pf->_channels; i++) { values[0] = (Float) pbuf[0][i]; values[1] = (Float) pbuf[1][i]; values[2] = (Float) pbuf[2][i]; values[3] = (Float) pbuf[3][i]; *dst++ = (PIX_W) (bilinear_double(fx, fy, values) + 0.5); } } return true; } } } } return false; } Bool pf_get_pixel_bilinear(PIXEL_REF pf, Float fx, Float fy, PIX_F* dst) { PIX_F pbuf[4][MAX_COMPONENTS]; Int32 ix; Int32 iy; fx -= 0.5; fy -= 0.5; ix = (Int32) floor(fx); iy = (Int32) floor(fy); if (pf_get_pixel(pf, ix, iy, pbuf[0])) { if (pf_get_pixel(pf, ix + 1, iy, pbuf[1])) { if (pf_get_pixel(pf, ix, iy + 1, pbuf[2])) { if (pf_get_pixel(pf, ix + 1, iy + 1, pbuf[3])) { Float values[4]; Int32 i; if (pf->color_space & CSPACE_ALPHA_FLAG) { Float alpha[4]; Float new_alpha; values[0] = pbuf[0][0]; values[1] = pbuf[1][0]; values[2] = pbuf[2][0]; values[3] = pbuf[3][0]; alpha[0] = values[0]; alpha[1] = values[1]; alpha[2] = values[2]; alpha[3] = values[3]; new_alpha = bilinear_double(fx, fy, values); *dst++ = new_alpha; if (new_alpha == 0.0) new_alpha = 1.0; for (i = 1; i < pf->_channels; i++) { Float v; values[0] = (Float) pbuf[0][i] * alpha[0]; values[1] = (Float) pbuf[1][i] * alpha[1]; values[2] = (Float) pbuf[2][i] * alpha[2]; values[3] = (Float) pbuf[3][i] * alpha[3]; v = bilinear_double(fx, fy, values); v /= new_alpha; if (v < 0.0) v = 0.0; *dst++ = v; } } else { for (i = 0; i < pf->_channels; i++) { values[0] = (Float) pbuf[0][i]; values[1] = (Float) pbuf[1][i]; values[2] = (Float) pbuf[2][i]; values[3] = (Float) pbuf[3][i]; *dst++ = bilinear_double(fx, fy, values); } } return true; } } } } return false; } UChar bilinear8(Float x, Float y, UChar* values) { Float m0, m1; x -= floor(x); y -= floor(y); // the following line ist just another way of writing m0 = ( values[0] * ( 1 - x )) + ( values[1] * x ) m0 = (Float) values[0] + (x * ((Float) values[1] - values[0])); m1 = (Float) values[2] + (x * ((Float) values[3] - values[2])); return (UChar) (m0 + (y * (m1 - m0))); } UInt16 bilinear16(Float x, Float y, UInt16* values) { Float m0, m1; x -= floor(x); y -= floor(y); m0 = (Float) values[0] + (x * ((Float) values[1] - values[0])); m1 = (Float) values[2] + (x * ((Float) values[3] - values[2])); return (UInt16) (m0 + (y * (m1 - m0))); } Float bilinear_double(Float x, Float y, Float* values) { Float m0, m1; x -= floor(x); y -= floor(y); // the following line ist just another way of writing m0 = ( values[0] * ( 1 - x )) + ( values[1] * x ) m0 = values[0] + (x * (values[1] - values[0])); m1 = values[2] + (x * (values[3] - values[2])); return m0 + (y * (m1 - m0)); }