//-----------------------------------------------------------------------------
// Demonstracni priklad k serialu "Graficke formaty"
// http://www.root.cz/
//
// Autor: Pavel Tisnovsky
//
// Tento program predvadi zpusob redukce barvonosnych slozek v barvovem modelu
// YCbCr.
//-----------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>

// pripony souboru, se kterymi se pracuje
#define EXTENSION_SRC   ".tga"
#define EXTENSION_DEST  "_red.tga"
#define EXTENSION_SUB   "_sub.tga"

// datova struktura popisujici pixmapu
typedef struct {
    unsigned int width;
    unsigned int height;
    unsigned char *pixels;
} pixmap;

// jmena vstupniho souboru a souboru vystupnich
char *src_name;
char *dest_name;
char *dest_name_sub;

// pracovni pixmapy
pixmap *src;
pixmap *dest;



//-----------------------------------------------------------------------------
// vytvoreni plnobarevne (truecolor) pixmapy
//-----------------------------------------------------------------------------
pixmap * createPixmapTruecolor(unsigned int width, unsigned int height)
{
    pixmap *px;
    unsigned int size;

    assert(width>0);
    assert(height>0);
    // alokace pameti pro hlavicku pixmapy
    px=(pixmap*)malloc(sizeof(pixmap));
    // kontrola alokace pameti
    assert(px);
    px->width=width;
    px->height=height;
    size=width*height*3;
    // alokace pameti pro pixely
    px->pixels=(unsigned char *)malloc(sizeof(unsigned char)*size);
    // kontrola alokace pameti
    assert(px->pixels);
    return px;
}



//-----------------------------------------------------------------------------
// ulozeni pixmapy (truecolor) do externiho souboru
//-----------------------------------------------------------------------------
void saveTrueColorPixmap(const pixmap *p, const char *filename)
{
    FILE *fout;
    unsigned int i, j;
    unsigned char tgaHeader[18]={               // hlavicka formatu typu TGA
                        0x00,                   // typ hlavicky TGA
                        0x00,                   // nepouzivame paletu
                        0x02,                   // typ obrazku je grayscale
                        0x00, 0x00,             // delka palety je nulova
                        0x00, 0x00, 0x00,       // pozice v palete nas nezajima
                        0x00, 0x00, 0x00, 0x00, // obrazek je umisteny na pozici [0, 0]
                        0x00, 0x00, 0x00, 0x00, // sirka a vyska obrazku (dva byty na polozku)
                        0x18,                   // format je 24 bitu na pixel
                        0x20                    // orientace bitmapy v obrazku
    };
    assert(p);                                  // je pixmapa v poradku?
    assert(p->pixels);
    printf("pixmap info:\n\tname:\t%s\n\twidth:\t%d\n\theight:\t%d\n\tbpp:\t%d\n", filename, p->width, p->height, 24);
    memcpy(tgaHeader+12, &(p->width), 2);       // do hlavicky TGA zapsat velikost obrazku
    memcpy(tgaHeader+14, &(p->height), 2);
    fout=fopen(filename, "wb");
    if (fout) {
        fwrite(tgaHeader, 18, 1, fout);         // zapsat hlavicku TGA do souboru
        for (i=p->height; i; i--) {             // radky zapisovat v opacnem poradi
            unsigned int yoff=3*(i-1)*p->width; // y-ovy offset v poli
            unsigned char *p1=p->pixels+yoff;
            for (j=0; j<p->width; j++) {        // prohozeni BGR na RGB a ulozeni radku do souboru
                int result;
                result=fputc(*(p1+2), fout); assert(result!=EOF);
                result=fputc(*(p1+1), fout); assert(result!=EOF);
                result=fputc(*(p1), fout);   assert(result!=EOF);
                p1+=3;
            }
        }
        fclose(fout);
    }
    puts("pixmap saved");
}



//-----------------------------------------------------------------------------
// nacteni pixmapy (truecolor) z obrazku typu TGA
//-----------------------------------------------------------------------------
pixmap * loadTruecolorPixmap(const char *filename)
{
    FILE  *fin;
    int   width=0, height=0, bpp=0;
    int   pallength=0;
    int   i, j;
    int   count;
    pixmap *p;
    unsigned char *p1, *p2;
    unsigned char tgaHeader[18]={               // hlavicka formatu typu TGA
                        0x00,                   // typ hlavicky TGA
                        0x00,                   // nepouzivame paletu
                        0x02,                   // typ obrazku je RGB TrueColor
                        0x00, 0x00,             // delka palety je nulova
                        0x00, 0x00, 0x00,       // pozice v palete nas nezajima
                        0x00, 0x00, 0x00, 0x00, // obrazek je umisteny na pozici [0, 0]
                        0x00, 0x00, 0x00, 0x00, // sirka a vyska obrazku (dva byty na polozku)
                        0x18,                   // format je 24 bitu na pixel
                        0x20                    // orientace bitmapy v obrazku
    };
    assert(filename);
    fin=fopen(filename, "rb");
    assert(fin);
    count=fread(tgaHeader, 18, 1, fin);         // nacteni hlavicky
    assert(count==1);
    memcpy(&width, tgaHeader+12, 2);            // naplneni hlavicky
    memcpy(&height, tgaHeader+14, 2);
    memcpy(&bpp, tgaHeader+16, 1);
    memcpy(&pallength, tgaHeader+5, 2);
    printf("pixmap info:\n\tname:\t%s\n\twidth:\t%d\n\theight:\t%d\n\tbpp:\t%d\n", filename, width, height, bpp);
    assert(bpp==24);
    p=createPixmapTruecolor(width, height);
    for (i=0; i<height; i++) {                  // nacitani dat do pixmapy
        int offset=i*3*p->width;
        count=fread(p->pixels+offset, 3*p->width, 1, fin);
        assert(count==1);
        p1=p->pixels+offset;
        p2=p1+2;
        for (j=0; j<width; j++, p1+=3, p2+=3) { // prohozeni BGR na RGB
            unsigned char k=*p1;
            *p1=*p2; *p2=k;
        }
    }
    fclose(fin);
    puts("pixmap loaded");
    return p;
}



//-----------------------------------------------------------------------------
// priprava jmen vstupniho souboru a souboru vystupnich
//-----------------------------------------------------------------------------
void createFileNames(char *full_name)
{
    int  name_size;
    char *p;

    // delka zadaneho jmena souboru
    name_size=strlen(full_name);

    // alokace pameti pro dalsi jmena
    src_name=     (char *)malloc(sizeof(char)*(name_size+strlen(EXTENSION_SRC)));
    dest_name=    (char *)malloc(sizeof(char)*(name_size+strlen(EXTENSION_DEST)));
    dest_name_sub=(char *)malloc(sizeof(char)*(name_size+strlen(EXTENSION_SUB)));

    // oddelit koncovku od jmena
    p=strrchr(full_name, '.');
    assert(p>full_name);
    *p=0;

    // naplnit konkretni jmena souboru
    strcpy(src_name, full_name);
    strcat(src_name, EXTENSION_SRC);

    strcpy(dest_name, full_name);
    strcat(dest_name, EXTENSION_DEST);

    strcpy(dest_name_sub, full_name);
    strcat(dest_name_sub, EXTENSION_SUB);

    // kontrolni vypis
    printf("source pixmap: %s\ndestination pixmap: %s\nsubtract pixmap: %s\n",
            src_name,
            dest_name,
            dest_name_sub);
}



//-----------------------------------------------------------------------------
// vytvoreni vystupniho obrazku
//-----------------------------------------------------------------------------
void createDestinationPixmap(void)
{
    dest=createPixmapTruecolor(src->width, src->height);
}



//-----------------------------------------------------------------------------
// ulozeni vystupniho obrazku
//-----------------------------------------------------------------------------
void saveDestinationPixmap(void)
{
    saveTrueColorPixmap(dest, dest_name);
}



//-----------------------------------------------------------------------------
// ulozeni rozdiloveho obrazku
//-----------------------------------------------------------------------------
void saveSubBitmap(void)
{
    saveTrueColorPixmap(dest, dest_name_sub);
}



//-----------------------------------------------------------------------------
// nacteni barvove slozky R
//-----------------------------------------------------------------------------
unsigned char getR(int x, int y)
{
    if (x>=src->width) x=src->width-1;
    if (y>=src->height) y=src->height-1;
    return *(src->pixels+3*(y*src->width+x));
}



//-----------------------------------------------------------------------------
// nacteni barvove slozky B
//-----------------------------------------------------------------------------
unsigned char getG(int x, int y)
{
    if (x>=src->width) x=src->width-1;
    if (y>=src->height) y=src->height-1;
    return *(src->pixels+3*(y*src->width+x)+1);
}



//-----------------------------------------------------------------------------
// nacteni barvove slozky B
//-----------------------------------------------------------------------------
unsigned char getB(int x, int y)
{
    if (x>=src->width) x=src->width-1;
    if (y>=src->height) y=src->height-1;
    return *(src->pixels+3*(y*src->width+x)+2);
}



//-----------------------------------------------------------------------------
// provedeni transformace z barvoveho prostoru RGB do barvoveho prostoru YCbCr
//-----------------------------------------------------------------------------
void reduce_colors(void)
{
#define BLOCK 1
    //memcpy(dest->pixels, src->pixels, src->width*src->height*3);
    int i, j;
    for (j=0; j<src->height; j++) {     // pro vsechny radky zdrojove pixmapy
        unsigned char *p_dest=dest->pixels+(3*j*dest->width);
        for (i=0; i<src->width; i++) {  // pro vsechny pixely na radku
            float r, g, b, rr, gg, bb;
            float y, cb, cr;

            // souradnice zarovnane na zacatek bloku
            int ii=i & (~BLOCK);
            int jj=j & (~BLOCK);

            // barvove slozky pro kazdy pixel
            r=getR(i, j);
            g=getG(i, j);
            b=getB(i, j);

            // barvove slozky platne pro cely blok
            rr=getR(ii, jj);
            gg=getG(ii, jj);
            bb=getB(ii, jj);

            // provest prepocet z prostoru RGB
            y = 0.299 *r+0.587 *g+0.114 *b;
            cb=-0.1687*rr-0.3313*gg+0.5   *bb+128;
            cr= 0.5   *rr-0.4187*gg-0.0813*bb+128;

            // zpetny prepocet do prostoru RGB
            r=y                 +1.402  *(cr-128);
            g=y-0.34414*(cb-128)-0.71414*(cr-128);
            b=y+1.772  *(cb-128);

            // zkontrolovat meze
            if (r>255) r=255;
            if (g>255) g=255;
            if (b>255) b=255;
            if (r<0) r=0;
            if (g<0) g=0;
            if (b<0) b=0;
            *p_dest++=(unsigned char)r;    // prevod na byty a ulozeni
            *p_dest++=(unsigned char)g;
            *p_dest++=(unsigned char)b;
        }
    }
}



//-----------------------------------------------------------------------------
// vypocet rozdiloveho obrazku
//-----------------------------------------------------------------------------
void calculate_subs(void)
{
    int i, j;
    for (j=0; j<src->height; j++) {     // pro vsechny radky zdrojove pixmapy
        unsigned char *p_src=src->pixels+(3*j*src->width);
        unsigned char *p_dest=dest->pixels+(3*j*dest->width);
        for (i=0; i<src->width; i++) {  // pro vsechny pixely na radku
            unsigned char rs, gs, bs;
            unsigned char rd, gd, bd;
            rs=*p_src++;                // ziskat barvove slozky RGB zdrojove pixmapy
            gs=*p_src++;
            bs=*p_src++;
            rd=*p_dest++;               // ziskat barvove slozky RGB cilove pixmapy
            gd=*p_dest++;
            bd=*p_dest++;
            p_dest-=3;
            // vypocet a zvyrazneni rozdilu
            *p_dest++=10.0*fabs(rs-rd);
            *p_dest++=10.0*fabs(gs-gd);
            *p_dest++=10.0*fabs(bs-bd);
        }
    }
}



//-----------------------------------------------------------------------------
// hlavni funkce konzolove aplikace
//-----------------------------------------------------------------------------
int main(int argc, char **argv)
{
    char *full_name;

    full_name=argv[1];
    createFileNames(full_name);
    src=loadTruecolorPixmap(src_name);
    createDestinationPixmap();
    reduce_colors();
    saveDestinationPixmap();
    calculate_subs();
    saveSubBitmap();
    printf("\ndone!\n");
    return 0;
}



//-----------------------------------------------------------------------------
// finito
//-----------------------------------------------------------------------------