#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__