303 lines
7.1 KiB
C++
303 lines
7.1 KiB
C++
#include <string>
|
|
#include <map>
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <cmath>
|
|
#include "hqn_surface.h"
|
|
#include <SDL_platform.h>
|
|
|
|
namespace hqn
|
|
{
|
|
|
|
// basically inlined functions for bounds checking
|
|
// Note that when #include'in windows.h you need to #define NOMINMAX becase
|
|
// windows.h breaks std::min and std::max by #defining min and max. ugh
|
|
#define boundLeft(x) std::max((int)(x), 0)
|
|
#define boundRight(x) std::min((int)(x), (int)m_width - 1)
|
|
#define boundTop(y) std::max((int)(y), 0)
|
|
#define boundBottom(y) std::min((int)(y), (int)m_height - 1)
|
|
|
|
// const float pi = 3.14159265359;
|
|
const float pi = 3.14159265359f;
|
|
|
|
// Color masks for building the SDL_Surface.
|
|
// They're constant because why not?
|
|
const Color MASK_R { 0xff, 0, 0, 0 };
|
|
const Color MASK_G { 0, 0xff, 0, 0 };
|
|
const Color MASK_B { 0, 0, 0xff, 0 };
|
|
const Color MASK_A { 0, 0, 0, 0xff };
|
|
|
|
|
|
// Copied from http://forum.devmaster.net/t/fast-and-accurate-sine-cosine/9648
|
|
float fastSin(float x)
|
|
{
|
|
const float B = 4/pi;
|
|
const float C = -4/(pi*pi);
|
|
|
|
float y = B * x + C * x * abs(x);
|
|
|
|
// const float Q = 0.775;
|
|
const float P = 0.225f;
|
|
|
|
y = P * (y * abs(y) - y) + y; // Q * y + P * y * abs(y)
|
|
return y;
|
|
}
|
|
|
|
float fastCos(float x)
|
|
{
|
|
return fastSin(x - pi / 2);
|
|
}
|
|
|
|
Surface::Surface(size_t w, size_t h)
|
|
{
|
|
m_pixels = new Color[w * h];
|
|
// initialize all pixels to transparent black
|
|
memset(m_pixels, 0, sizeof(Color) * w * h);
|
|
// set default colors
|
|
m_width = (int)w;
|
|
m_height = (int)h;
|
|
setBlendMode(HQN_BLENDMODE_BLEND);
|
|
|
|
// Create an SDL Surface we can blit to
|
|
m_surface = SDL_CreateRGBSurfaceFrom(m_pixels, w, h, 32, w * sizeof(Color),
|
|
MASK_R.bits, MASK_G.bits, MASK_B.bits, MASK_A.bits);
|
|
}
|
|
|
|
Surface::~Surface()
|
|
{
|
|
SDL_FreeSurface(m_surface);
|
|
m_surface = nullptr;
|
|
delete[] m_pixels;
|
|
}
|
|
|
|
void Surface::drawRect(int x, int y, size_t w, size_t h, Color color)
|
|
{
|
|
int x2 = x + w;
|
|
int y2 = y + h;
|
|
|
|
int x1Bound = boundLeft(x);
|
|
int y1Bound = boundTop(y) + 1;
|
|
int x2Bound = boundRight(x2) - 1;
|
|
int y2Bound = boundBottom(y2);
|
|
// draw vertical lines
|
|
if (x >= 0)
|
|
{
|
|
for (int yy = y1Bound; yy < y2Bound; yy++)
|
|
rawset(x, yy, color);
|
|
}
|
|
if (x2 < (int)m_width)
|
|
for (int yy = y1Bound; yy < y2Bound; yy++)
|
|
rawset(x2, yy, color);
|
|
// draw horizontal lines
|
|
if (y >= 0)
|
|
for (int xx = x1Bound; xx < x2Bound; xx++)
|
|
rawset(xx, y, color);
|
|
if (y2 < (int)m_height)
|
|
for (int xx = x1Bound; xx < x2Bound; xx++)
|
|
rawset(xx, y2, color);
|
|
}
|
|
|
|
void Surface::fillRect(int x, int y, size_t w, size_t h, Color fg, Color bg)
|
|
{
|
|
// draw the outline
|
|
drawRect(x, y, w, h, fg);
|
|
// now fill in the middle
|
|
int x2 = boundRight(x + w - 1);
|
|
int y2 = boundBottom(y + h - 1);
|
|
|
|
x = boundLeft(x);
|
|
y = boundTop(y);
|
|
for (int yy = y + 1; yy < y2; yy++)
|
|
for (int xx = x + 1; xx < x2; xx++)
|
|
rawset(xx, yy, bg);
|
|
}
|
|
|
|
|
|
#define SWAP(a, b, temp) temp = a; a = b; b = temp
|
|
void Surface::fastLine(int x1, int y1, int x2, int y2, Color color)
|
|
{
|
|
int temp;
|
|
// Make sure x1 <= x2
|
|
if (x1 > x2)
|
|
{
|
|
SWAP(x1, x2, temp);
|
|
SWAP(y1, y2, temp);
|
|
}
|
|
// If any of the points are outside the surface then don't draw
|
|
if (x1 < 0 || y1 < 0 || y1 >= m_height
|
|
|| x2 >= m_width || y2 < 0 || y2 >= m_height)
|
|
return;
|
|
|
|
// special case
|
|
if (x1 == x2)
|
|
{
|
|
// safety checks
|
|
if (y1 > y2)
|
|
{
|
|
SWAP(y1, y2, temp);
|
|
}
|
|
for (int y = y1; y <= y2; y++)
|
|
rawset(x1, y, color);
|
|
// that's all folks
|
|
}
|
|
else
|
|
{
|
|
const int deltaX = x2 - x1;
|
|
const int deltaY = y2 - y1;
|
|
// absolute value of the slope of the line
|
|
const float deltaErr = std::abs((float)deltaY / deltaX);
|
|
// calculate the sign of delta Y
|
|
const int signDY = deltaY ? deltaY / abs(deltaY) : 0;
|
|
float error = 0;
|
|
int y = y1;
|
|
for (int x = x1; x < x2; x++)
|
|
{
|
|
rawset(x, y, color);
|
|
error += deltaErr;
|
|
while (error >= 0.5)
|
|
{
|
|
rawset(x, y, color);
|
|
y += signDY;
|
|
error -= 1.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Surface::clear(Color color) {
|
|
int dataSize = m_width * m_height;
|
|
for (int i = 0; i < dataSize; i++)
|
|
{
|
|
m_pixels[i] = color;
|
|
}
|
|
}
|
|
|
|
void Surface::safeLine(int x1, int y1, int x2, int y2, Color color)
|
|
{
|
|
// can be used a lot
|
|
int temp;
|
|
// Make sure x1 <= x2
|
|
if (x1 > x2)
|
|
{
|
|
SWAP(x1, x2, temp);
|
|
SWAP(y1, y2, temp);
|
|
}
|
|
// Special case
|
|
if (x1 == x2)
|
|
{
|
|
if (y1 > y2)
|
|
{
|
|
SWAP(y1, y2, temp);
|
|
}
|
|
int y1Bound = boundTop(y1);
|
|
int y2Bound = boundBottom(y2);
|
|
for (int y = y1Bound; y < y2Bound; y++)
|
|
rawset(x1, y, color);
|
|
// that's all folks
|
|
}
|
|
else
|
|
{
|
|
// Prep the variables
|
|
// Remember y = mx + b
|
|
const int deltaX = x2 - x1;
|
|
const int deltaY = y2 - y1;
|
|
const float slope = (float)deltaY / deltaX;
|
|
const float deltaErr = std::abs(slope);
|
|
const int signDY = deltaY ? deltaY / std::abs(deltaY) : 0;
|
|
|
|
// Ensure the line is inside the bounds
|
|
if (x1 < 0 || y1 < 0 || y2 < 0)
|
|
{
|
|
}
|
|
float error = 0;
|
|
int y = y1;
|
|
for (int x = x1; x < x2; x++)
|
|
{
|
|
rawset(x, y, color);
|
|
error += deltaErr;
|
|
while (error >= 0.5)
|
|
{
|
|
// safe setPixel instead of rawset
|
|
setPixel(x, y, color);
|
|
y += signDY;
|
|
error -= 1.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Surface::setPixel(int x, int y, Color color)
|
|
{
|
|
if (x < 0 || y < 0 || x >= (int)m_width || y >= (int)m_height)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
rawset(x, y, color);
|
|
}
|
|
}
|
|
|
|
void Surface::setBlendMode(BlendMode mode)
|
|
{
|
|
switch(mode)
|
|
{
|
|
case HQN_BLENDMODE_NONE:
|
|
m_blendFunc = &blendNone;
|
|
break;
|
|
case HQN_BLENDMODE_BLEND:
|
|
m_blendFunc = &blendBlend;
|
|
break;
|
|
case HQN_BLENDMODE_ADD:
|
|
m_blendFunc = &blendAdd;
|
|
break;
|
|
case HQN_BLENDMODE_MOD:
|
|
m_blendFunc = &blendMod;
|
|
break;
|
|
default:
|
|
// skip setting m_blend
|
|
return;
|
|
}
|
|
m_blend = mode;
|
|
}
|
|
|
|
BlendMode Surface::getBlendMode() const
|
|
{
|
|
return m_blend;
|
|
}
|
|
|
|
|
|
#define BYTE(exp) (uint8_t)((exp) / 255)
|
|
Color Surface::blendBlend(Color src, Color dst)
|
|
{
|
|
const uint8_t invA = 255 - src.a;
|
|
dst.r = BYTE(src.r * src.a + dst.r * invA);
|
|
dst.g = BYTE(src.g * src.a + dst.g * invA);
|
|
dst.b = BYTE(src.b * src.a + dst.b * invA);
|
|
dst.a = (uint8_t)(src.a + (dst.a * invA));
|
|
return dst;
|
|
}
|
|
|
|
Color Surface::blendNone(Color src, Color dst)
|
|
{
|
|
return src;
|
|
}
|
|
|
|
Color Surface::blendAdd(Color src, Color dst)
|
|
{
|
|
dst.r = BYTE(src.r * src.a + dst.r);
|
|
dst.g = BYTE(src.g * src.a + dst.g);
|
|
dst.b = BYTE(src.g * src.a + dst.b);
|
|
return dst;
|
|
}
|
|
|
|
Color Surface::blendMod(Color src, Color dst)
|
|
{
|
|
dst.r = BYTE(src.r * dst.r);
|
|
dst.g = BYTE(src.g * dst.g);
|
|
dst.b = BYTE(src.b * dst.b);
|
|
return dst;
|
|
}
|
|
|
|
}
|