KiCad PCB EDA Suite
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 (C) 2015-2016 Mario Luzeiro <[email protected]>
5  * Copyright (C) 2015-2020 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 
30 #include "image.h"
31 #include "buffers_debug.h"
32 #include <cstring> // For memcpy
33 
34 #include <algorithm>
35 #include <atomic>
36 #include <thread>
37 #include <chrono>
38 
39 
40 #ifndef CLAMP
41 #define CLAMP( n, min, max ) {if( n < min ) n=min; else if( n > max ) n = max;}
42 #endif
43 
44 
45 IMAGE::IMAGE( unsigned int aXsize, unsigned int aYsize )
46 {
47  m_wxh = aXsize * aYsize;
48  m_pixels = new unsigned char[m_wxh];
49  memset( m_pixels, 0, m_wxh );
50  m_width = aXsize;
51  m_height = aYsize;
53 }
54 
55 
56 IMAGE::IMAGE( const IMAGE& aSrcImage )
57 {
58  m_wxh = aSrcImage.GetWidth() * aSrcImage.GetHeight();
59  m_pixels = new unsigned char[m_wxh];
60  memcpy( m_pixels, aSrcImage.GetBuffer(), m_wxh );
61  m_width = aSrcImage.GetWidth();
62  m_height = aSrcImage.GetHeight();
64 }
65 
66 
68 {
69  delete[] m_pixels;
70 }
71 
72 
73 unsigned char* IMAGE::GetBuffer() const
74 {
75  return m_pixels;
76 }
77 
78 
79 bool IMAGE::wrapCoords( int* aXo, int* aYo ) const
80 {
81  int x = *aXo;
82  int y = *aYo;
83 
84  switch( m_wraping )
85  {
86  case IMAGE_WRAP::CLAMP:
87  x = ( x < 0 ) ? 0 : x;
88  x = ( x >= (int) ( m_width - 1 ) ) ? ( m_width - 1 ) : x;
89  y = ( y < 0 ) ? 0 : y;
90  y = ( y >= (int) ( m_height - 1 ) ) ? ( m_height - 1 ) : y;
91  break;
92 
93  case IMAGE_WRAP::WRAP:
94  x = ( x < 0 ) ? ( ( m_width - 1 ) + x ) : x;
95  x = ( x >= (int) ( m_width - 1 ) ) ? ( x - m_width ) : x;
96  y = ( y < 0 ) ? ( ( m_height - 1 ) + y ) : y;
97  y = ( y >= (int) ( m_height - 1 ) ) ? ( y - m_height ) : y;
98  break;
99 
100  default:
101  break;
102  }
103 
104  if( ( x < 0 ) || ( x >= (int) m_width ) || ( y < 0 ) || ( y >= (int) m_height ) )
105  return false;
106 
107  *aXo = x;
108  *aYo = y;
109 
110  return true;
111 }
112 
113 
114 void IMAGE::plot8CircleLines( int aCx, int aCy, int aX, int aY, unsigned char aValue )
115 {
116  Hline( aCx - aX, aCx + aX, aCy + aY, aValue );
117  Hline( aCx - aX, aCx + aX, aCy - aY, aValue );
118  Hline( aCx - aY, aCx + aY, aCy + aX, aValue );
119  Hline( aCx - aY, aCx + aY, aCy - aX, aValue );
120 }
121 
122 
123 void IMAGE::Setpixel( int aX, int aY, unsigned char aValue )
124 {
125  if( wrapCoords( &aX, &aY ) )
126  m_pixels[aX + aY * m_width] = aValue;
127 }
128 
129 
130 unsigned char IMAGE::Getpixel( int aX, int aY ) const
131 {
132  if( wrapCoords( &aX, &aY ) )
133  return m_pixels[aX + aY * m_width];
134  else
135  return 0;
136 }
137 
138 
139 void IMAGE::Hline( int aXStart, int aXEnd, int aY, unsigned char aValue )
140 {
141  if( ( aY < 0 ) || ( aY >= (int) m_height ) || ( ( aXStart < 0 ) && ( aXEnd < 0 ) )
142  || ( ( aXStart >= (int) m_width ) && ( aXEnd >= (int) m_width ) ) )
143  return;
144 
145  if( aXStart > aXEnd )
146  {
147  int swap = aXStart;
148 
149  aXStart = aXEnd;
150  aXEnd = swap;
151  }
152 
153  // Clamp line
154  if( aXStart < 0 )
155  aXStart = 0;
156 
157  if( aXEnd >= (int)m_width )
158  aXEnd = m_width - 1;
159 
160  unsigned char* pixelPtr = &m_pixels[aXStart + aY * m_width];
161  unsigned char* pixelPtrEnd = pixelPtr + (unsigned int) ( ( aXEnd - aXStart ) + 1 );
162 
163  while( pixelPtr < pixelPtrEnd )
164  {
165  *pixelPtr = aValue;
166  pixelPtr++;
167  }
168 }
169 
170 
171 // Based on paper
172 // http://web.engr.oregonstate.edu/~sllu/bcircle.pdf
173 void IMAGE::CircleFilled( int aCx, int aCy, int aRadius, unsigned char aValue )
174 {
175  int x = aRadius;
176  int y = 0;
177  int xChange = 1 - 2 * aRadius;
178  int yChange = 0;
179  int radiusError = 0;
180 
181  while( x >= y )
182  {
183  plot8CircleLines( aCx, aCy, x, y, aValue );
184  y++;
185  radiusError += yChange;
186  yChange += 2;
187 
188  if( ( 2 * radiusError + xChange ) > 0 )
189  {
190  x--;
191  radiusError += xChange;
192  xChange += 2;
193  }
194  }
195 }
196 
197 
199 {
200  for( unsigned int it = 0; it < m_wxh; it++ )
201  m_pixels[it] = 255 - m_pixels[it];
202 }
203 
204 
205 void IMAGE::CopyFull( const IMAGE* aImgA, const IMAGE* aImgB, IMAGE_OP aOperation )
206 {
207  int aV, bV;
208 
209  if( aOperation == IMAGE_OP::RAW )
210  {
211  if( aImgA == nullptr )
212  return;
213  }
214  else
215  {
216  if( ( aImgA == nullptr ) || ( aImgB == nullptr ) )
217  return;
218  }
219 
220  switch( aOperation )
221  {
222  case IMAGE_OP::RAW:
223  memcpy( m_pixels, aImgA->m_pixels, m_wxh );
224  break;
225 
226  case IMAGE_OP::ADD:
227  for( unsigned int it = 0;it < m_wxh; it++ )
228  {
229  aV = aImgA->m_pixels[it];
230  bV = aImgB->m_pixels[it];
231 
232  aV = (aV + bV);
233  aV = (aV > 255)?255:aV;
234 
235  m_pixels[it] = aV;
236  }
237  break;
238 
239  case IMAGE_OP::SUB:
240  for( unsigned int it = 0;it < m_wxh; it++ )
241  {
242  aV = aImgA->m_pixels[it];
243  bV = aImgB->m_pixels[it];
244 
245  aV = (aV - bV);
246  aV = (aV < 0)?0:aV;
247 
248  m_pixels[it] = aV;
249  }
250  break;
251 
252  case IMAGE_OP::DIF:
253  for( unsigned int it = 0;it < m_wxh; it++ )
254  {
255  aV = aImgA->m_pixels[it];
256  bV = aImgB->m_pixels[it];
257 
258  m_pixels[it] = abs( aV - bV );
259  }
260  break;
261 
262  case IMAGE_OP::MUL:
263  for( unsigned int it = 0;it < m_wxh; it++ )
264  {
265  aV = aImgA->m_pixels[it];
266  bV = aImgB->m_pixels[it];
267 
268  m_pixels[it] =
269  (unsigned char) ( ( ( (float) aV / 255.0f ) * ( (float) bV / 255.0f ) ) * 255 );
270  }
271  break;
272 
273  case IMAGE_OP::AND:
274  for( unsigned int it = 0;it < m_wxh; it++ )
275  {
276  m_pixels[it] = aImgA->m_pixels[it] & aImgB->m_pixels[it];
277  }
278  break;
279 
280  case IMAGE_OP::OR:
281  for( unsigned int it = 0;it < m_wxh; it++ )
282  {
283  m_pixels[it] = aImgA->m_pixels[it] | aImgB->m_pixels[it];
284  }
285  break;
286 
287  case IMAGE_OP::XOR:
288  for( unsigned int it = 0;it < m_wxh; it++ )
289  {
290  m_pixels[it] = aImgA->m_pixels[it] ^ aImgB->m_pixels[it];
291  }
292  break;
293 
294  case IMAGE_OP::BLEND50:
295  for( unsigned int it = 0;it < m_wxh; it++ )
296  {
297  aV = aImgA->m_pixels[it];
298  bV = aImgB->m_pixels[it];
299 
300  m_pixels[it] = (aV + bV) / 2;
301  }
302  break;
303 
304  case IMAGE_OP::MIN:
305  for( unsigned int it = 0;it < m_wxh; it++ )
306  {
307  aV = aImgA->m_pixels[it];
308  bV = aImgB->m_pixels[it];
309 
310  m_pixels[it] = (aV < bV)?aV:bV;
311  }
312  break;
313 
314  case IMAGE_OP::MAX:
315  for( unsigned int it = 0;it < m_wxh; it++ )
316  {
317  aV = aImgA->m_pixels[it];
318  bV = aImgB->m_pixels[it];
319 
320  m_pixels[it] = (aV > bV)?aV:bV;
321  }
322  break;
323 
324  default:
325  break;
326  }
327 }
328 
329 
330 // TIP: If you want create or test filters you can use GIMP
331 // with a generic convolution matrix and get the values from there.
332 // http://docs.gimp.org/nl/plug-in-convmatrix.html
333 // clang-format off
334 static const S_FILTER FILTERS[] = {
335  // IMAGE_FILTER::HIPASS
336  {
337  { { 0, -1, -1, -1, 0},
338  {-1, 2, -4, 2, -1},
339  {-1, -4, 13, -4, -1},
340  {-1, 2, -4, 2, -1},
341  { 0, -1, -1, -1, 0}
342  },
343  7,
344  255
345  },
346 
347  // IMAGE_FILTER::GAUSSIAN_BLUR
348  {
349  { { 3, 5, 7, 5, 3},
350  { 5, 9, 12, 9, 5},
351  { 7, 12, 20, 12, 7},
352  { 5, 9, 12, 9, 5},
353  { 3, 5, 7, 5, 3}
354  },
355  182,
356  0
357  },
358 
359  // IMAGE_FILTER::GAUSSIAN_BLUR2
360  {
361  { { 1, 4, 7, 4, 1},
362  { 4, 16, 26, 16, 4},
363  { 7, 26, 41, 26, 7},
364  { 4, 16, 26, 16, 4},
365  { 1, 4, 7, 4, 1}
366  },
367  273,
368  0
369  },
370 
371  // IMAGE_FILTER::INVERT_BLUR
372  {
373  { { 0, 0, 0, 0, 0},
374  { 0, 0, -1, 0, 0},
375  { 0, -1, 0, -1, 0},
376  { 0, 0, -1, 0, 0},
377  { 0, 0, 0, 0, 0}
378  },
379  4,
380  255
381  },
382 
383  // IMAGE_FILTER::CARTOON
384  {
385  { {-1, -1, -1, -1, 0},
386  {-1, 0, 0, 0, 0},
387  {-1, 0, 4, 0, 0},
388  { 0, 0, 0, 1, 0},
389  { 0, 0, 0, 0, 4}
390  },
391  3,
392  0
393  },
394 
395  // IMAGE_FILTER::EMBOSS
396  {
397  { {-1, -1, -1, -1, 0},
398  {-1, -1, -1, 0, 1},
399  {-1, -1, 0, 1, 1},
400  {-1, 0, 1, 1, 1},
401  { 0, 1, 1, 1, 1}
402  },
403  1,
404  128
405  },
406 
407  // IMAGE_FILTER::SHARPEN
408  {
409  { {-1, -1, -1, -1, -1},
410  {-1, 2, 2, 2, -1},
411  {-1, 2, 8, 2, -1},
412  {-1, 2, 2, 2, -1},
413  {-1, -1, -1, -1, -1}
414  },
415  8,
416  0
417  },
418 
419  // IMAGE_FILTER::MELT
420  {
421  { { 4, 2, 6, 8, 1},
422  { 1, 2, 5, 4, 2},
423  { 0, -1, 1, -1, 0},
424  { 0, 0, -2, 0, 0},
425  { 0, 0, 0, 0, 0}
426  },
427  32,
428  0
429  },
430 
431  // IMAGE_FILTER::SOBEL_GX
432  {
433  { { 0, 0, 0, 0, 0},
434  { 0, -1, 0, 1, 0},
435  { 0, -2, 0, 2, 0},
436  { 0, -1, 0, 1, 0},
437  { 0, 0, 0, 0, 0}
438  },
439  1,
440  0
441  },
442 
443  // IMAGE_FILTER::SOBEL_GY
444  {
445  { { 1, 2, 4, 2, 1},
446  {-1, -1, 0, 1, 1},
447  {-2, -2, 0, 2, 2},
448  {-1, -1, 0, 1, 1},
449  {-1, -2, -4, -2, -1},
450  },
451  1,
452  0
453  },
454 
455  // IMAGE_FILTER::BLUR_3X3
456  {
457  { { 0, 0, 0, 0, 0},
458  { 0, 1, 2, 1, 0},
459  { 0, 2, 4, 2, 0},
460  { 0, 1, 2, 1, 0},
461  { 0, 0, 0, 0, 0},
462  },
463  16,
464  0
465  }
466 };// Filters
467 // clang-format on
468 
469 
474 void IMAGE::EfxFilter( IMAGE* aInImg, IMAGE_FILTER aFilterType )
475 {
476  S_FILTER filter = FILTERS[static_cast<int>( aFilterType )];
477 
478  aInImg->m_wraping = IMAGE_WRAP::CLAMP;
480 
481  std::atomic<size_t> nextRow( 0 );
482  std::atomic<size_t> threadsFinished( 0 );
483 
484  size_t parallelThreadCount = std::max<size_t>( std::thread::hardware_concurrency(), 2 );
485 
486  for( size_t ii = 0; ii < parallelThreadCount; ++ii )
487  {
488  std::thread t = std::thread( [&]()
489  {
490  for( size_t iy = nextRow.fetch_add( 1 ); iy < m_height; iy = nextRow.fetch_add( 1 ) )
491  {
492  for( size_t ix = 0; ix < m_width; ix++ )
493  {
494  int v = 0;
495 
496  for( size_t sy = 0; sy < 5; sy++ )
497  {
498  for( size_t sx = 0; sx < 5; sx++ )
499  {
500  int factor = filter.kernel[sx][sy];
501  unsigned char pixelv = aInImg->Getpixel( ix + sx - 2, iy + sy - 2 );
502 
503  v += pixelv * factor;
504  }
505  }
506 
507  v /= filter.div;
508  v += filter.offset;
509  CLAMP(v, 0, 255);
510 
512  m_pixels[ix + iy * m_width] = v;
513  }
514  }
515 
516  threadsFinished++;
517  } );
518 
519  t.detach();
520  }
521 
522  while( threadsFinished < parallelThreadCount )
523  std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
524 }
525 
526 
527 void IMAGE::EfxFilter_SkipCenter( IMAGE* aInImg, IMAGE_FILTER aFilterType, unsigned int aRadius )
528 {
529  if( ( !aInImg ) || ( m_width != aInImg->m_width ) || ( m_height != aInImg->m_height ) )
530  return;
531 
532  S_FILTER filter = FILTERS[static_cast<int>( aFilterType )];
533 
534  aInImg->m_wraping = IMAGE_WRAP::ZERO;
535 
536  const unsigned int radiusSquared = aRadius * aRadius;
537 
538  const unsigned int xCenter = m_width / 2;
539  const unsigned int yCenter = m_height / 2;
540 
541  for( size_t iy = 0; iy < m_height; iy++ )
542  {
543  int yc = iy - yCenter;
544 
545  unsigned int ycsq = yc * yc;
546 
547  for( size_t ix = 0; ix < m_width; ix++ )
548  {
549  int xc = ix - xCenter;
550 
551  unsigned int xcsq = xc * xc;
552 
553  if( ( xcsq + ycsq ) < radiusSquared )
554  {
555  const unsigned int offset = ix + iy * m_width;
556 
557  m_pixels[offset] = aInImg->m_pixels[offset];
558 
559  continue;
560  }
561 
562  int v = 0;
563 
564  for( size_t sy = 0; sy < 5; sy++ )
565  {
566  for( size_t sx = 0; sx < 5; sx++ )
567  {
568  int factor = filter.kernel[sx][sy];
569  unsigned char pixelv = aInImg->Getpixel( ix + sx - 2, iy + sy - 2 );
570 
571  v += pixelv * factor;
572  }
573  }
574 
575  v /= filter.div;
576  v += filter.offset;
577  CLAMP(v, 0, 255);
578 
579  m_pixels[ix + iy * m_width] = v;
580  }
581  }
582 }
583 
584 
585 void IMAGE::SetPixelsFromNormalizedFloat( const float* aNormalizedFloatArray )
586 {
587  for( unsigned int i = 0; i < m_wxh; i++ )
588  {
589  int v = aNormalizedFloatArray[i] * 255;
590 
591  CLAMP( v, 0, 255 );
592  m_pixels[i] = v;
593  }
594 }
595 
596 
597 void IMAGE::SaveAsPNG( const wxString& aFileName ) const
598 {
599  DBG_SaveBuffer( aFileName, m_pixels, m_width, m_height );
600 }
unsigned char * GetBuffer() const
Get the image buffer pointer.
Definition: image.cpp:73
Coords are wrapped around.
unsigned int m_width
width of the image
Definition: image.h:229
bool wrapCoords(int *aXo, int *aYo) const
Calculate the coordinates points in accord with the current clamping settings.
Definition: image.cpp:79
IMAGE_OP
Image operation type.
Definition: image.h:36
IMAGE_WRAP m_wraping
current wrapping type
Definition: image.h:232
void CopyFull(const IMAGE *aImgA, const IMAGE *aImgB, IMAGE_OP aOperation)
Perform a copy operation based on aOperation type.
Definition: image.cpp:205
IMAGE_FILTER
Filter type enumeration.
Definition: image.h:62
void Hline(int aXStart, int aXEnd, int aY, unsigned char aValue)
Draw a horizontal line.
Definition: image.cpp:139
~IMAGE()
Definition: image.cpp:67
void SaveAsPNG(const wxString &aFileName) const
Save image buffer to a PNG file into the working folder.
Definition: image.cpp:597
unsigned char Getpixel(int aX, int aY) const
Get the pixel value from pixel position, position is clamped in accord with the current clamp setting...
Definition: image.cpp:130
void DBG_SaveBuffer(const wxString &aFileName, const unsigned char *aInBuffer, unsigned int aXSize, unsigned int aYSize)
void EfxFilter_SkipCenter(IMAGE *aInImg, IMAGE_FILTER aFilterType, unsigned int aRadius)
Apply a filter to the input image and store it in the image class.
Definition: image.cpp:527
one 8bit-channel image definition.
void CircleFilled(int aCx, int aCy, int aRadius, unsigned char aValue)
Definition: image.cpp:173
void Setpixel(int aX, int aY, unsigned char aValue)
Set a value in a pixel position, position is clamped in accordance with the current clamp settings.
Definition: image.cpp:123
unsigned char * m_pixels
buffer to store the image 8bit-channel
Definition: image.h:228
unsigned int m_wxh
width * height precalc value
Definition: image.h:231
Coords that wraps are not evaluated.
Coords are clamped to image size.
IMAGE(unsigned int aXsize, unsigned int aYsize)
Construct a IMAGE based on image size.
Definition: image.cpp:45
void SetPixelsFromNormalizedFloat(const float *aNormalizedFloatArray)
Set the current channel from a float normalized (0.0 - 1.0) buffer.
Definition: image.cpp:585
static const S_FILTER FILTERS[]
Definition: image.cpp:334
5x5 Filter struct parameters
Definition: image.h:78
void Invert()
Invert the values of this image <- (255 - this)
Definition: image.cpp:198
unsigned int GetHeight() const
Definition: image.h:214
Manage an 8-bit channel image.
Definition: image.h:89
void EfxFilter(IMAGE *aInImg, IMAGE_FILTER aFilterType)
Apply a filter to the input image and store it in the image class.
Definition: image.cpp:474
unsigned int GetWidth() const
Definition: image.h:213
unsigned int m_height
height of the image
Definition: image.h:230
void plot8CircleLines(int aCx, int aCy, int aX, int aY, unsigned char aValue)
Definition: image.cpp:114