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
220BOOST_FIXTURE_TEST_CASE( ReferenceImagePpiScaleMigration, REFERENCE_IMAGE_LOAD_TEST_FIXTURE )
221{
222 BOARD board;
223 auto image = std::make_unique<PCB_REFERENCE_IMAGE>( &board );
224
225 wxMemoryBuffer buffer;
226 buffer.AppendData( png_3780_ppm.data(), png_3780_ppm.size() );
227 BOOST_REQUIRE( image->GetReferenceImage().ReadImageFile( buffer ) );
228
229 BOOST_REQUIRE_EQUAL( image->GetReferenceImage().GetImage().GetPPI(), 96 );
230 BOOST_REQUIRE_EQUAL( image->GetReferenceImage().GetImage().GetLegacyPPI(), 94 );
231
232 image->GetReferenceImage().SetImageScale( 2.0 );
233 image->SetLayer( F_Cu );
234 board.Add( image.release() );
235
236 const std::string path = ( std::filesystem::temp_directory_path()
237 / "issue23575_ppi_migration.kicad_pcb" ).string();
239
240 std::ifstream in( path );
241 std::stringstream ss;
242 ss << in.rdbuf();
243 const std::string current = ss.str();
244
245 // Current format version: the stored scale is already correct, so it loads verbatim.
246 std::unique_ptr<BOARD> reloaded = parseBoardString( current );
247 BOOST_REQUIRE( reloaded );
248 BOOST_CHECK_CLOSE( firstImageScale( *reloaded ), 2.0, 1e-6 );
249
250 // Simulate a pre-fix file by lowering the format version below the migration cutoff. Match
251 // the live version rather than a literal so a later format bump does not break this test.
252 std::string legacy = current;
253 const std::string version = std::to_string( SEXPR_BOARD_FILE_VERSION );
254 const size_t pos = legacy.find( version );
255 BOOST_REQUIRE( pos != std::string::npos );
256 legacy.replace( pos, version.size(), "20260512" );
257
258 std::unique_ptr<BOARD> migrated = parseBoardString( legacy );
259 BOOST_REQUIRE( migrated );
260 BOOST_CHECK_CLOSE( firstImageScale( *migrated ), 2.0 * 96.0 / 94.0, 1e-3 );
261}
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:83
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition board.cpp:1355
PCB_LAYER_ID FlipLayer(PCB_LAYER_ID aLayer) const
Definition board.cpp:987
const DRAWINGS & Drawings() const
Definition board.h:423
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...
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.
#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:82
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683