Skip to content

Instantly share code, notes, and snippets.

@lorenbrichter
Created December 10, 2010 01:50
Show Gist options
  • Save lorenbrichter/735636 to your computer and use it in GitHub Desktop.
Save lorenbrichter/735636 to your computer and use it in GitHub Desktop.
#include "Painting.h"
#include "Bezier.h"
#include "ProgressOp.h"
#include <math.h>
#include <sys/time.h>
#include <float.h>
#define MAX_ZOOM 12.0
#define MIN_ZOOM -40.0
#define SAVE_STROKE_INFO(STROKEPTR, SAVE1, SAVE2, SAVE3) do { SAVE1 = STROKEPTR->commands.strokeCommand.nextStartDrawIndex; SAVE2 = STROKEPTR->commands.strokeCommand.savePoint; SAVE3 = STROKEPTR->commands.strokeCommand.saveRadius; } while(0)
#define RESTORE_STROKE_INFO(STROKEPTR, SAVE1, SAVE2, SAVE3) do { STROKEPTR->commands.strokeCommand.nextStartDrawIndex = SAVE1; STROKEPTR->commands.strokeCommand.savePoint = SAVE2; STROKEPTR->commands.strokeCommand.saveRadius = SAVE3; } while(0)
#define UNSET_PARTIAL_STROKE_INFO(STROKEPTR) do { STROKEPTR->commands.strokeCommand.nextStartDrawIndex = 0; STROKEPTR->commands.strokeCommand.savePoint = Vector2D_(-FLT_MAX, -FLT_MAX); STROKEPTR->commands.strokeCommand.saveRadius = 0.0f; } while(0)
static PaintStroke* PaintLayer_topStroke(PaintLayer *layer);
// cool ease in / ease out 0->1
static float Perlin_fade(float t)
{
return t * t * t * (t * (t * 6 - 15) + 10);
}
#define Perlin_lerp(t, a, b) lerp(a, b, t)
static float Perlin_grad(int hash, float x, float y, float z)
{
int h = hash & 15;
float u = h < 8 ? x : y;
float v = h < 4 ? y : ((h == 12) || (h == 14)) ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
static int Perlin_numbers[512];
static int Perlin_permutation[] = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 };
static void Perlin_init()
{
static int once = 1;
if(once)
{
once = 0;
int i;
for(i = 0; i < 256; ++i)
{
Perlin_numbers[i] = Perlin_permutation[i];
Perlin_numbers[256+i] = Perlin_numbers[i];
}
}
}
static float Perlin_noise(float x, float y, float z)
{
int X = (int)floorf(x);
int Y = (int)floorf(y);
int Z = (int)floorf(z);
x -= floorf(x);
y -= floorf(y);
z -= floorf(z);
float u = Perlin_fade(x);
float v = Perlin_fade(y);
float w = Perlin_fade(z);
int A = Perlin_numbers[X ]+Y;
int AA = Perlin_numbers[A]+Z;
int AB = Perlin_numbers[A+1]+Z;
int B = Perlin_numbers[X+1]+Y;
int BA = Perlin_numbers[B]+Z;
int BB = Perlin_numbers[B+1]+Z;
return Perlin_lerp(w, Perlin_lerp(v, Perlin_lerp(u, Perlin_grad(Perlin_numbers[AA ], x , y , z ), // AND ADD
Perlin_grad(Perlin_numbers[BA ], x-1, y , z )), // BLENDED
Perlin_lerp(u, Perlin_grad(Perlin_numbers[AB ], x , y-1, z ), // RESULTS
Perlin_grad(Perlin_numbers[BB ], x-1, y-1, z ))),// FROM 8
Perlin_lerp(v, Perlin_lerp(u, Perlin_grad(Perlin_numbers[AA+1], x , y , z-1 ), // CORNERS
Perlin_grad(Perlin_numbers[BA+1], x-1, y , z-1 )), // OF CUBE
Perlin_lerp(u, Perlin_grad(Perlin_numbers[AB+1], x , y-1, z-1 ),
Perlin_grad(Perlin_numbers[BB+1], x-1, y-1, z-1 ))));
}
static unsigned char GlowBitmap[16*16*2];
static void GenerateGlowTexture()
{
int ix, iy;
Vector2D center = Vector2D_(0.0, 0.0);
int size = 16;
unsigned char *bitmap = GlowBitmap;
for(iy = 0; iy < size; ++iy)
{
for(ix = 0; ix < size; ++ix)
{
float value = 1.0;
unsigned char *lum = &bitmap[size * iy * 2 + ix * 2 + 0];
unsigned char *alp = &bitmap[size * iy * 2 + ix * 2 + 1];
Vector2D p = Vector2D_(((float)ix / (float)(size-1)) * 0.5, ((float)iy / (float)(size-1)) * 0.5);
float dist = Vector2D_distance(p, center) * 2.0;
if(dist > 1.0) dist = 1.0;
dist = 1.0 - dist;
dist *= dist * dist;
value = dist;
*lum = (unsigned char)(value * 255.0);
*alp = (unsigned char)(value * 258.0); // overflow on purpose
}
}
GlowBitmap[0 + 0] = 0;
GlowBitmap[0 + 1] = 0;
}
static unsigned char BorderBitmap[16*16*2];
static void GenerateBorderTexture()
{
int ix, iy;
Vector2D center = Vector2D_(0.0, 0.0);
int size = 16;
unsigned char *bitmap = BorderBitmap;
for(iy = 0; iy < size; ++iy)
{
for(ix = 0; ix < size; ++ix)
{
float value = 1.0;
unsigned char *lum = &bitmap[size * iy * 2 + ix * 2 + 0];
unsigned char *alp = &bitmap[size * iy * 2 + ix * 2 + 1];
Vector2D p = Vector2D_(((float)ix / (float)(size-1)) * 0.5, ((float)iy / (float)(size-1)) * 0.5);
float dist = Vector2D_distance(p, center) * 2.0;
if(dist > 1.0) dist = 1.0;
dist = 1.0 - dist;
dist = dist * 12.0 - 6.0;
value = clamp(dist, 0.0, 1.0);
*lum = (unsigned char)(value * 255.0);
*alp = (unsigned char)(value * 255.0);
}
}
}
#define BRUSH_BITMAP_SIZE 128
static unsigned char BrushBitmap_StdSoft[BRUSH_BITMAP_SIZE*BRUSH_BITMAP_SIZE*2];
static unsigned char BrushBitmap_StdMed[BRUSH_BITMAP_SIZE*BRUSH_BITMAP_SIZE*2];
static unsigned char BrushBitmap_StdHard[BRUSH_BITMAP_SIZE*BRUSH_BITMAP_SIZE*2];
static unsigned char BrushBitmap_Bristle[BRUSH_BITMAP_SIZE*BRUSH_BITMAP_SIZE*2];
static unsigned char BrushBitmap_Charcoal[BRUSH_BITMAP_SIZE*BRUSH_BITMAP_SIZE*2];
static unsigned char BrushBitmap_Calligraphy[BRUSH_BITMAP_SIZE*BRUSH_BITMAP_SIZE*2];
static unsigned char BrushBitmap_Goo[BRUSH_BITMAP_SIZE*BRUSH_BITMAP_SIZE*2];
static unsigned char BrushBitmap_Shade[BRUSH_BITMAP_SIZE*BRUSH_BITMAP_SIZE*2];
static void GenerateTexture_Goo(unsigned char *bitmap)
{
int ix, iy;
Vector2D center = Vector2D_(0.0, 0.0);
for(iy = 0; iy < BRUSH_BITMAP_SIZE; ++iy)
{
for(ix = 0; ix < BRUSH_BITMAP_SIZE; ++ix)
{
float value = 1.0;
unsigned char *lum = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 0];
unsigned char *alp = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 1];
float mx = ((float)ix / (float)(BRUSH_BITMAP_SIZE-1));
float my = ((float)iy / (float)(BRUSH_BITMAP_SIZE-1));
Vector2D p = Vector2D_(mx - 0.5, my - 0.5);
float dist = Vector2D_distance(p, center) * 2.0;
if(dist > 1.0) dist = 1.0;
dist = 1.0 - dist;
value = SmoothStep(clamp(dist * 4.0f, 0.0, 1.0));
*lum = (unsigned char)(value * 255.0 * (my * 0.5 + 0.5));
*alp = (unsigned char)(value * 255.0);
}
}
}
static void GenerateTexture_Shade(unsigned char *bitmap)
{
int ix, iy;
Vector2D center = Vector2D_(0.0, 0.0);
for(iy = 0; iy < BRUSH_BITMAP_SIZE; ++iy)
{
for(ix = 0; ix < BRUSH_BITMAP_SIZE; ++ix)
{
float value = 1.0;
unsigned char *lum = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 0];
unsigned char *alp = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 1];
float mx = ((float)ix / (float)(BRUSH_BITMAP_SIZE-1));
float my = ((float)iy / (float)(BRUSH_BITMAP_SIZE-1));
Vector2D p = Vector2D_(mx - 0.5, my - 0.5);
float dist = Vector2D_distance(p, center) * 2.0;
if(dist > 1.0) dist = 1.0;
dist = 1.0 - dist;
value = SmoothStep(clamp(dist * 0.3f, 0.0, 1.0));
//value *= (random() & 0x3) ? 0.0 : 1.0;
*lum = (unsigned char)(value * 255.0);
*alp = (unsigned char)(value * 255.0);
}
}
}
#if 0
static void GenerateTexture_Spots(unsigned char *bitmap, int spotmask, float falloff)
{
int ix, iy;
Vector2D center = Vector2D_(0.0, 0.0);
for(iy = 0; iy < BRUSH_BITMAP_SIZE; ++iy)
{
for(ix = 0; ix < BRUSH_BITMAP_SIZE; ++ix)
{
float value = 1.0;
unsigned char *lum = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 0];
unsigned char *alp = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 1];
float mx = ((float)ix / (float)(BRUSH_BITMAP_SIZE-1));
float my = ((float)iy / (float)(BRUSH_BITMAP_SIZE-1));
Vector2D p = Vector2D_(mx - 0.5, my - 0.5);
float dist = Vector2D_distance(p, center) * 2.0;
if(dist > 1.0) dist = 1.0;
dist = 1.0 - dist;
value = SmoothStep(clamp(dist * falloff, 0.0, 1.0));
value *= (random() & spotmask) ? 0.1 : 0.8;
*lum = (unsigned char)(value * 255.0);
*alp = (unsigned char)(value * 255.0);
}
}
}
#endif
static float BrushXY(float mx, float my)
{
float noise1 = Perlin_noise(mx*5.0, my*5.0, 0.0) * 0.4;
float noise2 = Perlin_noise(mx*17.0, my*17.0, 0.0);
return noise1 + noise2;
}
static void GenerateTexture_Bristle(unsigned char *bitmap)
{
int ix, iy;
Vector2D center = Vector2D_(0.0, 0.0);
for(iy = 1; iy < BRUSH_BITMAP_SIZE; ++iy)
{
for(ix = 0; ix < BRUSH_BITMAP_SIZE; ++ix)
{
unsigned char *lum = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 0];
unsigned char *alp = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 1];
float mx = ((float)ix / (float)(BRUSH_BITMAP_SIZE-1));
float my = ((float)iy / (float)(BRUSH_BITMAP_SIZE-1));
Vector2D p = Vector2D_(mx - 0.5, my - 0.5);
float dist = Vector2D_distance(p, center) * 2.0;
if(dist > 1.0) dist = 1.0;
dist = 1.0 - dist;
float value = SmoothStep(clamp(dist * 4.0, 0.0, 1.0));
float a = BrushXY(mx, my);
float b = BrushXY(mx, my+(1.5/128.0));
float c = BrushXY(mx, my-(1.5/128.0));
//b = 0.0;
float flum = a + c * 0.2;
float falp = a + b * 0.2;
flum = clamp(value * flum, 0.0f, 1.0f);
falp = clamp(value * falp, 0.0f, 1.0f);
*lum = (unsigned char)(flum * 255.0);
*alp = (unsigned char)(falp * 255.0);
}
}
}
static void GenerateTexture_Charcoal(unsigned char *bitmap)
{
int ix, iy;
float falloff = 0.8;
Vector2D center = Vector2D_(0.0, 0.0);
for(iy = 0; iy < BRUSH_BITMAP_SIZE; ++iy)
{
for(ix = 0; ix < BRUSH_BITMAP_SIZE; ++ix)
{
float value = 1.0;
unsigned char *lum = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 0];
unsigned char *alp = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 1];
float mx = ((float)ix / (float)(BRUSH_BITMAP_SIZE-1));
float my = ((float)iy / (float)(BRUSH_BITMAP_SIZE-1));
Vector2D p = Vector2D_(mx - 0.5, my - 0.5);
float dist = Vector2D_distance(p, center) * 2.0;
if(dist > 1.0) dist = 1.0;
dist = 1.0 - dist;
value = SmoothStep(clamp(dist * falloff, 0.0, 1.0));
value *= Perlin_noise(mx * 40.0, my * 40.0, 0.0) * 0.5 + 0.5;
value *= Perlin_noise(mx * 23.0, my * 23.0, 0.0) * 0.5 + 0.5;
*lum = (unsigned char)(value * 255.0);
*alp = (unsigned char)(value * 255.0);
}
}
}
static void GenerateTexture_Calligraphy(unsigned char *bitmap)
{
int ix, iy;
Vector2D center = Vector2D_(0.0, 0.0);
for(iy = 0; iy < BRUSH_BITMAP_SIZE; ++iy)
{
for(ix = 0; ix < BRUSH_BITMAP_SIZE; ++ix)
{
float value = 1.0;
unsigned char *lum = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 0];
unsigned char *alp = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 1];
float mx = ((float)ix / (float)(BRUSH_BITMAP_SIZE-1));
float my = ((float)iy / (float)(BRUSH_BITMAP_SIZE-1));
Vector2D p = Vector2D_(mx - 0.5, my - 0.5);
Vector2D d = Vector2D_subtract(p, center);
//float ang = Vector2D_angle(d);
float dist = Vector2D_magnitude(d) * 2.0;
if(dist > 1.0) dist = 1.0;
dist = 1.0 - dist;
float a = 1.0f - fabsf(my - mx) * 5.0;
if(a < 0.0f) a = 0.0f;
float q = dist * a * 8.0;
value = SmoothStep(clamp(q, 0.0, 1.0));
*lum = (unsigned char)(value * 255.0);
*alp = (unsigned char)(value * 255.0);
}
}
}
static void GenerateTexture_Std(unsigned char *bitmap, int t)
{
int ix, iy;
Vector2D center = Vector2D_(0.0, 0.0);
for(iy = 0; iy < BRUSH_BITMAP_SIZE; ++iy)
{
for(ix = 0; ix < BRUSH_BITMAP_SIZE; ++ix)
{
float value = 1.0;
unsigned char *lum = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 0];
unsigned char *alp = &bitmap[BRUSH_BITMAP_SIZE * iy * 2 + ix * 2 + 1];
float mx = ((float)ix / (float)(BRUSH_BITMAP_SIZE-1));
float my = ((float)iy / (float)(BRUSH_BITMAP_SIZE-1));
Vector2D p = Vector2D_(mx - 0.5, (my - 0.5));
float dist = Vector2D_distance(p, center) * 2.0;
if(dist > 1.0) dist = 1.0;
dist = 1.0 - dist;
//SmoothStep(dist);
switch(t)
{
case 0: value = SmoothStep(clamp(dist * 1.0f, 0.0, 1.0)); break;
case 1: value = SmoothStep(clamp(dist * 2.0f, 0.0, 1.0)); break;
case 2: value = SmoothStep(clamp(dist * 8.0f, 0.0, 1.0)); break;
}
*lum = (unsigned char)(value * 255.0f);
*alp = (unsigned char)(value * 255.0f);
}
}
}
static void GenerateBrushTextures()
{
GenerateTexture_Goo(BrushBitmap_Goo);
//GenerateTexture_Shadow(BrushBitmap_Shadow);
GenerateTexture_Shade(BrushBitmap_Shade);
GenerateTexture_Bristle(BrushBitmap_Bristle);
GenerateTexture_Charcoal(BrushBitmap_Charcoal);
GenerateTexture_Calligraphy(BrushBitmap_Calligraphy);
GenerateTexture_Std(BrushBitmap_StdSoft, 0);
GenerateTexture_Std(BrushBitmap_StdMed, 1);
GenerateTexture_Std(BrushBitmap_StdHard, 2);
//GenerateTexture_Std(BrushBitmap_EraserSoft, 0);
//GenerateTexture_Std(BrushBitmap_EraserMed, 1);
//GenerateTexture_Std(BrushBitmap_EraserHard, 2);
}
// 1,048,576 bytes
#define QUAD_DUMP_ELEMENT_COUNT 512
typedef struct _QuadDump_Vertex {
struct {
float x;
float y;
} v;
struct {
float x;
float y;
} t;
struct {
float r;
float g;
float b;
float a;
} c;
} QuadDump_Vertex;
typedef struct _QuadDump_Quad {
QuadDump_Vertex v[4];
} QuadDump_Quad;
// expected format 2V.2T.4C...
typedef struct _QuadDump {
QuadDump_Quad *buffer;
int offset;
} QuadDump;
static void QuadDump_begin(QuadDump *dump)
{
dump->offset = 0;
dump->buffer = malloc(QUAD_DUMP_ELEMENT_COUNT * sizeof(QuadDump_Quad));
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer( 2, GL_FLOAT, 8 * sizeof(float), (unsigned char *)dump->buffer + 0 * sizeof(float));
glTexCoordPointer( 2, GL_FLOAT, 8 * sizeof(float), (unsigned char *)dump->buffer + 2 * sizeof(float));
glColorPointer( 4, GL_FLOAT, 8 * sizeof(float), (unsigned char *)dump->buffer + 4 * sizeof(float));
//printf("BEGIN\n");
}
static void QuadDump_dump(QuadDump *dump)
{
//printf("DUMP... (%d)\n", dump->offset);
glDrawArrays(GL_QUADS, 0, dump->offset * 4);
dump->offset = 0;
}
// requests sizeof(float) * 8 * 4 == 64 bytes per quad
static QuadDump_Quad* QuadDump_requestBuffer(QuadDump *dump)
{
//printf("REQUEST BUFFER\n");
if(dump->offset >= QUAD_DUMP_ELEMENT_COUNT - 1)
{
//printf("ran out of space\n");
QuadDump_dump(dump);
}
QuadDump_Quad *d = &dump->buffer[dump->offset];
++(dump->offset);
return d;
}
static void QuadDump_end(QuadDump *dump)
{
//printf("END\n");
QuadDump_dump(dump);
free(dump->buffer);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
//printf("\n");
}
#define COMMON_STROKE_ITERATOR_BEGIN \
float dist = Vector2D_distance(a->position, b->position); \
float iter; \
float step = size * 0.01f; \
Vector2D p; \
const Vector2D p0 = pre->position; \
const Vector2D p1 = a->position; \
const Vector2D p2 = b->position; \
const Vector2D p3 = post->position; \
const float tension = 0.5f; \
int quadNumber = 0; \
for(iter = 0.0; iter < dist; iter += step) \
{ \
float t = iter / dist; \
float t2 = t * t; \
float t3 = t2 * t; \
p.x = tension * ((2.0f * p1.x) + \
(-p0.x + p2.x) * t + \
(2.0f * p0.x - 5.0f * p1.x + 4.0f * p2.x - p3.x) * t2 + \
(-p0.x + 3.0f * p1.x - 3.0f * p2.x + p3.x) * t3); \
p.y = tension * ((2.0f * p1.y) + \
(-p0.y + p2.y) * t + \
(2.0f * p0.y - 5.0f * p1.y + 4.0f * p2.y - p3.y) * t2 + \
(-p0.y + 3.0f * p1.y - 3.0f * p2.y + p3.y) * t3); \
float l = lerp(a->pressure, b->pressure, t); \
float radius; \
if(brushOptions & PaintBrushOptionDisableSizeModulation) \
radius = size; \
else \
radius = l * size; \
float disttosavepoint2 = Vector2D_distance2(p, *savePoint); \
float threshold = (radius + *saveRadius) * distMult; \
if(disttosavepoint2 < (threshold * threshold)) continue; \
float x1 = p.x - radius; \
float x2 = p.x + radius; \
float y1 = p.y - radius; \
float y2 = p.y + radius; \
*savePoint = p; \
*saveRadius = radius; \
#define COMMON_STROKE_ITERATOR_END \
++quadNumber; \
}
static void PaintSubStroke_accumExtent(int strokeNumber,
PaintStrokePoint *pre, PaintStrokePoint *a, PaintStrokePoint *b, PaintStrokePoint *post,
Vector2D *savePoint, float *saveRadius, float size, float distMult, int brushOptions,
float *xmin, float *xmax, float *ymin, float *ymax, int *count)
{
Vector2D restorePoint = *savePoint;
float restoreRadius = *saveRadius;
COMMON_STROKE_ITERATOR_BEGIN {
l = l; // for warning
if(x1 < *xmin) *xmin = x1;
if(x2 > *xmax) *xmax = x2;
if(y1 < *ymin) *ymin = y1;
if(y2 > *ymax) *ymax = y2;
++*count;
} COMMON_STROKE_ITERATOR_END
*savePoint = restorePoint;
*saveRadius = restoreRadius;
}
extern unsigned int PaintWindowGlobalModifierFlags; // see PaintWindow.m
static void RandomCycleColor(int i, float lum, float *c3)
{
i += 20;
float sat = 1.0;
float hue = sin(i * (1.0f / 20.0f)) * 0.5f + 0.5f;
float r, g, b;
HSL_to_RGB(hue, sat, lum, &r, &g, &b);
c3[0] = r;
c3[1] = g;
c3[2] = b;
}
static void PaintStroke_renderSegment(int strokeNumber,
PaintStrokePoint *pre, PaintStrokePoint *a, PaintStrokePoint *b, PaintStrokePoint *post,
Vector2D *savePoint, float *saveRadius, float size, float distMult, Color color, QuadDump *quadDump, int rainbowMod, int rotDeg, int emboss, int brushOptions)
{
QuadDump_Quad *q;
COMMON_STROKE_ITERATOR_BEGIN {
float r, g, b, a;
if(brushOptions & PaintBrushOptionDisableOpacityModulation)
{
r = color.r;
g = color.g;
b = color.b;
a = color.a;
} else {
r = color.r * l;
g = color.g * l;
b = color.b * l;
a = color.a * l;
}
if(rainbowMod)
{
float hue, sat, lum;
RGB_to_HSL(color.r, color.g, color.b, &hue, &sat, &lum);
lum = (lum * 0.6f) + 0.1f;
float c1[3];
float c2[3];
RandomCycleColor(strokeNumber, lum, c1);
RandomCycleColor(strokeNumber+1, lum, c2);
if(brushOptions & PaintBrushOptionDisableOpacityModulation)
{
r = lerp(c1[0], c2[0], t);
g = lerp(c1[1], c2[1], t);
b = lerp(c1[2], c2[2], t);
} else {
r = l * lerp(c1[0], c2[0], t);
g = l * lerp(c1[1], c2[1], t);
b = l * lerp(c1[2], c2[2], t);
}
}
float tx1 = 0.0;
float tx2 = 1.0;
float ty1 = 0.0;
float ty2 = 1.0;
// x1 = roundf(x1);
// x2 = roundf(x2);
// y1 = roundf(y1);
// y2 = roundf(y2);
Vector2D pa = Vector2D_(x1, y1);
Vector2D pb = Vector2D_(x2, y1);
Vector2D pc = Vector2D_(x2, y2);
Vector2D pd = Vector2D_(x1, y2);
if(rotDeg)
{
float f_rotDeg = (strokeNumber * 50 + quadNumber) * (float)rotDeg * M_PI / 180.0f;
pa = Vector2D_add(Vector2D_rotate(Vector2D_subtract(pa, p), f_rotDeg), p);
pb = Vector2D_add(Vector2D_rotate(Vector2D_subtract(pb, p), f_rotDeg), p);
pc = Vector2D_add(Vector2D_rotate(Vector2D_subtract(pc, p), f_rotDeg), p);
pd = Vector2D_add(Vector2D_rotate(Vector2D_subtract(pd, p), f_rotDeg), p);
}
q = QuadDump_requestBuffer(quadDump);
q->v[0].v.x = pa.x;
q->v[0].v.y = pa.y;
q->v[0].t.x = tx1;
q->v[0].t.y = ty1;
q->v[0].c.r = r;
q->v[0].c.g = g;
q->v[0].c.b = b;
q->v[0].c.a = a;
q->v[1].v.x = pb.x;
q->v[1].v.y = pb.y;
q->v[1].t.x = tx2;
q->v[1].t.y = ty1;
q->v[1].c.r = r;
q->v[1].c.g = g;
q->v[1].c.b = b;
q->v[1].c.a = a;
q->v[2].v.x = pc.x;
q->v[2].v.y = pc.y;
q->v[2].t.x = tx2;
q->v[2].t.y = ty2;
q->v[2].c.r = r;
q->v[2].c.g = g;
q->v[2].c.b = b;
q->v[2].c.a = a;
q->v[3].v.x = pd.x;
q->v[3].v.y = pd.y;
q->v[3].t.x = tx1;
q->v[3].t.y = ty2;
q->v[3].c.r = r;
q->v[3].c.g = g;
q->v[3].c.b = b;
q->v[3].c.a = a;
if(emboss)
{
#if 1
r = 0.0f;
g = 0.0f;
b = 0.0f;
a = 0.2f;
pa.y -= 1.5f;
pb.y -= 1.5f;
pc.y -= 1.5f;
pd.y -= 1.5f;
q = QuadDump_requestBuffer(quadDump);
q->v[0].v.x = pa.x;
q->v[0].v.y = pa.y;
q->v[0].t.x = tx1;
q->v[0].t.y = ty1;
q->v[0].c.r = r;
q->v[0].c.g = g;
q->v[0].c.b = b;
q->v[0].c.a = a;
q->v[1].v.x = pb.x;
q->v[1].v.y = pb.y;
q->v[1].t.x = tx2;
q->v[1].t.y = ty1;
q->v[1].c.r = r;
q->v[1].c.g = g;
q->v[1].c.b = b;
q->v[1].c.a = a;
q->v[2].v.x = pc.x;
q->v[2].v.y = pc.y;
q->v[2].t.x = tx2;
q->v[2].t.y = ty2;
q->v[2].c.r = r;
q->v[2].c.g = g;
q->v[2].c.b = b;
q->v[2].c.a = a;
q->v[3].v.x = pd.x;
q->v[3].v.y = pd.y;
q->v[3].t.x = tx1;
q->v[3].t.y = ty2;
q->v[3].c.r = r;
q->v[3].c.g = g;
q->v[3].c.b = b;
q->v[3].c.a = a;
pa.y += 2.5f;
pb.y += 2.5f;
pc.y += 2.5f;
pd.y += 2.5f;
#endif
r = 0.2f;
g = 0.2f;
b = 0.2f;
a = 0.2f;
q = QuadDump_requestBuffer(quadDump);
q->v[0].v.x = pa.x;
q->v[0].v.y = pa.y;
q->v[0].t.x = tx1;
q->v[0].t.y = ty1;
q->v[0].c.r = r;
q->v[0].c.g = g;
q->v[0].c.b = b;
q->v[0].c.a = a;
q->v[1].v.x = pb.x;
q->v[1].v.y = pb.y;
q->v[1].t.x = tx2;
q->v[1].t.y = ty1;
q->v[1].c.r = r;
q->v[1].c.g = g;
q->v[1].c.b = b;
q->v[1].c.a = a;
q->v[2].v.x = pc.x;
q->v[2].v.y = pc.y;
q->v[2].t.x = tx2;
q->v[2].t.y = ty2;
q->v[2].c.r = r;
q->v[2].c.g = g;
q->v[2].c.b = b;
q->v[2].c.a = a;
q->v[3].v.x = pd.x;
q->v[3].v.y = pd.y;
q->v[3].t.x = tx1;
q->v[3].t.y = ty2;
q->v[3].c.r = r;
q->v[3].c.g = g;
q->v[3].c.b = b;
q->v[3].c.a = a;
}
} COMMON_STROKE_ITERATOR_END
}
float _GetCurrentDistMult(PaintStroke *stroke)
{
float distMult = 0.1f;
PaintBrushType brushType = stroke->commands.strokeCommand.state.brushType;
PaintBrushType pureBrushType = brushType & PaintBrushTypeMask;
switch(pureBrushType)
{
case PaintBrushTypeCalligraphy:
distMult = 0.05f;
break;
case PaintBrushTypeBristle:
distMult = 0.02f;
break;
case PaintBrushTypeCharcoal:
distMult = 0.2f;
break;
case PaintBrushTypeEraserSoft:
distMult = 0.25f;
break;
case PaintBrushTypeStdSoft:
distMult = 0.2f;
break;
case PaintBrushTypeStdHard:
if(brushType & PaintBrushOptionDisableSizeModulation)
distMult = 0.05;
break;
}
return distMult;
}
void PaintStroke_accumExtent(PaintStroke *stroke, float *xmin, float *xmax, float *ymin, float *ymax, int *pcount)
{
PaintBrushType brushType = stroke->commands.strokeCommand.state.brushType;
float radius = stroke->commands.strokeCommand.state.brushSize;
PaintStrokePoint *pre, *a, *b, *post;
int count = 0;
float distMult = _GetCurrentDistMult(stroke);
if(stroke->commands.strokeCommand.points_array)
{
count = stroke->commands.strokeCommand.points_array_size;
int index;
PaintStrokePoint *points = stroke->commands.strokeCommand.points_array;
for(index = stroke->commands.strokeCommand.nextStartDrawIndex; index < count; ++index)
{
if(index + 3 > count - 1) break;
pre = &points[index + 0];
a = &points[index + 1];
b = &points[index + 2];
post = &points[index + 3];
PaintSubStroke_accumExtent(index, pre, a, b, post, &stroke->commands.strokeCommand.savePoint, &stroke->commands.strokeCommand.saveRadius, radius, distMult, brushType, xmin, xmax, ymin, ymax, pcount);
}
} else {
count = ArrayStack_count(&stroke->commands.strokeCommand.points);
int index;
void *value;
void **points = ArrayStack_data(&stroke->commands.strokeCommand.points);
ArrayStack_foreachWithStartIndex(&stroke->commands.strokeCommand.points, stroke->commands.strokeCommand.nextStartDrawIndex, index, value)
{
if(index + 3 > count - 1) break;
pre = points[index + 0];
a = points[index + 1];
b = points[index + 2];
post = points[index + 3];
PaintSubStroke_accumExtent(index, pre, a, b, post, &stroke->commands.strokeCommand.savePoint, &stroke->commands.strokeCommand.saveRadius, radius, distMult, brushType, xmin, xmax, ymin, ymax, pcount);
}
}
}
void PaintLayer_accumExtent(PaintLayer *layer, float *xmin, float *xmax, float *ymin, float *ymax, int *pcount) // really need 'partialOnly' flag!
{
int index;
void *value;
ArrayStack_foreach(&layer->strokes, index, value) {
PaintStroke *stroke = (PaintStroke *)value;
if(stroke->commandType == PaintCommandTypeBrushStroke)
{
if(stroke->commands.strokeCommand.points_array) // warning: don't call this from other functions till this is fixed
{
// nutin...?... yeah.
PaintStroke_accumExtent(stroke, xmin, xmax, ymin, ymax, pcount);
} else {
PaintStroke_accumExtent(stroke, xmin, xmax, ymin, ymax, pcount);
}
}
}
}
int PaintLayer_getExtent(PaintLayer *layer, float *xmin, float *xmax, float *ymin, float *ymax)
{
*xmin = FLT_MAX;
*ymin = FLT_MAX;
*xmax = -FLT_MAX;
*ymax = -FLT_MAX;
int count = 0;
PaintLayer_accumExtent(layer, xmin, xmax, ymin, ymax, &count);
return count;
}
// important! state changes here: stroke->commands.strokeCommand.nextStartDrawIndex!!!
void Painting_renderStroke(int strokeIndex, PaintStroke *stroke, QuadDump *quadDump)
{
strokeIndex *= 50;
float radius = stroke->commands.strokeCommand.state.brushSize;
PaintStrokePoint *pre, *a, *b, *post;
int count = 0;
Color strokeColor = stroke->commands.strokeCommand.state.brushColor;
float distMult = _GetCurrentDistMult(stroke);
int rainbowMod = 0;
int rotDeg = 0;
int emboss = 0;
// sane state, also update in _renderLayer
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
PaintBrushType brushType = stroke->commands.strokeCommand.state.brushType;
PaintBrushType pureBrushType = brushType & PaintBrushTypeMask;
int brushOptions = brushType & ~PaintBrushTypeMask;
switch(pureBrushType)
{
case PaintBrushTypeRainbow:
rainbowMod = 1;
strokeColor.a *= 0.5;
break;
case PaintBrushTypeGoo:
strokeColor.a *= 0.7;
break;
case PaintBrushTypeCharcoal:
rotDeg = 37;
break;
case PaintBrushTypeBristle:
//emboss = 1;
//rotDeg = 2;
break;
case PaintBrushTypeShade:
strokeColor.r *= 0.2;
strokeColor.g *= 0.2;
strokeColor.b *= 0.2;
strokeColor.a *= 0.2;
glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
break;
case PaintBrushTypeEraserSoft:
strokeColor.r = 0.4;
strokeColor.g = 0.4;
strokeColor.b = 0.4;
strokeColor.a = 0.4;
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_DST_COLOR, GL_ONE);
break;
case PaintBrushTypeEraserMed:
case PaintBrushTypeEraserHard:
strokeColor.r = 1.0;
strokeColor.g = 1.0;
strokeColor.b = 1.0;
strokeColor.a = 1.0;
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_DST_COLOR, GL_ONE);
break;
}
if(stroke->commands.strokeCommand.points_array)
{
count = stroke->commands.strokeCommand.points_array_size;
int index;
PaintStrokePoint *points = stroke->commands.strokeCommand.points_array;
for(index = stroke->commands.strokeCommand.nextStartDrawIndex; index < count; ++index)
{
if(index + 3 > count - 1) break;
pre = &points[index + 0];
a = &points[index + 1];
b = &points[index + 2];
post = &points[index + 3];
PaintStroke_renderSegment(strokeIndex+index, pre, a, b, post, &stroke->commands.strokeCommand.savePoint, &stroke->commands.strokeCommand.saveRadius, radius, distMult, strokeColor, quadDump, rainbowMod, rotDeg, emboss, brushOptions);
stroke->commands.strokeCommand.nextStartDrawIndex = index + 1;
}
} else {
count = ArrayStack_count(&stroke->commands.strokeCommand.points);
int index;
void *value;
void **points = ArrayStack_data(&stroke->commands.strokeCommand.points);
ArrayStack_foreachWithStartIndex(&stroke->commands.strokeCommand.points, stroke->commands.strokeCommand.nextStartDrawIndex, index, value)
{
//printf("%d %d\n", stroke->commands.strokeCommand.nextStartDrawIndex, index);
if(index + 3 > count - 1) break;
pre = points[index + 0];
a = points[index + 1];
b = points[index + 2];
post = points[index + 3];
PaintStroke_renderSegment(strokeIndex+index, pre, a, b, post, &stroke->commands.strokeCommand.savePoint, &stroke->commands.strokeCommand.saveRadius, radius, distMult, strokeColor, quadDump, rainbowMod, rotDeg, emboss, brushOptions);
stroke->commands.strokeCommand.nextStartDrawIndex = index + 1;
}
}
//glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
//glBlendEquation(GL_FUNC_ADD);
//glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}
int PaintLayer_needsUpdate(PaintLayer *layer)
{
int index;
void *value;
ArrayStack_foreach(&layer->strokes, index, value) {
PaintStroke *stroke = (PaintStroke *)value;
if(stroke->commandType == PaintCommandTypeBrushStroke)
{
int count;
if(stroke->commands.strokeCommand.points_array)
count = stroke->commands.strokeCommand.points_array_size;
else
count = ArrayStack_count(&stroke->commands.strokeCommand.points);
if(count > stroke->commands.strokeCommand.nextStartDrawIndex + 3) return 1;
}
}
return 0;
}
void _DoApplyStrokeTransform(PaintStroke *stroke)
{
switch(stroke->commandType)
{
case PaintCommandTypeBrushStroke:
// ignore
break;
case PaintCommandTypeTransformTranslate:
glTranslatef(stroke->commands.translateCommand.translate.x, stroke->commands.translateCommand.translate.y, 0.0f);
break;
case PaintCommandTypeTransformRotate:
glTranslatef(stroke->commands.rotateCommand.axis.x, stroke->commands.rotateCommand.axis.y, 0.0f);
glRotatef(stroke->commands.rotateCommand.angle, 0.0f, 0.0f, 1.0f);
glTranslatef(-stroke->commands.rotateCommand.axis.x, -stroke->commands.rotateCommand.axis.y, 0.0f);
break;
case PaintCommandTypeTransformScale:
{
float amt = exp2f(stroke->commands.scaleCommand.amount);
glTranslatef(stroke->commands.scaleCommand.center.x, stroke->commands.scaleCommand.center.y, 0.0f);
glScalef(amt, amt, 1.0f);
glTranslatef(-stroke->commands.scaleCommand.center.x, -stroke->commands.scaleCommand.center.y, 0.0f);
break;
}
default:
// ignore
break;
}
}
#define APPLY_STROKE_TRANSFORM(STROKE) if(STROKE->commandType & PaintCommandTypeTransform) _DoApplyStrokeTransform(STROKE)
void Painting_renderLayer_(Painting *painting, PaintLayer *layer, int noCache, int partialOnly)
{
glDisable(GL_TEXTURE_RECTANGLE_ARB);
glEnable(GL_TEXTURE_2D);
int index;
void *value;
QuadDump quadDump;
QuadDump_begin(&quadDump);
ArrayStack_foreach(&layer->strokes, index, value) {
PaintStroke *stroke = (PaintStroke *)value;
switch(stroke->commandType)
{
case PaintCommandTypeBrushStroke:
{
if(partialOnly)
{
if(stroke->commands.strokeCommand.points_array)
break;
}
glPushMatrix();
//ArrayStack_foreach(&layer->strokes, index, value) {
int s_index;
void *s_value;
ArrayStack_foreachReverseWithEndIndex(&layer->strokes, index, s_index, s_value) {
PaintStroke *s_stroke = (PaintStroke *)s_value;
APPLY_STROKE_TRANSFORM(s_stroke);
}
PaintBrushType brushType = stroke->commands.strokeCommand.state.brushType;
PaintBrushType pureBrushType = brushType & PaintBrushTypeMask;
glBindTexture(GL_TEXTURE_2D, painting->brushTextures[pureBrushType]);
if(noCache)
{
int save1;
Vector2D save2;
float save3;
SAVE_STROKE_INFO(stroke, save1, save2, save3);
UNSET_PARTIAL_STROKE_INFO(stroke);
Painting_renderStroke(index, stroke, &quadDump);
RESTORE_STROKE_INFO(stroke, save1, save2, save3);
} else {
Painting_renderStroke(index, stroke, &quadDump);
}
QuadDump_dump(&quadDump);
glPopMatrix();
break;
}
}
}
QuadDump_end(&quadDump);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDisable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
}
void Painting_renderLayer(Painting *painting, PaintLayer *layer, int partialOnly)
{
Painting_renderLayer_(painting, layer, 0, partialOnly);
}
void Painting_renderLayer_noCache(Painting *painting, PaintLayer *layer)
{
Painting_renderLayer_(painting, layer, 1, 0);
}
static void RenderCachedSubTexture(GLuint tex, GLint filterMode, int minx, int maxx, int miny, int maxy, float opacity)
{
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, tex);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, filterMode);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, filterMode);
float v[8] = {
(float)minx, (float)miny,
(float)minx, (float)maxy,
(float)maxx, (float)maxy,
(float)maxx, (float)miny,
};
//glColor4f(1.0, 1.0, 1.0, 1.0);
glColor4f(opacity, opacity, opacity, opacity);
//glColor4f(1.0, 1.0, 1.0, opacity); // additive
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, v);
glTexCoordPointer(2, GL_FLOAT, 0, v);
glDrawArrays(GL_QUADS, 0, 4);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
static void RenderCachedTexture(GLuint tex, int w, int h, GLint filterMode, float opacity)
{
RenderCachedSubTexture(tex, filterMode, 0, w, 0, h, opacity);
}
static GLuint UploadBrushTexture(unsigned char *bitmap)
{
GLuint tex = 0;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, BRUSH_BITMAP_SIZE, BRUSH_BITMAP_SIZE, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, bitmap);
return tex;
}
void BeginPaintingRender(Painting *painting)
{
if(!painting->brushTextures[PaintBrushTypeEraserSoft])
{
glDisable(GL_TEXTURE_RECTANGLE_ARB);
glEnable(GL_TEXTURE_2D);
//painting->brushTextures[PaintBrushTypeEraserSoft ] = UploadBrushTexture(BrushBitmap_EraserSoft);
//painting->brushTextures[PaintBrushTypeEraserMed ] = UploadBrushTexture(BrushBitmap_EraserMed);
//painting->brushTextures[PaintBrushTypeEraserHard ] = UploadBrushTexture(BrushBitmap_EraserHard);
painting->brushTextures[PaintBrushTypeStdSoft ] = UploadBrushTexture(BrushBitmap_StdSoft);
painting->brushTextures[PaintBrushTypeStdMed ] = UploadBrushTexture(BrushBitmap_StdMed);
painting->brushTextures[PaintBrushTypeStdHard ] = UploadBrushTexture(BrushBitmap_StdHard);
painting->brushTextures[PaintBrushTypeEraserSoft ] = painting->brushTextures[PaintBrushTypeStdSoft];
painting->brushTextures[PaintBrushTypeEraserMed ] = painting->brushTextures[PaintBrushTypeStdMed];
painting->brushTextures[PaintBrushTypeEraserHard ] = painting->brushTextures[PaintBrushTypeStdHard];
painting->brushTextures[PaintBrushTypeBristle ] = UploadBrushTexture(BrushBitmap_Bristle);
painting->brushTextures[PaintBrushTypeCharcoal ] = UploadBrushTexture(BrushBitmap_Charcoal);
//painting->brushTextures[PaintBrushTypeBristle ] = painting->brushTextures[PaintBrushTypeCharcoal];
painting->brushTextures[PaintBrushTypeCalligraphy ] = UploadBrushTexture(BrushBitmap_Calligraphy);
painting->brushTextures[PaintBrushTypeGoo ] = UploadBrushTexture(BrushBitmap_Goo);
//painting->brushTextures[PaintBrushTypeRainbow ] = UploadBrushTexture(BrushBitmap_Shadow);
painting->brushTextures[PaintBrushTypeShade ] = UploadBrushTexture(BrushBitmap_Shade);
painting->brushTextures[PaintBrushTypeRainbow ] = painting->brushTextures[PaintBrushTypeGoo];
glDisable(GL_TEXTURE_2D);
painting->textures.glowTexture = GLUtil_uploadGrayscaleTexture(GlowBitmap, 16, 16);
painting->textures.borderTexture = GLUtil_uploadGrayscaleTexture(BorderBitmap, 16, 16);
}
glEnable(GL_BLEND);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
void EndPaintingRender(Painting *painting)
{
glDisable(GL_TEXTURE_RECTANGLE_ARB);
glDisable(GL_BLEND);
}
void Painting_preRender(Painting *painting, int framebufferWidth, int framebufferHeight)
{
//printf("--- prerender\n");
int showProgessBar = 0;
BeginPaintingRender(painting);
glClearColor(0.0, 0.0, 0.0, 0.0); // clear to transparent black
int rearNeedsUpdate = 0;
int foreNeedsUpdate = 0;
//printf("prerender... pan = %f %f\n", painting->pan.x, painting->pan.y);
if( (painting->cache.framebufferWidth != framebufferWidth) ||
(painting->cache.framebufferHeight != framebufferHeight) ||
(painting->pan.x != painting->cache.pan.x) ||
(painting->pan.y != painting->cache.pan.y) ||
(painting->zoom != painting->cache.zoom))
{
if(painting->cache.framebufferWidth != 0 && painting->cache.framebufferHeight != 0) // don't show on first regen
showProgessBar = 1;
// if framebuffer size changed, regen everything
painting->cache.framebufferWidth = framebufferWidth;
painting->cache.framebufferHeight = framebufferHeight;
painting->cache.pan = painting->pan;
painting->cache.zoom = painting->zoom;
int index;
void *value;
ArrayStack_foreach(&painting->layers, index, value) {
PaintLayer *layer = (PaintLayer *)value;
if(layer->cache.texture)
{
glDeleteTextures(1, &layer->cache.texture);
layer->cache.texture = 0;
}
int sindex;
void *svalue;
ArrayStack_foreach(&layer->strokes, sindex, svalue) {
PaintStroke *stroke = (PaintStroke *)svalue;
UNSET_PARTIAL_STROKE_INFO(stroke);
}
}
if(painting->cache.rearTexture)
{
glDeleteTextures(1, &painting->cache.rearTexture);
painting->cache.rearTexture = 0;
}
if(painting->cache.foreTexture)
{
glDeleteTextures(1, &painting->cache.foreTexture);
painting->cache.foreTexture = 0;
}
rearNeedsUpdate = 1;
foreNeedsUpdate = 1;
//printf("NEEDS FULL UPDATE\n");
}
ProgressOpInfo op;
if(showProgessBar) StartProgressOp(&op);
if(painting->cache.selectedLayer != painting->selectedLayer)
{
// if selected layer changed, only regen collapsed caches
painting->cache.selectedLayer = painting->selectedLayer;
rearNeedsUpdate = 1;
foreNeedsUpdate = 1;
}
//printf("updating layers (");
// render each layer to texture
int index;
void *value;
ArrayStack_foreach(&painting->layers, index, value) {
PaintLayer *layer = (PaintLayer *)value;
int needsUpdate = 0;
int fullUpdate = 0;
if(!layer->cache.texture)
{
glGenTextures(1, &layer->cache.texture);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, layer->cache.texture);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
needsUpdate = 1;
fullUpdate = 1;
}
if(layer->needsCompleteRedraw)
{
int sindex;
void *svalue;
ArrayStack_foreach(&layer->strokes, sindex, svalue) {
PaintStroke *stroke = (PaintStroke *)svalue;
UNSET_PARTIAL_STROKE_INFO(stroke);
}
needsUpdate = 1;
fullUpdate = 1;
layer->needsCompleteRedraw = 0;
}
if(!needsUpdate)
{
//printf("checking to see if layer needs update... \n");
if(PaintLayer_needsUpdate(layer))
{
//printf("YES\n");
needsUpdate = 1;
}
}
if(needsUpdate)
{
float scale = exp2f(painting->zoom);
//printf("%d ", index);
//printf("yeah!\n");
if(fullUpdate)
{
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glTranslatef(painting->pan.x, painting->pan.y, 0.0);
glScalef(scale, scale, 1.0);
Painting_renderLayer(painting, layer, 0); // not partial only
glPopMatrix();
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, layer->cache.texture);
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, 0, 0, framebufferWidth, framebufferHeight, 0);
} else {
//printf("partial update\n");
#if 1
int minx = 0;
int maxx = framebufferWidth;
int miny = 0;
int maxy = framebufferHeight;
#endif
#if 0
int minx = 200;
int maxx = 400;
int miny = 200;
int maxy = 400;
#endif
float xminf = 0.0;
float xmaxf = framebufferWidth;
float yminf = 0.0;
float ymaxf = framebufferHeight;
int extentPointCount = 0;
//#warning "change me back"
#if 1
//extentPointCount = Painting_getExtent(painting, &xminf, &xmaxf, &yminf, &ymaxf);
extentPointCount = PaintLayer_getExtent(layer, &xminf, &xmaxf, &yminf, &ymaxf);
//if(extentPointCount > 0) printf("%d:: %f %f -> %f %f\n", extentPointCount, xminf, yminf, xmaxf, ymaxf);
//else printf("noting\n");
if(!extentPointCount)
{
//printf("extentPointCount = 0\n");
goto noNeedForUpdate;
}
xminf = (xminf * scale + painting->pan.x);
xmaxf = (xmaxf * scale + painting->pan.x);
yminf = (yminf * scale + painting->pan.y);
ymaxf = (ymaxf * scale + painting->pan.y);
minx = (int)(xminf - 2.0);
miny = (int)(yminf - 2.0);
maxx = (int)(xmaxf + 2.0);
maxy = (int)(ymaxf + 2.0);
if(minx < 0) minx = 0;
if(miny < 0) miny = 0;
if(maxx > framebufferWidth) maxx = framebufferWidth;
if(maxy > framebufferHeight) maxy = framebufferHeight;
#endif
#if 0
if(maxx < minx || maxy < miny)
{
printf("do nothing...\n");
//goto noNeedForUpdate;
}
#endif
int sub_width = maxx - minx;
int sub_height = maxy - miny;
if(sub_width <= 0 || sub_height <= 0)
{
//printf("oh shit...\n");
goto noNeedForUpdate;
}
glEnable(GL_SCISSOR_TEST);
glScissor(minx, miny, sub_width, sub_height);
glClear(GL_COLOR_BUFFER_BIT);
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy, 1.0f);
#if 0
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy);
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy);
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy);
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy);
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy);
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy);
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy);
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy);
RenderCachedSubTexture(layer->cache.texture, GL_NEAREST, minx, maxx, miny, maxy);
#endif
#if 0
glDisable(GL_TEXTURE_RECTANGLE_ARB);
glColor4f(1.0, 0.0, 0.0, 1.0);
glBegin(GL_LINE_LOOP);
glVertex2f(minx, miny);
glVertex2f(minx, maxy-1);
glVertex2f(maxx-1, maxy-1);
glVertex2f(maxx-1, miny);
glEnd();
glEnable(GL_TEXTURE_RECTANGLE_ARB);
#endif
glPushMatrix();
glTranslatef(painting->pan.x, painting->pan.y, 0.0);
glScalef(scale, scale, 1.0);
int partialOnly = 1;
PaintStroke *activeStroke = PaintLayer_topStroke(layer);
if(activeStroke->commands.strokeCommand.points_array) partialOnly = 0;
Painting_renderLayer(painting, layer, partialOnly); // partial only
glPopMatrix();
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, layer->cache.texture);
glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
minx, miny,
minx, miny,
sub_width, sub_height);
}
noNeedForUpdate:
if(index < painting->selectedLayer) rearNeedsUpdate = 1;
if(index > painting->selectedLayer) foreNeedsUpdate = 1;
}
if(showProgessBar) StepProgressOp(&op);
}
//printf(")\n");
// collapse rear layers
if(!painting->cache.rearTexture)
{
glGenTextures(1, &painting->cache.rearTexture);
rearNeedsUpdate = 1;
}
if(rearNeedsUpdate)
{
glDisable(GL_SCISSOR_TEST);
//printf("rear needed update...\n");
//printf("updating rear (");
glClear(GL_COLOR_BUFFER_BIT);
ArrayStack_foreach(&painting->layers, index, value) {
if(index == painting->selectedLayer) break;
PaintLayer *layer = (PaintLayer *)value;
//printf("%d ", index);
RenderCachedTexture(layer->cache.texture, framebufferWidth, framebufferHeight, GL_NEAREST, layer->opacity);
if(showProgessBar) StepProgressOp(&op);
}
//printf(")\n");
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, painting->cache.rearTexture);
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, 0, 0, framebufferWidth, framebufferHeight, 0);
}
// collapse fore textures
if(!painting->cache.foreTexture)
{
glGenTextures(1, &painting->cache.foreTexture);
foreNeedsUpdate = 1;
}
if(foreNeedsUpdate)
{
glDisable(GL_SCISSOR_TEST);
//printf("updating fore (");
glClear(GL_COLOR_BUFFER_BIT);
ArrayStack_foreachWithStartIndex(&painting->layers, painting->selectedLayer + 1, index, value) {
PaintLayer *layer = (PaintLayer *)value;
//printf("%d ", index);
RenderCachedTexture(layer->cache.texture, framebufferWidth, framebufferHeight, GL_NEAREST, layer->opacity);
if(showProgessBar) StepProgressOp(&op);
}
//printf(")\n");
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, painting->cache.foreTexture);
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, 0, 0, framebufferWidth, framebufferHeight, 0);
}
//printf("\n");
if(showProgessBar) EndProgressOp(&op);
EndPaintingRender(painting);
}
#define BOTTOM_OFFSET 100.0
void Painting_enterLayerMode(Painting *painting)
{
//printf("enter layer mode...\n");
painting->layerUIInfo.lastMousePosition = Vector2D_(0.0, 40.0);
painting->layerUIInfo.isDragging = 0;
painting->layerUIInfo.dragIndex = -1;
painting->layerUIInfo.selectionOffset = 0.0;
}
int Painting_mouseEventInLayerMode(Painting *painting, int eventType, Vector2D mousePosition)
{
painting->layerUIInfo.lastMousePosition = mousePosition;
int nLayers = ArrayStack_count(&painting->layers);
float spacing = (painting->cache.framebufferHeight - 150.0) / (nLayers);
float yMousePosition = mousePosition.y - BOTTOM_OFFSET /* - painting->layerUIInfo.selectionOffset */;
int pickIndex = (int)((yMousePosition) / spacing);
//printf("pickIndex = %d\n", pickIndex);
switch(eventType)
{
case Painting_mouseDown:
{
//printf("pickIndex = %d\n", pickIndex);
if(pickIndex < 0) break;
if(pickIndex > nLayers - 1)
{
//Painting_createLayer(painting);
} else {
painting->layerUIInfo.dragIndex = pickIndex;
float diff = yMousePosition - (pickIndex * spacing);
//printf("diff = <<<<<<<<<<<<<<<<< %f\n", diff);
painting->layerUIInfo.selectionOffset = diff;
Painting_selectLayer(painting, pickIndex);
}
break;
}
case Painting_mouseDrag:
{
if(painting->layerUIInfo.dragIndex != -1)
{
painting->layerUIInfo.isDragging = 1;
//printf("drag:: %f\n", mousePosition.y);
//int index = (int)(((yMousePosition - painting->layerUIInfo.selectionOffset) / spacing) + 0.5);
//printf("DRAG INDEX: %d\n", index);
}
break;
}
case Painting_mouseUp:
{
if(painting->layerUIInfo.isDragging)
{
pickIndex = (int)(((yMousePosition - painting->layerUIInfo.selectionOffset) / spacing) + 0.5); // make sure i'm right...
if(pickIndex < 0) pickIndex = 0;
if(pickIndex > nLayers - 1) pickIndex = nLayers - 1;
//printf("drop %d -> %d\n", painting->layerUIInfo.dragIndex, pickIndex);
Painting_moveLayer(painting, painting->layerUIInfo.dragIndex, pickIndex);
Painting_selectLayer(painting, pickIndex);
painting->layerUIInfo.dragIndex = -1;
painting->layerUIInfo.isDragging = 0;
painting->layerUIInfo.selectionOffset = 0.0;
}
break;
}
}
return 1;
}
void Painting_renderInLayerMode(Painting *painting, int actualWidth, int actualHeight, float t)
{
int vwidth = painting->cache.framebufferWidth;
int vheight = painting->cache.framebufferHeight;
float transition = Bezier1D_interpolate(t, Bezier1D_easeInEaseOut);
//float otransition = 1.0 - transition;
BeginPaintingRender(painting);
glDisable(GL_TEXTURE_RECTANGLE_ARB);
glEnable(GL_BLEND);
glBegin(GL_QUADS);
#define SHADEA 0.3f
#define SHADEB 0.6f
glColor4f(0.0f, 0.0f, 0.0f, transition * SHADEA);
glVertex2f(0.0f, 0.0f);
glColor4f(0.0f, 0.0f, 0.0f, transition * SHADEB);
glVertex2f(0.0f, actualHeight);
glColor4f(0.0f, 0.0f, 0.0f, transition * SHADEB);
glVertex2f(actualWidth, actualHeight);
glColor4f(0.0f, 0.0f, 0.0f, transition * SHADEA);
glVertex2f(actualWidth, 0.0f);
glEnd();
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
float hw = actualWidth * 0.5f;
float hh = actualHeight * 0.5f;
float near = (actualWidth + actualHeight) * 0.7f;
float far = near + near;
glFrustum(-hw * 0.5f, hw * 0.5f, -hh * 0.5f, hh * 0.5f, near * 0.5f, far);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glTranslatef(-hw, -hh, -near);
int hoverIndex = 0;
int selectedIndex = painting->selectedLayer;
int index;
void *value;
int nLayers = ArrayStack_count(&painting->layers);
float spacing = (actualHeight - 150.0f) / (nLayers);
float xMousePosition = ((painting->layerUIInfo.lastMousePosition.x) / actualWidth - 0.5f) * 3.0f;
xMousePosition = clamp(xMousePosition, -1.0, 1.0);
xMousePosition = (cosf(xMousePosition * M_PI) + 1.0f) * 0.5;
float yMousePosition = painting->layerUIInfo.lastMousePosition.y - BOTTOM_OFFSET;
hoverIndex = (int)((yMousePosition) / spacing);
float delayTransition = transition * transition;
delayTransition *= delayTransition;
int dragIndex = painting->layerUIInfo.dragIndex;
float dragYPosition = yMousePosition - painting->layerUIInfo.selectionOffset;
ArrayStack_foreach(&painting->layers, index, value) {
PaintLayer *layer = (PaintLayer *)value;
glPushMatrix();
float xshift = 0.0f;
float yPosition = spacing * index;
if(painting->layerUIInfo.isDragging)
{
if(painting->layerUIInfo.dragIndex == index)
{
yPosition = dragYPosition;
xshift = 0.0f;
} else {
float nearDrag = yPosition - dragYPosition;
float m = 0.0f;
float nos = nearDrag / spacing;
if(index > dragIndex)
{
m = clamp((1.0f - nos) * 1.0f, 0.0f, 1.0f);
yPosition -= spacing * Bezier1D_interpolate(m, Bezier1D_easeInEaseOut);
} else if(index < dragIndex) {
m = clamp((1.0f + nos) * 1.0f, 0.0f, 1.0f);
yPosition += spacing * Bezier1D_interpolate(m, Bezier1D_easeInEaseOut);
}
m = m * (1.0f - m) * 4.0f;
xshift -= Bezier1D_interpolate(m, Bezier1D_easeInEaseOut) * hh;
}
}
float dist = (yPosition - yMousePosition) / (spacing) * xMousePosition;
float at = atanf(dist);
float at2 = at * at;
float col = 1.0f - at2;
if(col < 0.0f) col = 0.0f;
float shift = at;
float mrot = 1.0f - at2;
yPosition += shift * actualHeight * 0.18f;
glTranslatef(0.0f, (yPosition + BOTTOM_OFFSET - xshift * 0.2f) * delayTransition, xshift);
glRotatef((mrot * 10.0f - 60.0f) * transition, 1.0f, 0.0f, 0.0f);
float scale = (1.0f - transition);
float PERSP_SCALE = 0.4f + mrot * 0.02f;
scale = scale * (1.0f - PERSP_SCALE) + (PERSP_SCALE);
float glowrad = hw * 0.3f;
glTranslatef(hw * transition * (1.0f - PERSP_SCALE), glowrad * transition * 0.0f, 0.0f);
glScalef(scale, scale, 1.0);
glDisable(GL_TEXTURE_RECTANGLE_ARB);
float lalp = layer->opacity * 0.4f + 0.6f;
float alp1 = (0.0f + col * 0.4f) * transition * lalp;
float alp2 = (0.2f + col * 0.4f) * transition * lalp;
float c1 = 1.0f * alp1;
float c2 = 1.0f * alp2;
glBegin(GL_QUADS);
glColor4f(c1, c1, c1, alp1);
glVertex2f(0.0, actualHeight);
glVertex2f(actualWidth, actualHeight);
glColor4f(c2, c2, c2, alp2);
glVertex2f(actualWidth, 0.0);
glVertex2f(0.0, 0.0);
glEnd();
if(index == selectedIndex)
{
#define VFUDGEMULT 1.5f
float verts[] = {
0.0, 0.0,
-glowrad, 0.0,
-glowrad, actualHeight,
0.0, actualHeight,
0.0, actualHeight,
0.0, actualHeight + glowrad * VFUDGEMULT,
actualWidth, actualHeight + glowrad * VFUDGEMULT,
actualWidth, actualHeight,
actualWidth, actualHeight,
actualWidth + glowrad, actualHeight,
actualWidth + glowrad, 0.0,
actualWidth, 0.0,
actualWidth, 0.0,
actualWidth, -glowrad * VFUDGEMULT,
0.0, -glowrad * VFUDGEMULT,
0.0, 0.0,
///// corners
0.0, 0.0,
-glowrad, 0.0,
-glowrad, -glowrad * VFUDGEMULT,
0.0, -glowrad * VFUDGEMULT,
0.0, actualHeight,
0.0, actualHeight + glowrad * VFUDGEMULT,
-glowrad, actualHeight + glowrad * VFUDGEMULT,
-glowrad, actualHeight,
actualWidth, actualHeight,
actualWidth + glowrad, actualHeight,
actualWidth + glowrad, actualHeight + glowrad * VFUDGEMULT,
actualWidth, actualHeight + glowrad * VFUDGEMULT,
actualWidth, 0.0,
actualWidth, -glowrad * VFUDGEMULT,
actualWidth + glowrad, -glowrad * VFUDGEMULT,
actualWidth + glowrad, 0.0,
};
float texcoords[] = {
0.0, 0.0,
0.0, 16.0,
0.0, 16.0,
0.0, 0.0,
0.0, 0.0,
0.0, 16.0,
0.0, 16.0,
0.0, 0.0,
0.0, 0.0,
0.0, 16.0,
0.0, 16.0,
0.0, 0.0,
0.0, 0.0,
0.0, 16.0,
0.0, 16.0,
0.0, 0.0,
///// corners
0.0, 0.0,
0.0, 16.0,
16.0, 16.0,
16.0, 0.0,
0.0, 0.0,
0.0, 16.0,
16.0, 16.0,
16.0, 0.0,
0.0, 0.0,
0.0, 16.0,
16.0, 16.0,
16.0, 0.0,
0.0, 0.0,
0.0, 16.0,
16.0, 16.0,
16.0, 0.0,
};
glColor4f(0.0, 0.5 * delayTransition, 1.0 * delayTransition, 0.8 * delayTransition);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, painting->textures.glowTexture);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
glVertexPointer(2, GL_FLOAT, 0, verts);
//glPushMatrix();
//glRotatef((mrot * 10.0f - 60.0f) * transition * -0.5, 1.0, 0.0, 0.0);
//glTranslatef(0.0, 0.0, -10.0);
glDrawArrays(GL_QUADS, 0, 32);
//glPopMatrix();
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisable(GL_TEXTURE_RECTANGLE_ARB);
}
{
// rounded rects
#if 1
glowrad = hw * 0.1;
float verts[] = {
0.0, 0.0,
-glowrad, 0.0,
-glowrad, actualHeight,
0.0, actualHeight,
0.0, actualHeight,
0.0, actualHeight + glowrad * VFUDGEMULT,
actualWidth, actualHeight + glowrad * VFUDGEMULT,
actualWidth, actualHeight,
actualWidth, actualHeight,
actualWidth + glowrad, actualHeight,
actualWidth + glowrad, 0.0,
actualWidth, 0.0,
actualWidth, 0.0,
actualWidth, -glowrad * VFUDGEMULT,
0.0, -glowrad * VFUDGEMULT,
0.0, 0.0,
///// corners
0.0, 0.0,
-glowrad, 0.0,
-glowrad, -glowrad * VFUDGEMULT,
0.0, -glowrad * VFUDGEMULT,
0.0, actualHeight,
0.0, actualHeight + glowrad * VFUDGEMULT,
-glowrad, actualHeight + glowrad * VFUDGEMULT,
-glowrad, actualHeight,
actualWidth, actualHeight,
actualWidth + glowrad, actualHeight,
actualWidth + glowrad, actualHeight + glowrad * VFUDGEMULT,
actualWidth, actualHeight + glowrad * VFUDGEMULT,
actualWidth, 0.0,
actualWidth, -glowrad * VFUDGEMULT,
actualWidth + glowrad, -glowrad * VFUDGEMULT,
actualWidth + glowrad, 0.0,
};
float texcoords[] = {
0.0, 0.0,
0.0, 16.0,
0.0, 16.0,
0.0, 0.0,
0.0, 0.0,
0.0, 16.0,
0.0, 16.0,
0.0, 0.0,
0.0, 0.0,
0.0, 16.0,
0.0, 16.0,
0.0, 0.0,
0.0, 0.0,
0.0, 16.0,
0.0, 16.0,
0.0, 0.0,
///// corners
0.0, 0.0,
0.0, 16.0,
16.0, 16.0,
16.0, 0.0,
0.0, 0.0,
0.0, 16.0,
16.0, 16.0,
16.0, 0.0,
0.0, 0.0,
0.0, 16.0,
16.0, 16.0,
16.0, 0.0,
0.0, 0.0,
0.0, 16.0,
16.0, 16.0,
16.0, 0.0,
};
#define ALP2 alp1, alp1, alp1, alp1,
#define ALP1 alp2, alp2, alp2, alp2,
float colors[] = {
ALP1
ALP1
ALP2
ALP2
ALP2
ALP2
ALP2
ALP2
ALP2
ALP2
ALP1
ALP1
ALP1
ALP1
ALP1
ALP1
///// corners
ALP1
ALP1
ALP1
ALP1
ALP2
ALP2
ALP2
ALP2
ALP2
ALP2
ALP2
ALP2
ALP1
ALP1
ALP1
ALP1
};
glColor4f(alp2, alp2, alp2, alp2);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, painting->textures.borderTexture);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, texcoords);
glVertexPointer(2, GL_FLOAT, 0, verts);
glColorPointer(4, GL_FLOAT, 0, colors);
//glPushMatrix();
//glRotatef((mrot * 10.0f - 60.0f) * transition * -0.5, 1.0, 0.0, 0.0);
//glTranslatef(0.0, 0.0, -10.0);
glDrawArrays(GL_QUADS, 0, 32);
//glPopMatrix();
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisable(GL_TEXTURE_RECTANGLE_ARB);
#endif
}
glEnable(GL_TEXTURE_RECTANGLE_ARB);
Vector2D delta = Vector2D_subtract(painting->pan, painting->cache.pan);
glTranslatef(delta.x, delta.y, 0.0);
RenderCachedTexture(layer->cache.texture, vwidth, vheight, GL_LINEAR, layer->opacity);
glPopMatrix();
}
glPopMatrix(); // modelview
glMatrixMode(GL_PROJECTION);
glPopMatrix(); // projection
glMatrixMode(GL_MODELVIEW);
EndPaintingRender(painting);
}
Vector2D Painting_pointCenteredInVisibleCanvas(Painting *painting)
{
Vector2D size = Vector2D_(painting->cache.framebufferWidth, painting->cache.framebufferHeight);
float scale = exp2f(painting->zoom);
Vector2D pan = painting->pan;
Vector2D c = Vector2D_scale(size, 0.5);
Vector2D v = pan;
v = Vector2D_subtract(pan, c);
v = Vector2D_scale(v, -1.0f/scale);
//printf("center point = %f %f\n", v.x, v.y);
return v;
}
static void DrawGrid(Painting *painting, int actualWidth, int actualHeight, float spac, float alpha)
{
double z;
double pz = painting->zoom + MIN_ZOOM;
z = exp2(pz - trunc(pz));
glEnable(GL_BLEND);
glColor4f(0.0, 0.0, 0.0, alpha);
glBegin(GL_LINES);
double spacing = spac * z;
double f, start;
start = fmod(painting->pan.x, spacing);
for(f = start; f < actualWidth; f += spacing)
{
glVertex2f(f, 0.0);
glVertex2f(f, actualHeight);
}
start = fmod(painting->pan.y, spacing);
for(f = start; f < actualHeight; f += spacing)
{
glVertex2f(0.0, f);
glVertex2f(actualWidth, f);
}
glEnd();
glDisable(GL_BLEND);
}
void Painting_render(Painting *painting, int actualWidth, int actualHeight, int onlySelectedLayer, int showGrid)
{
/*
while panning, the cache.pan stays constant till mouse-up
*/
int vwidth = painting->cache.framebufferWidth;
int vheight = painting->cache.framebufferHeight;
glPushMatrix();
float scale = exp2f(painting->zoom - painting->cache.zoom);
glTranslatef(painting->pan.x, painting->pan.y, 0.0);
glScalef(scale, scale, 1.0);
glTranslatef(-painting->cache.pan.x, -painting->cache.pan.y, 0.0);
BeginPaintingRender(painting);
if(!onlySelectedLayer)
RenderCachedTexture(painting->cache.rearTexture, vwidth, vheight, GL_LINEAR, 1.0f);
PaintLayer *selectedLayer = Painting_selectedLayer(painting);
PaintStroke *stroke = PaintLayer_topStroke(selectedLayer);
glPushMatrix();
if(painting->transforming)
{
//APPLY_STROKE_TRANSFORM(stroke);
if(stroke->commandType & PaintCommandTypeTransform)
{
switch(stroke->commandType)
{
case PaintCommandTypeTransformTranslate:
{
float d = exp2f(painting->zoom);
Vector2D temp_translate = Vector2D_scale(stroke->commands.translateCommand.translate, d);
glTranslatef(temp_translate.x, temp_translate.y, 0.0f);
break;
}
case PaintCommandTypeTransformRotate:
{
glTranslatef(vwidth*0.5, vheight*0.5, 0.0f);
glRotatef(stroke->commands.rotateCommand.angle, 0.0f, 0.0f, 1.0f);
glTranslatef(-vwidth*0.5, -vheight*0.5, 0.0f);
break;
}
case PaintCommandTypeTransformScale:
{
float amt = exp2f(stroke->commands.scaleCommand.amount);
glTranslatef(vwidth*0.5, vheight*0.5, 0.0f);
glScalef(amt, amt, 1.0f);
glTranslatef(-vwidth*0.5, -vheight*0.5, 0.0f);
break;
}
}
}
}
RenderCachedTexture(selectedLayer->cache.texture, vwidth, vheight, GL_LINEAR, selectedLayer->opacity);
glPopMatrix();
if(!onlySelectedLayer)
RenderCachedTexture(painting->cache.foreTexture, vwidth, vheight, GL_LINEAR, 1.0f);
EndPaintingRender(painting);
glPopMatrix();
if(showGrid)
{
extern float GridSpacingGlobal;
double z;
double pz = painting->zoom + MIN_ZOOM;
z = exp2(pz - truncf(pz));
z = (z - 0.5) * 2.0;
DrawGrid(painting, actualWidth, actualHeight, GridSpacingGlobal * 2.0, 0.3 * (1.0-z));
DrawGrid(painting, actualWidth, actualHeight, GridSpacingGlobal, 0.3 * (z));
}
}
float PaintLayer_opacity(PaintLayer *layer) {
return layer->opacity;
}
PaintLayer* Painting_layerAtIndex(Painting *painting, int index) {
return (PaintLayer *)ArrayStack_get(&painting->layers, index);
}
void Painting_preRender_noCache(Painting *painting, int framebufferWidth, int framebufferHeight)
{
//int vwidth = painting->cache.framebufferWidth;
//int vheight = painting->cache.framebufferHeight;
BeginPaintingRender(painting);
//PaintLayer *layer = Painting_layerAtIndex(painting, layerIndex);
//Painting_renderLayer_noCache(painting, layer);
// layer->cache.texture
glClearColor(0.0, 0.0, 0.0, 0.0);
glPushMatrix();
//float scale = exp2f(painting->zoom - painting->cache.zoom);
float scale = exp2f(painting->zoom);
// don't change me! see _render()
glTranslatef(painting->pan.x, painting->pan.y, 0.0);
glScalef(scale, scale, 1.0);
//glTranslatef(-painting->cache.pan.x, -painting->cache.pan.y, 0.0);
//glTranslatef(-painting->pan.x, -painting->pan.y, 0.0); // note! no cache data used
int layerIndex;
void *layerPtr;
ArrayStack_foreach(&painting->layers, layerIndex, layerPtr) {
glClear(GL_COLOR_BUFFER_BIT);
PaintLayer *layer = (PaintLayer *)layerPtr;
Painting_renderLayer_noCache(painting, layer);
if(!layer->cache.texture)
{
printf("why don't I have a valid texture here?\n");
glGenTextures(1, &layer->cache.texture);
}
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, layer->cache.texture);
glCopyTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, 0, 0, framebufferWidth, framebufferHeight, 0);
layer->needsCompleteRedraw = 1;
}
glPopMatrix();
//RenderCachedTexture(painting->cache.rearTexture, vwidth, vheight, GL_LINEAR, 1.0f);
//
//PaintLayer *selectedLayer = Painting_selectedLayer(painting);
//RenderCachedTexture(selectedLayer->cache.texture, vwidth, vheight, GL_LINEAR, selectedLayer->opacity);
//
//RenderCachedTexture(painting->cache.foreTexture, vwidth, vheight, GL_LINEAR, 1.0f);
EndPaintingRender(painting);
}
void Painting_render_noCache(Painting *painting, int framebufferWidth, int framebufferHeight)
{
int layerIndex;
void *layerPtr;
BeginPaintingRender(painting);
ArrayStack_foreach(&painting->layers, layerIndex, layerPtr) {
PaintLayer *layer = (PaintLayer *)layerPtr;
RenderCachedTexture(layer->cache.texture, framebufferWidth, framebufferHeight, GL_NEAREST, layer->opacity);
}
EndPaintingRender(painting);
}
void Painting_initOnce()
{
static int once = 1;
if(once)
{
once = 0;
Perlin_init();
GenerateGlowTexture();
GenerateBorderTexture();
//GenerateBrushTexture(BristleBrushBitmap, 64);
GenerateBrushTextures();
}
}
void Painting_setBrushSize(Painting *painting, float size) { painting->state.brushSize = size; }
void Painting_setBrushColor(Painting *painting, Color color) { painting->state.brushColor = color; /*printf("%f %f %f\n", color.r, color.g, color.b);*/ }
void Painting_setBrushType(Painting *painting, PaintBrushType type) {
//printf("set brush %d\n", type);
painting->state.brushType = type;
}
void Painting_enableBrushOption(Painting *painting, int option)
{
painting->state.brushOptions |= option;
}
void Painting_disableBrushOption(Painting *painting, int option)
{
painting->state.brushOptions &= ~option;
}
void Painting_toggleBrushOption(Painting *painting, int option)
{
painting->state.brushOptions ^= option;
}
int Painting_isOptionEnabled(Painting *painting, int option)
{
return painting->state.brushOptions & option;
}
PaintBrushType Painting_currentBrush(Painting *painting)
{
return painting->state.brushType;
}
void Painting_selectLayer(Painting *painting, int layerIndex) { if((layerIndex < ArrayStack_count(&painting->layers)) && (layerIndex >= 0)) painting->selectedLayer = layerIndex; }
void Painting_selectLayerAbove(Painting *painting)
{
Painting_selectLayer(painting, painting->selectedLayer + 1);
}
void Painting_selectLayerBelow(Painting *painting)
{
Painting_selectLayer(painting, painting->selectedLayer - 1);
}
void Painting_swapWithLayerAbove(Painting *painting)
{
Painting_moveLayer(painting, painting->selectedLayer, painting->selectedLayer + 1);
}
void Painting_swapWithLayerBelow(Painting *painting)
{
Painting_moveLayer(painting, painting->selectedLayer, painting->selectedLayer - 1);
}
void Painting_moveLayer(Painting *painting, int fromIndex, int toIndex)
{
ArrayStack_move(&painting->layers, fromIndex, toIndex);
}
PaintLayer* Painting_selectedLayer(Painting *painting) {
return (PaintLayer *)ArrayStack_get(&painting->layers, painting->selectedLayer);
}
static PaintStroke* PaintLayer_topStroke(PaintLayer *layer) {
return ArrayStack_top(&layer->strokes);
}
float Painting_layerOpacity(Painting *painting)
{
PaintLayer *l = Painting_selectedLayer(painting);
return l->opacity;
}
void Painting_setLayerOpacity(Painting *painting, float o)
{
PaintLayer *l = Painting_selectedLayer(painting);
l->opacity = o;
}
void Painting_resetPanZoom(Painting *painting)
{
painting->pan = Vector2D_(0.0, 0.0);
painting->zoom = 0.0;
}
void Painting_pan(Painting *painting, Vector2D p)
{
painting->pan = Vector2D_add(painting->pan, p);
}
void Painting_zoom(Painting *painting, float z, Vector2D center)
{
//Vector2D center = Vector2D_(painting->cache.framebufferWidth * 0.5f, painting->cache.framebufferHeight * 0.5f);
//painting->cache.zoom
//painting->cache.pan
painting->zoom += z;
painting->zoom = clamp(painting->zoom, MIN_ZOOM, MAX_ZOOM);
//printf("zoom = %f\n", painting->zoom);
float newZoom = painting->zoom;
float oldZoom = painting->cache.zoom;
Vector2D oldPan = painting->cache.pan;
float zoomDiff = newZoom - oldZoom;
float scaleDiff = exp2f(zoomDiff);
Vector2D newPan;
newPan.x = center.x + (oldPan.x - center.x) * scaleDiff;
newPan.y = center.y + (oldPan.y - center.y) * scaleDiff;
painting->pan = newPan;
}
void Painting_init(Painting *painting /*, Color defaultColor, PaintBrushType defaultBrushType */)
{
ArrayStack_init(&painting->layers);
ArrayStack_init(&painting->undoStrokeStack);
ArrayStack_init(&painting->deletedLayers);
// create default layer
Painting_createLayer(painting); // at index 0
Painting_selectLayer(painting, 0);
painting->state.brushColor = Color_(0.173848, 0.516462, 0.826152, 1.0);
painting->state.brushType = PaintBrushTypeStdHard;
painting->state.brushOptions = 0;
// default brush state
//painting->state.brushColor = defaultColor;
//painting->state.brushType = defaultBrushType;
painting->textures.glowTexture = 0;
//painting->brushtexture = 0;
painting->pan = Vector2D_(0.0, 0.0);
painting->zoom = 0.0;
painting->transforming = 0;
painting->cache.pan = Vector2D_(0.0, 0.0);
painting->cache.zoom = 0.0;
painting->cache.selectedLayer = 0;
painting->cache.framebufferWidth = 0;
painting->cache.framebufferHeight = 0;
painting->cache.rearTexture = 0;
painting->cache.foreTexture = 0;
}
void Painting_destroy(Painting *painting)
{
//ArrayStack_foreach()
//ArrayStack_foreach()
}
int Painting_canUndo(Painting *painting)
{
PaintLayer *currentLayer = Painting_selectedLayer(painting);
int strokeCount = ArrayStack_count(&currentLayer->strokes);
return (strokeCount > 0);
}
int Painting_canRedo(Painting *painting)
{
int count = ArrayStack_count(&painting->undoStrokeStack);
return (count > 0);
}
int Painting_canUndeleteLayer(Painting *painting)
{
int count = ArrayStack_count(&painting->deletedLayers);
return (count > 0);
}
void Painting_undo(Painting *painting)
{
if(Painting_canUndo(painting))
{
PaintLayer *currentLayer = Painting_selectedLayer(painting);
PaintStroke *topStroke = ArrayStack_pop(&currentLayer->strokes);
UNSET_PARTIAL_STROKE_INFO(topStroke);
ArrayStack_push(&painting->undoStrokeStack, (void *)topStroke);
currentLayer->needsCompleteRedraw = 1;
}
}
void Painting_redo(Painting *painting)
{
if(Painting_canRedo(painting))
{
PaintLayer *currentLayer = Painting_selectedLayer(painting);
PaintStroke *pushStroke = ArrayStack_pop(&painting->undoStrokeStack);
ArrayStack_push(&currentLayer->strokes, (void *)pushStroke);
currentLayer->needsCompleteRedraw = 1;
}
}
void Painting_undeleteLayer(Painting *painting)
{
printf("undelete...\n");
PaintLayer *layer = ArrayStack_pop(&painting->deletedLayers);
if(layer)
{
ArrayStack_push(&painting->layers, layer);
}
}
void Painting_createLayer(Painting *painting)
{
PaintLayer *newLayer = malloc(sizeof(PaintLayer));
//printf("Painting_createLayer: malloc returned %p\n", newLayer);
ArrayStack_init(&(newLayer->strokes));
newLayer->translation = Vector2D_(0.0, 0.0);
newLayer->opacity = 1.0f;
newLayer->needsCompleteRedraw = 0;
newLayer->cache.texture = 0;
ArrayStack_push(&(painting->layers), newLayer);
Painting_selectLayer(painting, ArrayStack_count(&painting->layers) - 1); // select newly created layer
}
void Painting_deallocStroke(PaintStroke *stroke)
{
if(stroke->commands.strokeCommand.points_array)
{
} else {
// delete each point
}
free(stroke);
}
void Painting_deallocLayer(PaintLayer *layer)
{
// todo... delete strokes
int index;
void *value;
ArrayStack_foreach(&layer->strokes, index, value) {
Painting_deallocStroke((PaintStroke *)value);
}
free(layer);
}
void Painting_removeLayer(Painting *painting, int layerIndex)
{
if(ArrayStack_count(&painting->layers) == 1) return;
PaintLayer *layer = ArrayStack_remove(&painting->layers, layerIndex);
painting->cache.selectedLayer = -1; // causes regen of rear+fore textures on next preRender
int newCount = ArrayStack_count(&painting->layers);
int newSelection = layerIndex;
if(newSelection > newCount - 1) newSelection = newCount - 1;
Painting_selectLayer(painting, newSelection);
if(ArrayStack_count(&layer->strokes) > 0)
{
printf("layer has strokes... pushing onto deletedLayers stack\n");
ArrayStack_push(&painting->deletedLayers, layer);
}
}
void Painting_deleteSelectedLayer(Painting *painting)
{
Painting_removeLayer(painting, painting->selectedLayer);
// add to layer undo stack
}
static PaintStroke* Painting_beginTransform(Painting *painting, PaintCommandType type)
{
painting->transforming = 1;
// create new stroke
PaintStroke *newstroke = malloc(sizeof(PaintStroke));
newstroke->commandType = type;
// add new stroke to active layer
PaintLayer *activeLayer = Painting_selectedLayer(painting);
ArrayStack_push(&activeLayer->strokes, newstroke);
return newstroke;
}
static void Painting_endTransform(Painting *painting)
{
painting->transforming = 0;
PaintLayer *activeLayer = Painting_selectedLayer(painting);
activeLayer->needsCompleteRedraw = 1;
}
void Painting_beginTranslateLayer(Painting *painting)
{
PaintStroke *newstroke = Painting_beginTransform(painting, PaintCommandTypeTransformTranslate);
newstroke->commands.translateCommand.translate = Vector2D_(0.0, 0.0);
}
void Painting_translateLayer(Painting *painting, Vector2D delta)
{
//printf("translate...\n");
PaintLayer *activeLayer = Painting_selectedLayer(painting);
PaintStroke *activeStroke = PaintLayer_topStroke(activeLayer);
if(activeStroke->commandType != PaintCommandTypeTransformTranslate)
{
printf("command stream mismatch, expected PaintCommandTypeTransformTranslate\n");
return;
}
float d = 1.0f / exp2f(painting->zoom);
//printf("scaling by %f\n", d);
delta = Vector2D_scale(delta, d);
activeStroke->commands.translateCommand.translate = Vector2D_add(activeStroke->commands.translateCommand.translate, delta);
}
void Painting_endTranslateLayer(Painting *painting)
{
Painting_endTransform(painting);
//printf("end translate\n");
}
void Painting_beginRotateLayer(Painting *painting)
{
PaintStroke *newstroke = Painting_beginTransform(painting, PaintCommandTypeTransformRotate);
//newstroke->commands.translateCommand.translate = Vector2D_(0.0, 0.0);
newstroke->commands.rotateCommand.axis = Painting_pointCenteredInVisibleCanvas(painting);
newstroke->commands.rotateCommand.angle = 0.0f;
}
void Painting_rotateLayer(Painting *painting, float angle)
{
PaintLayer *activeLayer = Painting_selectedLayer(painting);
PaintStroke *activeStroke = PaintLayer_topStroke(activeLayer);
if(activeStroke->commandType != PaintCommandTypeTransformRotate)
{
printf("command stream mismatch, expected PaintCommandTypeTransformTranslate\n");
return;
}
activeStroke->commands.rotateCommand.angle += angle;
}
void Painting_endRotateLayer(Painting *painting)
{
Painting_endTransform(painting);
//printf("end rotate\n");
}
void Painting_beginScaleLayer(Painting *painting)
{
//printf("begin scale\n");
PaintStroke *newstroke = Painting_beginTransform(painting, PaintCommandTypeTransformScale);
newstroke->commands.scaleCommand.center = Painting_pointCenteredInVisibleCanvas(painting);
newstroke->commands.scaleCommand.amount = 0.0f;
}
void Painting_scaleLayer(Painting *painting, float amount)
{
PaintLayer *activeLayer = Painting_selectedLayer(painting);
PaintStroke *activeStroke = PaintLayer_topStroke(activeLayer);
if(activeStroke->commandType != PaintCommandTypeTransformScale)
{
printf("command stream mismatch, expected PaintCommandTypeTransformScale\n");
return;
}
//printf("scale...\n");
activeStroke->commands.scaleCommand.amount += amount;
activeStroke->commands.scaleCommand.amount = clamp(activeStroke->commands.scaleCommand.amount, MIN_ZOOM, MAX_ZOOM);
}
void Painting_endScaleLayer(Painting *painting)
{
Painting_endTransform(painting);
//printf("end scale\n");
}
void Painting_beginStroke(Painting *painting, unsigned int brushOptions)
{
// create new stroke
PaintStroke *newstroke = malloc(sizeof(PaintStroke));
newstroke->commandType = PaintCommandTypeBrushStroke;
newstroke->commands.strokeCommand.state = painting->state; // remember current painting state
newstroke->commands.strokeCommand.state.brushType |= painting->state.brushOptions; // REAL dynamics options
newstroke->commands.strokeCommand.state.brushType |= brushOptions; // dynamics options, eg opacity-only pressure modulation (not used, always 0)
float scale = exp2f(painting->zoom);
newstroke->commands.strokeCommand.state.brushSize /= scale;
ArrayStack_init(&newstroke->commands.strokeCommand.points); // empty list
newstroke->commands.strokeCommand.points_array = NULL;
newstroke->commands.strokeCommand.points_array_size = 0;
UNSET_PARTIAL_STROKE_INFO(newstroke);
//newstroke->commands.strokeCommand.nextStartDrawIndex = 0;
//newstroke->commands.strokeCommand.savePoint = Vector2D_(-10000.0, -10000.0); // this is a hack
//newstroke->cache.cachedCount = 0; // no points in cache yet
// add new stroke to active layer
PaintLayer *activeLayer = Painting_selectedLayer(painting);
ArrayStack_push(&activeLayer->strokes, newstroke);
}
void Painting_strokeToPoint(Painting *painting, Vector2D position, float pressure)
{
// create new point
PaintStrokePoint *newpoint = malloc(sizeof(PaintStrokePoint));
float scale = exp2f(painting->zoom);
//newpoint->position = Vector2D_subtract(Vector2D_scale(position, 1.0/scale), painting->pan);
newpoint->position = Vector2D_scale(Vector2D_subtract(position, painting->pan), 1.0/scale);
newpoint->pressure = pressure;
// add point to active stroke of active layer
PaintLayer *activeLayer = Painting_selectedLayer(painting);
PaintStroke *activeStroke = PaintLayer_topStroke(activeLayer);
if(activeStroke->commandType != PaintCommandTypeBrushStroke)
{
printf("command stream mismatch, expected PaintCommandTypeBrushStroke\n");
free(newpoint);
return;
}
ArrayStack_push(&activeStroke->commands.strokeCommand.points, newpoint);
}
void Painting_endStroke(Painting *painting)
{
PaintLayer *activeLayer = Painting_selectedLayer(painting);
PaintStroke *activeStroke = PaintLayer_topStroke(activeLayer);
if(activeStroke->commandType != PaintCommandTypeBrushStroke)
{
printf("command stream mismatch, expected PaintCommandTypeBrushStroke\n");
return;
}
if(activeStroke->commands.strokeCommand.points_array != NULL)
{
printf("UH OH... end stroke.. but points_array isnt NULL\n");
}
//printf("endstroke -- copy to static array\n");
int count = ArrayStack_count(&activeStroke->commands.strokeCommand.points);
//printf("endstroke... copying, count = %d, nextStartDrawIndex = %d\n", count, activeStroke->commands.strokeCommand.nextStartDrawIndex);
activeStroke->commands.strokeCommand.points_array = malloc(sizeof(PaintStrokePoint) * (count));
activeStroke->commands.strokeCommand.points_array_size = count;
//printf(" copying...\n");
int index;
void *value;
ArrayStack_foreach(&activeStroke->commands.strokeCommand.points, index, value) {
PaintStrokePoint *p = (PaintStrokePoint *)value;
activeStroke->commands.strokeCommand.points_array[index] = *p;
}
//printf(" deleting array stack\n");
ArrayStack_foreach(&activeStroke->commands.strokeCommand.points, index, value) { free(value); }
ArrayStack_clear(&activeStroke->commands.strokeCommand.points);
}
// see MyDocument.h
extern uint32_t EncodeInt32(uint32_t x);
extern uint32_t DecodeInt32(uint32_t x);
extern uint32_t EncodeFloat32(float x);
extern float DecodeFloat32(uint32_t x);
typedef struct _PNTMPoint {
uint32_t x;
uint32_t y;
uint32_t p;
} PNTMPoint;
typedef struct _PNTMCommand { // introduced in 1.2
uint32_t type;
} PNTMCommand;
typedef struct _PNTMTranslateCommand {
uint32_t translateX;
uint32_t translateY;
} PNTMTranslateCommand;
typedef struct _PNTMRotateCommand {
uint32_t axisX;
uint32_t axisY;
uint32_t angle;
} PNTMRotateCommand;
typedef struct _PNTMScaleCommand {
uint32_t centerX;
uint32_t centerY;
uint32_t amount;
} PNTMScaleCommand;
typedef struct _PNTMStroke { // PNTMStrokeCommand
uint32_t red;
uint32_t green;
uint32_t blue;
uint32_t alpha;
uint32_t size;
uint32_t brush;
uint32_t pointCount;
} PNTMStroke;
typedef struct _PNTMLayer {
uint32_t strokeCount;
uint32_t opacity;
uint32_t translationX;
uint32_t translationY;
} PNTMLayer;
typedef struct _PNTMDocument {
uint32_t layerCount;
uint32_t panX;
uint32_t panY;
uint32_t zoom;
uint32_t width;
uint32_t height;
} PNTMDocument;
typedef struct _PNTMHeader {
char hdr[4];
uint32_t version;
} PNTMHeader;
int Painting__binarySize(Painting *painting)
{
int totalSize = 0;
int layerIndex;
void *layerPtr;
int layerCount = ArrayStack_count(&painting->layers);
totalSize += sizeof(PNTMHeader);
totalSize += sizeof(PNTMDocument);
totalSize += layerCount * sizeof(PNTMLayer);
ArrayStack_foreach(&painting->layers, layerIndex, layerPtr) {
PaintLayer *layer = (PaintLayer *)layerPtr;
int strokeCount = ArrayStack_count(&layer->strokes);
totalSize += strokeCount * (sizeof(PNTMStroke) + sizeof(PNTMCommand)); // just a little extra
int strokeIndex;
void *strokePtr;
ArrayStack_foreach(&layer->strokes, strokeIndex, strokePtr) {
PaintStroke *stroke = (PaintStroke *)strokePtr;
if(stroke->commandType == PaintCommandTypeBrushStroke)
{
int pointCount = 0;
if(stroke->commands.strokeCommand.points_array) pointCount = stroke->commands.strokeCommand.points_array_size;
else pointCount = ArrayStack_count(&stroke->commands.strokeCommand.points);
totalSize += pointCount * sizeof(PNTMPoint);
}
}
}
return totalSize;
}
#define GET_HUNK(VAR, TYPE, BUF) \
TYPE * VAR = (TYPE *)BUF; \
BUF += sizeof(TYPE)
int Painting_saveData(Painting *painting, void **d, int *length)
{
int size = Painting__binarySize(painting);
//printf("[][] save size = %d bytes\n", size);
unsigned char *buffer = malloc(size);
*d = (void *)buffer;
*length = size;
GET_HUNK(header, PNTMHeader, buffer);
header->hdr[0] = 'P';
header->hdr[1] = 'N';
header->hdr[2] = 'T';
header->hdr[3] = 'M';
header->version = EncodeInt32(1004);
ProgressOpInfo op;
StartProgressOp(&op);
int layerCount = ArrayStack_count(&painting->layers);
GET_HUNK(doc, PNTMDocument, buffer);
doc->layerCount = EncodeInt32(*(unsigned int *)&layerCount);
doc->width = EncodeInt32(*(unsigned int *)&painting->cache.framebufferWidth);
doc->height = EncodeInt32(*(unsigned int *)&painting->cache.framebufferHeight);
doc->panX = EncodeFloat32(painting->pan.x);
doc->panY = EncodeFloat32(painting->pan.y);
doc->zoom = EncodeFloat32(painting->zoom);
printf("encoded pan: %f %f\n", painting->pan.x, painting->pan.y);
int layerIndex;
void *layerPtr;
ArrayStack_foreach(&painting->layers, layerIndex, layerPtr) {
PaintLayer *layer = (PaintLayer *)layerPtr;
int strokeCount = ArrayStack_count(&layer->strokes);
GET_HUNK(lyr, PNTMLayer, buffer);
lyr->strokeCount = EncodeInt32(*(unsigned int *)&strokeCount);
lyr->opacity = EncodeFloat32(layer->opacity);
lyr->translationX = EncodeFloat32(layer->translation.x);
lyr->translationY = EncodeFloat32(layer->translation.y);
int strokeIndex;
void *strokePtr;
ArrayStack_foreach(&layer->strokes, strokeIndex, strokePtr) {
PaintStroke *stroke = (PaintStroke *)strokePtr;
GET_HUNK(command, PNTMCommand, buffer);
command->type = EncodeInt32(stroke->commandType);
switch(stroke->commandType)
{
case PaintCommandTypeBrushStroke:
{
int pointCount = 0;
if(stroke->commands.strokeCommand.points_array) pointCount = stroke->commands.strokeCommand.points_array_size;
else pointCount = ArrayStack_count(&stroke->commands.strokeCommand.points);
StepProgressOp(&op);
GET_HUNK(str, PNTMStroke, buffer);
str->red = EncodeFloat32(stroke->commands.strokeCommand.state.brushColor.r);
str->green = EncodeFloat32(stroke->commands.strokeCommand.state.brushColor.g);
str->blue = EncodeFloat32(stroke->commands.strokeCommand.state.brushColor.b);
str->alpha = EncodeFloat32(stroke->commands.strokeCommand.state.brushColor.a);
str->size = EncodeFloat32(stroke->commands.strokeCommand.state.brushSize);
str->brush = EncodeInt32(stroke->commands.strokeCommand.state.brushType);
str->pointCount = EncodeInt32(*(unsigned int *)&pointCount);
if(stroke->commands.strokeCommand.points_array)
{
int pointIndex;
for(pointIndex = 0; pointIndex < stroke->commands.strokeCommand.points_array_size; ++pointIndex)
{
PaintStrokePoint *point = &stroke->commands.strokeCommand.points_array[pointIndex];
GET_HUNK(pnt, PNTMPoint, buffer);
pnt->x = EncodeFloat32(point->position.x);
pnt->y = EncodeFloat32(point->position.y);
pnt->p = EncodeFloat32(point->pressure);
}
} else {
int pointIndex;
void *pointPtr;
ArrayStack_foreach(&stroke->commands.strokeCommand.points, pointIndex, pointPtr) {
PaintStrokePoint *point = (PaintStrokePoint *)pointPtr;
GET_HUNK(pnt, PNTMPoint, buffer);
pnt->x = EncodeFloat32(point->position.x);
pnt->y = EncodeFloat32(point->position.y);
pnt->p = EncodeFloat32(point->pressure);
}
}
break;
}
case PaintCommandTypeTransformTranslate:
{
GET_HUNK(cmd, PNTMTranslateCommand, buffer);
cmd->translateX = EncodeFloat32(stroke->commands.translateCommand.translate.x);
cmd->translateY = EncodeFloat32(stroke->commands.translateCommand.translate.y);
break;
}
case PaintCommandTypeTransformRotate:
{
GET_HUNK(cmd, PNTMRotateCommand, buffer);
cmd->axisX = EncodeFloat32(stroke->commands.rotateCommand.axis.x);
cmd->axisY = EncodeFloat32(stroke->commands.rotateCommand.axis.y);
cmd->angle = EncodeFloat32(stroke->commands.rotateCommand.angle);
break;
}
case PaintCommandTypeTransformScale:
{
GET_HUNK(cmd, PNTMScaleCommand, buffer);
cmd->centerX = EncodeFloat32(stroke->commands.scaleCommand.center.x);
cmd->centerY = EncodeFloat32(stroke->commands.scaleCommand.center.y);
cmd->amount = EncodeFloat32(stroke->commands.scaleCommand.amount);
break;
}
default:
{
printf("saving: attempting to encode invalid command type!\n");
return 0;
}
}
}
}
EndProgressOp(&op);
return 1;
}
#define unless(E) if(!(E))
#define GET_HUNK_ERR(VAR, TYPE, BUF, BUFEND) \
TYPE * VAR = (TYPE *)BUF; \
BUF += sizeof(TYPE); \
if(BUF > BUFEND)
int Painting_loadData_version1004(Painting *painting, const unsigned char *buffer, const unsigned char *buffer_end, int *saveWidth, int *saveHeight)
{
printf("loading 1004\n");
GET_HUNK_ERR(doc, PNTMDocument, buffer, buffer_end) {
fprintf(stderr, "bad file :0001\n");
return 0;
}
int layerIndex;
int layerCount = (int)DecodeInt32(doc->layerCount);
if(layerCount > 1000) {
fprintf(stderr, "yikes! you've got more than 1000 layers!\n");
return 0;
}
int width = (int)DecodeInt32(doc->width);
int height = (int)DecodeInt32(doc->height);
float zoom = DecodeFloat32(doc->zoom);
float panX = DecodeFloat32(doc->panX);
float panY = DecodeFloat32(doc->panY);
//printf("decoded pan: %f %f\n", panX, panY);
painting->pan.x = 0.0f;
painting->pan.y = 0.0f;
painting->zoom = 0.0;
//printf("loading document, layerCount=%d:\n", layerCount);
for(layerIndex = 0; layerIndex < layerCount; ++layerIndex)
{
GET_HUNK_ERR(lyr, PNTMLayer, buffer, buffer_end) {
fprintf(stderr, "bad file :0002\n");
return 0;
}
if(layerIndex > 0) // already have one layer implicitly created in _init
Painting_createLayer(painting);
PaintLayer *currentLayer = (PaintLayer *)ArrayStack_top(&painting->layers);
currentLayer->needsCompleteRedraw = 1;
currentLayer->translation.x = 0.0f;
currentLayer->translation.y = 0.0f;
currentLayer->opacity = 0.0f;
int strokeIndex;
int strokeCount = (int)DecodeInt32(lyr->strokeCount);
//printf(" strokeCount=%d\n", strokeCount);
for(strokeIndex = 0; strokeIndex < strokeCount; ++strokeIndex)
{
GET_HUNK_ERR(command, PNTMCommand, buffer, buffer_end) {
fprintf(stderr, "bad file :0003\n");
return 0;
}
int type = DecodeInt32(command->type);
switch(type)
{
case PaintCommandTypeBrushStroke:
{
GET_HUNK_ERR(str, PNTMStroke, buffer, buffer_end) {
fprintf(stderr, "bad file :0003\n");
return 0;
}
float r = DecodeFloat32(str->red);
float g = DecodeFloat32(str->green);
float b = DecodeFloat32(str->blue);
float a = DecodeFloat32(str->alpha);
float size = DecodeFloat32(str->size);
int brush = (int)DecodeInt32(str->brush);
//printf(" stroke %d, (%f %f %f %f) %f (%x)\n", strokeIndex, r, g, b, a, size, str->size);
Painting_setBrushSize(painting, size);
Painting_setBrushColor(painting, Color_(r, g, b, a));
Painting_setBrushType(painting, brush);
Painting_beginStroke(painting, 0);
int pointIndex;
int pointCount = (int)DecodeInt32(str->pointCount);
//printf(" pointCount=%d\n", pointCount);
for(pointIndex = 0; pointIndex < pointCount; ++pointIndex)
{
GET_HUNK_ERR(pnt, PNTMPoint, buffer, buffer_end) {
fprintf(stderr, "bad file :0004\n");
return 0;
}
float x = DecodeFloat32(pnt->x);
float y = DecodeFloat32(pnt->y);
float p = DecodeFloat32(pnt->p);
Painting_strokeToPoint(painting, Vector2D_(x, y), p);
//printf(" . %f %f . %f\n", x, y, p);
}
Painting_endStroke(painting);
break;
}
case PaintCommandTypeTransformTranslate:
{
GET_HUNK_ERR(cmd, PNTMTranslateCommand, buffer, buffer_end) {
fprintf(stderr, "bad file :0011\n");
return 0;
}
PaintStroke *stroke = Painting_beginTransform(painting, PaintCommandTypeTransformTranslate);
stroke->commands.translateCommand.translate.x = DecodeFloat32(cmd->translateX);
stroke->commands.translateCommand.translate.y = DecodeFloat32(cmd->translateY);
Painting_endTransform(painting);
break;
}
case PaintCommandTypeTransformRotate:
{
GET_HUNK_ERR(cmd, PNTMRotateCommand, buffer, buffer_end) {
fprintf(stderr, "bad file :0011\n");
return 0;
}
PaintStroke *stroke = Painting_beginTransform(painting, PaintCommandTypeTransformRotate);
stroke->commands.rotateCommand.axis.x = DecodeFloat32(cmd->axisX);
stroke->commands.rotateCommand.axis.y = DecodeFloat32(cmd->axisY);
stroke->commands.rotateCommand.angle = DecodeFloat32(cmd->angle);
Painting_endTransform(painting);
break;
}
case PaintCommandTypeTransformScale:
{
GET_HUNK_ERR(cmd, PNTMScaleCommand, buffer, buffer_end) {
fprintf(stderr, "bad file :0011\n");
return 0;
}
PaintStroke *stroke = Painting_beginTransform(painting, PaintCommandTypeTransformScale);
stroke->commands.scaleCommand.center.x = DecodeFloat32(cmd->centerX);
stroke->commands.scaleCommand.center.y = DecodeFloat32(cmd->centerY);
stroke->commands.scaleCommand.amount = DecodeFloat32(cmd->amount);
Painting_endTransform(painting);
break;
}
default:
printf("attempting to decode bad command type!\n");
return 0;
}
}
// because _strokeToPoint looks at these values
currentLayer->translation.x = DecodeFloat32(lyr->translationX);
currentLayer->translation.y = DecodeFloat32(lyr->translationX);
currentLayer->opacity = DecodeFloat32(lyr->opacity);
}
// aha! pan is accumulated in _strokeToPoint, so must be zero during load...
painting->pan.x = panX;
painting->pan.y = panY;
painting->zoom = zoom;
*saveWidth = width;
*saveHeight = height;
return 1;
}
int Painting_loadData_version1003(Painting *painting, const unsigned char *buffer, const unsigned char *buffer_end, int *saveWidth, int *saveHeight)
{
GET_HUNK_ERR(doc, PNTMDocument, buffer, buffer_end) {
fprintf(stderr, "bad file :0001\n");
return 0;
}
int layerIndex;
int layerCount = (int)DecodeInt32(doc->layerCount);
if(layerCount > 1000) {
fprintf(stderr, "yikes! you've got more than 1000 layers!\n");
return 0;
}
int width = (int)DecodeInt32(doc->width);
int height = (int)DecodeInt32(doc->height);
float zoom = DecodeFloat32(doc->zoom);
float panX = DecodeFloat32(doc->panX);
float panY = DecodeFloat32(doc->panY);
//printf("decoded pan: %f %f\n", panX, panY);
painting->pan.x = 0.0f;
painting->pan.y = 0.0f;
painting->zoom = 0.0;
//printf("loading document, layerCount=%d:\n", layerCount);
for(layerIndex = 0; layerIndex < layerCount; ++layerIndex)
{
GET_HUNK_ERR(lyr, PNTMLayer, buffer, buffer_end) {
fprintf(stderr, "bad file :0002\n");
return 0;
}
if(layerIndex > 0) // already have one layer implicitly created in _init
Painting_createLayer(painting);
PaintLayer *currentLayer = (PaintLayer *)ArrayStack_top(&painting->layers);
currentLayer->needsCompleteRedraw = 1;
currentLayer->translation.x = 0.0f;
currentLayer->translation.y = 0.0f;
currentLayer->opacity = 0.0f;
int strokeIndex;
int strokeCount = (int)DecodeInt32(lyr->strokeCount);
//printf(" strokeCount=%d\n", strokeCount);
for(strokeIndex = 0; strokeIndex < strokeCount; ++strokeIndex)
{
GET_HUNK_ERR(str, PNTMStroke, buffer, buffer_end) {
fprintf(stderr, "bad file :0003\n");
return 0;
}
float r = DecodeFloat32(str->red);
float g = DecodeFloat32(str->green);
float b = DecodeFloat32(str->blue);
float a = DecodeFloat32(str->alpha);
float size = DecodeFloat32(str->size);
int brush = (int)DecodeInt32(str->brush);
//printf(" stroke %d, (%f %f %f %f) %f (%x)\n", strokeIndex, r, g, b, a, size, str->size);
Painting_setBrushSize(painting, size);
Painting_setBrushColor(painting, Color_(r, g, b, a));
Painting_setBrushType(painting, brush);
Painting_beginStroke(painting, 0);
int pointIndex;
int pointCount = (int)DecodeInt32(str->pointCount);
//printf(" pointCount=%d\n", pointCount);
for(pointIndex = 0; pointIndex < pointCount; ++pointIndex)
{
GET_HUNK_ERR(pnt, PNTMPoint, buffer, buffer_end) {
fprintf(stderr, "bad file :0004\n");
return 0;
}
float x = DecodeFloat32(pnt->x);
float y = DecodeFloat32(pnt->y);
float p = DecodeFloat32(pnt->p);
Painting_strokeToPoint(painting, Vector2D_(x, y), p);
//printf(" . %f %f . %f\n", x, y, p);
}
Painting_endStroke(painting);
}
// because _strokeToPoint looks at these values
currentLayer->translation.x = DecodeFloat32(lyr->translationX);
currentLayer->translation.y = DecodeFloat32(lyr->translationX);
currentLayer->opacity = DecodeFloat32(lyr->opacity);
}
// aha! pan is accumulated in _strokeToPoint, so must be zero during load...
painting->pan.x = panX;
painting->pan.y = panY;
painting->zoom = zoom;
*saveWidth = width;
*saveHeight = height;
return 1;
}
int Painting_loadData(Painting *painting, const void *data, int length, int *saveWidth, int *saveHeight)
{
const unsigned char *buffer = (const unsigned char *)data;
const unsigned char *buffer_end = buffer + length;
ProgressOpInfo op;
StartProgressOp(&op);
int ret = 0;
PaintState saveState = painting->state;
GET_HUNK_ERR(header, PNTMHeader, buffer, buffer_end) {
fprintf(stderr, "could not get header chunk\n");
goto doReturn;
}
unless(
(header->hdr[0] == 'P') &&
(header->hdr[1] == 'N') &&
(header->hdr[2] == 'T') &&
(header->hdr[3] == 'M')) {
fprintf(stderr, "invalid start sequence\n");
goto doReturn;
}
int version = DecodeInt32(header->version);
switch(version)
{
case 1003: // 1.0
//printf("PNTM:version1:::\n");
ret = Painting_loadData_version1003(painting, buffer, buffer_end, saveWidth, saveHeight);
goto doReturn;
case 1004: // introduced in 1.2
ret = Painting_loadData_version1004(painting, buffer, buffer_end, saveWidth, saveHeight);
goto doReturn;
default:
fprintf(stderr, "cannot open file: bad version\n");
goto doReturn;
}
doReturn:
EndProgressOp(&op);
painting->state = saveState;
return ret;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment