/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 by Linus Nielsen Feltzing
 *
 * 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.
 *
 * 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.
2006-11-18 Jens Arnold: complete rework
 - All canonical formats supported now (1, 4, 8, 15/16, 24 and 32 bit)
 - better protection against malformed / non-standard BMPs
 - code heavily optimised for both size and speed
 - dithering for 2 bit targets
2008-11-02 Akio Idehara: refactor for scaler frontend
2008-12-08 Andrew Mahone: partial-line reading, scaler frontend
 - read_part_line does the actual source BMP reading, return columns read
   and updates fields in a struct bmp_args with the new data and current
   reader state
 - skip_lines_bmp and store_part_bmp implement the scaler callbacks to skip
   ahead by whole lines, or read the next chunk of the current line
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "inttypes.h"
#include "system.h"
#ifndef PLUGIN
#include "debug.h"
#endif
#include "lcd.h"
#include "file.h"
#include "bmp.h"
#ifdef HAVE_REMOTE_LCD
#include "lcd-remote.h"
#endif
#ifdef ROCKBOX_DEBUG_BMP_LOADER
#define BDEBUGF DEBUGF
#else
#define BDEBUGF(...)
#endif
#ifndef __PCTOOL__
#include "config.h"
#include "resize.h"
#else
#undef DEBUGF
#define DEBUGF(...)
#endif

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

/* BMP header structure */
struct bmp_header {
    uint16_t type;          /* signature - 'BM' */
    uint32_t size;          /* file size in bytes */
    uint16_t reserved1;     /* 0 */
    uint16_t reserved2;     /* 0 */
    uint32_t off_bits;      /* offset to bitmap */
    uint32_t struct_size;   /* size of this struct (40) */
    int32_t width;          /* bmap width in pixels */
    int32_t height;         /* bmap height in pixels */
    uint16_t planes;        /* num planes - always 1 */
    uint16_t bit_count;     /* bits per pixel */
    uint32_t compression;   /* compression flag */
    uint32_t size_image;    /* image size in bytes */
    int32_t x_pels_per_meter;   /* horz resolution */
    int32_t y_pels_per_meter;   /* vert resolution */
    uint32_t clr_used;      /* 0 -> color table size */
    uint32_t clr_important; /* important color count */
} STRUCT_PACKED;

/* masks for supported BI_BITFIELDS encodings (16/32 bit) */
static const struct uint8_rgb bitfields[][4] = {
    /* 15bit */
    {
        { .blue = 0x00, .green = 0x7c, .red = 0x00, .alpha = 0x00 },
        { .blue = 0xe0, .green = 0x03, .red = 0x00, .alpha = 0x00 },
        { .blue = 0x1f, .green = 0x00, .red = 0x00, .alpha = 0x00 },
        { .blue = 0x00, .green = 0x00, .red = 0x00, .alpha = 0x00 },
    },
    /* 16bit */
    {
        { .blue = 0x00, .green = 0xf8, .red = 0x00, .alpha = 0x00  },
        { .blue = 0xe0, .green = 0x07, .red = 0x00, .alpha = 0x00  },
        { .blue = 0x1f, .green = 0x00, .red = 0x00, .alpha = 0x00  },
        { .blue = 0x00, .green = 0x00, .red = 0x00, .alpha = 0x00  },
    },
    /* 32bit BGRA */
    {
        { .blue = 0x00, .green = 0x00, .red = 0xff, .alpha = 0x00  },
        { .blue = 0x00, .green = 0xff, .red = 0x00, .alpha = 0x00  },
        { .blue = 0xff, .green = 0x00, .red = 0x00, .alpha = 0x00  },
        { .blue = 0x00, .green = 0x00, .red = 0x00, .alpha = 0xff  },
    },
    /* 32bit ABGR */
    {
        { .blue = 0x00, .green = 0x00, .red = 0x00, .alpha = 0xff },
        { .blue = 0x00, .green = 0x00, .red = 0xff, .alpha = 0x00 },
        { .blue = 0x00, .green = 0xff, .red = 0x00, .alpha = 0x00 },
        { .blue = 0xff, .green = 0x00, .red = 0x00, .alpha = 0x00 },
    },
};

#if (LCD_DEPTH > 1) || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)
/* the full 16x16 Bayer dither matrix may be calculated quickly with this table
*/
const unsigned char dither_table[16] =
    {   0,192, 48,240, 12,204, 60,252,  3,195, 51,243, 15,207, 63,255 };
#endif

#if ((LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_INTERLEAVED)) \
 || (defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH == 2) \
     && (LCD_REMOTE_PIXELFORMAT == VERTICAL_INTERLEAVED))
const unsigned short vi_pattern[4] = {
    0x0101, 0x0100, 0x0001, 0x0000
};
#endif

/******************************************************************************
 * read_bmp_file()
 *
 * Reads a BMP file and puts the data in rockbox format in *bitmap.
 *
 *****************************************************************************/
int read_bmp_file(const char* filename,
                  struct bitmap *bm,
                  int maxsize,
                  int format,
                  const struct custom_format *cformat)
{
    int fd, ret;
    fd = open(filename, O_RDONLY);

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

    BDEBUGF("read_bmp_file: '%s' remote: %d resize: %d keep_aspect: %d\n",
           filename, !!(format & FORMAT_REMOTE), !!(format & FORMAT_RESIZE),
           !!(format & FORMAT_KEEP_ASPECT));
    ret = read_bmp_fd(fd, bm, maxsize, format, cformat);
    close(fd);
    return ret;
}

enum color_order {
    /* only used for different types of 32bpp images */
    BGRA, /* should be most common */
    ABGR  /* generated by some GIMP versions */
};

struct bmp_args {
    /* needs to be at least 2byte aligned for faster 16bit reads.
     * but aligning to cache should be even faster */
    unsigned char buf[BM_MAX_WIDTH * 4] CACHEALIGN_AT_LEAST_ATTR(2);
    int fd;
    short padded_width;
    short read_width;
    short width;
    short depth;
    enum color_order order;
    struct uint8_rgb *palette;
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
    int cur_row;
    int cur_col;
    struct img_part part;
#endif
    /* as read_part_line() goes through the rows it'll set this to true
     * if it finds transparency. Initialize to 0 before calling */
    int alpha_detected;
    /* for checking transparency it checks the against the very first byte
     * of the bitmap. Initalize to 0x80 before calling */
    unsigned char first_alpha_byte;
};

static unsigned int read_part_line(struct bmp_args *ba)
{
    const int padded_width = ba->padded_width;
    const int read_width = ba->read_width;
    const int width = ba->width;
    int depth = ba->depth;
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
    int cur_row = ba->cur_row;
    int cur_col = ba->cur_col;
#endif
    const int fd = ba->fd;
    uint8_t *ibuf;
    struct uint8_rgb *buf = (struct uint8_rgb *)(ba->buf);
    const struct uint8_rgb *palette = ba->palette;
    uint32_t component, data;
    int ret;
    int i, cols, len;

#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
    cols = MIN(width - cur_col,(int)BM_MAX_WIDTH);
    BDEBUGF("reading %d cols (width: %d, max: %d)\n",cols,width,BM_MAX_WIDTH);
    len = (cols * (depth == 15 ? 16 : depth) + 7) >> 3;
#else
    cols = width;
    len = read_width;
#endif
    ibuf = ((unsigned char *)buf) + (BM_MAX_WIDTH << 2) - len;
    BDEBUGF("read_part_line: cols=%d len=%d\n",cols,len);
    ret = read(fd, ibuf, len);
    if (ret != len)
    {
        DEBUGF("read_part_line: error reading image, read returned %d "
               "expected %d\n", ret, len);
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
        BDEBUGF("cur_row: %d cur_col: %d cols: %d len: %d\n", cur_row, cur_col,
                cols, len);
#endif
        return 0;
    }

    /* detect if the image has useful alpha information.
     * if all alpha bits are 0xff or 0x00 discard the information.
     * if it has other bits, or is mixed with 0x00 and 0xff then interpret
     * as alpha. assume no alpha until the opposite is proven. as mixed
     * is alpha, compare to the first byte instead of 0xff and 0x00 separately
     */
    if (depth == 32 && ba->first_alpha_byte == 0x80)
        ba->first_alpha_byte = ibuf[3] ? 0xff : 0x0;

     /* select different color orders within the switch-case to avoid
      * nested if/switch */
    if (depth == 32)
        depth += ba->order;

    while (ibuf < ba->buf + (BM_MAX_WIDTH << 2))
    {
        switch (depth)
        {
          case 1:
            data = *ibuf++;
            for (i = 0; i < 8; i++)
            {
                *buf++ = palette[data & 0x80 ? 1 : 0];
                data <<= 1;
            }
            break;
          case 4:
            data = *ibuf++;
            *buf++ = palette[data >> 4];
            *buf++ = palette[data & 0xf];
            break;
          case 8:
            *buf++ = palette[*ibuf++];
            break;
          case 15:
          case 16:
            data = letoh16(*(uint16_t*)ibuf);
            component = (data << 3) & 0xf8;
            component |= component >> 5;
            buf->blue = component;
            if (depth == 15)
            {
                data >>= 2;
                component = data & 0xf8;
                component |= component >> 5;
            } else {
                data >>= 3;
                component = data & 0xfc;
                component |= component >> 6;
            }
            buf->green = component;
            data >>= 5;
            component = data & 0xf8;
            component |= component >> 5;
            buf->red = component;
            buf->alpha = 0xff;
            buf++;
            ibuf += 2;
            break;
          case 24:
            buf->blue = *ibuf++;
            buf->green = *ibuf++;
            buf->red = *ibuf++;
            buf->alpha = 0xff;
            buf++;
            break;
          case 32 + BGRA:
            buf->blue = *ibuf++;
            buf->green = *ibuf++;
            buf->red = *ibuf++;
            buf->alpha = *ibuf++;
            ba->alpha_detected |= (buf->alpha != ba->first_alpha_byte);
            buf++;
            break;
          case 32 + ABGR:
            buf->alpha = *ibuf++;
            buf->blue = *ibuf++;
            buf->green = *ibuf++;
            buf->red = *ibuf++;
            ba->alpha_detected |= (buf->alpha != ba->first_alpha_byte);
            buf++;
            break;
        }
    }
#if !defined(HAVE_LCD_COLOR) && \
    ((LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) || \
    defined(PLUGIN))
    ibuf = ba->buf;
    buf = (struct uint8_rgb*)ba->buf;
    while (ibuf < ba->buf + cols)
        *ibuf++ = brightness(*buf++);
#endif

#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
    cur_col += cols;
    if (cur_col == width)
    {
#endif
        int pad = padded_width - read_width;
        if (pad > 0)
        {
            BDEBUGF("seeking %d bytes to next line\n",pad);
            lseek(fd, pad, SEEK_CUR);
        }
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
        cur_col = 0;
        BDEBUGF("read_part_line: completed row %d\n", cur_row);
        cur_row += 1;
    }

    ba->cur_row = cur_row;
    ba->cur_col = cur_col;
#endif
    return cols;
}

#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
static struct img_part *store_part_bmp(void *args)
{
    struct bmp_args *ba = (struct bmp_args *)args;

    ba->part.len = read_part_line(ba);
#ifdef HAVE_LCD_COLOR
    ba->part.buf = (struct uint8_rgb *)ba->buf;
#else
    ba->part.buf = (uint8_t *)ba->buf;
#endif
    if (ba->part.len)
        return &(ba->part);
    else
        return NULL;
}
#endif

static inline int rgbcmp(const struct uint8_rgb *rgb1, const struct uint8_rgb *rgb2)
{
    return memcmp(rgb1, rgb2, sizeof(struct uint8_rgb));
}

#if LCD_DEPTH > 1
#if !defined(PLUGIN) && !defined(HAVE_JPEG) && !defined(HAVE_BMP_SCALING)
static inline
#endif
void output_row_8_native(uint32_t row, void * row_in,
                              struct scaler_context *ctx)
{
    int col;
    int fb_width = BM_WIDTH(ctx->bm->width,FORMAT_NATIVE,0);
    uint8_t dy = DITHERY(row);
#ifdef HAVE_LCD_COLOR
    struct uint8_rgb *qp = (struct uint8_rgb*)row_in;
#else
    uint8_t *qp = (uint8_t*)row_in;
#endif
    BDEBUGF("output_row: y: %lu in: %p\n",row, row_in);
#if LCD_DEPTH == 2
#if LCD_PIXELFORMAT == HORIZONTAL_PACKING
                /* greyscale iPods */
                fb_data *dest = (fb_data *)ctx->bm->data + fb_width * row;
                int shift = 6;
                int delta = 127;
                unsigned bright;
                unsigned data = 0;

                for (col = 0; col < ctx->bm->width; col++) {
                    if (ctx->dither)
                        delta = DITHERXDY(col,dy);
                    bright = *qp++;
                    bright = (3 * bright + (bright >> 6) + delta) >> 8;
                    data |= (~bright & 3) << shift;
                    shift -= 2;
                    if (shift < 0) {
                        *dest++ = data;
                        data = 0;
                        shift = 6;
                    }
                }
                if (shift < 6)
                    *dest++ = data;
#elif LCD_PIXELFORMAT == VERTICAL_PACKING
                /* iriver H1x0 */
                fb_data *dest = (fb_data *)ctx->bm->data + fb_width *
                                (row >> 2);
                int shift = 2 * (row & 3);
                int delta = 127;
                unsigned bright;

                for (col = 0; col < ctx->bm->width; col++) {
                    if (ctx->dither)
                        delta = DITHERXDY(col,dy);
                    bright = *qp++;
                    bright = (3 * bright + (bright >> 6) + delta) >> 8;
                    *dest++ |= (~bright & 3) << shift;
                }
#elif LCD_PIXELFORMAT == VERTICAL_INTERLEAVED
                /* iAudio M3 */
                fb_data *dest = (fb_data *)ctx->bm->data + fb_width *
                                (row >> 3);
                int shift = row & 7;
                int delta = 127;
                unsigned bright;

                for (col = 0; col < ctx->bm->width; col++) {
                    if (ctx->dither)
                        delta = DITHERXDY(col,dy);
                    bright = *qp++;
                    bright = (3 * bright + (bright >> 6) + delta) >> 8;
                    *dest++ |= vi_pattern[bright] << shift;
                }
#endif /* LCD_PIXELFORMAT */
#elif LCD_DEPTH >= 16
                /* iriver h300, colour iPods, X5 */
                (void)fb_width;
                fb_data *dest = STRIDE_MAIN((fb_data *)ctx->bm->data + fb_width * row,
                                            (fb_data *)ctx->bm->data + row);
                int delta = 127;
                unsigned r, g, b;                
                /* setup alpha channel buffer */
                unsigned char *bm_alpha = NULL;
                if (ctx->bm->alpha_offset > 0)
                    bm_alpha = ctx->bm->data + ctx->bm->alpha_offset;
                if (bm_alpha)
                    bm_alpha += ALIGN_UP(ctx->bm->width, 2) * row/2;

                for (col = 0; col < ctx->bm->width; col++) {
                    (void) delta;
                    if (ctx->dither)
                        delta = DITHERXDY(col,dy);
                    r = qp->red;
                    g = qp->green;
                    b = qp->blue;
#if LCD_DEPTH < 24
                    r = (31 * r + (r >> 3) + delta) >> 8;
                    g = (63 * g + (g >> 2) + delta) >> 8;
                    b = (31 * b + (b >> 3) + delta) >> 8;
#endif
                    *dest = FB_RGBPACK_LCD(r, g, b);
                    dest += STRIDE_MAIN(1, ctx->bm->height);
                    if (bm_alpha) {
                        /* pack alpha channel for 2 pixels into 1 byte and negate
                         * according to the interal alpha channel format */
                        uint8_t alpha = ~qp->alpha;
                        if (col%2)
                            *bm_alpha++ |= alpha&0xf0;
                        else
                            *bm_alpha = alpha>>4;
                    }
                    qp++;
                }
#endif /* LCD_DEPTH */
}
#endif

/******************************************************************************
 * read_bmp_fd()
 *
 * Reads a BMP file in an open file descriptor and puts the data in rockbox
 * format in *bitmap.
 *
 *****************************************************************************/
int read_bmp_fd(int fd,
                struct bitmap *bm,
                int maxsize,
                int format,
                const struct custom_format *cformat)
{
    struct bmp_header bmph;
    int padded_width;
    int read_width;
    int depth, numcolors, compression, totalsize;
    int ret, hdr_size;
    bool return_size = format & FORMAT_RETURN_SIZE;
    bool read_alpha = format & FORMAT_TRANSPARENT;
    enum color_order order = BGRA;

    unsigned char *bitmap = bm->data;
    struct uint8_rgb palette[256];
    struct rowset rset;
    struct dim src_dim;
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) || \
    defined(PLUGIN)
    bool dither = false;
#endif

    bool remote = false;
#ifdef HAVE_REMOTE_LCD
    if (format & FORMAT_REMOTE) {
        remote = true;
#if LCD_REMOTE_DEPTH == 1
        format = FORMAT_MONO;
#endif
    }
#endif /* HAVE_REMOTE_LCD */

#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
    unsigned int resize = IMG_NORESIZE;

    if (format & FORMAT_RESIZE) {
        resize = IMG_RESIZE;
    }

#else

    (void)format;
#endif /*(LCD_DEPTH > 1) || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)*/
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) || \
    defined(PLUGIN)
    if (format & FORMAT_DITHER) {
        dither = true;
    }
#endif
    /* read fileheader */
    ret = read(fd, &bmph, sizeof(struct bmp_header));
    if (ret < 0) {
        return ret * 10 - 2;
    }

    if (ret != sizeof(struct bmp_header)) {
        DEBUGF("read_bmp_fd: can't read BMP header.");
        return -3;
    }

    src_dim.width = letoh32(bmph.width);
    src_dim.height = letoh32(bmph.height);
    if (src_dim.height < 0) {     /* Top-down BMP file */
        src_dim.height = -src_dim.height;
        rset.rowstep = 1;
    } else {              /* normal BMP */
        rset.rowstep = -1;
    }

    depth = letoh16(bmph.bit_count);
    /* 4-byte boundary aligned */
    read_width = ((src_dim.width * (depth == 15 ? 16 : depth) + 7) >> 3);
    padded_width = (read_width + 3) & ~3;

    BDEBUGF("width: %d height: %d depth: %d padded_width: %d\n", src_dim.width,
            src_dim.height, depth, padded_width);

#if (LCD_DEPTH > 1) || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)
    if ((format & 3) == FORMAT_ANY) {
        if (depth == 1)
            format = (format & ~3);
        else
            format = (format & ~3) | FORMAT_NATIVE;
    }
    bm->format = format & 1;
    if ((format & 1) == FORMAT_MONO)
    {
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
        resize &= ~IMG_RESIZE;
        resize |= IMG_NORESIZE;
#endif
        remote = false;
    }
#elif !defined(PLUGIN)
    if (src_dim.width > BM_MAX_WIDTH)
            return -6;
#endif /*(LCD_DEPTH > 1) || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)*/

#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
    if (resize & IMG_RESIZE) {
        if(format & FORMAT_KEEP_ASPECT) {
            /* keep aspect ratio.. */
            struct dim resize_dim = {
                .width = bm->width,
                .height = bm->height,
            };
            if (recalc_dimension(&resize_dim, &src_dim))
                resize = IMG_NORESIZE;
            bm->width = resize_dim.width;
            bm->height = resize_dim.height;
        }
    }

    if (!(resize & IMG_RESIZE)) {
#endif
        /* returning image size */
        bm->width = src_dim.width;
        bm->height = src_dim.height;

#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
    }
#endif
#if LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)
    format &= 1;
#endif
    if (rset.rowstep > 0) {     /* Top-down BMP file */
        rset.rowstart = 0;
        rset.rowstop = bm->height;
    } else {              /* normal BMP */
        rset.rowstart = bm->height - 1;
        rset.rowstop = -1;
    }

    /* need even rows (see lcd-16bit-common.c for details) */
    int alphasize = ALIGN_UP(bm->width, 2) * bm->height / 2;
    if (cformat)
        totalsize = cformat->get_size(bm);
    else {
        totalsize = BM_SIZE(bm->width,bm->height,format,remote);
        if (!remote)
            if (depth == 32 && read_alpha) /* account for possible 4bit alpha per pixel */
                totalsize += alphasize;
    }

    if(return_size)
    {
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
        if(resize)
            totalsize += BM_SCALED_SIZE(bm->width, 0, 0, 0);
        else if (bm->width > BM_MAX_WIDTH)
            totalsize += bm->width*4;
#endif
        return totalsize;
    }

    /* Check if this fits the buffer */
    if (totalsize > maxsize) {
        DEBUGF("read_bmp_fd: Bitmap too large for buffer: "
               "%d bytes.\n", totalsize);
        return -6;
    }

    hdr_size = letoh32(bmph.struct_size);
    compression = letoh32(bmph.compression);
    if (depth <= 8) {
        numcolors = letoh32(bmph.clr_used);
        if (numcolors == 0)
            numcolors = BIT_N(depth);
        /* forward to the color table */
        lseek(fd, 14+hdr_size, SEEK_SET);
    } else {
        numcolors = 0;
        if (compression == 3) {
            if (hdr_size >= 56)
                numcolors = 4;
            else /* hdr_size == 52 */
                numcolors = 3;
        }
    }

    /* read color tables. for BI_BITFIELDS this actually
     * reads the color masks */
    if (numcolors > 0 && numcolors <= 256) {
        int i;
        for (i = 0; i < numcolors; i++) {
            if (read(fd, &palette[i], sizeof(struct uint8_rgb))
                    != (int)sizeof(struct uint8_rgb)) {
                DEBUGF("read_bmp_fd: Can't read color palette\n");
                return -7;
            }
        }
    }

    switch (depth) {
      case 16:
#if LCD_DEPTH >= 16
        /* don't dither 16 bit BMP to LCD with same or larger depth */
        if (!remote)
            dither = false;
#endif
        if (compression == 0) { /* BI_RGB, i.e. 15 bit */
            depth = 15;
            break;
        }   /* else fall through */

      case 32:
        if (compression == 3) { /* BI_BITFIELDS */
            bool found = false;
            int i, j;

            /* (i == 0) is 15bit, (i == 1) is 16bit, (i == {2,3}) is 32bit */
            for (i = 0; i < ARRAY_SIZE(bitfields) && !found; i++) {
                /* for 15bpp and higher numcolors has the number of color masks */
                for (j = 0; j < numcolors; j++) {
                    if (!rgbcmp(&palette[j], &bitfields[i][j])) {
                        found = true;
                    } else {
                        found = false;
                        break;
                    }
                }
            }
            if (found) {
                if (i == 1) /* 15bit */
                    depth = 15;
                else if (i == 4) /* 32bit, ABGR bitmap */
                    order = ABGR;
                break;
            }
        }   /* else fall through */

      default:
        if (compression != 0) { /* not BI_RGB */
            DEBUGF("read_bmp_fd: Unsupported compression (type %d)\n",
                   compression);
            return -8;
        }
        break;
    }

#if LCD_DEPTH >= 24
    /* Never dither 24/32 bit BMP to 24 bit LCDs */
    if (depth >= 24 && !remote)
        dither = false;
#endif

    /* Search to the beginning of the image data */
    lseek(fd, (off_t)letoh32(bmph.off_bits), SEEK_SET);

    memset(bitmap, 0, totalsize);

#ifdef HAVE_LCD_COLOR
    if (read_alpha && depth == 32)
        bm->alpha_offset = totalsize - alphasize;
    else
        bm->alpha_offset = 0;
#endif

    struct bmp_args ba = {
        .fd = fd, .padded_width = padded_width, .read_width = read_width,
        .width = src_dim.width, .depth = depth, .palette = palette,
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
        .cur_row = 0, .cur_col = 0, .part = {0,0},
#endif
        .alpha_detected = false, .first_alpha_byte = 0x80,
        .order = order,
    };

#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
    if (resize)
    {
        if (resize_on_load(bm, dither, &src_dim, &rset,
                           bitmap + totalsize, maxsize - totalsize,
                           cformat, IF_PIX_FMT(0,) store_part_bmp, &ba))
            return totalsize;
        else
            return 0;
    }
#endif /* LCD_DEPTH */

#if LCD_DEPTH > 1 || defined(PLUGIN)
    struct scaler_context ctx = {
        .bm = bm,
        .dither = dither,
    };
#endif
#if defined(PLUGIN) || defined(HAVE_JPEG) || defined(HAVE_BMP_SCALING)
#if LCD_DEPTH > 1
    void (*output_row_8)(uint32_t, void*, struct scaler_context*) =
        output_row_8_native;
#elif defined(PLUGIN)
    void (*output_row_8)(uint32_t, void*, struct scaler_context*) = NULL;
#endif
#if LCD_DEPTH > 1 || defined(PLUGIN)
    if (cformat)
        output_row_8 = cformat->output_row_8;
#endif
#endif

    unsigned char *buf = ba.buf;
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) || \
    defined(PLUGIN)
    if (bm->width > BM_MAX_WIDTH)
    {
#if defined(HAVE_BMP_SCALING) || defined(PLUGIN)
        unsigned int len = maxsize - totalsize;
        buf = bitmap + totalsize;
        ALIGN_BUFFER(buf, len, sizeof(uint32_t));
        if (bm->width*4 > (int)len)
#endif
            return -6;
    }
#endif
    int row;
    /* loop to read rows and put them to buffer */
    for (row = rset.rowstart; row != rset.rowstop; row += rset.rowstep) {
#if (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1)) && \
    defined(HAVE_BMP_SCALING) || defined(PLUGIN)
        if (bm->width > BM_MAX_WIDTH)
        {
#if defined(HAVE_LCD_COLOR)
            struct uint8_rgb *p = (struct uint8_rgb *)buf;
#else
            uint8_t* p = buf;
#endif
            do {
                int len = read_part_line(&ba);
                if (!len)
                    return -9;
                memcpy(p, ba.buf, len*sizeof(*p));
                p += len;
            } while (ba.cur_col);
        }
        else
#endif
        if (!read_part_line(&ba))
            return -9;
#ifndef PLUGIN
#if !defined(HAVE_LCD_COLOR) && \
        (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1))
        uint8_t* qp = buf;
#else
        struct uint8_rgb *qp = (struct uint8_rgb *)buf;
#endif
#endif
        /* Convert to destination format */
#if ((LCD_DEPTH > 1) || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) && \
    !defined(PLUGIN)
        if (format == FORMAT_NATIVE) {
#if defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1
            if (remote) {
                unsigned char dy = DITHERY(row);
#if (LCD_REMOTE_DEPTH == 2) && (LCD_REMOTE_PIXELFORMAT == VERTICAL_INTERLEAVED)
                /* iAudio X5/M5 remote */
                fb_remote_data *dest = (fb_remote_data *)bitmap
                                     + bm->width * (row >> 3);
                int shift = row & 7;
                int delta = 127;
                unsigned bright;

                int col;
                for (col = 0; col < bm->width; col++) {
                    if (dither)
                        delta = DITHERXDY(col,dy);
#if !defined(HAVE_LCD_COLOR) && \
        (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1))
                    bright = *qp++;
#else
                    bright = brightness(*qp++);
#endif
                    bright = (3 * bright + (bright >> 6) + delta) >> 8;
                    *dest++ |= vi_pattern[bright] << shift;
                }
#endif /* LCD_REMOTE_DEPTH / LCD_REMOTE_PIXELFORMAT */
            } else
#endif /* defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1 */
#endif /* (LCD_DEPTH > 1) || defined(HAVE_REMOTE_LCD) &&
    (LCD_REMOTE_DEPTH > 1) */
#if LCD_DEPTH > 1 || defined(PLUGIN)
            {
#if !defined(PLUGIN) && !defined(HAVE_JPEG) && !defined(HAVE_BMP_SCALING)
                output_row_8_native(row, buf, &ctx);
#else
                output_row_8(row, buf, &ctx);
#endif
            }
#endif
#if ((LCD_DEPTH > 1) || defined(HAVE_REMOTE_LCD) && (LCD_REMOTE_DEPTH > 1)) && \
    !defined(PLUGIN)
        }
#ifndef PLUGIN
        else
#endif
#endif
#ifndef PLUGIN
        {
            unsigned char *p = bitmap + bm->width * (row >> 3);
            unsigned char mask = BIT_N(row & 7);
            int col;
            for (col = 0; col < bm->width; col++, p++)
#if !defined(HAVE_LCD_COLOR) && \
        (LCD_DEPTH > 1 || (defined(HAVE_REMOTE_LCD) && LCD_REMOTE_DEPTH > 1))
                if (*qp++ < 128)
                    *p |= mask;
#else
                if (brightness(*qp++) < 128)
                    *p |= mask;
#endif
        }
#endif
    }
#ifdef HAVE_LCD_COLOR
    if (!ba.alpha_detected)
    {   /* if this has an alpha channel, totalsize accounts for it as well
         * subtract if no actual alpha information was found */
        if (bm->alpha_offset > 0)
            totalsize -= alphasize;
        bm->alpha_offset = 0;
    }
#endif
    return totalsize; /* return the used buffer size. */
}
