// Copyright (c) 2018 The Foundry Visionmongers Ltd. All Rights Reserved. static const char* const CLASS = "SimpleBlurCached"; static const char* const HELP = "Does a simple box blur"; #include "DDImage/Iop.h" #include "DDImage/NukeWrapper.h" #include "DDImage/Row.h" #include "DDImage/Tile.h" #include "DDImage/Knobs.h" #include "DDImage/ImageCache.h" #include "DDImage/ChannelSet.h" using namespace std; using namespace DD::Image; class SimpleBlurCached : public Iop { int _param_blur; float _param_constant; bool _isFirstTime; std::vector<float> vec_dst_image; Lock _lock; bool fetchRGBAImage(std::vector<float>&, const int height, const int width); /* Return available RGBA channels */ ChannelSet getNeededChannels() const; public: int maximum_inputs() const { return 1; } int minimum_inputs() const { return 1; } //! Constructor. Initialize user controls to their default values. SimpleBlurCached (Node* node) : Iop (node) { _param_blur = 1; _param_constant = 0; _isFirstTime = true; } ~SimpleBlurCached () {} void _validate(bool); void _request(int x, int y, int r, int t, ChannelMask channels, int count); void knobs(Knob_Callback f); //! This function does all the work. void engine ( int y, int x, int r, ChannelMask channels, Row& out ); //! Return the name of the class. const char* Class() const { return CLASS; } const char* node_help() const { return HELP; } //! Information to the plug-in manager of DDNewImage/Nuke. static const Iop::Description description; }; /*! This is a function that creates an instance of the operator, and is needed for the Iop::Description to work. */ static Iop* SimpleBlurCreate(Node* node) { return new SimpleBlurCached(node); } /*! The Iop::Description is how NUKE knows what the name of the operator is, how to create one, and the menu item to show the user. The menu item may be 0 if you do not want the operator to be visible. */ const Iop::Description SimpleBlurCached::description ( CLASS, "Merge/SimpleBlurCached", SimpleBlurCreate ); ChannelSet SimpleBlurCached::getNeededChannels() const { ChannelSet rgbaChannels = Mask_RGBA; rgbaChannels &= info_.channels(); return rgbaChannels; } void SimpleBlurCached::_validate(bool for_real) { copy_info(); // copy bbox channels etc from input0, which will validate it. _isFirstTime = true; } void SimpleBlurCached::_request(int x, int y, int r, int t, ChannelMask channels, int count) { // Add available RGBA channels so that we can pull them when setting up the cache ChannelSet requiredChannels = channels + getNeededChannels(); input(0)->request(0, 0, info_.w(), info_.h(), requiredChannels, count); } void SimpleBlurCached::knobs(Knob_Callback f) { Int_knob(f, &_param_blur, "size", "size"); Float_knob(f, &_param_constant, "constant", "constant"); } bool SimpleBlurCached::fetchRGBAImage(std::vector<float>& vec, const int height, const int width) { ChannelSet rgbaChannels = getNeededChannels(); Tile tile(input0(), 0, 0, width, height, rgbaChannels); if (aborted()) { return false; } vec.resize(width * height * 4); foreach(z, rgbaChannels) { int channel_skip = 0; if(z == Chan_Red) channel_skip = 0; else if(z == Chan_Green) channel_skip = 1; else if(z == Chan_Blue) channel_skip = 2; else if(z == Chan_Alpha) channel_skip = 3; for (int py = 0; py < height; ++py) { for (int px = 0; px < width; ++px) { const size_t index = (py*width + px)*4 + channel_skip; float value = tile[z][py][px]; vec[index] = value; } } } return true; } void SimpleBlurCached::engine ( int y, int x, int r, ChannelMask channels, Row& row ) { const int width = info_.w(); const int height = info_.h(); // engine calls are multi-threaded so any processing must be locked if (_isFirstTime) { Guard guard(_lock); if (_isFirstTime) { const size_t totalPixels = width * height * 4; std::vector<float> vec_src_image; vec_dst_image.resize(totalPixels); /* try fetch input image */ if (fetchRGBAImage(vec_src_image, height, width) ) { /* now going to see if the input is in the cache */ Image_Cache *i_cache = &Image_Cache::mainCache(); printf("Checking active cache: %d.\n", i_cache->is_active()); size_t desired_read_bytes = (vec_src_image.size())*sizeof(float); DD::Image::Hash hash; hash.reset(); hash.append(input0().hash()); hash.append(_param_blur); printf("Printing hash value: %d.\n", (int)hash.value()); printf("Has file: %d.\n", i_cache->has_file(hash)); /* is our blurred image already in the cache? */ bool cache_read_success = true; if (i_cache->is_active() && i_cache->has_file(hash) ) { DD::Image::ImageCacheReadI* cache_read = i_cache->open( hash ); size_t read_bytes = cache_read->read(vec_dst_image.data(), desired_read_bytes); if (!i_cache->is_read() || read_bytes != desired_read_bytes) { cache_read_success = false; } cache_read->close(); } else { cache_read_success = false; } /* did we get the cached blur image or do we need to calculate it? */ if (!cache_read_success) { /* blur the image, very naively, do not use this code for anything useful! */ float blur_sum; int blur_counter; for(int i=0;i<height;i++) for(int j=0;j<width;j++) for(int c=0;c<4;c++){ blur_sum = 0; blur_counter = 0; for (int u=std::max(0,i-_param_blur); u<=std::min(height-1,i+_param_blur); u++) for (int v=std::max(0,j-_param_blur); v<=std::min(width-1,j+_param_blur); v++){ blur_sum += vec_src_image[(u*width + v)*4 + c]; blur_counter++; } vec_dst_image[(i*width + j)*4 + c] = (blur_counter > 0) ? blur_sum / (float)blur_counter : 0; } /* write result to cache */ DD::Image::ImageCacheWriteI* cache_write = i_cache->create( hash ); size_t desired_write_bytes = (vec_dst_image.size())*sizeof(float); cache_write->write(vec_dst_image.data(), desired_write_bytes); if (!i_cache->is_written()) printf("Error saving blurred image to cache (is written: %d).\n", (int)i_cache->is_written()); cache_write->close(); } /* now just going to add the constant to the image */ for(size_t i = 0; i<vec_dst_image.size(); i++) vec_dst_image[i] = vec_dst_image[i] + _param_constant; } else { /* return on abort */ return; } _isFirstTime = false; } } // Copy unprocessed channels through to the output // NOTE: this must happen first as .get(...) changes row const ChannelSet rgbaChannels = getNeededChannels(); ChannelSet enginePass = channels; enginePass -= rgbaChannels; if (enginePass) { input0().get( y, x, r, enginePass, row ); } // an example to set the output data, per image buffer ChannelSet engineOut = channels; engineOut &= rgbaChannels; if (engineOut) { foreach (z, channels) { float * row1 = row.writable(z); int channel_skip = 0; if(z == Chan_Red) channel_skip = 0; else if(z == Chan_Green) channel_skip = 1; else if(z == Chan_Blue) channel_skip = 2; else if(z == Chan_Alpha) channel_skip = 3; for(int xx=x; xx<r; xx++) row1[xx] = vec_dst_image[y*4*width + (xx-x)*4 + channel_skip]; } } }