diff --git a/Sources/Plasma/CoreLib/hsColorRGBA.h b/Sources/Plasma/CoreLib/hsColorRGBA.h index 21010db3..398acaf2 100644 --- a/Sources/Plasma/CoreLib/hsColorRGBA.h +++ b/Sources/Plasma/CoreLib/hsColorRGBA.h @@ -103,6 +103,7 @@ struct hsColorRGBA { hsColorRGBA& FromARGB32(uint32_t c); uint32_t ToARGB32() const; + uint32_t ToARGB32Premultiplied() const; void Read(hsStream *stream); void Write(hsStream *stream) const; @@ -141,6 +142,14 @@ inline uint32_t hsColorRGBA::ToARGB32() const | (uint32_t(b * 255.99f) << 0); } +inline uint32_t hsColorRGBA::ToARGB32Premultiplied() const +{ + return (uint32_t(a * 255.0f + 0.5f) << 24) + | (uint32_t(a * r * 255.0f + 0.5f) << 16) + | (uint32_t(a * g * 255.0f + 0.5f) << 8) + | (uint32_t(a * b * 255.0f + 0.5f) << 0); +} + inline hsColorRGBA operator+(const hsColorRGBA& s, const hsColorRGBA& t) { hsColorRGBA res; diff --git a/Sources/Plasma/FeatureLib/pfGameGUIMgr/pfGUIControlMod.cpp b/Sources/Plasma/FeatureLib/pfGameGUIMgr/pfGUIControlMod.cpp index 0ca70560..75ee85d7 100644 --- a/Sources/Plasma/FeatureLib/pfGameGUIMgr/pfGUIControlMod.cpp +++ b/Sources/Plasma/FeatureLib/pfGameGUIMgr/pfGUIControlMod.cpp @@ -683,7 +683,7 @@ bool pfGUIControlMod::ISetUpDynTextMap( plPipeline *pipe ) extraH -= height; fDynTextMap->Reset(); - fDynTextMap->Create( width, height, HasFlag( kXparentBgnd ), extraW, extraH ); + fDynTextMap->Create( width, height, HasFlag( kXparentBgnd ), extraW, extraH, true ); fDynTextMap->SetFont( GetColorScheme()->fFontFace, GetColorScheme()->fFontSize, GetColorScheme()->fFontFlags, HasFlag( kXparentBgnd ) ? false : true ); @@ -694,6 +694,7 @@ bool pfGUIControlMod::ISetUpDynTextMap( plPipeline *pipe ) // out with 1:1 mapping from textel to pixel plLayer *layer = (plLayer *)fDynTextLayer; layer->SetTransform( fDynTextMap->GetLayerTransform() ); + layer->SetBlendFlags( layer->GetBlendFlags() | hsGMatState::kBlendAlphaPremultiplied ); // Let the derived classes do their things IPostSetUpDynTextMap(); diff --git a/Sources/Plasma/NucleusLib/inc/hsGMatState.h b/Sources/Plasma/NucleusLib/inc/hsGMatState.h index d8a08c9c..aa6f1eb6 100644 --- a/Sources/Plasma/NucleusLib/inc/hsGMatState.h +++ b/Sources/Plasma/NucleusLib/inc/hsGMatState.h @@ -85,7 +85,8 @@ enum hsGMatBlendFlags { kBlendEnvBumpNext = 0x800000, kBlendSubtract = 0x1000000, kBlendRevSubtract = 0x2000000, - kBlendAlphaTestHigh = 0x4000000 + kBlendAlphaTestHigh = 0x4000000, + kBlendAlphaPremultiplied = 0x8000000 }; enum hsGMatClampFlags { diff --git a/Sources/Plasma/PubUtilLib/plGImage/plDynamicTextMap.cpp b/Sources/Plasma/PubUtilLib/plGImage/plDynamicTextMap.cpp index c8424b60..ad869803 100644 --- a/Sources/Plasma/PubUtilLib/plGImage/plDynamicTextMap.cpp +++ b/Sources/Plasma/PubUtilLib/plGImage/plDynamicTextMap.cpp @@ -76,7 +76,7 @@ plProfile_Extern(MemMipmaps); //// Constructor & Destructor ///////////////////////////////////////////////// plDynamicTextMap::plDynamicTextMap() - : fVisWidth(0), fVisHeight(0), fHasAlpha(false), fJustify(kLeftJustify), + : fVisWidth(0), fVisHeight(0), fHasAlpha(false), fPremultipliedAlpha(false), fJustify(kLeftJustify), fInitBuffer(nullptr), fFontSize(0), fFontFlags(0), fFontAntiAliasRGB(false), fFontBlockRGB(false), fHasCreateBeenCalled(false) { @@ -88,10 +88,10 @@ plDynamicTextMap::~plDynamicTextMap() Reset(); } -plDynamicTextMap::plDynamicTextMap( uint32_t width, uint32_t height, bool hasAlpha, uint32_t extraWidth, uint32_t extraHeight ) +plDynamicTextMap::plDynamicTextMap( uint32_t width, uint32_t height, bool hasAlpha, uint32_t extraWidth, uint32_t extraHeight, bool premultipliedAlpha ) : fInitBuffer(nullptr) { - Create( width, height, hasAlpha, extraWidth, extraHeight ); + Create( width, height, hasAlpha, extraWidth, extraHeight, premultipliedAlpha ); } //// SetNoCreate ////////////////////////////////////////////////////////////// @@ -113,7 +113,7 @@ void plDynamicTextMap::SetNoCreate( uint32_t width, uint32_t height, bool has //// Create /////////////////////////////////////////////////////////////////// -void plDynamicTextMap::Create( uint32_t width, uint32_t height, bool hasAlpha, uint32_t extraWidth, uint32_t extraHeight ) +void plDynamicTextMap::Create( uint32_t width, uint32_t height, bool hasAlpha, uint32_t extraWidth, uint32_t extraHeight, bool premultipliedAlpha ) { SetConfig( hasAlpha ? kARGB32Config : kRGB32Config ); @@ -121,6 +121,7 @@ void plDynamicTextMap::Create( uint32_t width, uint32_t height, bool hasAlpha fVisWidth = (uint16_t)width; fVisHeight = (uint16_t)height; fHasAlpha = hasAlpha; + fPremultipliedAlpha = premultipliedAlpha; for( fWidth = 1; fWidth < width + extraWidth; fWidth <<= 1 ); for( fHeight = 1; fHeight < height + extraHeight; fHeight <<= 1 ); @@ -385,7 +386,7 @@ void plDynamicTextMap::ClearToColor( hsColorRGBA &color ) if( !IIsValid() ) return; - uint32_t i, hex = color.ToARGB32(); + uint32_t i, hex = fPremultipliedAlpha ? color.ToARGB32Premultiplied() : color.ToARGB32(); uint32_t *data = (uint32_t *)fImage; // Buffer is of size fVisWidth x fVisHeight, so we need a bit of work to do this right @@ -500,6 +501,7 @@ void plDynamicTextMap::DrawString( uint16_t x, uint16_t y, const wchar_t *tex fCurrFont->SetRenderClipRect( 0, 0, fVisWidth, fVisHeight ); fCurrFont->SetRenderColor( fFontColor.ToARGB32() ); fCurrFont->SetRenderFlag( plFont::kRenderIntoAlpha, fFontBlockRGB ); + fCurrFont->SetRenderFlag( plFont::kRenderAlphaPremultiplied, fPremultipliedAlpha ); fCurrFont->RenderString( this, x, y, text ); } @@ -520,6 +522,7 @@ void plDynamicTextMap::DrawClippedString( int16_t x, int16_t y, const wchar_t fCurrFont->SetRenderClipping( x, y, width, height ); fCurrFont->SetRenderColor( fFontColor.ToARGB32() ); fCurrFont->SetRenderFlag( plFont::kRenderIntoAlpha, fFontBlockRGB ); + fCurrFont->SetRenderFlag( plFont::kRenderAlphaPremultiplied, fPremultipliedAlpha ); fCurrFont->RenderString( this, x, y, text ); } @@ -539,6 +542,7 @@ void plDynamicTextMap::DrawClippedString( int16_t x, int16_t y, const wchar_t SetJustify( fJustify ); fCurrFont->SetRenderClipping( clipX, clipY, width, height ); fCurrFont->SetRenderColor( fFontColor.ToARGB32() ); + fCurrFont->SetRenderFlag( plFont::kRenderAlphaPremultiplied, fPremultipliedAlpha ); fCurrFont->RenderString( this, x, y, text ); } @@ -559,6 +563,7 @@ void plDynamicTextMap::DrawWrappedString( uint16_t x, uint16_t y, const wchar fCurrFont->SetRenderWrapping( x, y, width, height ); fCurrFont->SetRenderColor( fFontColor.ToARGB32() ); fCurrFont->SetRenderFlag( plFont::kRenderIntoAlpha, fFontBlockRGB ); + fCurrFont->SetRenderFlag( plFont::kRenderAlphaPremultiplied, fPremultipliedAlpha ); fCurrFont->RenderString( this, x, y, text, lastX, lastY ); } @@ -639,7 +644,7 @@ void plDynamicTextMap::FillRect( uint16_t x, uint16_t y, uint16_t width, uint width = (uint16_t)(fWidth - x); // Gee, how hard can it REALLY be? - uint32_t i, hex = color.ToARGB32(); + uint32_t i, hex = fPremultipliedAlpha ? color.ToARGB32Premultiplied() : color.ToARGB32(); height += y; if( height > fHeight ) height = (uint16_t)fHeight; @@ -664,7 +669,7 @@ void plDynamicTextMap::FrameRect( uint16_t x, uint16_t y, uint16_t width, uin height = (uint16_t)(fHeight - y); // Shouldn't be much harder - uint32_t i, hex = color.ToARGB32(); + uint32_t i, hex = fPremultipliedAlpha ? color.ToARGB32Premultiplied() : color.ToARGB32(); uint32_t *dest1, *dest2; dest1 = GetAddr32( x, y ); @@ -699,6 +704,9 @@ void plDynamicTextMap::DrawImage( uint16_t x, uint16_t y, plMipmap *image, Dr else if( method == kImgSprite ) opts.fFlags = plMipmap::kCopySrcAlpha; + if( fPremultipliedAlpha ) + opts.fFlags |= plMipmap::kDestPremultiplied; + Composite( image, x, y, &opts ); /// HACK for now, since the alpha in the mipmap gets copied straight into the @@ -737,6 +745,9 @@ void plDynamicTextMap::DrawClippedImage( uint16_t x, uint16_t y, plMipmap *im else if( method == kImgSprite ) opts.fFlags = plMipmap::kCopySrcAlpha; + if( fPremultipliedAlpha ) + opts.fFlags |= plMipmap::kDestPremultiplied; + opts.fSrcClipX = srcClipX; opts.fSrcClipY = srcClipY; opts.fSrcClipWidth = srcClipWidth; @@ -883,6 +894,7 @@ void plDynamicTextMap::Swap( plDynamicTextMap *other ) // Swap DTMap info SWAP_ME( bool, fHasAlpha, other->fHasAlpha ); + SWAP_ME( bool, fPremultipliedAlpha, other->fPremultipliedAlpha ); SWAP_ME( bool, fShadowed, other->fShadowed ); SWAP_ME( Justify, fJustify, other->fJustify ); diff --git a/Sources/Plasma/PubUtilLib/plGImage/plDynamicTextMap.h b/Sources/Plasma/PubUtilLib/plGImage/plDynamicTextMap.h index eddf8c11..318238b8 100644 --- a/Sources/Plasma/PubUtilLib/plGImage/plDynamicTextMap.h +++ b/Sources/Plasma/PubUtilLib/plGImage/plDynamicTextMap.h @@ -113,14 +113,14 @@ class plDynamicTextMap : public plMipmap plDynamicTextMap(); - plDynamicTextMap( uint32_t width, uint32_t height, bool hasAlpha = false, uint32_t extraWidth = 0, uint32_t extraHeight = 0 ); + plDynamicTextMap( uint32_t width, uint32_t height, bool hasAlpha = false, uint32_t extraWidth = 0, uint32_t extraHeight = 0, bool premultipliedAlpha = false ); virtual ~plDynamicTextMap(); CLASSNAME_REGISTER( plDynamicTextMap ); GETINTERFACE_ANY( plDynamicTextMap, plMipmap ); - void Create( uint32_t width, uint32_t height, bool hasAlpha, uint32_t extraWidth = 0, uint32_t extraHeight = 0 ); + void Create( uint32_t width, uint32_t height, bool hasAlpha, uint32_t extraWidth = 0, uint32_t extraHeight = 0, bool premultipliedAlpha = false ); void SetNoCreate( uint32_t width, uint32_t height, bool hasAlpha ); virtual void Reset( void ); @@ -221,7 +221,7 @@ class plDynamicTextMap : public plMipmap uint32_t *IAllocateOSSurface( uint16_t width, uint16_t height ); void IDestroyOSSurface( void ); - bool fHasAlpha, fShadowed; + bool fHasAlpha, fPremultipliedAlpha, fShadowed; Justify fJustify; plString fFontFace; diff --git a/Sources/Plasma/PubUtilLib/plGImage/plFont.cpp b/Sources/Plasma/PubUtilLib/plGImage/plFont.cpp index 027de1c9..764c77f1 100644 --- a/Sources/Plasma/PubUtilLib/plGImage/plFont.cpp +++ b/Sources/Plasma/PubUtilLib/plGImage/plFont.cpp @@ -334,7 +334,9 @@ void plFont::IRenderString( plMipmap *mip, uint16_t x, uint16_t y, const wcha { if( fRenderInfo.fFlags & kRenderIntoAlpha ) { - if( ( fRenderInfo.fColor & 0xff000000 ) != 0xff000000 ) + if( fRenderInfo.fFlags & kRenderAlphaPremultiplied ) + fRenderInfo.fRenderFunc = &plFont::IRenderChar8To32AlphaPremultiplied; + else if( ( fRenderInfo.fColor & 0xff000000 ) != 0xff000000 ) fRenderInfo.fRenderFunc = &plFont::IRenderChar8To32Alpha; else fRenderInfo.fRenderFunc = &plFont::IRenderChar8To32FullAlpha; @@ -1021,6 +1023,65 @@ void plFont::IRenderChar8To32Alpha( const plFont::plCharacter &c ) } } +void plFont::IRenderChar8To32AlphaPremultiplied( const plFont::plCharacter &c ) +{ + uint8_t *src = fBMapData + c.fBitmapOff; + uint32_t *destPtr, *destBasePtr = (uint32_t *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride ); + int16_t x, y, thisHeight, xstart, thisWidth; + uint8_t srcA, srcR, srcG, srcB; + + + // Unfortunately for some fonts, their right kern value actually is + // farther left than the right edge of the bitmap (think of overlapping + // script fonts). Ideally, we should store the actual width of each char's + // bitmap and use that here. However, it really shouldn't make too big of a + // difference, especially since the dest pixels that we end up overlapping + // should already be in the cache. If it does, time to upgrade the font + // format (again) + thisWidth = fWidth;// + (int32_t)c.fRightKern; + if( thisWidth >= fRenderInfo.fMaxWidth ) + thisWidth = fRenderInfo.fMaxWidth; + + xstart = fRenderInfo.fClipRect.fX - fRenderInfo.fX; + if( xstart < 0 ) + xstart = 0; + + srcA = (uint8_t)(( fRenderInfo.fColor >> 24 ) & 0x000000ff); + srcR = (uint8_t)(( fRenderInfo.fColor >> 16 ) & 0x000000ff); + srcG = (uint8_t)(( fRenderInfo.fColor >> 8 ) & 0x000000ff); + srcB = (uint8_t)(( fRenderInfo.fColor ) & 0x000000ff); + + y = fRenderInfo.fClipRect.fY - fRenderInfo.fY + (int16_t)c.fBaseline; + if( y < 0 ) + y = 0; + else + { + destBasePtr = (uint32_t *)( (uint8_t *)destBasePtr + y*fRenderInfo.fDestStride ); + src += y*fRenderInfo.fNumCols; + } + + thisHeight = fRenderInfo.fMaxHeight + (int16_t)c.fBaseline; + if( thisHeight > (int16_t)c.fHeight ) + thisHeight = (int16_t)c.fHeight; + + for( ; y < thisHeight; y++ ) + { + destPtr = destBasePtr; + for( x = xstart; x < thisWidth; x++ ) + { + uint32_t a = src[ x ]; + if (a != 0) + { + if (srcA != 0xff) + a = (srcA*a + 127)/255; + destPtr[ x ] = ( a << 24 ) | (((srcR*a + 127)/255) << 16) | (((srcG*a + 127)/255) << 8) | ((srcB*a + 127)/255); + } + } + destBasePtr = (uint32_t *)( (uint8_t *)destBasePtr + fRenderInfo.fDestStride ); + src += fWidth; + } +} + void plFont::IRenderCharNull( const plCharacter &c ) { diff --git a/Sources/Plasma/PubUtilLib/plGImage/plFont.h b/Sources/Plasma/PubUtilLib/plGImage/plFont.h index 7c971325..20dae743 100644 --- a/Sources/Plasma/PubUtilLib/plGImage/plFont.h +++ b/Sources/Plasma/PubUtilLib/plGImage/plFont.h @@ -116,6 +116,7 @@ class plFont : public hsKeyedObject // value between the renderColor and the destColor and // leave the alpha as-is // This flag has no effect on monochrome fonts + kRenderAlphaPremultiplied = 0x00001000, // Destination has color values premultiplied by alpha }; enum Flags @@ -231,6 +232,7 @@ class plFont : public hsKeyedObject void IRenderChar8To32( const plCharacter &c ); void IRenderChar8To32Alpha( const plCharacter &c ); void IRenderChar8To32FullAlpha( const plCharacter &c ); + void IRenderChar8To32AlphaPremultiplied( const plCharacter &c ); void IRenderCharNull( const plCharacter &c ); public: diff --git a/Sources/Plasma/PubUtilLib/plGImage/plMipmap.cpp b/Sources/Plasma/PubUtilLib/plGImage/plMipmap.cpp index 3a868836..9ec4a70a 100644 --- a/Sources/Plasma/PubUtilLib/plGImage/plMipmap.cpp +++ b/Sources/Plasma/PubUtilLib/plGImage/plMipmap.cpp @@ -1746,6 +1746,18 @@ void plMipmap::Composite( plMipmap *source, uint16_t x, uint16_t y, plMipmap: for( pY = (uint16_t)srcHeight; pY > 0; pY-- ) { memcpy( dstPtr, srcPtr, srcRowBytesToCopy ); + if( options->fFlags & kDestPremultiplied ) + { + // multiply color values by alpha + for( pX = 0; pX < srcWidth; pX++ ) + { + srcAlpha = ((dstPtr[ pX ] >> 24) & 0x000000ff); + dstPtr[ pX ] = ( srcAlpha << 24 ) + | (((((dstPtr[ pX ] >> 16) & 0xff)*srcAlpha + 127)/255) << 16) + | (((((dstPtr[ pX ] >> 8) & 0xff)*srcAlpha + 127)/255) << 8) + | (((((dstPtr[ pX ] ) & 0xff)*srcAlpha + 127)/255) ); + } + } dstPtr += dstRowBytes >> 2; srcPtr += srcRowBytes >> 2; } @@ -1777,7 +1789,15 @@ void plMipmap::Composite( plMipmap *source, uint16_t x, uint16_t y, plMipmap: { srcAlpha = options->fOpacity * ( ( srcPtr[ pX ] >> 16 ) & 0x0000ff00 ) / 255 / 256; if( srcAlpha != 0 ) - dstPtr[ pX ] = ( srcPtr[ pX ] & 0x00ffffff ) | ( srcAlpha << 24 ); + { + if( options->fFlags & kDestPremultiplied ) + dstPtr[ pX ] = ( srcAlpha << 24 ) + | (((((srcPtr[ pX ] >> 16) & 0xff)*srcAlpha + 127)/255) << 16) + | (((((srcPtr[ pX ] >> 8) & 0xff)*srcAlpha + 127)/255) << 8) + | (((((srcPtr[ pX ] ) & 0xff)*srcAlpha + 127)/255) ); + else + dstPtr[ pX ] = ( srcPtr[ pX ] & 0x00ffffff ) | ( srcAlpha << 24 ); + } } dstPtr += dstRowBytes >> 2; srcPtr += srcRowBytes >> 2; diff --git a/Sources/Plasma/PubUtilLib/plGImage/plMipmap.h b/Sources/Plasma/PubUtilLib/plGImage/plMipmap.h index ebda5902..4e9769ee 100644 --- a/Sources/Plasma/PubUtilLib/plGImage/plMipmap.h +++ b/Sources/Plasma/PubUtilLib/plGImage/plMipmap.h @@ -234,11 +234,14 @@ class plMipmap : public plBitmap enum CompositeFlags { - kForceOpaque = 0x0001, // Copy src pixels raw, force dest alphas to opaque - kCopySrcAlpha = 0x0002, // Copy the src pixels raw, including alphas, overwrite dest - kBlendSrcAlpha = 0x0004, // Blend src pixels onto dest using src alpha, dest alpha = src alpha - kMaskSrcAlpha = 0x0008, // Same as copySrcAlpha, but dest is untouched when src alpha = 0 - kBlendWriteAlpha= 0x0010 // Like default (0), but writes dest alpha values + kForceOpaque = 0x0001, // Copy src pixels raw, force dest alphas to opaque + kCopySrcAlpha = 0x0002, // Copy the src pixels raw, including alphas, overwrite dest + kBlendSrcAlpha = 0x0004, // Blend src pixels onto dest using src alpha, dest alpha = src alpha + kMaskSrcAlpha = 0x0008, // Same as copySrcAlpha, but dest is untouched when src alpha = 0 + kBlendWriteAlpha = 0x0010, // Like default (0), but writes dest alpha values + + kDestPremultiplied = 0x0020, // Dest has color premultiplied by alpha + // (src always assumed nonpremultiplied for now) }; class CompositeOptions diff --git a/Sources/Plasma/PubUtilLib/plPipeline/DX/plDXPipeline.cpp b/Sources/Plasma/PubUtilLib/plPipeline/DX/plDXPipeline.cpp index b116d1ef..733297a1 100644 --- a/Sources/Plasma/PubUtilLib/plPipeline/DX/plDXPipeline.cpp +++ b/Sources/Plasma/PubUtilLib/plPipeline/DX/plDXPipeline.cpp @@ -7211,12 +7211,26 @@ void plDXPipeline::IHandleFirstStageBlend() fD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); if( fLayerState[0].fBlendFlags & hsGMatState::kBlendInvertFinalAlpha ) { - fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_INVSRCALPHA ); + if( fLayerState[0].fBlendFlags & hsGMatState::kBlendAlphaPremultiplied ) + { + fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); + } + else + { + fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_INVSRCALPHA ); + } fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_SRCALPHA ); } else { - fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); + if( fLayerState[0].fBlendFlags & hsGMatState::kBlendAlphaPremultiplied ) + { + fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); + } + else + { + fD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); + } fD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); } break;