//==============================================
//  copyright            : (C) 2003-2005 by Will Stokes
//==============================================
//  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.
//==============================================

//Systemwide includes
#include <qimage.h>
#include <qstring.h>
#include <qapplication.h>
#include <math.h>

//Projectwide includes
#include "painting.h"
#include "manipulationOptions.h"
#include "../../gui/statusWidget.h"

//----------------------------------------------
// Inputs:
// -------
// QString filename - location of original image on disk
// StatusWidget* status - widget for making progress visible to user
//
// Outputs:
// --------
// QImage* returned - constructed image
//
// Description:
// ------------
// This method constructs an oil painting version of
// the image by replacing each pixel with an average of the
// original pixel color and the most common pixel color within a local radius.
//
// A histogram of color values (which fall in the 0-255 range) 
// is constructed at each pixel for all pixels without a given 
// radius. The most commonly occuring red, green, and blue color 
// values are found and used in combination with the current color 
// to produce the oil effect. This is done because in oil painting 
// (and water colors) color bleeds out from a given area across the
// canvass. By averaging with the most common color in a given 
// neighborhood the larger blobs spread and the higher frequency 
// information (details) fade into the background.
//
// TODO:
// The local area idealy would be circular, but currently is square.
//
// TODO:
// Experiment adaptively adjusting the oil radius using local image contrast measure?
//
// TODO:
// Come up with method for avoiding strange color shifts near object boundaries.
//----------------------------------------------

//==============================================
struct Triplet
{ int r,g,b; };
//----------------------------------------------
struct Histogram
{
  //histogram data
  Triplet values[256]; 
  
  //index of highest count for each component
  Triplet highestCountIndex;
};
//----------------------------------------------
Histogram histogram;
//----------------------------------------------
void resetHistogram()
{
  static int i;
  for(i=0;i<256;i++)
  {
    histogram.values[i].r = 0;
    histogram.values[i].g = 0;
    histogram.values[i].b = 0;
  }
  histogram.highestCountIndex.r = 0;
  histogram.highestCountIndex.g = 0;
  histogram.highestCountIndex.b = 0;
}
//----------------------------------------------
void findHighestCounts()
{
  static int i;
  for(i = 1; i<256; i++)
  {    
    if( histogram.values[i].r > histogram.values[ histogram.highestCountIndex.r ].r )    
    { histogram.highestCountIndex.r = i; }

    if( histogram.values[i].g > histogram.values[ histogram.highestCountIndex.g ].g )    
    { histogram.highestCountIndex.g = i; }
  
    if( histogram.values[i].b > histogram.values[ histogram.highestCountIndex.b ].b )    
    { histogram.highestCountIndex.b = i; }                    
  }
}
//----------------------------------------------
QImage* oilPaintingEffect( QString filename, ManipulationOptions* options )
{
  //load original image  
  QImage originalImage( filename );

  //convert to 32-bit depth if necessary
  if( originalImage.depth() < 32 ) { originalImage = originalImage.convertDepth( 32, Qt::AutoColor ); }

//determine if busy indicators will be used
  bool useBusyIndicators = false;
  StatusWidget* status = NULL;
  if( options != NULL && options->getStatus() != NULL )
  {
    useBusyIndicators = true;
    status = options->getStatus(); 
  }
  
  //setup progress bar
  if(useBusyIndicators)
  {
    QString statusMessage = qApp->translate( "oilPaintingEffect", "Applying Oil Painting Effect:" );
    status->showProgressBar( statusMessage, 100 );
    qApp->processEvents();  
  }

  //update progress bar for every 1% of completion
  const int updateIncrement = (int) ( 0.01 * originalImage.width() * originalImage.height() );
  int newProgress = 0; 
  
  //construct edited image
  QImage* editedImage = new QImage( filename );
  
  //convert to 32-bit depth if necessary
  if( editedImage->depth() < 32 )
  {
    QImage* tmp = editedImage;
    editedImage = new QImage( tmp->convertDepth( 32, Qt::AutoColor ) );
    delete tmp; tmp=NULL;
  }
  
  //compute the radius using image resolution
  double minDimen = (double) QMIN( editedImage->width(), editedImage->height() );  
  const int RADIUS = (int) QMAX( 2, (sqrt(minDimen)/4) );
  
  //iterate over image
  int originalImageX, originalImageY;
  int editedImageX, editedImageY;
  int clampedX, clampedY;
  int trailingEdgeY, leadingEdgeY;
  
  QRgb* rgb;
  uchar* scanLine;  
  uchar* trailingScanLine;
  uchar* leadingScanLine;

  //iterate over columns    
  for( editedImageX=0; editedImageX < editedImage->width(); editedImageX++)
  {
    //------------------
    //reset histogram object
    resetHistogram();
    //------------------
    //fill histogram with data that would have results from Y=-1
    for(originalImageY =  0 - 1 - RADIUS; 
        originalImageY <= 0 - 1 + RADIUS; 
        originalImageY++)
    {
      clampedY = QMAX( QMIN( originalImageY, originalImage.height() - 1 ), 0 );        
      scanLine = originalImage.scanLine( clampedY );
      
      for(originalImageX =  editedImageX - RADIUS;
          originalImageX <= editedImageX + RADIUS; 
          originalImageX++)
      {
        clampedX = QMAX( QMIN( originalImageX, originalImage.width() - 1 ), 0 );
        
        //get rgb value
        rgb = ((QRgb*)scanLine+clampedX);
        
        //update counts for this r/g/b value
        histogram.values[ qRed(*rgb)   ].r++;
        histogram.values[ qGreen(*rgb) ].g++;
        histogram.values[ qBlue(*rgb)   ].b++;        
      } //originalX
    } //originalY
    //------------------

    //now iterate over rows by simply removing trailing edge data and adding leading edge data
    for( editedImageY=0; editedImageY < editedImage->height(); editedImageY++)
    {             
      trailingEdgeY = QMAX( QMIN( editedImageY-1-RADIUS, originalImage.height() - 1 ), 0 );        
      leadingEdgeY  = QMAX( QMIN( editedImageY+RADIUS, originalImage.height() - 1 ), 0 );        
    
      trailingScanLine = originalImage.scanLine( trailingEdgeY );
      leadingScanLine  = originalImage.scanLine( leadingEdgeY );
  
      for(originalImageX =  editedImageX - RADIUS;
          originalImageX <= editedImageX + RADIUS; 
          originalImageX++)
      {
        clampedX = QMAX( QMIN( originalImageX, originalImage.width() - 1 ), 0 );
       
        //remove trail edge data
        rgb = ((QRgb*)trailingScanLine+clampedX);
        histogram.values[ qRed(*rgb)   ].r--;
        histogram.values[ qGreen(*rgb) ].g--;
        histogram.values[ qBlue(*rgb)   ].b--;        
        
        //add leading edge data
        rgb = ((QRgb*)leadingScanLine+clampedX);
        histogram.values[ qRed(*rgb)   ].r++;
        histogram.values[ qGreen(*rgb) ].g++;
        histogram.values[ qBlue(*rgb)   ].b++;        
      } //originalX

      //find highest color counts
      findHighestCounts();
      
      //replace each color channel value with average of 
      //current value and most occuring value within neighborhood
      scanLine = editedImage->scanLine( editedImageY );
      rgb = ((QRgb*)scanLine+editedImageX);                                         
      *rgb = qRgb( (qRed(*rgb)   + histogram.highestCountIndex.r) / 2,
                   (qGreen(*rgb) + histogram.highestCountIndex.g) / 2,
                   (qBlue(*rgb)  + histogram.highestCountIndex.b) / 2 );                            


      //update status bar if significant progress has been made since last update
      if(useBusyIndicators)
      {
        newProgress++;
        if(newProgress >= updateIncrement)
        {
          newProgress = 0;
          status->incrementProgress();
          qApp->processEvents();  
        }
      }
      
    } //editedImageX
  } //editedImageY
  
  //return pointer to edited image
  return editedImage;  
}
//==============================================
