diff options
Diffstat (limited to 'colorsc/canvas.h')
-rw-r--r-- | colorsc/canvas.h | 1848 |
1 files changed, 1848 insertions, 0 deletions
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 + |