У вас уже есть решение, но, эй, можно обойтись без альфа-канала и сделать определенный цвет прозрачным.Принимая участие в Global Game Jam (48-часовой ограниченный игровой конкурс), нам нужно было быстро создать множество спрайтов для различных объектов без использования каких-либо сложных инструментов.
В действительности мы использовали цифровую камеру и окна.краска (мспейнт).Мы установили правило, согласно которому верхний левый угол изображения должен всегда содержать прозрачный цвет (поэтому прозрачный цвет может быть практически любым цветом, выбранным художником).Когда изображение было загружено, альфа-канал был установлен в соответствии с появлением прозрачного цвета.Несмотря на то, что это работало хорошо, все же оставались некоторые утечки прозрачного цвета в изображение (благодаря фильтрации текстур).
/**
* @brief a simple raster image with fixed RGBA8 storage
*
* The image data are always RGBA8. Alpha is stored in the most significant byte,
* followed by red, green and blue with decreasing significance.
*
* The storage is very simple, each 32 bits in the buffer contains a single pixel,
* the first pixel is in top left corner, there is no scanline padding.
*/
struct TBmp {
char n_former_bpp; /**< @brief former bpp, before conversion to RGBA8 */
bool b_grayscale; /**< @brief grayscale flag (if set, the bitmap is assumed
to contain grayscale image, stored as RGBA8) */
bool b_alpha; /**< @brief alpha channel flag (if set, the alpha channel is significant;
otherwise it's expected to be 0xff in all image pixels) */
int n_width; /**< @brief image width, in pixels */
int n_height; /**< @brief image height, in pixels */
uint32_t *p_buffer; /**< @brief pointer to image data */
};
void TransparentColor_to_Alpha(TBmp *p_sprite, bool b_force_alpha_recalc = false)
{
if(b_force_alpha_recalc || !p_sprite->b_alpha) {
uint32_t n_transparent_color = p_sprite->p_buffer[0] & 0xffffff;
// get transparent color from lower left corner
for(int i = 0, n = p_sprite->n_width * p_sprite->n_height; i < n; ++ i) {
uint32_t n_color = p_sprite->p_buffer[i];
if(n_color == n_transparent_color)
;//p_sprite->p_buffer[i] = n_color; // do nothing, color is transparent and alpha is zero
else if((n_color & 0xffffff) == n_transparent_color)
p_sprite->p_buffer[i] = n_color & 0xffffff; // clear alpha
else
p_sprite->p_buffer[i] = n_color | 0xff000000U; // set alpha
}
// calculate alpha based on transparent color (binary only)
p_sprite->b_alpha = true;
}
// build alpha channel using "transparent color"
}
Чтобы удалить прозрачный цвет с изображения, мы написали дополнительную функцию, которая быдублирующий цвет граничных пикселей, эффективно стирающий прозрачный цвет с изображения (это можно сделать, потому что прозрачность теперь находится в альфа-канале).
bool Sprite_FloodEdgeColor(TBmp *p_sprite, int n_max_grow_step_num = 0)
{
{
uint32_t n_transparent_color = p_sprite->p_buffer[0] & 0xffffff;
// get transparent color from lower left corner
TBmp *p_clone;
if(!(p_clone = p_sprite->p_Clone()))
return false;
// clone the bitmap
uint32_t *p_buffer = p_sprite->p_buffer;
uint32_t *p_buffer_pong = p_clone->p_buffer;
for(int i = 0; !n_max_grow_step_num || i < n_max_grow_step_num; ++ i) {
bool b_change = false;
for(int y = 0, w = p_sprite->n_width, h = p_sprite->n_height; y < h; ++ y) {
for(int x = 0; x < w; ++ x) {
if(p_buffer[x + w * y] == n_transparent_color) {
int n_neigh_rb = 0, n_neigh_g = 0;
int n_neigh_num = 0;
for(int sy = max(1, y) - 1, ey = min(y + 1, h - 1); sy <= ey; ++ sy) {
for(int sx = max(1, x) - 1, ex = min(x + 1, w - 1); sx <= ex; ++ sx) {
if(sx == x && sy == y)
continue; // skip self (it's transparent anyway)
uint32_t n_neigh = p_buffer[sx + w * sy];
if(n_neigh != n_transparent_color) {
n_neigh_rb += n_neigh & 0xff00ff;
n_neigh_g += n_neigh & 0xff00;
++ n_neigh_num;
}
}
}
// gather neighbour colors
if(n_neigh_num > 2) {
int r = (n_neigh_rb & 0xffff0000) / n_neigh_num;
int g = n_neigh_g / n_neigh_num;
int b = (n_neigh_rb & 0xffff) / n_neigh_num;
uint32_t n_color = (0xff0000 & min(0xff0000, r)) |
(0xff00 & min(0xff00, g)) | (0xff & min(0xff, b));
// calculate average neighbor color
p_buffer_pong[x + w * y] = n_color;
b_change = true;
}
} else
p_buffer_pong[x + w * y] = p_buffer[x + w * y]; // just copy
}
}
// grow 1px into transparent color
if(b_change || p_buffer != p_sprite->p_buffer)
std::swap(p_buffer, p_buffer_pong);
// swap the buffers ...
if(!b_change)
break;
}
if(p_buffer != p_sprite->p_buffer) {
memcpy(p_sprite->p_buffer, p_buffer,
p_sprite->n_width * p_sprite->n_height * sizeof(uint32_t));
}
// in case the last result is not in
p_clone->Delete();
// cleanup
}
// bleed colors on edge into the transparent space (to enable hifi blending)
return true;
}
Это было почти все, но изображения объектовмы использовали цифровую камеру часто с более яркими пикселями по краю, что было особенно неприятно для игрока.Итак, мы написали еще одну функцию, которая использовала бы медианный фильтр для удаления ярких пикселей с границы (в то время как остальная часть изображения не изменялась).
bool SpriteEdge_MedianFilter(TBmp *p_sprite,
bool b_prefer_darker = true, bool b_5x5_median = true)
{
{
uint32_t n_transparent_color = p_sprite->p_buffer[0] & 0xffffff;
// get transparent color from lower left corner
TBmp *p_clone;
if(!(p_clone = p_sprite->p_Clone()))
return false;
// clone the bitmap
uint32_t *p_buffer = p_sprite->p_buffer;
uint32_t *p_buffer_pong = p_clone->p_buffer;
{
const int n_off = (b_5x5_median)? 2 : 1;
const int n_thresh = (b_5x5_median)? 25 : 9;
bool b_change = false;
for(int y = 0, w = p_sprite->n_width, h = p_sprite->n_height; y < h; ++ y) {
for(int x = 0; x < w; ++ x) {
if(p_buffer[x + w * y] != n_transparent_color) {
uint32_t p_neigh_color[25];
int n_neigh_num = 0;
for(int sy = max(n_off, y) - n_off,
ey = min(y + n_off, h - 1); sy <= ey; ++ sy) {
for(int sx = max(n_off, x) - n_off,
ex = min(x + n_off, w - 1); sx <= ex; ++ sx) {
uint32_t n_neigh = p_buffer[sx + w * sy];
if(n_neigh != n_transparent_color) {
p_neigh_color[n_neigh_num] = n_neigh;
++ n_neigh_num;
}
}
}
// gather neighbour colors (including self)
if(n_neigh_num < n_thresh) { // if the pixel is on the edge ...
uint32_t r[25], g[25], b[25];
for(int i = 0; i < n_neigh_num; ++ i) {
r[i] = p_neigh_color[i] & 0xff0000;
g[i] = p_neigh_color[i] & 0xff00;
b[i] = p_neigh_color[i] & 0xff;
}
std::sort(r, r + n_neigh_num);
std::sort(g, g + n_neigh_num);
std::sort(b, b + n_neigh_num);
// calculate median neighbor color
uint32_t n_self = p_buffer[x + w * y];
int mr, mg, mb;
if(b_prefer_darker) {
mr = min(r[n_neigh_num / 2], n_self & 0xff0000);
mg = min(g[n_neigh_num / 2], n_self & 0xff00);
mb = min(b[n_neigh_num / 2], n_self & 0xff);
} else {
mr = r[n_neigh_num / 2];
mg = g[n_neigh_num / 2];
mb = b[n_neigh_num / 2];
}
int a = n_self & 0xff000000U;
p_buffer_pong[x + w * y] = mr | mg | mb | a;
b_change = true;
}
} else
p_buffer_pong[x + w * y] = p_buffer[x + w * y]; // just copy
}
}
// grow 1px into transparent color
if(b_change || p_buffer != p_sprite->p_buffer)
std::swap(p_buffer, p_buffer_pong);
// swap the buffers ...
}
if(p_buffer != p_sprite->p_buffer) {
memcpy(p_sprite->p_buffer, p_buffer,
p_sprite->n_width * p_sprite->n_height * sizeof(uint32_t));
}
// in case the last result is not in
p_clone->Delete();
// cleanup
}
return true;
}
На самом деле мы написали еще одну функцию, которая могла бы разрушитьнепрозрачные части изображения, эффективно уменьшая спрайт на определенное количество пикселей и удаляя проблемные области в случае, если их нельзя было удалить с помощью медианной функции.Вот и все, хотя он был написан примерно через час, но это в значительной степени идеальный инструмент для создания быстрых и грязных спрайтов.
Получение полного исходного кода .