// AppendClip.C // Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved. #include "DDImage/Iop.h" #include "DDImage/Row.h" #include "DDImage/Knobs.h" #include "DDImage/Knob.h" #include "DDImage/DDMath.h" #include "DDImage/MetaData.h" #include #include //#include "DoubleKnob.h" using namespace DD::Image; static const char* const CLASS = "AppendClip"; static const char* const HELP = "Append one clip to another."; class AppendClip : public Iop { int fadeIn, fadeOut, crossDis; int firstFrame; int thisFrame; int input0; int input1; float weight0; float weight1; bool _takeMetaDataFromFirst; protected: void _validate(bool); void _request(int, int, int, int, ChannelMask, int); void engine(int y, int x, int r, ChannelMask, Row &); const OutputContext& inputContext(int, int, OutputContext&) const; public: AppendClip(Node* node) : Iop(node) { fadeIn = fadeOut = crossDis = 0; firstFrame = 1; // init so uses_input sort of works: input0 = input1 = 0; weight0 = 1; weight1 = 0; _takeMetaDataFromFirst = true; } int minimum_inputs() const { return 1; } int maximum_inputs() const { return 10000; } float uses_input(int i) const; virtual void knobs(Knob_Callback); const char* Class() const { return CLASS; } const char* node_help() const { return HELP; } static Iop::Description d; void setOutputContext(const OutputContext& c) { Iop::setOutputContext(c); thisFrame = int(rint(outputContext().frame())); } void append(Hash& hash) { hash.append(thisFrame); } virtual const MetaData::Bundle& _fetchMetaData(const char* keyname); // Disconnected input other than first one get a null: Op* default_input(int i) const { return i ? 0 : Iop::default_input(i); } }; static Iop* build(Node* node) { return new AppendClip(node); } Iop::Description AppendClip::d(CLASS, "Image/Clip/Append", build); float AppendClip::uses_input(int i) const { if (i == input0 && weight0 > .01f) return weight0; if (i == input1 && weight1 > .01f) return weight1; return .01f; } void AppendClip::knobs(Knob_Callback f) { Int_knob( f, &fadeIn, "fadeIn", "Fade In"); Text_knob( f, "frames"); Int_knob( f, &fadeOut, "fadeOut", "Fade Out"); Text_knob( f, "frames"); Int_knob( f, &crossDis, "dissolve", "Cross Dissolve"); Text_knob( f, "frames"); Int_knob( f, &firstFrame, "firstFrame", "First Frame"); SetFlags(f, Knob::EARLY_STORE); Knob* k = Int_knob(f, 0, "lastFrame", "Last Frame"); if (k) k->disable(); Bool_knob(f, &_takeMetaDataFromFirst, "meta_from_first", "take metadata from first clip"); } const OutputContext& AppendClip::inputContext(int in, int, OutputContext& context) const { int f = thisFrame; f -= firstFrame; for (int i = 0; i < in; i++) { Iop* iop = (Iop*)(input_op(i)); if (!iop) continue; iop->validate(false); f -= (iop->last_frame() - iop->first_frame() + 1) - crossDis; } Iop* iop = (Iop*)(input_op(in)); if (iop) { iop->validate(false); f += iop->first_frame(); } context = outputContext(); context.setFrame(f); return context; } void AppendClip::_validate(bool for_real) { // figure out total length: int f, i; f = firstFrame; input0 = -1; for (i = 0; i < inputs(); i++) { Iop* iop = (Iop*)(input(i)); if (!iop) continue; iop->validate(for_real); int g = f + iop->last_frame() - iop->first_frame() + 1; if (input0 < 0 && thisFrame < g) { input0 = i; for (input1 = i + 1; input1 < inputs() && !input(input1); input1++) ; weight0 = 1; weight1 = 0; if (input1 < inputs() && thisFrame >= g - crossDis) { float w = float(g - thisFrame) / (crossDis + 1); w = (3 - 2 * w) * w * w; weight0 = w; weight1 = 1 - w; } } f = g - crossDis; } int lastFrame = f + crossDis - 1; knob("lastFrame")->set_value(lastFrame); if (input0 < 0) { // after the last frame input0 = input1 = inputs() - 1; weight0 = 1; weight1 = 0; } if (fadeIn && thisFrame < firstFrame + fadeIn) { float w = float(thisFrame - firstFrame + 1) / (fadeIn + 1); if (w < 0) w = 0; else w = w * w; weight0 *= w; weight1 *= w; } if (fadeOut && thisFrame > lastFrame - fadeOut) { float w = float(lastFrame - thisFrame + 1) / (fadeOut + 1); if (w < 0) w = 0; else w = w * w; weight0 *= w; weight1 *= w; } // fix problems with null inputs: if (!input(input0)) input0 = 0; if (input1 >= inputs() || !input(input1)) input1 = 0; copy_info(input0); if (weight0 == 1) { set_out_channels(Mask_None, input0); } else { set_out_channels(Mask_All, input0); if (weight1) merge_info(input1); } info_.first_frame(firstFrame); info_.last_frame(lastFrame); } const MetaData::Bundle& AppendClip::_fetchMetaData(const char* keyname) { if (!_takeMetaDataFromFirst) { if (weight0 > weight1 && input0 != -1) return input(input0)->fetchMetaData(keyname); else if (input1 != -1) return input(input1)->fetchMetaData(keyname); else return DD::Image::MetaData::emptyBundle(); } return Iop::_fetchMetaData(keyname); } void AppendClip::_request(int x, int y, int r, int t, ChannelMask channels, int count) { if (weight0) input(input0)->request(x, y, r, t, channels, count); if (weight1) input(input1)->request(x, y, r, t, channels, count); } void AppendClip::engine(int y, int x, int r, ChannelMask channels, Row& out) { if (!weight0) { out.erase(channels); return; } input(input0)->get(y, x, r, channels, out); if (weight1) { Row in(x, r); input(input1)->get(y, x, r, channels, in); foreach (z, channels) { const float* A = out[z] + x; const float* B = in[z] + x; float* C = out.writable(z) + x; float* E = C + (r - x); while (C < E) *C++ = *A++ *weight0 + *B++ *weight1; } } else if (weight0 < 1) { foreach (z, channels) { const float* A = out[z] + x; float* C = out.writable(z) + x; float* E = C + (r - x); while (C < E) *C++ = *A++ *weight0; } } }