// Functions for loading/saving standard 24 bit uncompressed RGB BMP files using planar RGB as native format.
// Written by Nils Liaaen Corneliusen 2013.
// License: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication license
// Read the 2023 article here: https://www.ignorantus.com/pages/neon_scaler/
// Read the 2018 article here: https://www.ignorantus.com/pages/image_transformation/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

#include "bmp_planar.h"

bmp_planar *bmp_planar_alloc( int w, int h )
{
    bmp_planar *bp = malloc( sizeof(bmp_planar) );
    if( bp == NULL ) {
        printf( "Error: Could not allocate bitmapinfo (%d bytes)\n", (int)sizeof(bmp_planar) );
        return NULL;
    }

    bp->w = w;
    bp->h = h;

    int h8   = (bp->h+0x07)&~0x07;
    int w128 = (bp->w+0x7f)&~0x7f;

    bp->stride = w128;

    int rc = posix_memalign( (void **)&bp->r, 16, bp->stride * h8 * 3 );
    if( rc ) {
        printf( "Error: Could not allocate data for %d*%d bitmap, rc=%d\n", w, h, rc );
        free( bp );
        return NULL;
    }

    bp->g = bp->r + bp->stride * h8;
    bp->b = bp->g + bp->stride * h8;

    return bp;
}

void bmp_planar_free( bmp_planar *src )
{
    free( src->r );
    free( src );
}


uint8_t bmpheader[] = {
    // Bitmap file header
    0x42,0x4D,           // 0x00 'BM'
    0x00,0x00,0x00,0x00, // 0x02 total file size
    0x00,0x00,           // 0x06 reserved
    0x00,0x00,           // 0x08 reserved
    0x36,0x00,0x00,0x00, // 0x0a offset to bitmap data (54)
    // BITMAPINFOHEADER
    0x28,0x00,0x00,0x00, // 0x0e header size (40)
    0x00,0x00,0x00,0x00, // 0x12 bitmap width
    0x00,0x00,0x00,0x00, // 0x16 bitmap height, negative for top to bottom storage
    0x01,0x00,           // 0x1a bitplanes (1)
    0x18,0x00,           // 0x1c bits per pixel (24)
    0x00,0x00,0x00,0x00, // 0x1e compression method (0)
    0x00,0x00,0x00,0x00, // 0x22 raw bitmap size (0)
    0x13,0x0B,0x00,0x00, // 0x26 horizontal ppm (2835)
    0x13,0x0B,0x00,0x00, // 0x2a vertical ppm (2835)
    0x00,0x00,0x00,0x00, // 0x2e colors in palette (0)
    0x00,0x00,0x00,0x00  // 0x32 important colors used (0)
};

bmp_planar *bmp_planar_load( const char *fname )
{
    FILE *fp;
    uint8_t hdr[sizeof(bmpheader)];
    bmp_planar *bp;
    int rc;

    printf( "Loading bitmap %s\n", fname );

    fp = fopen( fname, "rb" );
    if( fp == NULL  ) {
        printf( "Error: Could not open %s!\n", fname );
        return NULL;
    }

    rc = fread( hdr, 1, sizeof(bmpheader), fp );
    if( rc != sizeof(bmpheader) ) {
        printf( "Error: Short read. Got %d, expected %d\n", rc, (int)sizeof(bmpheader) );
        fclose( fp );
        return NULL;
    }

    if( hdr[0x00] != 0x42 || hdr[0x01] != 0x4D || hdr[0x0e] != 40 || hdr[0x1c] != 24 ) {
        printf( "Error: Wrong format. Got %02x,%02x size %02x format %02x\n", hdr[0x00], hdr[0x01], hdr[0x0e], hdr[0x1c] );
        fclose( fp );
        return NULL;
    }

    int w  = hdr[0x12]|(hdr[0x13]<<8)|(hdr[0x14]<<16)|(hdr[0x15]<<24);
    int h  = hdr[0x16]|(hdr[0x17]<<8)|(hdr[0x18]<<16)|(hdr[0x19]<<24);

    if( w <= 0 || h <= 0 ) {
        printf( "Error: Illegal size. w=%d, h=%d\n", w, h );
        fclose( fp );
        return NULL;
    }

    bool flip = h > 0;

    h = abs( h );

    bp = bmp_planar_alloc( w, h );
    if( bp == NULL ) {
        fclose( fp );
        return NULL;
    }

    int rowbytes = ((w*3)+3)&~0x03;

    printf( "Width: %d Height: %d Rowbytes: %d\n", w, h, rowbytes );

    uint8_t *row = alloca( rowbytes );

    for( int y = 0; y < h; y++ ) {

        int dsty = flip ? h-1-y : y;
        uint8_t *r = bp->r + dsty*bp->stride;
        uint8_t *g = bp->g + dsty*bp->stride;
        uint8_t *b = bp->b + dsty*bp->stride;

        int rc = fread( row, 1, rowbytes, fp );
        if( rc != rowbytes ) {
            printf( "Warning: Short read on row %d. Got %d, expected %d\n", y, rc, rowbytes );
            break;
        }

        for( int x = 0; x < w*3; x += 3 ) {
            *b++ = row[x+0];
            *g++ = row[x+1];
            *r++ = row[x+2];
        }

    }

    printf( "File loaded!\n" );

    fclose( fp );

    return bp;
}

bool bmp_planar_save( bmp_planar *bp, const char *fname )
{
    FILE *fp;
    int rc;
    uint8_t hdr[sizeof(bmpheader)];

    int rowbytes = ((bp->w*3)+3)&~0x03;

    printf( "Saving bitmap %s\n", fname );

    printf( "Width: %d Height: %d Rowbytes: %d\n", bp->w, bp->h, rowbytes );

    fp = fopen( fname, "wb" );
    if( fp == NULL ) {
        printf( "Error: Could not create ouput file\n" );
        return false;
    }

    memcpy( hdr, bmpheader, sizeof(bmpheader) );

    int size = sizeof(bmpheader) + rowbytes * bp->h;

    hdr[0x02] = (size>> 0)&0xff;
    hdr[0x03] = (size>> 8)&0xff;
    hdr[0x04] = (size>>16)&0xff;
    hdr[0x05] = (size>>24)&0xff;

    hdr[0x12] = (bp->w    )&0xff;
    hdr[0x13] = (bp->w>> 8)&0xff;
    hdr[0x14] = (bp->w>>16)&0xff;
    hdr[0x15] = (bp->w>>24)&0xff;

    hdr[0x16] = (bp->h    )&0xff;
    hdr[0x17] = (bp->h>> 8)&0xff;
    hdr[0x18] = (bp->h>>16)&0xff;
    hdr[0x19] = (bp->h>>24)&0xff;

    rc = fwrite( hdr, 1, sizeof(bmpheader), fp );
    if( rc != sizeof(bmpheader) ) {
        printf( "Error: Header truncated. Got %d, expected %d\n", rc, rowbytes );
        fclose( fp );
        return false;
    }

    uint8_t *row = alloca( rowbytes );

    for( int y = 0; y < bp->h; y++ ) {

        int srcy = bp->h-1-y;
        uint8_t *r = bp->r + srcy*bp->stride;
        uint8_t *g = bp->g + srcy*bp->stride;
        uint8_t *b = bp->b + srcy*bp->stride;

        for( int x = 0; x < bp->w*3; x += 3 ) {
            row[x+0] = *b++;
            row[x+1] = *g++;
            row[x+2] = *r++;
        }

        rc = fwrite( row, 1, rowbytes, fp );
        if( rc != rowbytes ) {
            printf( "Error: File truncated on row %d. Got %d, expected %d\n", y, rc, rowbytes );
            fclose( fp );
            return false;
        }

    }

    fclose( fp );

    return true;
}
