// Copyright (c) 2025 The Foundry Visionmongers Ltd. All Rights Reserved. #include "DDImage/DDWindows.h" #include "DDImage/Knob.h" #include "DDImage/ViewerContext.h" #include "DDImage/Iop.h" #include "DDImage/gl.h" #include //! The Dust_knob() will update an array of these objects. struct DustPoints { int size; struct Point { double x, y, w, h, source_x, source_y; }* points; const struct Point& operator[](int n) { return points[n]; } DustPoints() { points = nullptr; size = 0; } }; //! Special knob type used by DustBust node. class Dust_Knob : public DD::Image::Knob { private: //! A point stored in the knob, rather than in the Op. In the Op the //! points for other frames have been removed from the list: struct KnobPoint { int frame; double x, y, w, h, dx, dy; void clear() { w = h = 5; dx = dy = 0; } void append(DD::Image::Hash& hash) { hash.append(&x, 6 * sizeof(double)); } }; std::vector points; static bool drag_point(DD::Image::ViewerContext*, DD::Image::Knob*, int); static bool drag_corner(DD::Image::ViewerContext*, DD::Image::Knob*, int); static bool drag_dxdy(DD::Image::ViewerContext*, DD::Image::Knob*, int); static bool add_point_cb(DD::Image::ViewerContext*, DD::Image::Knob*, int); int frame_; unsigned first; // index of first point at this frame unsigned last; // index of last point at this frame public: const char* Class() const override { return "Dust_Knob"; } Dust_Knob(DD::Image::Knob_Closure* kc, DustPoints* p, const char* n) : DD::Image::Knob(kc, n) { frame_ = first = last = 0; } void convert(unsigned i, DustPoints::Point& q); ~Dust_Knob() override {} void store(DD::Image::StoreType, void*, DD::Image::Hash &, const DD::Image::OutputContext &) override; void append(DD::Image::Hash&, const DD::Image::OutputContext*) override; bool from_script(const char* arg) override; void to_script(std::ostream& o, const DD::Image::OutputContext*, bool quote) const override; bool not_default() const override { return points.size() != 0; } bool gotoContext(const DD::Image::OutputContext&); bool build_handle(DD::Image::ViewerContext*) override; void draw_handle(DD::Image::ViewerContext*) override; }; bool Dust_Knob::gotoContext(const DD::Image::OutputContext& context) { const int i = fast_rint(context.frame()); frame_ = i; unsigned a = 0; while (a < points.size()) { if (points[a].frame >= i) { break; } ++a; } unsigned b = a; while (b < points.size()) { if (points[b].frame > i) { break; } ++b; } if (first == a && last == b) { return false; } first = a; last = b; return true; } // Convert handles proxy conversions, and changes the coordinates from // the centered x,y to having x,y in the corner of the rectangle, and // changes the source to an absolute location and also at the corner of // the rectangle. void Dust_Knob::convert(unsigned i, DustPoints::Point& q) { const DD::Image::OutputContext& oc = uiContext(); const KnobPoint& p = points[i]; q.x = p.x; q.y = p.y; q.w = p.w; q.h = p.h; q.source_x = p.x - p.dx; q.source_y = p.y - p.dy; oc.to_proxy_xy(q.x, q.y); oc.to_proxy_area(q.w, q.h); oc.to_proxy_wh(q.source_x, q.source_y); } void Dust_Knob::store(DD::Image::StoreType t, void* dest, DD::Image::Hash& hash, const DD::Image::OutputContext& context) { gotoContext(context); DustPoints* p = static_cast(dest); if (last <= first) { if (p->size) { delete[] p->points; p->points = nullptr; p->size = 0; } return; } const unsigned n = last - first; if (n != (unsigned)(p->size)) { delete[] p->points; p->points = new DustPoints::Point[n]; p->size = n; } for (unsigned i = 0; i < n; ++i) { DustPoints::Point q; convert(first + i, q); DustPoints::Point& r = p->points[i]; r = q; } if (last <= first) { hash.append(false); } else { for (unsigned i = first; i < last; ++i) { points[i].append(hash); } } } void Dust_Knob::append(DD::Image::Hash& hash, const DD::Image::OutputContext* context) { if (context) { gotoContext(*context); } if (last <= first) { hash.append(false); } else { for (unsigned i = first; i < last; ++i) { points[i].append(hash); } } } bool Dust_Knob::drag_point(DD::Image::ViewerContext* ctx, DD::Image::Knob* v, int n) { Dust_Knob* knob = static_cast(v); if (ctx->event() == DD::Image::KEY && ctx->key() == DD::Image::DeleteKey) { knob->points.erase(knob->points.begin() + n); knob->last--; knob->changed(); return true; } if (ctx->event() != DD::Image::DRAG) { return false; } knob->points[n].x = ctx->x(); knob->points[n].y = ctx->y(); knob->changed(); return true; } bool Dust_Knob::add_point_cb(DD::Image::ViewerContext* ctx, DD::Image::Knob* v, int n) { Dust_Knob* knob = static_cast(v); if (ctx->event() == DD::Image::PUSH) { knob->points.insert(knob->points.begin() + n, KnobPoint()); knob->points[n].frame = knob->frame_; knob->points[n].clear(); ++knob->last; } else { if (ctx->event() != DD::Image::DRAG) { return false; } } knob->points[n].x = ctx->x(); knob->points[n].y = ctx->y(); knob->changed(); return true; } bool Dust_Knob::drag_corner(DD::Image::ViewerContext* ctx, DD::Image::Knob* v, int n) { if (ctx->event() != DD::Image::DRAG) { return false; } Dust_Knob* knob = static_cast(v); const double x = ctx->x(); const double y = ctx->y(); knob->points[n].w = fabs(x - knob->points[n].x); knob->points[n].h = fabs(y - knob->points[n].y); knob->changed(); return true; } bool Dust_Knob::drag_dxdy(DD::Image::ViewerContext* ctx, DD::Image::Knob* v, int n) { if (ctx->event() != DD::Image::DRAG) { return false; } Dust_Knob* knob = static_cast(v); const double x = ctx->x(); const double y = ctx->y(); knob->points[n].dx = knob->points[n].x - x; knob->points[n].dy = knob->points[n].y - y; knob->changed(); return true; } bool Dust_Knob::build_handle(DD::Image::ViewerContext* ctx) { return ctx->transform_mode() == DD::Image::VIEWER_2D; } void Dust_Knob::draw_handle(DD::Image::ViewerContext* ctx) { if (!ctx->draw_knobs()) { return; } if (ctx->event() == DD::Image::PUSH) { new_undo(); } // Translate so we can draw in full-size space: DD::Image::Matrix4 saved_matrix(ctx->modelmatrix); uiContext().to_proxy(ctx->modelmatrix); glLoadMatrixf(ctx->modelmatrix.array()); gotoContext(uiContext()); bool ctrl = false; if (ctx->event() == DD::Image::PUSH) { ctrl = ctx->state(DD::Image::CTRL | DD::Image::ALT | DD::Image::META); if (ctrl) { make_handle(ANYWHERE, ctx, add_point_cb, last, 0, 0); } } for (unsigned i = first; i < last; ++i) { KnobPoint& p = points[i]; if (ctx->event() == DD::Image::PUSH) { make_handle(ctx, drag_corner, i, p.x - p.w, p.y - p.h); make_handle(ctx, drag_corner, i, p.x + p.w, p.y - p.h); make_handle(ctx, drag_corner, i, p.x + p.w, p.y + p.h); make_handle(ctx, drag_corner, i, p.x - p.w, p.y + p.h); if (ctrl || fabs(p.dx) > p.w || fabs(p.dy) > p.h) { make_handle(ctx, drag_dxdy, i, p.x - p.dx, p.y - p.dy); } if (!ctrl || fabs(p.dx) > p.w || fabs(p.dy) > p.h) { make_handle(SELECTABLE, ctx, drag_point, i, p.x, p.y); } } else { if (ctx->event() == DD::Image::DRAW_LINES && is_selected(ctx, drag_point, i)) { DD::Image::glColor(ctx->selected_color()); } else { DD::Image::glColor(ctx->node_color()); } glBegin(GL_LINE_LOOP); glVertex2f(p.x + p.w, p.y + p.h); glVertex2f(p.x + p.w, p.y - p.h); glVertex2f(p.x - p.w, p.y - p.h); glVertex2f(p.x - p.w, p.y + p.h); glEnd(); glBegin(GL_LINE_STRIP); glVertex2f(p.x, p.y); glVertex2f(p.x - p.dx, p.y - p.dy); glEnd(); if (ctx->zoom() >= 1) { make_handle(SELECTABLE, ctx, drag_point, i, p.x, p.y); } } } ctx->modelmatrix = saved_matrix; glLoadMatrixf(ctx->modelmatrix.array()); } bool Dust_Knob::from_script(const char* arg) { Script_List list(arg); if (list.error()) { return false; } const int n = list.size(); points.clear(); points.reserve(n); for (int i = 0; i < n; ++i) { Script_List list2(list[i]); if (list2.error()) { return false; } const int m = list2.size(); if (m < 3) { error("point %d has %d numbers, should have 3", n, m); return false; } points.insert(points.begin() + i, KnobPoint()); double d; if (!to_double(list2[0], d)) { return false; } points[i].frame = static_cast(d); if (!to_double(list2[1], d)) { return false; } points[i].x = d; if (!to_double(list2[2], d)) { return false; } points[i].y = d; points[i].clear(); if (m > 3) { if (!to_double(list2[3], d)) { return false; } points[i].w = d; } if (m > 4) { if (!to_double(list2[4], d)) { return false; } points[i].h = d; } if (m > 5) { if (!to_double(list2[5], d)) { return false; } points[i].dx = d; } if (m > 6) { if (!to_double(list2[6], d)) { return false; } points[i].dy = d; } } changed(); return true; } void Dust_Knob::to_script(std::ostream& o, const DD::Image::OutputContext*, bool quote) const { if (quote) { o << "{\n"; } for (unsigned i = 0; i < points.size(); ++i) { const KnobPoint& p = points[i]; if (quote) { o << indent() << " "; } o << '{' << p.frame << ' ' << p.x << ' ' << p.y << ' ' << p.w << ' ' << p.h << ' ' << p.dx << ' ' << p.dy << "}\n"; } if (quote) { o << indent() << '}'; } }