lumosquad/FFT.cpp
2025-07-26 22:14:37 +02:00

233 lines
8.6 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// File : FFT.cpp
///////////////////////////////////////////////////////////////////////////////////
//
// LumosQuad - A Lightning Generator
// Copyright 2007
// The University of North Carolina at Chapel Hill
//
///////////////////////////////////////////////////////////////////////////////////
//
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// The University of North Carolina at Chapel Hill makes no representations
// about the suitability of this software for any purpose. It is provided
// "as is" without express or implied warranty.
//
// Permission to use, copy, modify and distribute this software and its
// documentation for educational, research and non-profit purposes, without
// fee, and without a written agreement is hereby granted, provided that the
// above copyright notice and the following three paragraphs appear in all
// copies.
//
// THE UNIVERSITY OF NORTH CAROLINA SPECIFICALLY DISCLAIM ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
// "AS IS" BASIS, AND THE UNIVERSITY OF NORTH CAROLINA HAS NO OBLIGATION TO
// PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
//
// Please send questions and comments about LumosQuad to kim@cs.unc.edu.
//
///////////////////////////////////////////////////////////////////////////////////
//
// This program uses OpenEXR, which has the following restrictions:
//
// Copyright (c) 2002, Industrial Light & Magic, a division of Lucas
// Digital Ltd. LLC
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Industrial Light & Magic nor the names of
// its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
#include "FFT.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
FFT::FFT()
{
}
FFT::~FFT()
{
}
bool FFT::convolve(float* source, float* kernel, int xSource, int ySource, int xKernel, int yKernel)
{
int x, y, index;
// get normalization params
float maxCurrent = 0.0f;
for (x = 0; x < xSource * ySource; x++)
maxCurrent = (maxCurrent < source[x]) ? source[x] : maxCurrent;
float maxKernel = 0.0f;
for (x = 0; x < xKernel * yKernel; x++)
maxKernel = (maxKernel < kernel[x]) ? kernel[x] : maxKernel;
float maxProduct = maxCurrent * maxKernel;
// retrieve dimensions
int xHalf = xKernel / 2;
int yHalf = yKernel / 2;
int xResPadded = xSource + xKernel;
int yResPadded = ySource + yKernel;
if (xResPadded != yResPadded)
(xResPadded > yResPadded) ? yResPadded = xResPadded : xResPadded = yResPadded;
// create padded field
fftw_complex* padded = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * xResPadded * yResPadded);
if (!padded)
{
cout << " IMAGE: Not enough memory! Try a smaller final image size." << endl;
return false;
}
// init padded field
for (index = 0; index < xResPadded * yResPadded; index++)
padded[index][0] = padded[index][1] = 0.0f;
index = 0;
for (y = 0; y < ySource; y++)
for (x = 0; x < xSource; x++, index++)
{
int paddedIndex = (x + xKernel / 2) + (y + yKernel / 2) * xResPadded;
padded[paddedIndex][0] = source[index];
}
// create padded filter
fftw_complex* filter = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * xResPadded * yResPadded);
if (!filter)
{
cout << " FILTER: Not enough memory! Try a smaller final image size." << endl;
return false;
}
// init padded filter
for (index = 0; index < xResPadded * yResPadded; index++)
filter[index][0] = filter[index][1] = 0.0f;
// quadrant IV
for (y = 0; y < (yHalf + 1); y++)
for (x = 0; x < (xHalf + 1); x++)
{
int filterIndex = x + xHalf + y * xKernel;
int fieldIndex = x + (y + yResPadded - (yHalf + 1)) * xResPadded;
filter[fieldIndex][0] = kernel[filterIndex];
}
// quadrant I
for (y = 0; y < yHalf; y++)
for (x = 0; x < (xHalf + 1); x++)
{
int filterIndex = (x + xHalf) + (y + yHalf + 1) * xKernel;
int fieldIndex = x + y * xResPadded;
filter[fieldIndex][0] = filter[fieldIndex][1] = kernel[filterIndex];
}
// quadrant III
for (y = 0; y < (yHalf + 1); y++)
for (x = 0; x < xHalf; x++)
{
int filterIndex = x + y * xKernel;
int fieldIndex = (x + xResPadded - xHalf) + (y + yResPadded - (yHalf + 1)) * xResPadded;
filter[fieldIndex][0] = filter[fieldIndex][1] = kernel[filterIndex];
}
// quadrant II
for (y = 0; y < yHalf; y++)
for (x = 0; x < xHalf; x++)
{
int filterIndex = x + (y + yHalf + 1) * xKernel;
int fieldIndex = (x + xResPadded - xHalf) + y * xResPadded;
filter[fieldIndex][0] = filter[fieldIndex][1] = kernel[filterIndex];
}
// perform forward FFT on field
fftw_complex* paddedTransformed = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * xResPadded * yResPadded);
if (!paddedTransformed)
{
cout << " T-IMAGE: Not enough memory! Try a smaller final image size." << endl;
return false;
}
fftw_plan forwardField = fftw_plan_dft_2d(xResPadded, yResPadded, padded, paddedTransformed, FFTW_FORWARD, FFTW_ESTIMATE);
fftw_execute(forwardField);
// perform forward FFT on filter
fftw_complex* filterTransformed = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * xResPadded * yResPadded);
if (!filterTransformed)
{
cout << " T-FILTER: Not enough memory! Try a smaller final image size." << endl;
return false;
}
fftw_plan forwardFilter = fftw_plan_dft_2d(xResPadded, yResPadded, filter, filterTransformed, FFTW_FORWARD, FFTW_ESTIMATE);
fftw_execute(forwardFilter);
// apply frequency space filter
for (index = 0; index < xResPadded * yResPadded; index++)
{
float newReal = paddedTransformed[index][0] * filterTransformed[index][0] -
paddedTransformed[index][1] * filterTransformed[index][1];
float newIm = paddedTransformed[index][0] * filterTransformed[index][1] +
paddedTransformed[index][1] * filterTransformed[index][0];
paddedTransformed[index][0] = newReal;
paddedTransformed[index][1] = newIm;
}
// transform back
fftw_plan backwardField = fftw_plan_dft_2d(xResPadded, yResPadded, paddedTransformed, padded, FFTW_BACKWARD, FFTW_ESTIMATE);
fftw_execute(backwardField);
// copy back into padded
index = 0;
for (y = 0; y < ySource; y++)
for (x = 0; x < xSource; x++, index++)
{
int paddedIndex = (x + xKernel / 2) + (y + yKernel / 2) * xResPadded;
source[index] = padded[paddedIndex][0];
}
// clean up
fftw_free(padded);
fftw_free(paddedTransformed);
fftw_free(filter);
fftw_free(filterTransformed);
// if normalization is exceeded, renormalize
float newMax = 0.0f;
for (x = 0; x < xSource * ySource; x++)
newMax = (newMax < source[x]) ? source[x] : newMax;
if (newMax > maxProduct)
{
float scale = maxProduct / newMax;
for (x = 0; x < xSource * ySource; x++)
source[x] *= scale;
}
return true;
}