Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/colorsc
diff options
context:
space:
mode:
Diffstat (limited to 'colorsc')
-rw-r--r--colorsc/Makefile11
-rw-r--r--colorsc/__init__.py0
-rw-r--r--colorsc/canvas.cpp28
-rw-r--r--colorsc/canvas.h1848
-rw-r--r--colorsc/colorsc.h205
-rw-r--r--colorsc/colorsc.i59
-rwxr-xr-xcolorsc/deploy.sh2
-rw-r--r--colorsc/drwfile.h77
-rw-r--r--colorsc/gtk_types.h130
-rw-r--r--colorsc/palette.cpp19
-rw-r--r--colorsc/palette.h458
-rw-r--r--colorsc/setup.py15
12 files changed, 2852 insertions, 0 deletions
diff --git a/colorsc/Makefile b/colorsc/Makefile
new file mode 100644
index 0000000..3b0055b
--- /dev/null
+++ b/colorsc/Makefile
@@ -0,0 +1,11 @@
+all: _colorsc.so
+
+clean:
+ rm -rf _colorsc.so *_wrap.cxx colorsc.py colorsc.pyc build
+
+%_wrap.cxx: %.i
+ swig -c++ -python $<
+
+_colorsc.so: colorsc_wrap.cxx colorsc.h canvas.h canvas.cpp
+ python setup.py build_ext --inplace
+
diff --git a/colorsc/__init__.py b/colorsc/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/colorsc/__init__.py
diff --git a/colorsc/canvas.cpp b/colorsc/canvas.cpp
new file mode 100644
index 0000000..ecdc11e
--- /dev/null
+++ b/colorsc/canvas.cpp
@@ -0,0 +1,28 @@
+/*
+ Copyright 2008 by Jens Andersson and Wade Brainerd.
+ This file is part of Colors! XO.
+
+ Colors is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Colors is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Colors. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "canvas.h"
+
+unsigned char BrushType::distance_tbl[DIST_TABLE_WIDTH][DIST_TABLE_WIDTH];
+
+BrushType Brush::brush_type[BrushType::NUM_BRUSHES];
+
+void test_method(void* data)
+{
+}
+
+
diff --git a/colorsc/canvas.h b/colorsc/canvas.h
new file mode 100644
index 0000000..6706c36
--- /dev/null
+++ b/colorsc/canvas.h
@@ -0,0 +1,1848 @@
+/*
+ Copyright 2008 by Jens Andersson and Wade Brainerd.
+ This file is part of Colors! XO.
+
+ Colors is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Colors is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Colors. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef _CANVAS_H_
+#define _CANVAS_H_
+
+#include "colorsc.h"
+#include "drwfile.h"
+
+using namespace std;
+
+// Uncomment this to print all executed drawing commands to stdout.
+//#define CANVAS_DEBUG_COMMANDS
+
+// Structure for passing pixel data to and from Python.
+struct SurfaceA8R8G8B8
+{
+ int width, height;
+ int stride;
+ unsigned int* pixels;
+};
+
+// Structure for passing buffers of draw commands to and from Python.
+struct DrawCommandBuffer
+{
+ DrawCommandBuffer()
+ {
+ cmds = NULL;
+ ncommands = 0;
+ }
+
+ DrawCommandBuffer(const char* _cmds, int _ncommands)
+ {
+ cmds = (char*)malloc(_ncommands*sizeof(unsigned int));
+ memcpy(cmds, _cmds, _ncommands*sizeof(unsigned int));
+ ncommands = _ncommands;
+ }
+
+ DrawCommandBuffer(const DrawCommandBuffer& b)
+ {
+ cmds = (char*)malloc(b.ncommands*sizeof(unsigned int));
+ memcpy(cmds, b.cmds, b.ncommands*sizeof(unsigned int));
+ ncommands = b.ncommands;
+ }
+
+ ~DrawCommandBuffer()
+ {
+ if (cmds)
+ {
+ free((void*)cmds);
+ cmds = NULL;
+ }
+ }
+
+ const DrawCommandBuffer& operator=(const DrawCommandBuffer& b)
+ {
+ if (cmds)
+ free((void*)cmds);
+ cmds = (char*)malloc(b.ncommands*sizeof(unsigned int));
+ memcpy(cmds, b.cmds, b.ncommands*sizeof(unsigned int));
+ ncommands = b.ncommands;
+ return *this;
+ }
+
+ void append(const DrawCommandBuffer& b)
+ {
+ char* newcmds = (char*)malloc((ncommands+b.ncommands)*sizeof(unsigned int));
+ if (cmds)
+ {
+ memcpy(newcmds, cmds, ncommands*sizeof(unsigned int));
+ free(cmds);
+ }
+ memcpy(newcmds + ncommands*sizeof(unsigned int), b.cmds, b.ncommands*sizeof(unsigned int));
+ cmds = newcmds;
+ ncommands = ncommands + b.ncommands;
+ }
+
+ void clear()
+ {
+ if (cmds)
+ {
+ free(cmds);
+ cmds = NULL;
+ }
+ ncommands = 0;
+ }
+
+ ByteBuffer get_bytes()
+ {
+ ByteBuffer buf;
+ buf.size = ncommands*sizeof(unsigned int);
+ buf.data = cmds;
+ return buf;
+ }
+
+ char* cmds;
+ int ncommands;
+
+ static DrawCommandBuffer create_from_string(const char* cmds, int ncommands)
+ {
+ return DrawCommandBuffer(cmds, ncommands);
+ }
+};
+
+struct DrawCommand
+{
+ enum
+ {
+ TYPE_DRAW = 0,
+ TYPE_DRAWEND = 1,
+ TYPE_COLORCHANGE = 2,
+ TYPE_SIZECHANGE = 3,
+ };
+
+ int type;
+ Pos pos;
+ Color color;
+ int pressure;
+ bool flipx;
+ bool flipy;
+ bool is_text;
+ uint32_t text;
+ int brush_control;
+ int brush_type;
+ float size;
+ float opacity;
+
+ DrawCommand()
+ {
+ type = TYPE_DRAW;
+ pressure = 0;
+ flipx = false;
+ flipy = false;
+ is_text = false;
+ text = 0;
+ brush_control = 0;
+ brush_type = 0;
+ size = 0;
+ opacity = 0;
+ }
+
+ static DrawCommand create_color_change(const Color& c)
+ {
+ DrawCommand cmd;
+ cmd.type = TYPE_COLORCHANGE;
+ cmd.color = c;
+ return cmd;
+ }
+
+ static DrawCommand create_draw(const Pos& pos, int pressure)
+ {
+ DrawCommand cmd;
+ cmd.type = TYPE_DRAW;
+ cmd.pos = pos;
+ cmd.pressure = pressure;
+ return cmd;
+ }
+
+ static DrawCommand create_end_draw(int pressure)
+ {
+ DrawCommand cmd;
+ cmd.type = TYPE_DRAWEND;
+ cmd.pressure = pressure;
+ return cmd;
+ }
+
+ static DrawCommand create_size_change(int brush_control, int brush_type, float size, float opacity)
+ {
+ DrawCommand cmd;
+ cmd.type = TYPE_SIZECHANGE;
+ cmd.brush_control = brush_control;
+ cmd.brush_type = brush_type;
+ cmd.size = size;
+ cmd.opacity = opacity;
+ return cmd;
+ }
+
+ static DrawCommand create_flip(bool flipx)
+ {
+ DrawCommand cmd;
+ cmd.type = TYPE_COLORCHANGE;
+ cmd.flipx = flipx;
+ cmd.flipy = !flipx;
+ return cmd;
+ }
+};
+
+struct BrushType
+{
+ enum
+ {
+ BRUSHTYPE_HARD = 0,
+ BRUSHTYPE_SOFT = 1,
+ BRUSHTYPE_CURSOR = 2,
+ NUM_BRUSHES = 3,
+ };
+
+ static const int DIST_TABLE_WIDTH = 256; // Width of distance lookup-table
+ static const int DIST_TABLE_CENTER = DIST_TABLE_WIDTH / 2; // Center of distance lookup-table
+
+ static const int BRUSH_TABLE_WIDTH = 256; // Width of brush lookup-table
+ static const int BRUSH_TABLE_HEIGHT = 65; // Height of 65 allows a brushsize down to 1.0f
+
+ static const float EXTRA_BRUSH_SCALE = 1.023f; // Scales down the brush-size so we don't index-out-of-range.
+
+ static unsigned char distance_tbl[DIST_TABLE_WIDTH][DIST_TABLE_WIDTH];
+
+ unsigned char intensity_tbl[BRUSH_TABLE_WIDTH][BRUSH_TABLE_HEIGHT];
+
+ // Creates a simple sqrt-lookup table.
+ static void create_distance_table()
+ {
+ for (int x = 0; x < DIST_TABLE_WIDTH; x++)
+ for (int y = 0; y < DIST_TABLE_WIDTH; y++)
+ {
+ int dx = x - DIST_TABLE_CENTER;
+ int dy = y - DIST_TABLE_CENTER;
+ float dist = sqrtf(float(dx * dx + dy * dy));
+ distance_tbl[x][y] = (unsigned char)min(255.0f, dist*255/DIST_TABLE_CENTER);
+ }
+ }
+
+ // Calculates a gradient between 0 and 1 where the derivate of both 0 and 1 is 0.
+ // This function is used to calculate the falloff of the brushes.
+ float smooth_step(float a)
+ {
+ return sinf((a*a - 0.5f) * 3.14159f) * 0.5f + 0.5f;
+ }
+
+ void create_brush(float brush_border, float amp)
+ {
+ // Find at what range from brush-center the brush intensity goes below 2
+ float max_r = 0;
+ for (int i = BRUSH_TABLE_WIDTH-1; i >= 0; i--)
+ {
+ float f = float(i) / BRUSH_TABLE_WIDTH;
+ float f2 = 1.0f - (f - brush_border) / (1.0f - brush_border);
+ if (round(smooth_step(f2) * amp) >= 2)
+ {
+ max_r = i;
+ break;
+ }
+ }
+
+ // Calculate a scale-factor so the brush optimally uses the area
+ float r = float(max_r + 2) / BRUSH_TABLE_WIDTH / BRUSH_TABLE_WIDTH;
+
+ for (int y = 0; y < BRUSH_TABLE_HEIGHT; y++)
+ {
+ // Each line in the brush-table is calculated for a specific brush-size
+ // This has two functions:
+ // 1. Be able to simulate the effect of resampling of the "perfect" big brush to a smaller one to improve precision
+ // 2. Compensate for the need to scale small brushes to avoid index out of range during rastering
+
+ // Calculate scale for this width
+ float brushscale = EXTRA_BRUSH_SCALE + y * 2.0f / 64.0f;
+
+ // Calculate brush
+ unsigned int intensity_row[BRUSH_TABLE_WIDTH];
+ for (int i = 0; i < BRUSH_TABLE_WIDTH; i++)
+ {
+ float f = min(i * r * brushscale, 1.0f); // Apply the two different scales
+ if (f < brush_border)
+ intensity_row[i] = int(amp);
+ else
+ {
+ float f2 = 1.0f - (f - brush_border) / (1.0f - brush_border);
+ f2 = smooth_step(f2) * amp; // Make sure the border-falloff is smooth
+ intensity_row[i] = int(round(f2));
+ }
+ }
+
+ // Simulate the effect of resampling
+ int blurradius = int(round(y * BRUSH_TABLE_WIDTH / (brushscale * 64.0f)));
+ float maxintensity = 0;
+ for (int x = 0; x < BRUSH_TABLE_WIDTH; x++)
+ {
+ float l = 0;
+ for (int x2 = x - blurradius; x2 < x + blurradius + 1; x2++)
+ {
+ int i = min(max(x2, 0), BRUSH_TABLE_WIDTH-1);
+ if (i < BRUSH_TABLE_WIDTH)
+ l += intensity_row[i];
+ }
+ float intensity = l / (blurradius * 2 + 1);
+ if (intensity > maxintensity)
+ maxintensity = intensity;
+ intensity_tbl[x][y] = int(intensity);
+ }
+ }
+ }
+
+ void create_hard_brush()
+ {
+ create_brush(0.8f, 255);
+ }
+
+ void create_soft_brush()
+ {
+ create_brush(0.0f, 128);
+ }
+
+ void create_cursor()
+ {
+ }
+};
+
+class Brush
+{
+public:
+ enum
+ {
+ BRUSHCONTROL_VARIABLEOPACITY = 1,
+ BRUSHCONTROL_VARIABLESIZE = 2,
+ };
+
+ static BrushType brush_type[BrushType::NUM_BRUSHES];
+
+ Color color;
+ int type;
+ int size;
+ int control;
+ float opacity;
+
+ Brush()
+ {
+ type = BrushType::BRUSHTYPE_HARD;
+ color = Color(255, 255, 255, 255);
+ size = 32;
+ control = 0;
+ opacity = 1.0f;
+ }
+
+ Brush(const Brush& a)
+ {
+ type = a.type;
+ color = a.color;
+ size = a.size;
+ control = a.control;
+ opacity = a.opacity;
+ }
+};
+
+// The canvas represents the current state of the user's painting. It maintains both the pixels representing the
+// image, and also the complete list of drawing commands that contributed to the image.
+class Canvas
+{
+public:
+ // Dimensions of the reference picture (webcam snapshot).
+ static const int REFERENCE_WIDTH = 640;
+ static const int REFERENCE_HEIGHT = 480;
+
+ // Dimensions of the videopaint buffer.
+ static const int VIDEO_WIDTH = 80;
+ static const int VIDEO_HEIGHT = 60;
+
+ enum
+ {
+ DRAWBRUSH_TYPE_NORMAL = 0,
+ DRAWBRUSH_TYPE_OLDCURSOR = 1,
+ DRAWBRUSH_TYPE_DIRECT = 2,
+ DRAWBRUSH_TYPE_GETCOLOR = 3,
+ DRAWBRUSH_TYPE_CURSOR = 4,
+ };
+
+ // List of drawing commands that make up the painting.
+ vector<DrawCommand> commands;
+
+ // Canvas dimensions.
+ int width;
+ int height;
+
+ // Current state of the canvas pixels.
+ unsigned int* image;
+
+ // Backup and alpha channel are used for multiple purposes. See description in Drawing section.
+ unsigned int* image_backup;
+ unsigned char* alpha;
+
+ // Shared (master) picture for collaborative painting.
+ unsigned int* image_shared;
+
+ // Reference (webcam snapshot) picture.
+ unsigned short* image_reference;
+
+ // Videopaint variables.
+ unsigned int* image_video[2];
+ int video_idx;
+ Pos videopaint_pos;
+ float videopaint_pressure;
+
+ // Current brush state.
+ Brush brush;
+
+ // Variables for interpolating the brush across strokes.
+ Pos lastpos;
+ Pos lastorgpos;
+ float lastpressure;
+
+ // Dimensions of the canvas that have been modified since the last call to reset_dirty_rect.
+ Pos dirtymin;
+ Pos dirtymax;
+
+ // Dimensions and state of the current stroke.
+ Pos strokemin;
+ Pos strokemax;
+ bool stroke;
+ int idle_while_drawing;
+ int drawtype;
+
+ // VCR playback variables.
+ bool playing;
+ int playback;
+ int playback_speed;
+
+ // True if the canvas has been modified since the last save.
+ bool modified;
+
+ Canvas(int width, int height) : width(width), height(height)
+ {
+ image = new unsigned int[width*height];
+ image_backup = new unsigned int[width*height];
+ alpha = new unsigned char[width*height];
+
+ image_shared = new unsigned int[width*height];
+
+ image_reference = new unsigned short[REFERENCE_WIDTH*REFERENCE_HEIGHT];
+ memset(image_reference, 0, REFERENCE_WIDTH*REFERENCE_HEIGHT*sizeof(unsigned short));
+
+ image_video[0] = new unsigned int[VIDEO_WIDTH*VIDEO_HEIGHT];
+ image_video[1] = new unsigned int[VIDEO_WIDTH*VIDEO_HEIGHT];
+ memset(image_video[0], 0, VIDEO_WIDTH*VIDEO_HEIGHT*sizeof(unsigned int));
+ memset(image_video[1], 0, VIDEO_WIDTH*VIDEO_HEIGHT*sizeof(unsigned int));
+ video_idx = 0;
+
+ clear();
+
+ // Initialize lookup table.
+ BrushType::create_distance_table();
+
+ // Initialize brushes.
+ Brush::brush_type[BrushType::BRUSHTYPE_HARD].create_hard_brush();
+ Brush::brush_type[BrushType::BRUSHTYPE_SOFT].create_soft_brush();
+ Brush::brush_type[BrushType::BRUSHTYPE_CURSOR].create_cursor();
+
+ reset_brush();
+
+ lastpos = Pos(0,0);
+ lastorgpos = Pos(0,0);
+ lastpressure = 0;
+
+ dirtymin = Pos(FLT_MAX,FLT_MAX);
+ dirtymax = Pos(-FLT_MAX,-FLT_MAX);
+
+ strokemin = Pos(0,0);
+ strokemax = Pos(0,0);
+ stroke = false;
+
+ playing = false;
+ playback = 0;
+ playback_speed = 1;
+ modified = false;
+
+ idle_while_drawing = 0;
+
+ drawtype = DRAWBRUSH_TYPE_NORMAL;
+ }
+
+ ~Canvas()
+ {
+ delete[] image;
+ delete[] image_backup;
+ delete[] image_shared;
+ delete[] alpha;
+ }
+
+ // Clears the entire canvas (command history and image).
+ void clear()
+ {
+ commands.clear();
+ clear_image();
+ }
+
+ // Changes the size of the canvas.
+ // Rather than trying to repaint everything from scratch, we simply quickly rescale it.
+ void resize(int new_width, int new_height)
+ {
+ unsigned int* new_image = new unsigned int[new_width*new_height];
+ unsigned int* new_image_backup = new unsigned int[new_width*new_height];
+ unsigned char* new_alpha = new unsigned char[new_width*new_height];
+ unsigned int* new_image_shared = new unsigned int[new_width*new_height];
+
+ int dx = (1<<16) * width / new_width;
+ int dy = (1<<16) * height / new_height;
+ int ry = 0;
+ for (int y = 0; y < new_height; y++)
+ {
+ int rx = 0;
+ for (int x = 0; x < new_width; x++)
+ {
+ int sofs = (ry>>16)*width + (rx>>16);
+ int dofs = y*new_width+x;
+ new_image[dofs] = image[sofs];
+ new_image_backup[dofs] = image_backup[sofs];
+ new_alpha[dofs] = alpha[sofs];
+ new_image_shared[dofs] = image_shared[sofs];
+ rx += dx;
+ }
+ ry += dy;
+ }
+
+ delete[] image;
+ delete[] image_backup;
+ delete[] alpha;
+ delete[] image_shared;
+
+ width = new_width;
+ height = new_height;
+
+ image = new_image;
+ image_backup = new_image_backup;
+ alpha = new_alpha;
+ image_shared = new_image_shared;
+ }
+
+ // Resets the brush to a random color and a default size and type.
+ void reset_brush()
+ {
+ // Choose a suitable random brush color.
+ srand(clock());
+ int c0 = rand()%3;
+ int c1 = c0 + 1 + rand()%2;
+ if (c1 > 2)
+ c1 -= 3;
+ brush.type = BrushType::BRUSHTYPE_HARD;
+ brush.color = Color::create_from_a8r8g8b8(0xff000000 | (255 << (c0 * 8) | ((rand()%255) << (c1 * 8))));
+ brush.size = width/16;
+ brush.control = Brush::BRUSHCONTROL_VARIABLESIZE;
+ brush.opacity = 1.0f;
+ }
+
+ //---------------------------------------------------------------------------------------------
+ // Shared image.
+ //
+ // The shared image is used in collaborative painting, and contains the current 'master' state of the canvas
+ //
+ // The user can paint ahead of the master state, but when a new master state is received the canvas
+ // state will be reset to the shared image. Before this happens though, the user commands are transmitted
+ // to the activity host, so they will be received again as a new master image later and not be lost.
+ void save_shared_image()
+ {
+ memcpy(image_shared, image, width*height*sizeof(unsigned int));
+ }
+
+ void restore_shared_image()
+ {
+ memcpy(image, image_shared, width*height*sizeof(unsigned int));
+ memcpy(image_backup, image_shared, width*height*sizeof(unsigned int));
+ }
+
+ //---------------------------------------------------------------------------------------------
+ // Drawing
+ //
+ // These methods are for drawing to and clearing the canvas.
+ //
+ // Drawing is not done directly to the canvas. The way the brush system works, the brush shapes are
+ // actually drawn into the alpha channel of the canvas, and the alpha channel is used to blend the
+ // brush color over the backup image to create the real image.
+ //
+ // The net effect this is that during a stroke, which may overlap itself many times over, the brush color
+ // will only be applied to any particular canvas pixel up to the defined brush transparency level.
+ // This is the core of our "natural media" engine.
+
+ void clear_image()
+ {
+ memset(image, 0xff, width*height*sizeof(unsigned int));
+ memset(image_backup, 0xff, width*height*sizeof(unsigned int));
+ memset(alpha, 0, width*height*sizeof(unsigned char));
+
+ memset(image_shared, 0xff, width*height*sizeof(unsigned int));
+
+ dirtymin = Pos(0, 0);
+ dirtymax = Pos(width, height);
+ }
+
+ // Called from command_draw and calculates a temporary brush size depending on pressure/alpha (0-255).
+ float get_variable_brush_size(float pressure)
+ {
+ if (brush.control & Brush::BRUSHCONTROL_VARIABLESIZE)
+ {
+ float size = pressure * brush.size / 255.0f;
+ if (size < 2.0f)
+ size = 2.0f;
+ return size;
+ }
+ else
+ return brush.size;
+ }
+
+ // Called each tick while stylus is touching the screen and draws the selected brush into the Alpha of the Canvas.
+ void command_draw(const Pos& pos, int pressure, bool forced)
+ {
+ lastorgpos = pos;
+
+ if (brush.control == 0)
+ pressure = 255;
+
+ int size, opacity;
+ if (!stroke)
+ {
+ // This is a new stroke. Just draw the brush on incoming position, store the information needed for interpolation and wait for next call
+ if (brush.control & Brush::BRUSHCONTROL_VARIABLESIZE)
+ size = int(get_variable_brush_size(pressure));
+ else
+ size = brush.size;
+
+ if (brush.control & Brush::BRUSHCONTROL_VARIABLEOPACITY)
+ opacity = int(round(pressure * brush.opacity));
+ else
+ opacity = int(round(255.0f * brush.opacity));
+ draw_brush(pos, size, opacity);
+
+ // Reset stroke dirty regions
+ strokemin = pos;
+ strokemax = pos;
+
+ lastpos = pos;
+ lastpressure = pressure;
+ idle_while_drawing = 0;
+ stroke = true;
+ }
+ else
+ {
+ // This is continous stroke. Interpolate from last postion/pressure
+
+ // Calculate the stroke-distance
+ float distx = pos.x - lastpos.x;
+ float disty = pos.y - lastpos.y;
+ float dista = pressure - lastpressure;
+ float distance = sqrtf(distx * distx + disty * disty);
+ if (distance == 0.0f) // To avoid division by zero. None or very small movements are handled later
+ distance = 0.0001f;
+
+ // Calculate interpolation constants
+ float dx = distx / distance;
+ float dy = disty / distance;
+ float da = dista / distance;
+
+ // Calculate the spacing between two brush-stamp. Normal spacing is 25% of brushsize.
+ float spacing = 0.225f;
+
+ // Do this special spacing only for just VariableOpacity with Hard brush to avoid banding
+ // Decrease spacing if pressure is changing rapidly
+ if (da != 0 && brush.control == Brush::BRUSHCONTROL_VARIABLEOPACITY && brush.type == BrushType::BRUSHTYPE_HARD)
+ spacing = min(0.225f, max(fabsf(15.0f / brush.size / (da * brush.opacity)), 0.05f));
+
+ // Calculate the distance between two brushes from spacing
+ float spacingdistance = get_variable_brush_size(lastpressure) * spacing;
+ if (distance < spacingdistance)
+ {
+ // Too small movement to interpolate
+ idle_while_drawing++;
+ if (idle_while_drawing > 15 || forced)
+ {
+ // We've been idling too long. Draw the brush and reduce idle-counter
+ // Idle-counter is invalid during playback, since playback only records input that actually drew something
+ idle_while_drawing = 10;
+
+ lastpos = pos;
+ lastpressure = pressure;
+ if (brush.control & Brush::BRUSHCONTROL_VARIABLEOPACITY)
+ draw_brush(pos, int(get_variable_brush_size(lastpressure)), int(round(pressure * brush.opacity)));
+ else
+ draw_brush(pos, int(get_variable_brush_size(lastpressure)), int(round(255.0f * brush.opacity)));
+ }
+ return;
+ }
+
+ if (brush.control & Brush::BRUSHCONTROL_VARIABLESIZE)
+ {
+ // Brush size is controlled by pressure
+ while (distance >= spacingdistance)
+ {
+ // Interpolate stroke
+ lastpressure += da * spacingdistance;
+ lastpos.x += dx * spacingdistance;
+ lastpos.y += dy * spacingdistance;
+ distance -= spacingdistance;
+
+ float brushsize = get_variable_brush_size(int(lastpressure));
+ if (brush.control & Brush::BRUSHCONTROL_VARIABLEOPACITY)
+ draw_brush(lastpos, int(get_variable_brush_size(int(lastpressure))), int(round(pressure * brush.opacity)));
+ else
+ draw_brush(lastpos, int(get_variable_brush_size(int(lastpressure))), int(round(255.0f * brush.opacity)));
+
+ // Since brush-size may have changed, we need to calculate new spacing
+ spacingdistance = brushsize * spacing;
+ }
+ }
+ else
+ {
+ // Brush size is static, so we can pre-multiply the interpolation constants
+ dx *= spacingdistance;
+ dy *= spacingdistance;
+ da *= spacingdistance;
+ while (distance >= spacingdistance)
+ {
+ lastpressure += da;
+ lastpos.x += dx;
+ lastpos.y += dy;
+ distance -= spacingdistance;
+
+ draw_brush(lastpos, brush.size, int(round(lastpressure * brush.opacity)));
+ }
+ }
+ }
+ }
+
+ // Called when stylus stops touching the screen.
+ void command_enddraw()
+ {
+ if (!stroke)
+ return;
+
+ // Copy current image to backup image and clear alpha in the region of the stroke.
+ int x0 = max(min(int(strokemin.x), width), 0);
+ int x1 = max(min(int(strokemax.x), width), 0);
+ int y0 = max(min(int(strokemin.y), height), 0);
+ int y1 = max(min(int(strokemax.y), height), 0);
+ for (int y = y0; y < y1; y++)
+ {
+ memcpy(&image_backup[y*width+x0], &image[y*width+x0], (x1-x0)*sizeof(unsigned int));
+ memset(&alpha[y*width+x0], 0, (x1-x0)*sizeof(unsigned char));
+ }
+
+ stroke = false;
+ }
+
+ // The canvas keeps track of the area that has been modified by draw commands, this is called the 'dirty' rectangle.
+ // The dirty rectangle keeps accumulating until reset_dirty_rect is called, at which point it is cleared to empty.
+ void reset_dirty_rect()
+ {
+ dirtymin = Pos(FLT_MAX,FLT_MAX);
+ dirtymax = Pos(-FLT_MAX,-FLT_MAX);
+ }
+
+ // Rasters a brush with specified width and opacity into alpha at a specified position using lookup-tables.
+ void draw_brush(const Pos& pos, int brushwidth, int opacity)
+ {
+ //printf("draw_brush %f,%f width=%d opacity=%d\n", pos.x, pos.y, brushwidth, opacity);
+
+ // Enforce minimum brush size.
+ if (brushwidth<2) brushwidth = 2;
+
+ // Calculate drawing rectangle.
+ float halfwidth = brushwidth/2;
+ float p0x = pos.x - halfwidth;
+ float p0y = pos.y - halfwidth;
+ float p1x = pos.x + halfwidth + 1;
+ float p1y = pos.y + halfwidth + 1;
+
+ int x0 = int(max(min(p0x, p1x), 0.0f));
+ int x1 = int(min(max(p0x, p1x), float(width)));
+ int y0 = int(max(min(p0y, p1y), 0.0f));
+ int y1 = int(min(max(p0y, p1y), float(height)));
+
+ // Accumulate dirty regions.
+ strokemin = Pos::create_from_min(strokemin, Pos(x0, y0));
+ strokemax = Pos::create_from_max(strokemax, Pos(x1, y1));
+ dirtymin = Pos::create_from_min(dirtymin, Pos(x0, y0));
+ dirtymax = Pos::create_from_max(dirtymax, Pos(x1, y1));
+
+ // Calculate interpolation constants
+ float db = (BrushType::DIST_TABLE_WIDTH-1) / float(brushwidth);
+
+ float xb = max(0.0f, BrushType::DIST_TABLE_CENTER - (pos.x - x0) * db);
+ float yb = max(0.0f, BrushType::DIST_TABLE_CENTER - (pos.y - y0) * db);
+
+ // Select which line of the brush-lookup-table to use that most closely matches the current brush width
+ int brushidx = int(float(BrushType::BRUSH_TABLE_HEIGHT) / brushwidth);
+
+ // Interpolate the distance table over the area. For each pixel find the distance, and look the
+ // brush-intensity up in the brush-table
+ if (drawtype == DRAWBRUSH_TYPE_NORMAL)
+ {
+ for (int y = y0; y < y1; y++)
+ {
+ float x2b = xb;
+ for (int x = x0; x < x1; x++)
+ {
+ // Find brush-intensity and mulitply that with incoming opacity
+ int lookup = BrushType::distance_tbl[int(x2b)][int(yb)];
+ int intensity = fixed_scale(Brush::brush_type[brush.type].intensity_tbl[lookup][brushidx], opacity);
+
+ // New Alpha = Brush Intensity + Old Alpha - (Brush Intensity * Old Alpha)
+ // Also make sure the result is clamped to the incoming opacity and isn't lower than the alpha
+ // already stored
+ int base = alpha[y*width+x];
+ int a = max(min(intensity + base - ((intensity * base) >> 8), opacity), base);
+ alpha[y*width+x] = a;
+
+ Color i = Color::create_from_a8r8g8b8(image_backup[y*width+x]);
+ i = Color::create_from_lerp(brush.color, i, a);
+ image[y*width+x] = i.get_a8r8g8b8();
+
+ x2b += db;
+ }
+ yb += db;
+ }
+ }
+ else if (drawtype == DRAWBRUSH_TYPE_GETCOLOR)
+ {
+ // Calculate color from a weighted average of the area that the brush touches.
+ Color c(0, 0, 0, 0);
+ for (int y = y0; y < y1; y++)
+ {
+ float x2b = xb;
+ for (int x = x0; x < x1; x++)
+ {
+ int lookup = BrushType::distance_tbl[int(x2b)][int(yb)];
+ int intensity = fixed_scale(Brush::brush_type[brush.type].intensity_tbl[lookup][brushidx], opacity);
+ Color i = Color::create_from_a8r8g8b8(image[y*width+x]);
+ c.r += i.r * intensity;
+ c.g += i.g * intensity;
+ c.b += i.b * intensity;
+ c.a += intensity;
+ x2b += db;
+ }
+ yb += db;
+ }
+ brush.color.r = c.r / c.a;
+ brush.color.g = c.g / c.a;
+ brush.color.b = c.b / c.a;
+ }
+ }
+
+ // Return the color underneath the pos.
+ Color pickup_color(const Pos& pos)
+ {
+ int x = int(max(min(pos.x, float(width)), 0.0f));
+ int y = int(max(min(pos.y, float(height)), 0.0f));
+ return Color::create_from_a8r8g8b8(image[y*width+x]);
+ }
+
+ //---------------------------------------------------------------------------------------------
+ // Playback
+ //
+ // These allow the canvas to be treated like a VCR, playing back and rewinding drawing commands
+ // to recreate the state of the canvas at different times.
+
+ void add_command(const DrawCommand& cmd)
+ {
+ commands.push_back(cmd);
+ modified = true;
+ }
+
+ void play_command(const DrawCommand& cmd, bool add)
+ {
+ if (cmd.type == DrawCommand::TYPE_DRAW)
+ {
+#ifdef CANVAS_DEBUG_COMMANDS
+ printf("TYPE_DRAW x=%f y=%f pressure=%d\n", cmd.pos.x, cmd.pos.y, cmd.pressure);
+#endif
+ Pos relpos = cmd.pos * Pos(width, height);
+ bool forcedraw = !add; // If we are not adding, we are playing back
+ command_draw(relpos, cmd.pressure, forcedraw);
+ }
+ else if (cmd.type == DrawCommand::TYPE_DRAWEND)
+ {
+#ifdef CANVAS_DEBUG_COMMANDS
+ printf("TYPE_DRAWEND pressure=%d\n", cmd.pressure);
+#endif
+ //if self.stroke and (self.lastpos.x != self.lastorgpos.x or self.lastpos.y != self.lastorgpos.y):
+ // self.command_draw(self.lastorgpos, cmd.pressure, add)
+ command_enddraw();
+ }
+ else if (cmd.type == DrawCommand::TYPE_COLORCHANGE)
+ {
+#ifdef CANVAS_DEBUG_COMMANDS
+ printf("TYPE_COLORCHANGE flipx=%d flipy=%d r=%d g=%d b=%d a=%d\n", cmd.flipx, cmd.flipy, cmd.color.r, cmd.color.g, cmd.color.b, cmd.color.a);
+#endif
+ if (cmd.flipx || cmd.flipy)
+ {
+ // fixme
+ //pygame.transform.flip(self.image, cmd.flipx, cmd.flipy)
+ }
+ else
+ brush.color = cmd.color;
+ }
+ else if (cmd.type == DrawCommand::TYPE_SIZECHANGE)
+ {
+#ifdef CANVAS_DEBUG_COMMANDS
+ printf("TYPE_SIZECHANGE size=%f control=%d type=%d opacity=%f\n", cmd.size, cmd.brush_control, cmd.brush_type, cmd.opacity);
+#endif
+ brush.size = int(cmd.size * width);
+ if (brush.size < 2)
+ brush.size = 2;
+ brush.control = cmd.brush_control;
+ brush.type = cmd.brush_type;
+ if (cmd.opacity > 0)
+ brush.opacity = cmd.opacity;
+ }
+
+#ifdef CANVAS_DEBUG_COMMANDS
+ fflush(stdout);
+#endif
+
+ if (add)
+ add_command(cmd);
+ }
+
+ bool playback_done()
+ {
+ return playback < 0 || playback >= (int)commands.size();
+ }
+
+ int playback_length()
+ {
+ return (int)commands.size();
+ }
+
+ int playback_pos()
+ {
+ return playback;
+ }
+
+ void start_playback()
+ {
+ command_enddraw();
+ clear_image();
+ playback = 0;
+ playing = true;
+ }
+
+ void pause_playback()
+ {
+ playing = false;
+ }
+
+ void resume_playback()
+ {
+ playing = true;
+ }
+
+ void stop_playback()
+ {
+ command_enddraw();
+ playback = -1;
+ playing = false;
+ }
+
+ void finish_playback()
+ {
+ while (!playback_done())
+ play_command(commands[playback++], false);
+ }
+
+ // This is used to avoid leaving the playback state in the middle of a stroke.
+ void playback_finish_stroke()
+ {
+ while (stroke && !playback_done())
+ play_command(commands[playback++], false);
+ }
+
+ void playback_to(int pos)
+ {
+ while (playback < pos && !playback_done())
+ play_command(commands[playback++], false);
+ }
+
+ void playback_step_to(int pos)
+ {
+ if (playback < pos && !playback_done())
+ play_command(commands[playback++], false);
+ }
+
+ // Same as playback_to, except it breaks if more than timeout seconds are taken.
+ void playback_to_timed(int pos, float timeout)
+ {
+ clock_t start = clock();
+ clock_t end = (clock_t)(start + timeout * CLOCKS_PER_SEC);
+ printf("start: %d end: %d CLOCKS_PER_SEC: %d\n", (int)start, (int)end, (int)CLOCKS_PER_SEC);
+ while (playback < pos && !playback_done() && clock() < end)
+ play_command(commands[playback++], false);
+ //if (clock() > end)
+ // printf("killed by timeout.\n");
+ }
+
+ void set_playback_speed(int speed)
+ {
+ playback_speed = speed;
+ }
+
+ void truncate_at_playback()
+ {
+ commands.resize(playback+1);
+ }
+
+ void update_playback()
+ {
+ if (playing)
+ {
+ for (int i = 0; i < playback_speed; i++)
+ {
+ if (!playback_done())
+ play_command(commands[playback++], false);
+ }
+ }
+ }
+
+ int get_num_commands()
+ {
+ return commands.size();
+ }
+
+ void play_range(int from, int to)
+ {
+ for (int i = from; i < to; i++)
+ play_command(commands[i], false);
+ }
+
+ //---------------------------------------------------------------------------------------------
+ // Blit
+ //
+ // Draws a region of the canvas into a GdkImage for display on the screen, with optional scaling
+ // and darkening.
+
+ void blit_1x(GdkImage* img, int src_x, int src_y, int dest_x, int dest_y, int dest_w, int dest_h, bool overlay)
+ {
+ unsigned short* pixels = (unsigned short*)img->mem;
+ int pitch = img->bpl/sizeof(unsigned short);
+
+ // Clip destination rectangle. Source clipping is handled per pixel.
+ if (dest_x < 0)
+ {
+ dest_w += dest_x;
+ dest_x = 0;
+ }
+ if (dest_y < 0)
+ {
+ dest_h += dest_y;
+ dest_y = 0;
+ }
+ if (dest_x+dest_w > img->width)
+ dest_w = img->width - dest_x;
+ if (dest_y+dest_h > img->height)
+ dest_h = img->height - dest_y;
+
+ int csy = src_y;
+ for (int cdy = dest_y; cdy < dest_y+dest_h; cdy++)
+ {
+ unsigned short* __restrict row = &pixels[cdy*pitch+dest_x];
+
+ // If out of bounds vertically, fill row with the background color.
+ if (csy < 0 || csy >= height)
+ {
+ for (int cdx = 0; cdx < dest_w; cdx++)
+ {
+ unsigned int rgb = 0;
+ *row++ = rgb;
+ }
+ }
+ else
+ {
+ unsigned int* __restrict src = &image[csy*width+src_x];
+
+ int cdx = 0;
+ int csx = src_x;
+
+ // Fill any portion that is to the left of the src image with
+ // background color.
+ while (csx < 0 && cdx < dest_w)
+ {
+ unsigned int rgb = 0;
+ *row++ = rgb;
+ src++;
+ csx++;
+ cdx++;
+ }
+
+ // Copy the pixels.
+ if (overlay)
+ {
+ while (csx < width && cdx < dest_w)
+ {
+ unsigned int p = *src;
+ p &= ~0x03030303;
+ p >>= 2;
+ unsigned int r = (((p>>16)&0xff)>>3)<<11;
+ unsigned int g = (((p>> 8)&0xff)>>2)<<5;
+ unsigned int b = (((p>> 0)&0xff)>>3);
+ unsigned int rgb = r|g|b;
+ *row++ = rgb;
+ src++;
+ csx++;
+ cdx++;
+ }
+ }
+ else
+ {
+ while (csx < width && cdx < dest_w)
+ {
+ unsigned int p = *src;
+ unsigned int r = (((p>>16)&0xff)>>3)<<11;
+ unsigned int g = (((p>> 8)&0xff)>>2)<<5;
+ unsigned int b = (((p>> 0)&0xff)>>3);
+ unsigned int rgb = r|g|b;
+ *row++ = rgb;
+ src++;
+ csx++;
+ cdx++;
+ }
+ }
+
+ // Fill any portion to the right of src with background pixels.
+ while (cdx < dest_w)
+ {
+ unsigned int rgb = 0;
+ *row++ = rgb;
+ src++;
+ csx++;
+ cdx++;
+ }
+ }
+
+ csy++;
+ }
+ }
+
+ void blit_2x(GdkImage* img, int src_x, int src_y, int dest_x, int dest_y, int dest_w, int dest_h, bool overlay)
+ {
+ unsigned short* pixels = (unsigned short*)img->mem;
+ int pitch = img->bpl/sizeof(unsigned short);
+
+ // Clip destination rectangle. Source clipping is handled per pixel.
+ if (dest_x < 0)
+ {
+ dest_w += dest_x;
+ dest_x = 0;
+ }
+ if (dest_y < 0)
+ {
+ dest_h += dest_y;
+ dest_y = 0;
+ }
+ if (dest_x+dest_w > img->width-2)
+ dest_w = (img->width-2) - dest_x;
+ if (dest_y+dest_h > img->height-2)
+ dest_h = (img->height-2) - dest_y;
+
+ int csy = src_y;
+ for (int cdy = dest_y; cdy < dest_y+dest_h; cdy += 2)
+ {
+ unsigned short* __restrict row0 = &pixels[cdy*pitch+dest_x];
+ unsigned short* __restrict row1 = row0 + pitch;
+
+ // If out of bounds vertically, fill row with the background color.
+ if (csy < 0 || csy >= height)
+ {
+ for (int cdx = 0; cdx < dest_w; cdx += 2)
+ {
+ unsigned int rgb = 0;
+ row0[0] = rgb;
+ row0[1] = rgb;
+ row1[0] = rgb;
+ row1[1] = rgb;
+ row0 += 2;
+ row1 += 2;
+ }
+ }
+ else
+ {
+ unsigned int* __restrict src = &image[csy*width+src_x];
+
+ int cdx = 0;
+ int csx = src_x;
+
+ // Fill any portion that is to the left of the src image with
+ // background color.
+ while (csx < 0 && cdx < dest_w)
+ {
+ unsigned int rgb = 0;
+ row0[0] = rgb;
+ row0[1] = rgb;
+ row1[0] = rgb;
+ row1[1] = rgb;
+ row0 += 2;
+ row1 += 2;
+ src++;
+ csx++;
+ cdx += 2;
+ }
+
+ // Copy the pixels.
+ if (overlay)
+ {
+ while (csx < width && cdx < dest_w)
+ {
+ unsigned int p = *src;
+ p &= ~0x03030303;
+ p >>= 2;
+ unsigned int r = (((p>>16)&0xff)>>3)<<11;
+ unsigned int g = (((p>> 8)&0xff)>>2)<<5;
+ unsigned int b = (((p>> 0)&0xff)>>3);
+ unsigned int rgb = r|g|b;
+ row0[0] = rgb;
+ row0[1] = rgb;
+ row1[0] = rgb;
+ row1[1] = rgb;
+ row0 += 2;
+ row1 += 2;
+ src++;
+ csx++;
+ cdx += 2;
+ }
+ }
+ else
+ {
+ while (csx < width && cdx < dest_w)
+ {
+ unsigned int p = *src;
+ unsigned int r = (((p>>16)&0xff)>>3)<<11;
+ unsigned int g = (((p>> 8)&0xff)>>2)<<5;
+ unsigned int b = (((p>> 0)&0xff)>>3);
+ unsigned int rgb = r|g|b;
+ row0[0] = rgb;
+ row0[1] = rgb;
+ row1[0] = rgb;
+ row1[1] = rgb;
+ row0 += 2;
+ row1 += 2;
+ src++;
+ csx++;
+ cdx += 2;
+ }
+ }
+
+ // Fill any portion to the right of src with background pixels.
+ while (cdx < dest_w)
+ {
+ unsigned int rgb = 0;
+ row0[0] = rgb;
+ row0[1] = rgb;
+ row1[0] = rgb;
+ row1[1] = rgb;
+ row0 += 2;
+ row1 += 2;
+ src++;
+ csx++;
+ cdx += 2;
+ }
+ }
+
+ csy++;
+ }
+ }
+
+ void blit_4x(GdkImage* img, int src_x, int src_y, int dest_x, int dest_y, int dest_w, int dest_h, bool overlay)
+ {
+ unsigned short* pixels = (unsigned short*)img->mem;
+ int pitch = img->bpl/sizeof(unsigned short);
+
+ // Clip rectangle.
+ // Clip destination rectangle. Source clipping is handled per pixel.
+ if (dest_x < 0)
+ {
+ dest_w += dest_x;
+ dest_x = 0;
+ }
+ if (dest_y < 0)
+ {
+ dest_h += dest_y;
+ dest_y = 0;
+ }
+ if (dest_x + dest_w > img->width-4)
+ dest_w = (img->width-4) - dest_x;
+ if (dest_y + dest_h > img->height-4)
+ dest_h = (img->height-4) - dest_y;
+
+ int csy = src_y;
+ for (int cdy = dest_y; cdy < dest_y+dest_h; cdy += 4)
+ {
+ unsigned short* __restrict row0 = &pixels[cdy*pitch+dest_x];
+ unsigned short* __restrict row1 = row0 + pitch;
+ unsigned short* __restrict row2 = row1 + pitch;
+ unsigned short* __restrict row3 = row2 + pitch;
+
+#define FILL_PIXEL(rgb) \
+ row0[0] = rgb; row0[1] = rgb; row0[2] = rgb; row0[3] = rgb; \
+ row1[0] = rgb; row1[1] = rgb; row1[2] = rgb; row1[3] = rgb; \
+ row2[0] = rgb; row2[1] = rgb; row2[2] = rgb; row2[3] = rgb; \
+ row3[0] = rgb; row3[1] = rgb; row3[2] = rgb; row3[3] = rgb; \
+ row0 += 4; row1 += 4; row2 += 4; row3 += 4;
+
+ if (csy < 0 || csy >= height)
+ {
+ for (int cdx = 0; cdx < dest_w; cdx += 4)
+ {
+ FILL_PIXEL(0)
+ }
+ }
+ else
+ {
+ unsigned int* __restrict src = &image[csy*width+src_x];
+
+ int cdx = 0;
+ int csx = src_x;
+
+ while (csx < 0 && cdx < dest_w)
+ {
+ FILL_PIXEL(0)
+
+ src++;
+ csx++;
+ cdx += 4;
+ }
+
+ if (overlay)
+ {
+ while (csx < width && cdx < dest_w)
+ {
+ unsigned int p = *src;
+ p &= ~0x03030303;
+ p >>= 2;
+ unsigned int r = (((p>>16)&0xff)>>3)<<11;
+ unsigned int g = (((p>> 8)&0xff)>>2)<<5;
+ unsigned int b = (((p>> 0)&0xff)>>3);
+ unsigned int rgb = r|g|b;
+ FILL_PIXEL(rgb)
+
+ src++;
+ csx++;
+ cdx += 4;
+ }
+ }
+ else
+ {
+ while (csx < width && cdx < dest_w)
+ {
+ unsigned int p = *src;
+ unsigned int r = (((p>>16)&0xff)>>3)<<11;
+ unsigned int g = (((p>> 8)&0xff)>>2)<<5;
+ unsigned int b = (((p>> 0)&0xff)>>3);
+ unsigned int rgb = r|g|b;
+ FILL_PIXEL(rgb)
+
+ src++;
+ csx++;
+ cdx += 4;
+ }
+ }
+
+ while (cdx < dest_w)
+ {
+ FILL_PIXEL(0)
+
+ src++;
+ csx++;
+ cdx += 4;
+ }
+ }
+
+#undef FILL_PIXEL
+
+ csy++;
+ }
+ }
+
+ void blit_8x(GdkImage* img, int src_x, int src_y, int dest_x, int dest_y, int dest_w, int dest_h, bool overlay)
+ {
+ unsigned short* pixels = (unsigned short*)img->mem;
+ int pitch = img->bpl/sizeof(unsigned short);
+
+ // Clip rectangle.
+ // Clip destination rectangle. Source clipping is handled per pixel.
+ if (dest_x < 0)
+ {
+ dest_w += dest_x;
+ dest_x = 0;
+ }
+ if (dest_y < 0)
+ {
+ dest_h += dest_y;
+ dest_y = 0;
+ }
+ if (dest_x + dest_w > img->width-8)
+ dest_w = (img->width-8) - dest_x;
+ if (dest_y + dest_h > img->height-8)
+ dest_h = (img->height-8) - dest_y;
+
+ int csy = src_y;
+ for (int cdy = dest_y; cdy < dest_y+dest_h; cdy += 8)
+ {
+ unsigned short* __restrict row0 = &pixels[cdy*pitch+dest_x];
+ unsigned short* __restrict row1 = row0 + pitch;
+ unsigned short* __restrict row2 = row1 + pitch;
+ unsigned short* __restrict row3 = row2 + pitch;
+ unsigned short* __restrict row4 = row3 + pitch;
+ unsigned short* __restrict row5 = row4 + pitch;
+ unsigned short* __restrict row6 = row5 + pitch;
+ unsigned short* __restrict row7 = row6 + pitch;
+
+#define FILL_PIXEL(rgb) \
+ row0[0] = rgb; row0[1] = rgb; row0[2] = rgb; row0[3] = rgb; row0[4] = rgb; row0[5] = rgb; row0[6] = rgb; row0[7] = rgb; \
+ row1[0] = rgb; row1[1] = rgb; row1[2] = rgb; row1[3] = rgb; row1[4] = rgb; row1[5] = rgb; row1[6] = rgb; row1[7] = rgb; \
+ row2[0] = rgb; row2[1] = rgb; row2[2] = rgb; row2[3] = rgb; row2[4] = rgb; row2[5] = rgb; row2[6] = rgb; row2[7] = rgb; \
+ row3[0] = rgb; row3[1] = rgb; row3[2] = rgb; row3[3] = rgb; row3[4] = rgb; row3[5] = rgb; row3[6] = rgb; row3[7] = rgb; \
+ row4[0] = rgb; row4[1] = rgb; row4[2] = rgb; row4[3] = rgb; row4[4] = rgb; row4[5] = rgb; row4[6] = rgb; row4[7] = rgb; \
+ row5[0] = rgb; row5[1] = rgb; row5[2] = rgb; row5[3] = rgb; row5[4] = rgb; row5[5] = rgb; row5[6] = rgb; row5[7] = rgb; \
+ row6[0] = rgb; row6[1] = rgb; row6[2] = rgb; row6[3] = rgb; row6[4] = rgb; row6[5] = rgb; row6[6] = rgb; row6[7] = rgb; \
+ row7[0] = rgb; row7[1] = rgb; row7[2] = rgb; row7[3] = rgb; row7[4] = rgb; row7[5] = rgb; row7[6] = rgb; row7[7] = rgb; \
+ row0 += 8; row1 += 8; row2 += 8; row3 += 8; row4 += 8; row5 += 8; row6 += 8; row7 += 8;
+
+ if (csy < 0 || csy >= height)
+ {
+ for (int cdx = 0; cdx < dest_w; cdx += 8)
+ {
+ FILL_PIXEL(0)
+ }
+ }
+ else
+ {
+ unsigned int* __restrict src = &image[csy*width+src_x];
+
+ int cdx = 0;
+ int csx = src_x;
+
+ while (csx < 0 && cdx < dest_w)
+ {
+ FILL_PIXEL(0)
+
+ src++;
+ csx++;
+ cdx += 8;
+ }
+
+ if (overlay)
+ {
+ while (csx < width && cdx < dest_w)
+ {
+ unsigned int p = *src;
+ p &= ~0x03030303;
+ p >>= 2;
+ unsigned int r = (((p>>16)&0xff)>>3)<<11;
+ unsigned int g = (((p>> 8)&0xff)>>2)<<5;
+ unsigned int b = (((p>> 0)&0xff)>>3);
+ unsigned int rgb = r|g|b;
+ FILL_PIXEL(rgb)
+
+ src++;
+ csx++;
+ cdx += 8;
+ }
+ }
+ else
+ {
+ while (csx < width && cdx < dest_w)
+ {
+ unsigned int p = *src;
+ unsigned int r = (((p>>16)&0xff)>>3)<<11;
+ unsigned int g = (((p>> 8)&0xff)>>2)<<5;
+ unsigned int b = (((p>> 0)&0xff)>>3);
+ unsigned int rgb = r|g|b;
+ FILL_PIXEL(rgb)
+
+ src++;
+ csx++;
+ cdx += 8;
+ }
+ }
+
+ while (cdx < dest_w)
+ {
+ unsigned int rgb = 0;
+ FILL_PIXEL(rgb)
+
+ src++;
+ csx++;
+ cdx += 8;
+ }
+ }
+
+#undef FILL_PIXEL
+
+ csy++;
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------
+ // Videopaint
+ //
+ // This code currently attempts to track a green object in the webcam and treat it as a
+ // mouse cursor, using the detected size to control the brush size.
+
+ void downsize_video(unsigned int* dest_pixels, GstBuffer* buf, int vwidth, int vheight)
+ {
+ if (vwidth != VIDEO_WIDTH*8 || vheight != VIDEO_HEIGHT*8 || buf->size != vwidth*vheight*sizeof(unsigned short))
+ {
+ printf("Invalid Gst video buffer size %d (%dx%d)\n", buf->size, vwidth, vheight);
+ return;
+ }
+
+ unsigned int* source_pixels = (unsigned int*)buf->data;
+
+ for (int y = 0; y < VIDEO_HEIGHT; y++)
+ {
+ unsigned int* __restrict src = &source_pixels[(y*4)*vwidth];
+ unsigned int* __restrict dest = &dest_pixels[y*VIDEO_WIDTH];
+ for (int x = 0; x < VIDEO_WIDTH; x++)
+ {
+ *dest = *src;
+ src += 4;
+ dest++;
+ }
+ }
+ }
+
+ void videopaint_motion(GstBuffer* buf, int vwidth, int vheight)
+ {
+ downsize_video(image_video[1], buf, vwidth, vheight);
+
+ unsigned int* pixels0 = image_video[0];
+ unsigned int* pixels1 = image_video[1];
+ double cx = 0, cy = 0, cnt = 0;
+ for (int y = 0; y < VIDEO_HEIGHT; y++)
+ {
+ unsigned int* __restrict row = &pixels1[y*VIDEO_WIDTH];
+ unsigned int* destrow = &pixels0[y*VIDEO_WIDTH];
+ for (int x = 0; x < VIDEO_WIDTH; x++)
+ {
+ // Convert YUYV to HSV
+ Color c = Color::yuv_to_hsv(*row);
+ // Threshold green
+ if ((c.r > 80) && (c.r < 150) && (c.g > 100)) {
+ destrow[0] = 0xffff;
+ cnt++;
+ cx += (80-x);
+ cy += y;
+ } else {
+ destrow[0] = 0;
+ }
+ row++;
+ destrow++;
+ }
+ }
+
+ if (cnt > 0)
+ {
+ cx /= cnt;
+ cy /= cnt;
+ // The mouse coordinates are scaled somewhat, as the nature of the video processing causes the blob to leave
+ // the screen partially and become smaller (and less significant) towards the edges.
+ videopaint_pos = Pos(
+ map_range(cx, 0, VIDEO_WIDTH, -0.2f, 1.2f),
+ map_range(cy, 0, VIDEO_HEIGHT, -0.2f, 1.2f));
+ // todo- estimate size
+ videopaint_pressure = map_range(cnt, 0, (VIDEO_WIDTH*VIDEO_HEIGHT)/8, 0, 255);
+ // Mark mouse pixel in red.
+ int rx = int(cx);
+ int ry = int(cy);
+ unsigned short red = Color(255,0,0,0).get_r5g6b5();
+ for (int x = -3; x <= 3; x++)
+ for (int y = -3; y <= 3; y++)
+ image_video[0][ry+y*VIDEO_WIDTH+rx+x] = red;
+ }
+ }
+
+ void blit_videopaint(GdkImage* img)
+ {
+ unsigned short* pixels = (unsigned short*)img->mem;
+ int pitch = img->bpl/sizeof(unsigned short);
+
+ for (int y = 0; y < VIDEO_HEIGHT; y++)
+ {
+ unsigned int* __restrict src = &image_video[0][y*VIDEO_WIDTH];
+ unsigned short* __restrict dest = &pixels[y*pitch];
+ for (int x = 0; x < VIDEO_WIDTH; x++)
+ {
+ unsigned int p = *src++;
+ unsigned int r = (((p>>16)&0xff)>>3)<<11;
+ unsigned int g = (((p>> 8)&0xff)>>2)<<5;
+ unsigned int b = (((p>> 0)&0xff)>>3);
+ unsigned int rgb = r|g|b;
+ *dest++ = rgb;
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------
+ // Reference image
+ //
+ // The reference image is a snapshot taken by the webcam that can transparently displayed over the canvas.
+ // Note that painting currently cannot take place while the reference image is shown.
+
+ void set_reference_buffer(GstBuffer* buf, int vwidth, int vheight)
+ {
+ if (vwidth != REFERENCE_WIDTH || vheight != REFERENCE_HEIGHT || buf->size != vwidth*vheight*sizeof(unsigned short))
+ {
+ printf("Invalid Gst reference buffer size %d\n", buf->size);
+ return;
+ }
+ memcpy(image_reference, buf->data, min(size_t(buf->size), size_t(REFERENCE_WIDTH*REFERENCE_HEIGHT*sizeof(unsigned short))));
+ }
+
+ void render_reference_overlay()
+ {
+ // Uses image_backup to blend over the canvas without affecting the contents of the canvas.
+ // Scales up from whatever REFERENCE_WIDTH/REFERENCE_HEIGHT are to the canvas size.
+ int dx = (1<<16) * REFERENCE_WIDTH / width;
+ int dy = (1<<16) * REFERENCE_HEIGHT / height;
+ int ry = 0;
+ for (int y = 0; y < height; y++, ry += dy)
+ {
+ int rx = 0;
+ for (int x = 0; x < width; x++, rx += dx)
+ {
+ Color r = Color::create_from_r5g6b5(image_reference[(ry>>16)*REFERENCE_WIDTH+(rx>>16)]);
+ r = Color::create_from_yuv(r.r, r.g, r.b);
+ Color b = Color::create_from_a8r8g8b8(image_backup[y*width+x]);
+ image[y*width+x] = Color::create_from_lerp(r, b, 192).get_a8r8g8b8();
+ }
+ }
+ }
+
+ //---------------------------------------------------------------------------------------------
+ // Overlay
+ //
+ // These functions basically just temporarily darken the entire canvas so that a PyGTK overlay
+ // like the brush controls can be drawn on top, and look like it has a translucent black background.
+
+ void render_overlay()
+ {
+ // Since the image is backed up in image_backup, it's ok to destroy the contents of image since
+ // clear_overlay will just restore it from image_backup.
+ for (int y = 0; y < height; y++)
+ for (int x = 0; x < width; x++)
+ {
+ unsigned int i = image_backup[y*width+x];
+ i &= ~0x03030303;
+ i >>= 2;
+ image[y*width+x] = i;
+ }
+ }
+
+ void clear_overlay()
+ {
+ memcpy(image, image_backup, width*height*sizeof(unsigned int));
+ }
+
+ //---------------------------------------------------------------------------------------------
+ // Load & Save
+
+ void upgrade_drw_header(DRW_Header* hdr, DRW_Command* cmds)
+ {
+ if (hdr->version == DRW_Header::ID) // Paying for old bug
+ hdr->version = 1002;
+
+ if (hdr->version < DRW_VERSION)
+ {
+ for(int i = 0; i < hdr->ncommands; i++)
+ {
+ DRW_Command* cmd = &cmds[i];
+ if (hdr->version < 1001)
+ {
+ if (cmd->type == DrawCommand::TYPE_DRAW)
+ {
+ cmd->x = int(round(cmd->x * 1024.0f / 2047.0f + 512.0f));
+ cmd->x = int(round(cmd->y * 1024.0f / 2047.0f + 512.0f));
+ }
+ }
+
+ if (hdr->version < 1002)
+ {
+ if (cmd->type == DrawCommand::TYPE_SIZECHANGE)
+ {
+ int type = (cmd->brushtype << 2) | cmd->brushcontrol;
+ switch(type)
+ {
+ case 0: cmd->brushtype = BrushType::BRUSHTYPE_HARD; cmd->brushcontrol = Brush::BRUSHCONTROL_VARIABLEOPACITY; break;
+ case 2: cmd->brushtype = BrushType::BRUSHTYPE_SOFT; cmd->brushcontrol = Brush::BRUSHCONTROL_VARIABLEOPACITY; break;
+ case 4: cmd->brushtype = BrushType::BRUSHTYPE_HARD; cmd->brushcontrol = 0; break;
+ case 6: cmd->brushtype = BrushType::BRUSHTYPE_SOFT; cmd->brushcontrol = 0; break;
+ }
+ cmd->size -= (1 << 6);
+ }
+ }
+ }
+ hdr->version = DRW_VERSION;
+ }
+ }
+
+ bool load(const char* filename)
+ {
+ FILE* drwfile = fopen(filename, "rb");
+ if (!drwfile)
+ return false;
+
+ int r;
+
+ DRW_Header header;
+ r = fread(&header, 1, sizeof(DRW_Header), drwfile);
+
+ // Backward compatible code for early versions when there was no header and the filesize is used
+ // to determine the number of commands.
+ if (header.id != DRW_Header::ID)
+ {
+ header.colorsversion_initial = 0;
+ fseek(drwfile, 0, SEEK_END);
+ header.ncommands = ftell(drwfile) / 4;
+ fseek(drwfile, 0, SEEK_SET);
+ }
+
+ DRW_Command* cmds = (DRW_Command*)malloc(header.ncommands*sizeof(DRW_Command));
+ r = fread(cmds, 1, header.ncommands*sizeof(DRW_Command), drwfile);
+
+ fclose(drwfile);
+
+ upgrade_drw_header(&header, cmds);
+
+ clear();
+ convert_from_drw(cmds, 0, header.ncommands);
+
+ free(cmds);
+
+ return true;
+ }
+
+ bool save(const char* filename)
+ {
+ FILE* drwfile = fopen(filename, "wb");
+ if (!drwfile)
+ return false;
+
+ int r;
+
+ DRW_Header header;
+ header.id = DRW_Header::ID;
+ header.version = DRW_VERSION;
+ header.colorsversion_initial = DRW_VERSION;
+ header.colorsversion_saved = DRW_VERSION;
+ header.strokes = 0;
+ header.time = 0;
+ header.timessaved = 0;
+ header.ncommands = commands.size();
+ r = fwrite(&header, 1, sizeof(DRW_Header), drwfile);
+
+ DRW_Command* cmds;
+ convert_to_drw(&cmds, 0, commands.size());
+
+ r = fwrite(cmds, sizeof(DRW_Command), commands.size(), drwfile);
+ free(cmds);
+
+ fclose(drwfile);
+
+ return true;
+ }
+
+ void convert_from_drw(DRW_Command* cmds, int start, int ncommands)
+ {
+ commands.resize(start+ncommands);
+ for (int i = 0; i < ncommands; i++)
+ {
+ DRW_Command* drw = &cmds[i];
+ DrawCommand* cmd = &commands[start+i];
+
+ cmd->type = drw->type;
+ cmd->pos.x = (drw->x-512.0f) / 1024.0f;
+ cmd->pos.y = (drw->y-512.0f) / 1024.0f;
+ cmd->pressure = drw->alpha;
+
+ cmd->color = Color::create_from_a8r8g8b8(drw->col);
+ cmd->flipx = drw->flipx;
+ cmd->flipy = drw->flipy;
+
+ cmd->brush_control = drw->brushcontrol;
+ cmd->brush_type = drw->brushtype;
+ cmd->size = drw->size / float(1 << 15);
+ cmd->opacity = drw->opacity / 255.0f;
+ }
+ }
+
+ void convert_to_drw(DRW_Command** cmds, int start, int ncommands)
+ {
+ *cmds = (DRW_Command*)malloc(sizeof(DRW_Command) * ncommands);
+
+ for (int i = 0; i < ncommands; i++)
+ {
+ DRW_Command* drw = &(*cmds)[i];
+ DrawCommand* cmd = &commands[start+i];
+
+ drw->type = cmd->type;
+
+ if (cmd->type == DrawCommand::TYPE_DRAW)
+ {
+ drw->x = int((cmd->pos.x*1024)+512);
+ drw->y = int((cmd->pos.y*1024)+512);
+ drw->alpha = cmd->pressure;
+ }
+ else if (cmd->type == DrawCommand::TYPE_DRAWEND)
+ {
+ drw->alpha = cmd->pressure;
+ }
+ else if (cmd->type == DrawCommand::TYPE_COLORCHANGE)
+ {
+ drw->flipx = cmd->flipx;
+ drw->flipy = cmd->flipy;
+ drw->col = cmd->color.get_a8r8g8b8();
+ }
+ else if (cmd->type == DrawCommand::TYPE_SIZECHANGE)
+ {
+ drw->brushcontrol = cmd->brush_control;
+ drw->brushtype = cmd->brush_type;
+ drw->size = int(cmd->size * float(1<<15));
+ drw->opacity = int(cmd->opacity * 255.0f);
+ }
+ }
+ }
+
+ DrawCommandBuffer send_drw_commands(int start, int ncommands)
+ {
+ DrawCommandBuffer buf;
+ buf.ncommands = ncommands;
+ convert_to_drw((DRW_Command**)&buf.cmds, start, ncommands);
+ return buf;
+ }
+
+ void receive_drw_commands(const DrawCommandBuffer& buf, int start)
+ {
+ convert_from_drw((DRW_Command*)buf.cmds, start, buf.ncommands);
+ }
+};
+
+#endif
+
diff --git a/colorsc/colorsc.h b/colorsc/colorsc.h
new file mode 100644
index 0000000..543a2ce
--- /dev/null
+++ b/colorsc/colorsc.h
@@ -0,0 +1,205 @@
+/*
+ Copyright 2008 by Jens Andersson and Wade Brainerd.
+ This file is part of Colors! XO.
+
+ Colors is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Colors is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Colors. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef _COLORSC_H_
+#define _COLORSC_H_
+
+#include <python2.5/Python.h>
+
+#include <vector>
+#include <algorithm>
+using namespace std;
+
+#include <cmath>
+#include <float.h>
+
+// todo- Include the real GTK headers when installed.
+#include "gtk_types.h"
+
+static const float PI = 3.14159f;
+
+inline float sgn(float a) { if (a>0) return 1; if (a<0) return -1; return 0; }
+
+inline float sqr(float a) { return a*a; }
+
+inline float clamp(float a, float mn, float mx) { return max(mn, min(mx, a)); }
+
+inline float to_rad(float degrees) { return degrees * PI/180.0f; }
+inline float to_deg(float rads) { return rads * 180.0f/PI; }
+
+// Maps an incoming value in a given f0-t0 range to a new value in the f1-t1 range, optionally clamping to f1-t1.
+inline float map_range(float a, float f0, float t0, float f1, float t1, bool clmp=true)
+{
+ float r = (a-f0)/(t0-f0);
+ if (clmp) r = clamp(r, 0.0f, 1.0f);
+ return f1+r*(t1-f1);
+}
+
+// Scales a value by an 8bit scale factor from 0-255. The MSB of the scale factor is added to it, which means that
+// scaling by 255 is an identity.
+inline int fixed_scale(int value, int scale)
+{
+ scale += scale >> 7;
+ return (value * scale) >> 8;
+}
+
+inline unsigned short endian_swap(unsigned short v)
+{
+ return (v>>8) | (v<<8);
+}
+
+struct Pos
+{
+ float x, y;
+ Pos() : x(0), y(0) {}
+ Pos(float x, float y) : x(x), y(y) {}
+ Pos operator+(const Pos& b) const { return Pos(x+b.x, y+b.y); }
+ Pos operator-(const Pos& b) const { return Pos(x-b.x, y-b.y); }
+ Pos operator*(const Pos& b) const { return Pos(x*b.x, y*b.y); }
+ Pos operator/(const Pos& b) const { return Pos(x/b.x, y/b.y); }
+ Pos operator*(float b) const { return Pos(x*b, y*b); }
+ Pos operator/(float b) const { return Pos(x/b, y/b); }
+ static Pos create_from_min(const Pos& a, const Pos& b) { return Pos(min(a.x,b.x), min(a.y,b.y)); }
+ static Pos create_from_max(const Pos& a, const Pos& b) { return Pos(max(a.x,b.x), max(a.y,b.y)); }
+ static Pos create_from_angle(float a, float r) { return Pos(cosf(a*PI/180.0f)*r, sinf(a*PI/180.0f)*r); }
+ static Pos create_from_rotation(const Pos& a, const Pos& center, float t)
+ {
+ return Pos(
+ (a.x-center.x)*cosf(t) - (a.y-center.y)*sinf(t) + center.x,
+ (a.y-center.y)*cosf(t) + (a.x-center.x)*sinf(t) + center.y);
+ }
+};
+
+struct Color
+{
+ unsigned char r, g, b, a;
+ Color() : r(0), g(0), b(0), a(0) {}
+ Color(int r, int g, int b, int a) : r(r), g(g), b(b), a(a) {}
+ unsigned int get_a8r8g8b8() { return (a<<24) | (r<<16) | (g<<8) | (b<<0); }
+ static Color create_from_a8r8g8b8(unsigned int v)
+ {
+ Color c;
+ c.a = (v>>24) & 0xff;
+ c.r = (v>>16) & 0xff;
+ c.g = (v>> 8) & 0xff;
+ c.b = (v>> 0) & 0xff;
+ return c;
+ }
+ unsigned int get_a8b8g8r8() { return (a<<24) | (b<<16) | (g<<8) | (r<<0); }
+ static Color create_from_a8b8g8r8(unsigned int v)
+ {
+ Color c;
+ c.a = (v>>24) & 0xff;
+ c.b = (v>>16) & 0xff;
+ c.g = (v>> 8) & 0xff;
+ c.r = (v>> 0) & 0xff;
+ return c;
+ }
+ unsigned int get_r5g6b5() { return ((r>>3)<<11) | ((g>>2)<<5) | (b>>3); }
+ static Color create_from_r5g6b5(unsigned short v)
+ {
+ Color c;
+ c.r = ((v>>11)&0x1f)<<3;
+ c.g = ((v>> 5)&0x1f)<<3;
+ c.b = ((v>> 0)&0x1f)<<3;
+ c.a = 255;
+ return c;
+ }
+ unsigned int get_b5g6r5() { return ((b>>3)<<11) | ((g>>2)<<5) | (r>>3); }
+ static Color create_from_float(float r, float g, float b, float a)
+ {
+ Color c;
+ c.r = int(r*255.0f);
+ c.g = int(g*255.0f);
+ c.b = int(b*255.0f);
+ c.a = int(a*255.0f);
+ return c;
+ }
+ static Color create_from_blend(const Color& a, const Color& b)
+ {
+ return create_from_lerp(a, b, a.a);
+ }
+ static Color create_from_lerp(const Color& a, const Color& b, unsigned int l)
+ {
+ unsigned int il = 255-l;
+ l += l >> 7;
+ il += il >> 7;
+ Color c;
+ c.r = ((a.r * l) + (b.r * il)) >> 8;
+ c.g = ((a.g * l) + (b.g * il)) >> 8;
+ c.b = ((a.b * l) + (b.b * il)) >> 8;
+ c.a = ((a.a * l) + (b.a * il)) >> 8;
+ return c;
+ }
+ static Color create_from_yuv(unsigned char y, unsigned char u, unsigned char v)
+ {
+ Color c;
+ c.r = (unsigned char)max(0.0f, min(255.0f, 1.164f*(y-16) + 1.596f*(v-128)));
+ c.g = (unsigned char)max(0.0f, min(255.0f, 1.164f*(y-16) - 0.813f*(v-128) - 0.391f*(u-128)));
+ c.b = (unsigned char)max(0.0f, min(255.0f, 1.164f*(y-16) + 2.018f*(u-128)));
+ return c;
+ }
+
+ static Color yuv_to_hsv(unsigned int yuv)
+ {
+ Color c, d;
+ unsigned char y,u,v,val,delta,vmin;
+ float hue, sat;
+ // v4l2src is sending YUYV, meaning, 4 bytes per 2 pixels.
+ // pixel 1 gets the first 8 bit y and both 8 bit U and V.
+ // pixel 2 gets the other y and u and v. we are only using pixel 1.
+ y = ((yuv>>24)&0xFF);
+ u = ((yuv>>16)&0xFF);
+ v = (yuv&0xFF);
+ // for now, yuv->first. TODO: go straight from yuv to hsv
+ d = create_from_yuv(y, u, v);
+
+ val = max(max(d.r, d.g), d.b);
+ vmin = min(min(d.r, d.g), d.b);
+ delta = val-vmin;
+ if (delta == 0) {
+ hue = 0.0;
+ sat = 0.0;
+ } else if (val == d.r) {
+ hue = 42.5*(d.g-d.b)/delta;
+ sat = 255.0*delta/val;
+ } else if (val == d.g) {
+ hue = 42.5*(d.b-d.r)/delta + 85.0;
+ sat = 255.0*delta/val;
+ } else {
+ hue = 42.5*(d.r-d.g)/delta + 170.0;
+ sat = 255.0*delta/val;
+ }
+ if (hue < 0.0) hue += 255.0;
+ if (hue > 255.0) hue -= 255.0;
+ c.r = (unsigned char)max(0.0f, min(255.0f, hue));
+ c.g = (unsigned char)max(0.0f, min(255.0f, sat));
+ c.b = (unsigned char)max((unsigned char)0, min((unsigned char)255, val));
+ return c;
+ }
+
+};
+
+// Structure for passing byte data to and from Python.
+struct ByteBuffer
+{
+ int size;
+ void* data;
+};
+
+#endif
+
diff --git a/colorsc/colorsc.i b/colorsc/colorsc.i
new file mode 100644
index 0000000..a1e3471
--- /dev/null
+++ b/colorsc/colorsc.i
@@ -0,0 +1,59 @@
+/*
+ Copyright 2008 by Jens Andersson and Wade Brainerd.
+ This file is part of Colors! XO.
+
+ Colors is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Colors is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Colors. If not, see <http://www.gnu.org/licenses/>.
+*/
+%module colorsc
+
+%{
+#include "canvas.h"
+#include "palette.h"
+%}
+
+// Testing facility.
+%typemap(in) void* {
+ $1 = $input;
+}
+
+// Pass a gst.Buffer as a GstBuffer*.
+%typemap(in) GstBuffer* {
+ // todo- Error checking would be nice.
+ PyGObject* pygo = (PyGObject*)$input;
+ GstBuffer* buf = (GstBuffer*)pygo->obj;
+ $1 = buf;
+}
+
+// Pass a gtk.gdk.Image as a GdkImage*.
+%typemap(in) GdkImage* {
+ // todo- Error checking would be nice.
+ PyGObject* pygo = (PyGObject*)$input;
+ GdkImage* img = (GdkImage*)pygo->obj;
+ $1 = img;
+}
+
+// Return SurfaceA8R8G8B8 as Python string.
+%typemap(out) SurfaceA8R8G8B8 {
+ $result = PyString_FromStringAndSize((const char*)$1.pixels, $1.stride*$1.height);
+}
+
+// Return ByteBuffer as Python string.
+%typemap(out) ByteBuffer {
+ $result = PyString_FromStringAndSize((const char*)$1.data, $1.size);
+}
+
+%include "colorsc.h"
+%include "canvas.h"
+%include "palette.h"
+
diff --git a/colorsc/deploy.sh b/colorsc/deploy.sh
new file mode 100755
index 0000000..0d548de
--- /dev/null
+++ b/colorsc/deploy.sh
@@ -0,0 +1,2 @@
+scp ~/Colors.activity/colors.py ~/Colors.activity/_colorsc.so ~/Colors.activity/colorsc.py olpc@$XOIP:~/Colors.activity/
+
diff --git a/colorsc/drwfile.h b/colorsc/drwfile.h
new file mode 100644
index 0000000..f581f9b
--- /dev/null
+++ b/colorsc/drwfile.h
@@ -0,0 +1,77 @@
+/*
+ Copyright 2008 by Jens Andersson and Wade Brainerd.
+ This file is part of Colors! XO.
+
+ Colors is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Colors is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Colors. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef _DRWFILE_H_
+#define _DRWFILE_H_
+
+#define DRW_VERSION 1070
+
+struct DRW_Command
+{
+ union
+ {
+ struct
+ {
+ unsigned int type0 : 2;
+ unsigned int alpha : 8;
+ unsigned int x : 11;
+ unsigned int y : 11;
+ };
+
+ struct
+ {
+ unsigned int type1 : 2;
+ unsigned int col : 24;
+ unsigned int flipx : 1;
+ unsigned int flipy : 1;
+ };
+
+ struct
+ {
+ unsigned int type2 : 2;
+ unsigned int size : 16;
+ unsigned int brushcontrol : 2;
+ unsigned int brushtype : 2;
+ unsigned int opacity : 8;
+ };
+
+ struct
+ {
+ unsigned int type : 2;
+ unsigned int undef : 30;
+ };
+
+ int raw;
+ };
+};
+
+struct DRW_Header
+{
+ static const unsigned int ID = 0x436f6c21; // 'Col!'
+
+ unsigned int id;
+ unsigned int version;
+ int colorsversion_initial;
+ int colorsversion_saved;
+ int strokes;
+ int time;
+ int timessaved;
+ int dummy[8];
+ int ncommands;
+};
+
+#endif
diff --git a/colorsc/gtk_types.h b/colorsc/gtk_types.h
new file mode 100644
index 0000000..f3e26bd
--- /dev/null
+++ b/colorsc/gtk_types.h
@@ -0,0 +1,130 @@
+// This file serves to allow access to a limited subset of GTK and GStreamer objects passed from PyGTK, without the full
+// GLib + GTK + GST development environment.
+//
+// Obviously, it is limited to working with a specific version of GTK, PyGTK and GStreamer, but these structures appear
+// to be fairly stable.
+#ifndef GTK_TYPES_H
+#define GTK_TYPES_H
+
+#include <stdint.h>
+
+typedef uint8_t guint8;
+typedef int32_t gint;
+typedef uint32_t guint;
+typedef uint16_t guint16;
+typedef uint64_t guint64;
+typedef void* gpointer;
+
+struct GTypeClass;
+struct GData;
+
+struct _GTypeInstance
+{
+ /*< private >*/
+ GTypeClass *g_class;
+};
+
+typedef struct _GTypeInstance GTypeInstance;
+
+struct _GObject
+{
+ GTypeInstance g_type_instance;
+
+ /*< private >*/
+ guint ref_count;
+ GData *qdata;
+};
+
+typedef struct _GObject GObject;
+
+typedef enum
+{
+ GDK_IMAGE_NORMAL,
+ GDK_IMAGE_SHARED,
+ GDK_IMAGE_FASTEST
+} GdkImageType;
+
+typedef enum
+{
+ GDK_LSB_FIRST,
+ GDK_MSB_FIRST
+} GdkByteOrder;
+
+struct GdkVisual;
+struct GdkColormap;
+
+typedef struct {
+ GObject parent_instance;
+
+
+ GdkImageType type; /* read only. */
+ GdkVisual *visual; /* read only. visual used to create the image */
+ GdkByteOrder byte_order; /* read only. */
+ gint width; /* read only. */
+ gint height; /* read only. */
+ guint16 depth; /* read only. */
+ guint16 bpp; /* read only. bytes per pixel */
+ guint16 bpl; /* read only. bytes per line */
+ guint16 bits_per_pixel; /* read only. bits per pixel */
+ gpointer mem;
+
+ GdkColormap *colormap; /* read only. */
+} GdkImage;
+
+struct GSList;
+
+typedef struct {
+ PyObject_HEAD
+ GObject *obj;
+ PyObject *inst_dict; /* the instance dictionary -- must be last */
+ PyObject *weakreflist; /* list of weak references */
+ GSList *closures;
+} PyGObject;
+
+struct _GstMiniObject {
+ GTypeInstance instance;
+ /*< public >*/ /* with COW */
+ gint refcount;
+ guint flags;
+
+ /*< private >*/
+ gpointer _gst_reserved;
+};
+
+typedef struct _GstMiniObject GstMiniObject;
+
+struct GstCaps;
+
+typedef guint64 GstClockTime;
+
+#define GST_PADDING 4
+
+struct _GstBuffer {
+ GstMiniObject mini_object;
+
+ /*< public >*/ /* with COW */
+ /* pointer to data and its size */
+ guint8 *data;
+ guint size;
+
+ /* timestamp */
+ GstClockTime timestamp;
+ GstClockTime duration;
+
+ /* the media type of this buffer */
+ GstCaps *caps;
+
+ /* media specific offset */
+ guint64 offset;
+ guint64 offset_end;
+
+ guint8 *malloc_data;
+
+ /*< private >*/
+ gpointer _gst_reserved[GST_PADDING];
+};
+
+typedef struct _GstBuffer GstBuffer;
+
+#endif
+
diff --git a/colorsc/palette.cpp b/colorsc/palette.cpp
new file mode 100644
index 0000000..0751983
--- /dev/null
+++ b/colorsc/palette.cpp
@@ -0,0 +1,19 @@
+/*
+ Copyright 2008 by Jens Andersson and Wade Brainerd.
+ This file is part of Colors! XO.
+
+ Colors is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Colors is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Colors. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "palette.h"
+
diff --git a/colorsc/palette.h b/colorsc/palette.h
new file mode 100644
index 0000000..03d51eb
--- /dev/null
+++ b/colorsc/palette.h
@@ -0,0 +1,458 @@
+/*
+ Copyright 2008 by Jens Andersson and Wade Brainerd.
+ This file is part of Colors! XO.
+
+ Colors is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Colors is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Colors. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef _PALETTE_H_
+#define _PALETTE_H_
+
+#include "colorsc.h"
+#include "canvas.h"
+
+// The Palette is the color wheel and triangle that are part of the brush controls dialog.
+// This class manages rendering the palette as efficiently as possible into GdkImage objects which are
+// then displayed on the screen.
+// It also manages responding to mouse inputs that are passed from the Python code.
+class Palette
+{
+public:
+ static const int WHEEL_WIDTH = 75;
+
+ int size;
+
+ float palette_h, palette_s, palette_v;
+
+ Pos triangle_cursor;
+ bool triangle_capture;
+ bool wheel_capture;
+
+ Palette(int size) : size(size)
+ {
+ palette_h = palette_s = palette_v = 0;
+ triangle_capture = false;
+ }
+
+ float get_wheel_radius()
+ {
+ return size - WHEEL_WIDTH/2;
+ }
+
+ void rgb_to_hsv(float r, float g, float b, float* h, float* s, float* v)
+ {
+ float mn = min(min(r, g), b);
+ float mx = max(max(r, g), b);
+ *v = mx; // v
+ float delta = mx - mn;
+ if (mx != 0)
+ *s = delta / mx; // s
+ else
+ {
+ // r = g = b = 0 // s = 0, v is undefined
+ *s = 0;
+ *h = -1;
+ return;
+ }
+ if (delta == 0)
+ *h = 0;
+ else if (r == mx)
+ *h = ( g - b ) / delta; // between yellow & magenta
+ else if (g == mx)
+ *h = 2 + ( b - r ) / delta; // between cyan & yellow
+ else
+ *h = 4 + ( r - g ) / delta; // between magenta & cyan
+ *h *= 60; // degrees
+ if (*h < 0)
+ *h += 360;
+ }
+
+ void hsv_to_rgb(float* r, float* g, float* b, float h, float s, float v)
+ {
+ if (s == 0)
+ {
+ // achromatic (grey)
+ *r = *g = *b = v;
+ return;
+ }
+ h /= 60; // sector 0 to 5
+ int i = int(floorf(h));
+ float f = h - i; // factorial part of h
+ float p = v * (1-s);
+ float q = v * (1-s * f);
+ float t = v * (1-s * (1 - f));
+ switch (i)
+ {
+ case 0: *r = v; *g = t; *b = p; break;
+ case 1: *r = q; *g = v; *b = p; break;
+ case 2: *r = p; *g = v; *b = t; break;
+ case 3: *r = p; *g = q; *b = v; break;
+ case 4: *r = t; *g = p; *b = v; break;
+ default:
+ case 5: *r = v; *g = p; *b = q; break;
+ }
+ }
+
+ void set_color(const Color& c)
+ {
+ rgb_to_hsv(c.r/255.0f, c.g/255.0f, c.b/255.0f, &palette_h, &palette_s, &palette_v);
+
+ // I couldn't work out an exact solution to the blobby triangle position, so I'm using a relaxation algorithm.
+ // It usually takes less than 10 iterations to converge and only runs when you first open the palette.
+ Pos p0, p1, p2;
+ get_triangle_points(&p0, &p1, &p2);
+ float side = distance(p0, p1);
+
+ Pos p = (p0+p1+p2)*(1.0f/3.0f);
+ float epsilon = 0.001f;
+ int max_iterations = 50;
+ for (int i = 0; i < max_iterations; i++)
+ {
+ float d0 = distance(p, p0);
+ float d0ok = fabsf((side-d0)-palette_s*side);
+ p = p + ((p0-p)/length(p0-p)) * (d0-(1.0f-palette_s)*side);
+
+ float d2 = distance(p, p2);
+ float d2ok = fabsf(d2-palette_v*side);
+ p = p + ((p2-p)/length(p2-p)) * (d2-palette_v*side);
+
+ if (d0ok <= epsilon && d2ok <= epsilon) break;
+ }
+ triangle_cursor = p;
+ }
+
+ Color get_color()
+ {
+ float r, g, b;
+ hsv_to_rgb(&r, &g, &b, palette_h, palette_s, palette_v);
+ return Color::create_from_float(r, g, b, 1.0f);
+ }
+
+ float sqr(float a)
+ {
+ return a*a;
+ }
+
+ float dot(const Pos& a, const Pos& b)
+ {
+ return a.x*b.x + a.y*b.y;
+ }
+
+ float length(const Pos& a)
+ {
+ return sqrtf(dot(a,a));
+ }
+
+ float length_sqr(const Pos& a)
+ {
+ return dot(a,a);
+ }
+
+ float distance(const Pos& a, const Pos& b)
+ {
+ return length(a-b);
+ }
+
+ float distance_sqr(const Pos& a, const Pos& b)
+ {
+ return length_sqr(a-b);
+ }
+
+ Pos normalize(const Pos& a)
+ {
+ return a/length(a);
+ }
+
+ void get_triangle_points(Pos* p0, Pos* p1, Pos* p2)
+ {
+ Pos center(size/2, size/2);
+ *p0 = center + Pos::create_from_angle(palette_h+0.0f, size/2-WHEEL_WIDTH);
+ *p1 = center + Pos::create_from_angle(palette_h+120.0f, size/2-WHEEL_WIDTH);
+ *p2 = center + Pos::create_from_angle(palette_h+240.0f, size/2-WHEEL_WIDTH);
+ }
+
+ // The wheel never changes, so it can be rendered once here. This also clears the image background.
+ void render_wheel(GdkImage* image)
+ {
+ if (image->width != size || image->height != size || image->bits_per_pixel != 16)
+ {
+ fprintf(stderr, "Error: Invalid Palette GdkImage.\n");
+ return;
+ }
+
+ Color bkg(64, 64, 64, 0);
+
+ float wheel_radius = size/2;
+ float ring_min_sqr = sqr(wheel_radius-WHEEL_WIDTH);
+ float ring_max_sqr = sqr(wheel_radius);
+
+ unsigned short* pixels = (unsigned short*)image->mem;
+ int stride = image->bpl/sizeof(unsigned short);
+
+ for (int y = 0; y < size; y++)
+ {
+ unsigned short* row = &pixels[y*stride];
+ for (int x = 0; x < size; x++)
+ {
+ // Get radius from center of palette.
+ float dx = x-wheel_radius;
+ float dy = y-wheel_radius;
+ float dist_sqr = dx*dx + dy*dy;
+
+ // Inside color wheel ring? Draw the wheel.
+ if (dist_sqr >= ring_min_sqr && dist_sqr < ring_max_sqr)
+ {
+ // Calculate HSV from wheel angle.
+ float h = atan2f(dy, dx)*180.0f/PI;
+ while (h<0) h += 360.0f;
+ while (h>360.0f) h -= 360.0f;
+ float r, g, b;
+ hsv_to_rgb(&r, &g, &b, h, 1.0f, 1.0f);
+ *row++ = Color::create_from_float(r, g, b, 1).get_r5g6b5();
+ }
+ else
+ *row++ = bkg.get_r5g6b5();
+ }
+ }
+ }
+
+ // Clears out the inner circle and redraws the color triangle, as fast as possible.
+ // Scales up implicitly by 2x to improve performance.
+ // The appearance was an accident which creates a kind of blobby triangle, like the intersection of three circles.
+ // But I like the look so I'm keeping it!
+ void render_triangle(GdkImage* image)
+ {
+ if (image->width != size || image->height != size || image->bits_per_pixel != 16 || (size&1))
+ {
+ fprintf(stderr, "Error: Invalid Palette GdkImage.\n");
+ return;
+ }
+
+ Color bkg(64, 64, 64, 0);
+ unsigned short bkgc = bkg.get_r5g6b5();
+
+ Pos p0, p1, p2;
+ get_triangle_points(&p0, &p1, &p2);
+ float triangle_side = distance(p0, p1);
+ float triangle_side_sqr = triangle_side*triangle_side;
+ float inv_triangle_side = 1.0f/triangle_side;
+
+ float wheel_radius = size/2;
+ float ring_min_sqr = sqr(wheel_radius-WHEEL_WIDTH);
+
+ int x0 = WHEEL_WIDTH;
+ int x1 = size-WHEEL_WIDTH;
+
+ unsigned short* pixels = (unsigned short*)image->mem;
+ int stride = image->bpl/sizeof(unsigned short);
+
+ Pos p(x0,x0);
+ for (int y = x0; y < x1; y+=2, p.y += 2.0f)
+ {
+ p.x = x0;
+ unsigned short* __restrict row0 = &pixels[(y+0)*stride+x0];
+ unsigned short* __restrict row1 = &pixels[(y+1)*stride+x0];
+ for (int x = x0; x < x1; x+=2, p.x += 2.0f)
+ {
+ // Calculate position inside triangle. If inside, then use as HSV.
+ float d0_sqr = distance_sqr(p, p0);
+ float d1_sqr = distance_sqr(p, p1);
+ float d2_sqr = distance_sqr(p, p2);
+ if (d0_sqr <= triangle_side_sqr && d1_sqr <= triangle_side_sqr && d2_sqr <= triangle_side_sqr)
+ {
+ float r, g, b;
+ hsv_to_rgb(&r, &g, &b, palette_h, 1.0f-sqrtf(d0_sqr)*inv_triangle_side, sqrtf(d2_sqr)*inv_triangle_side);
+ unsigned short c = Color::create_from_float(r, g, b, 1).get_r5g6b5();
+ row0[0] = c;
+ row0[1] = c;
+ row1[0] = c;
+ row1[1] = c;
+ }
+ else
+ {
+ // Inside ring? Clear to background.
+ if (distance_sqr(p, Pos(wheel_radius, wheel_radius)) < ring_min_sqr)
+ {
+ row0[0] = bkgc;
+ row0[1] = bkgc;
+ row1[0] = bkgc;
+ row1[1] = bkgc;
+ }
+ }
+ row0 += 2;
+ row1 += 2;
+ }
+ }
+ }
+
+ Pos get_wheel_pos()
+ {
+ float a = palette_h*PI/180.0f;
+ float r = size/2 - WHEEL_WIDTH/2;
+ return Pos(size/2+r*cosf(a), size/2+r*sinf(a));
+ }
+
+ Pos get_triangle_pos()
+ {
+ return triangle_cursor;
+ }
+
+ void process_mouse(int mx, int my)
+ {
+ Pos p0, p1, p2;
+ get_triangle_points(&p0, &p1, &p2);
+
+ // If the triangle has the mouse capture, clip the mouse position to within the blobby triangle.
+ if (!wheel_capture)
+ {
+ if (triangle_capture)
+ {
+ Pos p(mx, my);
+ float d0 = distance(p, p0);
+ float d1 = distance(p, p1);
+ float d2 = distance(p, p2);
+ float side = distance(p0, p1)-1;
+ if (d0 > side || d1 > side || d2 > side)
+ {
+ Pos far_point = p0;
+ float far_dist = d0;
+ if (d1 > far_dist) { far_point = p1; far_dist = d1; }
+ if (d2 > far_dist) { far_point = p2; far_dist = d2; }
+ p = far_point + normalize(p-far_point) * side;
+ mx = int(p.x);
+ my = int(p.y);
+ }
+ }
+
+ // Now check to see if it's on the triangle.
+ float side = 1.0f/distance(p0, p1);
+ Pos p(mx, my);
+ float d0 = distance(p, p0)*side;
+ float d1 = distance(p, p1)*side;
+ float d2 = distance(p, p2)*side;
+ if (d0 <= 1.0f && d1 <= 1.0f && d2 <= 1.0f)
+ {
+ triangle_capture = true;
+ triangle_cursor = p;
+ palette_s = 1.0f-d0;
+ palette_v = d2;
+ }
+ }
+
+ if (!triangle_capture)
+ {
+ // Check to see if the mouse is in the wheel ring.
+ int dx = mx-size/2;
+ int dy = my-size/2;
+ float dist = sqrtf(dx*dx + dy*dy);
+ if (dist >= size/2-WHEEL_WIDTH || wheel_capture)
+ {
+ wheel_capture = true;
+ float h = atan2f(float(dy), float(dx))*180.0f/PI;
+ while (h<0) h += 360.0f;
+ while (h>360.0f) h -= 360.0f;
+ // Rotate the triangle cursor with the wheel.
+ triangle_cursor = Pos::create_from_rotation(triangle_cursor, (p0+p1+p2)/3.0f, (h-palette_h)*PI/180.0f);
+ palette_h = h;
+ }
+ }
+ }
+
+ void process_mouse_release()
+ {
+ triangle_capture = false;
+ wheel_capture = false;
+ }
+};
+
+// The BrushPreview is a simple preview of the current brush as displayed in the Brush Controls panel.
+// This class handles rendering the brush preview to a GdkImage to be displayed on the screen.
+class BrushPreview
+{
+public:
+ int size;
+
+ Brush brush;
+
+ BrushPreview(int size) : size(size)
+ {
+ }
+
+ void render(GdkImage* image)
+ {
+ if (image->width != size || image->height != size || image->bits_per_pixel != 16 || (size&1))
+ {
+ fprintf(stderr, "Error: Invalid BrushPreview GdkImage.\n");
+ return;
+ }
+
+ // Minimum brush size.
+ if (brush.size<2) brush.size = 2;
+
+ // Calculate interpolation constants
+ float db = (BrushType::DIST_TABLE_WIDTH-1) / float(brush.size);
+
+ float xb = BrushType::DIST_TABLE_CENTER - (size/2)*db;
+ float yb = BrushType::DIST_TABLE_CENTER - (size/2)*db;
+
+ // Select which line of the brush-lookup-table to use that most closely matches the current brush width
+ int brushidx = int(float(BrushType::BRUSH_TABLE_HEIGHT) / brush.size);
+
+ int opacity = int(round(255.0f * brush.opacity));
+
+ unsigned short* pixels = (unsigned short*)image->mem;
+ int stride = image->bpl/sizeof(unsigned short);
+
+ // Interpolate the distance table over the area. For each pixel find the distance, and look the
+ // brush-intensity up in the brush-table
+ for (int y = 0; y < size; y+=2)
+ {
+ float x2b = xb;
+ unsigned short* __restrict row0 = &pixels[(y+0)*stride];
+ unsigned short* __restrict row1 = &pixels[(y+1)*stride];
+ for (int x = 0; x < size; x+=2)
+ {
+ // Find brush-intensity and mulitply that with incoming opacity
+ if (x2b >= 0 && x2b < BrushType::DIST_TABLE_WIDTH && yb >= 0 && yb < BrushType::DIST_TABLE_WIDTH)
+ {
+ int lookup = BrushType::distance_tbl[int(x2b)][int(yb)];
+ int intensity = fixed_scale(Brush::brush_type[brush.type].intensity_tbl[lookup][brushidx], opacity);
+ Color i = Color::create_from_a8r8g8b8(0xffffffff);
+ //Color i = Color::create_from_a8r8g8b8(image[y*size+x]);
+ i = Color::create_from_lerp(brush.color, i, intensity);
+ unsigned short c = i.get_r5g6b5();
+ row0[0] = c;
+ row0[1] = c;
+ row1[0] = c;
+ row1[1] = c;
+ }
+ else
+ {
+ row0[0] = 0xffff;
+ row0[1] = 0xffff;
+ row1[0] = 0xffff;
+ row1[1] = 0xffff;
+ }
+
+ row0 += 2;
+ row1 += 2;
+
+ x2b += db*2;
+ }
+ yb += db*2;
+ }
+ }
+};
+
+#endif
+
diff --git a/colorsc/setup.py b/colorsc/setup.py
new file mode 100644
index 0000000..1295652
--- /dev/null
+++ b/colorsc/setup.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+
+"""
+setup.py file for colorsc
+"""
+
+from distutils.core import setup, Extension
+
+colorsc_module = Extension('_colorsc', sources=['canvas.cpp', 'palette.cpp', 'colorsc_wrap.cxx'] )
+
+setup (name = 'colorsc', version = '0.1',
+ author = "Jens Andersson",
+ description = """Colors C extension library.""",
+ ext_modules = [colorsc_module], py_modules = ["colorsc"])
+