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>
32#include <richio.h>
34
35#include <filesystem>
36#include <fstream>
37#include <sstream>
38
39namespace
40{
41
42struct REFERENCE_IMAGE_LOAD_TEST_FIXTURE
43{
44 REFERENCE_IMAGE_LOAD_TEST_FIXTURE() {}
45};
46
47
48struct REFERENCE_IMAGE_LOAD_TEST_CASE
49{
50 // Which one to look at in the file
51 KIID m_imageUuid;
52 // Expected values
53 bool m_expectedLocked;
54 VECTOR2I m_expectedPos;
55 double m_expectedScale;
56 // This should also check correct image decoding, as it won't work otherwise
57 VECTOR2I m_expectedPixelSize;
58};
59
60
61struct REFERENCE_IMAGE_LOAD_BOARD_TEST_CASE: public KI_TEST::BOARD_LOAD_TEST_CASE
62{
63 // List of images to check
64 std::vector<REFERENCE_IMAGE_LOAD_TEST_CASE> m_imageCases;
65};
66
67const std::vector<REFERENCE_IMAGE_LOAD_BOARD_TEST_CASE> ReferenceImageLoading_testCases{
68 {
69 "reference_images_load_save",
70 std::nullopt,
71 {
72 // From top to bottom in the board file
73 {
74 "7dde345e-020a-4fdd-af77-588b452be5e0",
75 false,
76 { 100, 46 },
77 1.0,
78 { 64, 64 },
79 },
80 {
81 "e4fd52dd-1d89-4c43-b621-aebfc9788d5c",
82 true,
83 { 100, 65 },
84 1.0,
85 { 64, 64 },
86 },
87 {
88 "d402397e-bce0-4cae-a398-b5aeef397e87",
89 false,
90 { 100, 90 },
91 2.0,
92 { 64, 64 }, // It's the same size, but scaled
93 },
94 },
95 },
96};
97
98// Minimal 1x1 RGB PNG with a pHYs chunk of 3780 pixels/meter in both axes.
99// 3780 PPM -> 37.8 px/cm -> 37.8 * 2.54 = 96.012 -> 96 PPI, but the pre-fix code truncated
100// "37.8" to 37 -> 94 PPI.
101const std::vector<unsigned char> png_3780_ppm = {
102 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
103 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
104 0xDE, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC4, 0x00, 0x00, 0x0E,
105 0xC4, 0x01, 0x95, 0x2B, 0x0E, 0x1B, 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x78, 0xDA,
106 0x63, 0xF8, 0xCF, 0xC0, 0x00, 0x00, 0x03, 0x01, 0x01, 0x00, 0xF7, 0x03, 0x41, 0x43, 0x00, 0x00,
107 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82
108};
109
110
111std::unique_ptr<BOARD> parseBoardString( const std::string& aData )
112{
113 STRING_LINE_READER reader( aData, wxT( "migration test board" ) );
114 PCB_IO_KICAD_SEXPR_PARSER parser( &reader, nullptr, nullptr );
115
116 return std::unique_ptr<BOARD>( dynamic_cast<BOARD*>( parser.Parse() ) );
117}
118
119
120double firstImageScale( const BOARD& aBoard )
121{
122 for( BOARD_ITEM* item : aBoard.Drawings() )
123 {
124 if( item->Type() == PCB_REFERENCE_IMAGE_T )
125 return static_cast<PCB_REFERENCE_IMAGE*>( item )->GetReferenceImage().GetImageScale();
126 }
127
128 return -1.0;
129}
130
131} // namespace
132
133
134BOOST_DATA_TEST_CASE_F( REFERENCE_IMAGE_LOAD_TEST_FIXTURE, ReferenceImageLoading,
135 boost::unit_test::data::make( ReferenceImageLoading_testCases ), testCase )
136{
137 const auto doBoardTest = [&]( const BOARD& aBoard )
138 {
139 for( const REFERENCE_IMAGE_LOAD_TEST_CASE& imageTestCase : testCase.m_imageCases )
140 {
142 "Checking for image with UUID: " << imageTestCase.m_imageUuid.AsString() );
143
144 const auto& image =
146 aBoard, PCB_REFERENCE_IMAGE_T, imageTestCase.m_imageUuid ) );
147 const REFERENCE_IMAGE& refImage = image.GetReferenceImage();
148
149 BOOST_CHECK_EQUAL( image.IsLocked(), imageTestCase.m_expectedLocked );
150 BOOST_CHECK_EQUAL( image.GetPosition(), imageTestCase.m_expectedPos * 1000000 );
151 BOOST_CHECK_CLOSE( refImage.GetImageScale(), imageTestCase.m_expectedScale, 1e-6 );
152
153 const BITMAP_BASE& bitmap = refImage.GetImage();
154
155 BOOST_CHECK_EQUAL( bitmap.GetSizePixels(), imageTestCase.m_expectedPixelSize );
156 }
157 };
158
159 KI_TEST::LoadAndTestBoardFile( testCase.m_BoardFileRelativePath, true, doBoardTest,
160 testCase.m_ExpectedBoardVersion );
161}
162
163
168BOOST_FIXTURE_TEST_CASE( ReferenceImageFlipLayer, REFERENCE_IMAGE_LOAD_TEST_FIXTURE )
169{
170 const auto doBoardTest = [&]( const BOARD& aBoard )
171 {
172 KIID targetUuid( "7dde345e-020a-4fdd-af77-588b452be5e0" );
173
174 auto& image = static_cast<PCB_REFERENCE_IMAGE&>(
176 targetUuid ) );
177
178 BOOST_REQUIRE( image.GetReferenceImage().GetImage().GetSizePixels().x > 0 );
179
180 PCB_LAYER_ID origLayer = image.GetLayer();
181 VECTOR2I origPos = image.GetPosition();
182
183 // Flip left-right and verify layer changes
184 image.Flip( origPos, FLIP_DIRECTION::LEFT_RIGHT );
185
186 PCB_LAYER_ID flippedLayer = aBoard.FlipLayer( origLayer );
187 BOOST_CHECK_EQUAL( image.GetLayer(), flippedLayer );
188
189 // Position should stay the same since we flipped around it
190 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
191
192 // Flip again to return to original state
193 image.Flip( origPos, FLIP_DIRECTION::LEFT_RIGHT );
194
195 BOOST_CHECK_EQUAL( image.GetLayer(), origLayer );
196 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
197
198 // Same test for top-bottom flip
199 image.Flip( origPos, FLIP_DIRECTION::TOP_BOTTOM );
200
201 BOOST_CHECK_EQUAL( image.GetLayer(), flippedLayer );
202 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
203
204 image.Flip( origPos, FLIP_DIRECTION::TOP_BOTTOM );
205
206 BOOST_CHECK_EQUAL( image.GetLayer(), origLayer );
207 BOOST_CHECK_EQUAL( image.GetPosition(), origPos );
208 };
209
210 KI_TEST::LoadAndTestBoardFile( "reference_images_load_save", true, doBoardTest );
211}
212
213
219BOOST_FIXTURE_TEST_CASE( ReferenceImagePpiScaleMigration, REFERENCE_IMAGE_LOAD_TEST_FIXTURE )
220{
221 BOARD board;
222 auto image = std::make_unique<PCB_REFERENCE_IMAGE>( &board );
223
224 wxMemoryBuffer buffer;
225 buffer.AppendData( png_3780_ppm.data(), png_3780_ppm.size() );
226 BOOST_REQUIRE( image->GetReferenceImage().ReadImageFile( buffer ) );
227
228 BOOST_REQUIRE_EQUAL( image->GetReferenceImage().GetImage().GetPPI(), 96 );
229 BOOST_REQUIRE_EQUAL( image->GetReferenceImage().GetImage().GetLegacyPPI(), 94 );
230
231 image->GetReferenceImage().SetImageScale( 2.0 );
232 image->SetLayer( F_Cu );
233 board.Add( image.release() );
234
235 const std::string path = ( std::filesystem::temp_directory_path()
236 / "issue23575_ppi_migration.kicad_pcb" ).string();
238
239 std::ifstream in( path );
240 std::stringstream ss;
241 ss << in.rdbuf();
242 const std::string current = ss.str();
243
244 // Current format version: the stored scale is already correct, so it loads verbatim.
245 std::unique_ptr<BOARD> reloaded = parseBoardString( current );
246 BOOST_REQUIRE( reloaded );
247 BOOST_CHECK_CLOSE( firstImageScale( *reloaded ), 2.0, 1e-6 );
248
249 // Simulate a pre-fix file by lowering the format version below the migration cutoff.
250 std::string legacy = current;
251 const size_t pos = legacy.find( "20260623" );
252 BOOST_REQUIRE( pos != std::string::npos );
253 legacy.replace( pos, 8, "20260512" );
254
255 std::unique_ptr<BOARD> migrated = parseBoardString( legacy );
256 BOOST_REQUIRE( migrated );
257 BOOST_CHECK_CLOSE( firstImageScale( *migrated ), 2.0 * 96.0 / 94.0, 1e-3 );
258}
General utilities for PCB file IO for QA programs.
This class handle bitmap images in KiCad.
Definition bitmap_base.h:45
VECTOR2I GetSizePixels() const
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1295
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayer) const
Definition board.cpp:978
const DRAWINGS & Drawings() const
Definition board.h:422
Definition kiid.h:44
wxString AsString() const
Definition kiid.cpp:242
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...
const BITMAP_BASE & GetImage() const
Get the underlying image.
double GetImageScale() const
Is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition richio.h:222
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 DumpBoardToFile(BOARD &board, const std::string &aFilename)
Utility function to simply write a Board out to a file.
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...
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.
Pcbnew s-expression file format parser definition.
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
std::string path
BOOST_TEST_MESSAGE("\n=== Real-World Polygon PIP Benchmark ===\n"<< formatTable(table))
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_CHECK_EQUAL(result, "25.4")
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:82
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683