KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_reference_image_load.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
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU 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 <boost/test/data/test_case.hpp>
22
23#include <board.h>
24#include <kiid.h>
25#include <layer_ids.h>
26#include <pcb_reference_image.h>
27#include <reference_image.h>
28#include <bitmap_base.h>
33#include <richio.h>
35
36#include <filesystem>
37#include <fstream>
38#include <sstream>
39
40#include <wx/image.h>
41
42namespace
43{
44
45struct REFERENCE_IMAGE_LOAD_TEST_FIXTURE
46{
47 REFERENCE_IMAGE_LOAD_TEST_FIXTURE() {}
48};
49
50
51struct REFERENCE_IMAGE_LOAD_TEST_CASE
52{
53 // Which one to look at in the file
54 KIID m_imageUuid;
55 // Expected values
56 bool m_expectedLocked;
57 VECTOR2I m_expectedPos;
58 double m_expectedScale;
59 // This should also check correct image decoding, as it won't work otherwise
60 VECTOR2I m_expectedPixelSize;
61};
62
63
64struct REFERENCE_IMAGE_LOAD_BOARD_TEST_CASE: public KI_TEST::BOARD_LOAD_TEST_CASE
65{
66 // List of images to check
67 std::vector<REFERENCE_IMAGE_LOAD_TEST_CASE> m_imageCases;
68};
69
70const std::vector<REFERENCE_IMAGE_LOAD_BOARD_TEST_CASE> ReferenceImageLoading_testCases{
71 {
72 "reference_images_load_save",
73 std::nullopt,
74 {
75 // From top to bottom in the board file
76 {
77 "7dde345e-020a-4fdd-af77-588b452be5e0",
78 false,
79 { 100, 46 },
80 1.0,
81 { 64, 64 },
82 },
83 {
84 "e4fd52dd-1d89-4c43-b621-aebfc9788d5c",
85 true,
86 { 100, 65 },
87 1.0,
88 { 64, 64 },
89 },
90 {
91 "d402397e-bce0-4cae-a398-b5aeef397e87",
92 false,
93 { 100, 90 },
94 2.0,
95 { 64, 64 }, // It's the same size, but scaled
96 },
97 },
98 },
99};
100
101// Minimal 1x1 RGB PNG with a pHYs chunk of 3780 pixels/meter in both axes.
102// 3780 PPM -> 37.8 px/cm -> 37.8 * 2.54 = 96.012 -> 96 PPI, but the pre-fix code truncated
103// "37.8" to 37 -> 94 PPI.
104const std::vector<unsigned char> png_3780_ppm = {
105 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
106 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
107 0xDE, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC4, 0x00, 0x00, 0x0E,
108 0xC4, 0x01, 0x95, 0x2B, 0x0E, 0x1B, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA,
109 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0xF7, 0x03, 0x41, 0x43, 0x00, 0x00,
110 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
111};
112
113
114std::unique_ptr<BOARD> parseBoardString( const std::string& aData )
115{
116 STRING_LINE_READER reader( aData, wxT( "migration test board" ) );
117 PCB_IO_KICAD_SEXPR_PARSER parser( &reader, nullptr, nullptr );
118
119 return std::unique_ptr<BOARD>( dynamic_cast<BOARD*>( parser.Parse() ) );
120}
121
122
123double firstImageScale( const BOARD& aBoard )
124{
125 for( BOARD_ITEM* item : aBoard.Drawings() )
126 {
127 if( item->Type() == PCB_REFERENCE_IMAGE_T )
128 return static_cast<PCB_REFERENCE_IMAGE*>( item )->GetReferenceImage().GetImageScale();
129 }
130
131 return -1.0;
132}
133
134} // namespace
135
136
137BOOST_DATA_TEST_CASE_F( REFERENCE_IMAGE_LOAD_TEST_FIXTURE, ReferenceImageLoading,
138 boost::unit_test::data::make( ReferenceImageLoading_testCases ), testCase )
139{
140 const auto doBoardTest = [&]( const BOARD& aBoard )
141 {
142 for( const REFERENCE_IMAGE_LOAD_TEST_CASE& imageTestCase : testCase.m_imageCases )
143 {
145 "Checking for image with UUID: " << imageTestCase.m_imageUuid.AsString() );
146
147 const auto& image =
149 aBoard, PCB_REFERENCE_IMAGE_T, imageTestCase.m_imageUuid ) );
150 const REFERENCE_IMAGE& refImage = image.GetReferenceImage();
151
152 BOOST_CHECK_EQUAL( image.IsLocked(), imageTestCase.m_expectedLocked );
153 BOOST_CHECK_EQUAL( image.GetPosition(), imageTestCase.m_expectedPos * 1000000 );
154 BOOST_CHECK_CLOSE( refImage.GetImageScale(), imageTestCase.m_expectedScale, 1e-6 );
155
156 const BITMAP_BASE& bitmap = refImage.GetImage();
157
158 BOOST_CHECK_EQUAL( bitmap.GetSizePixels(), imageTestCase.m_expectedPixelSize );
159 }
160 };
161
162 KI_TEST::LoadAndTestBoardFile( testCase.m_BoardFileRelativePath, true, doBoardTest,
163 testCase.m_ExpectedBoardVersion );
164}
165
166
171BOOST_FIXTURE_TEST_CASE( ReferenceImageFlipLayer, REFERENCE_IMAGE_LOAD_TEST_FIXTURE )
172{
173 const auto doBoardTest = [&]( const BOARD& aBoard )
174 {
175 KIID targetUuid( "7dde345e-020a-4fdd-af77-588b452be5e0" );
176
177 auto& image = static_cast<PCB_REFERENCE_IMAGE&>(
179 targetUuid ) );
180
181 BOOST_REQUIRE( image.GetReferenceImage().GetImage().GetSizePixels().x > 0 );
182
183 PCB_LAYER_ID origLayer = image.GetLayer();
184 VECTOR2I origPos = image.GetPosition();
185
186 // Flip left-right and verify layer changes
187 image.Flip( origPos, FLIP_DIRECTION::LEFT_RIGHT );
188
189 PCB_LAYER_ID flippedLayer = aBoard.FlipLayer( origLayer );
190 BOOST_CHECK_EQUAL( image.GetLayer(), flippedLayer );
191
192 // Position should stay the same since we flipped around it
193 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
194
195 // Flip again to return to original state
196 image.Flip( origPos, FLIP_DIRECTION::LEFT_RIGHT );
197
198 BOOST_CHECK_EQUAL( image.GetLayer(), origLayer );
199 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
200
201 // Same test for top-bottom flip
202 image.Flip( origPos, FLIP_DIRECTION::TOP_BOTTOM );
203
204 BOOST_CHECK_EQUAL( image.GetLayer(), flippedLayer );
205 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
206
207 image.Flip( origPos, FLIP_DIRECTION::TOP_BOTTOM );
208
209 BOOST_CHECK_EQUAL( image.GetLayer(), origLayer );
210 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
211 };
212
213 KI_TEST::LoadAndTestBoardFile( "reference_images_load_save", true, doBoardTest );
214}
215
216
218{
219public:
221 {
222 m_board = std::make_unique<BOARD>();
223 m_image = new PCB_REFERENCE_IMAGE( m_board.get() );
224 m_board->Add( m_image );
225 }
226
227 BOARD& GetBoard() { return *m_board; }
229
230 template <typename T>
231 void SetImageData( const T& aData )
232 {
233 wxMemoryBuffer buffer;
234 buffer.AppendData( aData.data(), aData.size() );
235 BOOST_REQUIRE( m_image->GetReferenceImage().ReadImageFile( buffer ) );
236 }
237
238private:
239 std::unique_ptr<BOARD> m_board;
241};
242
243
250{
251 BOARD& board = GetBoard();
252 PCB_REFERENCE_IMAGE& image = GetImage();
253
254 SetImageData( png_3780_ppm );
255
256 REFERENCE_IMAGE& refImage = image.GetReferenceImage();
257
258 BOOST_REQUIRE_EQUAL( refImage.GetImage().GetPPI(), 96 );
259 BOOST_REQUIRE_EQUAL( refImage.GetImage().GetLegacyPPI(), 94 );
260
261 refImage.SetImageScale( 2.0 );
262 image.SetLayer( F_Cu );
263
264 const std::string path = ( std::filesystem::temp_directory_path()
265 / "issue23575_ppi_migration.kicad_pcb" ).string();
267
268 std::ifstream in( path );
269 std::stringstream ss;
270 ss << in.rdbuf();
271 const std::string current = ss.str();
272
273 // Current format version: the stored scale is already correct, so it loads verbatim.
274 std::unique_ptr<BOARD> reloaded = parseBoardString( current );
275 BOOST_REQUIRE( reloaded );
276 BOOST_CHECK_CLOSE( firstImageScale( *reloaded ), 2.0, 1e-6 );
277
278 // Simulate a pre-fix file by lowering the format version below the migration cutoff. Match
279 // the live version rather than a literal so a later format bump does not break this test.
280 std::string legacy = current;
281 const std::string version = std::to_string( SEXPR_BOARD_FILE_VERSION );
282 const size_t pos = legacy.find( version );
283 BOOST_REQUIRE( pos != std::string::npos );
284 legacy.replace( pos, version.size(), "20260512" );
285
286 std::unique_ptr<BOARD> migrated = parseBoardString( legacy );
287 BOOST_REQUIRE( migrated );
288 BOOST_CHECK_CLOSE( firstImageScale( *migrated ), 2.0 * 96.0 / 94.0, 1e-3 );
289}
290
291
296BOOST_FIXTURE_TEST_CASE( ReferenceImageRotateTransformOrigin, REFERENCE_IMAGE_BOARD_FIXTURE )
297{
298 BOARD& board = GetBoard();
299 PCB_REFERENCE_IMAGE& image = GetImage();
300
301 SetImageData( png_3780_ppm );
302
303 REFERENCE_IMAGE& refImage = image.GetReferenceImage();
304 refImage.SetPosition( VECTOR2I( 1000, 2000 ) );
305 refImage.SetTransformOriginOffset( VECTOR2I( 300, -100 ) );
306
307 const VECTOR2I pos = refImage.GetPosition();
308
309 // Rotating about the image position leaves the position unchanged; the offset
310 // must follow the content.
311 refImage.Rotate( pos, ANGLE_90 );
312
313 BOOST_CHECK_EQUAL( refImage.GetPosition(), pos );
314 BOOST_CHECK_EQUAL( refImage.GetTransformOriginOffset(), VECTOR2I( -100, -300 ) );
315
316 // Now mirror it and make sure that the transform origin follows the content
317 refImage.Flip( pos, FLIP_DIRECTION::LEFT_RIGHT );
318
319 BOOST_CHECK_EQUAL( refImage.GetPosition(), pos );
320 BOOST_CHECK_EQUAL( refImage.GetTransformOriginOffset(), VECTOR2I( 100, -300 ) );
321}
322
323
329{
330 BOARD& board = GetBoard();
331 PCB_REFERENCE_IMAGE& image = GetImage();
332
333 REFERENCE_IMAGE& refImage = image.GetReferenceImage();
334
335 wxImage raster( 42, 24 );
336 raster.SetRGB( wxRect( 0, 0, 42, 24 ), 0, 0, 0 );
337 BOOST_REQUIRE( refImage.SetImage( raster ) );
338
339 refImage.SetPosition( VECTOR2I( 1000, 2000 ) );
340
341 const VECTOR2I pos = refImage.GetPosition();
342 const VECTOR2I size = refImage.GetSize();
343
344 BOOST_REQUIRE( size.x != size.y );
345
346 refImage.Rotate( pos, ANGLE_45 );
347
349 BOOST_CHECK_EQUAL( refImage.GetPosition(), pos );
350 BOOST_CHECK_EQUAL( refImage.GetSize(), size );
351
352 // The axis-aligned box of a rotated rectangle spans |w cos| + |h sin|.
353 BOOST_CHECK_CLOSE( refImage.GetBoundingBox().GetWidth(), ( size.x + size.y ) * M_SQRT1_2, 0.001 );
354 BOOST_CHECK_CLOSE( refImage.GetBoundingBox().GetHeight(), ( size.x + size.y ) * M_SQRT1_2, 0.001 );
355
356 // A quarter turn is stored too, rather than messing with pixel data.
357 refImage.Rotate( pos, ANGLE_45 );
358
360 BOOST_CHECK_EQUAL( refImage.GetSize(), size );
361 // box is rotated 90 degrees, so width and height are swapped.
362 BOOST_CHECK_EQUAL( refImage.GetBoundingBox().GetWidth(), size.y );
363 BOOST_CHECK_EQUAL( refImage.GetBoundingBox().GetHeight(), size.x );
364
365 // Rotating back restores the unrotated extent.
366 refImage.Rotate( pos, ANGLE_270 );
367
369 BOOST_CHECK_EQUAL( refImage.GetBoundingBox().GetSize(), size );
370}
General utilities for PCB file IO for QA programs.
This class handle bitmap images in KiCad.
Definition bitmap_base.h:47
VECTOR2I GetSizePixels() const
int GetPPI() const
int GetLegacyPPI() const
Recompute the PPI the way it was computed before the pixels/cm truncation fix.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayer) const
Definition board.cpp:1120
const DRAWINGS & Drawings() const
Definition board.h:467
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr const SizeVec & GetSize() const
Definition box2.h:203
Definition kiid.h:46
wxString AsString() const
Definition kiid.cpp:264
Read a Pcbnew s-expression formatted LINE_READER object and returns the appropriate BOARD_ITEM object...
Object to handle a bitmap image that can be inserted in a PCB.
A REFERENCE_IMAGE is a wrapper around a BITMAP_IMAGE that is displayed in an editor as a reference fo...
EDA_ANGLE GetOrientation() const
bool Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
void Rotate(const VECTOR2I &aCenter, const EDA_ANGLE &aAngle)
Rotate the image about aCenter.
void SetTransformOriginOffset(const VECTOR2I &aCenter)
VECTOR2I GetTransformOriginOffset() const
Get the center of scaling, etc, relative to the image center (GetPosition()).
VECTOR2I GetPosition() const
bool SetImage(const wxImage &aImage)
Set the image from an existing wxImage.
VECTOR2I GetSize() const
void SetPosition(const VECTOR2I &aPos)
const BITMAP_BASE & GetImage() const
Get the underlying image.
double GetImageScale() const
void SetImageScale(double aScale)
Set the image "zoom" value.
BOX2I GetBoundingBox() const
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:225
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:450
static constexpr EDA_ANGLE ANGLE_45
Definition eda_angle.h:449
static constexpr EDA_ANGLE ANGLE_270
Definition eda_angle.h:453
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_Cu
Definition layer_ids.h:60
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:24
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:25
void LoadAndTestBoardFile(const wxString aRelativePath, bool aRoundtrip, std::function< void(BOARD &)> aBoardTestFunction, std::optional< int > aExpectedBoardVersion)
Perform "some test" on a board file loaded from the path, then optionally save and reload and run the...
void DumpBoardToFile(BOARD &board, const std::filesystem::path &aFilename)
Utility function to simply write a Board out to a file.
BOARD_ITEM & RequireBoardItemWithTypeAndId(const BOARD &aBoard, KICAD_T aItemType, const KIID &aID)
Get an item from the given board with a certain type and UUID.
#define SEXPR_BOARD_FILE_VERSION
Current s-expression file format version. 2 was the last legacy format version.
Pcbnew s-expression file format parser definition.
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
std::string path
BOOST_FIXTURE_TEST_CASE(ReferenceImageFlipLayer, REFERENCE_IMAGE_LOAD_TEST_FIXTURE)
Test that flipping a reference image changes its associated layer, matching the behavior of all other...
BOOST_DATA_TEST_CASE_F(REFERENCE_IMAGE_LOAD_TEST_FIXTURE, ReferenceImageLoading, boost::unit_test::data::make(ReferenceImageLoading_testCases), testCase)
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
BOOST_CHECK_EQUAL(result, "25.4")
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:81
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708