7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-02-18 21:59:36 +00:00
kicad/3d-viewer/3d_rendering/image.cpp
Seth Hillbrand 0b2d4d4879 Revise Copyright statement to align with TLF
Recommendation is to avoid using the year nomenclature as this
information is already encoded in the git repo.  Avoids needing to
repeatly update.

Also updates AUTHORS.txt from current repo with contributor names
2025-01-01 14:12:04 -08:00

601 lines
14 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 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 );
}