// Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved. /*! \class CornerPin2DIop Allows four points to fit an image to another in translation, rotaion and scale */ #include "DDImage/Convolve.h" #include "DDImage/DDWindows.h" #include "DDImage/Transform.h" #include "DDImage/Format.h" #include "DDImage/ViewerContext.h" #include "DDImage/gl.h" #include "DDImage/Knobs.h" using namespace DD::Image; static const char* const CLASS = "CornerPin2D"; static const char* const HELP = "Allows four points to fit an image to another in translation, rotation and scale."; struct xyStruct { double x, y; bool enable; }; class CornerPin2D : public Transform { xyStruct sc[4]; // source coordinates xyStruct dc[4]; // destination coordinates bool _inverted; float _extraMatrix[16]; ConvolveArray _extraMatrixArray; // map 0,0,1,1 square to the four corners: void setCornerPinMatrix(xyStruct c[4], Matrix4& q) { q.makeIdentity(); double dx3 = (c[0].x - c[1].x) + (c[2].x - c[3].x); double dy3 = (c[0].y - c[1].y) + (c[2].y - c[3].y); if (dx3 == 0 && dy3 == 0) { q.a00 = c[1].x - c[0].x; q.a01 = c[2].x - c[1].x; q.a03 = c[0].x; q.a10 = c[1].y - c[0].y; q.a11 = c[2].y - c[1].y; q.a13 = c[0].y; } else { double dx1 = c[1].x - c[2].x; double dy1 = c[1].y - c[2].y; double dx2 = c[3].x - c[2].x; double dy2 = c[3].y - c[2].y; double z = (dx1 * dy2 - dx2 * dy1); q.a30 = (dx3 * dy2 - dx2 * dy3) / z; q.a31 = (dx1 * dy3 - dx3 * dy1) / z; q.a00 = (c[1].x - c[0].x) + q.a30 * c[1].x; q.a01 = (c[3].x - c[0].x) + q.a31 * c[3].x; q.a03 = c[0].x; q.a10 = (c[1].y - c[0].y) + q.a30 * c[1].y; q.a11 = (c[3].y - c[0].y) + q.a31 * c[3].y; q.a13 = c[0].y; } } void setMatrix(const xyStruct sc[4], const xyStruct dc[4], Matrix4& matrix) { // pack the enabled points together into start of array: xyStruct sc2[4], dc2[4]; int ix, cnt = 0; for (ix = 0; ix < 4; ix++) { if (sc[ix].enable) { // point is enabled sc2[cnt] = sc[ix]; dc2[cnt] = dc[ix]; cnt++; } } OutputContext oc = outputContext(); Matrix4 fullToProxy, proxyToFull; fullToProxy.makeIdentity(); proxyToFull.makeIdentity(); oc.to_proxy(fullToProxy); oc.from_proxy(proxyToFull); proxyToFull.transpose(); Matrix4 world(_extraMatrix); world.transpose(); world = fullToProxy * world * proxyToFull; matrix.makeIdentity(); if (cnt == 0) { matrix = world; } else if (cnt == 1) { //translate by a single point; Matrix4 t; t.translation(dc2[0].x - sc2[0].x, dc2[0].y - sc2[0].y); matrix = world * t; } else { // copy the last point to the unenabled points to get 4 of them: if (cnt == 2) { //create a third point sc2[2].x = sc2[0].x - (sc2[1].y - sc2[0].y); sc2[2].y = sc2[0].y + (sc2[1].x - sc2[0].x); dc2[2].x = dc2[0].x - (dc2[1].y - dc2[0].y); dc2[2].y = dc2[0].y + (dc2[1].x - dc2[0].x); ++cnt; } if (cnt == 3) { //create a fourth point sc2[3].x = sc2[1].x + (sc2[2].x - sc2[0].x); sc2[3].y = sc2[1].y + (sc2[2].y - sc2[0].y); dc2[3].x = dc2[1].x + (dc2[2].x - dc2[0].x); dc2[3].y = dc2[1].y + (dc2[2].y - dc2[0].y); } //transform from source coordinates to a 0,0,1,1, square Matrix4 p, q; setCornerPinMatrix(sc2, p); setCornerPinMatrix(dc2, q); matrix = world * (q * p.inverse()); } if (_inverted) matrix = matrix.inverse(); } public: void _validate(bool for_real) override { setMatrix(sc, dc, *matrix()); Transform::_validate(for_real); } void matrixAt(const OutputContext& context, Matrix4& matrix) override { xyStruct sc[4]; xyStruct dc[4]; Hash hash; knob("to1")->store(DoublePtr, &dc[0].x, hash, context); knob("enable1")->store(BoolPtr, &sc[0].enable, hash, context); knob("to2")->store(DoublePtr, &dc[1].x, hash, context); knob("enable2")->store(BoolPtr, &sc[1].enable, hash, context); knob("to3")->store(DoublePtr, &dc[2].x, hash, context); knob("enable3")->store(BoolPtr, &sc[2].enable, hash, context); knob("to4")->store(DoublePtr, &dc[3].x, hash, context); knob("enable4")->store(BoolPtr, &sc[3].enable, hash, context); knob("from1")->store(DoublePtr, &sc[0].x, hash, context); knob("from2")->store(DoublePtr, &sc[1].x, hash, context); knob("from3")->store(DoublePtr, &sc[2].x, hash, context); knob("from4")->store(DoublePtr, &sc[3].x, hash, context); knob("invert")->store(BoolPtr, &_inverted, hash, context); setMatrix(sc, dc, matrix); } const char* Class() const override { return CLASS; } const char* node_help() const override { return HELP; } static const Description desc; CornerPin2D(Node* node) : Transform(node), _inverted(false) { // initialize the matrix for ( int i = 0; i < 16; i++ ) { _extraMatrix[i] = 0.0f; } _extraMatrix[0] = _extraMatrix[5] = _extraMatrix[10] = _extraMatrix[15] = 1.0f; _extraMatrixArray.set(4, 4, _extraMatrix); const Format& format = input_format(); sc[0].x = sc[3].x = format.x(); sc[1].x = sc[2].x = format.r(); sc[0].y = sc[1].y = format.y(); sc[2].y = sc[3].y = format.t(); dc[0].x = dc[3].x = sc[0].x; //(format.x()*3+format.r())/4; dc[1].x = dc[2].x = sc[1].x; //(format.x()+format.r()*3)/4; dc[0].y = dc[1].y = sc[0].y; //(format.y()*3+format.t())/4; dc[2].y = dc[3].y = sc[2].y; //(format.y()+format.t()*3)/4; sc[0].enable = sc[1].enable = sc[2].enable = sc[3].enable = true; dc[0].enable = dc[1].enable = dc[2].enable = dc[3].enable = true; } void knobs(Knob_Callback f) override { XY_knob(f, &dc[0].x, "to1"); SetFlags(f, DD::Image::Knob::ALWAYS_SAVE); Bool_knob(f, &sc[0].enable, "enable1"); XY_knob(f, &dc[1].x, "to2"); SetFlags(f, DD::Image::Knob::ALWAYS_SAVE); Bool_knob(f, &sc[1].enable, "enable2"); XY_knob(f, &dc[2].x, "to3"); SetFlags(f, DD::Image::Knob::ALWAYS_SAVE); Bool_knob(f, &sc[2].enable, "enable3"); XY_knob(f, &dc[3].x, "to4"); SetFlags(f, DD::Image::Knob::ALWAYS_SAVE); Bool_knob(f, &sc[3].enable, "enable4"); PyScript_knob(f, "nuke.thisNode().knob('to1').fromScript(nuke.thisNode().knob('from1').toScript())\n" "nuke.thisNode().knob('to2').fromScript(nuke.thisNode().knob('from2').toScript())\n" "nuke.thisNode().knob('to3').fromScript(nuke.thisNode().knob('from3').toScript())\n" "nuke.thisNode().knob('to4').fromScript(nuke.thisNode().knob('from4').toScript())\n" , "copy_from", "Copy 'from'"); Tooltip(f, "Take the contents from the 'from' knobs and put them in the 'to' knobs."); SetFlags(f, DD::Image::Knob::STARTLINE); BeginClosedGroup(f, "extra matrix"); DD::Image::Knob *k = Array_knob(f, &_extraMatrixArray, 4, 4, "transform_matrix", "", true); k->set_flag(Knob::NO_CURVE_EDITOR | Knob::NO_MULTIVIEW); Tooltip(f, "This matrix gets concatenated against the transform defined by the other knobs."); EndGroup(f); Bool_knob(f, &_inverted, "invert", "invert"); SetFlags(f, DD::Image::Knob::ALWAYS_SAVE | DD::Image::Knob::STARTLINE); Transform::knobs(f); Tab_knob(f, 0, "From"); XY_knob(f, &sc[0].x, "from1"); SetFlags(f, DD::Image::Knob::ALWAYS_SAVE); XY_knob(f, &sc[1].x, "from2"); SetFlags(f, DD::Image::Knob::ALWAYS_SAVE); XY_knob(f, &sc[2].x, "from3"); SetFlags(f, DD::Image::Knob::ALWAYS_SAVE); XY_knob(f, &sc[3].x, "from4"); SetFlags(f, DD::Image::Knob::ALWAYS_SAVE); PyScript_knob(f, "f = None\n" "try:\n" " f = nuke.thisNode().input(0).format()\n" "except:\n" " f = nuke.root().format()\n" "f1 = nuke.thisNode().knob('from1').fromScript(\"0 0\")\n" "f2 = nuke.thisNode().knob('from2').fromScript(str(f.width()) + \" 0\")\n" "f3 = nuke.thisNode().knob('from3').fromScript(str(f.width()) + \" \" + str(f.height()))\n" "f4 = nuke.thisNode().knob('from4').fromScript(\"0 \" + str(f.height()))\n" , "set_to_input", "Set to input"); SetFlags(f, DD::Image::Knob::STARTLINE); ClearFlags(f, Knob::ENDLINE); Tooltip(f, "Set the size of the from knobs to the input format."); PyScript_knob(f, "nuke.thisNode().knob('from1').fromScript(nuke.thisNode().knob('to1').toScript())\n" "nuke.thisNode().knob('from2').fromScript(nuke.thisNode().knob('to2').toScript())\n" "nuke.thisNode().knob('from3').fromScript(nuke.thisNode().knob('to3').toScript())\n" "nuke.thisNode().knob('from4').fromScript(nuke.thisNode().knob('to4').toScript())\n" , "copy_to", "Copy 'to'"); Tooltip(f, "Take the contents from the 'to' knobs and put them in the 'from' knobs."); ClearFlags(f, DD::Image::Knob::STARTLINE); } /*! Draw the outlines of the source and destination quadrilaterals. */ void draw_handle(ViewerContext* ctx) override { if (ctx->draw_lines()) { const Info& i = concat_input_->info(); glColor(ctx->node_color()); gl_rectangle((float)i.x(), (float)i.y(), (float)i.r(), (float)i.t()); } Transform::draw_handle(ctx); } }; static Iop* build(Node* node) { return new CornerPin2D(node); } const Iop::Description CornerPin2D::desc(CLASS, "Transform/CornerPin2D", build); // end of CornerPin2D.C