e6a904c445
* BUG: Image.PaintImage() incorrectly handled alpha channel compositing. git-svn-id: svn://localhost/gambas/trunk@3926 867c0c6c-44f3-4631-809d-bfa615b0a4ec
1284 lines
27 KiB
C
1284 lines
27 KiB
C
/***************************************************************************
|
|
|
|
image.c
|
|
|
|
(c) 2000-2011 Benoît Minisini <gambas@users.sourceforge.net>
|
|
|
|
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, 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
MA 02110-1301, USA.
|
|
|
|
***************************************************************************/
|
|
|
|
#define __IMAGE_C
|
|
|
|
#include "c_color.h"
|
|
#include "image.h"
|
|
|
|
bool IMAGE_debug = FALSE;
|
|
|
|
typedef
|
|
struct { unsigned char d[3]; } PACKED uint24;
|
|
|
|
typedef
|
|
struct {
|
|
int format;
|
|
const char *name;
|
|
}
|
|
FORMAT;
|
|
|
|
//#define DEBUG_CONVERT
|
|
//#define DEBUG_ME 1
|
|
|
|
static int _default_format = GB_IMAGE_RGBA;
|
|
|
|
static FORMAT _formats[] =
|
|
{
|
|
{ GB_IMAGE_BGRX, "BGRX" },
|
|
{ GB_IMAGE_XRGB, "XRGB" },
|
|
{ GB_IMAGE_RGBX, "RGBX" },
|
|
{ GB_IMAGE_XBGR, "XBGR" },
|
|
{ GB_IMAGE_BGR , "BGR" },
|
|
{ GB_IMAGE_RGB , "RGB" },
|
|
|
|
{ GB_IMAGE_BGRA, "BGRA" },
|
|
{ GB_IMAGE_ARGB, "ARGB" },
|
|
{ GB_IMAGE_RGBA, "RGBA" },
|
|
{ GB_IMAGE_ABGR, "ABGR" },
|
|
|
|
{ GB_IMAGE_BGRP, "BGRP" },
|
|
{ GB_IMAGE_PRGB, "PRGB" },
|
|
{ GB_IMAGE_RGBP, "RGBP" },
|
|
{ GB_IMAGE_PBGR, "PBGR" },
|
|
{ 0, NULL }
|
|
};
|
|
|
|
/*static inline unsigned char *GET_END_POINTER(GB_IMG *image)
|
|
{
|
|
return &image->data[IMAGE_size(image)];
|
|
}*/
|
|
|
|
#define GET_END_POINTER(_img) (&(_img)->data[IMAGE_size(_img)])
|
|
|
|
#define PREMUL(_x) \
|
|
({ \
|
|
uint x = (_x); \
|
|
uint a = x >> 24; \
|
|
\
|
|
if (a == 0) \
|
|
x = 0; \
|
|
else if (a != 0xFF) \
|
|
{ \
|
|
uint t = (x & 0xFF00FF) * a; \
|
|
t = (t + ((t >> 8) & 0xFF00FF) + 0x800080) >> 8; \
|
|
t &= 0xff00ff; \
|
|
\
|
|
x = ((x >> 8) & 0xff) * a; \
|
|
x = (x + ((x >> 8) & 0xFF) + 0x80); \
|
|
x &= 0xFF00; \
|
|
x |= t | (a << 24); \
|
|
} \
|
|
x; \
|
|
})
|
|
|
|
#define INV_PREMUL(__p) \
|
|
({ \
|
|
uint _p = (__p); \
|
|
if (ALPHA(_p) == 0) \
|
|
_p = 0; \
|
|
else if (ALPHA(_p) != 0xFF) \
|
|
_p = ((ALPHA(_p) << 24) \
|
|
| (((255*RED(_p))/ ALPHA(_p)) << 16) \
|
|
| (((255*GREEN(_p)) / ALPHA(_p)) << 8) \
|
|
| ((255*BLUE(_p)) / ALPHA(_p))); \
|
|
_p; \
|
|
})
|
|
|
|
#define SWAP(__p) \
|
|
({ \
|
|
uint _p = (__p); \
|
|
RGBA(ALPHA(_p), BLUE(_p), GREEN(_p), RED(_p)); \
|
|
})
|
|
|
|
#define SWAP_RED_BLUE(__p) \
|
|
({ \
|
|
uint _p = (__p); \
|
|
RGBA(BLUE(_p), GREEN(_p), RED(_p), ALPHA(_p)); \
|
|
})
|
|
|
|
// Convert from GB_COLOR to a specific format
|
|
|
|
static uint GB_COLOR_to_format(GB_COLOR col, int format)
|
|
{
|
|
col ^= 0xFF000000;
|
|
if (GB_IMAGE_FMT_IS_PREMULTIPLIED(format))
|
|
col = PREMUL(col);
|
|
if (GB_IMAGE_FMT_IS_SWAPPED(format))
|
|
col = SWAP(col);
|
|
if (GB_IMAGE_FMT_IS_RGBA(format))
|
|
col = SWAP_RED_BLUE(col);
|
|
return col;
|
|
}
|
|
|
|
// Convert from a specific format to GB_COLOR
|
|
|
|
static GB_COLOR GB_COLOR_from_format(uint col, int format)
|
|
{
|
|
if (GB_IMAGE_FMT_IS_RGBA(format))
|
|
col = SWAP_RED_BLUE(col);
|
|
if (GB_IMAGE_FMT_IS_SWAPPED(format))
|
|
col = SWAP(col);
|
|
if (GB_IMAGE_FMT_IS_PREMULTIPLIED(format))
|
|
col = INV_PREMUL(col);
|
|
return col ^ 0xFF000000;
|
|
}
|
|
|
|
// Convert from BGRA to a specific format
|
|
|
|
static inline uint BGRA_to_format(uint col, int format)
|
|
{
|
|
if (GB_IMAGE_FMT_IS_PREMULTIPLIED(format))
|
|
col = PREMUL(col);
|
|
if (GB_IMAGE_FMT_IS_SWAPPED(format))
|
|
col = SWAP(col);
|
|
if (GB_IMAGE_FMT_IS_RGBA(format))
|
|
col = SWAP_RED_BLUE(col);
|
|
return col;
|
|
}
|
|
|
|
// Convert from a specific format to BGRA
|
|
|
|
static inline uint BGRA_from_format(uint col, int format)
|
|
{
|
|
if (GB_IMAGE_FMT_IS_RGBA(format))
|
|
col = SWAP_RED_BLUE(col);
|
|
if (GB_IMAGE_FMT_IS_SWAPPED(format))
|
|
col = SWAP(col);
|
|
if (GB_IMAGE_FMT_IS_PREMULTIPLIED(format))
|
|
col = INV_PREMUL(col);
|
|
return col;
|
|
}
|
|
|
|
// Convert from GB_COLOR to BGRA
|
|
|
|
static inline uint GB_COLOR_to_BGRA(GB_COLOR col)
|
|
{
|
|
return col ^ 0xFF000000;
|
|
}
|
|
|
|
// Compose two BGRA colors
|
|
|
|
static inline uint BGRA_compose(uint dst, uint src)
|
|
{
|
|
unsigned char a = ALPHA(src);
|
|
if (a == 255)
|
|
return src;
|
|
else if (a == 0)
|
|
return dst;
|
|
else
|
|
{
|
|
unsigned char r = ((RED(src) - RED(dst)) * a) / 256 + RED(dst);
|
|
unsigned char g = ((GREEN(src) - GREEN(dst)) * a) / 256 + GREEN(dst);
|
|
unsigned char b = ((BLUE(src) - BLUE(dst)) * a) / 256 + BLUE(dst);
|
|
if (ALPHA(dst) > a)
|
|
a = ALPHA(dst);
|
|
return RGBA(r, g, b, a);
|
|
}
|
|
}
|
|
|
|
static inline bool is_valid(GB_IMG *img, int x, int y)
|
|
{
|
|
return !(x >= img->width || y >= img->height || x < 0 || y < 0);
|
|
}
|
|
|
|
static void free_image(GB_IMG *img, void *image)
|
|
{
|
|
//fprintf(stderr, "free_image: %p %p : %d\n", img, img->data, IMAGE_size(img));
|
|
GB.Free(POINTER(&img->data));
|
|
}
|
|
|
|
static GB_IMG_OWNER _image_owner = {
|
|
"gb.image",
|
|
0,
|
|
free_image,
|
|
free_image,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
|
|
// Only converts to the following formats:
|
|
// - GB_IMAGE_BGRA
|
|
// - GB_IMAGE_BGRX
|
|
// - GB_IMAGE_RGBA
|
|
// - GB_IMAGE_RGBX
|
|
|
|
static void convert_image(uchar *dst, int dst_format, uchar *src, int src_format, int w, int h)
|
|
{
|
|
unsigned char *s = src;
|
|
unsigned char *d = dst;
|
|
int len;
|
|
unsigned char *dm;
|
|
uint *p, *pm;
|
|
bool psrc, pdst;
|
|
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: src_format = %d dst_format = %d\n", src_format, dst_format);
|
|
#endif
|
|
|
|
len = w * h * sizeof(uint);
|
|
dm = &d[len];
|
|
|
|
psrc = GB_IMAGE_FMT_IS_PREMULTIPLIED(src_format);
|
|
src_format = GB_IMAGE_FMT_CLEAR_PREMULTIPLIED(src_format);
|
|
|
|
pdst = GB_IMAGE_FMT_IS_PREMULTIPLIED(dst_format);
|
|
dst_format = GB_IMAGE_FMT_CLEAR_PREMULTIPLIED(dst_format);
|
|
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: after: src_format = %d dst_format = %d\n", src_format, dst_format);
|
|
#endif
|
|
|
|
if (dst_format == GB_IMAGE_BGRA || dst_format == GB_IMAGE_BGRX)
|
|
{
|
|
switch (src_format)
|
|
{
|
|
case GB_IMAGE_BGRA: case GB_IMAGE_BGRX:
|
|
goto __0123;
|
|
|
|
case GB_IMAGE_ARGB: case GB_IMAGE_XRGB:
|
|
goto __3210;
|
|
|
|
case GB_IMAGE_RGBA: case GB_IMAGE_RGBX:
|
|
goto __2103;
|
|
|
|
case GB_IMAGE_ABGR: case GB_IMAGE_XBGR:
|
|
goto __1230;
|
|
|
|
case GB_IMAGE_BGR:
|
|
goto __012X;
|
|
|
|
case GB_IMAGE_RGB:
|
|
goto __210X;
|
|
}
|
|
}
|
|
else if (dst_format == GB_IMAGE_RGBA || dst_format == GB_IMAGE_RGBX)
|
|
{
|
|
switch (src_format)
|
|
{
|
|
case GB_IMAGE_RGBA: case GB_IMAGE_RGBX:
|
|
goto __0123;
|
|
|
|
case GB_IMAGE_ABGR: case GB_IMAGE_XBGR:
|
|
goto __3210;
|
|
|
|
case GB_IMAGE_BGRA: case GB_IMAGE_BGRX:
|
|
goto __2103;
|
|
|
|
case GB_IMAGE_ARGB: case GB_IMAGE_XRGB:
|
|
goto __1230;
|
|
|
|
case GB_IMAGE_RGB:
|
|
goto __012X;
|
|
|
|
case GB_IMAGE_BGR:
|
|
goto __210X;
|
|
}
|
|
}
|
|
|
|
__0123:
|
|
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: 0123\n");
|
|
#endif
|
|
memcpy(dst, src, len);
|
|
goto __PREMULTIPLIED;
|
|
|
|
__3210:
|
|
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: 3210\n");
|
|
#endif
|
|
while (d != dm)
|
|
{
|
|
d[0] = s[3];
|
|
d[1] = s[2];
|
|
d[2] = s[1];
|
|
d[3] = s[0];
|
|
s += 4;
|
|
d += 4;
|
|
}
|
|
goto __PREMULTIPLIED;
|
|
|
|
__2103:
|
|
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: 2103\n");
|
|
#endif
|
|
|
|
while (d != dm)
|
|
{
|
|
d[0] = s[2];
|
|
d[1] = s[1];
|
|
d[2] = s[0];
|
|
d[3] = s[3];
|
|
s += 4;
|
|
d += 4;
|
|
}
|
|
goto __PREMULTIPLIED;
|
|
|
|
__1230:
|
|
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: 1230\n");
|
|
#endif
|
|
|
|
while (d != dm)
|
|
{
|
|
d[0] = s[1];
|
|
d[1] = s[2];
|
|
d[2] = s[3];
|
|
d[3] = s[0];
|
|
s += 4;
|
|
d += 4;
|
|
}
|
|
goto __PREMULTIPLIED;
|
|
|
|
__012X:
|
|
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: 012X\n");
|
|
#endif
|
|
|
|
while (d != dm)
|
|
{
|
|
d[0] = s[0];
|
|
d[1] = s[1];
|
|
d[2] = s[2];
|
|
d[3] = 0xFF;
|
|
s += 3;
|
|
d += 4;
|
|
}
|
|
return;
|
|
|
|
__210X:
|
|
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: 210X\n");
|
|
#endif
|
|
|
|
while (d != dm)
|
|
{
|
|
d[0] = s[2];
|
|
d[1] = s[1];
|
|
d[2] = s[0];
|
|
d[3] = 0xFF;
|
|
s += 3;
|
|
d += 4;
|
|
}
|
|
return;
|
|
|
|
__PREMULTIPLIED:
|
|
|
|
if (psrc == pdst)
|
|
return;
|
|
|
|
p = (uint *)dst;
|
|
pm = (uint *)dm;
|
|
|
|
if (psrc)
|
|
{
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: premultiplied -> normal\n");
|
|
#endif
|
|
// convert premultiplied to normal
|
|
while (p != pm)
|
|
{
|
|
*p = INV_PREMUL(*p);
|
|
p++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_CONVERT
|
|
fprintf(stderr, "convert_image: normal -> premultiplied\n");
|
|
#endif
|
|
// convert normal to premultiplied
|
|
while (p != pm)
|
|
{
|
|
*p = PREMUL(*p);
|
|
p++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define SYNCHRONIZE(_img) ({ if ((_img)->sync) (*(_img)->temp_owner->sync)(_img); })
|
|
#define MODIFY(_img) ((_img)->modified = TRUE)
|
|
|
|
int IMAGE_size(GB_IMG *img)
|
|
{
|
|
return img->width * img->height * (GB_IMAGE_FMT_IS_24_BITS(img->format) ? 3 : 4);
|
|
}
|
|
|
|
void IMAGE_create(GB_IMG *img, int width, int height, int format)
|
|
{
|
|
GB_BASE save = img->ob;
|
|
CLEAR(img);
|
|
img->ob = save;
|
|
img->owner = &_image_owner;
|
|
|
|
if (width <= 0 || height <= 0)
|
|
{
|
|
img->is_void = TRUE;
|
|
return;
|
|
}
|
|
|
|
img->width = width;
|
|
img->height = height;
|
|
img->format = format;
|
|
GB.Alloc(POINTER(&img->data), IMAGE_size(img));
|
|
img->owner_handle = img->data;
|
|
|
|
#ifdef DEBUG_ME
|
|
fprintf(stderr, "IMAGE_create: %p\n", img);
|
|
#endif
|
|
}
|
|
|
|
|
|
void IMAGE_create_with_data(GB_IMG *img, int width, int height, int format, unsigned char *data)
|
|
{
|
|
IMAGE_create(img, width, height, format);
|
|
if (data && !IMAGE_is_void(img))
|
|
memcpy(img->data, data, IMAGE_size(img));
|
|
}
|
|
|
|
const char *IMAGE_format_to_string(int fmt)
|
|
{
|
|
FORMAT *pf;
|
|
|
|
for (pf = _formats; pf->name; pf++)
|
|
{
|
|
if (fmt == pf->format)
|
|
return pf->name;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void IMAGE_convert(GB_IMG *img, int dst_format)
|
|
{
|
|
uchar *data;
|
|
int src_format = img->format;
|
|
|
|
if (src_format == dst_format)
|
|
return;
|
|
|
|
//IMAGE_create(&tmp, img->width, img->height, format);
|
|
img->format = dst_format;
|
|
if (IMAGE_is_void(img))
|
|
return;
|
|
|
|
if (IMAGE_debug)
|
|
fprintf(stderr, "gb.image: convert: %s -> %s\n", IMAGE_format_to_string(src_format), IMAGE_format_to_string(dst_format));
|
|
|
|
GB.Alloc(POINTER(&data), IMAGE_size(img));
|
|
convert_image(data, dst_format, img->data, src_format, img->width, img->height);
|
|
//GB.Free(POINTER(&img->data));
|
|
IMAGE_take(img, &_image_owner, data, img->width, img->height, data);
|
|
}
|
|
|
|
// Check if a temporary handle is needed, and create it if needed by calling the owner "temp" function
|
|
|
|
void *IMAGE_check(GB_IMG *img, GB_IMG_OWNER *temp_owner)
|
|
{
|
|
if (!img)
|
|
return NULL;
|
|
|
|
// If we already have the temporary handle, then do nothing
|
|
if (img->temp_owner == temp_owner)
|
|
return img->temp_handle;
|
|
|
|
#ifdef DEBUG_ME
|
|
fprintf(stderr, "IMAGE_check: %p: %s (%p) / %s (%p) -> %s\n",
|
|
img,
|
|
img->owner->name, img->owner_handle,
|
|
img->temp_owner ? img->temp_owner->name : "NULL", img->temp_handle,
|
|
temp_owner ? temp_owner->name : "NULL");
|
|
#endif
|
|
|
|
// If somebody else has a temporary handle
|
|
if (img->temp_owner)
|
|
{
|
|
// release it only if it is not the owner
|
|
if (img->temp_owner != img->owner && img->temp_owner->release)
|
|
(*img->temp_owner->release)(img, img->temp_handle);
|
|
img->temp_handle = 0;
|
|
img->temp_owner = NULL;
|
|
}
|
|
|
|
// Get the temporary handle
|
|
if (temp_owner)
|
|
{
|
|
// If we are the owner, we must use our owner handle as temporary handle
|
|
if (img->owner == temp_owner)
|
|
img->temp_handle = img->owner_handle;
|
|
// If we are not the owner, then we will have to create the temporary handle ourself
|
|
else
|
|
{
|
|
// Synchronize the image if needed
|
|
SYNCHRONIZE(img);
|
|
// Conversion can make gb.image the new owner and temporary owner
|
|
IMAGE_convert(img, temp_owner->format);
|
|
img->temp_handle = (*temp_owner->temp)(img);
|
|
}
|
|
}
|
|
|
|
// Become the temporary owner
|
|
img->temp_owner = temp_owner;
|
|
|
|
#ifdef DEBUG_ME
|
|
fprintf(stderr, "==========>: %p: %s (%p) / %s (%p)\n",
|
|
img,
|
|
img->owner->name, img->owner_handle,
|
|
img->temp_owner ? img->temp_owner->name : "NULL", img->temp_handle);
|
|
#endif
|
|
|
|
return img->temp_handle;
|
|
}
|
|
|
|
// Take ownership of the image
|
|
void IMAGE_take(GB_IMG *img, GB_IMG_OWNER *owner, void *owner_handle, int width, int height, unsigned char *data)
|
|
{
|
|
if (!img)
|
|
return;
|
|
|
|
// If we are already the owner with the same handle, then do nothing
|
|
if (img->owner == owner && img->owner_handle == owner_handle)
|
|
return;
|
|
|
|
// Release the old owner
|
|
//fprintf(stderr, "releasing image %p owned by %s\n", img, img->owner->name);
|
|
(*img->owner->free)(img, img->owner_handle);
|
|
|
|
// If we have the temporary handle too, then clean it as it is necessarily the same
|
|
if (img->temp_owner == img->owner)
|
|
{
|
|
img->temp_owner = NULL;
|
|
img->temp_handle = 0;
|
|
}
|
|
|
|
// Become the owner
|
|
img->owner = owner;
|
|
img->owner_handle = owner_handle;
|
|
|
|
// As we are now the owner, then we must have the temporary handle too
|
|
IMAGE_check(img, NULL);
|
|
img->temp_owner = owner;
|
|
img->temp_handle = owner_handle;
|
|
|
|
// Initialize the data
|
|
img->width = width;
|
|
img->height = height;
|
|
img->data = data;
|
|
if (owner && owner->format)
|
|
img->format = owner->format;
|
|
img->is_void = width <= 0 || height <= 0;
|
|
}
|
|
|
|
void IMAGE_delete(GB_IMG *img)
|
|
{
|
|
//IMAGE_take(img, &_image_owner, NULL, 0, 0, NULL);
|
|
|
|
// Release the temporary handle before the owner, because the temporary owner may write to the data before freeing!
|
|
|
|
if (img->temp_owner && img->temp_owner != img->owner && img->temp_handle)
|
|
(*img->temp_owner->release)(img, img->temp_handle);
|
|
|
|
if (!IMAGE_is_void(img))
|
|
(*img->owner->free)(img, img->owner_handle);
|
|
|
|
img->width = img->height = 0;
|
|
img->format = 0;
|
|
img->temp_owner = NULL;
|
|
img->temp_handle = NULL;
|
|
img->owner = &_image_owner;
|
|
img->owner_handle = NULL;
|
|
img->is_void = TRUE;
|
|
}
|
|
|
|
void IMAGE_synchronize(GB_IMG *img)
|
|
{
|
|
SYNCHRONIZE(img);
|
|
}
|
|
|
|
#define GET_POINTER(_img, _p, _pm) \
|
|
uint *_p = (uint *)(_img)->data; \
|
|
uint *_pm = (uint *)GET_END_POINTER(_img); \
|
|
if ((_img)->is_void) return;
|
|
|
|
void IMAGE_fill(GB_IMG *img, GB_COLOR col)
|
|
{
|
|
GET_POINTER(img, p, pm);
|
|
|
|
col = GB_COLOR_to_format(col, img->format);
|
|
while (p != pm)
|
|
*p++ = col;
|
|
|
|
MODIFY(img);
|
|
}
|
|
|
|
// GB_IMAGE_RGB[APX] only
|
|
void IMAGE_make_gray(GB_IMG *img)
|
|
{
|
|
GET_POINTER(img, p, pm);
|
|
uint col;
|
|
uchar g;
|
|
int format = img->format;
|
|
|
|
SYNCHRONIZE(img);
|
|
|
|
while (p != pm)
|
|
{
|
|
col = BGRA_from_format(*p, format);
|
|
g = (((RED(col) + BLUE(col)) >> 1) + GREEN(col)) >> 1;
|
|
|
|
*p++ = BGRA_to_format(RGBA(g, g, g, ALPHA(col)), format);
|
|
}
|
|
|
|
MODIFY(img);
|
|
}
|
|
|
|
GB_COLOR IMAGE_get_pixel(GB_IMG *img, int x, int y)
|
|
{
|
|
uint col;
|
|
|
|
if (!is_valid(img, x, y))
|
|
return (-1);
|
|
|
|
SYNCHRONIZE(img);
|
|
col = ((uint *)img->data)[y * img->width + x];
|
|
return GB_COLOR_from_format(col, img->format);
|
|
}
|
|
|
|
void IMAGE_set_pixel(GB_IMG *img, int x, int y, GB_COLOR col)
|
|
{
|
|
if (!is_valid(img, x, y))
|
|
return;
|
|
|
|
SYNCHRONIZE(img);
|
|
((uint *)img->data)[y * img->width + x] = GB_COLOR_to_format(col, img->format);
|
|
MODIFY(img);
|
|
}
|
|
|
|
void IMAGE_fill_rect(GB_IMG *img, int x, int y, int w, int h, GB_COLOR col, bool opaque)
|
|
{
|
|
uint *p;
|
|
int i;
|
|
uint c;
|
|
int format = img->format;
|
|
|
|
if (x >= img->width || y >= img->height) return;
|
|
|
|
if (x < 0) { w += x; x = 0; }
|
|
if (y < 0) { h += y; y = 0; }
|
|
if ((x + w) > img->width) { w = img->width - x; }
|
|
if ((y + h) > img->height) { h = img->height - y; }
|
|
|
|
if (w <= 0 || h <= 0) return;
|
|
|
|
SYNCHRONIZE(img);
|
|
|
|
p = &((uint *)img->data)[y * img->width + x];
|
|
|
|
c = GB_COLOR_to_BGRA(col);
|
|
|
|
if (opaque || ALPHA(c) == 255)
|
|
{
|
|
c = BGRA_to_format(c, format);
|
|
while (h)
|
|
{
|
|
for(i = w; i; i--)
|
|
*p++ = c;
|
|
h--;
|
|
p += img->width - w;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (h)
|
|
{
|
|
for(i = w; i; i--, p++)
|
|
*p = BGRA_to_format(BGRA_compose(BGRA_from_format(*p, format), c), format);
|
|
h--;
|
|
p += img->width - w;
|
|
}
|
|
}
|
|
|
|
MODIFY(img);
|
|
}
|
|
|
|
void IMAGE_replace(GB_IMG *img, GB_COLOR src, GB_COLOR dst, bool noteq)
|
|
{
|
|
GET_POINTER(img, p, pm);
|
|
|
|
src = GB_COLOR_to_format(src, img->format);
|
|
dst = GB_COLOR_to_format(dst, img->format);
|
|
|
|
SYNCHRONIZE(img);
|
|
|
|
if (noteq)
|
|
{
|
|
while (p != pm)
|
|
{
|
|
if (*p != src)
|
|
*p = dst;
|
|
p++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (p != pm)
|
|
{
|
|
if (*p == src)
|
|
*p = dst;
|
|
p++;
|
|
}
|
|
}
|
|
|
|
MODIFY(img);
|
|
}
|
|
|
|
// Comes from the GIMP
|
|
|
|
typedef
|
|
struct {
|
|
float r;
|
|
float b;
|
|
float g;
|
|
float a;
|
|
}
|
|
FLOAT_RGB;
|
|
|
|
static void color_to_alpha(FLOAT_RGB *src, const FLOAT_RGB *color)
|
|
{
|
|
FLOAT_RGB alpha;
|
|
|
|
alpha.a = src->a;
|
|
|
|
if (color->r < 0.0001)
|
|
alpha.r = src->r;
|
|
else if (src->r > color->r)
|
|
alpha.r = (src->r - color->r) / (1.0 - color->r);
|
|
else if (src->r < color->r)
|
|
alpha.r = (color->r - src->r) / color->r;
|
|
else alpha.r = 0.0;
|
|
|
|
if (color->g < 0.0001)
|
|
alpha.g = src->g;
|
|
else if (src->g > color->g)
|
|
alpha.g = (src->g - color->g) / (1.0 - color->g);
|
|
else if (src->g < color->g)
|
|
alpha.g = (color->g - src->g) / (color->g);
|
|
else alpha.g = 0.0;
|
|
|
|
if (color->b < 0.0001)
|
|
alpha.b = src->b;
|
|
else if (src->b > color->b)
|
|
alpha.b = (src->b - color->b) / (1.0 - color->b);
|
|
else if (src->b < color->b)
|
|
alpha.b = (color->b - src->b) / (color->b);
|
|
else alpha.b = 0.0;
|
|
|
|
if (alpha.r > alpha.g)
|
|
{
|
|
if (alpha.r > alpha.b)
|
|
src->a = alpha.r;
|
|
else
|
|
src->a = alpha.b;
|
|
}
|
|
else if (alpha.g > alpha.b)
|
|
{
|
|
src->a = alpha.g;
|
|
}
|
|
else
|
|
{
|
|
src->a = alpha.b;
|
|
}
|
|
|
|
if (src->a < 0.0001)
|
|
return;
|
|
|
|
src->r = (src->r - color->r) / src->a + color->r;
|
|
src->g = (src->g - color->g) / src->a + color->g;
|
|
src->b = (src->b - color->b) / src->a + color->b;
|
|
|
|
src->a *= alpha.a;
|
|
}
|
|
|
|
void IMAGE_make_transparent(GB_IMG *img, GB_COLOR col)
|
|
{
|
|
uint color;
|
|
FLOAT_RGB rgb_color;
|
|
FLOAT_RGB rgb_src;
|
|
int format = img->format;
|
|
GET_POINTER(img, p, pm);
|
|
//uint *p = (uint *)img->data;
|
|
//uint *pm = (uint *)(img->data + IMAGE_size(img));
|
|
|
|
//fprintf(stderr, "IMAGE_make_transparent: %d x %d / %d\n", img->width, img->height, img->format);
|
|
|
|
SYNCHRONIZE(img);
|
|
|
|
color = GB_COLOR_to_BGRA(col);
|
|
rgb_color.b = BLUE(color) / 255.0;
|
|
rgb_color.g = GREEN(color) / 255.0;
|
|
rgb_color.r = RED(color) / 255.0;
|
|
rgb_color.a = 1.0;
|
|
|
|
while (p != pm)
|
|
{
|
|
color = BGRA_from_format(*p, format);
|
|
rgb_src.b = BLUE(color) / 255.0;
|
|
rgb_src.g = GREEN(color) / 255.0;
|
|
rgb_src.r = RED(color) / 255.0;
|
|
rgb_src.a = ALPHA(color) / 255.0;
|
|
|
|
color_to_alpha(&rgb_src, &rgb_color);
|
|
|
|
color = RGBA(
|
|
(unsigned char)(255.0 * rgb_src.r + 0.5),
|
|
(unsigned char)(255.0 * rgb_src.g + 0.5),
|
|
(unsigned char)(255.0 * rgb_src.b + 0.5),
|
|
(unsigned char)(255.0 * rgb_src.a + 0.5)
|
|
);
|
|
|
|
*p = BGRA_to_format(color, format);
|
|
//fprintf(stderr, "[%d] %08X\n", p - (uint *)img->data, *p);
|
|
p++;
|
|
}
|
|
|
|
MODIFY(img);
|
|
//fprintf(stderr, "IMAGE_make_transparent: ** DONE **\n");
|
|
}
|
|
|
|
|
|
void IMAGE_set_default_format(int format)
|
|
{
|
|
_default_format = GB_IMAGE_FMT_CLEAR_PREMULTIPLIED(format);
|
|
}
|
|
|
|
int IMAGE_get_default_format()
|
|
{
|
|
return _default_format;
|
|
}
|
|
|
|
// Parameter correction
|
|
#define CHECK_PARAMETERS(dst, dx, dy, dw, dh, src, sx, sy, sw, sh) \
|
|
if ( sw < 0 ) sw = src->width; \
|
|
if ( sh < 0 ) sh = src->height; \
|
|
if (dw < 0) dw = sw; \
|
|
if (dh < 0) dh = sh; \
|
|
if (dw != sw || dh != sh) \
|
|
{ \
|
|
GB.Error("Stretching images is not implemented in gb.image"); \
|
|
return; \
|
|
} \
|
|
if ( sx < 0 ) { dx -= sx; dw += sx; sw += sx; sx = 0; } \
|
|
if ( sy < 0 ) { dy -= sy; dh += sx; sh += sy; sy = 0; } \
|
|
if ( dx < 0 ) { sx -= dx; sw += dx; dx = 0; } \
|
|
if ( dy < 0 ) { sy -= dy; sh += dy; dy = 0; } \
|
|
if ( (sx + sw) > src->width ) sw = src->width - sx; \
|
|
if ( (sy + sh) > src->height ) sh = src->height - sy; \
|
|
if ( (dx + sw) > dst->width ) sw = dst->width - dx; \
|
|
if ( (dy + sh) > dst->height ) sh = dst->height - dy; \
|
|
if (sw <= 0 || sh <= 0) \
|
|
return;
|
|
|
|
void IMAGE_bitblt(GB_IMG *dst, int dx, int dy, int dw, int dh, GB_IMG *src, int sx, int sy, int sw, int sh)
|
|
{
|
|
int sfmt = src->format;
|
|
int dfmt = dst->format;
|
|
|
|
CHECK_PARAMETERS(dst, dx, dy, dw, dh, src, sx, sy, sw, sh);
|
|
|
|
SYNCHRONIZE(src);
|
|
SYNCHRONIZE(dst);
|
|
|
|
uint *d = (uint *)dst->data + dy * dst->width + dx;
|
|
uint *s = (uint *)src->data + sy * src->width + sx;
|
|
|
|
if (GB_IMAGE_FMT_IS_32_BITS(sfmt) && GB_IMAGE_FMT_IS_32_BITS(dfmt))
|
|
{
|
|
if (sfmt != dfmt)
|
|
{
|
|
const int dd = dst->width - sw;
|
|
const int ds = src->width - sw;
|
|
int t;
|
|
while (sh--)
|
|
{
|
|
for (t = sw; t--;)
|
|
{
|
|
*d = BGRA_to_format(BGRA_from_format(*s, sfmt), dfmt);
|
|
d++;
|
|
s++;
|
|
}
|
|
|
|
d += dd;
|
|
s += ds;
|
|
}
|
|
}
|
|
else if (sw < 64)
|
|
{
|
|
const int dd = dst->width - sw;
|
|
const int ds = src->width - sw;
|
|
int t;
|
|
while (sh--)
|
|
{
|
|
for (t = sw; t--;)
|
|
*d++ = *s++;
|
|
d += dd;
|
|
s += ds;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Trust libc
|
|
const int dd = dst->width;
|
|
const int ds = src->width;
|
|
const int b = sw * sizeof(uint);
|
|
while (sh--)
|
|
{
|
|
memcpy(d, s, b);
|
|
d += dd;
|
|
s += ds;
|
|
}
|
|
}
|
|
}
|
|
else if (GB_IMAGE_FMT_IS_24_BITS(sfmt) && GB_IMAGE_FMT_IS_24_BITS(sfmt))
|
|
{
|
|
char *d = (char *)dst->data + (dy * dst->width + dx) * 3;
|
|
char *s = (char *)src->data + (sy * src->width + sx) * 3;
|
|
const int dd = dst->width * 3;
|
|
const int ds = src->width * 3;
|
|
const int b = sw * 3;
|
|
|
|
while (sh--)
|
|
{
|
|
memcpy(d, s, b);
|
|
d += dd;
|
|
s += ds;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GB.Error("The pixel size of both images must be the same");
|
|
}
|
|
|
|
MODIFY(dst);
|
|
}
|
|
|
|
void IMAGE_draw_alpha(GB_IMG *dst, int dx, int dy, GB_IMG *src, int sx, int sy, int sw, int sh)
|
|
{
|
|
if (!GB_IMAGE_FMT_IS_32_BITS(src->format) || !GB_IMAGE_FMT_IS_32_BITS(dst->format))
|
|
{
|
|
GB.Error("The images must have an alpha channel");
|
|
return;
|
|
}
|
|
|
|
CHECK_PARAMETERS(dst, dx, dy, sw, sh, src, sx, sy, sw, sh);
|
|
|
|
SYNCHRONIZE(src);
|
|
SYNCHRONIZE(dst);
|
|
|
|
uchar *d = (uchar *)((uint *)dst->data + dy * dst->width + dx);
|
|
uchar *s = (uchar *)((uint *)src->data + sy * src->width + sx);
|
|
|
|
const int dd = (dst->width - sw) * 4;
|
|
const int ds = (src->width - sw) * 4;
|
|
//uint cs, cd;
|
|
int sformat = src->format;
|
|
int dformat = dst->format;
|
|
int t;
|
|
|
|
if (!GB_IMAGE_FMT_IS_SWAPPED(sformat))
|
|
s += 3;
|
|
if (!GB_IMAGE_FMT_IS_SWAPPED(dformat))
|
|
d += 3;
|
|
|
|
while (sh--)
|
|
{
|
|
for (t = sw; t--; d += 4,s += 4)
|
|
{
|
|
if (*s < *d)
|
|
*d = *s;
|
|
}
|
|
|
|
d += dd;
|
|
s += ds;
|
|
}
|
|
|
|
MODIFY(dst);
|
|
}
|
|
|
|
void IMAGE_compose(GB_IMG *dst, int dx, int dy, int dw, int dh, GB_IMG *src, int sx, int sy, int sw, int sh)
|
|
{
|
|
if (dst->format != src->format)
|
|
{
|
|
GB.Error("The images must have the same format");
|
|
return;
|
|
}
|
|
|
|
CHECK_PARAMETERS(dst, dx, dy, dw, dh, src, sx, sy, sw, sh);
|
|
|
|
SYNCHRONIZE(src);
|
|
SYNCHRONIZE(dst);
|
|
|
|
switch(src->format)
|
|
{
|
|
case GB_IMAGE_RGBA: case GB_IMAGE_BGRA:
|
|
{
|
|
#if 0
|
|
uint *d = (uint *)dst->data + dy * dst->width + dx;
|
|
uint *s = (uint *)src->data + sy * src->width + sx;
|
|
|
|
const int dd = dst->width - sw;
|
|
const int ds = src->width - sw;
|
|
int t;
|
|
while (sh--)
|
|
{
|
|
for (t = sw; t--;)
|
|
{
|
|
unsigned char a = ALPHA(*s);
|
|
if (a == 255)
|
|
*d = *s;
|
|
else if (a)
|
|
{
|
|
unsigned char r = ((RED(*s)-RED(*d)) * a) / 256 + RED(*d);
|
|
unsigned char g = ((GREEN(*s)-GREEN(*d)) * a) / 256 + GREEN(*d);
|
|
unsigned char b = ((BLUE(*s)-BLUE(*d)) * a) / 256 + BLUE(*d);
|
|
if (ALPHA(*d) > a)
|
|
a = ALPHA(*d);
|
|
*d = RGBA(r,g,b,a);
|
|
}
|
|
d++;
|
|
s++;
|
|
}
|
|
d += dd;
|
|
s += ds;
|
|
}
|
|
#else
|
|
uchar *d = (uchar *)((uint *)dst->data + dy * dst->width + dx);
|
|
uchar *s = (uchar *)((uint *)src->data + sy * src->width + sx);
|
|
|
|
const int dd = (dst->width - sw) * 4;
|
|
const int ds = (src->width - sw) * 4;
|
|
int t;
|
|
while (sh--)
|
|
{
|
|
for (t = sw; t--;)
|
|
{
|
|
unsigned char a = s[3];
|
|
if (a == 255)
|
|
*(uint *)d = *(uint *)s;
|
|
else if (a)
|
|
{
|
|
d[0] = ((s[0] - d[0]) * a) / 256 + d[0];
|
|
d[1] = ((s[1] - d[1]) * a) / 256 + d[1];
|
|
d[2] = ((s[2] - d[2]) * a) / 256 + d[2];
|
|
if (d[3] > a)
|
|
d[3] = a;
|
|
}
|
|
d += 4;
|
|
s += 4;
|
|
}
|
|
d += dd;
|
|
s += ds;
|
|
}
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
GB.Error("Unsupported image format");
|
|
return;
|
|
}
|
|
|
|
MODIFY(dst);
|
|
}
|
|
|
|
void IMAGE_colorize(GB_IMG *img, GB_COLOR color)
|
|
{
|
|
GET_POINTER(img, p, pm);
|
|
uint col;
|
|
int h, s, v;
|
|
int r, g, b;
|
|
int hcol, scol, vcol;
|
|
int format = img->format;
|
|
|
|
SYNCHRONIZE(img);
|
|
|
|
col = GB_COLOR_to_BGRA(color);
|
|
COLOR_rgb_to_hsv(RED(col), GREEN(col), BLUE(col), &hcol, &scol, &vcol);
|
|
|
|
while (p != pm)
|
|
{
|
|
col = BGRA_from_format(*p, format);
|
|
COLOR_rgb_to_hsv(RED(col), GREEN(col), BLUE(col), &h, &s, &v);
|
|
COLOR_hsv_to_rgb(hcol, scol, v, &r, &g, &b);
|
|
*p++ = BGRA_to_format(RGBA(r, g, b, ALPHA(col)), img->format);
|
|
}
|
|
|
|
MODIFY(img);
|
|
}
|
|
|
|
void IMAGE_mask(GB_IMG *img, GB_COLOR color)
|
|
{
|
|
GET_POINTER(img, p, pm);
|
|
uint col;
|
|
unsigned char red[256], blue[256], green[256], alpha[256];
|
|
int i, r, g, b, a;
|
|
int format = img->format;
|
|
|
|
SYNCHRONIZE(img);
|
|
|
|
col = GB_COLOR_to_format(color, img->format);
|
|
r = RED(col);
|
|
g = GREEN(col);
|
|
b = BLUE(col);
|
|
a = ALPHA(col);
|
|
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
red[i] = i * r / 255;
|
|
green[i] = i * g / 255;
|
|
blue[i] = i * b / 255;
|
|
alpha[i] = i * a / 255;
|
|
}
|
|
|
|
while (p != pm)
|
|
{
|
|
col = BGRA_from_format(*p, format);
|
|
*p++ = BGRA_to_format(RGBA(red[RED(col)], green[GREEN(col)], blue[BLUE(col)], alpha[ALPHA(col)]), format);
|
|
}
|
|
|
|
MODIFY(img);
|
|
}
|
|
|
|
void IMAGE_mirror(GB_IMG *src, GB_IMG *dst, bool horizontal, bool vertical)
|
|
{
|
|
if (dst->width != src->width || dst->height != src->height || dst->format != src->format || IMAGE_is_void(src))
|
|
return;
|
|
|
|
int w = src->width;
|
|
int h = src->height;
|
|
|
|
int dxi = horizontal ? -1 : 1;
|
|
int dxs = horizontal ? (w - 1) : 0;
|
|
int dyi = vertical ? -1 : 1;
|
|
int dy = vertical ? (h - 1) : 0;
|
|
int sx, sy;
|
|
|
|
SYNCHRONIZE(src);
|
|
|
|
if (GB_IMAGE_FMT_IS_24_BITS(src->format))
|
|
{
|
|
for (sy = 0; sy < h; sy++, dy += dyi)
|
|
{
|
|
uint24 *ssl = (uint24 *)(src->data + sy * src->width * 3);
|
|
uint24 *dsl = (uint24 *)(dst->data + dy * dst->width * 3);
|
|
int dx = dxs;
|
|
for (sx = 0; sx < w; sx++, dx += dxi)
|
|
dsl[dx] = ssl[sx];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (sy = 0; sy < h; sy++, dy += dyi)
|
|
{
|
|
uint *ssl = (uint *)(src->data + sy * src->width * 4);
|
|
uint *dsl = (uint *)(dst->data + dy * dst->width * 4);
|
|
int dx = dxs;
|
|
for (sx = 0; sx < w; sx++, dx += dxi)
|
|
dsl[dx] = ssl[sx];
|
|
}
|
|
}
|
|
|
|
MODIFY(dst);
|
|
}
|
|
|
|
#if 0
|
|
#define GET_RGBA(_col, _r, _g, _b, _a) { _r = RED(_col); _g = GREEN(_col); _b = BLUE(_col); _a = ALPHA(_col); }
|
|
|
|
void IMAGE_transform(GB_IMG *dst, GB_IMG *src, double sx, double sy, double sdx, double sdy)
|
|
{
|
|
int x, y;
|
|
double ssx;
|
|
double ssy;
|
|
uint col, cdp;
|
|
int ix, iy;
|
|
uchar r, g, b, a;
|
|
uchar rc, gc, bc, ac;
|
|
int wx, wy;
|
|
|
|
uint *dp = (uint *)dst->data;
|
|
memset(dp, 0, IMAGE_size(dst));
|
|
|
|
dp += dst->width;
|
|
|
|
for (y = 1; y < (dst->height - 1); y++)
|
|
{
|
|
ssx = sx;
|
|
ssy = sy;
|
|
|
|
dp++;
|
|
for (x = 1; x < (dst->width - 1); x++)
|
|
{
|
|
ix = sx;
|
|
iy = sy;
|
|
|
|
if (is_valid(src, ix, iy))
|
|
{
|
|
col = BGRA_from_format(*((uint *)src->data + iy * src->width + ix), src->format);
|
|
|
|
//dp[1] = RGBA(RED(col) * wx / 256, GREEN(col) * wx / 256, BLUE(col) * wx / 256, ALPHA(col) * wx / 256);
|
|
|
|
wx = 128;
|
|
|
|
cdp = BGRA_from_format(*dp, dst->format);
|
|
dp[0] = RGBA(RED(cdp) + RED(col) * wx / 256, GREEN(cdp) + GREEN(col) * wx / 256, BLUE(cdp) + BLUE(col) * wx / 256, ALPHA(col));
|
|
|
|
wx = 32;
|
|
|
|
cdp = BGRA_from_format(dp[-1], dst->format);
|
|
dp[-1] = RGBA(RED(cdp) + RED(col) * wx / 256, GREEN(cdp) + GREEN(col) * wx / 256, BLUE(cdp) + BLUE(col) * wx / 256, ALPHA(col));
|
|
|
|
cdp = BGRA_from_format(dp[-dst->width], dst->format);
|
|
dp[-dst->width] = RGBA(RED(cdp) + RED(col) * wx / 256, GREEN(cdp) + GREEN(col) * wx / 256, BLUE(cdp) + BLUE(col) * wx / 256, ALPHA(col));
|
|
|
|
dp[1] = RGBA(RED(col) * wx / 256, GREEN(col) * wx / 256, BLUE(col) * wx / 256, 0);
|
|
|
|
dp[dst->width] = dp[1]; //RGBA(RED(col) * wx / 256, GREEN(col) * wx / 256, BLUE(col) * wx / 256, ALPHA(col) * wx / 256);
|
|
}
|
|
|
|
sx += sdx;
|
|
sy += sdy;
|
|
dp++;
|
|
}
|
|
dp++;
|
|
sx = ssx - sdy;
|
|
sy = ssy + sdx;
|
|
}
|
|
}
|
|
#endif
|