Skip to content

Instantly share code, notes, and snippets.

@xsbee
Created July 28, 2022 06:02
Show Gist options
  • Save xsbee/fc9b99f924e013334069d3be2541d7de to your computer and use it in GitHub Desktop.
Save xsbee/fc9b99f924e013334069d3be2541d7de to your computer and use it in GitHub Desktop.
// g++ -Wall -Wextra -shared -Ofast -march=native -o vhs.dll vhs.cxx
#include <random>
struct vhs
{
unsigned int m_width;
unsigned int m_height;
unsigned int m_sr_off = 0;
unsigned int m_sr_sz;
std::random_device m_dev;
std::uniform_int_distribution<unsigned int> m_right_shift;
std::uniform_int_distribution<size_t> m_white_stroke_pos;
std::uniform_int_distribution<unsigned int> m_white_stroke_len;
vhs(unsigned int width, unsigned int height)
: m_width(width), m_height(height),
m_sr_sz(height * 0.08),
m_right_shift(4, 8),
m_white_stroke_pos(0, static_cast<size_t>(width) * height),
m_white_stroke_len(16, 64)
{}
void process(const uint32_t *inframe, uint32_t *outframe)
{
unsigned int sr_end = std::min(m_sr_off + m_sr_sz, m_height);
// TODO: add chroma-shift, some noise, lowpass to image (using FFTW?)
// Row-shift distortion and colour fade.
for (unsigned int j = 0; j < m_height; ++j)
{
unsigned int shift = m_right_shift(m_dev);
const size_t r = static_cast<size_t>(j) * m_width;
// Fill left-side with zeros.
std::fill(outframe + r, outframe + r + shift, 0);
size_t in_row_end = r + m_width - shift;
for (size_t i = r; i < in_row_end; ++i)
{
const uint8_t *P0 = reinterpret_cast<const uint8_t*>(inframe + i);
uint8_t *P1 = reinterpret_cast<uint8_t*>(&outframe[i + shift]);
// 50% contrast reduction (y = 1/n * (x - 128) + 128)
P1[0] = P0[0] / 2 + 64;
P1[1] = P0[1] / 2 + 64;
P1[2] = P0[2] / 2 + 64;
P1[3] = P0[3];
}
}
// Sliding horizontal blur-like effect.
for (unsigned int j = m_sr_off; j < sr_end; ++j)
{
for (unsigned int i = 0; i < m_width; ++i)
{
const size_t p0 = static_cast<size_t>(j) * m_width + i;
const size_t p1 = static_cast<size_t>(m_sr_off) * m_width + i;
outframe[p0] = outframe[p1];
}
}
// Ocassional white strokes, as in VHS.
// TODO: Make the apperance of this random.
for (unsigned int j = 0; j < 4; ++j)
{
unsigned int stroke_len = m_white_stroke_len(m_dev);
size_t stroke_end = m_white_stroke_pos(m_dev);
size_t stroke_begin = std::max<size_t>(stroke_len, stroke_end) - stroke_len;
for (size_t i = stroke_begin; i < stroke_end; ++i)
{
uint8_t* P = reinterpret_cast<uint8_t*>(&outframe[i]);
P[0] = P[1] = P[2] = 255;
}
}
m_sr_off = (m_sr_off + 8) % m_height;
}
};
extern "C" {
#include <frei0r.h>
int f0r_init()
{
return 0;
}
f0r_instance_t f0r_construct(unsigned int width, unsigned int height)
{
vhs *instance = new vhs(width, height);
return instance;
}
void f0r_get_plugin_info(f0r_plugin_info_t *info)
{
info->name = "vhs";
info->author = "xsbee";
info->explanation = "VHS tape-like effect";
info->major_version = 1;
info->minor_version = 0;
info->frei0r_version = FREI0R_MAJOR_VERSION;
info->color_model = F0R_COLOR_MODEL_RGBA8888;
info->plugin_type = F0R_PLUGIN_TYPE_FILTER;
info->num_params = 0;
}
void f0r_get_param_info (f0r_param_info_t *info, int param_index) {}
void f0r_get_param_value(
f0r_instance_t instance,
f0r_param_t param,
int param_index
) {}
void f0r_set_param_value(
f0r_instance_t instance,
f0r_param_t param,
int param_index
) {}
void f0r_update (
f0r_instance_t instance,
double,
const uint32_t *inframe,
uint32_t *outframe
)
{
static_cast<vhs*>(instance)->process(inframe, outframe);
}
void f0r_deinit() {}
void f0r_destruct (f0r_instance_t instance)
{
delete static_cast<vhs*>(instance);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment