// C RunTime Header Files
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <ctime>
#include <climits>
// C++ Headers
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <map>
// Project Headers
#include "Graphics.h"
#include "Common.h"
#include "MemoryMapping.h"
#include "Window.h"
#include "TrueType.h"
BEGIN_NAMESPACE
void LogMessage(LogLevel a_level, const char* a_utf8TextFmt, ...);
// Using Debug can be used for quickly adding debug output, and in release builds the calls to it shouldn't
// produce any code (if NDEBUG is defined which the ANSI C standard defines for disabling assert in release builds)
#ifdef NDEBUG
# define Debug(a_utf8TextFmt, ...) ((void)0)
#else
# define Debug(a_utf8TextFmt, ...) LogMessage(DEBUG, a_utf8TextFmt, __VA_ARGS__)
#endif
static uint32_t blendColors(uint32_t a_color1, uint32_t a_color2, uint8_t a_alpha)
{
unsigned alpha2 = 255 - a_alpha;
unsigned red = ((a_color1 >> 16) & 0xff) * a_alpha + ((a_color2 >> 16) & 0xff) * alpha2;
unsigned green = ((a_color1 >> 8) & 0xff) * a_alpha + ((a_color2 >> 8) & 0xff) * alpha2;
unsigned blue = ((a_color1 >> 0) & 0xff) * a_alpha + ((a_color2 >> 0) & 0xff) * alpha2;
return 0xff000000 | ((red & 0xff00) << 8) | (green & 0xff00) | (blue >> 8);
}
static uint32_t blendColorsF(uint32_t a_color1, uint32_t a_color2, float a_alpha)
{
float alpha2 = 1.0f - a_alpha;
float red = float((a_color1 >> 16) & 0xff) * a_alpha + float((a_color2 >> 16) & 0xff) * alpha2;
float green = float((a_color1 >> 8) & 0xff) * a_alpha + float((a_color2 >> 8) & 0xff) * alpha2;
float blue = float((a_color1 >> 0) & 0xff) * a_alpha + float((a_color2 >> 0) & 0xff) * alpha2;
return 0xff000000 | ((int(red) & 0xff) << 16) | ((int(green) & 0xff) << 8) | (int(blue) & 0xff);
}
/*
template <typename functor>
void ForEachPixel(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
functor.func();
}
}
}
*/
void DrawRectangleAlpha(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst = blendColors(*dst, a_color, a_color >> 24);
}
}
}
void DrawRectangle(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst = a_color;
}
}
}
void DrawEllipse(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height, bool a_smoothEdge)
{
int alpha = (a_color >> 24) & 0xff;
if ( alpha == 0 )
return;
int centerX = a_width/2;
int centerY = a_height/2;
int scale = 64; // This is to stop overflows with 32bit ints
if ( a_width < 256 && a_height < 256 )
scale = 1;
if ( alpha == 255 )
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
int a = (i - centerX) * a_height / scale;
int b = (j - centerY) * a_width / scale;
int c1 = a_width * a_height / (2 * scale);
int c2 = (a_width-1) * (a_height-1) / (2 * scale);
//int c2 = ((a_width+1) * (a_height+1)) / (2 * scale);
int innerR = c1*c1;
int outerR = c2*c2;
int dist = a*a + b*b;
int diffR = outerR - innerR;
int distR = dist - innerR;
int alpha = ((distR * 255) / diffR);
if ( dist < outerR )
*dst = a_color;
else if ( dist < innerR )
*dst = blendColors(a_color, *dst, alpha);
//a_antiAlias
}
}
} else {
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
int a = (i - centerX) * a_height / scale;
int b = (j - centerY) * a_width / scale;
int c = a_width * a_height / (2 * scale);
if ( (a*a + b*b) < c*c )
*dst = (alpha<<24) | (blendColors(a_color, *dst, alpha) & 0xffffff);
}
}
}
}
void DrawGradient(PixelBuffer* a_target, Gradient a_gradient, int a_x, int a_y, int a_width, int a_height)
{
for ( int j = 0; j < a_height; j++ )
for ( int i = 0; i < a_width; i++ )
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height )
{
float dist = 0.0;
if (a_gradient.m_type == RADIAL_GRADIENT)
{
int centerX = a_gradient.m_data.m_radial.m_centerX;
int centerY = a_gradient.m_data.m_radial.m_centerY;
//float errLUT[16] = { 1.1, -1.1, 2.2, -2.2, 3.3, -3.3, 4.4, -4.4, 5.5, -5.5, 6.6, -6.6, 7.7, -7.7, 8.8, -8.8 };
//int dist = (int)(errLUT[rand() % 4] + sqrt(float((centerX - i)*(centerX - i) + (centerY - j)*(centerY - j))) * 256 / a_gradient.m_data.m_radial.m_distance);
dist = sqrt(float((centerX - i)*(centerX - i) + (centerY - j)*(centerY - j))) / a_gradient.m_data.m_radial.m_distance;
/*
float distf = sqrt(float((centerX - i)*(centerX - i) + (centerY - j)*(centerY - j))) * 256 / a_gradient.m_data.m_radial.m_distance;
float frac = distf - floor(distf) - 0.51;
float frac2 = frac + 0.7;
float frac3 = float(j*8) / a_height + float(i-90) / a_width;
if ( frac < 0.1 && frac > -0.1 )
frac = ( frac < 0.0 ) ? -0.081 : 0.08;
int dist = (int)(float(frac * 64) - float(frac2 * frac3) + distf);
//dist += ;
*/
//dist = (dist > 255) ? 255 : ((dist < 0) ? 0 : dist);
}
else if (a_gradient.m_type == LINEAR_GRADIENT)
{
/*
m = dy / dx
y = mx + c;
c = y - mx;
m = (y - c) / x;
*/
// TODO:
int y1 = a_gradient.m_data.m_linear.m_y1;
int y2 = a_gradient.m_data.m_linear.m_y2;
if ( y <= y1 )
dist = 0;
else if ( y >= y2 )
dist = 1.0;
else {
dist = float(y2 - y) / (y2 - y1);
}
}
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
dist = (dist > 1.0f) ? 1.0f : ((dist < 0.0f) ? 0.0f : dist);
*dst = blendColorsF(a_gradient.m_color1, a_gradient.m_color2, dist);
}
}
}
void DrawLine(PixelBuffer* a_target, uint32_t a_color, int a_x1, int a_y1, int a_x2, int a_y2)
{
float x = 0.0f;
float y = 0.0f;
float fdy = 1.0f;
float fdx = 1.0f;
int dx = a_x2 - a_x1;
int dy = a_y2 - a_y1;
int count = 1;
if ( !dy && !dx ) {
if ( a_x1 >= 0 && a_x1 < a_target->m_width && a_y1 >= 0 && a_y1 < a_target->m_height )
a_target->m_pixels[a_y1*a_target->m_strideBytes/4 + a_x1] = a_color;
return;
}
if ( dx*dx > dy*dy ) {
count = dx;
fdy = float(dy) / dx;
} else {
count = dy;
fdx = float(dx) / dy;
}
if ( count < 0 ) {
count = -count;
fdy = -fdy;
fdx = -fdx;
}
//count++;
for (; count--; x+=fdx, y+=fdy)
{
int xi = int(a_x1 + x);
int yi = int(a_y1 + y);
if ( xi >= 0 && xi < a_target->m_width && yi >= 0 && yi < a_target->m_height )
a_target->m_pixels[yi*a_target->m_strideBytes/4 + xi] = a_color;
}
}
void DrawPixels(PixelBuffer* a_target, uint32_t* a_bits, int a_x, int a_y, int a_width, int a_height, int a_xOffset, int a_yOffset, int a_pixelsWidth, int a_pixelsHeight)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( y > 0 && y < a_target->m_height && x > 0 && x < a_target->m_width )
{
uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
uint32_t src = a_bits[(j+a_yOffset)*a_pixelsWidth+(i+a_xOffset)];
if ( src & 0xff000000 )
*dst = src;
}
}
}
void DrawPixelsAlpha(PixelBuffer* a_target, uint32_t* a_bits, int a_x, int a_y, int a_width, int a_height, int a_xOffset, int a_yOffset, int a_pixelsWidth, int a_pixelsHeight)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( y > 0 && y < a_target->m_height && x > 0 && x < a_target->m_width )
{
uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
uint32_t src = a_bits[(j+a_yOffset)*a_pixelsWidth+(i+a_xOffset)];
if ( src & 0xff000000 )
*dst = blendColors(src, *dst, (src>>24) & 0xff);
}
}
}
void DrawPixelsAlphaBlended(PixelBuffer* a_target, uint32_t* a_bits, int a_x, int a_y, int a_width, int a_height, int a_xOffset, int a_yOffset, int a_pixelsWidth, int a_pixelsHeight, int a_alpha)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( y > 0 && y < a_target->m_height && x > 0 && x < a_target->m_width )
{
uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
uint32_t src = a_bits[(j+a_yOffset)*a_pixelsWidth+(i+a_xOffset)];
if ( src & 0xff000000 )
*dst = blendColors(src, *dst, a_alpha);
}
}
}
// TODO: move to header/common, make use of else where
// perhaps PixelBuffer can be of PixelValue instead of uint32_t
struct PixelValue
{
PixelValue(uint32_t v) : m_value(v) {}
operator uint32_t() { return m_value; }
union
{
uint32_t m_value;
uint8_t m_components[4];
};
};
struct WidePixelValue
{
WidePixelValue()
{
for (int i = 0; i < 4; i++)
m_components[i] = 0;
}
WidePixelValue& operator=(PixelValue other) {
for (int i = 0; i < 4; i++)
m_components[i] = other.m_components[i];
return *this;
}
WidePixelValue& operator+=(PixelValue other) {
for (int i = 0; i < 4; i++)
m_components[i] += other.m_components[i];
return *this;
}
WidePixelValue& operator/=(int v) {
for (int i = 0; i < 4; i++)
m_components[i] /= v;
return *this;
}
operator uint32_t() {
PixelValue out(0);
for (int i = 0; i < 4; i++)
out.m_components[i] = m_components[i];
return out;
}
uint32_t m_components[4];
};
template <bool axis>
void SmoothDownSampleHelper(uint32_t* a_dstBits, int a_dstW, int a_dstH, uint32_t* a_srcBits, int a_width, int a_height)
{
int maxJ = (axis) ? a_height : a_width;
int maxI = (axis) ? a_width : a_height;
int maxDI = (axis) ? a_dstW : a_dstH;
float dDdS = float(maxDI) / float(maxI);
for (int j = 0; j < maxJ; j++) {
float d = 0.33f;
int c = 0, di = 0;
WidePixelValue col;
for (int i = 0; i < maxI; i++) {
int x = (axis) ? i : j;
int y = (axis) ? j : i;
col += a_srcBits[y * a_width + x];
c++;
d += dDdS; // DDA style (TODO: perhaps convert to fixed point)
if (d >= 1.0f) { // emit
d -= 1.0f;
col /= c;
if (axis)
a_dstBits[y * a_dstW + di] = col;
else
a_dstBits[di * a_dstW + x] = col;
c = 0;
col = 0;
di++;
if (di == maxDI)
break;
}
}
}
}
bool SmoothDownSample(uint32_t* a_dstBits, int a_dstW, int a_dstH, uint32_t* a_srcBits, int a_width, int a_height)
{
if (a_dstW > a_width || a_dstH > a_height)
return false;
uint32_t *tmpBits = new uint32_t[a_height * a_dstW]; // resize in x only
// First scale in the x-axis
SmoothDownSampleHelper<true>(tmpBits, a_dstW, a_height, a_srcBits, a_width, a_height);
// Now scale in the y-axis
SmoothDownSampleHelper<false>(a_dstBits, a_dstW, a_dstH, tmpBits, a_dstW, a_height);
delete[] tmpBits;
return true;
}
#if 0
static bool TestFileExtension(const char* a_file, const char* a_ext)
{
size_t len = strlen(a_file);
if (len < 4 || strlen(a_ext) != 4)
return false;
for (int i = 0; i < 4; i++)
if (toupper(a_file[len-4+i]) != toupper(a_ext[i]))
return false;
return true;
}
extern int DecodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32);
static bool LoadImage(const char* file, PixelBuffer& a_img, bool chromaKeyed = true)
{
if (TestFileExtension(file, ".png"))
{
// It's a PNG image
MemoryMappingData* mapping = MemoryMapping_Open(file);
if (!mapping)
return false;
// For 32bit builds, this code won't support input PNG files that are more than 4GiB, but neither can it support
// having a buffer as large as that for storing the pixels in to, so perhaps best just to use 64bit builds if dealing with massive images.
size_t siz = (size_t)MemoryMapping_GetSize(mapping);
uint8_t* buf = (uint8_t*)MemoryMapping_GetAddress(mapping);
std::vector<unsigned char> out_image;
unsigned long w, h;
if (DecodePNG(out_image, w, h, buf, siz, true))
{
a_img.m_height = h;
a_img.m_width = w;
a_img.m_strideBytes = w * sizeof(uint32_t);
a_img.m_format = PF_ARGB8888;
a_img.m_pixels = new uint32_t[w * h];
memcpy(a_img.m_pixels, out_image.data(), w * h * sizeof(uint32_t));
for ( int i = 0; i < w*h; i++ )
a_img.m_pixels[i] = ((a_img.m_pixels[i] << 16) & 0xff0000) |
((a_img.m_pixels[i] >> 16) & 0xff) | (a_img.m_pixels[i] & 0xff00ff00);
if ( chromaKeyed )
for ( int i = 0; i < w*h; i++ )
if ( a_img.m_pixels[i] != 0x00ff00 )
a_img.m_pixels[i] |= 0xff000000;
return true;
}
}
return false;
}
void DrawPixmapFile(PixelBuffer* a_target, const char* file, int x, int y, int x1, int y1, int x2, int y2)
{
static std::map<const char*,PixelBuffer> imageMap; // pixmap cache
PixelBuffer img = imageMap[file];
if (!img.m_pixels)
{
if (LoadImage(file, img, false))
{
imageMap[file] = img;
}
}
DrawPixels(a_target, img.m_pixels, x, y, x2-x1+1, y2-y1+1, x1, y1, img.m_width, img.m_height);
}
void DrawNumber(PixelBuffer* a_target, uint32_t col, int x, int y, int num, int width)
{
// LoadImage("numbers.png", g_numbersPixelsW, g_numbersPixelsH, g_numbersPixels, false);
int divisor = 1;
for (int i = width; i != 0; i--, divisor*=10)
DrawPixels(a_target, g_numbersPixels, x + (i-1)*16, y, 16, 30, 0, 30*((num/divisor)%10), g_numbersPixelsW, g_numbersPixelsH);
}
#endif
void LogMessage(LogLevel a_level, const char* a_formatString, ...)
{
char buf[1024];
va_list vaargs;
va_start(vaargs, a_formatString);
vsnprintf(buf, 1024, a_formatString, vaargs);
buf[1023] = 0; // Ensure null terminated
#ifdef _WIN32
OutputDebugStringA(buf);
#endif
puts(buf);
va_end(vaargs);
}
struct vec2i
{
int x, y;
};
// Simple 3 control point bezier curve
// p1 and p3 are the 2 end points the curve starts/ends at
// p2 is another control point but not a point on the curve
// t is the ratio between the two end points to interpolate a result for
vec2i bezierCurve(const vec2i& p1, const vec2i& p2, const vec2i& p3, double t)
{
double t2 = 1.0 - t;
return (vec2i){ int(t2*t2*p1.x + 2.0*t2*t*p2.x + t*t*p3.x),
int(t2*t2*p1.y + 2.0*t2*t*p2.y + t*t*p3.y) };
}
vec2i bezierCurveFixed(const vec2i& p1, const vec2i& p2, const vec2i& p3, uint64_t t) // t is 0 to 1023
{
uint64_t t2 = 0x10000 - t;
uint64_t t2t2 = t2*t2;
uint64_t t2t = t2*t;
uint64_t tt = t*t;
return (vec2i){ int((t2t2*p1.x + 2*t2t*p2.x + tt*p3.x) >> 32),
int((t2t2*p1.y + 2*t2t*p2.y + tt*p3.y) >> 32) };
}
void DrawCurve(PixelBuffer* a_target, uint32_t a_color, const vec2i& p1, const vec2i& p2, const vec2i& p3)
{
int dx = p1.x - p3.x;
int dy = p1.y - p3.y;
if (dx*dx > dy*dy) {
dx = (dx < 0) ? -dx : dx;
} else {
dx = (dy < 0) ? -dy : dy;
}
dx *= 2;
/*
float divisor = 1.0f / float(dx);
for (int i = 0; i < (dx-1); i++)
{
vec2i pos1 = bezierCurve(p1, p2, p3, float(i) * divisor);
vec2i pos2 = bezierCurve(p1, p2, p3, float(i + 1) * divisor);
DrawLine(a_target, a_color, pos1.x, pos1.y, pos2.x, pos2.y);
}
*/
// Fixed precision version
for (int i = 0; i < dx; i++)
{
vec2i pos1 = bezierCurveFixed(p1, p2, p3, (i * 1024) / dx);
vec2i pos2 = bezierCurveFixed(p1, p2, p3, (i * 1024 + 1024) / dx);
DrawLine(a_target, a_color, pos1.x, pos1.y, pos2.x, pos2.y);
}
}
void DrawRectangleB(PixelBuffer* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
for (int j = 0; j < a_height; j++)
for (int i = 0; i < a_width; i++)
{
int x = i + a_x;
int y = j + a_y;
if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
*dst += a_color;//0x2000000;
}
}
}
void DrawCurveB(PixelBuffer* a_target, uint32_t a_color,
const vec2i& p1, const vec2i& p2, const vec2i& p3, // 3 control points
bool firstInContour, bool lastInContour, int firstAngle) // connectivity flags
{
// if firstInContour - always output the first point
// if lastInContour - don't output the last point
// TODO: what if the angle from p1->p2->p3 makes a 'v' or '^' shape?
// if so, at the apex of this bezier curve, it won't have
// output 2 points as required.
// Probably some checking of angle between p1->p2 and p2->p3 are needed
// Definately some edge cases there.
int dy = p1.y - p3.y;
int dx = p1.x - p3.x;
int thisAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
static int lastAngle = 0;
bool drawFirst = true;
bool drawLast = true;
if (!firstInContour)
drawFirst = ((thisAngle != 0) && ((thisAngle + lastAngle) == 0))
//|| ( (thisAngle == 0) && (dx > 0))
//|| ( (thisAngle > 0) && (lastAngle == 0))
;//lastAngle != 0) );
if (lastInContour)
drawLast = (thisAngle != 0) && ((thisAngle + firstAngle) == 0);
lastAngle = thisAngle;
if (firstInContour)
drawFirst = true;
if (lastInContour) {
drawLast = (thisAngle != 0) && ((thisAngle + firstAngle) == 0);
if (thisAngle == 1 && thisAngle == firstAngle)
drawLast = true;//false;
}
dy = (dy < 0) ? -dy : dy; // dy = |dy|
dy *= 2; // over-sampling -> we try to sample
// the bezier with an even distribution of 't' values but this
// doesn't mean the resulting positions are evenly spaced along
// the curve, but because we want one sample for each y value,
// we need to over-sample the bezier curve to actually ensure we
// get what we want in an efficient way. Less efficient would be
// to try to solve the linear equation to figure out the value
// of 't' that gives a given y value. But doing the forward
// calculation twice instead is quite efficient and practical.
if (!dy) {
dy = 0;//1024;//512;
}
// Fixed precision version
if (dy)
{
//dy+=5;
int lastY = -1;
for (int i = 0; i <= dy;)// i++)
{
vec2i pos1;
bool final = false;
do {
pos1 = bezierCurveFixed(p1, p2, p3, (i << 16) / dy);
final = pos1.y == p3.y && pos1.x == p3.x;
i++;
} while (!final && (pos1.y == lastY));
bool first = pos1.y == p1.y && pos1.x == p1.x;
//bool final = pos1.y == p3.y && pos1.x == p3.x;
bool canDraw = (final && lastInContour) || pos1.y != lastY; // do we need to output something
if (first && !drawFirst)
canDraw = false;
if (final && !drawLast)
canDraw = false;
if (canDraw)
DrawRectangleB(a_target, a_color, pos1.x, pos1.y, 1, 1);
lastY = pos1.y;
if (final)
break;
}
}
else
{
if (drawFirst)
DrawRectangleB(a_target, a_color, p1.x, p1.y, 1, 1);
if (!lastInContour && drawLast)
DrawRectangleB(a_target, a_color, p3.x, p3.y, 1, 1);
}
}
Rectangle glyphBounds(const GlyphOutline& outline)
{
Rectangle ret;
int minX = INT_MAX, minY = INT_MAX;
int maxX = INT_MIN, maxY = INT_MIN;
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
const FontLineSegment& b = outline.m_lines[i];
if (b.m_x1 < minX) minX = b.m_x1; if (b.m_x1 > maxX) maxX = b.m_x1;
if (b.m_y1 < minY) minY = b.m_y1; if (b.m_y1 > maxY) maxY = b.m_y1;
if (b.m_x2 < minX) minX = b.m_x2; if (b.m_x2 > maxX) maxX = b.m_x2;
if (b.m_y2 < minY) minY = b.m_y2; if (b.m_y2 > maxY) maxY = b.m_y2;
if (b.m_x3 < minX) minX = b.m_x3; if (b.m_x3 > maxX) maxX = b.m_x3;
if (b.m_y3 < minY) minY = b.m_y3; if (b.m_y3 > maxY) maxY = b.m_y3;
}
ret.m_x = minX;
ret.m_y = minY;
ret.m_width = maxX - minX;
ret.m_height = maxY - minY;
return ret;
}
void DrawOutlineText(PixelBuffer* a_target, uint32_t a_color, const char* a_fontFamily, int a_size, int a_x, int a_y, const char* a_utf8String)
{
if (!a_target || !a_fontFamily || !a_utf8String || a_size <= 0)
{
return;
}
TtfFont& font = getFont("Arial");
while (*a_utf8String)
{
Glyph& glyph = font.getGlyph(*a_utf8String);
GlyphOutline& outline = glyph.outline;
GlyphMetrics& g = glyph.metrics;
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
FontLineSegment& b = outline.m_lines[i];
{
vec2i p1 = (vec2i){ a_x + int(b.m_x1 * a_size >> 10), a_y + a_size - int(b.m_y1 * a_size >> 10) };
vec2i p2 = (vec2i){ a_x + int(b.m_x2 * a_size >> 10), a_y + a_size - int(b.m_y2 * a_size >> 10) };
vec2i p3 = (vec2i){ a_x + int(b.m_x3 * a_size >> 10), a_y + a_size - int(b.m_y3 * a_size >> 10) };
DrawCurve(a_target, a_color, p1, p2, p3);
}
}
float spacing = 1.0;
a_x += ((g.advanceWidth * a_size) >> 10) + (int)(spacing + 0.5);
a_utf8String++;
}
}
int fixedFontSize = 10;
bool drawAAText = true;
bool drawFill = true;
bool drawOutline = false;
bool drawControlPoints = false;
std::string fontOpt = "Arial";
void RasterizeCharacter(PixelBuffer* a_target, uint32_t a_color, int a_size, int a_x, int a_y, GlyphOutline& outline)
{
int aaShift = 3;
int bias = 512;
if (drawAAText)
a_size <<= aaShift;
Rectangle rect = glyphBounds(outline);
rect.m_width = ((rect.m_width * a_size + bias) >> 10) + 3;
rect.m_height = (rect.m_height * a_size + bias) >> 10;
PixelBuffer buf = {
new uint32_t[rect.m_width*rect.m_height],
rect.m_width*4,
rect.m_width,
rect.m_height,
PF_ARGB8888
};
//memset(buf.m_pixels, 0, rect.m_width*rect.m_height*4);
for (int i = 0; i < rect.m_width*rect.m_height; i++)
buf.m_pixels[i] = 0x00C0C0C0;
vec2i lastPos{ -1, -1 };
vec2i firstPos{ -1, -1 };
int firstAngle = 0;
bool firstInContour = true;
bool lastInContour = true;
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
FontLineSegment& b = outline.m_lines[i];
{
vec2i p1 = (vec2i){ int((((b.m_x1 - rect.m_x) * a_size) + bias) >> 10), rect.m_height - int((b.m_y1 - rect.m_y) * a_size >> 10) };
vec2i p2 = (vec2i){ int((((b.m_x2 - rect.m_x) * a_size) + bias) >> 10), rect.m_height - int((b.m_y2 - rect.m_y) * a_size >> 10) };
vec2i p3 = (vec2i){ int((((b.m_x3 - rect.m_x) * a_size) + bias) >> 10), rect.m_height - int((b.m_y3 - rect.m_y) * a_size >> 10) };
firstInContour = lastInContour;
if (firstInContour)
{
int dy = p1.y - p3.y;
firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
firstPos = p1;
}
lastInContour = p3.x == firstPos.x && p3.y == firstPos.y;
/*
if (p1.x != lastPos.x || p1.y != lastPos.y)
{
firstPos = p1;
firstInContour = true;
}
lastPos = p3;
*/
DrawCurveB(&buf, 0x02000000, p1, p2, p3, firstInContour, lastInContour, firstAngle);
#if 0 // debug control points
DrawRectangle(&buf, 0xFF0000FF, p1.x-3, p1.y-3, 5, 5);
DrawRectangle(&buf, 0xFF0000FF, p2.x-3, p2.y-3, 5, 5);
DrawRectangle(&buf, 0xFF0000FF, p3.x-3, p3.y-3, 5, 5);
#endif
}
}
if (drawFill) {
#if 1 // rasterize
for (int j = 0; j < rect.m_height; j++)
{
uint8_t state = 0; // 0 off 1 off->on 2 on 3 on->off
//uint8_t w = 0;
for (int i = 0; i < rect.m_width; i++)
{
int count = buf.m_pixels[j*rect.m_width + i] >> 25;
do
{
if (count)
{
buf.m_pixels[j*rect.m_width + i] = 0x80C0C0C0 | a_color;
state = 1 - state;
count--;
}
else
{
if (state)
buf.m_pixels[j*rect.m_width + i] = 0xFF000000 | a_color;
}
}
while (count);
}
}
#endif
}
if (drawControlPoints) {
#if 1 // debug control points
lastPos = vec2i{ -1, -1 };
firstPos = vec2i{ -1, -1 };
firstInContour = true;
lastInContour = true;
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
FontLineSegment& b = outline.m_lines[i];
{
vec2i p1 = (vec2i){ int((b.m_x1 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y1 - rect.m_y) * a_size >> 10) };
vec2i p2 = (vec2i){ int((b.m_x2 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y2 - rect.m_y) * a_size >> 10) };
vec2i p3 = (vec2i){ int((b.m_x3 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y3 - rect.m_y) * a_size >> 10) };
firstInContour = lastInContour;
if (firstInContour)
{
int dy = p1.y - p3.y;
firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
firstPos = p1;
}
lastInContour = p3.x == firstPos.x && p3.y == firstPos.y;
if (firstInContour)//p1.x != lastPos.x || p1.y != lastPos.y)
{
DrawRectangle(&buf, 0xFF00FF00, p1.x-3, p1.y-3, 5, 5);
DrawRectangle(&buf, 0xFF0000FF, p2.x-3, p2.y-3, 5, 5);
DrawRectangle(&buf, 0xFF0000FF, p3.x-3, p3.y-3, 5, 5);
}
else if (lastInContour)
{
DrawRectangleAlpha(&buf, 0x1FFF7F00, p1.x-3, p1.y-3, 5, 5);
DrawRectangle(&buf, 0xFFFF4444, p2.x-3, p2.y-3, 5, 5);
DrawRectangleAlpha(&buf, 0x1F1FFF1F, p3.x-3, p3.y-3, 5, 5);
}
else
{
DrawRectangleAlpha(&buf, 0x3F808044, p1.x-1, p1.y-1, 3, 3);
DrawRectangleAlpha(&buf, 0x3FFF4444, p2.x-1, p2.y-1, 3, 3);
DrawRectangleAlpha(&buf, 0x3FFF4444, p3.x-1, p3.y-1, 3, 3);
}
lastPos = p3;
}
}
#endif
}
if (drawOutline) {
#if 1 // Draw outline overlay debug mode
lastPos = vec2i{ -1, -1 };
firstPos = vec2i{ -1, -1 };
firstInContour = true;
lastInContour = true;
firstAngle = 0;
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
FontLineSegment& b = outline.m_lines[i];
{
vec2i p1 = (vec2i){ int((b.m_x1 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y1 - rect.m_y) * a_size >> 10) };
vec2i p2 = (vec2i){ int((b.m_x2 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y2 - rect.m_y) * a_size >> 10) };
vec2i p3 = (vec2i){ int((b.m_x3 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y3 - rect.m_y) * a_size >> 10) };
firstInContour = lastInContour;
if (firstInContour)
{
int dy = p1.y - p3.y;
firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
firstPos = p1;
}
lastInContour = p3.x == firstPos.x && p3.y == firstPos.y;
DrawCurveB(&buf, 0xFF007F7F, p1, p2, p3, firstInContour, lastInContour, firstAngle);
}
}
#endif
}
if (drawAAText)
{
int w = rect.m_width >> aaShift;
int h = rect.m_height >> aaShift;
PixelBuffer downScaled = { new uint32_t[w*h], w*4, w, h, PF_ARGB8888 };
if (SmoothDownSample(downScaled.m_pixels, w, h, buf.m_pixels, rect.m_width, rect.m_height))
{
a_size >>= aaShift;
//int fy = (((rect.m_y+511)*a_size)>>10);
int fy = (rect.m_y*a_size + bias) >> 10;
int fh = rect.m_height >> aaShift;
int yOff = a_y + a_size - fh - fy;
DrawPixelsAlpha(a_target, downScaled.m_pixels, 5 + a_x + ((rect.m_x*a_size)>>10),
a_y + 2 + yOff, w, h, 0, 0, w, h);
}
delete[] downScaled.m_pixels;
}
else
{
int yOff = a_size - rect.m_height;
DrawPixels(a_target, buf.m_pixels, a_x + ((rect.m_x*a_size)>>10),
a_y + yOff - ((rect.m_y*a_size)>>10), buf.m_width, buf.m_height, 0, 0, buf.m_width, buf.m_height);
}
delete[] buf.m_pixels;
/*
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
FontLineSegment& b = outline.m_lines[i];
{
vec2i p1 = (vec2i){ a_x + int(b.m_x1 * a_size >> 10), a_y + a_size - int(b.m_y1 * a_size >> 10) };
vec2i p2 = (vec2i){ a_x + int(b.m_x2 * a_size >> 10), a_y + a_size - int(b.m_y2 * a_size >> 10) };
vec2i p3 = (vec2i){ a_x + int(b.m_x3 * a_size >> 10), a_y + a_size - int(b.m_y3 * a_size >> 10) };
DrawCurve(a_target, 0x00FF00, p1, p2, p3);
}
}
*/
}
void GetTextExtents(PixelBuffer* a_target, const char* a_fontFamily, int a_size, const char* a_utf8String, int& a_width, int& a_height)
{
float totalSpacing = 0.0f;
a_width = 0;
a_height = 0;
TtfFont& font = getFont(fontOpt.c_str());//"Arial");
Rectangle rect{ 0, 0, 0, 0 };
while (*a_utf8String)
{
Glyph& glyph = font.getGlyph(*a_utf8String);
GlyphOutline& outline = glyph.outline;
GlyphMetrics& g = glyph.metrics;
rect = glyphBounds(outline);
//rect.m_width >>= 10;
//rect.m_height >>= 10;
if (rect.m_height > a_height)
a_height = rect.m_height;
float spacing = 1.0;
totalSpacing += (spacing + 0.5);
//a_width += ((g.advanceWidth * a_size) >> 10) + (int)(spacing + 0.5);
a_width += g.advanceWidth;
a_utf8String++;
}
//a_width += rect.m_width;
a_width *= a_size;
a_width >>= 10;
a_width += int(totalSpacing);
//a_height += rect.m_height;
a_height *= a_size;
a_height >>= 10;
a_height += 4;
}
void DrawText(PixelBuffer* a_target, uint32_t a_color, const char* a_fontFamily, int a_size, int a_x, int a_y, const char* a_utf8String)
{
if (!a_target || !a_fontFamily || !a_utf8String || a_size <= 0)
{
return;
}
// TODO: Loop over the string and break on line-breaks and call some internal function which does the body of this function
//a_size = fixedFontSize;
TtfFont& font = getFont(fontOpt.c_str());//"Arial");
//TtfFont& font = getFont("Tahoma");
while (*a_utf8String)
{
Glyph& glyph = font.getGlyph(*a_utf8String);
GlyphOutline& outline = glyph.outline;
GlyphMetrics& g = glyph.metrics;
RasterizeCharacter(a_target, a_color, a_size, a_x, a_y + 1, outline);
/*
Rectangle rect = glyphBounds(outline);
// iterate the bezier curves
for (size_t i = 0; i < outline.m_lines.size(); i++)
{
FontLineSegment& b = outline.m_lines[i];
{
vec2i p1 = (vec2i){ a_x + int(b.m_x1 * a_size >> 10), a_y + a_size - int(b.m_y1 * a_size >> 10) };
vec2i p2 = (vec2i){ a_x + int(b.m_x2 * a_size >> 10), a_y + a_size - int(b.m_y2 * a_size >> 10) };
vec2i p3 = (vec2i){ a_x + int(b.m_x3 * a_size >> 10), a_y + a_size - int(b.m_y3 * a_size >> 10) };
DrawCurve(a_target, a_color, p1, p2, p3);
}
}
*/
float spacing = 1.0;
//a_x += -g.xMin + g.leftSideBearing + g.advance + (int)(spacing + 0.5);
a_x += ((g.advanceWidth * a_size) >> 10) + (int)(spacing + 0.5);
a_utf8String++;
}
// printf("done %s:%i: Here!\n", __FILE__, __LINE__);
}
/*
struct vec2f
{
float x, y;
};
vec2f operator*(const vec2f& v, float c)
{
return (vec2f){ v.x*c, v.y*c };
}
vec2f operator*(float c, const vec2f& v)
{
return (vec2f){ v.x*c, v.y*c };
}
vec2f operator+(const vec2f& v1, const vec2f& v2)
{
return (vec2f){ v1.x+v2.x, v1.y+v2.y };
}
// Generalized spline function
// a_tension -1.0 -> 1.0 Round -> Tight
// a_bias -1.0 -> 1.0 Pre shoot -> Post shoot
// a_continuity -1.0 -> 1.0 Box corners -> Inverted corners
vec2f kochanekBartelsSpline(const std::vector<vec2f>& a_points, float t, float a_tension, float a_bias, float a_continuity)
{
int pnts = a_points.size();
if (!pnts)
return (vec2f){ 0.0f, 0.0f };
t = std::max(0.0f, std::min(1.0f, t)); // clamp t to be between 0->1
int intervals = pnts - 1; // the intervals are the segments between each point
int n = std::max(0, std::min(pnts-1, int(intervals * t))); // figure out which interval t is between
t = t * intervals - n; // the new t value is the ratio between the points at n and n+1
float tmp1 = 0.5f * (1.0f - a_tension);
float A = tmp1 * (1.0f + a_continuity) * (1.0f + a_bias); // A,B,C,D are the parameter factors
float B = tmp1 * (1.0f - a_continuity) * (1.0f - a_bias);
float C = tmp1 * (1.0f - a_continuity) * (1.0f + a_bias);
float D = tmp1 * (1.0f + a_continuity) * (1.0f - a_bias);
const vec2f& pnt0 = a_points[std::max(n - 1, 0)];
const vec2f& pnt1 = a_points[n];
const vec2f& pnt2 = a_points[std::min(pnts - 1, n + 1)];
const vec2f& pnt3 = a_points[std::min(pnts - 1, n + 2)];
vec2f vA = -A*pnt0 + (2+A-B-C)*pnt1 + (-2+B+C-D)*pnt2 + D*pnt3; // vA,vB,vC - polynomial factors
vec2f vB = 2*A*pnt0 + (-3-2*A+2*B+C)*pnt1 + (3-2*B-C+D)*pnt2 + -D*pnt3;
vec2f vC = -A*pnt0 + (A-B)*pnt1 + B*pnt2;
return ((vA*t + vB)*t + vC)*t + pnt1; // blends by the factors between the 4 closest control points
}
vec2f catmulRomSpline(const std::vector<vec2f>& a_points, float t)
{
return kochanekBartelsSpline(a_points, t, 0.0f, 0.0f, 0.0f);
}
vec2f cubicSpline(const std::vector<vec2f>& a_points, float t)
{
return kochanekBartelsSpline(a_points, t, 1.0f, 0.0f, 0.0f);
}
vec2f lineSpline(const std::vector<vec2f>& a_points, float t)
{
return kochanekBartelsSpline(a_points, t, 0.0f, 0.0f, -1.0f);
}
*/
END_NAMESPACE