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