// 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++)
img.pixels[j*img.w+i] = pixelVal(255, i>>1, i>>1, i>>1);
}
template <bool withAlpha>
void drawLine(uint32_t* dst, const uint32_t* src, size_t len)
{
for (int i = 0; i < len; i++, src++, dst++) {
if (!withAlpha) {
*dst = *src;
} else {
uint32_t sa = (*src >> 24) & 0xFF;
uint32_t da = 256 - sa;
uint32_t r = ((((*dst >> 0 ) & 0xFF) * da) >> 8) + ((((*src >> 0 ) & 0xFF) * sa) >> 8);
uint32_t g = ((((*dst >> 8 ) & 0xFF) * da) >> 8) + ((((*src >> 8 ) & 0xFF) * sa) >> 8);
uint32_t b = ((((*dst >> 16) & 0xFF) * da) >> 8) + ((((*src >> 16) & 0xFF) * sa) >> 8);
*dst = (255<<24) | (b<<16) | (g<<8) | (r<<0);
}
}
}
template <bool withAlpha>
void drawImageGeneric(Image& image, int x, int y, const Image& img, uint32_t srcX = 0, uint32_t srcY = 0, int w = -1, int h = -1)
{
#if USE_OPENGL
y = image.h - y;
#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-1-x));
for (int j = 0; j < copyH; j++)
{
drawLine<withAlpha>(dstPix, srcPix, copyW);
#if USE_OPENGL
dstPix -= image.w;
#else
dstPix += image.w;
#endif
srcPix += img.w;
}
}
void drawImage(Image& image, int x, int y, const Image& img)
{
drawImageGeneric<false>(image, x, y, img);
}
void drawImageWithAlpha(Image& image, int x, int y, const Image& img)
{
drawImageGeneric<true>(image, x, y, img);
}
void drawSprite(Image& image, int x, int y, const Sprite& s)
{
drawImageGeneric<true>(image, x, y, *s.image, s.x, s.y, s.w, s.h);
}
void drawCanvas(Image& image, 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, item.x, item.y, *item.sprite);
}
}
}
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.sprite = &s;
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);
}