KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_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
24
26
27// Code under test
28#include <render_utils.h>
29
30#include <vector>
31
32
33namespace
34{
35
37wxImage makeOpaquePixel( unsigned char aR, unsigned char aG, unsigned char aB )
38{
39 wxImage img( 1, 1 );
40 img.SetRGB( 0, 0, aR, aG, aB );
41 return img;
42}
43
47unsigned char compositeOver( int aColour, int aAlpha, int aBackground )
48{
49 return ( aColour * aAlpha + aBackground * ( 255 - aAlpha ) ) / 255;
50}
51
52
56void checkPixelRGBA( const wxImage& aImage, int aX, int aY, int aR, int aG, int aB, int aA )
57{
58 BOOST_REQUIRE( aImage.HasAlpha() );
59
60 BOOST_TEST_INFO( "pixel (" << aX << ", " << aY << "): got rgba("
61 << static_cast<int>( aImage.GetRed( aX, aY ) ) << ", "
62 << static_cast<int>( aImage.GetGreen( aX, aY ) ) << ", "
63 << static_cast<int>( aImage.GetBlue( aX, aY ) ) << ", "
64 << static_cast<int>( aImage.GetAlpha( aX, aY ) )
65 << "), expected rgba(" << aR << ", " << aG << ", " << aB << ", " << aA
66 << ")" );
67
68 BOOST_CHECK_EQUAL( static_cast<int>( aImage.GetRed( aX, aY ) ), aR );
69 BOOST_CHECK_EQUAL( static_cast<int>( aImage.GetGreen( aX, aY ) ), aG );
70 BOOST_CHECK_EQUAL( static_cast<int>( aImage.GetBlue( aX, aY ) ), aB );
71 BOOST_CHECK_EQUAL( static_cast<int>( aImage.GetAlpha( aX, aY ) ), aA );
72}
73
74} // namespace
75
76
77BOOST_AUTO_TEST_SUITE( RenderUtils )
78
79
80BOOST_AUTO_TEST_CASE( OpaquePixelIsPreserved )
81{
82 // An opaque pixel renders identically over white and black.
83 const wxImage onWhite = makeOpaquePixel( 255, 0, 0 );
84 const wxImage onBlack = onWhite;
85
86 const tl::expected<wxImage, std::string> result = CreateAlphaImageFromTwoRenders( onWhite, onBlack );
87
88 BOOST_REQUIRE( result.has_value() );
89
90 const wxImage& image = *result;
91
92 checkPixelRGBA( image, 0, 0, 255, 0, 0, 255 );
93}
94
95
96BOOST_AUTO_TEST_CASE( TransparentPixelHasNoColour )
97{
98 // Fully transparent content shows the background: white over white, black over black.
99 const wxImage onWhite = makeOpaquePixel( 255, 255, 255 );
100 const wxImage onBlack = makeOpaquePixel( 0, 0, 0 );
101
102 const tl::expected<wxImage, std::string> result = CreateAlphaImageFromTwoRenders( onWhite, onBlack );
103
104 BOOST_REQUIRE( result.has_value() );
105
106 const wxImage& image = *result;
107
108 checkPixelRGBA( image, 0, 0, 0, 0, 0, 0 );
109}
110
111
112BOOST_AUTO_TEST_CASE( SemiTransparentPixelRecoversAlphaAndColour )
113{
114 // A 50% red pixel composites over white to (255, 128, 128) and over black to
115 // (128, 0, 0): recovered alpha is 128 and the straight colour is pure red.
116 const wxImage onWhite = makeOpaquePixel( 255, 128, 128 );
117 const wxImage onBlack = makeOpaquePixel( 128, 0, 0 );
118
119 const tl::expected<wxImage, std::string> result =
120 CreateAlphaImageFromTwoRenders( onWhite, onBlack );
121
122 BOOST_REQUIRE( result.has_value() );
123
124 const wxImage& image = *result;
125
126 checkPixelRGBA( image, 0, 0, 255, 0, 0, 128 );
127}
128
129
130BOOST_AUTO_TEST_CASE( RoundTripReproducesTheRenders )
131{
132 // Synthesise the white/black renders of a known straight-alpha pixel, then check the
133 // recovered image recomposites back to those same renders (within the rounding of the
134 // 8-bit integer arithmetic).
135 struct PIXEL
136 {
137 unsigned char r, g, b, a;
138 };
139
140 const std::vector<PIXEL> cases = {
141 { 255, 0, 0, 255 },
142 { 255, 0, 0, 128 },
143 { 0, 255, 0, 64 },
144 { 0, 0, 255, 200 },
145 { 200, 100, 50, 80 },
146 { 255, 255, 255, 30 },
147 { 0, 0, 0, 0 },
148 };
149
150 const auto checkNear =
151 []( int a, int b, const char* channel )
152 {
153 BOOST_TEST_INFO( "channel " << channel << ": got " << a << ", expected " << b );
154 BOOST_CHECK( a >= b - 1 && a <= b + 1 );
155 };
156
157 for( const PIXEL& pix : cases )
158 {
159 BOOST_TEST_CONTEXT( "rgba(" << pix.r << ", " << pix.g << ", " << pix.b << ", " << pix.a << ")" )
160 {
161 const wxImage onWhite = makeOpaquePixel( compositeOver( pix.r, pix.a, 255 ),
162 compositeOver( pix.g, pix.a, 255 ),
163 compositeOver( pix.b, pix.a, 255 ) );
164 const wxImage onBlack = makeOpaquePixel( compositeOver( pix.r, pix.a, 0 ),
165 compositeOver( pix.g, pix.a, 0 ),
166 compositeOver( pix.b, pix.a, 0 ) );
167
168 const tl::expected<wxImage, std::string> result = CreateAlphaImageFromTwoRenders( onWhite, onBlack );
169
170 BOOST_REQUIRE( result.has_value() );
171
172 const wxImage& image = *result;
173
174 BOOST_REQUIRE( image.HasAlpha() );
175
176 // The recovered coverage is within rounding of the original.
177 const int alpha = image.GetAlpha( 0, 0 );
178 BOOST_CHECK( alpha >= pix.a - 1 && alpha <= pix.a + 1 );
179
180 // Recompositing over white and black must reproduce the input renders.
181 for( const int bg : { 255, 0 } )
182 {
183 const wxImage& render = bg ? onWhite : onBlack;
184
185 checkNear( compositeOver( image.GetRed( 0, 0 ), alpha, bg ), render.GetRed( 0, 0 ), "red" );
186 checkNear( compositeOver( image.GetGreen( 0, 0 ), alpha, bg ), render.GetGreen( 0, 0 ), "green" );
187 checkNear( compositeOver( image.GetBlue( 0, 0 ), alpha, bg ), render.GetBlue( 0, 0 ), "blue" );
188 }
189 }
190 }
191}
192
193
194BOOST_AUTO_TEST_CASE( InvalidInputsReturnError )
195{
196 const wxImage empty;
197 const wxImage pixel = makeOpaquePixel( 0, 0, 0 );
198
199 wxImage wide( 2, 1 );
200 wide.SetRGB( 0, 0, 0, 0, 0 );
201 wide.SetRGB( 1, 0, 0, 0, 0 );
202
203 const auto emptyInput = CreateAlphaImageFromTwoRenders( empty, pixel );
204 BOOST_CHECK( !emptyInput.has_value() );
205 BOOST_CHECK( !emptyInput.error().empty() );
206
207 const auto emptyInput2 = CreateAlphaImageFromTwoRenders( pixel, empty );
208 BOOST_CHECK( !emptyInput2.has_value() );
209 BOOST_CHECK( !emptyInput2.error().empty() );
210
211 const auto sizeMismatch = CreateAlphaImageFromTwoRenders( pixel, wide );
212 BOOST_CHECK( !sizeMismatch.has_value() );
213 BOOST_CHECK( !sizeMismatch.error().empty() );
214}
215
216
221BOOST_AUTO_TEST_CASE( ColourToAlphaMakesBackgroundTransparent )
222{
223 wxImage img( 3, 1 );
224 img.SetRGB( 0, 0, 255, 255, 255 ); // closest to the background -> transparent
225 img.SetRGB( 1, 0, 42, 42, 42 ); // in between -> semi-transparent
226 img.SetRGB( 2, 0, 0, 0, 0 ); // farthest from the background
227
228 ConvertColourToAlphaInPlace( img, wxColour( 255, 255, 255 ) );
229
230 BOOST_REQUIRE( img.HasAlpha() );
231
232 // White is now fully transparent, black stays fully opaque.
233 BOOST_CHECK_EQUAL( static_cast<int>( img.GetAlpha( 0, 0 ) ), 0 );
234 BOOST_CHECK_EQUAL( static_cast<int>( img.GetAlpha( 1, 0 ) ), 255 - 42 );
235 BOOST_CHECK_EQUAL( static_cast<int>( img.GetAlpha( 2, 0 ) ), 255 );
236}
237
238
static bool empty(const wxTextEntryBase *aCtrl)
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.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST_INFO("Two-port Series .op current = "<< iDevice)
BOOST_AUTO_TEST_CASE(OpaquePixelIsPreserved)
BOOST_TEST_CONTEXT("Test Clearance")
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")