// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
// See the file "COPYING" for licence details.

#include "pixbufs.hpp"

#include <cassert>

#include <gdkmm/pixbuf.h>

#include "auto_array.hpp"
#include "jquant2.h"

// Find pixel differences between an "old" and "new" RGB Pixbuf
// (or RGBA, but the alpha component will be ignored) and copy the
// differing pixels from the new one to a third RGBA Pixbuf at the
// specified offset with full opacity.
// The width and height of the old and new Pixbufs must be equal
// and match the specified dimensions.  The width and height of
// the third Pixbuf must be large enough to store a rectangle of
// those dimensions at the specified offset.
void diff_rgb_pixbufs(Glib::RefPtr<Gdk::Pixbuf> old_buf,
		      Glib::RefPtr<Gdk::Pixbuf> new_buf,
		      Glib::RefPtr<Gdk::Pixbuf> diff_buf,
		      int offset_x, int offset_y,
		      int width, int height)
{
    assert(old_buf->get_colorspace() == Gdk::COLORSPACE_RGB);
    assert(new_buf->get_colorspace() == Gdk::COLORSPACE_RGB);
    assert(diff_buf->get_colorspace() == Gdk::COLORSPACE_RGB
	   && diff_buf->get_has_alpha());
    int old_bpr = old_buf->get_rowstride();
    int old_bpp = old_buf->get_n_channels();
    assert(old_bpp >= 3);
    assert(old_buf->get_width() == width);
    assert(old_buf->get_height() == height);
    int new_bpr = new_buf->get_rowstride();
    int new_bpp = new_buf->get_n_channels();
    assert(new_bpp >= 3);
    assert(new_buf->get_width() == width);
    assert(new_buf->get_height() == height);
    int diff_bpr = diff_buf->get_rowstride();
    int diff_bpp = diff_buf->get_n_channels();
    assert(diff_bpp == 4);
    assert(diff_buf->get_width() >= offset_x + width);
    assert(diff_buf->get_height() >= offset_y + height);

    const guint8 * old_p = old_buf->get_pixels();
    const guint8 * new_p = new_buf->get_pixels();
    guint8 * diff_p = (diff_buf->get_pixels()
		       + diff_bpr * offset_y
		       + diff_bpp * offset_x);

    for (int y = 0; y != height; ++y)
    {
	for (int x = 0; x != width; ++x)
	{
	    if (old_p[0] != new_p[0]
		|| old_p[1] != new_p[1]
		|| old_p[2] != new_p[2])
	    {
		diff_p[0] = new_p[0];
		diff_p[1] = new_p[1];
		diff_p[2] = new_p[2];
		diff_p[3] = 0xFF; // fully opaque
	    }
	    old_p += old_bpp;
	    new_p += new_bpp;
	    diff_p += diff_bpp;
	}
	old_p += old_bpr - old_bpp * width;
	new_p += new_bpr - new_bpp * width;
	diff_p += diff_bpr - diff_bpp * width;
    }
}

// Quantise an RGBA Pixbuf to the specified number of colours, including
// one transparent colour.  Currently uses Floyd-Steinberg dithering.
void quantise_rgba_pixbuf(Glib::RefPtr<Gdk::Pixbuf> buf, int n_colours)
{
    assert(buf->get_colorspace() == Gdk::COLORSPACE_RGB
	   && buf->get_has_alpha());
    int bpr = buf->get_rowstride();
    assert(buf->get_n_channels() == 4);
    int width = buf->get_width();
    int height = buf->get_height();

    unsigned char * buf_p = buf->get_pixels();
    auto_array<unsigned char *> rows(new unsigned char *[height]);
    for (int y = 0; y != height; ++y)
	rows[y] = &buf_p[y * bpr];
    auto_array<unsigned char> quant_buf_p(
	new unsigned char[width * height]);
    auto_array<unsigned char *> quant_rows(
	new unsigned char *[height]);
    for (int y = 0; y != height; ++y)
	quant_rows[y] = &quant_buf_p[y * width];
    auto_array<unsigned int> colours(new unsigned int[n_colours]);

    quantize(rows.get(), quant_rows.get(), width, height,
	     JDITHER_FS, n_colours, colours.get());

    // Pixbuf doesn't support quantised images, so convert back to RGBA.
    for (int y = 0; y != height; ++y)
	for (int x = 0; x != width; ++x)
	{
	    unsigned int colour = colours[quant_rows[y][x]];
	    rows[y][4*x]   = colour;
	    rows[y][4*x+1] = colour >> 8;
	    rows[y][4*x+2] = colour >> 16;
	    rows[y][4*x+3] = colour >> 24;
	}
}
