29 if( !aOnWhite.IsOk() || !aOnBlack.IsOk() )
30 return tl::make_unexpected(
"Invalid input images" );
32 if( aOnWhite.GetSize() != aOnBlack.GetSize() )
33 return tl::make_unexpected(
"Input images have different sizes" );
35 const int width = aOnWhite.GetWidth();
36 const int height = aOnWhite.GetHeight();
38 wxImage
result( width, height );
41 const unsigned char* rgbWhite = aOnWhite.GetData();
42 const unsigned char* rgbBlack = aOnBlack.GetData();
43 unsigned char* rgbResult =
result.GetData();
44 unsigned char* alphaResult =
result.GetAlpha();
46 for(
int i = 0; i < width * height; ++i )
48 const int idx = i * 3;
50 const int rW = rgbWhite[idx];
51 const int gW = rgbWhite[idx + 1];
52 const int bW = rgbWhite[idx + 2];
53 const int rB = rgbBlack[idx];
54 const int gB = rgbBlack[idx + 1];
55 const int bB = rgbBlack[idx + 2];
59 const int diffR = rW - rB;
60 const int diffG = gW - gB;
61 const int diffB = bW - bB;
62 const int avgDiff = ( diffR + diffG + diffB ) / 3;
64 const int alpha = std::clamp( 255 - avgDiff, 0, 255 );
65 alphaResult[i] =
static_cast<unsigned char>( alpha );
70 rgbResult[idx] =
static_cast<unsigned char>( std::min( 255, rB * 255 / alpha ) );
71 rgbResult[idx + 1] =
static_cast<unsigned char>( std::min( 255, gB * 255 / alpha ) );
72 rgbResult[idx + 2] =
static_cast<unsigned char>( std::min( 255, bB * 255 / alpha ) );
78 rgbResult[idx + 1] = 0;
79 rgbResult[idx + 2] = 0;
110 constexpr float TRANSPARENCY_THRESHOLD = 0.0f;
111 constexpr float OPACITY_THRESHOLD = 1.0f;
113 const int w = aImage.GetWidth();
114 const int h = aImage.GetHeight();
116 if( w == 0 || h == 0 )
121 if( !aImage.HasAlpha() )
124 unsigned char* rgb = aImage.GetData();
125 unsigned char* alpha = aImage.GetAlpha();
128 const float bgRgb[3] = {
129 static_cast<float>( aColour.Red() ) / 255.0f,
130 static_cast<float>( aColour.Green() ) / 255.0f,
131 static_cast<float>( aColour.Blue() ) / 255.0f,
134 constexpr float EPSILON = 0.00001f;
135 const float transparencyThreshold = TRANSPARENCY_THRESHOLD +
EPSILON;
136 const float opacityThreshold = OPACITY_THRESHOLD -
EPSILON;
138 for(
int y = 0; y < h; ++y )
140 for(
int x = 0; x < w; ++x )
143 const int pos = ( y * w + x ) * 3;
144 const int alphaPos = y * w + x;
146 const float srcRgb[3] = {
147 static_cast<float>( rgb[pos] ) / 255.0f,
148 static_cast<float>( rgb[pos + 1] ) / 255.0f,
149 static_cast<float>( rgb[pos + 2] ) / 255.0f,
151 const float srcAlpha =
static_cast<float>( alpha[alphaPos] ) / 255.0f;
156 float outAlpha = 0.0f;
159 for(
int i = 0; i < 3; ++i )
161 const float channelDist = std::fabs( srcRgb[i] - bgRgb[i] );
165 if( channelDist < transparencyThreshold )
169 else if( channelDist > opacityThreshold )
173 else if( srcRgb[i] < bgRgb[i] )
175 const float factor = std::min( opacityThreshold, bgRgb[i] ) - transparencyThreshold;
176 a = ( channelDist - transparencyThreshold ) / factor;
180 const float factor = std::min( opacityThreshold, 1.0f - bgRgb[i] ) - transparencyThreshold;
181 a = ( channelDist - transparencyThreshold ) / factor;
196 const float ratio = transparencyThreshold / dist;
197 const float alphaInv = 1.0f / outAlpha;
199 for(
int i = 0; i < 3; ++i )
201 const float c = bgRgb[i] + ( srcRgb[i] - bgRgb[i] ) * ratio;
202 const int value =
KiROUND( ( c + ( srcRgb[i] - c ) * alphaInv ) * 255.0f );
203 rgb[pos + i] = std::clamp( value, 0, 255 );
207 alpha[alphaPos] = std::clamp(
KiROUND( srcAlpha * outAlpha * 255.0f ), 0, 255 );
tl::expected< wxImage, std::string > CreateAlphaImageFromTwoRenders(const wxImage &aOnWhite, const wxImage &aOnBlack)
Combine two opaque renders of the same content into a single image with an alpha channel.
wxString result
Test unit parsing edge cases and error handling.