1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * This file is part of openfx-supportext <https://github.com/devernay/openfx-supportext>,
4 * Copyright (C) 2013-2018 INRIA
5 *
6 * openfx-supportext is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * openfx-supportext is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with openfx-supportext. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
18 * ***** END LICENSE BLOCK ***** */
19
20 /*
21 * OFX generic rectangle interact with 4 corner points + center point and 4 mid-points.
22 * You can use it to define any rectangle in an image resizable by the user.
23 */
24
25 #include "ofxsRamp.h"
26
27 #include <cmath>
28 #include <cfloat> // DBL_MAX
29
30 #ifdef __APPLE__
31 #include <OpenGL/gl.h>
32 #else
33 #include <GL/gl.h>
34 #endif
35 #define POINT_TOLERANCE 6
36 #define POINT_SIZE 5
37
38 namespace OFX {
39 static inline
40 void
crossProd(const Ofx3DPointD & u,const Ofx3DPointD & v,Ofx3DPointD * w)41 crossProd(const Ofx3DPointD& u,
42 const Ofx3DPointD& v,
43 Ofx3DPointD* w)
44 {
45 w->x = u.y * v.z - u.z * v.y;
46 w->y = u.z * v.x - u.x * v.z;
47 w->z = u.x * v.y - u.y * v.x;
48 }
49
50 // round to the closest int, 1/10 int, etc
51 // this make parameter editing easier
52 // pscale is args.pixelScale.x / args.renderScale.x;
53 // pscale10 is the power of 10 below pscale
54 static inline
55 double
fround(double val,double pscale)56 fround(double val,
57 double pscale)
58 {
59 double pscale10 = std::pow( 10., std::floor( std::log10(pscale) ) );
60
61 return pscale10 * std::floor(val / pscale10 + 0.5);
62 }
63
64 bool
draw(const DrawArgs & args)65 RampInteractHelper::draw(const DrawArgs &args)
66 {
67 const double time = args.time;
68
69 if ( !_interactOpen->getValueAtTime(time) ) {
70 return false;
71 }
72 RampTypeEnum type = (RampTypeEnum)_type->getValueAtTime(time);
73 bool noramp = (type == eRampTypeNone);
74 if (noramp) {
75 return false;
76 }
77 OfxRGBColourD color = { 0.8, 0.8, 0.8 };
78 _interact->getSuggestedColour(color);
79 const OfxPointD &pscale = args.pixelScale;
80 GLdouble projection[16];
81 glGetDoublev( GL_PROJECTION_MATRIX, projection);
82 GLint viewport[4];
83 glGetIntegerv(GL_VIEWPORT, viewport);
84 OfxPointD shadow; // how much to translate GL_PROJECTION to get exactly one pixel on screen
85 shadow.x = 2. / (projection[0] * viewport[2]);
86 shadow.y = 2. / (projection[5] * viewport[3]);
87
88 OfxPointD p[2];
89 if (_state == eInteractStateIdle) {
90 _point0->getValueAtTime(time, p[0].x, p[0].y);
91 _point1->getValueAtTime(time, p[1].x, p[1].y);
92 } else {
93 p[0] = _point0DragPos;
94 p[1] = _point1DragPos;
95 }
96
97 ///Clamp points to the rod
98 OfxRectD rod = _dstClip->getRegionOfDefinition(time);
99
100 // A line is represented by a 3-vector (a,b,c), and its equation is (a,b,c).(x,y,1)=0
101 // The intersection of two lines is given by their cross-product: (wx,wy,w) = (a,b,c)x(a',b',c').
102 // The line passing through 2 points is obtained by their cross-product: (a,b,c) = (x,y,1)x(x',y',1)
103 // The two lines passing through p0 and p1 and orthogonal to p0p1 are:
104 // (p1.x - p0.x, p1.y - p0.y, -p0.x*(p1.x-p0.x) - p0.y*(p1.y-p0.y)) passing through p0
105 // (p1.x - p0.x, p1.y - p0.y, -p1.x*(p1.x-p0.x) - p1.y*(p1.y-p0.y)) passing through p1
106 // the four lines defining the RoD are:
107 // (1,0,-x1) [x=x1]
108 // (1,0,-x2) [x=x2]
109 // (0,1,-y1) [x=y1]
110 // (0,1,-y2) [y=y2]
111 const Ofx3DPointD linex1 = {1, 0, -rod.x1};
112 const Ofx3DPointD linex2 = {1, 0, -rod.x2};
113 const Ofx3DPointD liney1 = {0, 1, -rod.y1};
114 const Ofx3DPointD liney2 = {0, 1, -rod.y2};
115 Ofx3DPointD line[2];
116 OfxPointD pline1[2];
117 OfxPointD pline2[2];
118
119 // line passing through p0
120 line[0].x = p[1].x - p[0].x;
121 line[0].y = p[1].y - p[0].y;
122 line[0].z = -p[0].x * (p[1].x - p[0].x) - p[0].y * (p[1].y - p[0].y);
123 // line passing through p1
124 line[1].x = p[1].x - p[0].x;
125 line[1].y = p[1].y - p[0].y;
126 line[1].z = -p[1].x * (p[1].x - p[0].x) - p[1].y * (p[1].y - p[0].y);
127 // for each line...
128 for (int i = 0; i < 2; ++i) {
129 // compute the intersection with the four lines
130 Ofx3DPointD interx1, interx2, intery1, intery2;
131
132 crossProd(line[i], linex1, &interx1);
133 crossProd(line[i], linex2, &interx2);
134 crossProd(line[i], liney1, &intery1);
135 crossProd(line[i], liney2, &intery2);
136 if ( (interx1.z != 0.) && (interx2.z != 0.) ) {
137 // initialize pline1 to the intersection with x=x1, pline2 with x=x2
138 pline1[i].x = interx1.x / interx1.z;
139 pline1[i].y = interx1.y / interx1.z;
140 pline2[i].x = interx2.x / interx2.z;
141 pline2[i].y = interx2.y / interx2.z;
142 if ( ( (pline1[i].y > rod.y2) && (pline2[i].y > rod.y2) ) ||
143 ( ( pline1[i].y < rod.y1) && ( pline2[i].y < rod.y1) ) ) {
144 // line doesn't intersect rectangle, don't draw it.
145 pline1[i].x = p[i].x;
146 pline1[i].y = p[i].y;
147 pline2[i].x = p[i].x;
148 pline2[i].y = p[i].y;
149 } else if (pline1[i].y < pline2[i].y) {
150 // y is an increasing function of x, test the two other endpoints
151 if ( (intery1.z != 0.) && (intery1.x / intery1.z > pline1[i].x) ) {
152 pline1[i].x = intery1.x / intery1.z;
153 pline1[i].y = intery1.y / intery1.z;
154 }
155 if ( (intery2.z != 0.) && (intery2.x / intery2.z < pline2[i].x) ) {
156 pline2[i].x = intery2.x / intery2.z;
157 pline2[i].y = intery2.y / intery2.z;
158 }
159 } else {
160 // y is an decreasing function of x, test the two other endpoints
161 if ( (intery2.z != 0.) && (intery2.x / intery2.z > pline1[i].x) ) {
162 pline1[i].x = intery2.x / intery2.z;
163 pline1[i].y = intery2.y / intery2.z;
164 }
165 if ( (intery1.z != 0.) && (intery1.x / intery1.z < pline2[i].x) ) {
166 pline2[i].x = intery1.x / intery1.z;
167 pline2[i].y = intery1.y / intery1.z;
168 }
169 }
170 } else {
171 // initialize pline1 to the intersection with y=y1, pline2 with y=y2
172 pline1[i].x = intery1.x / intery1.z;
173 pline1[i].y = intery1.y / intery1.z;
174 pline2[i].x = intery2.x / intery2.z;
175 pline2[i].y = intery2.y / intery2.z;
176 if ( ( (pline1[i].x > rod.x2) && (pline2[i].x > rod.x2) ) ||
177 ( ( pline1[i].x < rod.x1) && ( pline2[i].x < rod.x1) ) ) {
178 // line doesn't intersect rectangle, don't draw it.
179 pline1[i].x = p[i].x;
180 pline1[i].y = p[i].y;
181 pline2[i].x = p[i].x;
182 pline2[i].y = p[i].y;
183 }
184 }
185 }
186
187 //glPushAttrib(GL_ALL_ATTRIB_BITS); // caller is responsible for protecting attribs
188
189 glEnable(GL_LINE_STIPPLE);
190 glEnable(GL_LINE_SMOOTH);
191 glDisable(GL_POINT_SMOOTH);
192 glEnable(GL_BLEND);
193 glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
194 glLineWidth(1.5f);
195 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
196 glLineStipple(2, 0xAAAA);
197
198 glPointSize(POINT_SIZE);
199
200 // Draw everything twice
201 // l = 0: shadow
202 // l = 1: drawing
203 for (int l = 0; l < 2; ++l) {
204 // shadow (uses GL_PROJECTION)
205 glMatrixMode(GL_PROJECTION);
206 int direction = (l == 0) ? 1 : -1;
207 // translate (1,-1) pixels
208 glTranslated(direction * shadow.x, -direction * shadow.y, 0);
209 glMatrixMode(GL_MODELVIEW); // Modelview should be used on Nuke
210
211 for (int i = 0; i < 2; ++i) {
212 bool dragging = _state == (i == 0 ? eInteractStateDraggingPoint0 : eInteractStateDraggingPoint1);
213 glBegin(GL_POINTS);
214 if (dragging) {
215 glColor3f(0.f * l, 1.f * l, 0.f * l);
216 } else {
217 glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
218 }
219 glVertex2d(p[i].x, p[i].y);
220 glEnd();
221
222 glBegin(GL_LINES);
223 glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
224 glVertex2d(pline1[i].x, pline1[i].y);
225 glVertex2d(pline2[i].x, pline2[i].y);
226 glEnd();
227
228 double xoffset = 5 * pscale.x;
229 double yoffset = 5 * pscale.y;
230 TextRenderer::bitmapString(p[i].x + xoffset, p[i].y + yoffset, i == 0 ? kParamRampPoint0Label : kParamRampPoint1Label);
231 }
232 }
233
234 //glPopAttrib();
235
236 return true;
237 } // RampInteractHelper::draw
238
239 static bool
isNearby(const OfxPointD & p,double x,double y,double tolerance,const OfxPointD & pscale)240 isNearby(const OfxPointD& p,
241 double x,
242 double y,
243 double tolerance,
244 const OfxPointD& pscale)
245 {
246 return std::fabs(p.x - x) <= tolerance * pscale.x && std::fabs(p.y - y) <= tolerance * pscale.y;
247 }
248
249 bool
penMotion(const PenArgs & args)250 RampInteractHelper::penMotion(const PenArgs &args)
251 {
252 const double time = args.time;
253
254 if ( !_interactOpen->getValueAtTime(time) ) {
255 return false;
256 }
257 RampTypeEnum type = (RampTypeEnum)_type->getValueAtTime(time);
258 bool noramp = (type == eRampTypeNone);
259 if (noramp) {
260 return false;
261 }
262
263 const OfxPointD &pscale = args.pixelScale;
264 OfxPointD p0, p1;
265 if (_state != eInteractStateIdle) {
266 p0 = _point0DragPos;
267 p1 = _point1DragPos;
268 } else {
269 _point0->getValueAtTime(time, p0.x, p0.y);
270 _point1->getValueAtTime(time, p1.x, p1.y);
271 }
272 bool valuesChanged = false;
273 OfxPointD delta;
274 delta.x = args.penPosition.x - _lastMousePos.x;
275 delta.y = args.penPosition.y - _lastMousePos.y;
276
277 if (_state == eInteractStateDraggingPoint0) {
278 _point0DragPos.x += delta.x;
279 _point0DragPos.y += delta.y;
280 valuesChanged = true;
281 } else if (_state == eInteractStateDraggingPoint1) {
282 _point1DragPos.x += delta.x;
283 _point1DragPos.y += delta.y;
284 valuesChanged = true;
285 }
286
287 if ( (_state != eInteractStateIdle) && _interactiveDrag && valuesChanged ) {
288 if (_state == eInteractStateDraggingPoint0) {
289 _point0->setValue( fround(_point0DragPos.x, pscale.x), fround(_point0DragPos.y, pscale.y) );
290 } else if (_state == eInteractStateDraggingPoint1) {
291 _point1->setValue( fround(_point1DragPos.x, pscale.x), fround(_point1DragPos.y, pscale.y) );
292 }
293 }
294
295 if (valuesChanged) {
296 _interact->requestRedraw();
297 }
298
299 _lastMousePos = args.penPosition;
300
301 return valuesChanged;
302 } // RampInteractHelper::penMotion
303
304 bool
penDown(const PenArgs & args)305 RampInteractHelper::penDown(const PenArgs &args)
306 {
307 const double time = args.time;
308
309 if ( !_interactOpen->getValueAtTime(time) ) {
310 return false;
311 }
312 RampTypeEnum type = (RampTypeEnum)_type->getValueAtTime(time);
313 bool noramp = (type == eRampTypeNone);
314 if (noramp) {
315 return false;
316 }
317
318 const OfxPointD &pscale = args.pixelScale;
319 OfxPointD p0, p1;
320 if (_state != eInteractStateIdle) {
321 p0 = _point0DragPos;
322 p1 = _point1DragPos;
323 } else {
324 _point0->getValueAtTime(time, p0.x, p0.y);
325 _point1->getValueAtTime(time, p1.x, p1.y);
326 _interactive->getValueAtTime(time, _interactiveDrag);
327 }
328
329 bool didSomething = false;
330
331 if ( isNearby(args.penPosition, p0.x, p0.y, POINT_TOLERANCE, pscale) ) {
332 _state = eInteractStateDraggingPoint0;
333 didSomething = true;
334 } else if ( isNearby(args.penPosition, p1.x, p1.y, POINT_TOLERANCE, pscale) ) {
335 _state = eInteractStateDraggingPoint1;
336 didSomething = true;
337 } else {
338 _state = eInteractStateIdle;
339 }
340
341 _point0DragPos = p0;
342 _point1DragPos = p1;
343 _lastMousePos = args.penPosition;
344
345 if (didSomething) {
346 _interact->requestRedraw();
347 }
348
349 return didSomething;
350 }
351
352 bool
penUp(const PenArgs & args)353 RampInteractHelper::penUp(const PenArgs &args)
354 {
355 const double time = args.time;
356
357 if ( !_interactOpen->getValueAtTime(time) ) {
358 return false;
359 }
360 RampTypeEnum type = (RampTypeEnum)_type->getValueAtTime(time);
361 bool noramp = (type == eRampTypeNone);
362 if (noramp) {
363 return false;
364 }
365
366 bool didSomething = false;
367 const OfxPointD &pscale = args.pixelScale;
368
369 if ( !_interactiveDrag && (_state != eInteractStateIdle) ) {
370 if (_state == eInteractStateDraggingPoint0) {
371 // round newx/y to the closest int, 1/10 int, etc
372 // this make parameter editing easier
373
374 _point0->setValue( fround(_point0DragPos.x, pscale.x), fround(_point0DragPos.y, pscale.y) );
375 didSomething = true;
376 } else if (_state == eInteractStateDraggingPoint1) {
377 _point1->setValue( fround(_point1DragPos.x, pscale.x), fround(_point1DragPos.y, pscale.y) );
378 didSomething = true;
379 }
380 } else if (_state != eInteractStateIdle) {
381 _interact->requestRedraw();
382 }
383
384 _state = eInteractStateIdle;
385
386 return didSomething;
387 }
388
389 /** @brief Called when the interact is loses input focus */
390 void
loseFocus(const FocusArgs &)391 RampInteractHelper::loseFocus(const FocusArgs & /*args*/)
392 {
393 _interactiveDrag = false;
394 _state = eInteractStateIdle;
395 }
396
397 void
ofxsRampDescribeParams(ImageEffectDescriptor & desc,PageParamDescriptor * page,GroupParamDescriptor * group,RampTypeEnum defaultType,bool isOpen,bool oldParams)398 ofxsRampDescribeParams(ImageEffectDescriptor &desc,
399 PageParamDescriptor *page,
400 GroupParamDescriptor *group,
401 RampTypeEnum defaultType,
402 bool isOpen,
403 bool oldParams)
404 {
405 // type
406 {
407 ChoiceParamDescriptor* param = desc.defineChoiceParam(oldParams ? kParamRampTypeOld : kParamRampType);
408 param->setLabelAndHint(kParamRampTypeLabel);
409 assert(param->getNOptions() == eRampTypeLinear);
410 param->appendOption(kParamRampTypeOptionLinear);
411 assert(param->getNOptions() == eRampTypePLinear);
412 param->appendOption(kParamRampTypeOptionPLinear);
413 assert(param->getNOptions() == eRampTypeEaseIn);
414 param->appendOption(kParamRampTypeOptionEaseIn);
415 assert(param->getNOptions() == eRampTypeEaseOut);
416 param->appendOption(kParamRampTypeOptionEaseOut);
417 assert(param->getNOptions() == eRampTypeSmooth);
418 param->appendOption(kParamRampTypeOptionSmooth);
419 assert(param->getNOptions() == eRampTypeNone);
420 param->appendOption(kParamRampTypeOptionNone);
421 param->setDefault(defaultType);
422 param->setAnimates(false);
423 if (group) {
424 param->setParent(*group);
425 }
426 if (page) {
427 page->addChild(*param);
428 }
429 }
430
431 // point0
432 {
433 Double2DParamDescriptor* param = desc.defineDouble2DParam(oldParams ? kParamRampPoint0Old : kParamRampPoint0);
434 param->setLabel(kParamRampPoint0Label);
435 param->setDoubleType(eDoubleTypeXYAbsolute);
436 param->setDefaultCoordinateSystem(eCoordinatesCanonical); // Nuke defaults to Normalized for XY and XYAbsolute!
437 param->setDefault(100., 100.);
438 param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
439 param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
440 //param->setUseHostNativeOverlayHandle(true);
441 if (group) {
442 param->setParent(*group);
443 }
444 if (page) {
445 page->addChild(*param);
446 }
447 }
448
449
450 // color0
451 {
452 RGBAParamDescriptor* param = desc.defineRGBAParam(oldParams ? kParamRampColor0Old : kParamRampColor0);
453 param->setLabel(kParamRampColor0Label);
454 param->setDefault(0, 0, 0, 0);
455 if (group) {
456 param->setParent(*group);
457 }
458 if (page) {
459 page->addChild(*param);
460 }
461 }
462
463 // point1
464 {
465 Double2DParamDescriptor* param = desc.defineDouble2DParam(oldParams ? kParamRampPoint1Old : kParamRampPoint1);
466 param->setLabel(kParamRampPoint1Label);
467 param->setDoubleType(eDoubleTypeXYAbsolute);
468 param->setDefaultCoordinateSystem(eCoordinatesCanonical); // Nuke defaults to Normalized for XY and XYAbsolute!
469 param->setDefault(100., 200.);
470 param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
471 param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
472 //param->setUseHostNativeOverlayHandle(true);
473 if (group) {
474 param->setParent(*group);
475 }
476 if (page) {
477 page->addChild(*param);
478 }
479 }
480
481 // color1
482 {
483 RGBAParamDescriptor* param = desc.defineRGBAParam(oldParams ? kParamRampColor1Old : kParamRampColor1);
484 param->setLabel(kParamRampColor1Label);
485 param->setDefault(1., 1., 1., 1. );
486 if (group) {
487 param->setParent(*group);
488 }
489 if (page) {
490 page->addChild(*param);
491 }
492 }
493
494 // interactOpen
495 {
496 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamRampInteractOpen);
497 param->setLabelAndHint(kParamRampInteractOpenLabel);
498 param->setDefault(isOpen); // open by default
499 param->setIsSecretAndDisabled(true); // secret by default, but this can be changed for specific hosts
500 param->setAnimates(false);
501 if (group) {
502 param->setParent(*group);
503 }
504 if (page) {
505 page->addChild(*param);
506 }
507 }
508
509
510 // interactive
511 {
512 BooleanParamDescriptor* param = desc.defineBooleanParam(oldParams ? kParamRampInteractiveOld : kParamRampInteractive);
513 param->setLabelAndHint(kParamRampInteractiveLabel);
514 param->setEvaluateOnChange(false);
515 if (group) {
516 param->setParent(*group);
517 }
518 if (page) {
519 page->addChild(*param);
520 }
521 }
522 } // ofxsRampDescribeParams
523 } // namespace OFX
524