1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2015-2016 Mario Luzeiro <mrluzeiro@ua.pt>
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 
25  /**
26  * @file image.cpp
27  * @brief one 8bit-channel image implementation.
28  */
29 
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 
IMAGE(unsigned int aXsize,unsigned int aYsize)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;
52     m_wraping = IMAGE_WRAP::CLAMP;
53 }
54 
55 
IMAGE(const IMAGE & aSrcImage)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();
63     m_wraping = IMAGE_WRAP::CLAMP;
64 }
65 
66 
~IMAGE()67 IMAGE::~IMAGE()
68 {
69     delete[] m_pixels;
70 }
71 
72 
GetBuffer() const73 unsigned char* IMAGE::GetBuffer() const
74 {
75     return m_pixels;
76 }
77 
78 
wrapCoords(int * aXo,int * aYo) const79 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 
plot8CircleLines(int aCx,int aCy,int aX,int aY,unsigned char aValue)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 
Setpixel(int aX,int aY,unsigned char aValue)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 
Getpixel(int aX,int aY) const130 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 
Hline(int aXStart,int aXEnd,int aY,unsigned char aValue)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
CircleFilled(int aCx,int aCy,int aRadius,unsigned char aValue)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 
Invert()198 void IMAGE::Invert()
199 {
200     for( unsigned int it = 0; it < m_wxh; it++ )
201         m_pixels[it] = 255 - m_pixels[it];
202 }
203 
204 
CopyFull(const IMAGE * aImgA,const IMAGE * aImgB,IMAGE_OP aOperation)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 
470 /// @todo: This function can be optimized slipping it between the edges and
471 ///        do it without use the getpixel function.
472 ///        Optimization can be done to m_pixels[ix + iy * m_width]
473 ///        but keep in mind the parallel process of the algorithm
EfxFilter(IMAGE * aInImg,IMAGE_FILTER aFilterType)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;
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 
511                     /// @todo This needs to write to a separate buffer.
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 
EfxFilter_SkipCenter(IMAGE * aInImg,IMAGE_FILTER aFilterType,unsigned int aRadius)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 
SetPixelsFromNormalizedFloat(const float * aNormalizedFloatArray)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 
SaveAsPNG(const wxString & aFileName) const597 void IMAGE::SaveAsPNG( const wxString& aFileName ) const
598 {
599     DBG_SaveBuffer( aFileName, m_pixels, m_width, m_height );
600 }
601