/**
* (c) Copyright 2025 Cynthia <cynthia2048@proton.me>
*
* This is based on Otsu's algorithm to segment an image into foreground
* or background based on the shape of the histogram. Instead of doing a
* hard threshold, we use the threshold obtained from Otsu's algorithm
* as the base for a sigmoidal transfer with a high slope; this produces
* a more eye-soothing threshold effect as seen in ImageMagick.
*
* This has the added benefit that, whereas the sigmoidal filter's base
* requires manual tuning, here it is determined algorithmically and thus
* can adapt on a frame-to-frame basis.
*
* This file is a Frei0r plugin.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include "frei0r.h"
#include "frei0r/math.h"
#include "variance.h"
typedef struct {
unsigned int width, height;
float slope;
uint8_t lut[256][256];
uint8_t *lumaframe;
} s0ft0tsu_t;
static void gen_sigmoid_lut (uint8_t lut[256][256], float slope)
{
float k = expf(slope) / 255.0;
for (int j = 0; j < 256; ++j)
for (int i = 0; i < 256; ++i)
lut[j][i] = CLAMP (255.0 / (1.0 + expf(-k * (i - j))), 0, 255);
}
int f0r_init()
{
return 0;
}
void f0r_deinit() {}
f0r_instance_t f0r_construct(unsigned int width, unsigned int height)
{
s0ft0tsu_t* s = calloc(1, sizeof(s0ft0tsu_t));
s->width = width;
s->height = height;
s->slope = 4.0;
s->lumaframe = malloc(s->width * s->height);
gen_sigmoid_lut(s->lut, s->slope);
return s;
}
void f0r_get_plugin_info(f0r_plugin_info_t* info)
{
info->name = "autothreshold";
info->author = "Cynthia";
info->explanation = "Automatically threshold moving pictures";
info->major_version = 0;
info->minor_version = 1;
info->frei0r_version = FREI0R_MAJOR_VERSION;
info->plugin_type = F0R_PLUGIN_TYPE_FILTER;
info->color_model = F0R_COLOR_MODEL_RGBA8888;
info->num_params = 1;
}
void f0r_get_param_info(f0r_param_info_t *info, int index)
{
switch (index)
{
case 0:
info->name = "Slope";
info->explanation = "Slope of sigmoidal transfer";
info->type = F0R_PARAM_DOUBLE;
break;
}
}
void f0r_get_param_value(f0r_instance_t inst, f0r_param_t param, int index)
{
s0ft0tsu_t* s = inst;
switch (index)
{
case 0:
*(double*)param = s->slope / 7.0;
break;
}
}
void f0r_set_param_value(f0r_instance_t inst, f0r_param_t param, int index)
{
s0ft0tsu_t* s = inst;
switch (index)
{
case 0:
s->slope = *(double*)param * 7.0;
gen_sigmoid_lut(s->lut, s->slope);
break;
}
}
void f0r_update(f0r_instance_t inst, double time,
const uint32_t* inframe, uint32_t* outframe)
{
s0ft0tsu_t *s = inst;
uint8_t *src = (uint8_t*)inframe;
uint8_t *dst = s->lumaframe;
uint8_t r, g, b, luma;
size_t len = s->width * s->height;
// Normalised histogram (L = 256)
float hist[256] = {0};
for (int i = 0; i < len; ++i)
{
r = *src++;
g = *src++;
b = *src++;
src++; // Ignore alpha
luma = CLAMP(0.299 * r + 0.587 * g + 0.114 * b, 0, 255);
*dst++ = luma;
++hist[luma]; // Add to histogram
}
// Normalise histogram; becomes a probability distribution
for (int i = 0; i < 256; ++i)
hist[i] /= len;
uint8_t thresh = 0.0;
float max_var = 0.0; // Maximum inter-class variance.
for (int i = 1; i < 256; ++i)
{
float var = find_variance(hist, i);
if (var > max_var)
{
thresh = i;
max_var = var;
}
}
src = (uint8_t*)s->lumaframe; // Replenish
dst = (uint8_t*)outframe;
for (int i = 0; i < len; ++i)
{
// Select the appropriate transfer
uint8_t* lut = s->lut[thresh];
luma = lut[*src++];
*dst++ = luma;
*dst++ = luma;
*dst++ = luma;
*dst++ = 0xFF; // TODO Copy alpha
}
}
void f0r_destruct(f0r_instance_t inst)
{
s0ft0tsu_t* s = inst;
free(s->lumaframe);
free(s);
}