From 9e64dbd7cd3093353f87e5a69006d8c449a58313 Mon Sep 17 00:00:00 2001 From: olpc Date: Sat, 17 Jan 2009 18:41:32 +0000 Subject: adding trimmed pygame from SVN --- (limited to 'pygame/palette.h') diff --git a/pygame/palette.h b/pygame/palette.h new file mode 100755 index 0000000..03d51eb --- /dev/null +++ b/pygame/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 . +*/ +#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 + -- cgit v0.9.1