kicad/3d-viewer/3d_rendering/cimage.cpp

543 lines
12 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015-2016 Mario Luzeiro <mrluzeiro@ua.pt>
* 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 <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
CIMAGE::CIMAGE( 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;
}
CIMAGE::CIMAGE( const CIMAGE &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;
}
CIMAGE::~CIMAGE()
{
delete[] 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 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 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, IMAGE_OP aOperation )
{
int aV, bV;
if( aOperation == IMAGE_OP::RAW )
{
if( aImgA == NULL )
return;
}
else
{
if( (aImgA == NULL) || (aImgB == NULL) )
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 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, 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 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 );
}