KiCad PCB EDA Suite
Loading...
Searching...
No Matches
bitmap_base.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 (C) 2017 jean-pierre.charras
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include "bitmap_base.h"
26
27#include <algorithm> // for std::swap
28#include <cstring> // for memcpy
29#include <gr_basic.h>
30#include <math/util.h> // for KiROUND
31#include <memory> // for make_unique, unique_ptr
32#include <plotters/plotter.h>
33#include <richio.h>
34#include <wx/bitmap.h> // for wxBitmap
35#include <wx/mstream.h>
36#include <wx/stream.h> // for wxInputStream, wxOutputStream
37#include <wx/string.h> // for wxString
38#include <wx/wfstream.h> // for wxFileInputStream
39
40
41
43{
44 m_scale = 1.0; // 1.0 = original bitmap size
45 m_imageType = wxBITMAP_TYPE_INVALID;
46 m_bitmap = nullptr;
47 m_bitmapDirty = false;
48 m_image = nullptr;
49 m_originalImage = nullptr;
50 m_ppi = 300; // the bitmap definition. the default is 300PPI
51 m_pixelSizeIu = 254000.0 / m_ppi; // a pixel size value OK for bitmaps using 300 PPI
52 // for Eeschema which uses currently 254000PPI
53 m_isMirroredX = false;
54 m_isMirroredY = false;
56}
57
58
60{
61 m_scale = aSchBitmap.m_scale;
62 m_ppi = aSchBitmap.m_ppi;
63 m_pixelSizeIu = aSchBitmap.m_pixelSizeIu;
64 m_isMirroredX = aSchBitmap.m_isMirroredX;
65 m_isMirroredY = aSchBitmap.m_isMirroredY;
66 m_rotation = aSchBitmap.m_rotation;
67 m_imageType = aSchBitmap.m_imageType;
68
69 m_image = nullptr;
70 m_bitmap = nullptr;
71 m_bitmapDirty = false;
72 m_originalImage = nullptr;
73
74 if( aSchBitmap.m_image )
75 {
76 m_image = new wxImage( *aSchBitmap.m_image );
77 m_bitmap = new wxBitmap( *m_image );
78 m_originalImage = new wxImage( *aSchBitmap.m_originalImage );
79 m_imageType = aSchBitmap.m_imageType;
80 m_imageData = aSchBitmap.m_imageData;
81 m_imageId = aSchBitmap.m_imageId;
82 }
83}
84
85
86void BITMAP_BASE::rebuildBitmap( bool aResetID )
87{
88 if( m_bitmap )
89 delete m_bitmap;
90
91 m_bitmap = new wxBitmap( *m_image );
92 m_bitmapDirty = false;
93
94 if( aResetID )
95 m_imageId = KIID();
96
97}
98
99
101{
102 if( m_bitmapDirty && m_image )
103 {
104 if( m_bitmap )
105 delete m_bitmap;
106
107 m_bitmap = new wxBitmap( *m_image );
108 m_bitmapDirty = false;
109 }
110}
111
112
114{
115 // Todo: eventually we need to support dpi / scaling in both dimensions
116 int dpiX = m_originalImage->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONX );
117
118 if( dpiX > 1 )
119 {
120 if( m_originalImage->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONUNIT ) == wxIMAGE_RESOLUTION_CM )
121 m_ppi = KiROUND( dpiX * 2.54 );
122 else
123 m_ppi = dpiX;
124 }
125}
126
127
129{
130 *m_image = *aItem.m_image;
132 m_imageId = aItem.m_imageId;
133 m_scale = aItem.m_scale;
134 m_ppi = aItem.m_ppi;
138 m_rotation = aItem.m_rotation;
139 m_imageType = aItem.m_imageType;
140 m_imageData = aItem.m_imageData;
141
142 rebuildBitmap( false );
143}
144
145
146bool BITMAP_BASE::ReadImageFile( wxInputStream& aInStream )
147{
148 // Store the original image data in m_imageData
149 size_t dataSize = aInStream.GetLength();
150 m_imageData.SetBufSize( dataSize );
151 aInStream.Read( m_imageData.GetData(), dataSize );
152 m_imageData.SetDataLen( dataSize );
153
154 std::unique_ptr<wxImage> new_image = std::make_unique<wxImage>();
155
156 // Load the image from the stream into new_image
157 wxMemoryInputStream mem_stream( m_imageData.GetData(), dataSize );
158 if( !new_image->LoadFile( mem_stream ) )
159 return false;
160
161 return SetImage( *new_image );
162}
163
164
165bool BITMAP_BASE::ReadImageFile( wxMemoryBuffer& aBuf )
166{
167 // Store the original image data in m_imageData
168 m_imageData = aBuf;
169
170 std::unique_ptr<wxImage> new_image = std::make_unique<wxImage>();
171
172 // Load the image from the buffer into new_image
173 wxMemoryInputStream mem_stream( m_imageData.GetData(), m_imageData.GetBufSize() );
174
175 if( !new_image->LoadFile( mem_stream ) )
176 return false;
177
178 return SetImage( *new_image );
179}
180
181
182bool BITMAP_BASE::ReadImageFile(const wxString& aFullFilename)
183{
184 wxFileInputStream file_stream(aFullFilename);
185
186 // Check if the file could be opened successfully
187 if (!file_stream.IsOk())
188 return false;
189
190 return ReadImageFile(file_stream);
191}
192
193
194bool BITMAP_BASE::SetImage( const wxImage& aImage )
195{
196 if( !aImage.IsOk() || aImage.GetWidth() == 0 || aImage.GetHeight() == 0 )
197 return false;
198
199 delete m_image;
200 m_image = new wxImage( aImage );
201
202 // Create a new wxImage object from m_image
203 delete m_originalImage;
204 m_originalImage = new wxImage( *m_image );
205
207 updatePPI();
208
209 return true;
210}
211
212
213bool BITMAP_BASE::SaveImageData( wxOutputStream& aOutStream ) const
214{
215 if( m_imageData.IsEmpty() )
216 {
217 // If m_imageData is empty, use wxImage::Save() method to write m_image contents to
218 // the stream.
219 wxBitmapType type = m_imageType == wxBITMAP_TYPE_JPEG ? wxBITMAP_TYPE_JPEG
220 : wxBITMAP_TYPE_PNG;
221
222 if( !m_image->SaveFile( aOutStream, type ) )
223 {
224 return false;
225 }
226 }
227 else
228 {
229 // Write the contents of m_imageData to the stream.
230 aOutStream.Write( m_imageData.GetData(), m_imageData.GetDataLen() );
231 }
232
233 return true;
234}
235
236
237bool BITMAP_BASE::LoadLegacyData( LINE_READER& aLine, wxString& aErrorMsg )
238{
239 wxMemoryOutputStream stream;
240 char* line;
241
242 while( true )
243 {
244 if( !aLine.ReadLine() )
245 {
246 aErrorMsg = wxT( "Unexpected end of data" );
247 return false;
248 }
249
250 line = aLine.Line();
251
252 if( strncasecmp( line, "EndData", 4 ) == 0 )
253 {
254 // all the PNG date is read.
255 // We expect here m_image and m_bitmap are void
256 m_image = new wxImage();
257 wxMemoryInputStream istream( stream );
258 m_image->LoadFile( istream, wxBITMAP_TYPE_ANY );
259 m_bitmap = new wxBitmap( *m_image );
260 m_originalImage = new wxImage( *m_image );
262 break;
263 }
264
265 // Read PNG data, stored in hexadecimal,
266 // each byte = 2 hexadecimal digits and a space between 2 bytes
267 // and put it in memory stream buffer
268 int len = strlen( line );
269
270 for( ; len > 0; len -= 3, line += 3 )
271 {
272 int value = 0;
273
274 if( sscanf( line, "%X", &value ) == 1 )
275 stream.PutC( (char) value );
276 else
277 break;
278 }
279 }
280
281 return true;
282}
283
284
286{
287 BOX2I bbox;
288 VECTOR2I size = GetSize();
289
290 bbox.Inflate( size.x / 2, size.y / 2 );
291
292 return bbox;
293}
294
295
296void BITMAP_BASE::DrawBitmap( wxDC* aDC, const VECTOR2I& aPos,
297 const KIGFX::COLOR4D& aBackgroundColor ) const
298{
300
301 if( m_bitmap == nullptr )
302 return;
303
304 VECTOR2I pos = aPos;
305 VECTOR2I size = GetSize();
306
307 // This fixes a bug in OSX that should be fixed in the 3.0.3 version or later.
308 if( ( size.x == 0 ) || ( size.y == 0 ) )
309 return;
310
311 // To draw the bitmap, pos is the upper left corner position
312 pos.x -= size.x / 2;
313 pos.y -= size.y / 2;
314
315 double scale;
316 int logicalOriginX, logicalOriginY;
317 aDC->GetUserScale( &scale, &scale );
318 aDC->GetLogicalOrigin( &logicalOriginX, &logicalOriginY );
319
320 // We already have issues to draw a bitmap on the wxDC, depending on wxWidgets version.
321 // Now we have an issue on wxWidgets 3.1.6 to fix the clip area
322 // and the bitmap position when using TransformMatrix
323 // So for version == 3.1.6 do not use it
324 // Be careful before changing the code.
325 bool useTransform = aDC->CanUseTransformMatrix();
326
327 wxAffineMatrix2D init_matrix = aDC->GetTransformMatrix();
328
329 // Note: clipping bitmap area was made to fix a minor issue in old versions of
330 // KiCad/wxWidgets (5.1 / wx 3.0)
331 // However SetClippingRegion creates a lot of issues (different ways to fix the
332 // position and size of the area, depending on wxWidgets version)because it changes with
333 // each versions of wxWidgets, so it is now disabled
334 // However the code is still here, just in case
335 // #define USE_CLIP_AREA
336
337 wxPoint clipAreaPos;
338
339 if( useTransform )
340 {
341 wxAffineMatrix2D matrix = aDC->GetTransformMatrix();
342 matrix.Translate( pos.x, pos.y );
343 matrix.Scale( GetScalingFactor(), GetScalingFactor() );
344 aDC->SetTransformMatrix( matrix );
345
346 // Needed on wx <= 3.1.5, and this is strange...
347 // Nevertheless, this code has problem (the bitmap is not seen)
348 // with wx version > 3.1.5
349 clipAreaPos.x = pos.x;
350 clipAreaPos.y = pos.y;
351
352 pos.x = pos.y = 0;
353 }
354 else
355 {
356 aDC->SetUserScale( scale * GetScalingFactor(), scale * GetScalingFactor() );
357 aDC->SetLogicalOrigin( logicalOriginX / GetScalingFactor(),
358 logicalOriginY / GetScalingFactor() );
359
360 pos.x = KiROUND( pos.x / GetScalingFactor() );
361 pos.y = KiROUND( pos.y / GetScalingFactor() );
362 size.x = KiROUND( size.x / GetScalingFactor() );
363 size.y = KiROUND( size.y / GetScalingFactor() );
364 clipAreaPos.x = pos.x;
365 clipAreaPos.y = pos.y;
366 }
367
368#ifdef USE_CLIP_AREA
369 aDC->DestroyClippingRegion();
370 aDC->SetClippingRegion( clipAreaPos, wxSize( size.x, size.y ) );
371#endif
372
373 if( aBackgroundColor != COLOR4D::UNSPECIFIED && m_bitmap->HasAlpha() )
374 {
375 // Most printers don't support transparent images properly,
376 // so blend the image with background color.
377
378 int w = m_bitmap->GetWidth();
379 int h = m_bitmap->GetHeight();
380
381 wxImage image( w, h );
382 wxColour bgColor = aBackgroundColor.ToColour();
383
384 image.SetRGB( wxRect( 0, 0, w, h ), bgColor.Red(), bgColor.Green(), bgColor.Blue() );
385 image.Paste( m_bitmap->ConvertToImage(), 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE );
386
388 image = image.ConvertToGreyscale();
389
390 aDC->DrawBitmap( wxBitmap( image ), pos.x, pos.y, true );
391 }
392 else if( GetGRForceBlackPenState() )
393 {
394 wxBitmap result( m_bitmap->ConvertToImage().ConvertToGreyscale() );
395 aDC->DrawBitmap( result, pos.x, pos.y, true );
396 }
397 else
398 {
399 aDC->DrawBitmap( *m_bitmap, pos.x, pos.y, true );
400 }
401
402 if( useTransform )
403 aDC->SetTransformMatrix( init_matrix );
404 else
405 {
406 aDC->SetUserScale( scale, scale );
407 aDC->SetLogicalOrigin( logicalOriginX, logicalOriginY );
408 }
409
410#ifdef USE_CLIP_AREA
411 aDC->DestroyClippingRegion();
412#endif
413}
414
415
417{
418 VECTOR2I size;
419
420 if( m_image )
421 {
422 size.x = KiROUND( m_image->GetWidth() * GetScalingFactor() );
423 size.y = KiROUND( m_image->GetHeight() * GetScalingFactor() );
424 }
425
426 return size;
427}
428
429
430void BITMAP_BASE::mirrorImageInPlace( wxImage& aImage, FLIP_DIRECTION aFlipDirection )
431{
432 const int w = aImage.GetWidth();
433 const int h = aImage.GetHeight();
434
435 if( w == 0 || h == 0 )
436 return;
437
438 unsigned char* rgb = aImage.GetData();
439 unsigned char* alpha = aImage.HasAlpha() ? aImage.GetAlpha() : nullptr;
440 const int bpp = 3;
441
442 if( aFlipDirection == FLIP_DIRECTION::LEFT_RIGHT )
443 {
444 // Swap columns left-to-right within each row
445 for( int y = 0; y < h; ++y )
446 {
447 unsigned char* rowRgb = rgb + y * w * bpp;
448
449 for( int lo = 0, hi = w - 1; lo < hi; ++lo, --hi )
450 {
451 std::swap( rowRgb[lo * bpp + 0], rowRgb[hi * bpp + 0] );
452 std::swap( rowRgb[lo * bpp + 1], rowRgb[hi * bpp + 1] );
453 std::swap( rowRgb[lo * bpp + 2], rowRgb[hi * bpp + 2] );
454 }
455
456 if( alpha )
457 {
458 unsigned char* rowAlpha = alpha + y * w;
459
460 for( int lo = 0, hi = w - 1; lo < hi; ++lo, --hi )
461 std::swap( rowAlpha[lo], rowAlpha[hi] );
462 }
463 }
464 }
465 else
466 {
467 // Swap entire rows top-to-bottom
468 const size_t rowBytes = w * bpp;
469 std::vector<unsigned char> tmpRgb( rowBytes );
470
471 for( int lo = 0, hi = h - 1; lo < hi; ++lo, --hi )
472 {
473 unsigned char* rowLo = rgb + lo * rowBytes;
474 unsigned char* rowHi = rgb + hi * rowBytes;
475 memcpy( tmpRgb.data(), rowLo, rowBytes );
476 memcpy( rowLo, rowHi, rowBytes );
477 memcpy( rowHi, tmpRgb.data(), rowBytes );
478 }
479
480 if( alpha )
481 {
482 std::vector<unsigned char> tmpAlpha( w );
483
484 for( int lo = 0, hi = h - 1; lo < hi; ++lo, --hi )
485 {
486 unsigned char* aLo = alpha + lo * w;
487 unsigned char* aHi = alpha + hi * w;
488 memcpy( tmpAlpha.data(), aLo, w );
489 memcpy( aLo, aHi, w );
490 memcpy( aHi, tmpAlpha.data(), w );
491 }
492 }
493 }
494}
495
496
498{
499 if( m_image )
500 {
501 mirrorImageInPlace( *m_image, aFlipDirection );
502
503 if( aFlipDirection == FLIP_DIRECTION::TOP_BOTTOM )
505 else
507
508 m_bitmapDirty = true;
509 m_imageData.Clear();
510 }
511}
512
513
514void BITMAP_BASE::Rotate( bool aRotateCCW )
515{
516 if( m_image )
517 {
518 // wxImage::Rotate90() clears resolution metadata, so preserve it
519 int resX = m_image->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONX );
520 int resY = m_image->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONY );
521 int unit = m_image->GetOptionInt( wxIMAGE_OPTION_RESOLUTIONUNIT );
522
523 // wxImage::Rotate90 parameter is "clockwise", so invert for CCW rotation
524 *m_image = m_image->Rotate90( !aRotateCCW );
525
526 m_image->SetOption( wxIMAGE_OPTION_RESOLUTIONUNIT, unit );
527 m_image->SetOption( wxIMAGE_OPTION_RESOLUTIONX, resX );
528 m_image->SetOption( wxIMAGE_OPTION_RESOLUTIONY, resY );
529
530 m_rotation += ( aRotateCCW ? ANGLE_90 : -ANGLE_90 );
531 m_bitmapDirty = true;
532 m_imageData.Clear();
533 }
534}
535
536
538{
539 if( m_image )
540 {
541 *m_image = m_image->ConvertToGreyscale();
542 *m_originalImage = m_originalImage->ConvertToGreyscale();
543 m_bitmapDirty = true;
544 m_imageData.Clear();
545 m_imageId = KIID();
546 }
547}
548
549
550void BITMAP_BASE::PlotImage( PLOTTER* aPlotter, const VECTOR2I& aPos,
551 const COLOR4D& aDefaultColor,
552 int aDefaultPensize ) const
553{
554 if( m_image == nullptr )
555 return;
556
557 // These 2 lines are useful only for plotters that cannot plot a bitmap
558 // and plot a rectangle instead of.
559 aPlotter->SetColor( aDefaultColor );
560 aPlotter->SetCurrentLineWidth( aDefaultPensize );
561 aPlotter->PlotImage( *m_image, aPos, GetScalingFactor() );
562}
563
564
566{
567 if( m_image )
568 {
569 wxMemoryOutputStream stream;
570 wxBitmapType type = m_imageType == wxBITMAP_TYPE_JPEG ? wxBITMAP_TYPE_JPEG
571 : wxBITMAP_TYPE_PNG;
572
573 if( !m_image->SaveFile( stream, type ) )
574 return;
575
576 m_imageData.GetWriteBuf( stream.GetLength() );
577 stream.CopyTo( m_imageData.GetData(), stream.GetLength() );
578 m_imageData.SetDataLen( stream.GetLength() );
579 }
580}
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
void ensureBitmapUpToDate() const
Ensure the cached wxBitmap is up-to-date with the current wxImage.
wxMemoryBuffer m_imageData
Cached encoded image data (PNG/JPEG).
void Rotate(bool aRotateCCW)
Rotate image CW or CCW.
bool LoadLegacyData(LINE_READER &aLine, wxString &aErrorMsg)
Load an image data saved by #SaveData.
void updateImageDataBuffer()
Resets the image data buffer using the current image data.
double GetScalingFactor() const
This scaling factor depends on m_pixelSizeIu and m_scale.
Definition bitmap_base.h:94
void PlotImage(PLOTTER *aPlotter, const VECTOR2I &aPos, const KIGFX::COLOR4D &aDefaultColor, int aDefaultPensize) const
Plot bitmap on plotter.
void Mirror(FLIP_DIRECTION aFlipDirection)
Mirror image vertically (i.e.
const BOX2I GetBoundingBox() const
Return the orthogonal, bounding box of this object for display purposes.
bool m_isMirroredY
bool m_isMirroredX
void ImportData(BITMAP_BASE &aItem)
Copy aItem image to this object and update m_bitmap.
bool m_bitmapDirty
True when m_bitmap needs rebuilding from m_image.
bool SaveImageData(wxOutputStream &aOutStream) const
Write the bitmap data to aOutStream.
wxImage * m_originalImage
Raw image data, not transformed by rotate/mirror.
void rebuildBitmap(bool aResetID=true)
Rebuild the internal bitmap used to draw/plot image.
static void mirrorImageInPlace(wxImage &aImage, FLIP_DIRECTION aFlipDirection)
Mirror the wxImage pixel data in-place without allocating a new image.
wxBitmapType m_imageType
The image type (png, jpeg, etc.).
wxBitmap * m_bitmap
The bitmap used to draw/plot image.
void ConvertToGreyscale()
bool SetImage(const wxImage &aImage)
Set the image from an existing wxImage.
bool ReadImageFile(const wxString &aFullFilename)
Reads and stores in memory an image file.
wxImage * m_image
The raw, uncompressed image data.
double m_scale
The scaling factor of the bitmap with m_pixelSizeIu, controls the actual draw size.
double m_pixelSizeIu
The scaling factor of the bitmap to convert the bitmap size (in pixels) to internal KiCad units.
int m_ppi
The bitmap definition. The default is 300PPI.
void DrawBitmap(wxDC *aDC, const VECTOR2I &aPos, const KIGFX::COLOR4D &aBackgroundColor=KIGFX::COLOR4D::UNSPECIFIED) const
BITMAP_BASE(const VECTOR2I &pos=VECTOR2I(0, 0))
VECTOR2I GetSize() const
EDA_ANGLE m_rotation
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:558
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:402
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
wxColour ToColour() const
Definition color4d.cpp:225
Definition kiid.h:49
An abstract class from which implementation specific LINE_READERs may be derived to read single lines...
Definition richio.h:66
virtual char * ReadLine()=0
Read a line of text into the buffer and increments the line number counter.
char * Line() const
Return a pointer to the last line that was read in.
Definition richio.h:102
Base plotter engine class.
Definition plotter.h:136
virtual void PlotImage(const wxImage &aImage, const VECTOR2I &aPos, double aScaleFactor)
Only PostScript plotters can plot bitmaps.
Definition plotter.cpp:260
virtual void SetCurrentLineWidth(int width, void *aData=nullptr)=0
Set the line width for the next drawing.
virtual void SetColor(const COLOR4D &color)=0
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
bool GetGRForceBlackPenState(void)
Definition gr_basic.cpp:165
FLIP_DIRECTION
Definition mirror.h:27
@ LEFT_RIGHT
Flip left to right (around the Y axis)
Definition mirror.h:28
@ TOP_BOTTOM
Flip top to bottom (around the X axis)
Definition mirror.h:29
const int scale
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695