KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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
45IMAGE::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;
52 m_wraping = IMAGE_WRAP::CLAMP;
53}
54
55
56IMAGE::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();
63 m_wraping = IMAGE_WRAP::CLAMP;
64}
65
66
68{
69 delete[] m_pixels;
70}
71
72
73unsigned char* IMAGE::GetBuffer() const
74{
75 return m_pixels;
76}
77
78
79bool 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
114void 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
123void 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
130unsigned 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
139void 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
173void 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
205void 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
334static 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
474void 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;
479 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
527void 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
585void 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
597void IMAGE::SaveAsPNG( const wxString& aFileName ) const
598{
599 DBG_SaveBuffer( aFileName, m_pixels, m_width, m_height );
600}
void DBG_SaveBuffer(const wxString &aFileName, const unsigned char *aInBuffer, unsigned int aXSize, unsigned int aYSize)
Manage an 8-bit channel image.
Definition: image.h:90
IMAGE_WRAP m_wraping
current wrapping type
Definition: image.h:232
void CircleFilled(int aCx, int aCy, int aRadius, unsigned char aValue)
Definition: image.cpp:173
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
void CopyFull(const IMAGE *aImgA, const IMAGE *aImgB, IMAGE_OP aOperation)
Perform a copy operation based on aOperation type.
Definition: image.cpp:205
unsigned int m_width
width of the image
Definition: image.h:229
~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
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
unsigned char * GetBuffer() const
Get the image buffer pointer.
Definition: image.cpp:73
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 int m_height
height of the image
Definition: image.h:230
unsigned char * m_pixels
buffer to store the image 8bit-channel
Definition: image.h:228
void Invert()
Invert the values of this image <- (255 - this)
Definition: image.cpp:198
void SetPixelsFromNormalizedFloat(const float *aNormalizedFloatArray)
Set the current channel from a float normalized (0.0 - 1.0) buffer.
Definition: image.cpp:585
IMAGE(unsigned int aXsize, unsigned int aYsize)
Construct a IMAGE based on image size.
Definition: image.cpp:45
unsigned int GetHeight() const
Definition: image.h:214
void plot8CircleLines(int aCx, int aCy, int aX, int aY, unsigned char aValue)
Definition: image.cpp:114
void Hline(int aXStart, int aXEnd, int aY, unsigned char aValue)
Draw a horizontal line.
Definition: image.cpp:139
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
bool wrapCoords(int *aXo, int *aYo) const
Calculate the coordinates points in accord with the current clamping settings.
Definition: image.cpp:79
unsigned int GetWidth() const
Definition: image.h:213
unsigned int m_wxh
width * height precalc value
Definition: image.h:231
static const S_FILTER FILTERS[]
Definition: image.cpp:334
one 8bit-channel image definition.
@ CLAMP
Coords are clamped to image size.
IMAGE_FILTER
Filter type enumeration.
Definition: image.h:63
IMAGE_OP
Image operation type.
Definition: image.h:37
5x5 Filter struct parameters
Definition: image.h:79