diff --git a/docs/AUTHORS.txt b/docs/AUTHORS.txt index 00d3e4f02..332f8444d 100644 --- a/docs/AUTHORS.txt +++ b/docs/AUTHORS.txt @@ -7,7 +7,7 @@ bill@newbreedsoftware.com http://www.tuxpaint.org/ -June 17, 2002 - June 2, 2009 +June 17, 2002 - June 5, 2009 $Id$ @@ -69,6 +69,20 @@ $Id$ Math for arc used by Real Rainbow provided by Jeff Newmiller + String Art Magic Tools + by Pere Pujal i Carabantes + + Tornado Magic Tool + (based on Flowers) + by Pere Pujal i Carabantes + + Tornado sound effect from + "Tornado" film, from Prelinger Archives, + produced by Calvin Productions, sponsored by U.S. Weather Bureau. + Extracted and edited using 'mplayer' and 'Audacity'. + Public Domain. Archived at the Internet Archive: + http://www.archive.org/details/tornado + * Graphics diff --git a/docs/CHANGES.txt b/docs/CHANGES.txt index c2538d3c4..a2fd6de4e 100644 --- a/docs/CHANGES.txt +++ b/docs/CHANGES.txt @@ -8,7 +8,7 @@ http://www.tuxpaint.org/ $Id$ -2009.June.2 (0.9.21) +2009.June.5 (0.9.21) * New Starters: ------------- * Silver Frame @@ -58,9 +58,10 @@ $Id$ Creative Commons Attribution 2.0 Generic http://creativecommons.org/licenses/by/2.0/deed.en - * String Edges - Draw string-like patters around the screen. + * String Edges - Draw string-like patters around the picture. String Corner - Draw aligned string-like patterns. String 'V' - Draw free-form string-like patterns. + Tornado (based on Flowers) - Draws a tornado effect onto the picture. By Pere Pujal i Carabantes * Icons for some new Magic Tools diff --git a/magic/icons/tornado.png b/magic/icons/tornado.png new file mode 100644 index 000000000..2b20d1276 Binary files /dev/null and b/magic/icons/tornado.png differ diff --git a/magic/icons/tornado_base.png b/magic/icons/tornado_base.png new file mode 100644 index 000000000..e58473bb8 Binary files /dev/null and b/magic/icons/tornado_base.png differ diff --git a/magic/icons/tornado_cloud.png b/magic/icons/tornado_cloud.png new file mode 100644 index 000000000..b4f33a210 Binary files /dev/null and b/magic/icons/tornado_cloud.png differ diff --git a/magic/sounds/tornado_release.ogg b/magic/sounds/tornado_release.ogg new file mode 100644 index 000000000..ef93e1ff2 Binary files /dev/null and b/magic/sounds/tornado_release.ogg differ diff --git a/magic/src/tornado.c b/magic/src/tornado.c new file mode 100644 index 000000000..987c6c635 --- /dev/null +++ b/magic/src/tornado.c @@ -0,0 +1,543 @@ +/* + tornado.c + + Tornado Magic Tool Plugin + Tux Paint - A simple drawing program for children. + + Copyright (c) 2002-2008 by Bill Kendrick and others; see AUTHORS.txt + bill@newbreedsoftware.com + http://www.tuxpaint.org/ + + Some modifications to convert the flower plugin in to a tornado + plugin by Pere Pujal i Carabantes + pere@fornol.no-ip.org + + This program 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 2 of the License, or + (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + (See COPYING.txt) + + Last updated: May 29, 2009 + $Id$ +*/ + +#include +#include +#include +#include "tp_magic_api.h" +#include "SDL_image.h" +#include "SDL_mixer.h" + +/* Our globals: */ + +enum { SIDE_LEFT, SIDE_RIGHT }; +enum { LEAFSIDE_RIGHT_DOWN, + LEAFSIDE_LEFT_DOWN, + LEAFSIDE_RIGHT_UP, + LEAFSIDE_LEFT_UP }; + +static Mix_Chunk /* * tornado_click_snd, */ * tornado_release_snd; +static Uint8 tornado_r, tornado_g, tornado_b; +static int tornado_min_x, tornado_max_x, tornado_bottom_x, tornado_bottom_y; +static int tornado_side_first; +static int tornado_side_decided; +static SDL_Surface * tornado_base, * tornado_cloud, + * tornado_cloud_colorized; + static int top_w; + +/* Local function prototypes: */ + +typedef struct +{ + float x, y; +} Point2D; + +static void tornado_predrag(magic_api * api, SDL_Surface * canvas, + SDL_Surface * last, int ox, int oy, int x, int y); +static void tornado_drawbase(magic_api * api, SDL_Surface * canvas); +static void tornado_drawstalk(magic_api * api, SDL_Surface * canvas, SDL_Surface * last, + int top_x, int top_y, int minx, int maxx, + int bottom_x, int bottom_y, int final); +static void tornado_drawtornado(magic_api * api, SDL_Surface * canvas, int x, int y); +static Point2D tornado_PointOnCubicBezier(Point2D* cp, float t); +static void tornado_ComputeBezier(Point2D* cp, int numberOfPoints, Point2D* curve); +static void tornado_colorize_cloud(magic_api * api); +static Uint32 tornado_mess(Uint32 pixel, SDL_Surface * canvas); + + +Uint32 tornado_api_version(void) { return(TP_MAGIC_API_VERSION); } + + +// No setup required: +int tornado_init(magic_api * api) +{ + char fname[1024]; + +/* + snprintf(fname, sizeof(fname), "%s/sounds/magic/tornado_click.ogg", + api->data_directory); + tornado_click_snd = Mix_LoadWAV(fname); +*/ + + snprintf(fname, sizeof(fname), "%s/sounds/magic/tornado_release.ogg", + api->data_directory); + tornado_release_snd = Mix_LoadWAV(fname); + + snprintf(fname, sizeof(fname), "%s/images/magic/tornado_base.png", + api->data_directory); + tornado_base = IMG_Load(fname); + + snprintf(fname, sizeof(fname), "%s/images/magic/tornado_cloud.png", + api->data_directory); + tornado_cloud = IMG_Load(fname); + + return(1); +} + +// We have multiple tools: +int tornado_get_tool_count(magic_api * api) +{ + return(1); +} + +// Load our icons: +SDL_Surface * tornado_get_icon(magic_api * api, int which) +{ + char fname[1024]; + + snprintf(fname, sizeof(fname), "%s/images/magic/tornado.png", + api->data_directory); + + return(IMG_Load(fname)); +} + +// Return our names, localized: +char * tornado_get_name(magic_api * api, int which) +{ + return(strdup(gettext_noop("Tornado"))); +} + +// Return our descriptions, localized: +char * tornado_get_description(magic_api * api, int which, int mode) +{ + return(strdup(gettext_noop("Click and drag to draw a tornado stalk. Let go to finish the tornado."))); +} + +// Affect the canvas on drag: +static void tornado_predrag(magic_api * api, SDL_Surface * canvas, + SDL_Surface * last, int ox, int oy, int x, int y) +{ + if (x < tornado_min_x) + tornado_min_x = x; + if (ox < tornado_min_x) + tornado_min_x = ox; + if (x > tornado_max_x) + tornado_max_x = x; + if (ox > tornado_max_x) + tornado_max_x = ox; + + if (y > tornado_bottom_y) + y = tornado_bottom_y; + if (oy > tornado_bottom_y) + y = tornado_bottom_y; + + // Determine which way to bend first: + // + if (tornado_side_decided == 0) + { + if (x < tornado_bottom_x - 10) + { + tornado_side_first = SIDE_LEFT; + tornado_side_decided = 1; + } + else if (x > tornado_bottom_x + 10) + { + tornado_side_first = SIDE_RIGHT; + tornado_side_decided = 1; + } + } +} + +void tornado_drag(magic_api * api, int which, SDL_Surface * canvas, + SDL_Surface * last, int ox, int oy, int x, int y, + SDL_Rect * update_rect) +{ + tornado_predrag(api, canvas, last, ox, oy, x, y); + + + /* Erase any old stuff; this is a live-edited effect: */ + + SDL_BlitSurface(last, NULL, canvas, NULL); + + + /* Draw the base and the stalk (low-quality) for now: */ + + tornado_drawstalk(api, canvas, last, + x, y, tornado_min_x, tornado_max_x, + tornado_bottom_x, tornado_bottom_y, !(api->button_down())); + + tornado_drawbase(api, canvas); + + update_rect->x = 0; + update_rect->y = 0; + update_rect->w = canvas->w; + update_rect->h = canvas->h; +} + +// Affect the canvas on click: +void tornado_click(magic_api * api, int which, int mode, + SDL_Surface * canvas, SDL_Surface * last, + int x, int y, SDL_Rect * update_rect) +{ + tornado_min_x = x; + tornado_max_x = x; + tornado_bottom_x = x; + tornado_bottom_y = y;// - tornado_base->h; + + tornado_side_decided = 0; + tornado_side_first = SIDE_LEFT; + + tornado_drag(api, which, canvas, last, x, y, x, y, update_rect); + +/* + api->playsound(tornado_click_snd, (x * 255) / canvas->w, 255); +*/ +} + +// Affect the canvas on release: +void tornado_release(magic_api * api, int which, + SDL_Surface * canvas, SDL_Surface * last, + int x, int y, SDL_Rect * update_rect) +{ + /* Don't let tornado be too low compared to base: */ + + if (y >= tornado_bottom_y - 128) + y = tornado_bottom_y - 128; + + + /* Do final calcs and draw base: */ + + tornado_predrag(api, canvas, last, x, y, x, y); + + + /* Erase any old stuff: */ + + SDL_BlitSurface(last, NULL, canvas, NULL); + + + /* Draw high-quality stalk, and tornado: */ + + tornado_drawstalk(api, canvas, last, + x, y, tornado_min_x, tornado_max_x, + tornado_bottom_x, tornado_bottom_y, 1); + + tornado_drawtornado(api, canvas, x, y); + + tornado_drawbase(api, canvas); + + + update_rect->x = 0; + update_rect->y = 0; + update_rect->w = canvas->w; + update_rect->h = canvas->h; + + api->playsound(tornado_release_snd, (x * 255) / canvas->w, 255); +} + + +static void tornado_drawtornado(magic_api * api, SDL_Surface * canvas, int x, int y) +{ + SDL_Surface * aux_surf; + SDL_Rect dest; + + aux_surf = api->scale(tornado_cloud_colorized, top_w *2, top_w,0); + dest.x = x - (aux_surf->w / 2); + dest.y = y - (aux_surf->h / 2); + + SDL_BlitSurface(aux_surf, NULL, canvas, &dest); + SDL_FreeSurface(aux_surf); +} + +static void tornado_drawbase(magic_api * api, SDL_Surface * canvas) +{ + SDL_Rect dest; + + dest.x = tornado_bottom_x - (tornado_base->w / 2); + dest.y = tornado_bottom_y - tornado_base->h / 2; + + SDL_BlitSurface(tornado_base, NULL, canvas, &dest); +} + +static Uint32 tornado_mess(Uint32 pixel, SDL_Surface * canvas) +{ + Uint8 r, g, b, a; + float f = (float)rand()*255/RAND_MAX; + SDL_GetRGBA(pixel, canvas->format, &r, &g, &b, &a); + return (SDL_MapRGBA(canvas->format, + (tornado_r + r + (Uint8)f * 2) / 4, + (tornado_g + g + (Uint8)f * 2) / 4, + (tornado_b + b + (Uint8)f * 2) / 4, + a)); +} + +static void tornado_drawstalk(magic_api * api, SDL_Surface * canvas, SDL_Surface * last, + int top_x, int top_y, int minx, int maxx, + int bottom_x, int bottom_y, int final) +{ + Point2D control_points[4]; + Point2D * curve; + int i, n_points; + int left, right; + SDL_Rect dest, src; + int xx, yy, side; + + + /* Compute a nice bezier curve for the stalk, based on the + base (x,y), leftmost (x), rightmost (x), and top (x,y) */ + + control_points[0].x = top_x; + control_points[0].y = top_y; + + if (tornado_side_first == SIDE_LEFT) + { + control_points[1].x = minx; + control_points[2].x = maxx; + } + else + { + control_points[1].x = maxx; + control_points[2].x = minx; + } + + control_points[1].y = ((bottom_y - top_y) / 3) + top_y; + control_points[2].y = (((bottom_y - top_y) / 3) * 2) + top_y; + + control_points[3].x = bottom_x; + control_points[3].y = bottom_y; + + if (final == 0) + n_points = 8; + else + n_points = max(bottom_y - top_y, maxx - minx); + + curve = (Point2D *) malloc(sizeof(Point2D) * n_points); + + tornado_ComputeBezier(control_points, n_points, curve); + top_w = max(32, n_points * n_points / 1000); + int rotation = 0; + int p; + /* Draw the curve: */ + + for (i = 0; i < n_points - 1; i++) + { + if (final == 0) + { + dest.x = curve[i].x; + dest.y = curve[i].y; + dest.w = 2; + dest.h = 2; + SDL_FillRect(canvas, &dest, SDL_MapRGB(canvas->format, 0, 0, 0)); + } + else + { + int ii; + ii = n_points - i; + /* min 10 pixels then ii^2 / 2000 after some trys */ + left = min(curve[i].x, curve[i + 1].x)-5-ii*ii/2000; + right = max(curve[i].x, curve[i + 1].x)+5+ii*ii/2000; + + dest.x = left; + dest.y = curve[i].y; + dest.w = right - left + 1; + dest.h = 2; + } + + rotation +=3; + /* The body of the tornado: 3x 1y rotation + some random particles */ + for (p = dest.x; p < dest.x + dest.w; p++) + { + if ((float)rand() * 100 / RAND_MAX > 10 ) + { + api->putpixel(canvas, p, dest.y, api->getpixel(last, dest.x + (p - dest.x + rotation) % dest.w , dest.y)); + } + else + { + api->putpixel(canvas, p, dest.y, tornado_mess(api->getpixel(last, dest.x + (p - dest.x + rotation) % dest.w , dest.y), canvas)); + } + } + + /* Some random particles flying around the tornado */ + for (p = dest.x - dest.w * 20 / 100; p < dest.x + dest.w + dest.w * 20 / 100; p++) + { + if ((float)rand() * 100 / RAND_MAX < 5 && ((p < dest.x) || (p > dest.w))) + api->putpixel(canvas, p, dest.y, tornado_mess(api->getpixel(last, dest.x + (p - dest.x + rotation) % dest.w , dest.y), canvas)); + } + } + + free(curve); +} + +void tornado_shutdown(magic_api * api) +{ +/* + if (tornado_click_snd != NULL) + Mix_FreeChunk(tornado_click_snd); +*/ + + if (tornado_release_snd != NULL) + Mix_FreeChunk(tornado_release_snd); + + if (tornado_base != NULL) + SDL_FreeSurface(tornado_base); + if (tornado_cloud != NULL) + SDL_FreeSurface(tornado_cloud); + if (tornado_cloud_colorized != NULL) + SDL_FreeSurface(tornado_cloud_colorized); +} + +// Record the color from Tux Paint: +void tornado_set_color(magic_api * api, Uint8 r, Uint8 g, Uint8 b) +{ + tornado_r = r; + tornado_g = g; + tornado_b = b; + + tornado_colorize_cloud(api); +} + +// Use colors: +int tornado_requires_colors(magic_api * api, int which) +{ + return 1; +} + + + +/* +Code to generate a cubic Bezier curve +*/ + +/* +cp is a 4 element array where: +cp[0] is the starting point, or P0 in the above diagram +cp[1] is the first control point, or P1 in the above diagram +cp[2] is the second control point, or P2 in the above diagram +cp[3] is the end point, or P3 in the above diagram +t is the parameter value, 0 <= t <= 1 +*/ + +static Point2D tornado_PointOnCubicBezier( Point2D* cp, float t ) +{ + float ax, bx, cx; + float ay, by, cy; + float tSquared, tCubed; + Point2D result; + + /* calculate the polynomial coefficients */ + + cx = 3.0 * (cp[1].x - cp[0].x); + bx = 3.0 * (cp[2].x - cp[1].x) - cx; + ax = cp[3].x - cp[0].x - cx - bx; + + cy = 3.0 * (cp[1].y - cp[0].y); + by = 3.0 * (cp[2].y - cp[1].y) - cy; + ay = cp[3].y - cp[0].y - cy - by; + + /* calculate the curve point at parameter value t */ + + tSquared = t * t; + tCubed = tSquared * t; + + result.x = (ax * tCubed) + (bx * tSquared) + (cx * t) + cp[0].x; + result.y = (ay * tCubed) + (by * tSquared) + (cy * t) + cp[0].y; + + return result; +} + +/* + ComputeBezier fills an array of Point2D structs with the curve + points generated from the control points cp. Caller must + allocate sufficient memory for the result, which is + +*/ + +static void tornado_ComputeBezier( Point2D* cp, int numberOfPoints, Point2D* curve ) +{ + float dt; + int i; + + dt = 1.0 / ( numberOfPoints - 1 ); + + for( i = 0; i < numberOfPoints; i++) + curve[i] = tornado_PointOnCubicBezier( cp, i*dt ); +} + + +static void tornado_colorize_cloud(magic_api * api) +{ + Uint32 amask; + int x, y; + Uint8 r, g, b, a; + + if (tornado_cloud_colorized != NULL) + SDL_FreeSurface(tornado_cloud_colorized); + + /* Create a surface to render into: */ + + amask = ~(tornado_cloud->format->Rmask | + tornado_cloud->format->Gmask | + tornado_cloud->format->Bmask); + + tornado_cloud_colorized = + SDL_CreateRGBSurface(SDL_SWSURFACE, + tornado_cloud->w, + tornado_cloud->h, + tornado_cloud->format->BitsPerPixel, + tornado_cloud->format->Rmask, + tornado_cloud->format->Gmask, + tornado_cloud->format->Bmask, amask); + + /* Render the new cloud: */ + + SDL_LockSurface(tornado_cloud); + SDL_LockSurface(tornado_cloud_colorized); + + for (y = 0; y < tornado_cloud->h; y++) + { + for (x = 0; x < tornado_cloud->w; x++) + { + SDL_GetRGBA(api->getpixel(tornado_cloud, x, y), + tornado_cloud->format, &r, &g, &b, &a); + + api->putpixel(tornado_cloud_colorized, x, y, + SDL_MapRGBA(tornado_cloud_colorized->format, + (tornado_r + r * 2) / 3, (tornado_g + g * 2) / 3, (tornado_b + b * 2) / 3, a)); + } + } + + SDL_UnlockSurface(tornado_cloud_colorized); + SDL_UnlockSurface(tornado_cloud); +} + +void tornado_switchin(magic_api * api, int which, int mode, SDL_Surface * canvas) +{ +} + +void tornado_switchout(magic_api * api, int which, int mode, SDL_Surface * canvas) +{ +} + +int tornado_modes(magic_api * api, int which) +{ + return(MODE_PAINT); +}