KiCad PCB EDA Suite
Loading...
Searching...
No Matches
render_utils.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <render_utils.h>
21
22#include <algorithm>
23#include <cmath>
24#include <math/util.h>
25
26
27tl::expected<wxImage, std::string> CreateAlphaImageFromTwoRenders( const wxImage& aOnWhite, const wxImage& aOnBlack )
28{
29 if( !aOnWhite.IsOk() || !aOnBlack.IsOk() )
30 return tl::make_unexpected( "Invalid input images" );
31
32 if( aOnWhite.GetSize() != aOnBlack.GetSize() )
33 return tl::make_unexpected( "Input images have different sizes" );
34
35 const int width = aOnWhite.GetWidth();
36 const int height = aOnWhite.GetHeight();
37
38 wxImage result( width, height );
39 result.InitAlpha();
40
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();
45
46 for( int i = 0; i < width * height; ++i )
47 {
48 const int idx = i * 3;
49
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];
56
57 // The difference between the white- and black-background renders reveals the
58 // per-pixel coverage; the black render carries the premultiplied colour.
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;
63
64 const int alpha = std::clamp( 255 - avgDiff, 0, 255 );
65 alphaResult[i] = static_cast<unsigned char>( alpha );
66
67 if( alpha > 0 )
68 {
69 // Un-premultiply the colour, recovering the straight colour.
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 ) );
73 }
74 else
75 {
76 // Fully transparent pixels carry no meaningful colour.
77 rgbResult[idx] = 0;
78 rgbResult[idx + 1] = 0;
79 rgbResult[idx + 2] = 0;
80 }
81 }
82
83 return result;
84}
85
86
87/*
88 * This is the "Color to Alpha" algorithm from GIMP (the GEGL color-to-alpha
89 * operation).
90 *
91 * The implementation of this is taken with love from GEGL's
92 * operations/common-gpl3+/color-to-alpha.c, which is licensed under the GNU
93 * General Public License version 3 or later (GPLv3+). The original code is
94 * Copyright (C) 1995-2017 by the GIMP Development Team and is licensed under the
95 * GPLv3+.
96 *
97 * A pixel that:
98 * - matches the background colour within the transparency threshold becomes
99 * fully transparent
100 * - has channels that are further than the opacity threshold stays fully opaque
101 * - everything in between gets a proportional opacity.
102 */
103void ConvertColourToAlphaInPlace( wxImage& aImage, const wxColour& aColour )
104{
105 /*
106 * Thresholds of 0 and 1 are the GIMP defaults, which means a linear
107 * scale from full match = transparent to full mismatch = opaque.
108 * These can be made into parameters if needed, but for now they are hard-coded.
109 */
110 constexpr float TRANSPARENCY_THRESHOLD = 0.0f;
111 constexpr float OPACITY_THRESHOLD = 1.0f;
112
113 const int w = aImage.GetWidth();
114 const int h = aImage.GetHeight();
115
116 if( w == 0 || h == 0 )
117 return;
118
119 aImage.UnShare();
120
121 if( !aImage.HasAlpha() )
122 aImage.InitAlpha();
123
124 unsigned char* rgb = aImage.GetData();
125 unsigned char* alpha = aImage.GetAlpha();
126
127 // Background colour to make transparent, as float components in [0, 1].
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,
132 };
133
134 constexpr float EPSILON = 0.00001f;
135 const float transparencyThreshold = TRANSPARENCY_THRESHOLD + EPSILON;
136 const float opacityThreshold = OPACITY_THRESHOLD - EPSILON;
137
138 for( int y = 0; y < h; ++y )
139 {
140 for( int x = 0; x < w; ++x )
141 {
142 // Index into the RGB array for the pixel
143 const int pos = ( y * w + x ) * 3;
144 const int alphaPos = y * w + x;
145
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,
150 };
151 const float srcAlpha = static_cast<float>( alpha[alphaPos] ) / 255.0f;
152
153 // The largest fraction of the pixel that each channel can keep
154 // without leaving its colour range, and the channel distance that
155 // produced it.
156 float outAlpha = 0.0f;
157 float dist = 0.0f;
158
159 for( int i = 0; i < 3; ++i )
160 {
161 const float channelDist = std::fabs( srcRgb[i] - bgRgb[i] );
162
163 float a = 0.0f;
164
165 if( channelDist < transparencyThreshold )
166 {
167 a = 0.0f;
168 }
169 else if( channelDist > opacityThreshold )
170 {
171 a = 1.0f;
172 }
173 else if( srcRgb[i] < bgRgb[i] )
174 {
175 const float factor = std::min( opacityThreshold, bgRgb[i] ) - transparencyThreshold;
176 a = ( channelDist - transparencyThreshold ) / factor;
177 }
178 else
179 {
180 const float factor = std::min( opacityThreshold, 1.0f - bgRgb[i] ) - transparencyThreshold;
181 a = ( channelDist - transparencyThreshold ) / factor;
182 }
183
184 // Choose the largest (most opaque) alpha value from the three channels
185 if( a > outAlpha )
186 {
187 outAlpha = a;
188 dist = channelDist;
189 }
190 }
191
192 // If the new alpha is nonzero, remove the background contribution from the colour and
193 // un-premultiply the colour by the new alpha. Otherwise leave the colour as-is.
194 if( outAlpha > EPSILON )
195 {
196 const float ratio = transparencyThreshold / dist;
197 const float alphaInv = 1.0f / outAlpha;
198
199 for( int i = 0; i < 3; ++i )
200 {
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 );
204 }
205 }
206
207 alpha[alphaPos] = std::clamp( KiROUND( srcAlpha * outAlpha * 255.0f ), 0, 255 );
208 }
209 }
210}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
#define EPSILON
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.
void ConvertColourToAlphaInPlace(wxImage &aImage, const wxColour &aColour)
Apply the "Color to Alpha" algorithm in place, converting aColour to transparent.
wxString result
Test unit parsing edge cases and error handling.