/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: bmp.c,v 1.31 2006/10/11 18:12:36 jethead71 Exp $
 *
 * Copyright (C) 2002 by Linus Nielsen Feltzing
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

/*
2005-04-16 Tomas Salfischberger:
 - New BMP loader function, based on the old one (borrowed a lot of
   calculations and checks there.)
 - Conversion part needs some optimization, doing unneeded calulations now.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "inttypes.h"
#include "debug.h"
#include "lcd.h"
#include "file.h"
#include "config.h"
#include "system.h"
#include "bmp.h"
#include "lcd.h"

#ifdef __GNUC__
#define STRUCT_PACKED __attribute__((packed))
#else
#define STRUCT_PACKED
#pragma pack (push, 2)
#endif

/* Struct from original code. */
struct Fileheader {
    uint16_t Type;        /* signature - 'BM' */
    uint32_t Size;         /* file size in bytes */
    uint16_t Reserved1;   /* 0 */
    uint16_t Reserved2;   /* 0 */
    uint32_t OffBits;      /* offset to bitmap */
    uint32_t StructSize;   /* size of this struct (40) */
    uint32_t Width;        /* bmap width in pixels */
    uint32_t Height;       /* bmap height in pixels */
    uint16_t Planes;      /* num planes - always 1 */
    uint16_t BitCount;    /* bits per pixel */
    uint32_t Compression;  /* compression flag */
    uint32_t SizeImage;    /* image size in bytes */
    int32_t XPelsPerMeter;         /* horz resolution */
    int32_t YPelsPerMeter;         /* vert resolution */
    uint32_t ClrUsed;      /* 0 -> color table size */
    uint32_t ClrImportant; /* important color count */
} STRUCT_PACKED;

struct rgb_quad { /* Little endian */
  unsigned char blue; 
  unsigned char green; 
  unsigned char red;
  unsigned char reserved;
} STRUCT_PACKED; 

/* big endian functions */
static uint16_t readshort(uint16_t *value) {
    unsigned char* bytes = (unsigned char*) value;
    return bytes[0] | (bytes[1] << 8);
}

static uint32_t readlong(uint32_t *value) {
    unsigned char* bytes = (unsigned char*) value;
    return (long)bytes[0] | ((long)bytes[1] << 8) |
        ((long)bytes[2] << 16) | ((long)bytes[3] << 24);
}

unsigned char brightness(struct rgb_quad color)
{
    return (3 * (unsigned int)color.red + 6 * (unsigned int)color.green
              + (unsigned int)color.blue) / 10;
}

/* Function to get a pixel from a line. (Tomas: maybe a better way?) */
inline int getpix(int px, unsigned char *bmpbuf) {
    int a = (px / 8);
    int b = (7 - (px % 8));

    return (bmpbuf[a] & (1 << b)) != 0;
}
#if LCD_DEPTH == 16
/* Cheapo 24 -> 16 bit dither */
static unsigned short dither_24_to_16(struct rgb_quad rgb, int row, int col)
{
    static const unsigned char dith[][16] =
    {
        {   /* 5 bit */
            0,6,1,7,
            4,2,5,3,
            1,7,0,6,
            5,3,4,2
        },
        {   /* 6 bit */
            0,3,0,3,
            2,1,2,1,
            0,3,0,3,
            2,1,2,1
        },
    };

    const int elm = (row & 3) + ((col & 3) << 2);
    unsigned short color;

    unsigned b = ((unsigned)rgb.blue  + dith[0][elm]) >> 3;
    if (b > 0x1f) b = 0x1f;
    unsigned g = ((unsigned)rgb.green + dith[1][elm]) >> 2;
    if (g > 0x3f) g = 0x3f;
    unsigned r = ((unsigned)rgb.red   + dith[0][elm]) >> 3;
    if (r > 0x1f) r = 0x1f;

    color = (unsigned short)(b | (g << 5) | (r << 11));

#if LCD_PIXELFORMAT == RGB565SWAPPED
    swap16(color);
#endif
    return color;
}
#endif /* LCD_DEPTH == 16 */


/* maximum bitmap width which can be read: */
#define MAX_WIDTH 1024
static unsigned char bmpbuf[MAX_WIDTH*sizeof(struct rgb_quad)]; /* Buffer for one line */


/******************************************************************************
+ * set_bmp_pixel()
 *
 * puts a pixel from BMP source (bmpbuf) to destination (bitmap).
 *
 *****************************************************************************/
static void set_bmp_pixel(int depth,
                          int format,
                          int invert_pixel,
                          struct rgb_quad *palette,
                          unsigned char *bmpbuf,
                          int src_col,
                          unsigned char *bitmap,
                          int dst_row,
                          int dst_col,
                          int dst_w,
                          int dst_h,
                          bool dither)
{
#if LCD_DEPTH > 1
    fb_data *dest = (fb_data *)bitmap;
#endif
    unsigned char *p;
    int ret;

    switch (depth) {
        case 1:
#if LCD_DEPTH > 1
            if (format == FORMAT_MONO) {
#endif
                /* Mono -> Mono */
                ret = getpix(src_col, bmpbuf) ^ invert_pixel;
                if (ret) {
                    bitmap[dst_w * ((dst_h - dst_row - 1) / 8) + dst_col]
                        |= 1 << ((dst_h - dst_row - 1) % 8);
                } else {
                    bitmap[dst_w * ((dst_h - dst_row - 1) / 8) + dst_col]
                        &= ~ 1 << ((dst_h - dst_row - 1) % 8);
                }
#if LCD_DEPTH > 1
            } else {
#endif
#if LCD_DEPTH == 2
#if LCD_PIXELFORMAT == VERTICAL_PACKING
                /* Mono -> 2gray (iriver H1xx) */
                ret = getpix(src_col, bmpbuf) ^ invert_pixel;
                if (ret) {
                    dest[((dst_h - dst_row - 1) / 4) * dst_w + dst_col] |=
                        0xC0 >> (2 * (~(dst_h - dst_row - 1) & 3));
                }
#else
                /* Mono -> 2gray (ipod) */
                ret = getpix(src_col, bmpbuf) ^ invert_pixel;
                if (ret) {
                    dest[(dst_h - dst_row - 1) * dst_w + dst_col/4] |=
                        0xC0 >> (2 * (dst_col & 3));
                }
#endif
#elif LCD_DEPTH == 16
                /* Mono -> RGB16 */
                ret = getpix(src_col, bmpbuf);
                unsigned short rgb16 = LCD_RGBPACK(palette[ret].red,
                                                   palette[ret].green,
                                                   palette[ret].blue);
                dest[dst_w * (dst_h - dst_row - 1) + dst_col] = rgb16;
#endif
#if LCD_DEPTH > 1
            }
#endif
        break; /* case 1 */

        case 8:
            p = bmpbuf + src_col;
#if LCD_DEPTH > 1
            if (format == FORMAT_MONO) {
#endif
                /* 8-bit RGB24 palette -> mono */
                struct rgb_quad rgb = palette[*p];
                ret = brightness(rgb);
                if (ret > 96) {
                    bitmap[dst_w * ((dst_h - dst_row - 1) / 8) + dst_col]
                        &= ~ 1 << ((dst_h - dst_row - 1) % 8);
                } else {
                    bitmap[dst_w * ((dst_h - dst_row - 1) / 8) + dst_col]
                        |= 1 << ((dst_h - dst_row - 1) % 8);
                }
#if LCD_DEPTH > 1
            } else {
#endif
#if LCD_DEPTH == 2
#if LCD_PIXELFORMAT == VERTICAL_PACKING
                /* 8-bit RGB24 palette -> 2gray (iriver H1xx) */
                struct rgb_quad rgb = palette[*p];
                ret = brightness(rgb);
                dest[((dst_h - dst_row - 1)/4) * dst_w + dst_col] |=
                    (~ret & 0xC0) >> (2 * (~(dst_h - dst_row - 1) & 3));
#else
                /* 8-bit RGB24 palette -> 2gray (ipod) */
                struct rgb_quad rgb = palette[*p];
                ret = brightness(rgb);

                dest[(dst_h - dst_row - 1) * dst_w + dst_col/4] |=
                    (~ret & 0xC0) >> (2 * (dst_col & 3));
#endif
#elif LCD_DEPTH == 16
                /* 8-bit RGB24 palette -> RGB16 */
                struct rgb_quad rgb = palette[*p];
                dest[dst_w * (dst_h - dst_row - 1) + dst_col] = dither ?
                dither_24_to_16(rgb, dst_row, dst_col) : LCD_RGBPACK(rgb.red, rgb.green, rgb.blue);
#endif
#if LCD_DEPTH > 1
            }
#endif
        break; /* case 8 */

        case 24:
            p = bmpbuf + 3 * src_col;
#if LCD_DEPTH > 1
            if (format == FORMAT_MONO) {
#endif
            /* RGB24 -> mono */
                struct rgb_quad rgb;
                rgb.red = p[2];
                rgb.green = p[1];
                rgb.blue = p[0];
                ret = brightness(rgb);
                if (ret > 96) {
                    bitmap[dst_w * ((dst_h - dst_row - 1) / 8) + dst_col]
                        &= ~ 1 << ((dst_h - dst_row - 1) % 8);
                } else {
                    bitmap[dst_w * ((dst_h - dst_row - 1) / 8) + dst_col]
                        |= 1 << ((dst_h - dst_row - 1) % 8);
                }
#if LCD_DEPTH > 1
            } else {
#endif
#if LCD_DEPTH == 2
#if LCD_PIXELFORMAT == VERTICAL_PACKING
                /* RGB24 -> 2gray (iriver H1xx) */
                struct rgb_quad rgb;
                rgb.red = p[2];
                rgb.green = p[1];
                rgb.blue = p[0];
                ret = brightness(rgb);

                dest[((dst_h - dst_row - 1)/4) * dst_w + dst_col] |=
                    (~ret & 0xC0) >> (2 * (~(dst_h - dst_row - 1) & 3));
#else
                /* RGB24 -> 2gray (ipod) */
                struct rgb_quad rgb;
                rgb.red = p[2];
                rgb.green = p[1];
                rgb.blue = p[0];
                ret = brightness(rgb);

                dest[(dst_h - dst_row - 1) * dst_w + dst_col/4] |=
                    (~ret & 0xC0) >> (2 * (dst_col & 3));
#endif
#elif LCD_DEPTH == 16
                /* RGB24 -> RGB16 */
                dest[dst_w * (dst_h - dst_row - 1) + dst_col] = dither?
                  dither_24_to_16(*(struct rgb_quad *)p, dst_row, dst_col) :
                  LCD_RGBPACK(p[2],p[1],p[0]);
#endif
#if LCD_DEPTH > 1
            }
#endif
        break; /* case 24 */
    } /* END switch(depth) */
}



/******************************************************************************
 * read_bmp_file()
 *
 * Reads a BMP file and puts the data in rockbox format in *bitmap.
 *
 *****************************************************************************/
int read_bmp_file(char* filename,
                  struct bitmap *bm,
                  int maxsize,
                  int format,
                  int dst_maxwidth,
                  int dst_width_resize,
                  int dst_maxheight,
                  int dst_height_resize)
{
    struct Fileheader fh;
    int src_w, src_h, SrcPaddedWidth;
    int dst_w, dst_h, DstPaddedHeight, DstPaddedWidth;
    int fact;
    int fd, row, ret;

    struct rgb_quad palette[256];
    int invert_pixel = 0;
    int numcolors;
    int depth;
    int totalsize;
    char *bitmap = bm->data;
//    unsigned char bmpbuf[LCD_WIDTH*sizeof(struct rgb_quad)]; /* Buffer for one line */
#if LCD_DEPTH != 1
    bool transparent = false;
#if LCD_DEPTH == 16
    /* Should adapt dithering to all native depths */
    bool dither = false;
    if(format & FORMAT_DITHER) {
        dither = true;
        format &= ~FORMAT_DITHER;
    }
#endif
    if(format & FORMAT_TRANSPARENT) {
        transparent = true;
        format &= ~FORMAT_TRANSPARENT;
    }
#else
    format = FORMAT_MONO;
#endif

    fd = open(filename, O_RDONLY);

    /* Exit if file opening failed */
    if (fd < 0) {
      DEBUGF("read_bmp_file: error - can't open '%s' open returned: %d\n", filename, fd);
      return (fd * 10) - 1; 
    }

    /* read fileheader */
    ret = read(fd, &fh, sizeof(struct Fileheader));
    if(ret < 0) {
        close(fd);
        return (ret * 10 - 2);
    }
    
    if(ret != sizeof(struct Fileheader)) {
        DEBUGF("read_bmp_file: error - can't read Fileheader structure.");
        close(fd);
        return -3;
    }

    /* read the source dimensions */
    src_w = readlong(&fh.Width);
    src_h = readlong(&fh.Height);

    if (src_w > MAX_WIDTH) {
        DEBUGF("read_bmp_file: error - Bitmap is too wide (%d pixels, max is %d)\n",
               src_w, MAX_WIDTH);
        close(fd);
        return -5;
    }

    depth = readshort(&fh.BitCount);

    /* Calculate resize factors and image size */
    {
        int fact_w = -1;
        int fact_h = -1;
        if (dst_maxwidth > 0)
            fact_w = (src_w * 1000) / dst_maxwidth;
        if (fact_w > 1000 && !(dst_width_resize & BMP_RESIZE_DECREASE)) {
            /* no decrease allowed */
            fact_w = -1;
        }
        if (fact_w < 1000 && !(dst_width_resize & BMP_RESIZE_INCREASE)) {
            /* no increase allowed */
            fact_w = -1;
        }

        if (dst_maxheight > 0)
            fact_h = (src_h * 1000) / dst_maxheight;
        if (fact_h > 1000 && !(dst_height_resize & BMP_RESIZE_DECREASE)) {
            /* no decrease allowed */
            fact_h = -1;
        }
        if (fact_h < 1000 && !(dst_height_resize & BMP_RESIZE_INCREASE)) {
            /* no increase allowed */
            fact_h = -1;
        }

        fact = MAX(fact_h, fact_w);
        if (fact > 0) {
            dst_w = (src_w * 1000) / fact;
            dst_h = (src_h * 1000) / fact;
        } else {
            /* don't resize */
            fact = 1000;
            dst_w = src_w;
            dst_h = src_h;
        }
    }

    DEBUGF("read_bmp_file: src_w/h=%d/%d, dst_w/h=%d/%d (fact=%d)\n",
           src_w, src_h, dst_w, dst_h, fact);

    /* Exit if too wide */
    if (dst_w > LCD_WIDTH) {
        DEBUGF("read_bmp_file: error - Bitmap is too wide (%d pixels, max is %d)\n",
               dst_w, LCD_WIDTH);
        close(fd);
        return -5;
    }

    if (dst_h > LCD_HEIGHT) {
        DEBUGF("read_bmp_file: error - Bitmap is too high (%d pixels, max is %d)\n",
               dst_h, LCD_HEIGHT);
        close(fd);
        return -6;
    }

    /* 4-byte boundary aligned */
    SrcPaddedWidth = ((src_w * depth + 31) / 8) & ~3;

#if LCD_DEPTH > 1
        if(format == FORMAT_ANY) {
            if(depth == 1)
                format = FORMAT_MONO;
            else
                format = FORMAT_NATIVE;
        }
#endif

    /* DstPaddedHeight/Width is for rockbox format. */
    if (format == FORMAT_MONO) {
        DstPaddedHeight = (dst_h + 7) / 8;
        DstPaddedWidth = dst_w;
        totalsize = DstPaddedHeight * DstPaddedWidth;
    } else {
#if LCD_DEPTH == 2
#if LCD_PIXELFORMAT == VERTICAL_PACKING
        DstPaddedHeight = (dst_h + 3) / 4;
        DstPaddedWidth = dst_w;
#else
        DstPaddedHeight = dst_h;
        DstPaddedWidth = (dst_w + 3) / 4;
#endif
#else
        DstPaddedHeight = dst_h;
        DstPaddedWidth = dst_w;
#endif
        totalsize = DstPaddedHeight * DstPaddedWidth * sizeof(fb_data);
    }

    /* Check if this fits the buffer */
    
    if (totalsize > maxsize) {
        DEBUGF("read_bmp_file: error - Bitmap is too large to fit the supplied buffer: "
               "%d bytes.%d:%d\n", (DstPaddedHeight * DstPaddedWidth),
               totalsize, maxsize);
        close(fd);
        return -7;
    }

    if (depth <= 8)
    {
        numcolors = readlong(&fh.ClrUsed);
        if (numcolors == 0)
            numcolors = 1 << depth;

        if(read(fd, palette, numcolors * sizeof(struct rgb_quad))
           != numcolors * (int)sizeof(struct rgb_quad))
        {
            DEBUGF("read_bmp_file: error - Can't read bitmap's color palette\n");
            close(fd);
            return -8;
        }
    }

    /* Use the darker palette color as foreground on mono bitmaps */
    if(depth == 1) {
        if(brightness(palette[0]) < brightness(palette[1]))
           invert_pixel = 1;
    }
    
    /* Search to the beginning of the image data */
    lseek(fd, (off_t)readlong(&fh.OffBits), SEEK_SET);

#if LCD_DEPTH == 2
    if(format == FORMAT_NATIVE)
        memset(bitmap, 0, totalsize);
#endif
    
 
      /* loop to read rows and put them to buffer */
     for (row = 0; row < src_h; row++) {
          /* read one row */
         ret = read(fd, bmpbuf, SrcPaddedWidth);
         if (ret != SrcPaddedWidth) {
             DEBUGF("read_bmp_file: error reading image, read returned: %d expected was: "
                    "%d\n", ret, SrcPaddedWidth);
              close(fd);
              return -9;
          }
  
         if (fact > 1000) {
             /* decrease
              * -> put only every "fact" row to the dest buffer */
             if ((row % (fact / 1000)) == 0) {
                 int dst_row = (row * 1000) / fact;
                 int dst_col;
 
                 for (dst_col = 0; dst_col < dst_w; dst_col++) {
                     int src_col = (dst_col * fact) / 1000;
 
                     set_bmp_pixel(depth, format, invert_pixel, palette,
                                   bmpbuf, src_col,
                                   bitmap, dst_row, dst_col, dst_w, dst_h,dither);
                  }
              }
         } else
         if (fact < 1000) {
             /* increase
              * -> duplicate src-pixel for not existing dst-rows/cols */
             int dst_row, dst_col;
             int src_col;
 
             for (dst_row = row * 1000 / fact;
                  dst_row < (row + 1) * 1000 / fact; dst_row++) {
                 for (src_col = 0; src_col < src_w; src_col++) {
                     for (dst_col = src_col * 1000 / fact;
                          dst_col < (src_col + 1) * 1000 / fact; dst_col++) {
                         set_bmp_pixel(depth, format, invert_pixel, palette,
                                       bmpbuf, src_col,
                                       bitmap, dst_row, dst_col, dst_w, dst_h,dither);
                     }
                  }
              }
         } else {
             /* same size -> just transport the pixels */
             int dst_col;
             for (dst_col = 0; dst_col < dst_w; dst_col++) {
                 set_bmp_pixel(depth, format, invert_pixel, palette,
                               bmpbuf, dst_col,
                               bitmap, row, dst_col, dst_w, dst_h,dither);
              }
          }
     } /* END for (row = 0; row < src_h; row++) */

    close(fd);

    /* returning image size: */
    bm->width = dst_w;
    bm->height = dst_h;
    #if LCD_DEPTH > 1
    bm->format = format;
#endif

    DEBUGF("read_bmp_file: totalsize=%d, width=%d, height=%d\n",
           totalsize, dst_w, dst_h);
    return totalsize; /* return the used buffer size. */
}
