/*
 * File Name: image_dither.cpp
 */

/*
 * This file is part of uds-plugin-images.
 *
 * uds-plugin-images 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.
 *
 * uds-plugin-images is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Copyright (C) 2008 iRex Technologies B.V.
 * All rights reserved.
 */

#include <string.h>
#include <cassert>
#include <iostream>
#include <fstream>
#include "image_dither.h"
#include "log.h"

namespace images
{
/*
 * class ImageDither
 */
ImageDither::ImageDither(void)
{
    LOGPRINTF("entry");
}

ImageDither::~ImageDither(void)
{
    LOGPRINTF("entry");
}

void ImageDither::dither_to_8bits(const BitmapAttributes *attrs_src,
                                   BitmapAttributes *attrs_dst)
{
    LOGPRINTF("entry %p, %p", attrs_src, attrs_dst);

    assert(attrs_src);
    assert(attrs_dst);
    assert(attrs_src->data);
    assert((attrs_src->bytes_per_pixel == 3) 
            || (attrs_src->bytes_per_pixel == 4));
    assert(attrs_src->rowstride >= 
            (attrs_src->width * attrs_src->bytes_per_pixel));
   
    attrs_dst->width = attrs_src->width;
    attrs_dst->height = attrs_src->height;
    attrs_dst->rowstride = get_rowstride(attrs_src->width, 1, 4);
    attrs_dst->bytes_per_pixel = 1;

    if (attrs_src->bytes_per_pixel == 4)
    {
        attrs_dst->data = dither_32bits_to_8bits(attrs_src->data, 
                                           attrs_src->width, 
                                           attrs_src->height, 
                                           attrs_src->rowstride);
    }
    else
    {
        attrs_dst->data = dither_24bits_to_8bits(attrs_src->data, 
                                           attrs_src->width, 
                                           attrs_src->height, 
                                           attrs_src->rowstride);
    }
}

unsigned char*ImageDither::dither_32bits_to_8bits(const unsigned char*data,
                                              int w, int h, int rowstride)
{
    LOGPRINTF("entry %p, %d, %d, %d", data, w, h, rowstride);

    int n_channels = 4;

    // Get the memory.
    int rowstride_dst = get_rowstride(w, 1, 4);
    unsigned char * dst = new unsigned char[rowstride_dst * h];
    
    // Walk throuth the whole pixels buffer.
    unsigned char r, g, b, a;
    unsigned char * p_src, *p_dst;
 
    // For dithering.
    int c;
    int orig_grey = 0;
    int grey = 0;
    int error = 0;
    int *errors = new int[(rowstride + 2) * 4];
    memset(errors, 0, (rowstride + 2) * 4);
    int error_right = 0;
    int error_down_right = 0;

    for (int y = 0; y < h; y++)
    {
        p_src = (unsigned char*)(data + y * rowstride);
        p_dst = dst + y * rowstride_dst;

        for (int x = 0; x < w; x++)
        {
            r = p_src[0];
            g = p_src[1];
            b = p_src[2];
            a = p_src[3];
            
            // Convert RGBA to grayscale color; 
            // take care of the alpha channel, 
            // but always use WHITE as background color
            // *p_dst = (((r << 8) + (g << 9) + (b << 7)) >> 10);
            // *p_dst = ((r * 307 + g * 604 + b * 113) >> 10);
            c = ((((255 - a) * WHITE_BACKGROUND) 
                + (a * ((r * 307 + g * 604 + b * 113) >> 10))) >> 8);
            c = c & 0x00ff;

            // Get Mono 8 pixel and add error distribution
            orig_grey = c + error_right + errors[x+1];

            // Convert pixel to Mono 4, clip < 0 and > Mono 4
            if (orig_grey <= 0) 
            {
              grey = 0;
            } 
            else if (orig_grey  >= 0x00ff) 
            {
              grey = 0x00ff; // No error for white of 255
            } 
            else 
            {
              grey = (orig_grey & 0x00f0) + ((orig_grey & 0x00f0) >> 4);
            }

            // Put Mono 4 pixel with distributed error back
            *p_dst = grey;

            // Calculate error
            error = orig_grey - grey;

            // Distribute error
            error_right = (error * 7) >> 4;
            errors[x] += (error * 3) >> 4;
            errors[x+1] = ((error * 5) >> 4) + error_down_right;
            error_down_right = error >> 4;
             
            p_src += n_channels;
            p_dst++;
        }
    }

    delete [] errors;
    errors = 0;

    // LOGPRINTF("return %p", dst);
    
    return dst;
}

unsigned char*ImageDither::dither_24bits_to_8bits(const unsigned char*data,
                                                int w, int h, int rowstride)
{
    LOGPRINTF("entry %p, %d, %d, %d", data, w, h, rowstride);

    int n_channels = 3;

    // Get the memory.
    int rowstride_dst = get_rowstride(w, 1, 4);
    unsigned char * dst = new unsigned char[rowstride_dst * h];
    
    // Walk throuth the whole pixels buffer.
    unsigned char r, g, b;
    unsigned char * p_src, *p_dst;
 
    // For dithering.
    int c;
    int orig_grey = 0;
    int grey = 0;
    int error = 0;
    int *errors = new int[(rowstride + 2) * 4];
    memset(errors, 0, (rowstride + 2) * 4);
    int error_right = 0;
    int error_down_right = 0;

    for (int y = 0; y < h; y++)
    {
        p_src = (unsigned char*)(data + y * rowstride);
        p_dst = dst + y * rowstride_dst;

        for (int x = 0; x < w; x++)
        {
            r = p_src[0];
            g = p_src[1];
            b = p_src[2];

            // Convert RGB to grayscale color.
            // *p_dst = (((r << 8) + (g << 9) + (b << 7)) >> 10);
            c = ((r * 307 + g * 604 + b * 113) >> 10);
            c = c & 0x00ff;

            // Get Mono 8 pixel and add error distribution
            orig_grey = c + error_right + errors[x+1];

            // Convert pixel to Mono 4, clip < 0 and > Mono 4
            if (orig_grey <= 0) 
            {
              grey = 0;
            } 
            else if (orig_grey  >= 0x00ff) 
            {
              grey = 0x00ff; // No error for white of 255
            } 
            else 
            {
              grey = (orig_grey & 0x00f0) + ((orig_grey & 0x00f0) >> 4);
            }

            // Put Mono 4 pixel with distributed error back
            *p_dst = grey;

            // Calculate error
            error = orig_grey - grey;

            // Distribute error
            error_right = (error * 7) >> 4;
            errors[x] += (error * 3) >> 4;
            errors[x+1] = ((error * 5) >> 4) + error_down_right;
            error_down_right = error >> 4;

            p_src += n_channels;
            p_dst++;
        }
    }

    delete [] errors;
    errors = 0; 

    // LOGPRINTF("return %p", dst);
    
    return dst;

 }

int ImageDither::get_rowstride(int width, 
                               int bytes_per_pixel, 
                               int alignment)
{
    LOGPRINTF("entry %d, %d, %d", width, bytes_per_pixel, alignment);

    int rowstride;

    int original_rowstride = width * bytes_per_pixel;
    if (original_rowstride % alignment)
    {
        int padding = alignment - (original_rowstride % alignment);
        rowstride = original_rowstride + padding;
    }
    else
    {
        rowstride = original_rowstride;
    }
    
    LOGPRINTF("return %d", rowstride);

    return rowstride;
}
}; // namespace image


