mirror of
https://github.com/jamriska/ebsynth.git
synced 2025-12-16 08:27:45 +01:00
552 lines
19 KiB
C++
552 lines
19 KiB
C++
// This software is in the public domain. Where that dedication is not
|
|
// recognized, you are granted a perpetual, irrevocable license to copy
|
|
// and modify this file as you see fit.
|
|
|
|
#include "ebsynth.h"
|
|
#include "ebsynth_cpu.h"
|
|
#include "ebsynth_cuda.h"
|
|
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
|
|
EBSYNTH_API
|
|
void ebsynthRun(int ebsynthBackend,
|
|
int numStyleChannels,
|
|
int numGuideChannels,
|
|
int sourceWidth,
|
|
int sourceHeight,
|
|
void* sourceStyleData,
|
|
void* sourceGuideData,
|
|
int targetWidth,
|
|
int targetHeight,
|
|
void* targetGuideData,
|
|
void* targetModulationData,
|
|
float* styleWeights,
|
|
float* guideWeights,
|
|
float uniformityWeight,
|
|
int patchSize,
|
|
int voteMode,
|
|
int numPyramidLevels,
|
|
int* numSearchVoteItersPerLevel,
|
|
int* numPatchMatchItersPerLevel,
|
|
int* stopThresholdPerLevel,
|
|
void* outputNnfData,
|
|
void* outputImageData)
|
|
{
|
|
void (*backendDispatch)(int,int,int,int,void*,void*,int,int,void*,void*,float*,float*,float,int,int,int,int*,int*,int*,void*,void*) = 0;
|
|
|
|
if (ebsynthBackend==EBSYNTH_BACKEND_CPU ) { backendDispatch = ebsynthRunCpu; }
|
|
else if (ebsynthBackend==EBSYNTH_BACKEND_CUDA) { backendDispatch = ebsynthRunCuda; }
|
|
else if (ebsynthBackend==EBSYNTH_BACKEND_AUTO) { backendDispatch = ebsynthBackendAvailableCuda() ? ebsynthRunCuda : ebsynthRunCpu; }
|
|
|
|
if (backendDispatch!=0)
|
|
{
|
|
backendDispatch(numStyleChannels,
|
|
numGuideChannels,
|
|
sourceWidth,
|
|
sourceHeight,
|
|
sourceStyleData,
|
|
sourceGuideData,
|
|
targetWidth,
|
|
targetHeight,
|
|
targetGuideData,
|
|
targetModulationData,
|
|
styleWeights,
|
|
guideWeights,
|
|
uniformityWeight,
|
|
patchSize,
|
|
voteMode,
|
|
numPyramidLevels,
|
|
numSearchVoteItersPerLevel,
|
|
numPatchMatchItersPerLevel,
|
|
stopThresholdPerLevel,
|
|
outputNnfData,
|
|
outputImageData);
|
|
}
|
|
}
|
|
|
|
EBSYNTH_API
|
|
int ebsynthBackendAvailable(int ebsynthBackend)
|
|
{
|
|
if (ebsynthBackend==EBSYNTH_BACKEND_CPU ) { return ebsynthBackendAvailableCpu(); }
|
|
else if (ebsynthBackend==EBSYNTH_BACKEND_CUDA) { return ebsynthBackendAvailableCuda(); }
|
|
else if (ebsynthBackend==EBSYNTH_BACKEND_AUTO) { return ebsynthBackendAvailableCpu() || ebsynthBackendAvailableCuda(); }
|
|
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
#include <algorithm>
|
|
|
|
#include "jzq.h"
|
|
|
|
template<typename FUNC>
|
|
bool tryToParseArg(const std::vector<std::string>& args,int* inout_argi,const char* name,bool* out_fail,FUNC handler)
|
|
{
|
|
int& argi = *inout_argi;
|
|
bool& fail = *out_fail;
|
|
|
|
if (argi<0 || argi>=args.size()) { fail = true; return false; }
|
|
|
|
if (args[argi]==name)
|
|
{
|
|
argi++;
|
|
fail = !handler();
|
|
return true;
|
|
}
|
|
|
|
fail = false; return false;
|
|
}
|
|
|
|
bool tryToParseIntArg(const std::vector<std::string>& args,int* inout_argi,const char* name,int* out_value,bool* out_fail)
|
|
{
|
|
return tryToParseArg(args,inout_argi,name,out_fail,[&]
|
|
{
|
|
int& argi = *inout_argi;
|
|
if (argi<args.size())
|
|
{
|
|
const std::string& arg = args[argi];
|
|
try
|
|
{
|
|
std::size_t pos = 0;
|
|
*out_value = std::stoi(arg,&pos);
|
|
if (pos!=arg.size()) { printf("error: bad %s argument '%s'\n",name,arg.c_str()); return false; }
|
|
return true;
|
|
}
|
|
catch(...)
|
|
{
|
|
printf("error: bad %s argument '%s'\n",name,arg.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
printf("error: missing argument for the %s option\n",name);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
bool tryToParseFloatArg(const std::vector<std::string>& args,int* inout_argi,const char* name,float* out_value,bool* out_fail)
|
|
{
|
|
return tryToParseArg(args,inout_argi,name,out_fail,[&]
|
|
{
|
|
int& argi = *inout_argi;
|
|
if (argi<args.size())
|
|
{
|
|
const std::string& arg = args[argi];
|
|
try
|
|
{
|
|
std::size_t pos = 0;
|
|
*out_value = std::stof(arg,&pos);
|
|
if (pos!=arg.size()) { printf("error: bad %s argument '%s'\n",name,arg.c_str()); return false; }
|
|
return true;
|
|
}
|
|
catch(...)
|
|
{
|
|
printf("error: bad %s argument '%s'\n",name,args[argi].c_str());
|
|
return false;
|
|
}
|
|
}
|
|
printf("error: missing argument for the %s option\n",name);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
bool tryToParseStringArg(const std::vector<std::string>& args,int* inout_argi,const char* name,std::string* out_value,bool* out_fail)
|
|
{
|
|
return tryToParseArg(args,inout_argi,name,out_fail,[&]
|
|
{
|
|
int& argi = *inout_argi;
|
|
if (argi<args.size())
|
|
{
|
|
*out_value = args[argi];
|
|
return true;
|
|
}
|
|
printf("error: missing argument for the %s option\n",name);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
bool tryToParseStringPairArg(const std::vector<std::string>& args,int* inout_argi,const char* name,std::pair<std::string,std::string>* out_value,bool* out_fail)
|
|
{
|
|
return tryToParseArg(args,inout_argi,name,out_fail,[&]
|
|
{
|
|
int& argi = *inout_argi;
|
|
if ((argi+1)<args.size())
|
|
{
|
|
*out_value = std::make_pair(args[argi],args[argi+1]);
|
|
argi++;
|
|
return true;
|
|
}
|
|
printf("error: missing argument for the %s option\n",name);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#include "stb_image.h"
|
|
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
#include "stb_image_write.h"
|
|
|
|
unsigned char* tryLoad(const std::string& fileName,int* width,int* height)
|
|
{
|
|
unsigned char* data = stbi_load(fileName.c_str(),width,height,NULL,4);
|
|
if (data==NULL)
|
|
{
|
|
printf("error: failed to load '%s'\n",fileName.c_str());
|
|
printf("%s\n",stbi_failure_reason());
|
|
exit(1);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
int evalNumChannels(const unsigned char* data,const int numPixels)
|
|
{
|
|
bool isGray = true;
|
|
bool hasAlpha = false;
|
|
|
|
for(int xy=0;xy<numPixels;xy++)
|
|
{
|
|
const unsigned char r = data[xy*4+0];
|
|
const unsigned char g = data[xy*4+1];
|
|
const unsigned char b = data[xy*4+2];
|
|
const unsigned char a = data[xy*4+3];
|
|
|
|
if (!(r==g && g==b)) { isGray = false; }
|
|
if (a<255) { hasAlpha = true; }
|
|
}
|
|
|
|
const int numChannels = (isGray ? 1 : 3) + (hasAlpha ? 1 : 0);
|
|
|
|
return numChannels;
|
|
}
|
|
|
|
V2i pyramidLevelSize(const V2i& sizeBase,const int level)
|
|
{
|
|
return V2i(V2f(sizeBase)*std::pow(2.0f,-float(level)));
|
|
}
|
|
|
|
std::string backendToString(const int ebsynthBackend)
|
|
{
|
|
if (ebsynthBackend==EBSYNTH_BACKEND_CPU) { return "cpu"; }
|
|
else if (ebsynthBackend==EBSYNTH_BACKEND_CUDA) { return "cuda"; }
|
|
else if (ebsynthBackend==EBSYNTH_BACKEND_AUTO) { return "auto"; }
|
|
return "unknown";
|
|
}
|
|
|
|
int main(int argc,char** argv)
|
|
{
|
|
if (argc<2)
|
|
{
|
|
printf("usage: %s [options]\n",argv[0]);
|
|
printf("\n");
|
|
printf("options:\n");
|
|
printf(" -style <style.png>\n");
|
|
printf(" -guide <source.png> <target.png>\n");
|
|
printf(" -output <output.png>\n");
|
|
printf(" -weight <value>\n");
|
|
printf(" -uniformity <value>\n");
|
|
printf(" -patchsize <size>\n");
|
|
printf(" -pyramidlevels <number>\n");
|
|
printf(" -searchvoteiters <number>\n");
|
|
printf(" -patchmatchiters <number>\n");
|
|
printf(" -stopthreshold <value>\n");
|
|
printf(" -backend [cpu|cuda]\n");
|
|
printf("\n");
|
|
return 1;
|
|
}
|
|
|
|
std::string styleFileName;
|
|
float styleWeight = NAN;
|
|
std::string outputFileName = "output.png";
|
|
|
|
struct Guide
|
|
{
|
|
std::string sourceFileName;
|
|
std::string targetFileName;
|
|
float weight;
|
|
|
|
int sourceWidth;
|
|
int sourceHeight;
|
|
unsigned char* sourceData;
|
|
|
|
int targetWidth;
|
|
int targetHeight;
|
|
unsigned char* targetData;
|
|
|
|
int numChannels;
|
|
};
|
|
|
|
std::vector<Guide> guides;
|
|
|
|
float uniformityWeight = 3500;
|
|
int patchSize = 5;
|
|
int numPyramidLevels = -1;
|
|
int numSearchVoteIters = 6;
|
|
int numPatchMatchIters = 4;
|
|
int stopThreshold = 5;
|
|
int backend = ebsynthBackendAvailable(EBSYNTH_BACKEND_CUDA) ? EBSYNTH_BACKEND_CUDA : EBSYNTH_BACKEND_CPU;
|
|
|
|
{
|
|
std::vector<std::string> args(argc);
|
|
for(int i=0;i<argc;i++) { args[i] = argv[i]; }
|
|
|
|
bool fail = false;
|
|
int argi = 1;
|
|
|
|
float* precedingStyleOrGuideWeight = 0;
|
|
while(argi<argc && !fail)
|
|
{
|
|
float weight;
|
|
std::pair<std::string,std::string> guidePair;
|
|
std::string backendName;
|
|
|
|
if (tryToParseStringArg(args,&argi,"-style",&styleFileName,&fail))
|
|
{
|
|
styleWeight = NAN;
|
|
precedingStyleOrGuideWeight = &styleWeight;
|
|
argi++;
|
|
}
|
|
else if (tryToParseStringPairArg(args,&argi,"-guide",&guidePair,&fail))
|
|
{
|
|
Guide guide;
|
|
guide.sourceFileName = guidePair.first;
|
|
guide.targetFileName = guidePair.second;
|
|
guide.weight = NAN;
|
|
guides.push_back(guide);
|
|
precedingStyleOrGuideWeight = &guides[guides.size()-1].weight;
|
|
argi++;
|
|
}
|
|
else if (tryToParseStringArg(args,&argi,"-output",&outputFileName,&fail))
|
|
{
|
|
argi++;
|
|
}
|
|
else if (tryToParseFloatArg(args,&argi,"-weight",&weight,&fail))
|
|
{
|
|
if (precedingStyleOrGuideWeight!=0) { *precedingStyleOrGuideWeight = weight; }
|
|
else { printf("error: at least one -style or -guide option must precede the -weight option!\n"); return 1; }
|
|
argi++;
|
|
}
|
|
else if (tryToParseFloatArg(args,&argi,"-uniformity",&uniformityWeight,&fail)) { argi++; }
|
|
else if (tryToParseIntArg(args,&argi,"-patchsize",&patchSize,&fail))
|
|
{
|
|
if (patchSize<3) { printf("error: patchsize is too small!\n"); return 1; }
|
|
if (patchSize%2==0) { printf("error: patchsize must be an odd number!\n"); return 1; }
|
|
argi++;
|
|
}
|
|
else if (tryToParseIntArg(args,&argi,"-pyramidlevels",&numPyramidLevels,&fail))
|
|
{
|
|
if (numPyramidLevels<1) { printf("error: bad argument for -pyramidlevels!\n"); return 1; }
|
|
argi++;
|
|
}
|
|
else if (tryToParseIntArg(args,&argi,"-searchvoteiters",&numSearchVoteIters,&fail))
|
|
{
|
|
if (numSearchVoteIters<0) { printf("error: bad argument for -searchvoteiters!\n"); return 1; }
|
|
argi++;
|
|
}
|
|
else if (tryToParseIntArg(args,&argi,"-patchmatchiters",&numPatchMatchIters,&fail))
|
|
{
|
|
if (numPatchMatchIters<0) { printf("error: bad argument for -patchmatchiters!\n"); return 1; }
|
|
argi++;
|
|
}
|
|
else if (tryToParseIntArg(args,&argi,"-stopthreshold",&stopThreshold,&fail))
|
|
{
|
|
if (stopThreshold<0) { printf("error: bad argument for -stopthreshold!\n"); return 1; }
|
|
argi++;
|
|
}
|
|
else if (tryToParseStringArg(args,&argi,"-backend",&backendName,&fail))
|
|
{
|
|
if (backendName=="cpu" ) { backend = EBSYNTH_BACKEND_CPU; }
|
|
else if (backendName=="cuda") { backend = EBSYNTH_BACKEND_CUDA; }
|
|
else { printf("error: unrecognized backend '%s'\n",backendName.c_str()); return 1; }
|
|
|
|
if (!ebsynthBackendAvailable(backend)) { printf("error: the %s backend is not available!\n",backendToString(backend).c_str()); return 1; }
|
|
|
|
argi++;
|
|
}
|
|
else
|
|
{
|
|
printf("error: unrecognized option '%s'\n",args[argi].c_str());
|
|
fail = true;
|
|
}
|
|
|
|
}
|
|
|
|
if (fail) { return 1; }
|
|
}
|
|
|
|
const int numGuides = guides.size();
|
|
|
|
int sourceWidth = 0;
|
|
int sourceHeight = 0;
|
|
unsigned char* sourceStyleData = tryLoad(styleFileName,&sourceWidth,&sourceHeight);
|
|
const int numStyleChannelsTotal = evalNumChannels(sourceStyleData,sourceWidth*sourceHeight);
|
|
|
|
std::vector<unsigned char> sourceStyle(sourceWidth*sourceHeight*numStyleChannelsTotal);
|
|
for(int xy=0;xy<sourceWidth*sourceHeight;xy++)
|
|
{
|
|
if (numStyleChannelsTotal>0) { sourceStyle[xy*numStyleChannelsTotal+0] = sourceStyleData[xy*4+0]; }
|
|
if (numStyleChannelsTotal==2) { sourceStyle[xy*numStyleChannelsTotal+1] = sourceStyleData[xy*4+3]; }
|
|
else if (numStyleChannelsTotal>1) { sourceStyle[xy*numStyleChannelsTotal+1] = sourceStyleData[xy*4+1]; }
|
|
if (numStyleChannelsTotal>2) { sourceStyle[xy*numStyleChannelsTotal+2] = sourceStyleData[xy*4+2]; }
|
|
if (numStyleChannelsTotal>3) { sourceStyle[xy*numStyleChannelsTotal+3] = sourceStyleData[xy*4+3]; }
|
|
}
|
|
|
|
int targetWidth = 0;
|
|
int targetHeight = 0;
|
|
int numGuideChannelsTotal = 0;
|
|
|
|
for(int i=0;i<numGuides;i++)
|
|
{
|
|
Guide& guide = guides[i];
|
|
|
|
guide.sourceData = tryLoad(guide.sourceFileName,&guide.sourceWidth,&guide.sourceHeight);
|
|
guide.targetData = tryLoad(guide.targetFileName,&guide.targetWidth,&guide.targetHeight);
|
|
|
|
if (guide.sourceWidth!=sourceWidth || guide.sourceHeight!=sourceHeight) { printf("error: source guide '%s' doesn't match the resolution of '%s'\n",guide.sourceFileName.c_str(),styleFileName.c_str()); return 1; }
|
|
if (i>0 && (guide.targetWidth!=targetWidth || guide.targetHeight!=targetHeight)) { printf("error: target guide '%s' doesn't match the resolution of '%s'\n",guide.targetFileName.c_str(),guides[0].targetFileName.c_str()); return 1; }
|
|
else if (i==0) { targetWidth = guide.targetWidth; targetHeight = guide.targetHeight; }
|
|
|
|
guide.numChannels = std::max(evalNumChannels(guide.sourceData,sourceWidth*sourceHeight),
|
|
evalNumChannels(guide.targetData,targetWidth*targetHeight));
|
|
|
|
numGuideChannelsTotal += guide.numChannels;
|
|
}
|
|
|
|
if (numStyleChannelsTotal>EBSYNTH_MAX_STYLE_CHANNELS) { printf("error: too many style channels (%d), maximum number is %d\n",numStyleChannelsTotal,EBSYNTH_MAX_STYLE_CHANNELS); return 1; }
|
|
if (numGuideChannelsTotal>EBSYNTH_MAX_GUIDE_CHANNELS) { printf("error: too many guide channels (%d), maximum number is %d\n",numGuideChannelsTotal,EBSYNTH_MAX_GUIDE_CHANNELS); return 1; }
|
|
|
|
std::vector<unsigned char> sourceGuides(sourceWidth*sourceHeight*numGuideChannelsTotal);
|
|
for(int xy=0;xy<sourceWidth*sourceHeight;xy++)
|
|
{
|
|
int c = 0;
|
|
for(int i=0;i<numGuides;i++)
|
|
{
|
|
const int numChannels = guides[i].numChannels;
|
|
|
|
if (numChannels>0) { sourceGuides[xy*numGuideChannelsTotal+c+0] = guides[i].sourceData[xy*4+0]; }
|
|
if (numChannels==2) { sourceGuides[xy*numGuideChannelsTotal+c+1] = guides[i].sourceData[xy*4+3]; }
|
|
else if (numChannels>1) { sourceGuides[xy*numGuideChannelsTotal+c+1] = guides[i].sourceData[xy*4+1]; }
|
|
if (numChannels>2) { sourceGuides[xy*numGuideChannelsTotal+c+2] = guides[i].sourceData[xy*4+2]; }
|
|
if (numChannels>3) { sourceGuides[xy*numGuideChannelsTotal+c+3] = guides[i].sourceData[xy*4+3]; }
|
|
|
|
c += numChannels;
|
|
}
|
|
}
|
|
|
|
std::vector<unsigned char> targetGuides(targetWidth*targetHeight*numGuideChannelsTotal);
|
|
for(int xy=0;xy<targetWidth*targetHeight;xy++)
|
|
{
|
|
int c = 0;
|
|
for(int i=0;i<numGuides;i++)
|
|
{
|
|
const int numChannels = guides[i].numChannels;
|
|
|
|
if (numChannels>0) { targetGuides[xy*numGuideChannelsTotal+c+0] = guides[i].targetData[xy*4+0]; }
|
|
if (numChannels==2) { targetGuides[xy*numGuideChannelsTotal+c+1] = guides[i].targetData[xy*4+3]; }
|
|
else if (numChannels>1) { targetGuides[xy*numGuideChannelsTotal+c+1] = guides[i].targetData[xy*4+1]; }
|
|
if (numChannels>2) { targetGuides[xy*numGuideChannelsTotal+c+2] = guides[i].targetData[xy*4+2]; }
|
|
if (numChannels>3) { targetGuides[xy*numGuideChannelsTotal+c+3] = guides[i].targetData[xy*4+3]; }
|
|
|
|
c += numChannels;
|
|
}
|
|
}
|
|
|
|
std::vector<float> styleWeights(numStyleChannelsTotal);
|
|
if (isnan(styleWeight)) { styleWeight = 1.0f; }
|
|
for(int i=0;i<numStyleChannelsTotal;i++) { styleWeights[i] = styleWeight / float(numStyleChannelsTotal); }
|
|
|
|
for(int i=0;i<numGuides;i++) { if (isnan(guides[i].weight)) { guides[i].weight = 1.0f/float(numGuides); } }
|
|
|
|
std::vector<float> guideWeights(numGuideChannelsTotal);
|
|
{
|
|
int c = 0;
|
|
for(int i=0;i<numGuides;i++)
|
|
{
|
|
const int numChannels = guides[i].numChannels;
|
|
|
|
for(int j=0;j<numChannels;j++)
|
|
{
|
|
guideWeights[c+j] = guides[i].weight / float(numChannels);
|
|
}
|
|
|
|
c += numChannels;
|
|
}
|
|
}
|
|
|
|
int maxPyramidLevels = 0;
|
|
for(int level=32;level>=0;level--)
|
|
{
|
|
if (min(pyramidLevelSize(std::min(V2i(sourceWidth,sourceHeight),V2i(targetWidth,targetHeight)),level)) >= (2*patchSize+1))
|
|
{
|
|
maxPyramidLevels = level+1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (numPyramidLevels==-1) { numPyramidLevels = maxPyramidLevels; }
|
|
numPyramidLevels = std::min(numPyramidLevels,maxPyramidLevels);
|
|
|
|
std::vector<int> numSearchVoteItersPerLevel(numPyramidLevels);
|
|
std::vector<int> numPatchMatchItersPerLevel(numPyramidLevels);
|
|
std::vector<int> stopThresholdPerLevel(numPyramidLevels);
|
|
for(int i=0;i<numPyramidLevels;i++)
|
|
{
|
|
numSearchVoteItersPerLevel[i] = numSearchVoteIters;
|
|
numPatchMatchItersPerLevel[i] = numPatchMatchIters;
|
|
stopThresholdPerLevel[i] = stopThreshold;
|
|
}
|
|
|
|
std::vector<unsigned char> output(targetWidth*targetHeight*numStyleChannelsTotal);
|
|
|
|
printf("uniformity: %.0f\n",uniformityWeight);
|
|
printf("patchsize: %d\n",patchSize);
|
|
printf("pyramidlevels: %d\n",numPyramidLevels);
|
|
printf("searchvoteiters: %d\n",numSearchVoteIters);
|
|
printf("patchmatchiters: %d\n",numPatchMatchIters);
|
|
printf("stopthreshold: %d\n",stopThreshold);
|
|
printf("backend: %s\n",backendToString(backend).c_str());
|
|
|
|
ebsynthRun(backend,
|
|
numStyleChannelsTotal,
|
|
numGuideChannelsTotal,
|
|
sourceWidth,
|
|
sourceHeight,
|
|
sourceStyle.data(),
|
|
sourceGuides.data(),
|
|
targetWidth,
|
|
targetHeight,
|
|
targetGuides.data(),
|
|
NULL,
|
|
styleWeights.data(),
|
|
guideWeights.data(),
|
|
uniformityWeight,
|
|
patchSize,
|
|
EBSYNTH_VOTEMODE_PLAIN,
|
|
numPyramidLevels,
|
|
numSearchVoteItersPerLevel.data(),
|
|
numPatchMatchItersPerLevel.data(),
|
|
stopThresholdPerLevel.data(),
|
|
NULL,
|
|
output.data());
|
|
|
|
stbi_write_png(outputFileName.c_str(),targetWidth,targetHeight,numStyleChannelsTotal,output.data(),numStyleChannelsTotal*targetWidth);
|
|
|
|
printf("result was written to %s\n",outputFileName.c_str());
|
|
|
|
stbi_image_free(sourceStyleData);
|
|
|
|
for(int i=0;i<numGuides;i++)
|
|
{
|
|
stbi_image_free(guides[i].sourceData);
|
|
stbi_image_free(guides[i].targetData);
|
|
}
|
|
|
|
return 0;
|
|
}
|