Newer
Older
Import / research / match3 / canvas.cpp
//  DescriptionHere - canvas.cpp
//  Created by John Ryland (jryland@xiaofrog.com), 29/10/2017
//  Copyright (c) 2017 InvertedLogic
//  All rights reserved.
#include <stdlib.h>
#include <stdio.h>
#include <png.h>
#include <algorithm>
#include "canvas.h"

bool loadSpriteSheet(SpriteSheet& sheet, const char* fileName)
{
  FILE *file = fopen(fileName, "r");
  if ( !file ) {
    printf("Error opening file %s\n", fileName);
    return false;
  }
  char buf[1024];
  if (!fscanf(file, "%s\n", buf)) {
    printf("Error reading file %s\n", fileName);
    fclose(file);
    return false;
  }
  loadPngImage(sheet.image, buf);
  while (fgets(buf, 1024, file)) {
    Sprite s;
    s.image = &sheet.image;
    sscanf(buf, "%u %u %u %u\n", &s.x, &s.y, &s.w, &s.h);
    sheet.sprites.push_back(s);
    printf("next sprite: %u %u %u %u\n", s.x, s.y, s.w, s.h);
  }
  fclose(file);
  return true;
}

bool loadPngImage(Image& img, const char *pngFileName)
{
  /* Open and initialise */
  FILE *file = fopen(pngFileName, "r");
  if ( !file ) {
    printf("Error opening file %s\n", pngFileName);
    return false; 
  }
  png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
  png_infop cinfo = png_create_info_struct(png);
  png_infop cinfo_end = png_create_info_struct(png);

  png_init_io(png, file);
  png_read_info(png, cinfo);

  /* Setup image */
  img.w = png_get_image_width(png, cinfo);
  img.h = png_get_image_height(png, cinfo);
  unsigned char **scanLine = (unsigned char**)malloc(sizeof(char*)*img.h);
  img.pixels.resize(img.w * img.h);
  for (int i = img.h - 1; i >= 0; i--)
    scanLine[i] = (unsigned char*)&img.pixels[i*img.w];

  /* Decode image */
  png_start_read_image(png);
  png_read_image(png, (png_bytepp)scanLine);

  /* Clean up */
  png_read_end(png, cinfo_end);
  png_destroy_read_struct(&png, &cinfo, &cinfo_end);
  free(scanLine);
  return true;
}

void drawTestGradient(Image& img)
{
  for (int j = 0; j < img.h; j++)
    for (int i = 0; i < img.w; i++) {
      int x = (i * 400) / img.w;
      int y = (j * 600) / img.h;
      int v = (x + 150 + y % 5) >> 2;
      img.pixels[j*img.w+i] = pixelVal(255, v, v+30, v+50);
    }
}

namespace AlphaMode {
  enum
  {
    None,            // src is copied to dst - alpha ignored
    AlphaAsMask,     // if src.alpha > threshold, then src is copied to dst
    ConstAlpha,      // src is blended with constant alpha in to dst
    MaskConstAlpha,  // if src.alpha > threshold, then src is blended with constant alpha in to dst
    SrcAlpha,        // src is blended with src.alpha in to dst
    SrcAndConstAlpha,// src is blended with src.alpha times constant alpha in to dst
  };
}

template <int mode> // bool withAlpha, bool alphaAsMask, bool useConstAlpha>
void blendPixels(uint32_t* dst, const uint32_t* src, int alphaThreshold, uint8_t constAlpha)
{
  uint32_t col = *src;
  if (mode != AlphaMode::None) {
    uint32_t sa = (col >> 24) & 0xFF;
    if ((mode == AlphaMode::AlphaAsMask || mode == AlphaMode::MaskConstAlpha) && sa < alphaThreshold)
      return;
    if (mode == AlphaMode::ConstAlpha || mode == AlphaMode::MaskConstAlpha)
      sa = constAlpha;
    if (mode == AlphaMode::SrcAndConstAlpha)
      sa = (sa * constAlpha) >> 8;
    if (mode == AlphaMode::ConstAlpha || mode == AlphaMode::MaskConstAlpha
        || mode == AlphaMode::SrcAlpha   || mode == AlphaMode::SrcAndConstAlpha) {
      uint32_t da = 256 - sa;
      uint32_t r = ((((*dst >> 0 ) & 0xFF) * da) >> 8) + ((((col >> 0 ) & 0xFF) * sa) >> 8);
      uint32_t g = ((((*dst >> 8 ) & 0xFF) * da) >> 8) + ((((col >> 8 ) & 0xFF) * sa) >> 8);
      uint32_t b = ((((*dst >> 16) & 0xFF) * da) >> 8) + ((((col >> 16) & 0xFF) * sa) >> 8);
      col = (255<<24) | (b<<16) | (g<<8) | (r<<0);
    }
  }
  *dst = col;
}

template <int mode> // bool withAlpha, bool alphaAsMask, bool useConstAlpha>
void drawLine(uint32_t* dst, const uint32_t* src, size_t len, int alphaThreshold, uint8_t constAlpha)
{
  for (int i = 0; i < len; i++, src++, dst++) {
    blendPixels<mode>(dst, src, alphaThreshold, constAlpha);
  }
}

template <int AlphaMode>
void drawImageGeneric(Image& image, int x, int y, const Image& img, uint8_t cAlpha = 128, uint32_t srcX = 0, uint32_t srcY = 0, int w = -1, int h = -1)
{
#if USE_RETINA
  x *= 2;
  y *= 2;
#endif
  if (w < 0) w = img.w;
  if (h < 0) h = img.h;
  if (x < 0) { srcX += -x; w += x; x = 0; }
  if (y < 0) { srcY += -y; h += y; y = 0; }
  if (x < 0 || y < 0 || x >= image.w || y >= image.h || srcX >= img.w || srcY >= img.h || w <= 0 || h <= 0)
    return;
  const uint32_t *srcPix = &img.pixels[srcY*img.w + srcX];
  uint32_t *dstPix = &image.pixels[y*image.w + x];
  int copyH = std::min(h, std::min<int>(img.h-srcY, image.h-y));
  int copyW = std::min(w, std::min<int>(img.w-srcX, image.w-x));
#if USE_OPENGL
  dstPix = &image.pixels[(image.h - y - 1)*image.w + x];
#endif
  if (copyH <= 0 || copyW <= 0)
    return;
  for (int j = 0; j < copyH; j++)
  {
    drawLine<AlphaMode>(dstPix, srcPix, copyW, 32, cAlpha);
#if USE_OPENGL
    dstPix -= image.w;
#else
    dstPix += image.w;
#endif
    srcPix += img.w;
  }
}

// TODO: refactor to make template that has special cases for no scaling, up scaling and down scaling
template <int AlphaMode>
void drawUpScaleImage(Image& image, int dstX, int dstY, int dstW, int dstH, const Image& img, uint8_t alpha, int srcX, int srcY, int srcW, int srcH)
{
#if USE_RETINA
  dstX *= 2;
  dstY *= 2;
#endif
  if (dstW == 0 || dstH == 0)
  {
    return;
  }
  if (dstW <= 0 || dstH <= 0 || srcX < 0 || srcY < 0 || srcW <= 0 || srcH <= 0)
  {
    printf("bad inputs to drawUpScaleImage\n");
    return;
  }
  /*
  if (dstW < srcW || dstH < srcH)
  {
    printf("non-upscale inputs to drawUpScaleImage\n");
    printf("dims:  dstW %i  srcW %i\n", dstW, srcW);
    printf("dims:  dstH %i  srcH %i\n", dstH, srcH);
    exit(0);
    return;
  }
  */
  if (dstX < 0) { srcX += -dstX * srcW / dstW; dstW += dstX; dstX = 0; }
  if (dstY < 0) { srcY += -dstY * srcH / dstH; dstH += dstY; dstY = 0; }
  if (dstX >= image.w || dstY >= image.h || srcX >= img.w || srcY >= img.h)
    return;

  for (int j = 0; j < dstH; j++)
  {
    int sy = srcY + j * srcH / dstH;
    if (sy >= img.h || (dstY+j) >= image.h)
      return;
    for (int i = 0; i < dstW; i++)
    {
      int sx = srcX + i * srcW / dstW;
      if (sx >= img.w || (dstX+i) >= image.w)
        break;
#if USE_OPENGL
      uint32_t* dst = &image.pixels[(image.h - (1+dstY+j))*image.w + dstX + i];// = img.pixels[sy*img.w + sx];
#else
      uint32_t* dst = &image.pixels[(dstY+j)*image.w + dstX + i];// = img.pixels[sy*img.w + sx];
#endif
      blendPixels<AlphaMode>(dst, &img.pixels[sy*img.w + sx], 32, alpha);
    }
  }
}

void drawImage(Image& image, int x, int y, const Image& img)
{
  drawImageGeneric<AlphaMode::None>(image, x, y, img);
}

void drawImageWithMask(Image& image, int x, int y, const Image& img)
{
  drawImageGeneric<AlphaMode::AlphaAsMask>(image, x, y, img);
}

void drawImageWithAlpha(Image& image, int x, int y, const Image& img)
{
  drawImageGeneric<AlphaMode::SrcAlpha>(image, x, y, img);
}

void drawImageWithMaskWithConstantAlpha(Image& image, int x, int y, const Image& img, uint8_t alpha)
{
  drawImageGeneric<AlphaMode::MaskConstAlpha>(image, x, y, img, alpha);
}

void drawScaledSprite(Image& image, int x, int y, int w, int h, const Sprite& s)
{
  drawUpScaleImage<AlphaMode::None>(image, x, y, w, h, *s.image, 255, s.x, s.y, s.w, s.h);
}

/*
void drawSprite(Image& image, int x, int y, const Sprite& s, uint8_t alpha)
{
  drawImageGeneric<AlphaMode::MaskConstAlpha>(image, x, y, *s.image, alpha, s.x, s.y, s.w, s.h);
}
*/

void drawSprite(Image& image, int x, int y, const Sprite& s, uint8_t alpha, int mode, float xScaling, float yScaling)
{
  if (mode == 0)
    drawImageGeneric<AlphaMode::AlphaAsMask>(image, x, y, *s.image, alpha, s.x, s.y, s.w, s.h);
  else if (mode == 1)
    drawImageGeneric<AlphaMode::MaskConstAlpha>(image, x, y, *s.image, alpha, s.x, s.y, s.w, s.h);
  else if (mode == 2)
    drawImageGeneric<AlphaMode::SrcAlpha>(image, x, y, *s.image, alpha, s.x, s.y, s.w, s.h);
  else if (mode == 3)
    drawImageGeneric<AlphaMode::SrcAndConstAlpha>(image, x, y, *s.image, alpha, s.x, s.y, s.w, s.h);
  else if (mode == 4)
    drawUpScaleImage<AlphaMode::SrcAndConstAlpha>(image,
          x + int(0.5 * s.w * (1.0 - xScaling)), y + int(0.5 * s.h * (1.0 - yScaling)),
                      int(s.w*xScaling), int(s.h*yScaling), *s.image, alpha, s.x, s.y, s.w, s.h);
    //drawImageGeneric<AlphaMode::SrcAndConstAlpha>(image, x, y, *s.image, alpha, s.x, s.y, s.w, s.h);
  else
    printf("bad drawSprite mode\n");
}

void drawCanvas(Image& image, int x, int y, const Canvas& canvas)
{
  //drawTestGradient(image);
  //drawImage(image, 0, 0, canvas.backgroundImage);
  for (int i = 0; i < canvas.sprites.size(); i++) {
    const CanvasItem& item = canvas.sprites[i];
    if (item.visible) {
      drawSprite(image, (x+item.x), (y+item.y), *item.sprite, item.alpha, item.mode, item.xScaling, item.yScaling);
    }
  }
}

void canvasAddSprite(Canvas& canvas, int x, int y, const Sprite& s)
{
  CanvasItem item;
  item.visible = true;
  item.frame = 0;
  item.x = x;
  item.y = y;
  item.alpha = 255;
  item.mode = 2;
  item.sprite = &s;
  item.xScaling = 1.0f;
  item.yScaling = 1.0f;
  canvas.sprites.push_back(item);
}

template <bool findClosestToCenter, bool testAlpha>
int canvasHitTestGeneric(const Canvas& canvas, int x, int y, int alphaThreshold)
{
  int closestIndex = -1;
  int closestDistSqr = INT_MAX;
  // Assuming the array's order is used for z-ordering with painters algorithm, the last drawn
  // items will draw on top so be the first to be hit by the mouse, therefore we search in reverse order.
  for (int i = canvas.sprites.size() - 1; i >= 0; i--)
  {
    const CanvasItem& item = canvas.sprites[i];
    // TODO: could use a quad-tree to store the sprites in if want to scale to large number of canvas items
    if (x >= item.x && x <= item.x+item.sprite->w &&
        y >= item.y && y <= item.y+item.sprite->h)
    {
      if (testAlpha)
      {
        const Sprite& s = *item.sprite;
        uint32_t pix = s.image->pixels[(s.y+y-item.y)*s.image->w + s.x+x-item.x];
        if (((pix >> 24) & 0xFF) < alphaThreshold) {
          continue;
        }
      }
      if (!findClosestToCenter) {
        return i;
      }
      int itemCenterX = item.x + item.sprite->w / 2;
      int itemCenterY = item.y + item.sprite->h / 2;
      int distSqr = (itemCenterX - x) * (itemCenterX - x) + (itemCenterY - y) * (itemCenterY - y);
      if (distSqr < closestDistSqr) {
        closestDistSqr = distSqr;
        closestIndex = i;
      }
    }
  }
  return closestIndex;
}

int canvasHitTest(const Canvas& canvas, int x, int y)
{
  return canvasHitTestGeneric<false, false>(canvas, x, y, 0);
}

int canvasHitTestWithAlpha(const Canvas& canvas, int x, int y)
{
  return canvasHitTestGeneric<false, true>(canvas, x, y, 32);
}