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
21#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
40namespace
41{
42
43struct REFERENCE_IMAGE_LOAD_TEST_FIXTURE
44{
45 REFERENCE_IMAGE_LOAD_TEST_FIXTURE() {}
46};
47
48
49struct REFERENCE_IMAGE_LOAD_TEST_CASE
50{
51 // Which one to look at in the file
52 KIID m_imageUuid;
53 // Expected values
54 bool m_expectedLocked;
55 VECTOR2I m_expectedPos;
56 double m_expectedScale;
57 // This should also check correct image decoding, as it won't work otherwise
58 VECTOR2I m_expectedPixelSize;
59};
60
61
62struct REFERENCE_IMAGE_LOAD_BOARD_TEST_CASE: public KI_TEST::BOARD_LOAD_TEST_CASE
63{
64 // List of images to check
65 std::vector<REFERENCE_IMAGE_LOAD_TEST_CASE> m_imageCases;
66};
67
68const std::vector<REFERENCE_IMAGE_LOAD_BOARD_TEST_CASE> ReferenceImageLoading_testCases{
69 {
70 "reference_images_load_save",
71 std::nullopt,
72 {
73 // From top to bottom in the board file
74 {
75 "7dde345e-020a-4fdd-af77-588b452be5e0",
76 false,
77 { 100, 46 },
78 1.0,
79 { 64, 64 },
80 },
81 {
82 "e4fd52dd-1d89-4c43-b621-aebfc9788d5c",
83 true,
84 { 100, 65 },
85 1.0,
86 { 64, 64 },
87 },
88 {
89 "d402397e-bce0-4cae-a398-b5aeef397e87",
90 false,
91 { 100, 90 },
92 2.0,
93 { 64, 64 }, // It's the same size, but scaled
94 },
95 },
96 },
97};
98
99// Minimal 1x1 RGB PNG with a pHYs chunk of 3780 pixels/meter in both axes.
100// 3780 PPM -> 37.8 px/cm -> 37.8 * 2.54 = 96.012 -> 96 PPI, but the pre-fix code truncated
101// "37.8" to 37 -> 94 PPI.
102const std::vector<unsigned char> png_3780_ppm = {
103 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
104 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
105 0xDE, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC4, 0x00, 0x00, 0x0E,
106 0xC4, 0x01, 0x95, 0x2B, 0x0E, 0x1B, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA,
107 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0xF7, 0x03, 0x41, 0x43, 0x00, 0x00,
108 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
109};
110
111
112std::unique_ptr<BOARD> parseBoardString( const std::string& aData )
113{
114 STRING_LINE_READER reader( aData, wxT( "migration test board" ) );
115 PCB_IO_KICAD_SEXPR_PARSER parser( &reader, nullptr, nullptr );
116
117 return std::unique_ptr<BOARD>( dynamic_cast<BOARD*>( parser.Parse() ) );
118}
119
120
121double firstImageScale( const BOARD& aBoard )
122{
123 for( BOARD_ITEM* item : aBoard.Drawings() )
124 {
125 if( item->Type() == PCB_REFERENCE_IMAGE_T )
126 return static_cast<PCB_REFERENCE_IMAGE*>( item )->GetReferenceImage().GetImageScale();
127 }
128
129 return -1.0;
130}
131
132} // namespace
133
134
135BOOST_DATA_TEST_CASE_F( REFERENCE_IMAGE_LOAD_TEST_FIXTURE, ReferenceImageLoading,
136 boost::unit_test::data::make( ReferenceImageLoading_testCases ), testCase )
137{
138 const auto doBoardTest = [&]( const BOARD& aBoard )
139 {
140 for( const REFERENCE_IMAGE_LOAD_TEST_CASE& imageTestCase : testCase.m_imageCases )
141 {
143 "Checking for image with UUID: " << imageTestCase.m_imageUuid.AsString() );
144
145 const auto& image =
147 aBoard, PCB_REFERENCE_IMAGE_T, imageTestCase.m_imageUuid ) );
148 const REFERENCE_IMAGE& refImage = image.GetReferenceImage();
149
150 BOOST_CHECK_EQUAL( image.IsLocked(), imageTestCase.m_expectedLocked );
151 BOOST_CHECK_EQUAL( image.GetPosition(), imageTestCase.m_expectedPos * 1000000 );
152 BOOST_CHECK_CLOSE( refImage.GetImageScale(), imageTestCase.m_expectedScale, 1e-6 );
153
154 const BITMAP_BASE& bitmap = refImage.GetImage();
155
156 BOOST_CHECK_EQUAL( bitmap.GetSizePixels(), imageTestCase.m_expectedPixelSize );
157 }
158 };
159
160 KI_TEST::LoadAndTestBoardFile( testCase.m_BoardFileRelativePath, true, doBoardTest,
161 testCase.m_ExpectedBoardVersion );
162}
163
164
169BOOST_FIXTURE_TEST_CASE( ReferenceImageFlipLayer, REFERENCE_IMAGE_LOAD_TEST_FIXTURE )
170{
171 const auto doBoardTest = [&]( const BOARD& aBoard )
172 {
173 KIID targetUuid( "7dde345e-020a-4fdd-af77-588b452be5e0" );
174
175 auto& image = static_cast<PCB_REFERENCE_IMAGE&>(
177 targetUuid ) );
178
179 BOOST_REQUIRE( image.GetReferenceImage().GetImage().GetSizePixels().x > 0 );
180
181 PCB_LAYER_ID origLayer = image.GetLayer();
182 VECTOR2I origPos = image.GetPosition();
183
184 // Flip left-right and verify layer changes
185 image.Flip( origPos, FLIP_DIRECTION::LEFT_RIGHT );
186
187 PCB_LAYER_ID flippedLayer = aBoard.FlipLayer( origLayer );
188 BOOST_CHECK_EQUAL( image.GetLayer(), flippedLayer );
189
190 // Position should stay the same since we flipped around it
191 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
192
193 // Flip again to return to original state
194 image.Flip( origPos, FLIP_DIRECTION::LEFT_RIGHT );
195
196 BOOST_CHECK_EQUAL( image.GetLayer(), origLayer );
197 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
198
199 // Same test for top-bottom flip
200 image.Flip( origPos, FLIP_DIRECTION::TOP_BOTTOM );
201
202 BOOST_CHECK_EQUAL( image.GetLayer(), flippedLayer );
203 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
204
205 image.Flip( origPos, FLIP_DIRECTION::TOP_BOTTOM );
206
207 BOOST_CHECK_EQUAL( image.GetLayer(), origLayer );
208 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
209 };
210
211 KI_TEST::LoadAndTestBoardFile( "reference_images_load_save", true, doBoardTest );
212}
213
214
216{
217public:
219 {
220 m_board = std::make_unique<BOARD>();
221 m_image = new PCB_REFERENCE_IMAGE( m_board.get() );
222 m_board->Add( m_image );
223 }
224
225 BOARD& GetBoard() { return *m_board; }
227
228 template <typename T>
229 void SetImageData( const T& aData )
230 {
231 wxMemoryBuffer buffer;
232 buffer.AppendData( aData.data(), aData.size() );
233 BOOST_REQUIRE( m_image->GetReferenceImage().ReadImageFile( buffer ) );
234 }
235
236private:
237 std::unique_ptr<BOARD> m_board;
239};
240
241
248{
249 BOARD& board = GetBoard();
250 PCB_REFERENCE_IMAGE& image = GetImage();
251
252 SetImageData( png_3780_ppm );
253
254 REFERENCE_IMAGE& refImage = image.GetReferenceImage();
255
256 BOOST_REQUIRE_EQUAL( refImage.GetImage().GetPPI(), 96 );
257 BOOST_REQUIRE_EQUAL( refImage.GetImage().GetLegacyPPI(), 94 );
258
259 refImage.SetImageScale( 2.0 );
260 image.SetLayer( F_Cu );
261
262 const std::string path = ( std::filesystem::temp_directory_path()
263 / "issue23575_ppi_migration.kicad_pcb" ).string();
265
266 std::ifstream in( path );
267 std::stringstream ss;
268 ss << in.rdbuf();
269 const std::string current = ss.str();
270
271 // Current format version: the stored scale is already correct, so it loads verbatim.
272 std::unique_ptr<BOARD> reloaded = parseBoardString( current );
273 BOOST_REQUIRE( reloaded );
274 BOOST_CHECK_CLOSE( firstImageScale( *reloaded ), 2.0, 1e-6 );
275
276 // Simulate a pre-fix file by lowering the format version below the migration cutoff. Match
277 // the live version rather than a literal so a later format bump does not break this test.
278 std::string legacy = current;
279 const std::string version = std::to_string( SEXPR_BOARD_FILE_VERSION );
280 const size_t pos = legacy.find( version );
281 BOOST_REQUIRE( pos != std::string::npos );
282 legacy.replace( pos, version.size(), "20260512" );
283
284 std::unique_ptr<BOARD> migrated = parseBoardString( legacy );
285 BOOST_REQUIRE( migrated );
286 BOOST_CHECK_CLOSE( firstImageScale( *migrated ), 2.0 * 96.0 / 94.0, 1e-3 );
287}
288
289
294BOOST_FIXTURE_TEST_CASE( ReferenceImageRotateTransformOrigin, REFERENCE_IMAGE_BOARD_FIXTURE )
295{
296 BOARD& board = GetBoard();
297 PCB_REFERENCE_IMAGE& image = GetImage();
298
299 SetImageData( png_3780_ppm );
300
301 REFERENCE_IMAGE& refImage = image.GetReferenceImage();
302 refImage.SetPosition( VECTOR2I( 1000, 2000 ) );
303 refImage.SetTransformOriginOffset( VECTOR2I( 300, -100 ) );
304
305 const VECTOR2I pos = refImage.GetPosition();
306
307 // Rotating about the image position leaves the position unchanged; the offset
308 // must follow the content.
309 refImage.Rotate( pos, ANGLE_90 );
310
311 BOOST_CHECK_EQUAL( refImage.GetPosition(), pos );
312 BOOST_CHECK_EQUAL( refImage.GetTransformOriginOffset(), VECTOR2I( -100, -300 ) );
313
314 // Now mirror it and make sure that the transform origin follows the content
315 refImage.Flip( pos, FLIP_DIRECTION::LEFT_RIGHT );
316
317 BOOST_CHECK_EQUAL( refImage.GetPosition(), pos );
318 BOOST_CHECK_EQUAL( refImage.GetTransformOriginOffset(), VECTOR2I( 100, -300 ) );
319}
General utilities for PCB file IO for QA programs.
This class handle bitmap images in KiCad.
Definition bitmap_base.h:45
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:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayer) const
Definition board.cpp:1124
const DRAWINGS & Drawings() const
Definition board.h:465
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...
void Rotate(const VECTOR2I &aCenter, const EDA_ANGLE &aAngle)
void SetTransformOriginOffset(const VECTOR2I &aCenter)
VECTOR2I GetTransformOriginOffset() const
Get the center of scaling, etc, relative to the image center (GetPosition()).
VECTOR2I GetPosition() const
void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
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.
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:225
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:424
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:683