/*
======================================================================
image.c

Ernie Wright  14 Apr 05
MSVC 4.0

Functions for creating and altering images.

An Image is a 2D array of floating-point red, green, blue, and alpha
components, along with a title, creation time, number, and black and
white points.  A Displayable is an Image with RGB and alpha DIBs
derived from it.  The DIBs can be blitted directly to the screen.

There are functions here for creating Images, DIBs (1-bit, 8-bit
grayscale, and 24-bit color), and Displayables; for copying and
pasting Image rectangles; for drawing the hot pixel and safe outline
overlays (implemented as 1-bit masking bitmaps); for realizing a
Displayable (deriving DIBs from an Image); and for reading the value
of a pixel.
====================================================================== */

#include "qv.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>


void freeImage( Image *image )
{
   if ( image ) {
      if ( image->r ) free( image->r );
      if ( image->g ) free( image->g );
      if ( image->b ) free( image->b );
      if ( image->a ) free( image->a );
      free( image );
   }
}


Image *createImage( int width, int height, int index, int rendertime )
{
   Image *image;

   image = calloc( 1, sizeof( Image ));
   if ( !image ) return NULL;

   image->width = width;
   image->height = height;
   image->index = index;
   image->created = time( NULL );
   image->rendertime = rendertime;
   image->black = 0.0f;
   image->white = 1.0f;
   image->r = malloc( width * height * 4 );
   image->g = malloc( width * height * 4 );
   image->b = malloc( width * height * 4 );
   image->a = malloc( width * height * 4 );

   if ( !image->r || !image->g || !image->b || !image->a ) {
      freeImage( image );
      return NULL;
   }
   return image;
}


void writeImageRow( Image *image, int y,
   const float *r, const float *g, const float *b, const float *a )
{
   int offset = y * image->width;

   memcpy( image->r + offset, r, image->width * 4 );
   memcpy( image->g + offset, g, image->width * 4 );
   memcpy( image->b + offset, b, image->width * 4 );
   memcpy( image->a + offset, a, image->width * 4 );
}


/* destination rectangle must be entirely within the source */

Image *copyImageRect( Image *source, int x0, int y0, int w, int h )
{
   Image *dest;
   int y, offset;

   dest = createImage( w, h, 0, 0 );
   if ( !dest ) return NULL;
   dest->black = source->black;
   dest->white = source->white;

   for ( y = 0; y < h; y++ ) {
      offset = ( y + y0 ) * source->width + x0;
      memcpy( dest->r + y * w, source->r + offset, w * 4 );
      memcpy( dest->g + y * w, source->g + offset, w * 4 );
      memcpy( dest->b + y * w, source->b + offset, w * 4 );
      memcpy( dest->a + y * w, source->a + offset, w * 4 );
   }
   return dest;
}


/* source rectangle must be entirely within the destination */

void pasteImageRect( Image *source, Image *dest, int x0, int y0 )
{
   int y, offset;

   for ( y = 0; y < source->height; y++ ) {
      offset = ( y + y0 ) * dest->width + x0;
      memcpy( dest->r + offset, source->r + y * source->width, source->width * 4 );
      memcpy( dest->g + offset, source->g + y * source->width, source->width * 4 );
      memcpy( dest->b + offset, source->b + y * source->width, source->width * 4 );
      memcpy( dest->a + offset, source->a + y * source->width, source->width * 4 );
   }
}


void freeBitmap1( Bitmap1 *bitmap )
{
   if ( bitmap ) {
      if ( bitmap->bits ) free( bitmap->bits );
      free( bitmap );
   }
}


Bitmap1 *createBitmap1( int width, int height )
{
   Bitmap1 *bitmap;

   bitmap = calloc( 1, sizeof( Bitmap1 ));
   if ( !bitmap ) return NULL;

   bitmap->w = width;
   bitmap->h = height;
   bitmap->rowbytes = (( width + 31 ) >> 5 ) << 2;
   bitmap->bits = malloc( bitmap->rowbytes * height );
   if ( !bitmap->bits ) {
      free( bitmap );
      return NULL;
   }

   bitmap->dib.biSize      = sizeof( BITMAPINFOHEADER );
   bitmap->dib.biWidth     = width;
   bitmap->dib.biHeight    = height;
   bitmap->dib.biPlanes    = 1;
   bitmap->dib.biBitCount  = 1;
   bitmap->dib.biSizeImage = bitmap->rowbytes * height;

   bitmap->pal[ 0 ].rgbRed =
   bitmap->pal[ 0 ].rgbGreen =
   bitmap->pal[ 0 ].rgbBlue = 255;
   bitmap->pal[ 1 ].rgbRed =
   bitmap->pal[ 1 ].rgbGreen =
   bitmap->pal[ 1 ].rgbBlue = 0;

   return bitmap;
}


void freeBitmap8( Bitmap8 *bitmap )
{
   if ( bitmap ) {
      if ( bitmap->bits ) free( bitmap->bits );
      free( bitmap );
   }
}


Bitmap8 *createBitmap8( int width, int height )
{
   Bitmap8 *bitmap;
   int i;

   bitmap = calloc( 1, sizeof( Bitmap8 ));
   if ( !bitmap ) return NULL;

   bitmap->w = width;
   bitmap->h = height;
   bitmap->rowbytes = (( width + 3 ) >> 2 ) << 2;
   bitmap->bits = malloc( bitmap->rowbytes * height );
   if ( !bitmap->bits ) {
      free( bitmap );
      return NULL;
   }

   bitmap->dib.biSize      = sizeof( BITMAPINFOHEADER );
   bitmap->dib.biWidth     = width;
   bitmap->dib.biHeight    = height;
   bitmap->dib.biPlanes    = 1;
   bitmap->dib.biBitCount  = 8;
   bitmap->dib.biSizeImage = bitmap->rowbytes * height;

   // grayscale palette
   for ( i = 0; i < 256; i++ )
      bitmap->pal[ i ].rgbRed =
      bitmap->pal[ i ].rgbGreen =
      bitmap->pal[ i ].rgbBlue = i;

   return bitmap;
}


void freeBitmap24( Bitmap24 *bitmap )
{
   if ( bitmap ) {
      if ( bitmap->bits ) free( bitmap->bits );
      free( bitmap );
   }
}


Bitmap24 *createBitmap24( int width, int height )
{
   Bitmap24 *bitmap;

   bitmap = calloc( 1, sizeof( Bitmap24 ));
   if ( !bitmap ) return NULL;

   bitmap->w = width;
   bitmap->h = height;
   bitmap->rowbytes = (( width * 3 + 3 ) >> 2 ) << 2;
   bitmap->bits = malloc( bitmap->rowbytes * height );
   if ( !bitmap->bits ) {
      free( bitmap );
      return NULL;
   }

   bitmap->dib.biSize      = sizeof( BITMAPINFOHEADER );
   bitmap->dib.biWidth     = width;
   bitmap->dib.biHeight    = height;
   bitmap->dib.biPlanes    = 1;
   bitmap->dib.biBitCount  = 24;
   bitmap->dib.biSizeImage = bitmap->rowbytes * height;

   return bitmap;
}


Displayable *freeDisplayable( Displayable *disp )
{
   if ( disp ) {
      freeImage( disp->image );
      freeImage( disp->undo.image );
      freeBitmap24( disp->rgb );
      freeBitmap8( disp->alpha );
      freeBitmap1( disp->hot );
      freeBitmap1( disp->safe );
      free( disp );
   }
   return NULL;
}


Displayable *createDisplayable( Image *image )
{
   Displayable *disp;

   disp = calloc( 1, sizeof( Displayable ));
   if ( !disp ) return NULL;

   disp->hotInfo.encoding = HOT_NTSC;
   disp->hotInfo.pedestal = 7.5f;
   disp->hotInfo.limComposite = 100.0f;
   disp->hotInfo.limChroma = 50.0f;
   disp->image = image;
   sprintf( disp->title, "Untitled%d", image->index );

   disp->rgb = createBitmap24( image->width, image->height );
   if ( !disp->rgb )
      return freeDisplayable( disp );

   disp->alpha = createBitmap8( image->width, image->height );
   if ( !disp->alpha )
      return freeDisplayable( disp );

   disp->hot = createBitmap1( image->width, image->height );
   if ( !disp->hot )
      return freeDisplayable( disp );

   disp->safe = createBitmap1( image->width, image->height );
   if ( !disp->safe )
      return freeDisplayable( disp );

   return disp;
}


static unsigned char channelRGB( float level, float black, float range )
{
   float fc;
   int c;

   fc = 255.0f * ( level - black ) / range;
   if ( fc > 255.0f )
      c = 255;
   else if ( fc < 0.0f )
      c = 0;
   else
      c = ( int ) fc;
   return ( unsigned char ) c;
}


static unsigned char channelAlpha( float level )
{
   float fc;
   int c;

   fc = 255.0f * level;
   if ( fc > 255.0f )
      c = 255;
   else if ( fc < 0.0f )
      c = 0;
   else
      c = ( int ) fc;
   return ( unsigned char ) c;
}


void drawRGB( Image *image, Bitmap24 *bitmap )
{
   float range, black, *r, *g, *b;
   int x, y, w, h;
   unsigned char *bits;

   r = image->r;
   g = image->g;
   b = image->b;
   w = image->width;
   h = image->height;
   black = image->black;
   range = image->white - black;

   for ( y = 0; y < h; y++ ) {
      bits = bitmap->bits + ( h - ( y + 1 )) * bitmap->rowbytes;
      for ( x = 0; x < w; x++ ) {
         *bits++ = channelRGB( *b++, black, range );
         *bits++ = channelRGB( *g++, black, range );
         *bits++ = channelRGB( *r++, black, range );
      }
   }
}


void drawAlpha( Image *image, Bitmap8 *bitmap )
{
   float *a;
   int x, y, w, h;
   unsigned char *bits;

   a = image->a;
   w = image->width;
   h = image->height;

   for ( y = 0; y < h; y++ ) {
      bits = bitmap->bits + ( h - ( y + 1 )) * bitmap->rowbytes;
      for ( x = 0; x < w; x++ ) {
         *bits++ = channelAlpha( *a++ );
      }
   }
}


void drawRGBARect( Displayable *disp, int x0, int y0, int w, int h )
{
   float range, black, *r, *g, *b, *a;
   int x, y, offset;
   unsigned char *bits, *abits;

   black = disp->image->black;
   range = disp->image->white - black;

   for ( y = y0; y < y0 + h; y++ ) {
      offset = disp->image->height - ( y + 1 );
      bits = disp->rgb->bits + offset * disp->rgb->rowbytes + x0 * 3;
      abits = disp->alpha->bits + offset * disp->alpha->rowbytes + x0;
      offset = y * disp->image->width + x0;
      r = disp->image->r + offset;
      g = disp->image->g + offset;
      b = disp->image->b + offset;
      a = disp->image->a + offset;

      for ( x = x0; x < x0 + w; x++ ) {
         *bits++ = channelRGB( *b++, black, range );
         *bits++ = channelRGB( *g++, black, range );
         *bits++ = channelRGB( *r++, black, range );
         *abits++ = channelAlpha( *a++ );
      }
   }
}


static HBRUSH createDottedBrush( void )
{
   HBRUSH hbr;
   BITMAPINFO *bmi;
   Bitmap1 *bitmap;
   unsigned long *p;
   int hdrsz, i;


   hdrsz = sizeof( BITMAPINFOHEADER ) + 8;
   bmi = malloc( hdrsz + 32 );
   bitmap = createBitmap1( 8, 8 );
   memcpy( bmi, &bitmap->dib, hdrsz );
   freeBitmap1( bitmap );

   p = ( unsigned long * )(( unsigned char * ) bmi + hdrsz );
   for ( i = 0; i < 4; i++ ) {
      *p++ = 0x55555555;
      *p++ = 0xAAAAAAAA;
   }
   hbr = CreateDIBPatternBrushPt( bmi, DIB_RGB_COLORS );
   free( bmi );
   return hbr;
}


void drawSafe( Displayable *disp )
{
   HDC hdc;
   HBITMAP hbm;
   HBRUSH hbr;
   HPEN hpen;
   LOGBRUSH lbr = { BS_SOLID, RGB( 255, 255, 255 ), 0 };
   int w, h, x1, y1, x2, y2, x3, y3, x4, y4, x5, y5, x6, y6, d1, d2, d3;

   w = disp->safe->dib.biWidth;
   h = disp->safe->dib.biHeight;

   if ( disp->safeInfo.types & SAFE_VIDEO ) {
      x1 = 5 * w / 100;
      y1 = 5 * h / 100;
      x2 = w - x1;
      y2 = h - y1;
      d1 = ( y2 - y1 ) / 2;
      x3 = x1 + x1;
      y3 = y1 + y1;
      x4 = x2 - x1;
      y4 = y2 - y1;
      d2  = ( y4 - y3 ) / 2;
   }

   if ( disp->safeInfo.types & SAFE_BOX ) {
      if (( float ) w / h < disp->safeInfo.aspect ) {
         /* margin applies to x axis */

         x5 = ( int )( disp->safeInfo.margin * w );
         x6 = w - x5;
         d3 = ( int )(( x6 - x5 + 1 ) / disp->safeInfo.aspect );
         y5 = ( h - d3 ) / 2;
         y6 = y5 + d3;
      }
      else {
         /* margin applies to y axis */

         y5 = ( int )( disp->safeInfo.margin * h );
         y6 = h - y5;
         d3 = ( int )(( y6 - y5 + 1 ) * disp->safeInfo.aspect );
         x5 = ( w - d3 ) / 2;
         x6 = x5 + d3;
      }
   }

   if ( hdc = CreateCompatibleDC( NULL )) {
      if ( hbm = CreateCompatibleBitmap( hdc, w, h )) {

         hbm = SelectObject( hdc, hbm );
         hpen = ExtCreatePen( PS_COSMETIC | PS_ALTERNATE, 1, &lbr, 0, NULL );
         if ( !hpen )
            hpen = CreatePen( PS_SOLID, 0, RGB( 255, 255, 255 ));
         hpen = SelectObject( hdc, hpen );
         hbr = SelectObject( hdc, GetStockObject( HOLLOW_BRUSH ));
         SetBkColor( hdc, RGB( 0, 0, 0 ));

         PatBlt( hdc, 0, 0, w, h, BLACKNESS );

         if ( disp->safeInfo.style == SAFE_SOLID ) {
            if ( disp->safeInfo.types & SAFE_VIDEO ) {
               HBRUSH hbrh = createDottedBrush();
               SelectObject( hdc, hbrh );
               RoundRect( hdc, x1, y1, x2, y2, d1, d1 );
               SelectObject( hdc, GetStockObject( WHITE_BRUSH ));
               DeleteObject( hbrh );
               RoundRect( hdc, x3, y3, x4, y4, d2, d2 );
            }
            if ( disp->safeInfo.types & SAFE_BOX ) {
               SelectObject( hdc, GetStockObject( WHITE_BRUSH ));
               Rectangle( hdc, x5, y5, x6, y6 );
            }
            PatBlt( hdc, 0, 0, w, h, DSTINVERT );
            SelectObject( hdc, GetStockObject( HOLLOW_BRUSH ));
         }

         if ( disp->safeInfo.types & SAFE_VIDEO ) {
            RoundRect( hdc, x1, y1, x2, y2, d1, d1 );
            RoundRect( hdc, x3, y3, x4, y4, d2, d2 );
         }

         if ( disp->safeInfo.types & SAFE_BOX ) {
            Rectangle( hdc, x5, y5, x6, y6 );
         }

         hbm = SelectObject( hdc, hbm );
         GetDIBits( hdc, hbm, 0, h, disp->safe->bits,
            ( BITMAPINFO * ) &disp->safe->dib, DIB_RGB_COLORS );

         DeleteObject( hbm );
         SelectObject( hdc, hbr );
         hpen = SelectObject( hdc, hpen );
         DeleteObject( hpen );
      }
      DeleteDC( hdc );
   }
}


void drawHot( Displayable *disp )
{
   unsigned char *p, *q;
   int xb, y, b, hbit;

   init_hot( disp->hotInfo.encoding, disp->hotInfo.pedestal,
      disp->hotInfo.limComposite, disp->hotInfo.limChroma );

   for ( y = 0; y < disp->rgb->dib.biHeight; y++ ) {
      p = disp->rgb->bits + disp->rgb->rowbytes * y;
      q = disp->hot->bits + disp->hot->rowbytes * y;
      memset( q, 0, disp->hot->rowbytes );

      for ( xb = 0; xb < disp->hot->rowbytes; xb++ ) {
         for ( b = 7; b >= 0; b-- ) {
            hbit = hot( p[ 2 ], p[ 1 ], p[ 0 ] );
            hbit <<= b;
            q[ xb ] |= hbit;
            p += 3;
         }
         q[ xb ] = ~q[ xb ];
      }
   }
}


void drawHotRect( Displayable *disp, int x0, int y0, int w, int h )
{
   unsigned char *p, *q;
   int xb, y, b, hbit, rowbytes, byte0, offset;

   init_hot( disp->hotInfo.encoding, disp->hotInfo.pedestal,
      disp->hotInfo.limComposite, disp->hotInfo.limChroma );

   byte0 = x0 / 8;
   xb = x0 - byte0 * 8;
   w += xb;
   x0 -= xb;
   rowbytes = (( w + 31 ) >> 5 ) << 2;

   for ( y = y0; y < y0 + h; y++ ) {
      offset = disp->image->height - ( y + 1 );
      p = disp->rgb->bits + offset * disp->rgb->rowbytes + x0 * 3;
      q = disp->hot->bits + offset * disp->hot->rowbytes + byte0;
      memset( q, 0, rowbytes );

      for ( xb = 0; xb < rowbytes; xb++ ) {
         for ( b = 7; b >= 0; b-- ) {
            hbit = hot( p[ 2 ], p[ 1 ], p[ 0 ] );
            hbit <<= b;
            q[ xb ] |= hbit;
            p += 3;
         }
         q[ xb ] = ~q[ xb ];
      }
   }
}


void realizeDisplayable( Displayable *disp )
{
   drawRGB( disp->image, disp->rgb );
   drawAlpha( disp->image, disp->alpha );
   drawHot( disp );
   drawSafe( disp );
}


void realizeDisplayableRect( Displayable *disp, int x0, int y0, int w, int h )
{
   drawRGBARect( disp, x0, y0, w, h );
   drawHotRect( disp, x0, y0, w, h );
}


void realizeDisplayableParts( Displayable *disp, int parts )
{
   if ( parts & CHANGED_LEVELS )
      drawRGB( disp->image, disp->rgb );
   if (( parts & CHANGED_HOT ) || ( parts & CHANGED_LEVELS ))
      drawHot( disp );
   if ( parts & CHANGED_SAFE )
      drawSafe( disp );
}


void getPixelByte( Displayable *disp, int x, int y, int channel,
   unsigned char pixel[] )
{
   int dy;
   unsigned char *p;

   dy = disp->image->height - y - 1;
   if ( channel == CH_ALPHA )
      p = disp->alpha->bits + dy * disp->alpha->rowbytes + x;
   else
      p = disp->rgb->bits + dy * disp->rgb->rowbytes + 3 * x;

   switch ( channel ) {
      case CH_IMAGE:  pixel[ 2 ] = p[ 0 ];
                      pixel[ 1 ] = p[ 1 ];
                      pixel[ 0 ] = p[ 2 ];   break;
      case CH_ALPHA:
      case CH_BLUE:   pixel[ 0 ] = p[ 0 ];   break;
      case CH_RED:    pixel[ 0 ] = p[ 2 ];   break;
      case CH_GREEN:  pixel[ 0 ] = p[ 1 ];   break;
   }
}


void getPixelFloat( Displayable *disp, int x, int y, int channel,
   float pixel[] )
{
   int offset;

   offset = y * disp->image->width + x;
   switch ( channel ) {
      case CH_IMAGE:  pixel[ 0 ] = disp->image->r[ offset ];
                      pixel[ 1 ] = disp->image->g[ offset ];
                      pixel[ 2 ] = disp->image->b[ offset ];  break;
      case CH_ALPHA:  pixel[ 0 ] = disp->image->a[ offset ];  break;
      case CH_RED:    pixel[ 0 ] = disp->image->r[ offset ];  break;
      case CH_GREEN:  pixel[ 0 ] = disp->image->g[ offset ];  break;
      case CH_BLUE:   pixel[ 0 ] = disp->image->b[ offset ];  break;
   }
}
