/* Modified from SDL_stretch.c (SDL2).
 * Original copyright:
 *
 * Simple DirectMedia Layer
 * Copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org>
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include "image.h"

struct VG_Image * image_modif_zoom(const struct VG_Image *, int, int, VG_BOOL);

#define PRECISION      7
#define FIXED_POINT(i)  ((unsigned int)(i)  << 16)
#define SRC_INDEX(fp)   ((unsigned int)(fp) >> 16)
#define INTEGER(fp)     ((unsigned int)(fp) >> PRECISION)
#define FRAC(fp)        ((unsigned int)(fp >> (16 - PRECISION)) & ((1<<PRECISION) - 1))
#define FRAC_ZERO       0
#define FRAC_ONE        (1 << PRECISION)
#define FP_ONE          FIXED_POINT(1)

static void get_scaler_datas(int, int, int *, int *, int *, int *);
static void interpol(const struct VG_PixelColor *, const struct VG_PixelColor *, int, int, struct VG_PixelColor *);
static void interpol_bilinear(const struct VG_PixelColor *, const struct VG_PixelColor *, int, int, int, struct VG_PixelColor *);


static void
get_scaler_datas(int src_nb, int dst_nb, int *fp_start, int *fp_step, int *left_pad, int *right_pad)
{
  int step = FIXED_POINT(src_nb) / (dst_nb);  /* source step in fixed point */
  int x0 = FP_ONE / 2;     /* dst first pixel center at 0.5 in fixed point */
  int fp_sum;
  int i1;
  long tmp[2];

  /* Use this code for perfect match with pixman */
  tmp[0] = (long)step * (x0 >> 16);
  tmp[1] = (long)step * (x0 & 0xFFFF);
  x0 = (int) (tmp[0] + ((tmp[1] + 0x8000) >> 16)); /*  x0 == (step + 1) / 2  */

  /* -= 0.5, get back the pixel origin, in source coordinates  */
  x0 -= FP_ONE / 2;

  *fp_start = x0;
  *fp_step = step;
  *left_pad = 0;
  *right_pad = 0;

  fp_sum = x0;
  for (i1 = 0; i1 < dst_nb; i1++) {
    if (fp_sum < 0) {
      *left_pad += 1;
    } else {
      int index = SRC_INDEX(fp_sum);
      if (index > src_nb - 2) {
        *right_pad += 1;
      }
    }
    fp_sum += step;
  }
}


/* Interpolated == x0 + frac * (x1 - x0) == x0 * (1 - frac) + x1 * frac */
static inline void
interpol(const struct VG_PixelColor *src_x0, const struct VG_PixelColor *src_x1, int frac0, int frac1, struct VG_PixelColor *dst_px)
{
  dst_px->r = INTEGER(frac1 * src_x0->r + frac0 * src_x1->r);
  dst_px->g = INTEGER(frac1 * src_x0->g + frac0 * src_x1->g);
  dst_px->b = INTEGER(frac1 * src_x0->b + frac0 * src_x1->b);
  dst_px->a = INTEGER(frac1 * src_x0->a + frac0 * src_x1->a);
}


static inline void
interpol_bilinear(const struct VG_PixelColor *s0, const struct VG_PixelColor *s1, int frac_w0, int frac_h0, int frac_h1, struct VG_PixelColor *dst_px)
{
  struct VG_PixelColor tmp[2];
  unsigned int frac_w1 = FRAC_ONE - frac_w0;

  /* Vertical first, store to 'tmp' */
  interpol(s0,     s1,     frac_h0, frac_h1, tmp);
  interpol(s0 + 1, s1 + 1, frac_h0, frac_h1, tmp + 1);

  /* Horizontal, store to 'dst' */
  interpol(tmp,   tmp + 1, frac_w0, frac_w1, dst_px);
} /* Ende interpol */


/* image_modif_zoom:
 * return new zoomed image
 * @param pxsrc        image
 * @param zoom_width   new width in pixels
 * @param zoom_height  new height in pixels
 * @param smooth       whether using linear zooming
 * @return  new image, or NULL = no change
 */
struct VG_Image *
image_modif_zoom(const struct VG_Image *pxsrc, int zoom_width, int zoom_height, VG_BOOL smooth)
{
  int i1;
  struct VG_PixelColor *dst_px;
  int src_w, src_h, dst_w, dst_h;
  struct VG_Image *pxdst;

  if (pxsrc == NULL) { outerr("image NULL"); return NULL; }

  src_w = pxsrc->img.w;
  src_h = pxsrc->img.h;
  dst_w = zoom_width;
  dst_h = zoom_height;
  if (dst_w <= 0 || dst_h <= 0) { return vg4->image->create(1, 1); }
  if (dst_w == src_w && dst_h == src_h) { return NULL; }

  pxdst = vg4->image->create(dst_w, dst_h);
  dst_px = pxdst->img.pixels;

  if (!smooth) {
    unsigned int posy, incy;
    unsigned int posx, incx;
    int srcx, srcy, n1;
    struct VG_PixelColor *src_px;
  
    incy = (src_h << 16) / dst_h;
    incx = (src_w << 16) / dst_w;
    posy = incy / 2;

    for (i1 = 0; i1 < dst_h; i1++) {
      srcy = (posy >> 16);
      src_px = &pxsrc->img.pixels[srcy * src_w];
      posy += incy;
      posx = incx / 2;
      n1 = dst_w;

      while (n1--) {
        srcx = (int)(posx >> 16);
        posx += incx;
        *dst_px++ = src_px[srcx];
      }
    }

  } else {
    int fp_sum_h, fp_step_h, left_pad_h, right_pad_h;
    int fp_sum_w, fp_step_w, left_pad_w, right_pad_w;
    int fp_sum_w_init, left_pad_w_init, right_pad_w_init, middle_init;

    get_scaler_datas(src_h, dst_h, &fp_sum_h, &fp_step_h, &left_pad_h, &right_pad_h);
    get_scaler_datas(src_w, dst_w, &fp_sum_w, &fp_step_w, &left_pad_w, &right_pad_w);

    fp_sum_w_init    = fp_sum_w + left_pad_w * fp_step_w;
    left_pad_w_init  = left_pad_w;
    right_pad_w_init = right_pad_w;
    middle_init      = dst_w - left_pad_w - right_pad_w;

    for (i1 = 0; i1 < dst_h; i1++) {
      int index_h, frac_h0, frac_h1, middle;
      const struct VG_PixelColor *src_h0, *src_h1;
      int no_padding, incr_h0, incr_h1;

      no_padding = !(i1 < left_pad_h || i1 > dst_h - 1 - right_pad_h);
      index_h    = SRC_INDEX(fp_sum_h);
      frac_h0    = FRAC(fp_sum_h);

      index_h = no_padding ? index_h : (i1 < left_pad_h ? 0 : src_h - 1);
      frac_h0 = no_padding ? frac_h0 : 0;
      incr_h1 = no_padding ? src_w : 0;
      incr_h0 = index_h * src_w;

      src_h0 = &pxsrc->img.pixels[incr_h0];
      src_h1 = &src_h0[incr_h1];

      fp_sum_h += fp_step_h;

      frac_h1  = FRAC_ONE - frac_h0;
      fp_sum_w = fp_sum_w_init;
      right_pad_w = right_pad_w_init;
      left_pad_w  = left_pad_w_init;
      middle      = middle_init;

      while (left_pad_w--) {
        interpol_bilinear(src_h0, src_h1, FRAC_ZERO, frac_h0, frac_h1, dst_px);
        dst_px += 1;
      }

      while (middle--) {
        const struct VG_PixelColor *s_00_01;
        const struct VG_PixelColor *s_10_11;
        int index_w = SRC_INDEX(fp_sum_w);
        int frac_w = FRAC(fp_sum_w);

        fp_sum_w += fp_step_w;
/*
        x00 ... x0_ ..... x01
        .       .         .
        .       x         .
        .       .         .
        .       .         .
        x10 ... x1_ ..... x11
*/
        s_00_01 = &src_h0[index_w];
        s_10_11 = &src_h1[index_w];

        interpol_bilinear(s_00_01, s_10_11, frac_w, frac_h0, frac_h1, dst_px);
        dst_px += 1;
      }

      while (right_pad_w--) {
        int index_w = (src_w - 2);
        const struct VG_PixelColor *s_00_01 = &src_h0[index_w];
        const struct VG_PixelColor *s_10_11 = &src_h1[index_w];
        interpol_bilinear(s_00_01, s_10_11, FRAC_ONE, frac_h0, frac_h1, dst_px);
        dst_px += 1;
      }
    }
  }

  return pxdst;
} /* Ende image_modif_zoom */
