ronantakizawa/codereview-bench huggingface dataset:
Repository: sz3/libcimbar
File: src/lib/fountain/concurrent_fountain_decoder_sink.h (C/C++)

Code before:
#pragma once

#include "fountain_decoder_sink.h"

#include "concurrentqueue/concurrentqueue.h"
#include <mutex>

class concurrent_fountain_decoder_sink
{
public:
	concurrent_fountain_decoder_sink(std::string data_dir, unsigned chunk_size)
		: _decoder(data_dir, chunk_size)
	{
	}

	unsigned chunk_size() const
	{
		return _decoder.chunk_size();
	}

	unsigned num_streams() const
	{
		return _decoder.num_streams();
	}

	unsigned num_done() const
	{
		return _decoder.num_done();
	}

	void process()
	{
		if (_mutex.try_lock())
		{
			std::string buff;
			while (_backlog.try_dequeue(buff))
				_decoder << buff;
			_mutex.unlock();
		}
	}


Review comment (suggestion):
I may use an interface, or [something like one](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) to enforce that the methods we need here actually exist...

There are a few templated types lurking in the code that would benefit from a compile-time interface check.

Code after:
#pragma once

#include "fountain_decoder_sink.h"

#include "concurrentqueue/concurrentqueue.h"
#include <mutex>

class concurrent_fountain_decoder_sink
{
public:
	concurrent_fountain_decoder_sink(std::string data_dir, unsigned chunk_size)
		: _decoder(data_dir, chunk_size)
	{
	}

	bool good() const
	{
		return true;
	}

	unsigned chunk_size() const
	{
		return _decoder.chunk_size();
	}

	unsigned num_streams() const
	{
		return _decoder.num_streams();
	}

	unsigned num_done() const
	{
		return _decoder.num_done();
	}

	void process()
	{
		if (_mutex.try_lock())
		{
			std::string buff;
			while (_backlog.try_dequeue(buff))
Repository: sz3/libcimbar
File: src/lib/fountain/concurrent_fountain_decoder_sink.h (C/C++)

Code before:
		return _decoder.num_streams();
	}

	unsigned num_done() const
	{
		return _decoder.num_done();
	}

	std::vector<std::string> get_done() const
	{
		std::lock_guard<std::mutex> lock(_readMutex);
		return _done;
	}

	std::vector<double> get_progress() const
	{
		std::lock_guard<std::mutex> lock(_readMutex);
		return _progress;
	}

	void update_status()
	{
		// called under the writeMutex -- don't hold this for long!
		std::lock_guard<std::mutex> lock(_readMutex);
		_done = _decoder.get_done();
		_progress = _decoder.get_progress();
	}

	void process()
	{
		if (_writeMutex.try_lock())
		{
			// maybe a 2nd mutex for retrieving _decoder.has_file()??
			std::string buff;
			while (_backlog.try_dequeue(buff))
				_decoder << buff;

			update_status();
			_writeMutex.unlock();
		}
	}

	bool write(const char* data, unsigned length)
	{
		std::string buffer(data, length);
		operator<<(buffer);
		return true;
	}

	concurrent_fountain_decoder_sink& operator<<(const std::string& buffer)
	{

Review comment (suggestion):
We could retrieve these from the streams on demand instead of caching them -- but for the moment, I'm trying to keep the read-side (the const methods) as braindead simple as possible. We want `process()` to be running unopposed.

Code after:
		return _decoder.num_streams();
	}

	unsigned num_done() const
	{
		return _decoder.num_done();
	}

	std::vector<std::string> get_done() const
	{
		std::lock_guard<std::mutex> lock(_readMutex);
		return _done;
	}

	std::vector<double> get_progress() const
	{
		std::lock_guard<std::mutex> lock(_readMutex);
		return _progress;
	}

	void update_status()
	{
		// we call this under the writeMutex+readMutex. The `const`s are only under readMutex.
		// just thought you ought to know
		std::lock_guard<std::mutex> lock(_readMutex);
		_done = _decoder.get_done();
		_progress = _decoder.get_progress();
	}

	void process()
	{
		if (_writeMutex.try_lock())
		{
			// maybe a 2nd mutex for retrieving _decoder.has_file()??
			std::string buff;
			while (_backlog.try_dequeue(buff))
				_decoder << buff;

			update_status();
			_writeMutex.unlock();
		}
	}

	bool write(const char* data, unsigned length)
	{
		std::string buffer(data, length);
		operator<<(buffer);
		return true;
	}

	concurrent_fountain_decoder_sink& operator<<(const std::string& buffer)
Repository: sz3/libcimbar
File: src/lib/fountain/FountainMetadata.h (C/C++)

Code before:
/* This code is subject to the terms of the Mozilla Public License, v.2.0. http://mozilla.org/MPL/2.0/. */
#pragma once

class FountainMetadata
{
public:
	static const unsigned md_size = 4;

public:
	FountainMetadata(uint64_t id)
	    : _data(id)
	{
	}

	FountainMetadata(const char* buff, unsigned len)
	{
		if (len > md_size)
			len = md_size;
		std::copy(buff, buff+len, data());
	}

	FountainMetadata(uint8_t encode_id, unsigned size)
	{
		uint8_t* d = data();
		d[0] = encode_id;
		d[1] = (size >> 16) & 0xFF;
		d[2] = (size >> 8) & 0xFF;
		d[3] = size & 0xFF;
	}

	unsigned id() const
	{
		return _data;
	}

	uint8_t encode_id() const

Review comment (suggestion):
We steal the first bit of the FountainMetadata for an extra bit of size, allowing us to address 2^25 (~33MB). This seems like a reasonable limit to me.

(notably, the fountain code implementation I'm using caps at `2^16` blocks...)

Code after:
/* This code is subject to the terms of the Mozilla Public License, v.2.0. http://mozilla.org/MPL/2.0/. */
#pragma once

class FountainMetadata
{
public:
	static const unsigned md_size = 4;

	static void to_uint8_arr(uint8_t encode_id, unsigned size, uint8_t* arr)
	{
		arr[0] = (encode_id & 0x7F) | ((size >> 17) & 0x80);
		arr[1] = (size >> 16) & 0xFF;
		arr[2] = (size >> 8) & 0xFF;
		arr[3] = size & 0xFF;
	}

public:
	FountainMetadata(uint64_t id)
	    : _data(id)
	{
	}

	FountainMetadata(const char* buff, unsigned len)
	{
		if (len > md_size)
			len = md_size;
		std::copy(buff, buff+len, data());
	}

	FountainMetadata(uint8_t encode_id, unsigned size)
	{
		uint8_t* d = data();
		to_uint8_arr(encode_id, size, d);
	}

	unsigned id() const
Repository: sz3/libcimbar
File: src/lib/gui/mat_to_gl.h (C/C++)

Code before:
namespace cimbar {
namespace mat_to_gl {

	GLuint create_gl_texture(const cv::Mat& mat)
	{
		GLuint texid;
		glGenTextures(1, &texid);
		glBindTexture(GL_TEXTURE_2D, texid);

		// not sure whether we need this or not
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		GLenum format = GL_RGB;
		switch (mat.channels())
		{
			case 1:
				format = GL_LUMINANCE;
				break;
			case 4:
				format = GL_RGBA;
				break;
			default:
				;
		}
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mat.cols, mat.rows, 0, format, GL_UNSIGNED_BYTE, mat.data);
		glBindTexture(GL_TEXTURE_2D, 0); // clean up
		return texid;
	}

}
}


Review comment (suggestion):
`glTexImage2D` seems finicky (at least in webGL, where we care), so we're focusing on the 24-bit RGB case that our inputs will theoretically match...

Code after:
namespace cimbar {
namespace mat_to_gl {

	void load_gl_texture(GLuint texid, const cv::Mat& mat)
	{
		glBindTexture(GL_TEXTURE_2D, texid);

		// not sure whether we need this or not
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		GLenum format = GL_RGB;
		switch (mat.channels())
		{
			case 1:
				format = GL_LUMINANCE;
				break;
			case 4:
				format = GL_RGBA;
				break;
			default:
				;
		}
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, mat.cols, mat.rows, 0, format, GL_UNSIGNED_BYTE, mat.data);
		glBindTexture(GL_TEXTURE_2D, 0); // clean up
	}

}
}
Repository: sz3/libcimbar
File: src/lib/gui/window_glfw.h (C/C++)

Code before:
	~window_glfw()
	{
		if (_w)
			glfwDestroyWindow(_w);
		glfwTerminate();
	}

	bool is_good() const
	{
		return _good;
	}

	bool should_close() const
	{
		return glfwWindowShouldClose(_w);
	}

	void show(const cv::Mat& img, unsigned delay)
	{
		std::chrono::time_point start = std::chrono::high_resolution_clock::now();

		if (_display)
		{
			cv::Mat rgb;
			cv::cvtColor(img, rgb, cv::COLOR_BGR2RGBA);
			GLuint texture = cimbar::mat_to_gl::create_gl_texture(rgb);
			_display->draw(texture);
			glDeleteTextures(1, &texture);

			swap();
			poll();
		}

		unsigned millis = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start).count();
		if (delay > millis)
			std::this_thread::sleep_for(std::chrono::milliseconds(delay-millis));
	}

protected:
	void init_opengl(int width, int height)
	{
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glViewport(0, 0, width, height);
	}

	void swap()
	{
		// show next frame
		glfwSwapBuffers(_w);
	}


Review comment (suggestion):
no `cvtColor` -- we're hoping for RGB by default. There will be a followup PR...

Also: now calling `glGenTextures` in the constructor, and reusing it.

Code after:
	~window_glfw()
	{
		if (_w)
			glfwDestroyWindow(_w);
		if (_texid)
			glDeleteTextures(1, &_texid);
		glfwTerminate();
	}

	bool is_good() const
	{
		return _good;
	}

	bool should_close() const
	{
		return glfwWindowShouldClose(_w);
	}

	void show(const cv::Mat& img, unsigned delay)
	{
		std::chrono::time_point start = std::chrono::high_resolution_clock::now();

		if (_display)
		{
			cimbar::mat_to_gl::load_gl_texture(_texid, img);
			_display->draw(_texid);

			swap();
			poll();
		}

		unsigned millis = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start).count();
		if (delay > millis)
			std::this_thread::sleep_for(std::chrono::milliseconds(delay-millis));
	}

protected:
	void init_opengl(int width, int height)
	{
		glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		glViewport(0, 0, width, height);
	}

	void swap()
	{
		// show next frame
		glfwSwapBuffers(_w);
	}

	void poll()
Repository: sz3/libcimbar
File: src/exe/cimbar_send/send.cpp (C++)

Code before:
	// if we don't need the shakycam, we'll just turn it off
	// we could use a separate code path (just do a mat copyTo),
	// but this is fine.
	if (!use_shakycam)
		cam.toggle();

	cimbar::window w(cam.width(), cam.height(), "cimbar_send");
	if (!w.is_good())
	{
		std::cerr << "failed to create window :(" << std::endl;
		return 50;
	}

	bool running = true;
	bool start = true;

	auto draw = [&w, &cam, use_rotatecam, delay, &running, &start] (const cv::Mat& frame, unsigned) {
		if (!start and w.should_close())
			return running = false;
		start = false;

		cv::Mat& windowImg = cam.draw(frame);
		w.show(windowImg, delay);
		if (use_rotatecam)
			w.rotate();
		return true;
	};

	Encoder en(ecc, cimbar::Config::symbol_bits(), colorBits);
	while (running)
		for (const string& f : infiles)
			en.encode_fountain(f, draw, compressionLevel);
	return 0;
}


Review comment (suggestion):
I may go a different direction with this at some point. We're passing in the size here to embed the 1024x1024 image in a larger image -- which will then get passed to the cimbar::window (which won't have to worry about resizing it).

This is somewhat silly -- the window should be able to render an image on a larger background.

But you know what else is silly? Trying to get transpose and scaling to work in a GLSL shader without hitting weird half-pixel blurring issues.

Code after:
	if (!w.is_good())
	{
		std::cerr << "failed to create window :(" << std::endl;
		return 50;
	}

	bool running = true;
	bool start = true;

	auto draw = [&w, use_rotatecam, use_shakycam, delay, &running, &start] (const cv::Mat& frame, unsigned) {
		if (!start and w.should_close())
			return running = false;
		start = false;

		w.show(frame, delay);
		if (use_rotatecam)
			w.rotate();
		if (use_shakycam)
			w.shake();
		return true;
	};

	Encoder en(ecc, cimbar::Config::symbol_bits(), colorBits);
	while (running)
		for (const string& f : infiles)
			en.encode_fountain(f, draw, compressionLevel, 8.0, window_size);
	return 0;
}
Repository: sz3/libcimbar
File: src/lib/gui/shaky_cam.h (C/C++)

Code before:
	}

	cv::Mat& draw(const cv::Mat& img)
	{
		_frame = _bgcolor;

		int offsetX = (_frame.cols - img.cols) >> 1;
		int offsetY = (_frame.rows - img.rows) >> 1;
		if (_shakycam)
		{
			_frame = _bgcolor;
			if (_shakePos)
			{
				offsetX += (*_shakePos).first;
				offsetY += (*_shakePos).second;
			}
			++_shakePos;
		}

		img.copyTo(_frame(cv::Rect(offsetX, offsetY, img.cols, img.rows)));
		return _frame;
	}

protected:
	bool _shakycam;
	cv::Scalar _bgcolor;
	cv::Mat _frame;
	loop_iterator<decltype(SHAKE_POS)> _shakePos;
};

}


Review comment (suggestion):
It's not really clear where this should live, but for now it's here. We spit out RGB images, opencv wants BGRs, at some point before we render to screen (or to file) with opencv we need to do the conversion.

Code after:
	}

	void shake()
	{
		if (_shakycam)
			++_shakePos;
	}

	cv::Mat& draw(const cv::Mat& img)
	{
		_frame = _bgcolor;

		int offsetX = (_frame.cols - img.cols) >> 1;
		int offsetY = (_frame.rows - img.rows) >> 1;
		if (_shakycam)
		{
			_frame = _bgcolor;
			if (_shakePos)
			{
				offsetX += (*_shakePos).first;
				offsetY += (*_shakePos).second;
			}
		}

		img.copyTo(_frame(cv::Rect(offsetX, offsetY, img.cols, img.rows)));
		cv::cvtColor(_frame, _frame, cv::COLOR_BGR2RGB);
		return _frame;
	}

protected:
	bool _shakycam;
	cv::Scalar _bgcolor;
	cv::Mat _frame;
	loop_iterator<decltype(SHAKE_POS)> _shakePos;
};

}
Repository: sz3/libcimbar
File: web/main.js (JavaScript)

Code before:
    dataOnHeap.set(imageData);
    Main.encode(f.name, dataOnHeap);
    Module._free(dataPtr);

    Main.setHTML("current-file", f.name);
  };
  fileReader.onerror = () => {
    console.error('Unable to read file ' + f.name + '.');
  };

  fileReader.readAsArrayBuffer(f);
}

// public interface
return {
  init : function(canvas)
  {
    Module._initialize_GL(1040, 1040);
    Main.resize();
  },

  resize : function()
  {
    // reset zoom
    var canvas = document.getElementById('canvas');
    canvas.style.zoom = (window.innerWidth / window.outerWidth);
    var width = window.outerWidth;
    var height = window.outerHeight;
    Main.scaleCanvas(canvas, width, height);
    Main.alignInvisibleClick(canvas);
  },

  toggleFullscreen : function()
  {
    toggleFullscreen().then(Main.resize);
  },

  scaleCanvas : function(canvas, width, height)
  {
    var dim = width;
    if (height < dim) {
      dim = height;
    }
    console.log(dim);
    if (dim > 1040) {
      dim = 1040;
    }
    canvas.style.width = dim + "px";
    canvas.style.height = dim + "px";
  },


Review comment (suggestion):
In the process of testing I found a weird interaction with `window.outerWidth` seemingly being >100% on android firefox? So we'll just use `innerWidth`...

The goal is to use 100% of the visible viewport, erring on the side of <100% if there's any ambiguity. Note that we're still using `outerHeight` (not innerHeight) below, so there is room for more rigor here.

Code after:
    dataOnHeap.set(imageData);
    Main.encode(f.name, dataOnHeap);
    Module._free(dataPtr);

    Main.setHTML("current-file", f.name);
  };
  fileReader.onerror = () => {
    console.error('Unable to read file ' + f.name + '.');
  };

  fileReader.readAsArrayBuffer(f);
}

// public interface
return {
  init : function(canvas)
  {
    Module._initialize_GL(1040, 1040);
    Main.resize();
  },

  resize : function()
  {
    // reset zoom
    var canvas = document.getElementById('canvas');
    var width = window.innerWidth;
    var height = window.outerHeight;
    Main.scaleCanvas(canvas, width, height);
    Main.alignInvisibleClick(canvas);
  },

  toggleFullscreen : function()
  {
    toggleFullscreen().then(Main.resize);
  },

  scaleCanvas : function(canvas, width, height)
  {
    var dim = width;
    if (height < dim) {
      dim = height;
    }
    console.log(dim);
    if (dim > 1040) {
      dim = 1040;
    }
    canvas.style.width = dim + "px";
    canvas.style.height = dim + "px";
  },

  alignInvisibleClick : function(canvas)
Repository: sz3/libcimbar
File: web/main.js (JavaScript)

Code before:
  },

  setMode : function(mode_str)
  {
    var is_4c = (mode_str == "4C");
    Module._configure(2, 255, 255, is_4c);

    var nav = document.getElementById("nav-container");
    if (is_4c) {
      nav.classList.remove("mode-b");
      nav.classList.add("mode-4c");
    } else if (mode_str == "B") {
      nav.classList.add("mode-b");
      nav.classList.remove("mode-4c");
    } else {
      nav.classList.remove("mode-b");
      nav.classList.remove("mode-4c");
    }
  },

  setHTML : function(id, msg)
  {
    document.getElementById(id).innerHTML = msg;
  },

  setTitle : function(msg)
  {
    document.title = "Cimbar: " + msg;
  }
};
}();

window.addEventListener('keydown', function(e) {
  e = e || event;
  if (e.target instanceof HTMLBodyElement) {
    if (e.key == 'Enter' || e.keyCode == 13 ||
        e.key == 'Tab' || e.keyCode == 9 ||
        e.key == 'Space' || e.keyCode == 32
    ) {
      Main.clickNav();
      e.preventDefault();
    }
  }
  else {
    if (e.key == 'Escape' || e.keyCode == 27 ||
        e.key == 'Backspace' || e.keyCode == 8 ||
        e.key == 'End' || e.keyCode == 35 ||
        e.key == 'Home' || e.keyCode == 36
    ) {
      Main.blurNav();
    }

Review comment (suggestion):
A bit of a hack -- since we're doing some trickery to make the canvas seem clickable, we need to manually reposition the (absolute positioned) click element if something changes. So we have to check it on an interval.

Code after:
    if (file)
       importFile(file);
    Main.blurNav(false);
  },

  nextFrame : function()
  {
    _counter += 1;
    if (_pause > 0) {
       _pause -= 1;
    }
    var start = performance.now();
    if (!Main.isPaused()) {
       Module._render();
       var frameCount = Module._next_frame();
    }

    var elapsed = performance.now() - start;
    var nextInterval = _interval>elapsed? _interval-elapsed : 0;
    setTimeout(Main.nextFrame, nextInterval);

    if (_showStats && frameCount) {
      _renderTime += elapsed;
      Main.setHTML( "status", elapsed + " : " + frameCount + " : " + Math.ceil(_renderTime/frameCount));
    }
    if ( !(_counter & 31) ) {
       Main.resize();
    }
  },

  setActive : function(active)
  {
    // hide cursor when there's a barcode active
    var invisi = document.getElementById("invisible_click");
    invisi.classList.remove("active");
    invisi.classList.add("active");
  },

  setMode : function(mode_str)
  {
    var is_4c = (mode_str == "4C");
    Module._configure(2, 255, 255, is_4c);

    var nav = document.getElementById("nav-container");
    if (is_4c) {
      nav.classList.remove("mode-b");
      nav.classList.add("mode-4c");
    } else if (mode_str == "B") {
      nav.classList.add("mode-b");
      nav.classList.remove("mode-4c");
    } else {
Repository: sz3/libcimbar
File: src/lib/encoder/Encoder.h (C/C++)

Code before:
	// we calculate requiredFrames based only on symbol bits, to avoid the situation where the color decode is failing while we're
	// refusing to generate additional frames...
	unsigned requiredFrames = fes->blocks_required() * redundancy / cimbar::Config::fountain_chunks_per_frame(_bitsPerSymbol, _coupled and _colorMode==0);
	if (requiredFrames == 0)
		requiredFrames = 1;

	unsigned i = 0;
	while (i < requiredFrames)
	{
		auto frame = encode_next(*fes, canvas_size);
		if (!frame)
			break;

		if (!on_frame(*frame, i))
			break;
		++i;
	}
	return i;
}

inline unsigned Encoder::encode_fountain(const std::string& filename, std::string output_prefix, int compression_level, double redundancy, int canvas_size)
{
	std::function<bool(const cv::Mat&, unsigned)> fun = [output_prefix] (const cv::Mat& frame, unsigned i) {
		std::string output = fmt::format("{}_{}.png", output_prefix, i);
		cv::Mat bgr;
		cv::cvtColor(frame, bgr, cv::COLOR_RGB2BGR);
		return cv::imwrite(output, bgr);
	};
	return encode_fountain(filename, fun, compression_level, redundancy, canvas_size);
}


Review comment (bug):
I expect that the likely way we hit this message is that the Scanner code gets messed up and everything (or many things) fail to scan. We could risk the infinite loop since that kind of failure should get caught by tests/etc, but... this seems smarter to me.

Code after:
	// if fountain_chunks_per_frame() is 10, the fountain_chunk_size will be 750.
	// we calculate requiredFrames based only on symbol bits, to avoid the situation where the color decode is failing while we're
	// refusing to generate additional frames...
	unsigned requiredFrames = fes->blocks_required() * redundancy / cimbar::Config::fountain_chunks_per_frame(_bitsPerSymbol, _coupled and _colorMode==0);
	if (requiredFrames == 0)
		requiredFrames = 1;

	unsigned i = 0;
	unsigned consecutiveScansFailed = 0;
	while (i < requiredFrames)
	{
		auto frame = encode_next(*fes, canvas_size);
		if (!frame)
			break;

		// some % of generated frames (for the current 8x8 impl)
		// will produce random patterns that falsely match as
		// corner "anchors" and fail to extract. So:
		// if frame fails the scan, skip it.
		if (!Scanner::will_it_scan(*frame))
		{
			if (++consecutiveScansFailed < 5)
				continue;

			// else, we gotta make forward progress. And it's probably a bug?
			std::cerr << fmt::format("generated {} bad frames in a row. This really shouldn't happen, maybe report a bug. :(", consecutiveScansFailed) << std::endl;
		}

		consecutiveScansFailed = 0;
		if (!on_frame(*frame, i))
			break;
		++i;
	}
	return i;
}

inline unsigned Encoder::encode_fountain(const std::string& filename, std::string output_prefix, int compression_level, double redundancy, int canvas_size)
{
	std::function<bool(const cv::Mat&, unsigned)> fun = [output_prefix] (const cv::Mat& frame, unsigned i) {
		std::string output = fmt::format("{}_{}.png", output_prefix, i);
		cv::Mat bgr;
		cv::cvtColor(frame, bgr, cv::COLOR_RGB2BGR);
		return cv::imwrite(output, bgr);
	};
	return encode_fountain(filename, fun, compression_level, redundancy, canvas_size);
}