1/* GTK - The GIMP Toolkit 2 * Copyright (C) 1997 David Mosberger 3 * 2004 Roger Leigh 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public 16 * License along with this library; if not, write to the 17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 * Boston, MA 02111-1307, USA. 19 */ 20 21/* 22 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS 23 * file for a list of people on the GTK+ Team. See the ChangeLog 24 * files for a list of changes. These files are distributed with 25 * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 26 */ 27 28/* 29 * Incorporated into Gutenprint by Roger Leigh in 2004. Subsequent 30 * changes are by the Gutenprint team. 31 */ 32 33#include <config.h> 34#include <stdlib.h> 35#include <string.h> 36#include <math.h> 37 38#include <gtk/gtkmain.h> 39#include <gtk/gtkradiobutton.h> 40#include <gtk/gtktable.h> 41 42#include <gutenprint/gutenprint-intl-internal.h> 43 44#include <gutenprintui2/curve.h> 45#include <gutenprintui2/typebuiltins.h> 46 47#define RADIUS 3 /* radius of the control points */ 48#define MIN_DISTANCE 8 /* min distance between control points */ 49 50#define GRAPH_MASK (GDK_EXPOSURE_MASK | \ 51 GDK_POINTER_MOTION_MASK | \ 52 GDK_POINTER_MOTION_HINT_MASK | \ 53 GDK_ENTER_NOTIFY_MASK | \ 54 GDK_BUTTON_PRESS_MASK | \ 55 GDK_BUTTON_RELEASE_MASK | \ 56 GDK_BUTTON1_MOTION_MASK) 57 58enum { 59 PROP_0, 60 PROP_CURVE_TYPE, 61 PROP_MIN_X, 62 PROP_MAX_X, 63 PROP_MIN_Y, 64 PROP_MAX_Y 65}; 66 67static GtkDrawingAreaClass *parent_class = NULL; 68static guint curve_type_changed_signal = 0; 69 70 71/* forward declarations: */ 72static void stpui_curve_class_init (StpuiCurveClass *class); 73static void stpui_curve_init (StpuiCurve *curve); 74static void stpui_curve_get_property (GObject *object, 75 guint param_id, 76 GValue *value, 77 GParamSpec *pspec); 78static void stpui_curve_set_property (GObject *object, 79 guint param_id, 80 const GValue *value, 81 GParamSpec *pspec); 82static void stpui_curve_finalize (GObject *object); 83static gint stpui_curve_graph_events (GtkWidget *widget, 84 GdkEvent *event, 85 StpuiCurve *c); 86static void stpui_curve_size_graph (StpuiCurve *curve); 87 88GType 89stpui_curve_get_type (void) 90{ 91 static GType curve_type = 0; 92 93 if (!curve_type) 94 { 95 static const GTypeInfo curve_info = 96 { 97 sizeof (StpuiCurveClass), 98 NULL, /* base_init */ 99 NULL, /* base_finalize */ 100 (GClassInitFunc) stpui_curve_class_init, 101 NULL, /* class_finalize */ 102 NULL, /* class_data */ 103 sizeof (StpuiCurve), 104 0, /* n_preallocs */ 105 (GInstanceInitFunc) stpui_curve_init, 106 }; 107 108 curve_type = g_type_register_static (GTK_TYPE_DRAWING_AREA, 109 "StpuiCurve", 110 &curve_info, 0); 111 } 112 return curve_type; 113} 114 115static void 116stpui_curve_class_init (StpuiCurveClass *class) 117{ 118 GObjectClass *gobject_class = G_OBJECT_CLASS (class); 119 120 parent_class = g_type_class_peek_parent (class); 121 122 gobject_class->finalize = stpui_curve_finalize; 123 124 gobject_class->set_property = stpui_curve_set_property; 125 gobject_class->get_property = stpui_curve_get_property; 126 127 g_object_class_install_property (gobject_class, 128 PROP_CURVE_TYPE, 129 g_param_spec_enum ("curve_type", 130 _("Curve type"), 131 _("Is this curve linear, spline interpolated, or free-form"), 132 STPUI_TYPE_CURVE_TYPE, 133 STPUI_CURVE_TYPE_LINEAR, 134 G_PARAM_READABLE | 135 G_PARAM_WRITABLE)); 136 g_object_class_install_property (gobject_class, 137 PROP_MIN_X, 138 g_param_spec_float ("min_x", 139 _("Minimum X"), 140 _("Minimum possible value for X"), 141 -G_MAXFLOAT, 142 G_MAXFLOAT, 143 0.0, 144 G_PARAM_READABLE | 145 G_PARAM_WRITABLE)); 146 g_object_class_install_property (gobject_class, 147 PROP_MAX_X, 148 g_param_spec_float ("max_x", 149 _("Maximum X"), 150 _("Maximum possible X value"), 151 -G_MAXFLOAT, 152 G_MAXFLOAT, 153 1.0, 154 G_PARAM_READABLE | 155 G_PARAM_WRITABLE)); 156 g_object_class_install_property (gobject_class, 157 PROP_MIN_Y, 158 g_param_spec_float ("min_y", 159 _("Minimum Y"), 160 _("Minimum possible value for Y"), 161 -G_MAXFLOAT, 162 G_MAXFLOAT, 163 0.0, 164 G_PARAM_READABLE | 165 G_PARAM_WRITABLE)); 166 g_object_class_install_property (gobject_class, 167 PROP_MAX_Y, 168 g_param_spec_float ("max_y", 169 _("Maximum Y"), 170 _("Maximum possible value for Y"), 171 -G_MAXFLOAT, 172 G_MAXFLOAT, 173 1.0, 174 G_PARAM_READABLE | 175 G_PARAM_WRITABLE)); 176 177 curve_type_changed_signal = 178 g_signal_new ("curve_type_changed", 179 G_OBJECT_CLASS_TYPE (gobject_class), 180 G_SIGNAL_RUN_FIRST, 181 G_STRUCT_OFFSET (StpuiCurveClass, curve_type_changed), 182 NULL, NULL, 183 g_cclosure_marshal_VOID__VOID, 184 G_TYPE_NONE, 0); 185} 186 187static void 188stpui_curve_init (StpuiCurve *curve) 189{ 190 gint old_mask; 191 192 curve->cursor_type = GDK_TOP_LEFT_ARROW; 193 curve->pixmap = NULL; 194 curve->curve_type = STPUI_CURVE_TYPE_SPLINE; 195 curve->height = 0; 196 curve->grab_point = -1; 197 198 curve->num_points = 0; 199 curve->point = 0; 200 201 curve->num_ctlpoints = 0; 202 curve->ctlpoint = NULL; 203 204 curve->min_x = 0.0; 205 curve->max_x = 1.0; 206 curve->min_y = 0.0; 207 curve->max_y = 1.0; 208 209 old_mask = gtk_widget_get_events (GTK_WIDGET (curve)); 210 gtk_widget_set_events (GTK_WIDGET (curve), old_mask | GRAPH_MASK); 211 g_signal_connect (curve, "event", 212 G_CALLBACK (stpui_curve_graph_events), curve); 213 stpui_curve_size_graph (curve); 214} 215 216static void 217stpui_curve_set_property (GObject *object, 218 guint prop_id, 219 const GValue *value, 220 GParamSpec *pspec) 221{ 222 StpuiCurve *curve = STPUI_CURVE (object); 223 224 switch (prop_id) 225 { 226 case PROP_CURVE_TYPE: 227 stpui_curve_set_curve_type (curve, g_value_get_enum (value)); 228 break; 229 case PROP_MIN_X: 230 stpui_curve_set_range (curve, g_value_get_float (value), curve->max_x, 231 curve->min_y, curve->max_y); 232 break; 233 case PROP_MAX_X: 234 stpui_curve_set_range (curve, curve->min_x, g_value_get_float (value), 235 curve->min_y, curve->max_y); 236 break; 237 case PROP_MIN_Y: 238 stpui_curve_set_range (curve, curve->min_x, curve->max_x, 239 g_value_get_float (value), curve->max_y); 240 break; 241 case PROP_MAX_Y: 242 stpui_curve_set_range (curve, curve->min_x, curve->max_x, 243 curve->min_y, g_value_get_float (value)); 244 break; 245 default: 246 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 247 break; 248 } 249} 250 251static void 252stpui_curve_get_property (GObject *object, 253 guint prop_id, 254 GValue *value, 255 GParamSpec *pspec) 256{ 257 StpuiCurve *curve = STPUI_CURVE (object); 258 259 switch (prop_id) 260 { 261 case PROP_CURVE_TYPE: 262 g_value_set_enum (value, curve->curve_type); 263 break; 264 case PROP_MIN_X: 265 g_value_set_float (value, curve->min_x); 266 break; 267 case PROP_MAX_X: 268 g_value_set_float (value, curve->max_x); 269 break; 270 case PROP_MIN_Y: 271 g_value_set_float (value, curve->min_y); 272 break; 273 case PROP_MAX_Y: 274 g_value_set_float (value, curve->max_y); 275 break; 276 default: 277 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 278 break; 279 } 280} 281 282static int 283project (gfloat value, gfloat min, gfloat max, int norm) 284{ 285 return (norm - 1) * ((value - min) / (max - min)) + 0.5; 286} 287 288static gfloat 289unproject (gint value, gfloat min, gfloat max, int norm) 290{ 291 return value / (gfloat) (norm - 1) * (max - min) + min; 292} 293 294/* Solve the tridiagonal equation system that determines the second 295 derivatives for the interpolation points. (Based on Numerical 296 Recipies 2nd Edition.) */ 297static void 298spline_solve (int n, gfloat x[], gfloat y[], gfloat y2[]) 299{ 300 gfloat p, sig, *u; 301 gint i, k; 302 303 u = g_malloc ((n - 1) * sizeof (u[0])); 304 305 y2[0] = u[0] = 0.0; /* set lower boundary condition to "natural" */ 306 307 for (i = 1; i < n - 1; ++i) 308 { 309 sig = (x[i] - x[i - 1]) / (x[i + 1] - x[i - 1]); 310 p = sig * y2[i - 1] + 2.0; 311 y2[i] = (sig - 1.0) / p; 312 u[i] = ((y[i + 1] - y[i]) 313 / (x[i + 1] - x[i]) - (y[i] - y[i - 1]) / (x[i] - x[i - 1])); 314 u[i] = (6.0 * u[i] / (x[i + 1] - x[i - 1]) - sig * u[i - 1]) / p; 315 } 316 317 y2[n - 1] = 0.0; 318 for (k = n - 2; k >= 0; --k) 319 y2[k] = y2[k] * y2[k + 1] + u[k]; 320 321 g_free (u); 322} 323 324static gfloat 325spline_eval (int n, gfloat x[], gfloat y[], gfloat y2[], gfloat val) 326{ 327 gint k_lo, k_hi, k; 328 gfloat h, b, a; 329 330 /* do a binary search for the right interval: */ 331 k_lo = 0; k_hi = n - 1; 332 while (k_hi - k_lo > 1) 333 { 334 k = (k_hi + k_lo) / 2; 335 if (x[k] > val) 336 k_hi = k; 337 else 338 k_lo = k; 339 } 340 341 h = x[k_hi] - x[k_lo]; 342 g_assert (h > 0.0); 343 344 a = (x[k_hi] - val) / h; 345 b = (val - x[k_lo]) / h; 346 return a*y[k_lo] + b*y[k_hi] + 347 ((a*a*a - a)*y2[k_lo] + (b*b*b - b)*y2[k_hi]) * (h*h)/6.0; 348} 349 350static void 351stpui_curve_interpolate (StpuiCurve *c, gint width, gint height) 352{ 353 gfloat *vector; 354 int i; 355 356 vector = g_malloc (width * sizeof (vector[0])); 357 358 stpui_curve_get_vector (c, width, vector); 359 360 c->height = height; 361 if (c->num_points != width) 362 { 363 c->num_points = width; 364 if (c->point) 365 g_free (c->point); 366 c->point = g_malloc (c->num_points * sizeof (c->point[0])); 367 } 368 369 for (i = 0; i < width; ++i) 370 { 371 c->point[i].x = RADIUS + i; 372 c->point[i].y = RADIUS + height 373 - project (vector[i], c->min_y, c->max_y, height); 374 } 375 376 g_free (vector); 377} 378 379static void 380stpui_curve_draw (StpuiCurve *c, gint width, gint height) 381{ 382 GtkStateType state; 383 GtkStyle *style; 384 gint i; 385 386 if (!c->pixmap) 387 return; 388 389 if (c->height != height || c->num_points != width) 390 stpui_curve_interpolate (c, width, height); 391 392 state = GTK_STATE_NORMAL; 393 if (!GTK_WIDGET_IS_SENSITIVE (GTK_WIDGET (c))) 394 state = GTK_STATE_INSENSITIVE; 395 396 style = GTK_WIDGET (c)->style; 397 398 /* clear the pixmap: */ 399 gtk_paint_flat_box (style, c->pixmap, GTK_STATE_NORMAL, GTK_SHADOW_NONE, 400 NULL, GTK_WIDGET (c), "curve_bg", 401 0, 0, width + RADIUS * 2, height + RADIUS * 2); 402 /* draw the grid lines: (XXX make more meaningful) */ 403 for (i = 0; i < 5; i++) 404 { 405 gdk_draw_line (c->pixmap, style->dark_gc[state], 406 RADIUS, i * (height / 4.0) + RADIUS, 407 width + RADIUS, i * (height / 4.0) + RADIUS); 408 gdk_draw_line (c->pixmap, style->dark_gc[state], 409 i * (width / 4.0) + RADIUS, RADIUS, 410 i * (width / 4.0) + RADIUS, height + RADIUS); 411 } 412 413 gdk_draw_points (c->pixmap, style->fg_gc[state], c->point, c->num_points); 414 if (c->curve_type != STPUI_CURVE_TYPE_FREE) 415 for (i = 0; i < c->num_ctlpoints; ++i) 416 { 417 gint x, y; 418 419 if (c->ctlpoint[i][0] < c->min_x) 420 continue; 421 422 x = project (c->ctlpoint[i][0], c->min_x, c->max_x, 423 width); 424 y = height - 425 project (c->ctlpoint[i][1], c->min_y, c->max_y, 426 height); 427 428 /* draw a bullet: */ 429 gdk_draw_arc (c->pixmap, style->fg_gc[state], TRUE, x, y, 430 RADIUS * 2, RADIUS*2, 0, 360*64); 431 } 432 gdk_draw_drawable (GTK_WIDGET (c)->window, style->fg_gc[state], c->pixmap, 433 0, 0, 0, 0, width + RADIUS * 2, height + RADIUS * 2); 434} 435 436static gint 437stpui_curve_graph_events (GtkWidget *widget, 438 GdkEvent *event, 439 StpuiCurve *c) 440{ 441 GdkCursorType new_type = c->cursor_type; 442 gint i, src, dst, leftbound, rightbound; 443 GdkEventButton *bevent; 444 GdkEventMotion *mevent; 445 GtkWidget *w; 446 gint tx, ty; 447 gint cx, x, y, width, height; 448 gint closest_point = 0; 449 gfloat rx, ry, min_x; 450 guint distance; 451 gint x1, x2, y1, y2; 452 gint retval = FALSE; 453 454 w = GTK_WIDGET (c); 455 width = w->allocation.width - RADIUS * 2; 456 height = w->allocation.height - RADIUS * 2; 457 458 if ((width < 0) || (height < 0)) 459 return FALSE; 460 461 /* get the pointer position */ 462 gdk_window_get_pointer (w->window, &tx, &ty, NULL); 463 x = CLAMP ((tx - RADIUS), 0, width-1); 464 y = CLAMP ((ty - RADIUS), 0, height-1); 465 466 min_x = c->min_x; 467 468 distance = ~0U; 469 for (i = 0; i < c->num_ctlpoints; ++i) 470 { 471 cx = project (c->ctlpoint[i][0], min_x, c->max_x, width); 472 if ((guint) abs (x - cx) < distance) 473 { 474 distance = abs (x - cx); 475 closest_point = i; 476 } 477 } 478 479 switch (event->type) 480 { 481 case GDK_CONFIGURE: 482 if (c->pixmap) 483 g_object_unref (c->pixmap); 484 c->pixmap = NULL; 485 /* fall through */ 486 case GDK_EXPOSE: 487 if (!c->pixmap) 488 c->pixmap = gdk_pixmap_new (w->window, 489 w->allocation.width, 490 w->allocation.height, -1); 491 stpui_curve_draw (c, width, height); 492 break; 493 494 case GDK_BUTTON_PRESS: 495 gtk_grab_add (widget); 496 497 bevent = (GdkEventButton *) event; 498 new_type = GDK_TCROSS; 499 500 switch (c->curve_type) 501 { 502 case STPUI_CURVE_TYPE_LINEAR: 503 case STPUI_CURVE_TYPE_SPLINE: 504 if (distance > MIN_DISTANCE) 505 { 506 /* insert a new control point */ 507 if (c->num_ctlpoints > 0) 508 { 509 cx = project (c->ctlpoint[closest_point][0], min_x, 510 c->max_x, width); 511 if (x > cx) 512 ++closest_point; 513 } 514 ++c->num_ctlpoints; 515 c->ctlpoint = 516 g_realloc (c->ctlpoint, 517 c->num_ctlpoints * sizeof (*c->ctlpoint)); 518 for (i = c->num_ctlpoints - 1; i > closest_point; --i) 519 memcpy (c->ctlpoint + i, c->ctlpoint + i - 1, 520 sizeof (*c->ctlpoint)); 521 } 522 c->grab_point = closest_point; 523 c->ctlpoint[c->grab_point][0] = 524 unproject (x, min_x, c->max_x, width); 525 c->ctlpoint[c->grab_point][1] = 526 unproject (height - y, c->min_y, c->max_y, height); 527 528 stpui_curve_interpolate (c, width, height); 529 break; 530 531 case STPUI_CURVE_TYPE_FREE: 532 c->point[x].x = RADIUS + x; 533 c->point[x].y = RADIUS + y; 534 c->grab_point = x; 535 c->last = y; 536 break; 537 } 538 stpui_curve_draw (c, width, height); 539 retval = TRUE; 540 break; 541 542 case GDK_BUTTON_RELEASE: 543 gtk_grab_remove (widget); 544 545 /* delete inactive points: */ 546 if (c->curve_type != STPUI_CURVE_TYPE_FREE) 547 { 548 for (src = dst = 0; src < c->num_ctlpoints; ++src) 549 { 550 if (c->ctlpoint[src][0] >= min_x) 551 { 552 memcpy (c->ctlpoint + dst, c->ctlpoint + src, 553 sizeof (*c->ctlpoint)); 554 ++dst; 555 } 556 } 557 if (dst < src) 558 { 559 c->num_ctlpoints -= (src - dst); 560 if (c->num_ctlpoints <= 0) 561 { 562 c->num_ctlpoints = 1; 563 c->ctlpoint[0][0] = min_x; 564 c->ctlpoint[0][1] = c->min_y; 565 stpui_curve_interpolate (c, width, height); 566 stpui_curve_draw (c, width, height); 567 } 568 c->ctlpoint = 569 g_realloc (c->ctlpoint, 570 c->num_ctlpoints * sizeof (*c->ctlpoint)); 571 } 572 } 573 new_type = GDK_FLEUR; 574 c->grab_point = -1; 575 retval = TRUE; 576 break; 577 578 case GDK_MOTION_NOTIFY: 579 mevent = (GdkEventMotion *) event; 580 581 switch (c->curve_type) 582 { 583 case STPUI_CURVE_TYPE_LINEAR: 584 case STPUI_CURVE_TYPE_SPLINE: 585 if (c->grab_point == -1) 586 { 587 /* if no point is grabbed... */ 588 if (distance <= MIN_DISTANCE) 589 new_type = GDK_FLEUR; 590 else 591 new_type = GDK_TCROSS; 592 } 593 else 594 { 595 /* drag the grabbed point */ 596 new_type = GDK_TCROSS; 597 598 leftbound = -MIN_DISTANCE; 599 if (c->grab_point > 0) 600 leftbound = project (c->ctlpoint[c->grab_point - 1][0], 601 min_x, c->max_x, width); 602 603 rightbound = width + RADIUS * 2 + MIN_DISTANCE; 604 if (c->grab_point + 1 < c->num_ctlpoints) 605 rightbound = project (c->ctlpoint[c->grab_point + 1][0], 606 min_x, c->max_x, width); 607 608 if (tx <= leftbound || tx >= rightbound 609 || ty > height + RADIUS * 2 + MIN_DISTANCE 610 || ty < -MIN_DISTANCE) 611 c->ctlpoint[c->grab_point][0] = min_x - 1.0; 612 else 613 { 614 rx = unproject (x, min_x, c->max_x, width); 615 ry = unproject (height - y, c->min_y, c->max_y, height); 616 c->ctlpoint[c->grab_point][0] = rx; 617 c->ctlpoint[c->grab_point][1] = ry; 618 } 619 stpui_curve_interpolate (c, width, height); 620 stpui_curve_draw (c, width, height); 621 } 622 break; 623 624 case STPUI_CURVE_TYPE_FREE: 625 if (c->grab_point != -1) 626 { 627 if (c->grab_point > x) 628 { 629 x1 = x; 630 x2 = c->grab_point; 631 y1 = y; 632 y2 = c->last; 633 } 634 else 635 { 636 x1 = c->grab_point; 637 x2 = x; 638 y1 = c->last; 639 y2 = y; 640 } 641 642 if (x2 != x1) 643 for (i = x1; i <= x2; i++) 644 { 645 c->point[i].x = RADIUS + i; 646 c->point[i].y = RADIUS + 647 (y1 + ((y2 - y1) * (i - x1)) / (x2 - x1)); 648 } 649 else 650 { 651 c->point[x].x = RADIUS + x; 652 c->point[x].y = RADIUS + y; 653 } 654 c->grab_point = x; 655 c->last = y; 656 stpui_curve_draw (c, width, height); 657 } 658 if (mevent->state & GDK_BUTTON1_MASK) 659 new_type = GDK_TCROSS; 660 else 661 new_type = GDK_PENCIL; 662 break; 663 } 664 if (new_type != (GdkCursorType) c->cursor_type) 665 { 666 GdkCursor *cursor; 667 668 c->cursor_type = new_type; 669 670 cursor = gdk_cursor_new_for_display (gtk_widget_get_display (w), 671 c->cursor_type); 672 gdk_window_set_cursor (w->window, cursor); 673 gdk_cursor_unref (cursor); 674 } 675 retval = TRUE; 676 break; 677 678 default: 679 break; 680 } 681 682 return retval; 683} 684 685void 686stpui_curve_set_curve_type (StpuiCurve *c, StpuiCurveType new_type) 687{ 688 gfloat rx, dx; 689 gint x, i; 690 691 if (new_type != c->curve_type) 692 { 693 gint width, height; 694 695 width = GTK_WIDGET (c)->allocation.width - RADIUS * 2; 696 height = GTK_WIDGET (c)->allocation.height - RADIUS * 2; 697 698 if (new_type == STPUI_CURVE_TYPE_FREE) 699 { 700 stpui_curve_interpolate (c, width, height); 701 c->curve_type = new_type; 702 } 703 else if (c->curve_type == STPUI_CURVE_TYPE_FREE) 704 { 705 if (c->ctlpoint) 706 g_free (c->ctlpoint); 707 c->num_ctlpoints = 9; 708 c->ctlpoint = g_malloc (c->num_ctlpoints * sizeof (*c->ctlpoint)); 709 710 rx = 0.0; 711 dx = (width - 1) / (gfloat) (c->num_ctlpoints - 1); 712 713 for (i = 0; i < c->num_ctlpoints; ++i, rx += dx) 714 { 715 x = (int) (rx + 0.5); 716 c->ctlpoint[i][0] = 717 unproject (x, c->min_x, c->max_x, width); 718 c->ctlpoint[i][1] = 719 unproject (RADIUS + height - c->point[x].y, 720 c->min_y, c->max_y, height); 721 } 722 c->curve_type = new_type; 723 stpui_curve_interpolate (c, width, height); 724 } 725 else 726 { 727 c->curve_type = new_type; 728 stpui_curve_interpolate (c, width, height); 729 } 730 g_signal_emit (c, curve_type_changed_signal, 0); 731 g_object_notify (G_OBJECT (c), "curve_type"); 732 stpui_curve_draw (c, width, height); 733 } 734} 735 736static void 737stpui_curve_size_graph (StpuiCurve *curve) 738{ 739 gint width, height; 740 gfloat aspect; 741 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (curve)); 742 743 width = (curve->max_x - curve->min_x) + 1; 744 height = (curve->max_y - curve->min_y) + 1; 745 aspect = width / (gfloat) height; 746 if (width > gdk_screen_get_width (screen) / 4) 747 width = gdk_screen_get_width (screen) / 4; 748 if (height > gdk_screen_get_height (screen) / 4) 749 height = gdk_screen_get_height (screen) / 4; 750 751 if (aspect < 1.0) 752 width = height * aspect; 753 else 754 height = width / aspect; 755 756 gtk_widget_set_size_request (GTK_WIDGET (curve), 757 width + RADIUS * 2, 758 height + RADIUS * 2); 759} 760 761static void 762stpui_curve_reset_vector (StpuiCurve *curve) 763{ 764 if (curve->ctlpoint) 765 g_free (curve->ctlpoint); 766 767 curve->num_ctlpoints = 2; 768 curve->ctlpoint = g_malloc (2 * sizeof (curve->ctlpoint[0])); 769 curve->ctlpoint[0][0] = curve->min_x; 770 curve->ctlpoint[0][1] = curve->min_y; 771 curve->ctlpoint[1][0] = curve->max_x; 772 curve->ctlpoint[1][1] = curve->max_y; 773 774 if (curve->pixmap) 775 { 776 gint width, height; 777 778 width = GTK_WIDGET (curve)->allocation.width - RADIUS * 2; 779 height = GTK_WIDGET (curve)->allocation.height - RADIUS * 2; 780 781 if (curve->curve_type == STPUI_CURVE_TYPE_FREE) 782 { 783 curve->curve_type = STPUI_CURVE_TYPE_LINEAR; 784 stpui_curve_interpolate (curve, width, height); 785 curve->curve_type = STPUI_CURVE_TYPE_FREE; 786 } 787 else 788 stpui_curve_interpolate (curve, width, height); 789 stpui_curve_draw (curve, width, height); 790 } 791} 792 793void 794stpui_curve_reset (StpuiCurve *c) 795{ 796 StpuiCurveType old_type; 797 798 old_type = c->curve_type; 799 c->curve_type = STPUI_CURVE_TYPE_SPLINE; 800 stpui_curve_reset_vector (c); 801 802 if (old_type != STPUI_CURVE_TYPE_SPLINE) 803 { 804 g_signal_emit (c, curve_type_changed_signal, 0); 805 g_object_notify (G_OBJECT (c), "curve_type"); 806 } 807} 808 809void 810stpui_curve_set_gamma (StpuiCurve *c, gfloat gamma) 811{ 812 gfloat x, one_over_gamma, height, one_over_width; 813 StpuiCurveType old_type; 814 gint i; 815 816 if (c->num_points < 2) 817 return; 818 819 old_type = c->curve_type; 820 c->curve_type = STPUI_CURVE_TYPE_FREE; 821 822 if (gamma <= 0) 823 one_over_gamma = 1.0; 824 else 825 one_over_gamma = 1.0 / gamma; 826 one_over_width = 1.0 / (c->num_points - 1); 827 height = c->height; 828 for (i = 0; i < c->num_points; ++i) 829 { 830 x = (gfloat) i / (c->num_points - 1); 831 c->point[i].x = RADIUS + i; 832 c->point[i].y = 833 RADIUS + (height * (1.0 - pow (x, one_over_gamma)) + 0.5); 834 } 835 836 if (old_type != STPUI_CURVE_TYPE_FREE) 837 g_signal_emit (c, curve_type_changed_signal, 0); 838 839 stpui_curve_draw (c, c->num_points, c->height); 840} 841 842void 843stpui_curve_set_range (StpuiCurve *curve, 844 gfloat min_x, 845 gfloat max_x, 846 gfloat min_y, 847 gfloat max_y) 848{ 849 g_object_freeze_notify (G_OBJECT (curve)); 850 if (curve->min_x != min_x) { 851 curve->min_x = min_x; 852 g_object_notify (G_OBJECT (curve), "min_x"); 853 } 854 if (curve->max_x != max_x) { 855 curve->max_x = max_x; 856 g_object_notify (G_OBJECT (curve), "max_x"); 857 } 858 if (curve->min_y != min_y) { 859 curve->min_y = min_y; 860 g_object_notify (G_OBJECT (curve), "min_y"); 861 } 862 if (curve->max_y != max_y) { 863 curve->max_y = max_y; 864 g_object_notify (G_OBJECT (curve), "max_y"); 865 } 866 g_object_thaw_notify (G_OBJECT (curve)); 867 868 stpui_curve_size_graph (curve); 869 stpui_curve_reset_vector (curve); 870} 871 872void 873stpui_curve_set_vector (StpuiCurve *c, int veclen, const gfloat vector[]) 874{ 875 StpuiCurveType old_type; 876 gfloat rx, dx, ry; 877 gint i, height; 878 GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (c)); 879 880 old_type = c->curve_type; 881 c->curve_type = STPUI_CURVE_TYPE_FREE; 882 883 if (c->point) 884 height = GTK_WIDGET (c)->allocation.height - RADIUS * 2; 885 else 886 { 887 height = (c->max_y - c->min_y); 888 if (height > gdk_screen_get_height (screen) / 4) 889 height = gdk_screen_get_height (screen) / 4; 890 891 c->height = height; 892 c->num_points = veclen; 893 c->point = g_malloc (c->num_points * sizeof (c->point[0])); 894 } 895 rx = 0; 896 dx = (veclen - 1.0) / (c->num_points - 1.0); 897 898 for (i = 0; i < c->num_points; ++i, rx += dx) 899 { 900 ry = vector[(int) (rx + 0.5)]; 901 if (ry > c->max_y) ry = c->max_y; 902 if (ry < c->min_y) ry = c->min_y; 903 c->point[i].x = RADIUS + i; 904 c->point[i].y = 905 RADIUS + height - project (ry, c->min_y, c->max_y, height); 906 } 907 if (old_type != STPUI_CURVE_TYPE_FREE) 908 { 909 g_signal_emit (c, curve_type_changed_signal, 0); 910 g_object_notify (G_OBJECT (c), "curve_type"); 911 } 912 913 stpui_curve_draw (c, c->num_points, height); 914} 915 916void 917stpui_curve_get_vector (StpuiCurve *c, int veclen, gfloat vector[]) 918{ 919 gfloat rx, ry, dx, dy, min_x, delta_x, *mem, *xv, *yv, *y2v, prev; 920 gint dst, i, x, next, num_active_ctlpoints = 0, first_active = -1; 921 922 min_x = c->min_x; 923 924 if (c->curve_type != STPUI_CURVE_TYPE_FREE) 925 { 926 /* count active points: */ 927 prev = min_x - 1.0; 928 for (i = num_active_ctlpoints = 0; i < c->num_ctlpoints; ++i) 929 if (c->ctlpoint[i][0] > prev) 930 { 931 if (first_active < 0) 932 first_active = i; 933 prev = c->ctlpoint[i][0]; 934 ++num_active_ctlpoints; 935 } 936 937 /* handle degenerate case: */ 938 if (num_active_ctlpoints < 2) 939 { 940 if (num_active_ctlpoints > 0) 941 ry = c->ctlpoint[first_active][1]; 942 else 943 ry = c->min_y; 944 if (ry < c->min_y) ry = c->min_y; 945 if (ry > c->max_y) ry = c->max_y; 946 for (x = 0; x < veclen; ++x) 947 vector[x] = ry; 948 return; 949 } 950 } 951 952 switch (c->curve_type) 953 { 954 case STPUI_CURVE_TYPE_SPLINE: 955 mem = g_malloc (3 * num_active_ctlpoints * sizeof (gfloat)); 956 xv = mem; 957 yv = mem + num_active_ctlpoints; 958 y2v = mem + 2*num_active_ctlpoints; 959 960 prev = min_x - 1.0; 961 for (i = dst = 0; i < c->num_ctlpoints; ++i) 962 if (c->ctlpoint[i][0] > prev) 963 { 964 prev = c->ctlpoint[i][0]; 965 xv[dst] = c->ctlpoint[i][0]; 966 yv[dst] = c->ctlpoint[i][1]; 967 ++dst; 968 } 969 970 spline_solve (num_active_ctlpoints, xv, yv, y2v); 971 972 rx = min_x; 973 dx = (c->max_x - min_x) / (veclen - 1); 974 for (x = 0; x < veclen; ++x, rx += dx) 975 { 976 ry = spline_eval (num_active_ctlpoints, xv, yv, y2v, rx); 977 if (ry < c->min_y) ry = c->min_y; 978 if (ry > c->max_y) ry = c->max_y; 979 vector[x] = ry; 980 } 981 982 g_free (mem); 983 break; 984 985 case STPUI_CURVE_TYPE_LINEAR: 986 dx = (c->max_x - min_x) / (veclen - 1); 987 rx = min_x; 988 ry = c->min_y; 989 dy = 0.0; 990 i = first_active; 991 for (x = 0; x < veclen; ++x, rx += dx) 992 { 993 if (rx >= c->ctlpoint[i][0]) 994 { 995 if (rx > c->ctlpoint[i][0]) 996 ry = c->min_y; 997 dy = 0.0; 998 next = i + 1; 999 while (next < c->num_ctlpoints 1000 && c->ctlpoint[next][0] <= c->ctlpoint[i][0]) 1001 ++next; 1002 if (next < c->num_ctlpoints) 1003 { 1004 delta_x = c->ctlpoint[next][0] - c->ctlpoint[i][0]; 1005 dy = ((c->ctlpoint[next][1] - c->ctlpoint[i][1]) 1006 / delta_x); 1007 dy *= dx; 1008 ry = c->ctlpoint[i][1]; 1009 i = next; 1010 } 1011 } 1012 vector[x] = ry; 1013 ry += dy; 1014 } 1015 break; 1016 1017 case STPUI_CURVE_TYPE_FREE: 1018 if (c->point) 1019 { 1020 rx = 0.0; 1021 dx = c->num_points / (double) veclen; 1022 for (x = 0; x < veclen; ++x, rx += dx) 1023 vector[x] = unproject (RADIUS + c->height - c->point[(int) rx].y, 1024 c->min_y, c->max_y, 1025 c->height); 1026 } 1027 else 1028 memset (vector, 0, veclen * sizeof (vector[0])); 1029 break; 1030 } 1031} 1032 1033GtkWidget* 1034stpui_curve_new (void) 1035{ 1036 return g_object_new (STPUI_TYPE_CURVE, NULL); 1037} 1038 1039static void 1040stpui_curve_finalize (GObject *object) 1041{ 1042 StpuiCurve *curve; 1043 1044 g_return_if_fail (STPUI_IS_CURVE (object)); 1045 1046 curve = STPUI_CURVE (object); 1047 if (curve->pixmap) 1048 g_object_unref (curve->pixmap); 1049 if (curve->point) 1050 g_free (curve->point); 1051 if (curve->ctlpoint) 1052 g_free (curve->ctlpoint); 1053 1054 G_OBJECT_CLASS (parent_class)->finalize (object); 1055} 1056