Skip to content

Commit

Permalink
Engine: rework PrepareSpriteForUse(), replace remove_alpha_channel()
Browse files Browse the repository at this point in the history
* Reworked PrepareSpriteForUse(), make it easier to understand what's done there.
* Replace remove_alpha_channel() function with ReplaceHalfAlphaWithRGBMask(), which shares the code with ReplaceAlphaWithRGBMask().
* Remove ReplaceBitmapWithSupportedFormat(), because it's no longer used anywhere.
  • Loading branch information
ivan-mogilko committed Aug 15, 2024
1 parent c991b5f commit 7696f10
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 117 deletions.
4 changes: 2 additions & 2 deletions Common/gfx/bitmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ void MakeOpaqueSkipMask(Bitmap *bmp)
}
}

void ReplaceAlphaWithRGBMask(Bitmap *bmp)
void ReplaceAlphaWithRGBMask(Bitmap *bmp, int alpha_threshold)
{
if (bmp->GetColorDepth() < 32)
return; // no alpha channel
Expand All @@ -170,7 +170,7 @@ void ReplaceAlphaWithRGBMask(Bitmap *bmp)
uint32_t *line = reinterpret_cast<uint32_t*>(bmp->GetScanLineForWriting(i));
uint32_t *line_end = line + bmp->GetWidth();
for (uint32_t *px = line; px != line_end; ++px)
if (geta32(*px) == 0)
if (geta32(*px) <= alpha_threshold)
*px = MASK_COLOR_32;
}
}
Expand Down
6 changes: 5 additions & 1 deletion Common/gfx/bitmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,12 @@ namespace BitmapHelper
// Makes the given bitmap opaque (full alpha), while keeping pixel RGB unchanged.
// Skips mask color (leaves it with zero alpha).
void MakeOpaqueSkipMask(Bitmap *bmp);
// Replaces pixels with alpha <= threshold with standard mask color.
void ReplaceAlphaWithRGBMask(Bitmap *bmp, int alpha_threshold);
// Replaces fully transparent (alpha = 0) pixels with standard mask color.
void ReplaceAlphaWithRGBMask(Bitmap *bmp);
inline void ReplaceZeroAlphaWithRGBMask(Bitmap *bmp) { ReplaceAlphaWithRGBMask(bmp, 0); }
// Replaces less than 50% transparent (alpha < 128) pixels with standard mask color.
inline void ReplaceHalfAlphaWithRGBMask(Bitmap *bmp) { ReplaceAlphaWithRGBMask(bmp, 127); }
// Copy transparency mask and/or alpha channel from one bitmap into another.
// Destination and mask bitmaps must be of the same pixel format.
// Transparency is merged, meaning that fully transparent pixels on
Expand Down
97 changes: 49 additions & 48 deletions Engine/ac/draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,55 +428,67 @@ void setpal() {
set_palette_range(palette, 0, 255, 0);
}

// NOTE: Some of these conversions are required even when using
// D3D and OpenGL rendering, for two reasons:
// 1) certain raw drawing operations are still performed by software
// Allegro methods, hence bitmaps should be kept compatible to any native
// software operations, such as blitting two bitmaps of different formats.
// 2) OpenGL renderer assumes native bitmaps are in OpenGL-compatible format,
// so that it could copy them to texture without additional changes.
//
// TODO: make gfxDriver->GetCompatibleBitmapFormat describe all necessary
// conversions, so that we did not have to guess.
//
Bitmap *AdjustBitmapForUseWithDisplayMode(Bitmap* bitmap, bool has_alpha)
Bitmap *CreateCompatBitmap(int width, int height, int col_depth)
{
return new Bitmap(width, height,
gfxDriver->GetCompatibleBitmapFormat(col_depth == 0 ? game.GetColorDepth() : col_depth));
}

// PrepareSpriteForUseImpl converts input bitmap to format which may be used
// in AGS sprite operations, including raw drawing operations.
// In addition, in rare cases, it may require a conversion to a format
// compatible with the graphics driver (which may be converted to a texture).
// * conv_to_gamedepth - tells whether the sprite has to be matching game's
// default color depth; otherwise its color depth is to be kept (if possible).
// * has_alpha - for sprites with alpha channel (ARGB) tells whether their
// alpha channel should be kept, otherwise it's filled with opaqueness
Bitmap *PrepareSpriteForUseImpl(Bitmap* bitmap, bool has_alpha)
{
// sprite must be converted to game's color depth;
// this behavior is hardcoded in the current engine version
const bool conv_to_gamedepth = true;
const int bmp_col_depth = bitmap->GetColorDepth();
const int game_col_depth = game.GetColorDepth();
const int compat_col_depth = gfxDriver->GetCompatibleBitmapFormat(game_col_depth);

const bool must_switch_palette = bitmap->GetColorDepth() == 8 && game_col_depth > 8;
// Palette must be selected if we convert a 8-bit bitmap for a 32-bit game
const bool must_switch_palette = conv_to_gamedepth && (bitmap->GetColorDepth() == 8) && (game_col_depth > 8);
if (must_switch_palette)
select_palette(palette);

Bitmap *new_bitmap = bitmap;

//
// The following code brings bitmaps to the native game's format
// (has no dependency on display mode).
//
// In 32-bit game 32-bit bitmaps should have transparent pixels marked
// (this adjustment is probably needed for DrawingSurface ops)
if (game_col_depth == 32 && bmp_col_depth == 32)
// If it was requested to convert bitmap to the game's default color depth,
// the do so if bitmap is not matching the game.
bool was_conv_to_gamedepth = false;
if (conv_to_gamedepth && (bmp_col_depth != game_col_depth))
{
if (has_alpha)
BitmapHelper::ReplaceAlphaWithRGBMask(new_bitmap);
}
// In 32-bit game hicolor bitmaps must be converted to the true color
else if (game_col_depth == 32 && (bmp_col_depth > 8 && bmp_col_depth <= 16))
{
new_bitmap = BitmapHelper::CreateBitmapCopy(bitmap, compat_col_depth);
// Prior to downgrading a 32-bit sprite with valid alpha channel,
// replace its alpha channel to a regular transparency mask.
if ((bmp_col_depth > 32) && has_alpha)
{
BitmapHelper::ReplaceHalfAlphaWithRGBMask(bitmap);
}

new_bitmap = GfxUtil::ConvertBitmap(bitmap, gfxDriver->GetCompatibleBitmapFormat(game_col_depth));
was_conv_to_gamedepth = true;
}
// In non-32-bit game truecolor bitmaps must be downgraded
else if (game_col_depth <= 16 && bmp_col_depth > 16)

// Handle alpha channel values for 32-bit bitmaps in 32-bit games.
// * If alpha channel is requested, then mark fully transparent pixels as
// MASK_COLOR. This adjustment is currently required for DrawingSurface
// operations in script.
// * Else this is either a 32-bit sprite with ignored alpha channel or
// it was converted from another color depth, then make a fully-opaque
// alpha channel, except for the existing MASK_COLOR pixels.
if ((game_col_depth == 32) && (new_bitmap->GetColorDepth() == 32))
{
if (has_alpha) // if has valid alpha channel, convert it to regular transparency mask
new_bitmap = remove_alpha_channel(bitmap);
else // else simply convert bitmap
new_bitmap = BitmapHelper::CreateBitmapCopy(bitmap, compat_col_depth);
if (has_alpha)
BitmapHelper::ReplaceZeroAlphaWithRGBMask(new_bitmap);
else
BitmapHelper::MakeOpaqueSkipMask(new_bitmap);
}

// Finally, if we did not create a new copy already, - convert to driver compatible format
// Finally, if we did not create a new copy already, - ensure gfxdriver compatible format
if (new_bitmap == bitmap)
new_bitmap = GfxUtil::ConvertBitmap(bitmap, gfxDriver->GetCompatibleBitmapFormat(bitmap->GetColorDepth()));

Expand All @@ -486,28 +498,17 @@ Bitmap *AdjustBitmapForUseWithDisplayMode(Bitmap* bitmap, bool has_alpha)
return new_bitmap;
}

Bitmap *CreateCompatBitmap(int width, int height, int col_depth)
{
return new Bitmap(width, height,
gfxDriver->GetCompatibleBitmapFormat(col_depth == 0 ? game.GetColorDepth() : col_depth));
}

Bitmap *ReplaceBitmapWithSupportedFormat(Bitmap *bitmap)
{
return GfxUtil::ConvertBitmap(bitmap, gfxDriver->GetCompatibleBitmapFormat(bitmap->GetColorDepth()));
}

Bitmap *PrepareSpriteForUse(Bitmap* bitmap, bool has_alpha)
{
Bitmap *new_bitmap = AdjustBitmapForUseWithDisplayMode(bitmap, has_alpha);
Bitmap *new_bitmap = PrepareSpriteForUseImpl(bitmap, has_alpha);
if (new_bitmap != bitmap)
delete bitmap;
return new_bitmap;
}

PBitmap PrepareSpriteForUse(PBitmap bitmap, bool has_alpha)
{
Bitmap *new_bitmap = AdjustBitmapForUseWithDisplayMode(bitmap.get(), has_alpha);
Bitmap *new_bitmap = PrepareSpriteForUseImpl(bitmap.get(), has_alpha);
return new_bitmap == bitmap.get() ? bitmap : PBitmap(new_bitmap); // if bitmap is same, don't create new smart ptr!
}

Expand Down
10 changes: 4 additions & 6 deletions Engine/ac/draw.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,11 @@ void defgame_to_finalgame_coords(int &x, int &y);
// Creates bitmap of a format compatible with the gfxdriver;
// if col_depth is 0, uses game's native color depth.
Common::Bitmap *CreateCompatBitmap(int width, int height, int col_depth = 0);
// Checks if the bitmap is compatible with the gfxdriver;
// returns same bitmap or its copy of a compatible format.
Common::Bitmap *ReplaceBitmapWithSupportedFormat(Common::Bitmap *bitmap);
// Checks if the bitmap needs any kind of adjustments before it may be used
// in AGS sprite operations. Also handles number of certain special cases
// (old systems or uncommon gfx modes, and similar stuff).
// Peforms any kind of conversions over bitmap if they are necessary for it
// to be be used in AGS sprite operations. Returns either old or new bitmap.
// Original bitmap **gets deleted** if a new bitmap had to be created.
// * has_alpha - for sprites with alpha channel (ARGB) tells whether their
// alpha channel should be kept, otherwise it's filled with opaqueness.
Common::Bitmap *PrepareSpriteForUse(Common::Bitmap *bitmap, bool has_alpha);
// Same as above, but compatible for std::shared_ptr.
Common::PBitmap PrepareSpriteForUse(Common::PBitmap bitmap, bool has_alpha);
Expand Down
2 changes: 1 addition & 1 deletion Engine/ac/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,7 @@ std::unique_ptr<Bitmap> read_savedgame_screenshot(const String &savedgame)
}
if (desc.UserImage)
{
desc.UserImage.reset(PrepareSpriteForUse(desc.UserImage.release(), false));
desc.UserImage.reset(PrepareSpriteForUse(desc.UserImage.release(), false /* no alpha */));
return std::move(desc.UserImage);
}
return {};
Expand Down
2 changes: 1 addition & 1 deletion Engine/ac/global_dynamicsprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ int LoadImageFile(const char *filename)
return 0;

return add_dynamic_sprite(std::unique_ptr<Bitmap>(
PrepareSpriteForUse(image.release(), false)));
PrepareSpriteForUse(image.release(), false /* no alpha */)));
}
2 changes: 1 addition & 1 deletion Engine/ac/room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ void load_new_room(int newnum, CharacterInfo*forchar) {
}

for (size_t i = 0; i < thisroom.BgFrameCount; ++i) {
thisroom.BgFrames[i].Graphic = PrepareSpriteForUse(thisroom.BgFrames[i].Graphic, false);
thisroom.BgFrames[i].Graphic = PrepareSpriteForUse(thisroom.BgFrames[i].Graphic, false /* no alpha */);
}

set_our_eip(202);
Expand Down
54 changes: 0 additions & 54 deletions Engine/ac/sprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,60 +40,6 @@ Size get_new_size_for_sprite(const Size &size, const uint32_t sprite_flags)
return newsz;
}

// from is a 32-bit RGBA image, to is a 15/16/24-bit destination image
Bitmap *remove_alpha_channel(Bitmap *from)
{
const int game_cd = game.GetColorDepth();
Bitmap *to = BitmapHelper::CreateBitmap(from->GetWidth(), from->GetHeight(), game_cd);
const int maskcol = to->GetMaskColor();
int y,x;
unsigned int c,b,g,r;

if (game_cd == 24) // 32-to-24
{
for (y=0; y < from->GetHeight(); y++) {
unsigned int*psrc = (unsigned int *)from->GetScanLine(y);
unsigned char*pdest = (unsigned char*)to->GetScanLine(y);

for (x=0; x < from->GetWidth(); x++) {
c = psrc[x];
// less than 50% opaque, remove the pixel
if (((c >> 24) & 0x00ff) < 128)
c = maskcol;

// copy the RGB values across
memcpy(&pdest[x * 3], &c, 3);
}
}
}
else if (game_cd > 8) // 32 to 15 or 16
{
for (y=0; y < from->GetHeight(); y++) {
unsigned int*psrc = (unsigned int *)from->GetScanLine(y);
unsigned short*pdest = (unsigned short *)to->GetScanLine(y);

for (x=0; x < from->GetWidth(); x++) {
c = psrc[x];
// less than 50% opaque, remove the pixel
if (((c >> 24) & 0x00ff) < 128)
pdest[x] = maskcol;
else {
// otherwise, copy it across
r = (c >> 16) & 0x00ff;
g = (c >> 8) & 0x00ff;
b = c & 0x00ff;
pdest[x] = makecol_depth(game_cd, r, g, b);
}
}
}
}
else // 32 to 8-bit game
{ // TODO: consider similar to above approach if this becomes a wanted feature
to->Blit(from);
}
return to;
}

Bitmap *initialize_sprite(sprkey_t index, Bitmap *image, uint32_t &sprite_flags)
{
int oldeip = get_our_eip();
Expand Down
3 changes: 0 additions & 3 deletions Engine/ac/sprite.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
#include "ac/spritecache.h"
#include "gfx/bitmap.h"

// Converts from 32-bit RGBA image, to a 15/16/24-bit destination image,
// replacing more than half-translucent alpha pixels with transparency mask pixels.
Common::Bitmap *remove_alpha_channel(Common::Bitmap *from);
Size get_new_size_for_sprite(const Size &size, const uint32_t sprite_flags);
// Initializes a loaded sprite for use in the game, adjusts the sprite flags.
// Returns a resulting bitmap, which may be a new or old bitmap; or null on failure.
Expand Down

0 comments on commit 7696f10

Please sign in to comment.