ronantakizawa/codereview-bench huggingface dataset:
Repository: sz3/libcimbar
File: web/main.js (JavaScript)

Code before:
  fileReader.onload = (event) => {
    const imageData = new Uint8Array(event.target.result);
    const numBytes = imageData.length * imageData.BYTES_PER_ELEMENT;
    const dataPtr = Module._malloc(numBytes);
    const dataOnHeap = new Uint8Array(Module.HEAPU8.buffer, dataPtr, numBytes);
    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();
    Main.check_GL_enabled(canvas);
  },

  check_GL_enabled : function(canvas)
  {
    if (canvas.getContext("2d")) {
       var elem = document.getElementById('dragdrop');
       elem.classList.add("error");
    }
  },

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

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


Review comment (suggestion):
The new default. I introduced mode B and the autodetect decoder behavior a year ago+, but I didn't want to change the default before giving people time to upgrade.

(I may not push this to cimbar.org right away, but it'll be soon)

Code after:
function importFile(f)
{
  const fileReader = new FileReader();
  fileReader.onload = (event) => {
    const imageData = new Uint8Array(event.target.result);
    const numBytes = imageData.length * imageData.BYTES_PER_ELEMENT;
    const dataPtr = Module._malloc(numBytes);
    const dataOnHeap = new Uint8Array(Module.HEAPU8.buffer, dataPtr, numBytes);
    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)
  {
    Main.setMode('B');
    Main.resize();
    Main.check_GL_enabled(canvas);
  },

  check_GL_enabled : function(canvas)
  {
    if (canvas.getContext("2d")) {
       var elem = document.getElementById('dragdrop');
       elem.classList.add("error");
    }
  },

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

  toggleFullscreen : function()
  {
    toggleFullscreen().then(Main.resize);
Repository: sz3/libcimbar
File: src/lib/compression/zstd_compressor.h (C/C++)

Code before:
			return 0;
		set_compression_level(compression_level);

		std::array<char, CHUNK_SIZE> rawBuff;
		size_t totalBytesRead = 0;
		while (raw)
		{
			raw.read(rawBuff.data(), rawBuff.size());
			std::streamsize bytesRead = raw.gcount();
			if (bytesRead <= 0)
				break;
			totalBytesRead += bytesRead;

			if (!write(rawBuff.data(), bytesRead))
				break;
		}
		return totalBytesRead;
	}

	unsigned pad(unsigned len)
	{
		if (len < 9)
			len = 9;

		std::array<char, 8> header = {0x50, 0x2A, 0x4D, 0x18, (char)(len&0xFF), (char)(len&0xFF00 >> 8), (char)(len&0xFF0000 >> 16), 0};
		STREAM::write(header.data(), header.size());

		len -= 8;
		std::fill(_compBuff.begin(), _compBuff.end(), 0);
		for (size_t writeLen = CHUNK_SIZE; len > 0; len -= writeLen)
		{
			if (len < writeLen)
				writeLen = len;
			STREAM::write(_compBuff.data(), writeLen);
		}
		return len;
	}

	size_t size()
	{
		STREAM::seekg(0, std::ios::end);
		size_t len = STREAM::tellg();
		STREAM::seekg(0, std::ios::beg);
		return len;
	}

protected:
	static const size_t CHUNK_SIZE = 0x4000;
	int _compressionLevel = 16;
	ZSTD_CCtx* _cctx = ZSTD_createCCtx();
	std::vector<char> _compBuff = std::vector<char>(ZSTD_compressBound(CHUNK_SIZE));

Review comment (bug):
So this is comedically wrong. Not only is the length the wrong length (we need to omit the header size, hence the `-=8` above instead of below), but the bitshifts were being applied before the bitwise and :upside_down_face: 

Anyway, it's fixed to match `ZSTD_writeSkippableFrame(x,y, a,b, 0);`, where `0` is the so-called "magicVariant". (there are 16 accepted values)

Code after:
			return 0;
		set_compression_level(compression_level);

		std::array<char, CHUNK_SIZE> rawBuff;
		size_t totalBytesRead = 0;
		while (raw)
		{
			raw.read(rawBuff.data(), rawBuff.size());
			std::streamsize bytesRead = raw.gcount();
			if (bytesRead <= 0)
				break;
			totalBytesRead += bytesRead;

			if (!write(rawBuff.data(), bytesRead))
				break;
		}
		return totalBytesRead;
	}

	unsigned pad(unsigned len)
	{
		if (len < 9)
			len = 9;

		len -= 8;
		std::array<char, 8> header = {0x50, 0x2A, 0x4D, 0x18, (char)(len&0xFF), (char)((len&0xFF00) >> 8), (char)((len&0xFF0000) >> 16), 0};
		STREAM::write(header.data(), header.size());

		std::fill(_compBuff.begin(), _compBuff.end(), 0);
		for (size_t writeLen = CHUNK_SIZE; len > 0; len -= writeLen)
		{
			if (len < writeLen)
				writeLen = len;
			STREAM::write(_compBuff.data(), writeLen);
		}
		return len;
	}

	size_t size()
	{
		STREAM::seekg(0, std::ios::end);
		size_t len = STREAM::tellg();
		STREAM::seekg(0, std::ios::beg);
		return len;
	}

protected:
	static const size_t CHUNK_SIZE = 0x4000;
	int _compressionLevel = 16;
	ZSTD_CCtx* _cctx = ZSTD_createCCtx();
	std::vector<char> _compBuff = std::vector<char>(ZSTD_compressBound(CHUNK_SIZE));
Repository: sz3/libcimbar
File: src/lib/cimbar_js/cimbar_js.cpp (C++)

Code before:
		_frameCount = 0;
	}

	Encoder enc;
	enc.set_encode_id(_encodeId);
	_next = enc.encode_next(*_fes, _window? cimbar::vec_xy{_window->width(), _window->height()} : cimbar::vec_xy{});
	return ++_frameCount;
}

// maybe init_encode w/ filename,size,encode_id,
// then encode() with buff,size? ... when size < chunksize (or size ==0), we're done
// return 0 on done, 1 iff work to continue?

int cimbare_init_encode(const char* filename, unsigned fnsize, int encode_id)
{
	_frameCount = 0;
	if (!FountainInit::init())
	{
		std::cerr << "failed FountainInit :(" << std::endl;
		return -5;
	}

	if (encode_id < 0)
		++_encodeId; // increment _encodeId every time we change files
	else
		_encodeId = encode_id;

	_comp = std::make_unique<cimbar::zstd_compressor<std::stringstream>>();
	if (!_comp)
		return -1;

	_comp->set_compression_level(_compressionLevel);

	if (fnsize > 0 and filename != nullptr)
		_comp->write_header(filename, fnsize);
	return 0;
}

int cimbare_encode_bufsize()
{
	return cimbar::zstd_compressor<std::stringstream>::CHUNK_SIZE;
}

int cimbare_encode(const unsigned char* buffer, unsigned size)
{
	if (!_comp)
		return -1;

	if (size > 0)
	{
		if (!_comp->write(reinterpret_cast<const char*>(buffer), size))

Review comment (suggestion):
This might be the strangest side effect of this change -- `_encodeId` will now persist if you override it, so you can change the auto-increment starting point. You probably shouldn't, though. The right behavior will generally be to use `-1` (for auto-increment).

(should probably define a constant for that)

Code after:
		_frameCount = 0;
	}

	Encoder enc;
	enc.set_encode_id(_encodeId);
	_next = enc.encode_next(*_fes, _window? cimbar::vec_xy{_window->width(), _window->height()} : cimbar::vec_xy{});
	return ++_frameCount;
}

// maybe init_encode w/ filename,size,encode_id,
// then encode() with buff,size? ... when size < chunksize (or size ==0), we're done
// return 0 on done, 1 iff work to continue?

int cimbare_init_encode(const char* filename, unsigned fnsize, int encode_id)
{
	_frameCount = 0;
	if (!FountainInit::init())
	{
		std::cerr << "failed FountainInit :(" << std::endl;
		return -5;
	}

	if (encode_id < 0)
		++_encodeId; // increment _encodeId every time we change files
	else
		_encodeId = encode_id;

	_comp = std::make_unique<cimbar::zstd_compressor<std::stringstream>>();
	if (!_comp)
		return -1;

	_comp->set_compression_level(_compressionLevel);

	if (fnsize > 0 and filename != nullptr)
		_comp->write_header(filename, fnsize);

	_fes.reset();
	return 0;
}

int cimbare_encode_bufsize()
{
	return cimbar::zstd_compressor<std::stringstream>::CHUNK_SIZE;
}

int cimbare_encode(const unsigned char* buffer, unsigned size)
{
	if (!_comp)
		return -1;

	if (size > 0)
Repository: sz3/libcimbar
File: src/lib/cimbar_js/cimbar_js.cpp (C++)

Code before:
// maybe init_encode w/ filename,size,encode_id,
// then encode() with buff,size? ... when size < chunksize (or size ==0), we're done
// return 0 on done, 1 iff work to continue?

int cimbare_init_encode(const char* filename, unsigned fnsize, int encode_id)
{
	_frameCount = 0;
	if (!FountainInit::init())
	{
		std::cerr << "failed FountainInit :(" << std::endl;
		return -5;
	}

	if (encode_id < 0)
		++_encodeId; // increment _encodeId every time we change files
	else
		_encodeId = encode_id;

	_comp = std::make_unique<cimbar::zstd_compressor<std::stringstream>>();
	if (!_comp)
		return -1;

	_comp->set_compression_level(_compressionLevel);

	if (fnsize > 0 and filename != nullptr)
		_comp->write_header(filename, fnsize);
	return 0;
}

int cimbare_encode_bufsize()
{
	return cimbar::zstd_compressor<std::stringstream>::CHUNK_SIZE;
}

int cimbare_encode(const unsigned char* buffer, unsigned size)
{
	if (!_comp)
		return -1;

	if (size > 0)
	{
		if (!_comp->write(reinterpret_cast<const char*>(buffer), size))
			return -2;
	}
	if (size == cimbare_encode_bufsize())
		return 1; // more to do

	// otherwise, we're ready
	unsigned fountainChunkSize = cimbar::Config::fountain_chunk_size();
	size_t compressedSize = _comp->size();
	if (compressedSize < fountainChunkSize)

Review comment (refactor):
All this "new" code is refactored from `Encoder::create_fountain_encoder()` -- that may go away at some point, now that it seems redundant.

Code after:
// maybe init_encode w/ filename,size,encode_id,
// then encode() with buff,size? ... when size < chunksize (or size ==0), we're done
// return 0 on done, 1 iff work to continue?

int cimbare_init_encode(const char* filename, unsigned fnsize, int encode_id)
{
	_frameCount = 0;
	if (!FountainInit::init())
	{
		std::cerr << "failed FountainInit :(" << std::endl;
		return -5;
	}

	if (encode_id < 0)
		++_encodeId; // increment _encodeId every time we change files
	else
		_encodeId = encode_id;

	_comp = std::make_unique<cimbar::zstd_compressor<std::stringstream>>();
	if (!_comp)
		return -1;

	_comp->set_compression_level(_compressionLevel);

	if (fnsize > 0 and filename != nullptr)
		_comp->write_header(filename, fnsize);

	_fes.reset();
	return 0;
}

int cimbare_encode_bufsize()
{
	return cimbar::zstd_compressor<std::stringstream>::CHUNK_SIZE;
}

int cimbare_encode(const unsigned char* buffer, unsigned size)
{
	if (!_comp)
		return -1;

	if (size > 0)
	{
		if (!_comp->write(reinterpret_cast<const char*>(buffer), size))
			return -2;
	}
	if (size == cimbare_encode_bufsize())
		return 1; // more to do

	// otherwise, we're ready
	unsigned fountainChunkSize = cimbar::Config::fountain_chunk_size();
Repository: sz3/libcimbar
File: src/lib/cimbar_js/cimbar_js.cpp (C++)

Code before:
}

int cimbare_encode(const unsigned char* buffer, unsigned size)
{
	if (!_comp)
		return -1;

	if (size > 0)
	{
		if (!_comp->write(reinterpret_cast<const char*>(buffer), size))
			return -2;
	}
	if (size == cimbare_encode_bufsize())
		return 1; // more to do

	// otherwise, we're ready
	unsigned fountainChunkSize = cimbar::Config::fountain_chunk_size();
	size_t compressedSize = _comp->size();
	if (compressedSize < fountainChunkSize)
		_comp->pad(fountainChunkSize - compressedSize + 1);

	// create the encoder stream
	_fes = fountain_encoder_stream::create(*_comp, fountainChunkSize, _encodeId);
	_comp.reset();
	if (!_fes)
		return -3;

	_next.reset();
	return 0;
}

int cimbare_configure(int mode_val, int compression)
{
	// defaults
	if (mode_val == 0)
		mode_val = 68;
	if (compression < 0 or compression > 22)
		compression = cimbar::Config::compression_level();

	// make sure we've initialized
	int window_size_x = cimbar::Config::image_size_x() + 16;
	int window_size_y = cimbar::Config::image_size_y() + 16;
	int initRes = cimbare_init_window(window_size_x, window_size_y);
	if (initRes < 0)
		return initRes;

	// check if we need to refresh the stream
	bool refresh = (mode_val != _modeVal or compression != _compressionLevel);
	if (refresh)
	{
		// update config

Review comment (suggestion):
After we return 0 from `cimbare_encode`, the next call to it will return `-1` (as if we never called `cimbare_init_encode`)

Code after:
{
	return cimbar::zstd_compressor<std::stringstream>::CHUNK_SIZE;
}

int cimbare_encode(const unsigned char* buffer, unsigned size)
{
	if (!_comp)
		return -1;

	if (size > 0)
	{
		if (!_comp->write(reinterpret_cast<const char*>(buffer), size))
			return -2;
	}
	if (size == cimbare_encode_bufsize())
		return 1; // more to do

	// otherwise, we're ready
	unsigned fountainChunkSize = cimbar::Config::fountain_chunk_size();
	size_t compressedSize = _comp->size();
	if (compressedSize < fountainChunkSize)
		_comp->pad(fountainChunkSize - compressedSize + 1);

	// create the encoder stream
	_fes = fountain_encoder_stream::create(*_comp, fountainChunkSize, _encodeId);
	_comp.reset();
	if (!_fes)
		return -3;

	_next.reset();
	return 0;
}

int cimbare_configure(int mode_val, int compression)
{
	// defaults
	if (mode_val == 0)
		mode_val = 68;
	if (compression < 0 or compression > 22)
		compression = cimbar::Config::compression_level();

	// make sure we've initialized
	int window_size_x = cimbar::Config::image_size_x() + 16;
	int window_size_y = cimbar::Config::image_size_y() + 16;
	int initRes = cimbare_init_window(window_size_x, window_size_y);
	if (initRes < 0)
		return initRes;

	// check if we need to refresh the stream
	bool refresh = (mode_val != _modeVal or compression != _compressionLevel);
	if (refresh)
Repository: sz3/libcimbar
File: src/lib/cimbar_js/cimbar_js.cpp (C++)

Code before:
	if (width % 4 != 0)
		width += (4 - width % 4);
	if (height % 4 != 0)
		height += (4 - height % 4);
	std::cerr << "initializing " << width << " by " << height << " window";

	if (_window and _window->is_good())
		_window->resize(width, height);
	else
		_window = std::make_shared<cimbar::window_glfw>(width, height, "Cimbar Encoder");
	if (!_window or !_window->is_good())
		return -1;

	return 0;
}

bool cimbare_auto_scale_window()
{
	if (!_window or !_window->is_good())
		return false;

	_window->auto_scale_to_window();
	return true;
}

// we may change the api to accept an buff to next_frame()
// rather than generating a fresh cv::Mat alloc each time
// but for now, for non-JS purposes we expose this function
int cimbare_get_frame_buff(unsigned char** buff)
{
	if (!_next)
		return -2;
	if (_next->cols == 0 or _next->rows == 0)
		return -1;

	*buff = _next->data;
	return _next->cols * _next->rows * _next->channels();
}

// render() and next_frame() could be put in the same function,
// but it seems cleaner to split them.
// in any case, we're concerned with frame pacing (some encodes take longer than others)
int cimbare_render()
{
	if (!_window or !_fes or _window->should_close())
		return -1;

	if (_next)
	{
		_window->show(*_next, 0);
		_window->shake();

Review comment (bug):
The existing rotation implementation has a weird rotation order due to what I initially implemented it for (an alternative attempt at the "shakycam" jitter we use to fast-fail bad frames). So 90 degrees is 2 "rotations", even though that makes no sense. (will probably change it to act normal in a followup)

Code after:
	if (width % 4 != 0)
		width += (4 - width % 4);
	if (height % 4 != 0)
		height += (4 - height % 4);
	std::cerr << "initializing " << width << " by " << height << " window";

	if (_window and _window->is_good())
		_window->resize(width, height);
	else
		_window = std::make_shared<cimbar::window_glfw>(width, height, "Cimbar Encoder");
	if (!_window or !_window->is_good())
		return -1;

	return 0;
}

int cimbare_rotate_window(bool rotate)
{
	if (!_window or !_window->is_good())
		return -1;

	_window->rotate(0);
	if (rotate) // 90 degrees
	{
		_window->rotate();
		_window->rotate();
	}
	return 0;
}

bool cimbare_auto_scale_window()
{
	if (!_window or !_window->is_good())
		return false;

	_window->auto_scale_to_window();
	return true;
}

// we may change the api to accept an buff to next_frame()
// rather than generating a fresh cv::Mat alloc each time
// but for now, for non-JS purposes we expose this function
int cimbare_get_frame_buff(unsigned char** buff)
{
	if (!_next)
		return -2;
	if (_next->cols == 0 or _next->rows == 0)
		return -1;

	*buff = _next->data;
	return _next->cols * _next->rows * _next->channels();
Repository: sz3/libcimbar
File: web/recv.js (JavaScript)

Code before:
var Sink = function () {

  var _fountainBuff = undefined;
  var _errBuff = undefined;
  var _errBuffSize = 1024;

  // public interface
  return {
    on_decode: function (buff) {
      if (_fountainBuff === undefined) {
        const size = Module._cimbard_get_bufsize(); // max length of buff. We could also resize as we go...
        const dataPtr = Module._malloc(size);
        _fountainBuff = new Uint8Array(Module.HEAPU8.buffer, dataPtr, size);
      }
      if (buff.length == 0) { // sanity check
        return;
      }
      _fountainBuff.set(buff);

      console.log('sink decode ' + _fountainBuff); //TODO: base64?
      var res = Module._cimbard_fountain_decode(_fountainBuff.byteOffset, buff.length);
      console.log("on decode got res " + res);

      const report = Sink.get_report();
      if (Array.isArray(report)) {
        Recv.render_progress(report);
      }
      else {
        Recv.set_HTML("tdec", "decode " + res + ". " + report);
      }

      if (res > 0) {
        const res32t = Number(res & 0xFFFFFFFFn);; // truncate BigInt res (int64_t) to a uint32_t
        Sink.reassemble_file(res32t);
      }

Review comment (bug):
Another bugfix: we may need to reallocate if the default (or current) fountain buffer size is smaller than the mode we switch to.

Code after:
var Sink = function () {

  var _fountainBuff = undefined;
  var _errBuff = undefined;
  var _errBuffSize = 1024;

  // public interface
  return {
    allocate: function () {
      const size = Module._cimbard_get_bufsize(); // max length of buff. We could also resize as we go...
      if (_fountainBuff && size > _fountainBuff.length) {
        Module._free(_fountainBuff.byteOffset);
        _fountainBuff = undefined;
      }
      if (_fountainBuff === undefined) {
        const dataPtr = Module._malloc(size);
        _fountainBuff = new Uint8Array(Module.HEAPU8.buffer, dataPtr, size);
      }
    },

    on_decode: function (buff) {
      if (buff.length == 0) { // sanity check
        return;
      }
      _fountainBuff.set(buff);

      console.log('sink decode ' + _fountainBuff); //TODO: base64?
      var res = Module._cimbard_fountain_decode(_fountainBuff.byteOffset, buff.length);
      console.log("on decode got res " + res);

      const report = Sink.get_report();
      if (Array.isArray(report)) {
        Recv.render_progress(report);
      }
      else {
Repository: sz3/libcimbar
File: src/lib/extractor/Deskewer.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

#include "Corners.h"

#include "util/vec_xy.h"
#include <opencv2/opencv.hpp>

#include <vector>

class Deskewer
{
public:
	Deskewer(cimbar::vec_xy image_size={}, unsigned anchor_size=0);

	template <typename MAT>
	MAT deskew(const MAT& img, const Corners& corners);

protected:
	cimbar::vec_xy _imageSize;
	int _anchorSize;
};

template <typename MAT>
inline MAT Deskewer::deskew(const MAT& img, const Corners& corners)
{
	std::vector<cv::Point2f> outputPoints;
	outputPoints.push_back(cv::Point2f(_anchorSize, _anchorSize));
	outputPoints.push_back(cv::Point2f(_imageSize.width() - _anchorSize, _anchorSize));
	outputPoints.push_back(cv::Point2f(_anchorSize, _imageSize.height() - _anchorSize));
	outputPoints.push_back(cv::Point2f(_imageSize.width() - _anchorSize, _imageSize.height() - _anchorSize));

	MAT output(_imageSize.height(), _imageSize.width(), img.type());
	cv::Mat transform = cv::getPerspectiveTransform(corners.all(), outputPoints);

	cv::warpPerspective(img, output, transform, output.size(), cv::INTER_LINEAR);
	return output;
}


Review comment (suggestion):
In usual circumstances you won't want to specify the image or anchor sizes here. So normal usage will be:

`Deskewer ds;`

or

`Deskewer ds(4);` (or whatever padding is desired)

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

#include "Corners.h"

#include "util/vec_xy.h"
#include <opencv2/opencv.hpp>

#include <vector>

class Deskewer
{
public:
	Deskewer(unsigned padding=0, cimbar::vec_xy image_size={}, unsigned anchor_size=0);

	template <typename MAT>
	MAT deskew(const MAT& img, const Corners& corners);

protected:
	cimbar::vec_xy _imageSize;
	unsigned _anchorSize;
	unsigned _padding;
};

template <typename MAT>
inline MAT Deskewer::deskew(const MAT& img, const Corners& corners)
{
	std::vector<cv::Point2f> outputPoints;
	outputPoints.push_back(cv::Point2f(_anchorSize+_padding, _anchorSize+_padding));
	outputPoints.push_back(cv::Point2f(_imageSize.width() - _anchorSize+_padding, _anchorSize+_padding));
	outputPoints.push_back(cv::Point2f(_anchorSize+_padding, _imageSize.height() - _anchorSize+_padding));
	outputPoints.push_back(cv::Point2f(_imageSize.width() - _anchorSize+_padding, _imageSize.height() - _anchorSize+_padding));

	// + 2*padding ?
	MAT output(_imageSize.height() + (_padding*2), _imageSize.width() + (_padding*2), img.type());
	cv::Mat transform = cv::getPerspectiveTransform(corners.all(), outputPoints);

	cv::warpPerspective(img, output, transform, output.size(), cv::INTER_LINEAR);
	return output;
Repository: sz3/libcimbar
File: src/lib/cimb_translator/Config.h (C/C++)

Code before:
		{
			return active_conf().ecc_block_size;
		}

		static unsigned image_size_x()
		{
			return active_conf().image_size_x;
		}

		static unsigned image_size_y()
		{
			return active_conf().image_size_y;
		}

		static unsigned anchor_size()
		{
			return 30;
		}

		static constexpr unsigned cell_size()
		{
			return 8;
		}

		static unsigned cell_spacing_x()
		{
			return active_conf().cell_spacing_x;
		}

		static unsigned cell_spacing_y()
		{
			return active_conf().cell_spacing_y;
		}

		static unsigned corner_padding_x()
		{
			return active_conf().corner_padding_x();
		}

		static unsigned corner_padding_y()
		{
			return active_conf().corner_padding_y();
		}

		static unsigned cell_offset()
		{
			return active_conf().cell_offset;
		}

		static unsigned cells_per_col_x()
		{

Review comment (suggestion):
I spent quite a bit of time testing 5x5 configs before settling on a small 8x8. Unfortunately I don't think it's going to work well enough :disappointed: 

There is maybe room for a large 5x5 config for "best case scenario" speed purposes (130+ kb/s is possible) . But it'll always be (afaik) much less reliable/useful compared to mode B.

Code after:
			return active_conf().ecc_bytes;
		}

		static unsigned ecc_block_size()
		{
			return active_conf().ecc_block_size;
		}

		static unsigned image_size_x()
		{
			return active_conf().image_size_x;
		}

		static unsigned image_size_y()
		{
			return active_conf().image_size_y;
		}

		static unsigned anchor_size()
		{
			return 30;
		}

		static constexpr unsigned cell_size()
		{
			// TODO: runtime
			return 8;
		}

		static unsigned cell_spacing_x()
		{
			return active_conf().cell_spacing_x;
		}

		static unsigned cell_spacing_y()
		{
			return active_conf().cell_spacing_y;
		}

		static unsigned corner_padding_x()
		{
			return active_conf().corner_padding_x();
		}

		static unsigned corner_padding_y()
		{
			return active_conf().corner_padding_y();
		}

		static unsigned cell_offset()
		{