You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

600 lines
14 KiB

  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 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. * @file image.cpp
  26. * @brief one 8bit-channel image implementation.
  27. */
  28. #include "image.h"
  29. #include "buffers_debug.h"
  30. #include <cstring> // For memcpy
  31. #include <algorithm>
  32. #include <atomic>
  33. #include <thread>
  34. #include <chrono>
  35. #ifndef CLAMP
  36. #define CLAMP( n, min, max ) {if( n < min ) n=min; else if( n > max ) n = max;}
  37. #endif
  38. IMAGE::IMAGE( unsigned int aXsize, unsigned int aYsize )
  39. {
  40. m_wxh = aXsize * aYsize;
  41. m_pixels = new unsigned char[m_wxh];
  42. memset( m_pixels, 0, m_wxh );
  43. m_width = aXsize;
  44. m_height = aYsize;
  45. m_wraping = IMAGE_WRAP::CLAMP;
  46. }
  47. IMAGE::IMAGE( const IMAGE& aSrcImage )
  48. {
  49. m_wxh = aSrcImage.GetWidth() * aSrcImage.GetHeight();
  50. m_pixels = new unsigned char[m_wxh];
  51. memcpy( m_pixels, aSrcImage.GetBuffer(), m_wxh );
  52. m_width = aSrcImage.GetWidth();
  53. m_height = aSrcImage.GetHeight();
  54. m_wraping = IMAGE_WRAP::CLAMP;
  55. }
  56. IMAGE::~IMAGE()
  57. {
  58. delete[] m_pixels;
  59. }
  60. unsigned char* IMAGE::GetBuffer() const
  61. {
  62. return m_pixels;
  63. }
  64. bool IMAGE::wrapCoords( int* aXo, int* aYo ) const
  65. {
  66. int x = *aXo;
  67. int y = *aYo;
  68. switch( m_wraping )
  69. {
  70. case IMAGE_WRAP::CLAMP:
  71. x = ( x < 0 ) ? 0 : x;
  72. x = ( x >= (int) ( m_width - 1 ) ) ? ( m_width - 1 ) : x;
  73. y = ( y < 0 ) ? 0 : y;
  74. y = ( y >= (int) ( m_height - 1 ) ) ? ( m_height - 1 ) : y;
  75. break;
  76. case IMAGE_WRAP::WRAP:
  77. x = ( x < 0 ) ? ( ( m_width - 1 ) + x ) : x;
  78. x = ( x >= (int) ( m_width - 1 ) ) ? ( x - m_width ) : x;
  79. y = ( y < 0 ) ? ( ( m_height - 1 ) + y ) : y;
  80. y = ( y >= (int) ( m_height - 1 ) ) ? ( y - m_height ) : y;
  81. break;
  82. default:
  83. break;
  84. }
  85. if( ( x < 0 ) || ( x >= (int) m_width ) || ( y < 0 ) || ( y >= (int) m_height ) )
  86. return false;
  87. *aXo = x;
  88. *aYo = y;
  89. return true;
  90. }
  91. void IMAGE::plot8CircleLines( int aCx, int aCy, int aX, int aY, unsigned char aValue )
  92. {
  93. Hline( aCx - aX, aCx + aX, aCy + aY, aValue );
  94. Hline( aCx - aX, aCx + aX, aCy - aY, aValue );
  95. Hline( aCx - aY, aCx + aY, aCy + aX, aValue );
  96. Hline( aCx - aY, aCx + aY, aCy - aX, aValue );
  97. }
  98. void IMAGE::Setpixel( int aX, int aY, unsigned char aValue )
  99. {
  100. if( wrapCoords( &aX, &aY ) )
  101. m_pixels[aX + aY * m_width] = aValue;
  102. }
  103. unsigned char IMAGE::Getpixel( int aX, int aY ) const
  104. {
  105. if( wrapCoords( &aX, &aY ) )
  106. return m_pixels[aX + aY * m_width];
  107. else
  108. return 0;
  109. }
  110. void IMAGE::Hline( int aXStart, int aXEnd, int aY, unsigned char aValue )
  111. {
  112. if( ( aY < 0 ) || ( aY >= (int) m_height ) || ( ( aXStart < 0 ) && ( aXEnd < 0 ) )
  113. || ( ( aXStart >= (int) m_width ) && ( aXEnd >= (int) m_width ) ) )
  114. return;
  115. if( aXStart > aXEnd )
  116. {
  117. int swap = aXStart;
  118. aXStart = aXEnd;
  119. aXEnd = swap;
  120. }
  121. // Clamp line
  122. if( aXStart < 0 )
  123. aXStart = 0;
  124. if( aXEnd >= (int)m_width )
  125. aXEnd = m_width - 1;
  126. unsigned char* pixelPtr = &m_pixels[aXStart + aY * m_width];
  127. unsigned char* pixelPtrEnd = pixelPtr + (unsigned int) ( ( aXEnd - aXStart ) + 1 );
  128. while( pixelPtr < pixelPtrEnd )
  129. {
  130. *pixelPtr = aValue;
  131. pixelPtr++;
  132. }
  133. }
  134. // Based on paper
  135. // http://web.engr.oregonstate.edu/~sllu/bcircle.pdf
  136. void IMAGE::CircleFilled( int aCx, int aCy, int aRadius, unsigned char aValue )
  137. {
  138. int x = aRadius;
  139. int y = 0;
  140. int xChange = 1 - 2 * aRadius;
  141. int yChange = 0;
  142. int radiusError = 0;
  143. while( x >= y )
  144. {
  145. plot8CircleLines( aCx, aCy, x, y, aValue );
  146. y++;
  147. radiusError += yChange;
  148. yChange += 2;
  149. if( ( 2 * radiusError + xChange ) > 0 )
  150. {
  151. x--;
  152. radiusError += xChange;
  153. xChange += 2;
  154. }
  155. }
  156. }
  157. void IMAGE::Invert()
  158. {
  159. for( unsigned int it = 0; it < m_wxh; it++ )
  160. m_pixels[it] = 255 - m_pixels[it];
  161. }
  162. void IMAGE::CopyFull( const IMAGE* aImgA, const IMAGE* aImgB, IMAGE_OP aOperation )
  163. {
  164. int aV, bV;
  165. if( aOperation == IMAGE_OP::RAW )
  166. {
  167. if( aImgA == nullptr )
  168. return;
  169. }
  170. else
  171. {
  172. if( ( aImgA == nullptr ) || ( aImgB == nullptr ) )
  173. return;
  174. }
  175. switch( aOperation )
  176. {
  177. case IMAGE_OP::RAW:
  178. memcpy( m_pixels, aImgA->m_pixels, m_wxh );
  179. break;
  180. case IMAGE_OP::ADD:
  181. for( unsigned int it = 0;it < m_wxh; it++ )
  182. {
  183. aV = aImgA->m_pixels[it];
  184. bV = aImgB->m_pixels[it];
  185. aV = (aV + bV);
  186. aV = (aV > 255)?255:aV;
  187. m_pixels[it] = aV;
  188. }
  189. break;
  190. case IMAGE_OP::SUB:
  191. for( unsigned int it = 0;it < m_wxh; it++ )
  192. {
  193. aV = aImgA->m_pixels[it];
  194. bV = aImgB->m_pixels[it];
  195. aV = (aV - bV);
  196. aV = (aV < 0)?0:aV;
  197. m_pixels[it] = aV;
  198. }
  199. break;
  200. case IMAGE_OP::DIF:
  201. for( unsigned int it = 0;it < m_wxh; it++ )
  202. {
  203. aV = aImgA->m_pixels[it];
  204. bV = aImgB->m_pixels[it];
  205. m_pixels[it] = abs( aV - bV );
  206. }
  207. break;
  208. case IMAGE_OP::MUL:
  209. for( unsigned int it = 0;it < m_wxh; it++ )
  210. {
  211. aV = aImgA->m_pixels[it];
  212. bV = aImgB->m_pixels[it];
  213. m_pixels[it] =
  214. (unsigned char) ( ( ( (float) aV / 255.0f ) * ( (float) bV / 255.0f ) ) * 255 );
  215. }
  216. break;
  217. case IMAGE_OP::AND:
  218. for( unsigned int it = 0;it < m_wxh; it++ )
  219. {
  220. m_pixels[it] = aImgA->m_pixels[it] & aImgB->m_pixels[it];
  221. }
  222. break;
  223. case IMAGE_OP::OR:
  224. for( unsigned int it = 0;it < m_wxh; it++ )
  225. {
  226. m_pixels[it] = aImgA->m_pixels[it] | aImgB->m_pixels[it];
  227. }
  228. break;
  229. case IMAGE_OP::XOR:
  230. for( unsigned int it = 0;it < m_wxh; it++ )
  231. {
  232. m_pixels[it] = aImgA->m_pixels[it] ^ aImgB->m_pixels[it];
  233. }
  234. break;
  235. case IMAGE_OP::BLEND50:
  236. for( unsigned int it = 0;it < m_wxh; it++ )
  237. {
  238. aV = aImgA->m_pixels[it];
  239. bV = aImgB->m_pixels[it];
  240. m_pixels[it] = (aV + bV) / 2;
  241. }
  242. break;
  243. case IMAGE_OP::MIN:
  244. for( unsigned int it = 0;it < m_wxh; it++ )
  245. {
  246. aV = aImgA->m_pixels[it];
  247. bV = aImgB->m_pixels[it];
  248. m_pixels[it] = (aV < bV)?aV:bV;
  249. }
  250. break;
  251. case IMAGE_OP::MAX:
  252. for( unsigned int it = 0;it < m_wxh; it++ )
  253. {
  254. aV = aImgA->m_pixels[it];
  255. bV = aImgB->m_pixels[it];
  256. m_pixels[it] = (aV > bV)?aV:bV;
  257. }
  258. break;
  259. default:
  260. break;
  261. }
  262. }
  263. // TIP: If you want create or test filters you can use GIMP
  264. // with a generic convolution matrix and get the values from there.
  265. // http://docs.gimp.org/nl/plug-in-convmatrix.html
  266. // clang-format off
  267. static const S_FILTER FILTERS[] = {
  268. // IMAGE_FILTER::HIPASS
  269. {
  270. { { 0, -1, -1, -1, 0},
  271. {-1, 2, -4, 2, -1},
  272. {-1, -4, 13, -4, -1},
  273. {-1, 2, -4, 2, -1},
  274. { 0, -1, -1, -1, 0}
  275. },
  276. 7,
  277. 255
  278. },
  279. // IMAGE_FILTER::GAUSSIAN_BLUR
  280. {
  281. { { 3, 5, 7, 5, 3},
  282. { 5, 9, 12, 9, 5},
  283. { 7, 12, 20, 12, 7},
  284. { 5, 9, 12, 9, 5},
  285. { 3, 5, 7, 5, 3}
  286. },
  287. 182,
  288. 0
  289. },
  290. // IMAGE_FILTER::GAUSSIAN_BLUR2
  291. {
  292. { { 1, 4, 7, 4, 1},
  293. { 4, 16, 26, 16, 4},
  294. { 7, 26, 41, 26, 7},
  295. { 4, 16, 26, 16, 4},
  296. { 1, 4, 7, 4, 1}
  297. },
  298. 273,
  299. 0
  300. },
  301. // IMAGE_FILTER::INVERT_BLUR
  302. {
  303. { { 0, 0, 0, 0, 0},
  304. { 0, 0, -1, 0, 0},
  305. { 0, -1, 0, -1, 0},
  306. { 0, 0, -1, 0, 0},
  307. { 0, 0, 0, 0, 0}
  308. },
  309. 4,
  310. 255
  311. },
  312. // IMAGE_FILTER::CARTOON
  313. {
  314. { {-1, -1, -1, -1, 0},
  315. {-1, 0, 0, 0, 0},
  316. {-1, 0, 4, 0, 0},
  317. { 0, 0, 0, 1, 0},
  318. { 0, 0, 0, 0, 4}
  319. },
  320. 3,
  321. 0
  322. },
  323. // IMAGE_FILTER::EMBOSS
  324. {
  325. { {-1, -1, -1, -1, 0},
  326. {-1, -1, -1, 0, 1},
  327. {-1, -1, 0, 1, 1},
  328. {-1, 0, 1, 1, 1},
  329. { 0, 1, 1, 1, 1}
  330. },
  331. 1,
  332. 128
  333. },
  334. // IMAGE_FILTER::SHARPEN
  335. {
  336. { {-1, -1, -1, -1, -1},
  337. {-1, 2, 2, 2, -1},
  338. {-1, 2, 8, 2, -1},
  339. {-1, 2, 2, 2, -1},
  340. {-1, -1, -1, -1, -1}
  341. },
  342. 8,
  343. 0
  344. },
  345. // IMAGE_FILTER::MELT
  346. {
  347. { { 4, 2, 6, 8, 1},
  348. { 1, 2, 5, 4, 2},
  349. { 0, -1, 1, -1, 0},
  350. { 0, 0, -2, 0, 0},
  351. { 0, 0, 0, 0, 0}
  352. },
  353. 32,
  354. 0
  355. },
  356. // IMAGE_FILTER::SOBEL_GX
  357. {
  358. { { 0, 0, 0, 0, 0},
  359. { 0, -1, 0, 1, 0},
  360. { 0, -2, 0, 2, 0},
  361. { 0, -1, 0, 1, 0},
  362. { 0, 0, 0, 0, 0}
  363. },
  364. 1,
  365. 0
  366. },
  367. // IMAGE_FILTER::SOBEL_GY
  368. {
  369. { { 1, 2, 4, 2, 1},
  370. {-1, -1, 0, 1, 1},
  371. {-2, -2, 0, 2, 2},
  372. {-1, -1, 0, 1, 1},
  373. {-1, -2, -4, -2, -1},
  374. },
  375. 1,
  376. 0
  377. },
  378. // IMAGE_FILTER::BLUR_3X3
  379. {
  380. { { 0, 0, 0, 0, 0},
  381. { 0, 1, 2, 1, 0},
  382. { 0, 2, 4, 2, 0},
  383. { 0, 1, 2, 1, 0},
  384. { 0, 0, 0, 0, 0},
  385. },
  386. 16,
  387. 0
  388. }
  389. };// Filters
  390. // clang-format on
  391. /// @todo: This function can be optimized slipping it between the edges and
  392. /// do it without use the getpixel function.
  393. /// Optimization can be done to m_pixels[ix + iy * m_width]
  394. /// but keep in mind the parallel process of the algorithm
  395. void IMAGE::EfxFilter( IMAGE* aInImg, IMAGE_FILTER aFilterType )
  396. {
  397. S_FILTER filter = FILTERS[static_cast<int>( aFilterType )];
  398. aInImg->m_wraping = IMAGE_WRAP::CLAMP;
  399. m_wraping = IMAGE_WRAP::CLAMP;
  400. std::atomic<size_t> nextRow( 0 );
  401. std::atomic<size_t> threadsFinished( 0 );
  402. size_t parallelThreadCount = std::max<size_t>( std::thread::hardware_concurrency(), 2 );
  403. for( size_t ii = 0; ii < parallelThreadCount; ++ii )
  404. {
  405. std::thread t = std::thread( [&]()
  406. {
  407. for( size_t iy = nextRow.fetch_add( 1 ); iy < m_height; iy = nextRow.fetch_add( 1 ) )
  408. {
  409. for( size_t ix = 0; ix < m_width; ix++ )
  410. {
  411. int v = 0;
  412. for( size_t sy = 0; sy < 5; sy++ )
  413. {
  414. for( size_t sx = 0; sx < 5; sx++ )
  415. {
  416. int factor = filter.kernel[sx][sy];
  417. unsigned char pixelv = aInImg->Getpixel( ix + sx - 2, iy + sy - 2 );
  418. v += pixelv * factor;
  419. }
  420. }
  421. v /= filter.div;
  422. v += filter.offset;
  423. CLAMP(v, 0, 255);
  424. /// @todo This needs to write to a separate buffer.
  425. m_pixels[ix + iy * m_width] = v;
  426. }
  427. }
  428. threadsFinished++;
  429. } );
  430. t.detach();
  431. }
  432. while( threadsFinished < parallelThreadCount )
  433. std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
  434. }
  435. void IMAGE::EfxFilter_SkipCenter( IMAGE* aInImg, IMAGE_FILTER aFilterType, unsigned int aRadius )
  436. {
  437. if( ( !aInImg ) || ( m_width != aInImg->m_width ) || ( m_height != aInImg->m_height ) )
  438. return;
  439. S_FILTER filter = FILTERS[static_cast<int>( aFilterType )];
  440. aInImg->m_wraping = IMAGE_WRAP::ZERO;
  441. const unsigned int radiusSquared = aRadius * aRadius;
  442. const unsigned int xCenter = m_width / 2;
  443. const unsigned int yCenter = m_height / 2;
  444. for( size_t iy = 0; iy < m_height; iy++ )
  445. {
  446. int yc = iy - yCenter;
  447. unsigned int ycsq = yc * yc;
  448. for( size_t ix = 0; ix < m_width; ix++ )
  449. {
  450. int xc = ix - xCenter;
  451. unsigned int xcsq = xc * xc;
  452. if( ( xcsq + ycsq ) < radiusSquared )
  453. {
  454. const unsigned int offset = ix + iy * m_width;
  455. m_pixels[offset] = aInImg->m_pixels[offset];
  456. continue;
  457. }
  458. int v = 0;
  459. for( size_t sy = 0; sy < 5; sy++ )
  460. {
  461. for( size_t sx = 0; sx < 5; sx++ )
  462. {
  463. int factor = filter.kernel[sx][sy];
  464. unsigned char pixelv = aInImg->Getpixel( ix + sx - 2, iy + sy - 2 );
  465. v += pixelv * factor;
  466. }
  467. }
  468. v /= filter.div;
  469. v += filter.offset;
  470. CLAMP(v, 0, 255);
  471. m_pixels[ix + iy * m_width] = v;
  472. }
  473. }
  474. }
  475. void IMAGE::SetPixelsFromNormalizedFloat( const float* aNormalizedFloatArray )
  476. {
  477. for( unsigned int i = 0; i < m_wxh; i++ )
  478. {
  479. int v = aNormalizedFloatArray[i] * 255;
  480. CLAMP( v, 0, 255 );
  481. m_pixels[i] = v;
  482. }
  483. }
  484. void IMAGE::SaveAsPNG( const wxString& aFileName ) const
  485. {
  486. DBG_SaveBuffer( aFileName, m_pixels, m_width, m_height );
  487. }