/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015-2016 Mario Luzeiro * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /** * @file cimage.cpp * @brief one 8bit-channel image implementation */ #include "cimage.h" #include "buffers_debug.h" #include // For memcpy #include #include #include #ifndef CLAMP #define CLAMP(n, min, max) {if( n < min ) n=min; else if( n > max ) n = max;} #endif CIMAGE::CIMAGE( unsigned int aXsize, unsigned int aYsize ) { m_wxh = aXsize * aYsize; m_pixels = (unsigned char*)malloc( m_wxh ); memset( m_pixels, 0, m_wxh ); m_width = aXsize; m_height = aYsize; m_wraping = (E_WRAP)WRAP_CLAMP; } CIMAGE::CIMAGE( const CIMAGE &aSrcImage ) { m_wxh = aSrcImage.GetWidth() * aSrcImage.GetHeight(); m_pixels = (unsigned char*)malloc( m_wxh ); memcpy( m_pixels, aSrcImage.GetBuffer(), m_wxh ); m_width = aSrcImage.GetWidth(); m_height = aSrcImage.GetHeight(); m_wraping = (E_WRAP)WRAP_CLAMP; } CIMAGE::~CIMAGE() { free( m_pixels ); } unsigned char* CIMAGE::GetBuffer() const { return m_pixels; } bool CIMAGE::wrapCoords( int *aXo, int *aYo ) const { int x = *aXo; int y = *aYo; switch(m_wraping) { case WRAP_CLAMP: x = (x < 0 )?0:x; x = (x >= (int)(m_width - 1))?(m_width - 1):x; y = (y < 0)?0:y; y = (y >= (int)(m_height - 1))?(m_height - 1):y; break; case WRAP_WRAP: x = (x < 0)?((m_width - 1)+x):x; x = (x >= (int)(m_width - 1))?(x - m_width):x; y = (y < 0)?((m_height - 1)+y):y; y = (y >= (int)(m_height - 1))?(y - m_height):y; break; default: break; } if( (x < 0) || (x >= (int)m_width) || (y < 0) || (y >= (int)m_height) ) return false; *aXo = x; *aYo = y; return true; } void CIMAGE::plot8CircleLines( int aCx, int aCy, int aX, int aY, unsigned char aValue ) { Hline( aCx - aX, aCx + aX, aCy + aY, aValue ); Hline( aCx - aX, aCx + aX, aCy - aY, aValue ); Hline( aCx - aY, aCx + aY, aCy + aX, aValue ); Hline( aCx - aY, aCx + aY, aCy - aX, aValue ); } void CIMAGE::Setpixel( int aX, int aY, unsigned char aValue ) { if( wrapCoords( &aX, &aY ) ) m_pixels[aX + aY * m_width] = aValue; } unsigned char CIMAGE::Getpixel( int aX, int aY ) const { if( wrapCoords( &aX, &aY ) ) return m_pixels[aX + aY * m_width]; else return 0; } void CIMAGE::Hline( int aXStart, int aXEnd, int aY, unsigned char aValue ) { if( ( aY < 0 ) || ( aY >= (int)m_height ) || ( ( aXStart < 0 ) && ( aXEnd < 0) ) || ( ( aXStart >= (int)m_width ) && ( aXEnd >= (int)m_width) ) ) return; if( aXStart > aXEnd ) { int swap = aXStart; aXStart = aXEnd; aXEnd = swap; } // Clamp line if( aXStart < 0 ) aXStart = 0; if( aXEnd >= (int)m_width ) aXEnd = m_width - 1; unsigned char* pixelPtr = &m_pixels[aXStart + aY * m_width]; unsigned char* pixelPtrEnd = pixelPtr + (unsigned int)((aXEnd - aXStart) + 1); while( pixelPtr < pixelPtrEnd ) { *pixelPtr = aValue; pixelPtr++; } } // Based on paper // http://web.engr.oregonstate.edu/~sllu/bcircle.pdf void CIMAGE::CircleFilled(int aCx, int aCy, int aRadius, unsigned char aValue) { int x = aRadius; int y = 0; int xChange = 1 - 2 * aRadius; int yChange = 0; int radiusError = 0; while( x >= y ) { plot8CircleLines( aCx, aCy, x, y, aValue ); y++; radiusError += yChange; yChange += 2; if( (2 * radiusError + xChange) > 0 ) { x--; radiusError += xChange; xChange += 2; } } } void CIMAGE::Invert() { for( unsigned int it = 0; it < m_wxh; it++ ) m_pixels[it] = 255 - m_pixels[it]; } void CIMAGE::CopyFull( const CIMAGE *aImgA, const CIMAGE *aImgB, E_IMAGE_OP aOperation ) { int aV, bV; if( aOperation == COPY_RAW ) { if( aImgA == NULL ) return; } else { if( (aImgA == NULL) || (aImgB == NULL) ) return; } switch(aOperation) { case COPY_RAW: memcpy( m_pixels, aImgA->m_pixels, m_wxh ); break; case COPY_ADD: for( unsigned int it = 0;it < m_wxh; it++ ) { aV = aImgA->m_pixels[it]; bV = aImgB->m_pixels[it]; aV = (aV + bV); aV = (aV > 255)?255:aV; m_pixels[it] = aV; } break; case COPY_SUB: for( unsigned int it = 0;it < m_wxh; it++ ) { aV = aImgA->m_pixels[it]; bV = aImgB->m_pixels[it]; aV = (aV - bV); aV = (aV < 0)?0:aV; m_pixels[it] = aV; } break; case COPY_DIF: for( unsigned int it = 0;it < m_wxh; it++ ) { aV = aImgA->m_pixels[it]; bV = aImgB->m_pixels[it]; m_pixels[it] = abs( aV - bV ); } break; case COPY_MUL: for( unsigned int it = 0;it < m_wxh; it++ ) { aV = aImgA->m_pixels[it]; bV = aImgB->m_pixels[it]; m_pixels[it] = (unsigned char)((((float)aV / 255.0f) * ((float)bV / 255.0f)) * 255); } break; case COPY_AND: for( unsigned int it = 0;it < m_wxh; it++ ) { m_pixels[it] = aImgA->m_pixels[it] & aImgB->m_pixels[it]; } break; case COPY_OR: for( unsigned int it = 0;it < m_wxh; it++ ) { m_pixels[it] = aImgA->m_pixels[it] | aImgB->m_pixels[it]; } break; case COPY_XOR: for( unsigned int it = 0;it < m_wxh; it++ ) { m_pixels[it] = aImgA->m_pixels[it] ^ aImgB->m_pixels[it]; } break; case COPY_BLEND50: for( unsigned int it = 0;it < m_wxh; it++ ) { aV = aImgA->m_pixels[it]; bV = aImgB->m_pixels[it]; m_pixels[it] = (aV + bV) / 2; } break; case COPY_MIN: for( unsigned int it = 0;it < m_wxh; it++ ) { aV = aImgA->m_pixels[it]; bV = aImgB->m_pixels[it]; m_pixels[it] = (aV < bV)?aV:bV; } break; case COPY_MAX: for( unsigned int it = 0;it < m_wxh; it++ ) { aV = aImgA->m_pixels[it]; bV = aImgB->m_pixels[it]; m_pixels[it] = (aV > bV)?aV:bV; } break; default: break; } } // TIP: If you want create or test filters you can use GIMP // with a generic convolution matrix and get the values from there. // http://docs.gimp.org/nl/plug-in-convmatrix.html static const S_FILTER FILTERS[] = { // FILTER_HIPASS { { { 0, -1, -1, -1, 0}, {-1, 2, -4, 2, -1}, {-1, -4, 13, -4, -1}, {-1, 2, -4, 2, -1}, { 0, -1, -1, -1, 0} }, 7, 255 }, // FILTER_GAUSSIAN_BLUR { { { 3, 5, 7, 5, 3}, { 5, 9, 12, 9, 5}, { 7, 12, 20, 12, 7}, { 5, 9, 12, 9, 5}, { 3, 5, 7, 5, 3} }, 182, 0 }, // FILTER_GAUSSIAN_BLUR2 { { { 1, 4, 7, 4, 1}, { 4, 16, 26, 16, 4}, { 7, 26, 41, 26, 7}, { 4, 16, 26, 16, 4}, { 1, 4, 7, 4, 1} }, 273, 0 }, // FILTER_INVERT_BLUR { { { 0, 0, 0, 0, 0}, { 0, 0, -1, 0, 0}, { 0, -1, 0, -1, 0}, { 0, 0, -1, 0, 0}, { 0, 0, 0, 0, 0} }, 4, 255 }, // FILTER_CARTOON { { {-1, -1, -1, -1, 0}, {-1, 0, 0, 0, 0}, {-1, 0, 4, 0, 0}, { 0, 0, 0, 1, 0}, { 0, 0, 0, 0, 4} }, 3, 0 }, // FILTER_EMBOSS { { {-1, -1, -1, -1, 0}, {-1, -1, -1, 0, 1}, {-1, -1, 0, 1, 1}, {-1, 0, 1, 1, 1}, { 0, 1, 1, 1, 1} }, 1, 128 }, // FILTER_SHARPEN { { {-1, -1, -1, -1, -1}, {-1, 2, 2, 2, -1}, {-1, 2, 8, 2, -1}, {-1, 2, 2, 2, -1}, {-1, -1, -1, -1, -1} }, 8, 0 }, // FILTER_MELT { { { 4, 2, 6, 8, 1}, { 1, 2, 5, 4, 2}, { 0, -1, 1, -1, 0}, { 0, 0, -2, 0, 0}, { 0, 0, 0, 0, 0} }, 32, 0 }, // FILTER_SOBEL_GX { { { 0, 0, 0, 0, 0}, { 0, -1, 0, 1, 0}, { 0, -2, 0, 2, 0}, { 0, -1, 0, 1, 0}, { 0, 0, 0, 0, 0} }, 1, 0 }, // FILTER_SOBEL_GY { { { 1, 2, 4, 2, 1}, {-1, -1, 0, 1, 1}, {-2, -2, 0, 2, 2}, {-1, -1, 0, 1, 1}, {-1, -2, -4, -2, -1}, }, 1, 0 }, // FILTER_BLUR_3X3 { { { 0, 0, 0, 0, 0}, { 0, 1, 2, 1, 0}, { 0, 2, 4, 2, 0}, { 0, 1, 2, 1, 0}, { 0, 0, 0, 0, 0}, }, 16, 0 } };// Filters // !TODO: This functions can be optimized slipting it between the edges and // do it without use the getpixel function. // Optimization can be done to m_pixels[ix + iy * m_width] // but keep in mind the parallel process of the algorithm void CIMAGE::EfxFilter( CIMAGE *aInImg, E_FILTER aFilterType ) { S_FILTER filter = FILTERS[aFilterType]; aInImg->m_wraping = WRAP_CLAMP; m_wraping = WRAP_CLAMP; std::atomic nextRow( 0 ); std::atomic threadsFinished( 0 ); size_t parallelThreadCount = std::max( std::thread::hardware_concurrency(), 2 ); for( size_t ii = 0; ii < parallelThreadCount; ++ii ) { std::thread t = std::thread( [&]() { for( size_t iy = nextRow.fetch_add( 1 ); iy < m_height; iy = nextRow.fetch_add( 1 ) ) { for( size_t ix = 0; ix < m_width; ix++ ) { int v = 0; for( size_t sy = 0; sy < 5; sy++ ) { for( size_t sx = 0; sx < 5; sx++ ) { int factor = filter.kernel[sx][sy]; unsigned char pixelv = aInImg->Getpixel( ix + sx - 2, iy + sy - 2 ); v += pixelv * factor; } } v /= filter.div; v += filter.offset; CLAMP(v, 0, 255); //TODO: This needs to write to a separate buffer m_pixels[ix + iy * m_width] = v; } } threadsFinished++; } ); t.detach(); } while( threadsFinished < parallelThreadCount ) std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); } void CIMAGE::SetPixelsFromNormalizedFloat( const float * aNormalizedFloatArray ) { for( unsigned int i = 0; i < m_wxh; i++ ) { int v = aNormalizedFloatArray[i] * 255; CLAMP( v, 0, 255 ); m_pixels[i] = v; } } void CIMAGE::SaveAsPNG( const wxString& aFileName ) const { DBG_SaveBuffer( aFileName, m_pixels, m_width, m_height ); }