|
|
/*
* This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015-2016 Mario Luzeiro <mrluzeiro@ua.pt> * Copyright The 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 image.cpp * @brief one 8bit-channel image implementation. */
#include "image.h"
#include "buffers_debug.h"
#include <cstring> // For memcpy
#include <algorithm>
#include <atomic>
#include <thread>
#include <chrono>
#ifndef CLAMP
#define CLAMP( n, min, max ) {if( n < min ) n=min; else if( n > max ) n = max;}
#endif
IMAGE::IMAGE( unsigned int aXsize, unsigned int aYsize ){ m_wxh = aXsize * aYsize; m_pixels = new unsigned char[m_wxh]; memset( m_pixels, 0, m_wxh ); m_width = aXsize; m_height = aYsize; m_wraping = IMAGE_WRAP::CLAMP;}
IMAGE::IMAGE( const IMAGE& aSrcImage ){ m_wxh = aSrcImage.GetWidth() * aSrcImage.GetHeight(); m_pixels = new unsigned char[m_wxh]; memcpy( m_pixels, aSrcImage.GetBuffer(), m_wxh ); m_width = aSrcImage.GetWidth(); m_height = aSrcImage.GetHeight(); m_wraping = IMAGE_WRAP::CLAMP;}
IMAGE::~IMAGE(){ delete[] m_pixels;}
unsigned char* IMAGE::GetBuffer() const{ return m_pixels;}
bool IMAGE::wrapCoords( int* aXo, int* aYo ) const{ int x = *aXo; int y = *aYo;
switch( m_wraping ) { case IMAGE_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 IMAGE_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 IMAGE::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 IMAGE::Setpixel( int aX, int aY, unsigned char aValue ){ if( wrapCoords( &aX, &aY ) ) m_pixels[aX + aY * m_width] = aValue;}
unsigned char IMAGE::Getpixel( int aX, int aY ) const{ if( wrapCoords( &aX, &aY ) ) return m_pixels[aX + aY * m_width]; else return 0;}
void IMAGE::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 IMAGE::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 IMAGE::Invert(){ for( unsigned int it = 0; it < m_wxh; it++ ) m_pixels[it] = 255 - m_pixels[it];}
void IMAGE::CopyFull( const IMAGE* aImgA, const IMAGE* aImgB, IMAGE_OP aOperation ){ int aV, bV;
if( aOperation == IMAGE_OP::RAW ) { if( aImgA == nullptr ) return; } else { if( ( aImgA == nullptr ) || ( aImgB == nullptr ) ) return; }
switch( aOperation ) { case IMAGE_OP::RAW: memcpy( m_pixels, aImgA->m_pixels, m_wxh ); break;
case IMAGE_OP::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 IMAGE_OP::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 IMAGE_OP::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 IMAGE_OP::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 IMAGE_OP::AND: for( unsigned int it = 0;it < m_wxh; it++ ) { m_pixels[it] = aImgA->m_pixels[it] & aImgB->m_pixels[it]; } break;
case IMAGE_OP::OR: for( unsigned int it = 0;it < m_wxh; it++ ) { m_pixels[it] = aImgA->m_pixels[it] | aImgB->m_pixels[it]; } break;
case IMAGE_OP::XOR: for( unsigned int it = 0;it < m_wxh; it++ ) { m_pixels[it] = aImgA->m_pixels[it] ^ aImgB->m_pixels[it]; } break;
case IMAGE_OP::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 IMAGE_OP::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 IMAGE_OP::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
// clang-format off
static const S_FILTER FILTERS[] = { // IMAGE_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 },
// IMAGE_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 },
// IMAGE_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 },
// IMAGE_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 },
// IMAGE_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 },
// IMAGE_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 },
// IMAGE_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 },
// IMAGE_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 },
// IMAGE_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 },
// IMAGE_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 },
// IMAGE_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
// clang-format on
/// @todo: This function can be optimized slipping 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 IMAGE::EfxFilter( IMAGE* aInImg, IMAGE_FILTER aFilterType ){ S_FILTER filter = FILTERS[static_cast<int>( aFilterType )];
aInImg->m_wraping = IMAGE_WRAP::CLAMP; m_wraping = IMAGE_WRAP::CLAMP;
std::atomic<size_t> nextRow( 0 ); std::atomic<size_t> threadsFinished( 0 );
size_t parallelThreadCount = std::max<size_t>( 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 IMAGE::EfxFilter_SkipCenter( IMAGE* aInImg, IMAGE_FILTER aFilterType, unsigned int aRadius ){ if( ( !aInImg ) || ( m_width != aInImg->m_width ) || ( m_height != aInImg->m_height ) ) return;
S_FILTER filter = FILTERS[static_cast<int>( aFilterType )];
aInImg->m_wraping = IMAGE_WRAP::ZERO;
const unsigned int radiusSquared = aRadius * aRadius;
const unsigned int xCenter = m_width / 2; const unsigned int yCenter = m_height / 2;
for( size_t iy = 0; iy < m_height; iy++ ) { int yc = iy - yCenter;
unsigned int ycsq = yc * yc;
for( size_t ix = 0; ix < m_width; ix++ ) { int xc = ix - xCenter;
unsigned int xcsq = xc * xc;
if( ( xcsq + ycsq ) < radiusSquared ) { const unsigned int offset = ix + iy * m_width;
m_pixels[offset] = aInImg->m_pixels[offset];
continue; }
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);
m_pixels[ix + iy * m_width] = v; } }}
void IMAGE::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 IMAGE::SaveAsPNG( const wxString& aFileName ) const{ DBG_SaveBuffer( aFileName, m_pixels, m_width, m_height );}
|