/*
 *  libpipi       Proper image processing implementation library
 *  Copyright (c) 2004-2008 Sam Hocevar <sam@zoy.org>
 *                All Rights Reserved
 *
 *  $Id$
 *
 *  This library is free software. It comes without any warranty, to
 *  the extent permitted by applicable law. You can redistribute it
 *  and/or modify it under the terms of the Do What The Fuck You Want
 *  To Public License, Version 2, as published by Sam Hocevar. See
 *  http://sam.zoy.org/wtfpl/COPYING for more details.
 */

/*
 * convolution.c: generic convolution functions
 */

#include "config.h"
#include "common.h"

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

#include "pipi.h"
#include "pipi_internals.h"

#define FLAG_GRAY ((FLAGS) & SET_FLAG_GRAY)
#define FLAG_WRAP ((FLAGS) & SET_FLAG_WRAP)

#define SET_FLAG_GRAY 0x01
#define SET_FLAG_WRAP 0x02

#undef FUNC1
#undef FUNC2
#undef PIXEL
#undef FLAGS
#define FUNC1 conv_std_rgba_f
#define FUNC2 conv_sep_rgba_f
#define PIXEL float
#define FLAGS 0
#include "convolution_template.h"

#undef FUNC1
#undef FUNC2
#undef PIXEL
#undef FLAGS
#define FUNC1 conv_std_y_f
#define FUNC2 conv_sep_y_f
#define PIXEL float
#define FLAGS SET_FLAG_GRAY
#include "convolution_template.h"

#undef FUNC1
#undef FUNC2
#undef PIXEL
#undef FLAGS
#define FUNC1 wrap_std_rgba_f
#define FUNC2 wrap_sep_rgba_f
#define PIXEL float
#define FLAGS SET_FLAG_WRAP
#include "convolution_template.h"

#undef FUNC1
#undef FUNC2
#undef PIXEL
#undef FLAGS
#define FUNC1 wrap_std_y_f
#define FUNC2 wrap_sep_y_f
#define PIXEL float
#define FLAGS SET_FLAG_GRAY|SET_FLAG_WRAP
#include "convolution_template.h"

pipi_image_t *pipi_convolution(pipi_image_t *src, int m, int n, double mat[])
{
    pipi_image_t *ret;
    double tmp;
    double *hvec, *vvec;
    int i, j, besti = -1, bestj = -1;

    /* Find the cell with the largest value */
    tmp = 0.0;
    for(i = 0; i < m * n; i++)
        if(mat[i] * mat[i] > tmp)
        {
            tmp = mat[i] * mat[i];
            besti = i % m;
            bestj = i / m;
        }

    /* If the kernel is empty, return an empty picture */
    if(tmp == 0.0)
        return pipi_new(src->w, src->h);

    /* Check whether the matrix rank is 1 */
    for(j = 0; j < n; j++)
    {
        if(j == bestj)
            continue;

        for(i = 0; i < m; i++)
        {
            double p, q;

            if(i == besti)
                continue;

            p = mat[j * m + i] * mat[bestj * m + besti];
            q = mat[bestj * m + i] * mat[j * m + besti];

            if(fabs(p - q) > 0.0001 * 0.0001)
            {
                if(src->last_modified == PIPI_PIXELS_Y_F)
                {
                    if(src->wrap)
                        return wrap_std_y_f(src, m, n, mat);
                    return conv_std_y_f(src, m, n, mat);
                }
                else
                {
                    if(src->wrap)
                        return wrap_std_rgba_f(src, m, n, mat);
                    return conv_std_rgba_f(src, m, n, mat);
                }
            }
        }
    }

    /* Matrix rank is 1! Separate the filter */
    hvec = malloc(m * sizeof(double));
    vvec = malloc(n * sizeof(double));

    tmp = sqrt(fabs(mat[bestj * m + besti]));
    for(i = 0; i < m; i++)
        hvec[i] = mat[bestj * m + i] / tmp;
    for(j = 0; j < n; j++)
        vvec[j] = mat[j * m + besti] / tmp;

    if(src->last_modified == PIPI_PIXELS_Y_F)
        ret = src->wrap ? wrap_sep_y_f(src, m, hvec, n, vvec)
                        : conv_sep_y_f(src, m, hvec, n, vvec);
    else
        ret = src->wrap ? wrap_sep_rgba_f(src, m, hvec, n, vvec)
                        : conv_sep_rgba_f(src, m, hvec, n, vvec);

    free(hvec);
    free(vvec);

    return ret;
}