KiCad PCB EDA Suite
Loading...
Searching...
No Matches
reference_image.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 "reference_image.h"
21
22#include <cmath>
23#include <cstdlib>
24#include <string>
25
26#include <wx/debug.h>
27#include <wx/mstream.h>
28
29#include <bitmap_base.h>
31#include <plotters/plotter.h>
32
33
34static bool compareImages( const BITMAP_BASE& aLeft, const BITMAP_BASE& aRight )
35{
36 const wxImage* leftImage = aLeft.GetImageData();
37 const wxImage* rightImage = aRight.GetImageData();
38
39 if( !leftImage || !rightImage )
40 return leftImage == rightImage;
41
42 wxMemoryOutputStream leftStream;
43 wxMemoryOutputStream rightStream;
44
45 if( !aLeft.SaveImageData( leftStream ) || !aRight.SaveImageData( rightStream ) )
46 return false;
47
48 size_t leftSize = leftStream.GetSize();
49 size_t rightSize = rightStream.GetSize();
50
51 if( leftSize != rightSize )
52 return false;
53
54 if( leftSize == 0 )
55 return true;
56
57 std::string leftData( leftSize, '\0' );
58 std::string rightData( rightSize, '\0' );
59
60 leftStream.CopyTo( leftData.data(), leftSize );
61 rightStream.CopyTo( rightData.data(), rightSize );
62
63 return leftData == rightData;
64}
65
66
68 m_iuScale( aIuScale ),
69 m_pos( 0, 0 ),
72 m_opacity( 1.0 ),
73 m_bitmapBase( std::make_unique<BITMAP_BASE>() )
74{
76}
77
78
80 m_iuScale( aOther.m_iuScale ),
81 m_pos( aOther.m_pos ),
84 m_opacity( aOther.m_opacity ),
85 m_bitmapBase( std::make_unique<BITMAP_BASE>( *aOther.m_bitmapBase ) )
86{
88}
89
90
94
95
97{
98 const double pixelSizeIu = (double) m_iuScale.MilsToIU( 1000 ) / m_bitmapBase->GetPPI();
99 m_bitmapBase->SetPixelSizeIu( pixelSizeIu );
100}
101
102
104{
105 wxASSERT( m_iuScale.IU_PER_MILS == aOther.m_iuScale.IU_PER_MILS );
106
107 if( &aOther != this )
108 {
109 if( aOther.m_bitmapBase )
110 {
111 m_bitmapBase = std::make_unique<BITMAP_BASE>( *aOther.m_bitmapBase );
112 }
113 m_pos = aOther.m_pos;
116 m_opacity = aOther.m_opacity;
118 }
119
120 return *this;
121}
122
123
125{
126 if( m_pos != aOther.m_pos )
127 return false;
128
130 return false;
131
132 if( m_orientation != aOther.m_orientation )
133 return false;
134
135 if( m_opacity != aOther.m_opacity )
136 return false;
137
138 if( m_bitmapBase->GetSize() != aOther.m_bitmapBase->GetSize() )
139 return false;
140
141 if( m_bitmapBase->GetPPI() != aOther.m_bitmapBase->GetPPI() )
142 return false;
143
144 if( m_bitmapBase->GetScale() != aOther.m_bitmapBase->GetScale() )
145 return false;
146
147 if( !compareImages( *m_bitmapBase, *aOther.m_bitmapBase ) )
148 return false;
149
150 return true;
151}
152
153
154double REFERENCE_IMAGE::Similarity( const REFERENCE_IMAGE& aOther ) const
155{
156 double similarity = 1.0;
157
158 if( m_pos != aOther.m_pos )
159 similarity *= 0.9;
160
161 if( m_orientation != aOther.m_orientation )
162 similarity *= 0.9;
163
164 if( m_opacity != aOther.m_opacity )
165 similarity *= 0.9;
166
167 if( m_bitmapBase->GetSize() != aOther.m_bitmapBase->GetSize() )
168 similarity *= 0.9;
169
170 if( m_bitmapBase->GetPPI() != aOther.m_bitmapBase->GetPPI() )
171 similarity *= 0.9;
172
173 if( m_bitmapBase->GetScale() != aOther.m_bitmapBase->GetScale() )
174 similarity *= 0.9;
175
176 if( !compareImages( *m_bitmapBase, *aOther.m_bitmapBase ) )
177 similarity *= 0.9;
178
179 return similarity;
180}
181
182
187
188
190{
191 // Un-rotated size of the image.
192 const VECTOR2I size = m_bitmapBase->GetSize();
193 const EDA_ANGLE angle = m_orientation.GetAngle();
194
195 if( angle.IsHorizontal() )
196 return size;
197
198 if( angle.IsVertical() )
199 return VECTOR2I{ size.y, size.x };
200
201 // The raster is stored unrotated, so its axis-aligned extent grows with the orientation.
202 const double angleCos = std::abs( angle.Cos() );
203 const double angleSin = std::abs( angle.Sin() );
204
205 const double w = size.x * angleCos + size.y * angleSin;
206 const double h = size.x * angleSin + size.y * angleCos;
207
208 return VECTOR2I{ KiROUND( w ), KiROUND( h ) };
209}
210
211
213{
214 return m_pos;
215}
216
217
219{
220 const BOX2D newBox = BOX2D::ByCenter( aPos, boundingSize() );
221
222 if( !IsBOX2Safe( newBox ) )
223 return;
224
225 m_pos = aPos;
226}
227
228
233
234
236{
237 m_transformOriginOffset = aCenter;
238}
239
240
242{
243 return m_bitmapBase->GetSize();
244}
245
246
248{
249 if( aWidth <= 0 )
250 return;
251
252 const double ratio = aWidth / (double) m_bitmapBase->GetSize().x;
253 scaleBy( ratio );
254}
255
256
257void REFERENCE_IMAGE::SetHeight( int aHeight )
258{
259 if( aHeight <= 0 )
260 return;
261
262 const double ratio = aHeight / (double) m_bitmapBase->GetSize().y;
263 scaleBy( ratio );
264}
265
266
268{
269 return m_bitmapBase->GetScale();
270}
271
272
274{
275 if( aScale <= 0 )
276 return;
277
278 const double ratio = aScale / m_bitmapBase->GetScale();
279 scaleBy( ratio );
280}
281
282
283void REFERENCE_IMAGE::scaleBy( double aRatio )
284{
285 if( aRatio <= 0 )
286 return;
287
288 const VECTOR2D currentOrigin = m_pos + m_transformOriginOffset;
289 const VECTOR2D newOffset = m_transformOriginOffset * aRatio;
290 const VECTOR2D newCenter = currentOrigin - newOffset;
291 const VECTOR2D newSize = boundingSize() * aRatio;
292
293 // The span of the image is limited to the size of the coordinate system
294 if( !IsVec2SafeXY( newSize ) )
295 return;
296
297 const BOX2D newBox = BOX2D::ByCenter( newCenter, newSize );
298
299 // Any overflow, just reject the call
300 if( !IsBOX2Safe( newBox ) )
301 return;
302
303 m_bitmapBase->SetScale( m_bitmapBase->GetScale() * aRatio );
304 SetTransformOriginOffset( KiROUND( newOffset ) );
305
306 // Don't need to recheck the box, we just did that
307 m_pos = KiROUND( newCenter );
308}
309
310
311bool REFERENCE_IMAGE::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
312{
313 VECTOR2I newPos = m_pos;
314 MIRROR( newPos, aCentre, aFlipDirection );
315
316 const BOX2D newBox = BOX2D::ByCenter( newPos, boundingSize() );
317
318 if( !IsBOX2Safe( newBox ) )
319 return false;
320
321 m_pos = newPos;
322 m_bitmapBase->Mirror( aFlipDirection );
323
324 // A reflection reverses the sense of the stored rotation.
326
327 MIRROR( m_transformOriginOffset, VECTOR2I( 0, 0 ), aFlipDirection );
328 return true;
329}
330
331
332void REFERENCE_IMAGE::Rotate( const VECTOR2I& aCenter, const EDA_ANGLE& aAngle )
333{
334 RotatePoint( m_pos, aCenter, aAngle );
336
337 m_orientation += aAngle;
338}
339
340
342{
343 return m_orientation.GetAngle();
344}
345
346
348{
349 m_orientation = aAngle;
350}
351
352
353void REFERENCE_IMAGE::SetOpacity( double aOpacity )
354{
355 m_opacity = std::clamp( aOpacity, 0.0, 1.0 );
356}
357
358
359bool REFERENCE_IMAGE::ReadImageFile( const wxString& aFullFilename )
360{
361 if( m_bitmapBase->ReadImageFile( aFullFilename ) )
362 {
364 return true;
365 }
366
367 return false;
368}
369
370
371bool REFERENCE_IMAGE::ReadImageFile( wxMemoryBuffer& aBuffer )
372{
373 if( m_bitmapBase->ReadImageFile( aBuffer ) )
374 {
376 return true;
377 }
378
379 return false;
380}
381
382
383bool REFERENCE_IMAGE::SetImage( const wxImage& aImage )
384{
385 if( m_bitmapBase->SetImage( aImage ) )
386 {
388 return true;
389 }
390
391 return false;
392}
393
394
395void REFERENCE_IMAGE::PackToBytes( std::string& aOutputBytes ) const
396{
397 wxMemoryOutputStream imageStream;
398
399 if( m_bitmapBase->GetImageData() && m_bitmapBase->SaveImageData( imageStream ) )
400 {
401 size_t size = imageStream.GetSize();
402
403 if( size > 0 )
404 {
405 aOutputBytes.resize( size );
406 imageStream.CopyTo( aOutputBytes.data(), size );
407 }
408 }
409}
410
411
412bool REFERENCE_IMAGE::UnpackFromBytes( const std::string& aInputBytes )
413{
414 if( aInputBytes.empty() )
415 return false;
416
417 wxMemoryBuffer imageBuffer;
418 imageBuffer.AppendData( aInputBytes.data(), aInputBytes.size() );
419
420 return ReadImageFile( imageBuffer );
421}
422
423
425{
426 // This cannot be null after construction
427 return *m_bitmapBase;
428}
429
430
435
436
437void REFERENCE_IMAGE::PlotImage( PLOTTER* aPlotter, const VECTOR2I& aPos, const KIGFX::COLOR4D& aDefaultColor,
438 int aDefaultPensize ) const
439{
440 const wxImage* image = m_bitmapBase->GetImageData();
441
442 if( !image || !image->IsOk() )
443 return;
444
445 // These 2 lines are useful only for plotters that cannot plot a bitmap and plot a
446 // rectangle instead of the actual image.
447 aPlotter->SetColor( aDefaultColor );
448 aPlotter->SetCurrentLineWidth( aDefaultPensize );
449
450 const double scaleFactor = m_bitmapBase->GetScalingFactor();
451 const EDA_ANGLE angle = m_orientation.GetAngle();
452 const bool faded = m_opacity < 1.0;
453
454 if( angle.IsZero() && !faded )
455 {
456 aPlotter->PlotImage( *image, aPos, scaleFactor );
457 return;
458 }
459
460 // Avoid modifying the original image via the shared buffer.
461 wxImage turned = image->Copy();
462
463 if( angle == ANGLE_90 )
464 {
465 turned = turned.Rotate90( false );
466 }
467 else if( angle == ANGLE_180 )
468 {
469 turned = turned.Rotate180();
470 }
471 else if( angle == ANGLE_270 )
472 {
473 turned = turned.Rotate90( true );
474 }
475 else if( !angle.IsZero() )
476 {
477 // Avoid opaque corners after non-cardinal rotation
478 if( !turned.HasAlpha() )
479 turned.InitAlpha();
480
481 const wxPoint centerPoint( turned.GetWidth() / 2, turned.GetHeight() / 2 );
482 turned = turned.Rotate( angle.AsRadians(), centerPoint, true );
483 }
484
485 wxCHECK_MSG( turned.IsOk(), /* void */, wxT( "Failed to rotate image" ) );
486
487 // Apply fading if the image opacity is less than 1.0
488 if( faded )
489 {
490 if( !turned.HasAlpha() )
491 turned.InitAlpha();
492
493 unsigned char* alpha = turned.GetAlpha();
494 const int pixels = turned.GetWidth() * turned.GetHeight();
495
496 // Post-multiply the alpha channel by the image overall opacity
497 for( int ii = 0; ii < pixels; ++ii )
498 alpha[ii] = static_cast<unsigned char>( KiROUND( alpha[ii] * m_opacity ) );
499 }
500
501 aPlotter->PlotImage( turned, aPos, scaleFactor );
502}
503
504
506{
507 std::swap( m_pos, aOther.m_pos );
509 std::swap( m_orientation, aOther.m_orientation );
510 std::swap( m_opacity, aOther.m_opacity );
511 std::swap( m_bitmapBase, aOther.m_bitmapBase );
512}
constexpr bool IsBOX2Safe(const BOX2< Vec > &aInput)
Check if a BOX2 is safe for use with BOX2D (probably BOX2D or BOX2L)
Definition box2.h:941
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
BOX2< VECTOR2D > BOX2D
Definition box2.h:915
This class handle bitmap images in KiCad.
Definition bitmap_base.h:47
double GetScale() const
Definition bitmap_base.h:71
bool SaveImageData(wxOutputStream &aOutStream) const
Write the bitmap data to aOutStream.
int GetPPI() const
wxImage * GetImageData()
Definition bitmap_base.h:66
VECTOR2I GetSize() const
static constexpr BOX2< VECTOR2I > ByCenter(const VECTOR2I &aCenter, const SizeVec &aSize)
Definition box2.h:72
double Sin() const
Definition eda_angle.h:177
bool IsZero() const
Definition eda_angle.h:135
bool IsHorizontal() const
Definition eda_angle.h:141
double AsRadians() const
Definition eda_angle.h:119
bool IsVertical() const
Definition eda_angle.h:147
double Cos() const
Definition eda_angle.h:196
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
Base plotter engine class.
Definition plotter.h:138
virtual void PlotImage(const wxImage &aImage, const VECTOR2I &aPos, double aScaleFactor)
Only PostScript plotters can plot bitmaps.
Definition plotter.cpp:258
virtual void SetCurrentLineWidth(int width, void *aData=nullptr)=0
Set the line width for the next drawing.
virtual void SetColor(const COLOR4D &color)=0
BITMAP_BASE & MutableImage() const
Only use this if you really need to modify the underlying image.
const EDA_IU_SCALE & m_iuScale
EDA_ANGLE GetOrientation() const
void SwapData(REFERENCE_IMAGE &aItem)
bool Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
VECTOR2I m_pos
XY coordinates of center of the bitmap.
void Rotate(const VECTOR2I &aCenter, const EDA_ANGLE &aAngle)
Rotate the image about aCenter.
void SetTransformOriginOffset(const VECTOR2I &aCenter)
bool ReadImageFile(const wxString &aFullFilename)
Read and store an image file.
VECTOR2I GetTransformOriginOffset() const
Get the center of scaling, etc, relative to the image center (GetPosition()).
VECTOR2I GetPosition() const
void PlotImage(PLOTTER *aPlotter, const VECTOR2I &aPos, const KIGFX::COLOR4D &aDefaultColor, int aDefaultPensize) const
Plot the image on a plotter, turned to its stored orientation.
EDA_ORIENTATION m_orientation
Rotation of the image content relative to the raster, in [0, 360).
bool SetImage(const wxImage &aImage)
Set the image from an existing wxImage.
std::unique_ptr< BITMAP_BASE > m_bitmapBase
REFERENCE_IMAGE & operator=(const REFERENCE_IMAGE &aOther)
void SetHeight(int aHeight)
VECTOR2I GetSize() const
VECTOR2I m_transformOriginOffset
Center of scaling, etc, relative to the image center.
bool UnpackFromBytes(const std::string &aInputBytes)
void SetOrientation(const EDA_ANGLE &aAngle)
Set the rotation of the image content relative to the stored raster.
void SetOpacity(double aOpacity)
void SetPosition(const VECTOR2I &aPos)
void SetWidth(int aWidth)
const BITMAP_BASE & GetImage() const
Get the underlying image.
double Similarity(const REFERENCE_IMAGE &aOther) const
double GetImageScale() const
void SetImageScale(double aScale)
Set the image "zoom" value.
REFERENCE_IMAGE(const EDA_IU_SCALE &aIuScale)
VECTOR2I boundingSize() const
BOX2I GetBoundingBox() const
void scaleBy(double ratio)
void PackToBytes(std::string &aOutputBytes) const
double m_opacity
The opacity of the image, in the range [0, 1].
bool operator==(const REFERENCE_IMAGE &aOther) const
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_270
Definition eda_angle.h:453
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:452
a few functions useful in geometry calculations.
bool IsVec2SafeXY(const VECTOR2< T > &aVec)
Check if both coordinates of a vector are within the limits of the integer type.
constexpr void MIRROR(T &aPoint, const T &aMirrorRef)
Updates aPoint with the mirror of aPoint relative to the aMirrorRef.
Definition mirror.h:41
FLIP_DIRECTION
Definition mirror.h:23
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
static bool compareImages(const BITMAP_BASE &aLeft, const BITMAP_BASE &aRight)
const double IU_PER_MILS
Definition base_units.h:75
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:227
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707