diff --git a/neuquant.cpp b/neuquant.cpp new file mode 100644 index 0000000..13285d4 --- /dev/null +++ b/neuquant.cpp @@ -0,0 +1,53 @@ +#include "neuquant.h" + +#include + + + + +/* +#define FILENAME "t.raw" +#define WIDTH (510) +#define HEIGHT (383) +/*/ +#define FILENAME "shorts.raw" +#define WIDTH (128) +#define HEIGHT (64) +//*/ + +#include +#include + +int main() +{ + printf("Loading...\n"); + FILE* f; + f = fopen(FILENAME,"rb"); + u8 texture[WIDTH*HEIGHT*3]; + fread(texture,WIDTH*HEIGHT*3,1,f); + fclose(f); + + printf("Quantizing, Remapping...\n"); + u8 newpal[16][3]; + u8 newimage[WIDTH*HEIGHT]; + NeuQuant_RGB_to_16((u8*)texture,WIDTH,HEIGHT,(u8*)newpal,(u8*)newimage,3); + + // unpalettize original (hack so we can write out another RAW) + for (unsigned int i=0;i<(WIDTH*HEIGHT);i++) + { + texture[i*3+0] = newpal[newimage[i]][0]; + texture[i*3+1] = newpal[newimage[i]][1]; + texture[i*3+2] = newpal[newimage[i]][2]; + } + + // write output (temp, test) + printf("Saving...\n"); + f = fopen("out" FILENAME,"wb"); + fwrite(texture,WIDTH*HEIGHT*3,1,f); + fclose(f); + + return 0; +} + + + diff --git a/neuquant.inl b/neuquant.inl new file mode 100644 index 0000000..3f670a9 --- /dev/null +++ b/neuquant.inl @@ -0,0 +1,772 @@ +#ifndef __neuquant_inl__ +#define __neuquant_inl__ + +#ifndef __neuquant_h__ +# error "neuquant.inl should only be included by neuquant.h" +#endif + + +// ********************************* +// ******* WRAPPER FUNCTIONS ******* +// ********************************* + + +inline bool NeuQuant_RGB_to_16(u8* input, unsigned int width, unsigned int height, u8* palette, u8* remapped_image, unsigned int quality) +{ + Palette<16,3> pal; // our palette + NeuralQuantizer<16,3> net; // our neural net + + // add image data to network + if ( !net.AddImage(input,width,height) ) + { + return false; + } + + // quantize + if ( !net.Run(pal, quality) ) + { + return false; + } + + // remap to palette + pal.ReMap(input,width,height,remapped_image); + + // copy palette data back to user + pal.CopyToMemory(palette); + + // finished + return true; +} + + +inline bool NeuQuant_RGB_to_256(u8* input, unsigned int width, unsigned int height, u8* palette, u8* remapped_image, unsigned int quality) +{ + Palette<256,3> pal; // our palette + NeuralQuantizer<256,3> net; // our neural net + + // add image data to network + if ( !net.AddImage(input,width,height) ) + { + return false; + } + + // quantize + if ( !net.Run(pal, quality) ) + { + return false; + } + + // remap to palette + pal.ReMap(input,width,height,remapped_image); + + // copy palette data back to user + pal.CopyToMemory(palette); + + // finished + return true; +} + + +inline bool NeuQuant_RGBA_to_16(u8* input, unsigned int width, unsigned int height, u8* palette, u8* remapped_image, unsigned int quality) +{ + Palette<16,4> pal; // our palette + NeuralQuantizer<16,4> net; // our neural net + + // add image data to network + if ( !net.AddImage(input,width,height) ) + { + return false; + } + + // quantize + if ( !net.Run(pal, quality) ) + { + return false; + } + + // remap to palette + pal.ReMap(input,width,height,remapped_image); + + // copy palette data back to user + pal.CopyToMemory(palette); + + // finished + return true; +} + + +inline bool NeuQuant_RGBA_to_256(u8* input, unsigned int width, unsigned int height, u8* palette, u8* remapped_image, unsigned int quality) +{ + Palette<256,4> pal; // our palette + NeuralQuantizer<256,4> net; // our neural net + + // add image data to network + if ( !net.AddImage(input,width,height) ) + { + return false; + } + + // quantize + if ( !net.Run(pal, quality) ) + { + return false; + } + + // remap to palette + pal.ReMap(input,width,height,remapped_image); + + // copy palette data back to user + pal.CopyToMemory(palette); + + // finished + return true; +} + + +// +// note that the only channel the algorithm cares about is the green channel, therefore, RGB is the same as BGR +// + +inline bool NeuQuant_BGR_to_16(u8* input, unsigned int width, unsigned int height, u8* palette, u8* remapped_image, unsigned int quality) +{ + return NeuQuant_RGB_to_16(input,width,height,palette,remapped_image, quality); +} + +inline bool NeuQuant_BGR_to_256(u8* input, unsigned int width, unsigned int height, u8* palette, u8* remapped_image, unsigned int quality) +{ + return NeuQuant_RGB_to_256(input,width,height,palette,remapped_image, quality); +} + +inline bool NeuQuant_BGRA_to_16(u8* input, unsigned int width, unsigned int height, u8* palette, u8* remapped_image, unsigned int quality) +{ + return NeuQuant_RGBA_to_16(input,width,height,palette,remapped_image, quality); +} + +inline bool NeuQuant_BGRA_to_256(u8* input, unsigned int width, unsigned int height, u8* palette, u8* remapped_image, unsigned int quality) +{ + return NeuQuant_RGBA_to_256(input,width,height,palette,remapped_image, quality); +} + + + + +// ****************************** +// ******* IMPLEMENTATION ******* +// ****************************** + +template +inline Palette::Palette() +{ + // initialize to zero + for (unsigned int color=0;color +inline Palette::Palette(u8* paldata) +{ + // copy from memory + for (unsigned int color=0;color +inline Palette::Palette(real* paldata) +{ + // copy from real array in memory + for (unsigned int color=0;color +inline Palette::Palette(const Palette& other) +{ + if ( this == &other ) + { + return; + } + + // copy from other + for (unsigned int color=0;color +inline Palette& Palette::operator=(const Palette& rhs) +{ + if ( this != &rhs ) + { + // copy from other + for (unsigned int color=0;color +inline Palette::~Palette() +{ + // empty +} + +template +inline void Palette::CopyToMemory(u8* ptr) +{ + // copy to memory + for (unsigned int color=0;color +inline void Palette::CopyToMemory(real* ptr) +{ + // copy to memory + for (unsigned int color=0;color +inline unsigned int Palette::NumChannels() +{ + return NCHANNELS; +} + +template +inline unsigned int Palette::NumColors() +{ + return NCOLORS; +} + +template +inline unsigned int Palette::NumElements() +{ + return NumChannels() * NumColors(); +} + +template +inline unsigned int Palette::GreenChannelID() +{ + return GREEN_CHANNEL; +} + +template +inline void Palette::Sort() +{ + // simple bubble sort (should be OK, size never likely to exceed 256) + for (unsigned int i=NCOLORS;i>0;i--) + { + for (unsigned int j=1;j 0 ) + { + Swap(j-1,j); + } + } + } +} + +template +inline bool Palette::IsSorted() +{ + for (unsigned int i=1;i 0 ) + { + return false; + } + } + + return true; +} + +template +inline void Palette::ReMap(u8* image, unsigned int width, unsigned int height, u8* newimage) +{ + unsigned int pixels = width*height; + bool sorted = IsSorted(); + + if ( IsSorted() ) + { + // store the smallest distance we've found in a match (this becomes our search neighborhood) + unsigned int d_min = NCOLORS; + + for (unsigned int i=0;i((int)center - (int)d_min,0,NCOLORS-1); + int maxsearch = Clamp((int)center + (int)d_min,0,NCOLORS-1); + + // perform search + newimage[i] = FindClosest(&(image[i*NCHANNELS]),minsearch,maxsearch); + + // store distance + unsigned int newdist = PerceptualDistance(&(image[i*NCHANNELS]),newimage[i]); + if ( newdist < d_min ) + { + d_min = Clamp(newdist,NCOLORS/4,NCOLORS); + } + } + } + else + { + // brute force + for (unsigned int i=0;i +inline unsigned int Palette::FindClosestGreen(u8* color) +{ + // binary search + int low = 0; + int high = NCOLORS-1; + int middle; + + while ( low <= high ) + { + // find middle for our comparison + middle = ( low + high ) / 2; + + // look for a match or which direction to continue search + if ( color[GREEN_CHANNEL] == data[middle][GREEN_CHANNEL] ) + { + // matched + return (unsigned int)middle; + } + else if ( color[GREEN_CHANNEL] < data[middle][GREEN_CHANNEL] ) + { + // search lower + high = middle - 1; + } + else + { + // search higher + low = middle + 1; + } + } + + // not found, return closest + return (unsigned int)Clamp(low,0,NCOLORS-1); +} + +template +inline unsigned int Palette::FindClosest(u8* color, unsigned int minentry, unsigned int maxentry) +{ + // store our best match + unsigned int mindist = 1000000000; + unsigned int minindex = 0; + + // search all colors for match + for (unsigned int i=minentry;i<=maxentry;i++) + { + unsigned int dist = PerceptualDistance(color,i); + if ( dist < mindist ) + { + mindist = dist; + minindex = i; + } + } + + // return best match + return minindex; +} + + +template +inline unsigned int Palette::PerceptualDistance(u8* color, unsigned int entry) +{ + unsigned int d = 0; + for (unsigned int i=0;i +inline int Palette::Compare(unsigned int a, unsigned int b) +{ + if ( data[a][GREEN_CHANNEL] < data[b][GREEN_CHANNEL] ) + { + return -1; + } + else if ( data[a][GREEN_CHANNEL] > data[b][GREEN_CHANNEL]) + { + return 1; + } + else + { + // green channels equal, test all others + for (unsigned int i=0;i data[b][i] ) + { + return 1; + } + } + } + } + + // exactly equal + return 0; +} + +template +inline void Palette::Swap(unsigned int a, unsigned int b) +{ + for (unsigned int i=0;i +inline NeuralQuantizer::NeuralQuantizer(): + image(0), + image_size(0) +{ + // initialize network + for (unsigned int i=0;i +inline NeuralQuantizer::~NeuralQuantizer() +{ + delete[] image; +} + +template +inline bool NeuralQuantizer::AddImage(u8* input, unsigned int width, unsigned int height) +{ + // remember image sizes + unsigned int this_image_size = width*height*NCHANNELS; + unsigned int prev_image_size = image_size; + unsigned int new_image_size = prev_image_size + this_image_size; + + // allocate + real* tmpimage = new real[new_image_size]; + if ( !tmpimage ) + { + return false; + } + + // convert original image bytes to reals + for (unsigned int i=0;i +inline bool NeuralQuantizer::Run(Palette& palette, unsigned int quality) +{ + // make sure we have an image + if ( !image || !image_size ) + { + return false; + } + + // clamp quality + quality = Clamp(quality,1,10); + + // train network + unsigned int total_pels = (image_size/NCHANNELS); + unsigned int pels_to_sample = total_pels / quality; + unsigned int pels_per_cycle = pels_to_sample / 100; + unsigned int next_pel = 0; + unsigned int prime = ChoosePrime(); + unsigned int total_cycles = 101; + + for (unsigned int cycle=0;cycle= total_pels ) + { + next_pel -= total_pels; + } + Present(&(image[next_pel*NCHANNELS]),alpha,radius); + } + } + + // write palette + for (unsigned int i=0;i +inline NeuralQuantizer::Neuron::Neuron() +{ + bias = real(0); + frequency = real(1) / real(NCOLORS); +} + +template +inline void NeuralQuantizer::Neuron::SetAllChannels(real value) +{ + for (unsigned int i=0;i +inline real NeuralQuantizer::Neuron::DesienoDistance(real* color) const +{ + real dist = real(0); + for (unsigned int i=0;i +inline void NeuralQuantizer::Neuron::UpdateLoserFrequencyAndBias(real* color, real beta, real gamma) +{ + // update frequency of non winner + frequency = frequency - beta*frequency; + + // update bias of non winner + bias = bias + gamma*beta*frequency; +} + +template +inline void NeuralQuantizer::Neuron::UpdateWinnerFrequencyAndBias(real* color, real beta, real gamma) +{ + // update frequency of winner + frequency = frequency - beta*frequency + beta; + + // update bias of winner + bias = bias + gamma*beta*frequency - gamma*beta; +} + +template +inline void NeuralQuantizer::Neuron::UpdateWeight(real* color, real alpha, real rho) +{ + for (unsigned int i=0;i +inline unsigned int NeuralQuantizer::FindWinningNeuron(real* color) +{ + // find winning neuron + real windist = real(1000000000000); + unsigned int winindex = NCOLORS/2; + + for (unsigned int i=0;i +inline void NeuralQuantizer::Present(real* color, real alpha, unsigned int radius) +{ + // search for winning neuron + int winindex = (int)FindWinningNeuron(color); + + // update frequency & bias + for (unsigned int i=0;i(winindex-(int)radius,0,NCOLORS-1); + int max_neighbor = Clamp(winindex+(int)radius,0,NCOLORS-1); + + // update neighbors + for (int i=min_neighbor;i<=max_neighbor;i++) + { + // update neuron + neurons[i].UpdateWeight(color,alpha,Rho(i,winindex,radius)); + } +} + +template +inline real NeuralQuantizer::Rho(int i, int j, unsigned int radius) +{ + if ( i == j ) + { + return real(1); + } + else + { + real tmp = real(Abs(j - i)) / real(radius); + return real(1) - (tmp * tmp); + } +} + +// choose prime near 500 which isn't a multiple of width*height +template +inline unsigned int NeuralQuantizer::ChoosePrime() +{ + if ( image_size % 487 ) + { + return 487; + } + else if ( image_size % 491 ) + { + return 491; + } + else if ( image_size % 499 ) + { + return 499; + } + else if ( image_size % 503 ) + { + return 503; + } + else + { + return 487; // TODO: assert? ever possible? or nearly completely unlikely??? prime stuff... + } +} + + + +#endif // __neuquant_inl__ +