/* bitmap_scale_bilinear.c - Bilinear image scaling algorithm. */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2006,2007  Free Software Foundation, Inc.
 *
 *  GRUB 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/video.h>
#include <grub/bitmap.h>
#include <grub/bitmap_scale.h>
#include <grub/types.h>

/* 
 * Copy the bitmap SRC to the bitmap DST, scaling the bitmap to fit the 
 * dimensions of DST.  This function uses the bilinear interpolation algorithm
 * to interpolate the pixels.
 *
 * Supports only direct color modes which have components separated
 * into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color). 
 * But because of this simplifying assumption, the implementation is
 * greatly simplified.
 */
grub_err_t
grub_video_bitmap_scale_bilinear (struct grub_video_bitmap *dst,
                                  struct grub_video_bitmap *src)
{
  /* Verify the simplifying assumptions. */
  if (dst == 0 || src == 0)
    return grub_error (GRUB_ERR_BAD_ARGUMENT, "null bitmap in scale func");
  if (dst->mode_info.red_field_pos % 8 != 0
      || dst->mode_info.green_field_pos % 8 != 0
      || dst->mode_info.blue_field_pos % 8 != 0
      || dst->mode_info.reserved_field_pos % 8 != 0)
    return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst format not supported");
  if (src->mode_info.red_field_pos % 8 != 0
      || src->mode_info.green_field_pos % 8 != 0
      || src->mode_info.blue_field_pos % 8 != 0
      || src->mode_info.reserved_field_pos % 8 != 0)
    return grub_error (GRUB_ERR_BAD_ARGUMENT, "src format not supported");
  if (dst->mode_info.red_field_pos != src->mode_info.red_field_pos
      || dst->mode_info.red_mask_size != src->mode_info.red_mask_size
      || dst->mode_info.green_field_pos != src->mode_info.green_field_pos
      || dst->mode_info.green_mask_size != src->mode_info.green_mask_size
      || dst->mode_info.blue_field_pos != src->mode_info.blue_field_pos
      || dst->mode_info.blue_mask_size != src->mode_info.blue_mask_size
      || dst->mode_info.reserved_field_pos !=
      src->mode_info.reserved_field_pos
      || dst->mode_info.reserved_mask_size !=
      src->mode_info.reserved_mask_size)
    return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible");
  if (dst->mode_info.bytes_per_pixel != src->mode_info.bytes_per_pixel)
    return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible");
  if (dst->mode_info.width == 0 || dst->mode_info.height == 0
      || src->mode_info.width == 0 || src->mode_info.height == 0)
    return grub_error (GRUB_ERR_BAD_ARGUMENT, "bitmap has a zero dimension");

  grub_uint8_t *ddata = dst->data;
  grub_uint8_t *sdata = src->data;
  int dw = dst->mode_info.width;
  int dh = dst->mode_info.height;
  int sw = src->mode_info.width;
  int sh = src->mode_info.height;
  int dstride = dst->mode_info.pitch;
  int sstride = src->mode_info.pitch;
  /* bytes_per_pixel is the same for both src and dst. */
  int bytes_per_pixel = dst->mode_info.bytes_per_pixel;

  int dy;
  for (dy = 0; dy < dh; dy++)
    {
      int dx;
      for (dx = 0; dx < dw; dx++)
        {
          grub_uint8_t *dptr;
          grub_uint8_t *sptr;
          int sx;
          int sy;
          int comp;

          /* Compute the source coordinate that the destination coordinate
           * maps to.  Note: sx/sw = dx/dw  =>  sx = sw*dx/dw. */
          sx = sw * dx / dw;
          sy = sh * dy / dh;

          /* Get the address of the pixels in src and dst. */
          dptr = ddata + dy * dstride + dx * bytes_per_pixel;
          sptr = sdata + sy * sstride + sx * bytes_per_pixel;

          /* If we have enough space to do so, use bilinear interpolation.
           * Otherwise, fall back to nearest neighbor for this pixel. */
          if (sx < sw - 1 && sy < sh - 1)
            {
              /* Do bilinear interpolation. */

              /* Fixed-point .8 numbers representing the fraction of the
               * distance in the x (u) and y (v) direction within the 
               * box of 4 pixels in the source. */
              int u = (256 * sw * dx / dw) - (sx * 256);
              int v = (256 * sh * dy / dh) - (sy * 256);

              for (comp = 0; comp < bytes_per_pixel; comp++)
                {
                  /* Get the component's values for the 
                   * 4 source corner pixels. */
                  grub_uint8_t f00 = sptr[comp];
                  grub_uint8_t f10 = sptr[comp + bytes_per_pixel];
                  grub_uint8_t f01 = sptr[comp + sstride];
                  grub_uint8_t f11 = sptr[comp + sstride + bytes_per_pixel];

                  /* Do linear interpolations along the top and bottom
                   * rows of the box. */
                  grub_uint8_t f0y = (256 - v) * f00 / 256 + v * f01 / 256;
                  grub_uint8_t f1y = (256 - v) * f10 / 256 + v * f11 / 256;

                  /* Interpolate vertically. */
                  grub_uint8_t fxy = (256 - u) * f0y / 256 + u * f1y / 256;

                  dptr[comp] = fxy;
                }
            }
          else
            {
              /* Fall back to nearest neighbor interpolation. */
              /* Copy the pixel color value. */
              for (comp = 0; comp < bytes_per_pixel; comp++)
                dptr[comp] = sptr[comp];
            }
        }
    }
  return GRUB_ERR_NONE;
}
