//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
// Contact: mcseemagg@yahoo.com
//----------------------------------------------------------------------------
//
// class pixel_map
//
//----------------------------------------------------------------------------

#include <cstring>
#include <cstdio>
#include "platform/win32/agg_win32_bmp.h"
#include "agg_basics.h"

namespace agg
{

    //------------------------------------------------------------------------
    pixel_map::~pixel_map()
    {
        destroy();
    }


    //------------------------------------------------------------------------
    pixel_map::pixel_map() :
        m_bmp(0),
        m_buf(0),
        m_bpp(0),
        m_is_internal(false),
        m_img_size(0),
        m_full_size(0)

    {
    }


    //------------------------------------------------------------------------
    void pixel_map::destroy()
    {
        if(m_bmp && m_is_internal) delete [] (unsigned char*)m_bmp;
        m_bmp  = 0;
        m_is_internal = false;
        m_buf = 0;
    }


    //------------------------------------------------------------------------
    void pixel_map::create(unsigned width, 
                           unsigned height, 
                           org_e    org,
                           unsigned clear_val)
    {
        destroy();
        if(width == 0)  width = 1;
        if(height == 0) height = 1;
        m_bpp = org;
        create_from_bmp(create_bitmap_info(width, height, m_bpp));
        create_gray_scale_palette(m_bmp);
        m_is_internal = true;
        if(clear_val <= 255)
        {
            std::memset(m_buf, clear_val, m_img_size);
        }
    }


    //------------------------------------------------------------------------
    HBITMAP pixel_map::create_dib_section(HDC h_dc,
                                          unsigned width, 
                                          unsigned height, 
                                          org_e    org,
                                          unsigned clear_val)
    {
        destroy();
        if(width == 0)  width = 1;
        if(height == 0) height = 1;
        m_bpp = org;
        HBITMAP h_bitmap = create_dib_section_from_args(h_dc, width, height, m_bpp);
        create_gray_scale_palette(m_bmp);
        m_is_internal = true;
        if(clear_val <= 255)
        {
            std::memset(m_buf, clear_val, m_img_size);
        }
        return h_bitmap;
    }



    //------------------------------------------------------------------------
    void pixel_map::clear(unsigned clear_val)
    {
        if(m_buf) std::memset(m_buf, clear_val, m_img_size);
    }


    //------------------------------------------------------------------------
    void pixel_map::attach_to_bmp(BITMAPINFO *bmp)
    {
        if(bmp)
        {
            destroy();
            create_from_bmp(bmp);
            m_is_internal = false;
        }
    }



    //static
    //------------------------------------------------------------------------
    unsigned pixel_map::calc_full_size(BITMAPINFO *bmp)
    {
        if(bmp == 0) return 0;

        return sizeof(BITMAPINFOHEADER) +
               sizeof(RGBQUAD) * calc_palette_size(bmp) +
               bmp->bmiHeader.biSizeImage;
    }

    //static
    //------------------------------------------------------------------------
    unsigned pixel_map::calc_header_size(BITMAPINFO *bmp)
    {
        if(bmp == 0) return 0;
        return sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * calc_palette_size(bmp);
    }


    //static
    //------------------------------------------------------------------------
    unsigned  pixel_map::calc_palette_size(unsigned  clr_used, unsigned bits_per_pixel)
    {
        int palette_size = 0;

        if(bits_per_pixel <= 8)
        {
            palette_size = clr_used;
            if(palette_size == 0)
            {
                palette_size = 1 << bits_per_pixel;
            }
        }
        return palette_size;
    }

    //static
    //------------------------------------------------------------------------
    unsigned pixel_map::calc_palette_size(BITMAPINFO *bmp)
    {
        if(bmp == 0) return 0;
        return calc_palette_size(bmp->bmiHeader.biClrUsed, bmp->bmiHeader.biBitCount);
    }


    //static
    //------------------------------------------------------------------------
    unsigned char * pixel_map::calc_img_ptr(BITMAPINFO *bmp)
    {
        if(bmp == 0) return 0;
        return ((unsigned char*)bmp) + calc_header_size(bmp);
    }

    //static
    //------------------------------------------------------------------------
    BITMAPINFO* pixel_map::create_bitmap_info(unsigned width, 
                                              unsigned height, 
                                              unsigned bits_per_pixel)
    {
        unsigned line_len = calc_row_len(width, bits_per_pixel);
        unsigned img_size = line_len * height;
        unsigned rgb_size = calc_palette_size(0, bits_per_pixel) * sizeof(RGBQUAD);
        unsigned full_size = sizeof(BITMAPINFOHEADER) + rgb_size + img_size;

        BITMAPINFO *bmp = (BITMAPINFO *) new unsigned char[full_size];

        bmp->bmiHeader.biSize   = sizeof(BITMAPINFOHEADER);
        bmp->bmiHeader.biWidth  = width;
        bmp->bmiHeader.biHeight = height;
        bmp->bmiHeader.biPlanes = 1;
        bmp->bmiHeader.biBitCount = (unsigned short)bits_per_pixel;
        bmp->bmiHeader.biCompression = 0;
        bmp->bmiHeader.biSizeImage = img_size;
        bmp->bmiHeader.biXPelsPerMeter = 0;
        bmp->bmiHeader.biYPelsPerMeter = 0;
        bmp->bmiHeader.biClrUsed = 0;
        bmp->bmiHeader.biClrImportant = 0;

        return bmp;
    }


    //static
    //------------------------------------------------------------------------
    void pixel_map::create_gray_scale_palette(BITMAPINFO *bmp)
    {
        if(bmp == 0) return;

        unsigned rgb_size = calc_palette_size(bmp);
        RGBQUAD *rgb = (RGBQUAD*)(((unsigned char*)bmp) + sizeof(BITMAPINFOHEADER));
        unsigned brightness;
        unsigned i;

        for(i = 0; i < rgb_size; i++)
        {
            brightness = (255 * i) / (rgb_size - 1);
            rgb->rgbBlue =
            rgb->rgbGreen =  
            rgb->rgbRed = (unsigned char)brightness; 
            rgb->rgbReserved = 0;
            rgb++;
        }
    }



    //static
    //------------------------------------------------------------------------
    unsigned pixel_map::calc_row_len(unsigned width, unsigned bits_per_pixel)
    {
        unsigned n = width;
        unsigned k;

        switch(bits_per_pixel)
        {
            case  1: k = n;
                     n = n >> 3;
                     if(k & 7) n++; 
                     break;

            case  4: k = n;
                     n = n >> 1;
                     if(k & 3) n++; 
                     break;

            case  8:
                     break;

            case 16: n *= 2;
                     break;

            case 24: n *= 3; 
                     break;

            case 32: n *= 4;
                     break;

            case 48: n *= 6; 
                     break;

            case 64: n *= 8; 
                     break;

            case 96: n *= 12; 
                     break;

            case 128: n *= 16; 
                     break;

            default: n = 0;
                     break;
        }
        return ((n + 3) >> 2) << 2;
    }





    //------------------------------------------------------------------------
    void pixel_map::draw(HDC h_dc, const RECT *device_rect, const RECT *bmp_rect) const
    {
        if(m_bmp == 0 || m_buf == 0) return;

        unsigned bmp_x = 0;
        unsigned bmp_y = 0;
        unsigned bmp_width  = m_bmp->bmiHeader.biWidth;
        unsigned bmp_height = m_bmp->bmiHeader.biHeight;
        unsigned dvc_x = 0;
        unsigned dvc_y = 0; 
        unsigned dvc_width  = m_bmp->bmiHeader.biWidth;
        unsigned dvc_height = m_bmp->bmiHeader.biHeight;
        
        if(bmp_rect) 
        {
            bmp_x      = bmp_rect->left;
            bmp_y      = bmp_rect->top;
            bmp_width  = bmp_rect->right  - bmp_rect->left;
            bmp_height = bmp_rect->bottom - bmp_rect->top;
        } 

        dvc_x      = bmp_x;
        dvc_y      = bmp_y;
        dvc_width  = bmp_width;
        dvc_height = bmp_height;

        if(device_rect) 
        {
            dvc_x      = device_rect->left;
            dvc_y      = device_rect->top;
            dvc_width  = device_rect->right  - device_rect->left;
            dvc_height = device_rect->bottom - device_rect->top;
        }

        if(dvc_width != bmp_width || dvc_height != bmp_height)
        {
            ::SetStretchBltMode(h_dc, COLORONCOLOR);
            ::StretchDIBits(
                h_dc,            // handle of device context 
                dvc_x,           // x-coordinate of upper-left corner of source rect. 
                dvc_y,           // y-coordinate of upper-left corner of source rect. 
                dvc_width,       // width of source rectangle 
                dvc_height,      // height of source rectangle 
                bmp_x,
                bmp_y,           // x, y -coordinates of upper-left corner of dest. rect. 
                bmp_width,       // width of destination rectangle 
                bmp_height,      // height of destination rectangle 
                m_buf,           // address of bitmap bits 
                m_bmp,           // address of bitmap data 
                DIB_RGB_COLORS,  // usage 
                SRCCOPY          // raster operation code 
            );
        }
        else
        {
            ::SetDIBitsToDevice(
                h_dc,            // handle to device context
                dvc_x,           // x-coordinate of upper-left corner of 
                dvc_y,           // y-coordinate of upper-left corner of 
                dvc_width,       // source rectangle width
                dvc_height,      // source rectangle height
                bmp_x,           // x-coordinate of lower-left corner of 
                bmp_y,           // y-coordinate of lower-left corner of 
                0,               // first scan line in array
                bmp_height,      // number of scan lines
                m_buf,           // address of array with DIB bits
                m_bmp,           // address of structure with bitmap info.
                DIB_RGB_COLORS   // RGB or palette indexes
            );
        }
    }


    //------------------------------------------------------------------------
    void pixel_map::draw(HDC h_dc, int x, int y, double scale) const
    {
        if(m_bmp == 0 || m_buf == 0) return;

        unsigned width  = unsigned(m_bmp->bmiHeader.biWidth * scale);
        unsigned height = unsigned(m_bmp->bmiHeader.biHeight * scale);
        RECT rect;
        rect.left   = x;
        rect.top    = y;
        rect.right  = x + width;
        rect.bottom = y + height;
        draw(h_dc, &rect);
    }




    //------------------------------------------------------------------------
    void pixel_map::blend(HDC h_dc, const RECT *device_rect, const RECT *bmp_rect) const
    {
#if !defined(AGG_BMP_ALPHA_BLEND)
        draw(h_dc, device_rect, bmp_rect);
        return;
#else
        if(m_bpp != 32)
        {
            draw(h_dc, device_rect, bmp_rect);
            return;
        }

        if(m_bmp == 0 || m_buf == 0) return;

        unsigned bmp_x = 0;
        unsigned bmp_y = 0;
        unsigned bmp_width  = m_bmp->bmiHeader.biWidth;
        unsigned bmp_height = m_bmp->bmiHeader.biHeight;
        unsigned dvc_x = 0;
        unsigned dvc_y = 0; 
        unsigned dvc_width  = m_bmp->bmiHeader.biWidth;
        unsigned dvc_height = m_bmp->bmiHeader.biHeight;
        
        if(bmp_rect) 
        {
            bmp_x      = bmp_rect->left;
            bmp_y      = bmp_rect->top;
            bmp_width  = bmp_rect->right  - bmp_rect->left;
            bmp_height = bmp_rect->bottom - bmp_rect->top;
        } 

        dvc_x      = bmp_x;
        dvc_y      = bmp_y;
        dvc_width  = bmp_width;
        dvc_height = bmp_height;

        if(device_rect) 
        {
            dvc_x      = device_rect->left;
            dvc_y      = device_rect->top;
            dvc_width  = device_rect->right  - device_rect->left;
            dvc_height = device_rect->bottom - device_rect->top;
        }

        HDC mem_dc = ::CreateCompatibleDC(h_dc);
        void* buf = 0;
        HBITMAP bmp = ::CreateDIBSection(
            mem_dc, 
            m_bmp,  
            DIB_RGB_COLORS,
            &buf,
            0,
            0
        );
        memcpy(buf, m_buf, m_bmp->bmiHeader.biSizeImage);

        HBITMAP temp = (HBITMAP)::SelectObject(mem_dc, bmp);

        BLENDFUNCTION blend;
        blend.BlendOp = AC_SRC_OVER;
        blend.BlendFlags = 0;

#if defined(AC_SRC_ALPHA)
        blend.AlphaFormat = AC_SRC_ALPHA;
//#elif defined(AC_SRC_NO_PREMULT_ALPHA)
//        blend.AlphaFormat = AC_SRC_NO_PREMULT_ALPHA;
#else 
#error "No appropriate constant for alpha format. Check version of wingdi.h, There must be AC_SRC_ALPHA or AC_SRC_NO_PREMULT_ALPHA"
#endif

        blend.SourceConstantAlpha = 255;
        ::AlphaBlend(
          h_dc,      
          dvc_x,      
          dvc_y,      
          dvc_width,  
          dvc_height, 
          mem_dc,
          bmp_x,
          bmp_y,     
          bmp_width, 
          bmp_height,
          blend
        );

        ::SelectObject(mem_dc, temp);
        ::DeleteObject(bmp);
        ::DeleteObject(mem_dc);
#endif //defined(AGG_BMP_ALPHA_BLEND)
    }


    //------------------------------------------------------------------------
    void pixel_map::blend(HDC h_dc, int x, int y, double scale) const
    {
        if(m_bmp == 0 || m_buf == 0) return;
        unsigned width  = unsigned(m_bmp->bmiHeader.biWidth * scale);
        unsigned height = unsigned(m_bmp->bmiHeader.biHeight * scale);
        RECT rect;
        rect.left   = x;
        rect.top    = y;
        rect.right  = x + width;
        rect.bottom = y + height;
        blend(h_dc, &rect);
    }


    //------------------------------------------------------------------------
    bool pixel_map::load_from_bmp(FILE *fd)
    {
        BITMAPFILEHEADER  bmf;
        BITMAPINFO       *bmi = 0;
        unsigned          bmp_size;

        if (std::fread(&bmf, sizeof(bmf), 1, fd) != 1) goto bmperr;
        if(bmf.bfType != 0x4D42) goto bmperr;

        bmp_size = bmf.bfSize - sizeof(BITMAPFILEHEADER);

        bmi = (BITMAPINFO*) new unsigned char [bmp_size];
        if(std::fread(bmi, 1, bmp_size, fd) != bmp_size) goto bmperr;
        destroy();
        m_bpp = bmi->bmiHeader.biBitCount;
        create_from_bmp(bmi);
        m_is_internal = 1;
        return true;

    bmperr:
        if(bmi) delete [] (unsigned char*) bmi;
        return false;
    }



    //------------------------------------------------------------------------
    bool pixel_map::load_from_bmp(const char *filename)
    {
        FILE *fd = std::fopen(filename, "rb");
        bool ret = false;
        if(fd)
        {
            ret = load_from_bmp(fd);
            std::fclose(fd);
        }
        return ret;
    }



    //------------------------------------------------------------------------
    bool pixel_map::save_as_bmp(FILE *fd) const
    {
        if(m_bmp == 0) return 0;

        BITMAPFILEHEADER bmf;

        bmf.bfType      = 0x4D42;
        bmf.bfOffBits   = calc_header_size(m_bmp) + sizeof(bmf);
        bmf.bfSize      = bmf.bfOffBits + m_img_size;
        bmf.bfReserved1 = 0;
        bmf.bfReserved2 = 0;

        std::fwrite(&bmf, sizeof(bmf), 1, fd);
        std::fwrite(m_bmp, m_full_size, 1, fd);
        return true;
    }



    //------------------------------------------------------------------------
    bool pixel_map::save_as_bmp(const char *filename) const
    {
        FILE *fd = std::fopen(filename, "wb");
        bool ret = false;
        if(fd)
        {
            ret = save_as_bmp(fd);
            std::fclose(fd);
        }
        return ret;
    }


    //------------------------------------------------------------------------
    unsigned char* pixel_map::buf()
    {
        return m_buf;
    }

    //------------------------------------------------------------------------
    unsigned pixel_map::width() const
    {
        return m_bmp->bmiHeader.biWidth;
    }

    //------------------------------------------------------------------------
    unsigned pixel_map::height() const
    {
        return m_bmp->bmiHeader.biHeight;
    }

    //------------------------------------------------------------------------
    int pixel_map::stride() const
    {
        return calc_row_len(m_bmp->bmiHeader.biWidth, 
                            m_bmp->bmiHeader.biBitCount);
    }


    //private
    //------------------------------------------------------------------------
    void pixel_map::create_from_bmp(BITMAPINFO *bmp)
    {
        if(bmp)
        {
            m_img_size  = calc_row_len(bmp->bmiHeader.biWidth, 
                                       bmp->bmiHeader.biBitCount) * 
                          bmp->bmiHeader.biHeight;

            m_full_size = calc_full_size(bmp);
            m_bmp       = bmp;
            m_buf       = calc_img_ptr(bmp);
        }
    }


    //private
    //------------------------------------------------------------------------
    HBITMAP pixel_map::create_dib_section_from_args(HDC h_dc,
                                                    unsigned width, 
                                                    unsigned height, 
                                                    unsigned bits_per_pixel)
    {
        unsigned line_len  = calc_row_len(width, bits_per_pixel);
        unsigned img_size  = line_len * height;
        unsigned rgb_size  = calc_palette_size(0, bits_per_pixel) * sizeof(RGBQUAD);
        unsigned full_size = sizeof(BITMAPINFOHEADER) + rgb_size;
        
        BITMAPINFO *bmp = (BITMAPINFO *) new unsigned char[full_size];
        
        bmp->bmiHeader.biSize   = sizeof(BITMAPINFOHEADER);
        bmp->bmiHeader.biWidth  = width;
        bmp->bmiHeader.biHeight = height;
        bmp->bmiHeader.biPlanes = 1;
        bmp->bmiHeader.biBitCount = (unsigned short)bits_per_pixel;
        bmp->bmiHeader.biCompression = 0;
        bmp->bmiHeader.biSizeImage = img_size;
        bmp->bmiHeader.biXPelsPerMeter = 0;
        bmp->bmiHeader.biYPelsPerMeter = 0;
        bmp->bmiHeader.biClrUsed = 0;
        bmp->bmiHeader.biClrImportant = 0;
        
        void*   img_ptr  = 0;
        HBITMAP h_bitmap = ::CreateDIBSection(h_dc, bmp, DIB_RGB_COLORS, &img_ptr, NULL, 0);
        
        if(img_ptr)
        {
            m_img_size  = calc_row_len(width, bits_per_pixel) * height;
            m_full_size = 0;
            m_bmp       = bmp;
            m_buf       = (unsigned char *) img_ptr;
        }
        
        return h_bitmap;
    }
}



