Add hue, saturation, lightness to c-extension
This adds a function hue_saturation_lightness to the _c_manipulate module. To keep the C code clean, the files manipulate.{h,c} have been split up into multiple header files and manipulate.c only defines the python part.
This commit is contained in:
parent
32c868f6db
commit
2eab4db8cd
|
@ -0,0 +1,59 @@
|
|||
/*******************************************************************************
|
||||
* C extension for vimiv
|
||||
* Functions to enhance brightness and contrast of an image.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "definitions.h"
|
||||
#include "helper_func.h"
|
||||
#include "math_func_eval.h"
|
||||
|
||||
/**
|
||||
* Enhance brightness using the GIMP algorithm.
|
||||
*
|
||||
* @param value Current R/G/B value of the pixel.
|
||||
* @param factor Factor to enhance brightness by.
|
||||
*/
|
||||
static inline float enhance_brightness(float value, float factor)
|
||||
{
|
||||
if (factor < 0)
|
||||
return value * (1 + factor);
|
||||
return value + (1 - value) * factor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance contrast using the GIMP algorithm:
|
||||
*
|
||||
* value = (value - 0.5) * (tan ((factor + 1) * PI/4) ) + 0.5
|
||||
*
|
||||
* @param value Current R/G/B value of the pixel.
|
||||
* @param factor Factor to enhance contrast by.
|
||||
*/
|
||||
static inline float enhance_contrast(float value, float factor)
|
||||
{
|
||||
U_CHAR tan_pos = (U_CHAR) (factor * 127 + 127);
|
||||
return (value - 0.5) * (TAN[tan_pos]) + 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance brightness and contrast of an image.
|
||||
*
|
||||
* @param data Image pixel data to update.
|
||||
* @param size Total size of the data.
|
||||
* @param brightness Factor to enhance brightness by.
|
||||
* @param contrast Factor to enhance contrast by.
|
||||
*/
|
||||
static void enhance_bc_c(U_CHAR* data, const int size, float brightness, float contrast)
|
||||
{
|
||||
float value;
|
||||
|
||||
for (int pixel = 0; pixel < size; pixel++) {
|
||||
/* Skip alpha channel */
|
||||
if (pixel % 4 != ALPHA_CHANNEL) {
|
||||
value = ((float) data[pixel]) / 255.;
|
||||
value = enhance_brightness(value, brightness);
|
||||
value = enhance_contrast(value, contrast);
|
||||
value = pixel_value(value);
|
||||
data[pixel] = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
* C extension for vimiv
|
||||
* definitions usable for more modules.
|
||||
*******************************************************************************/
|
||||
#ifndef definitions_h__
|
||||
#define definitions_h__
|
||||
|
||||
/*****************************************
|
||||
* Alpha channel depends on endianness *
|
||||
|
@ -9,14 +11,23 @@
|
|||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN /* BGRA */
|
||||
|
||||
#define ALPHA_CHANNEL 3
|
||||
#define R_CHANNEL 2
|
||||
#define G_CHANNEL 1
|
||||
#define B_CHANNEL 0
|
||||
|
||||
#elif G_BYTE_ORDER == G_BIG_ENDIAN /* ARGB */
|
||||
|
||||
#define ALPHA_CHANNEL 0
|
||||
#define R_CHANNEL 1
|
||||
#define G_CHANNEL 2
|
||||
#define B_CHANNEL 3
|
||||
|
||||
#else /* PDP endianness: RABG */
|
||||
|
||||
#define ALPHA_CHANNEL 1
|
||||
#define R_CHANNEL 0
|
||||
#define G_CHANNEL 2
|
||||
#define B_CHANNEL 3
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -25,3 +36,5 @@
|
|||
*************/
|
||||
typedef unsigned short U_SHORT;
|
||||
typedef unsigned char U_CHAR;
|
||||
|
||||
#endif // ifndef definitions_h__
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*******************************************************************************
|
||||
* C extension for vimiv
|
||||
* Small inline helper functions
|
||||
*******************************************************************************/
|
||||
#ifndef helper_func_h__
|
||||
#define helper_func_h__
|
||||
|
||||
#include "definitions.h"
|
||||
|
||||
/**
|
||||
* Return the minimum of two numbers.
|
||||
*/
|
||||
inline float min2(float a, float b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum of two numbers.
|
||||
*/
|
||||
inline float max2(float a, float b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum of three numbers.
|
||||
*/
|
||||
inline float min3(float a, float b, float c) {
|
||||
if (a <= b && a <= c)
|
||||
return a;
|
||||
else if (b <= c)
|
||||
return b;
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum of three numbers.
|
||||
*/
|
||||
inline float max3(float a, float b, float c) {
|
||||
if (a >= b && a >= c)
|
||||
return a;
|
||||
else if (b >= c)
|
||||
return b;
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a number stays within lower and upper.
|
||||
*/
|
||||
inline float clamp(float value, float lower, float upper) {
|
||||
if (value < lower)
|
||||
return lower;
|
||||
if (value > upper)
|
||||
return upper;
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the remainder of a floating point division.
|
||||
*/
|
||||
inline float remainder_fl(float dividend, float divisor) {
|
||||
int intdiv = dividend / divisor;
|
||||
return dividend - intdiv * divisor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a valid pixel value (0..255) from a floating point value (0..1).
|
||||
*/
|
||||
static inline U_CHAR pixel_value(float value) {
|
||||
return (U_CHAR) clamp(value * 255, 0, 255);
|
||||
}
|
||||
|
||||
#endif // ifndef helper_func_h__
|
|
@ -0,0 +1,132 @@
|
|||
/*******************************************************************************
|
||||
* C extension for vimiv
|
||||
* Functions to enhance hue, saturation and value of an image.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "definitions.h"
|
||||
#include "helper_func.h"
|
||||
|
||||
/**
|
||||
* Enhance hue using the GIMP algorithm.
|
||||
*
|
||||
* @param hue Initial hue to enhance.
|
||||
* @param v Value to change hue by.
|
||||
*/
|
||||
inline float enhance_hue(float hue, float v) {
|
||||
hue += v;
|
||||
if (hue > 360)
|
||||
return hue - 360;
|
||||
if (hue < 0)
|
||||
return hue + 360;
|
||||
return hue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance saturation using the GIMP algorithm.
|
||||
*
|
||||
* @param saturation Initial saturation to enhance.
|
||||
* @param v Value to change saturation by.
|
||||
*/
|
||||
inline float enhance_saturation(float saturation, float v) {
|
||||
saturation *= (v + 1);
|
||||
return clamp(saturation, 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance lightness using the GIMP algorithm
|
||||
*
|
||||
* @param lightness Initial lightness to enhance.
|
||||
* @param v Value to change lightness by.
|
||||
*/
|
||||
inline float enhance_lightness(float lightness, float v) {
|
||||
if (v < 0)
|
||||
return lightness * (v + 1.0);
|
||||
return lightness + (v * (1.0 - lightness));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RGB to HSL.
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
|
||||
*/
|
||||
static void rgb_to_hsl(float r, float g, float b, float* h, float* s, float* l) {
|
||||
float MIN = min3(r, g, b);
|
||||
float MAX = max3(r, g, b);
|
||||
|
||||
// Hue
|
||||
if (MIN == MAX)
|
||||
*h = 0;
|
||||
else if (MAX == r)
|
||||
*h = 60 * (g - b) / (MAX - MIN);
|
||||
else if (MAX == g)
|
||||
*h = 60 * (2 + (b - r) / (MAX - MIN));
|
||||
else
|
||||
*h = 60 * (4 + (r - g) / (MAX - MIN));
|
||||
*h = *h < 0 ? *h + 360 : *h;
|
||||
|
||||
// Lightness
|
||||
*l = (MAX + MIN) / 2.;
|
||||
|
||||
// Saturation
|
||||
if (MAX == 0 || MIN == 1)
|
||||
*s = 0;
|
||||
else
|
||||
*s = (MAX - *l) / min2(*l, 1 - *l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert HSL to RGB.
|
||||
*/
|
||||
inline float hsl_to_rgb_helper(float a, float n, float h, float l) {
|
||||
float k = remainder_fl((n + h / 30.), 12.);
|
||||
return l - a * max2(min3(k - 3, 9 - k, 1), -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL to RGB.
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB.
|
||||
*/
|
||||
static void hsl_to_rgb(float h, float s, float l, float* r, float* g, float* b) {
|
||||
float a = s * min2(l, 1 - l);
|
||||
|
||||
*r = hsl_to_rgb_helper(a, 0, h, l);
|
||||
*g = hsl_to_rgb_helper(a, 8, h, l);
|
||||
*b = hsl_to_rgb_helper(a, 4, h, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance hue, saturation and lightness of an image.
|
||||
*
|
||||
* This requires converting the image data to the HSL space, applying changes there and then
|
||||
* converting back to RGB.
|
||||
*
|
||||
* @param data Image pixel data to update.
|
||||
* @param size Total size of the data.
|
||||
* @param hue Value to change hue by.
|
||||
* @param saturation Value to change saturation by.
|
||||
* @param lightness Value to change lightness by.
|
||||
*/
|
||||
static void enhance_hsl_c(U_CHAR* data, const int size, float hue, float saturation,
|
||||
float lightness)
|
||||
{
|
||||
float r, g, b, h, s, l;
|
||||
|
||||
int channels = 4; // RGBA channels
|
||||
|
||||
for (int pixel = 0; pixel < size; pixel += channels) {
|
||||
r = ((float) data[pixel + R_CHANNEL]) / 255.;
|
||||
g = ((float) data[pixel + G_CHANNEL]) / 255.;
|
||||
b = ((float) data[pixel + B_CHANNEL]) / 255.;
|
||||
rgb_to_hsl(r, g, b, &h, &s, &l);
|
||||
hsl_to_rgb(
|
||||
enhance_hue(h, hue),
|
||||
enhance_saturation(s, saturation),
|
||||
enhance_lightness(l, lightness),
|
||||
&r, &g, &b
|
||||
);
|
||||
data[pixel + R_CHANNEL] = pixel_value(r);
|
||||
data[pixel + G_CHANNEL] = pixel_value(g);
|
||||
data[pixel + B_CHANNEL] = pixel_value(b);
|
||||
}
|
||||
}
|
|
@ -7,8 +7,8 @@
|
|||
#include <Python.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "manipulate.h"
|
||||
#include "math_func_eval.h"
|
||||
#include "brightness_contrast.h"
|
||||
#include "hue_saturation_lightness.h"
|
||||
|
||||
/*****************************
|
||||
* Generate python functions *
|
||||
|
@ -40,12 +40,40 @@ manipulate_bc(PyObject *self, PyObject *args)
|
|||
return PyBytes_FromStringAndSize((char*) data, size);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
manipulate_hsl(PyObject *self, PyObject *args)
|
||||
{
|
||||
/* Receive arguments from python */
|
||||
PyObject *py_data;
|
||||
float hue;
|
||||
float saturation;
|
||||
float lightness;
|
||||
if (!PyArg_ParseTuple(args, "Offf",
|
||||
&py_data, &hue, &saturation, &lightness))
|
||||
return NULL;
|
||||
|
||||
/* Convert python bytes to U_CHAR* for pixel data */
|
||||
if (!PyBytes_Check(py_data)) {
|
||||
PyErr_SetString(PyExc_TypeError, "Expected bytes");
|
||||
return NULL;
|
||||
}
|
||||
U_CHAR* data = (U_CHAR*) PyBytes_AsString(py_data);
|
||||
const int size = PyBytes_Size(py_data);
|
||||
|
||||
/* Run the C function to enhance brightness and contrast */
|
||||
enhance_hsl_c(data, size, hue, saturation, lightness);
|
||||
|
||||
/* Return python bytes of updated data */
|
||||
return PyBytes_FromStringAndSize((char*) data, size);
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Initialize python module *
|
||||
*****************************/
|
||||
|
||||
static PyMethodDef ManipulateMethods[] = {
|
||||
{"manipulate", manipulate_bc, METH_VARARGS, "Manipulate brightness and contrast"},
|
||||
{"brightness_contrast", manipulate_bc, METH_VARARGS, "Manipulate brightness and contrast"},
|
||||
{"hue_saturation_lightness", manipulate_hsl, METH_VARARGS, "Manipulate hue, saturation and lightness"},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
@ -65,60 +93,3 @@ PyInit__c_manipulate(void)
|
|||
return NULL;
|
||||
return m;
|
||||
}
|
||||
|
||||
/***************************************
|
||||
* Actual C functions doing the math *
|
||||
***************************************/
|
||||
|
||||
/* Make sure value stays between 0 and 255 */
|
||||
static inline U_CHAR clamp(float value)
|
||||
{
|
||||
if (value < 0)
|
||||
return 0;
|
||||
else if (value > 1)
|
||||
return 255;
|
||||
return (U_CHAR) (value * 255);
|
||||
}
|
||||
|
||||
/* Enhance brightness using the GIMP algorithm. */
|
||||
static inline float enhance_brightness(float value, float factor)
|
||||
{
|
||||
if (factor < 0)
|
||||
return value * (1 + factor);
|
||||
return value + (1 - value) * factor;
|
||||
}
|
||||
|
||||
/* Enhance contrast using the GIMP algorithm:
|
||||
value = (value - 0.5) * (tan ((factor + 1) * PI/4) ) + 0.5; */
|
||||
static inline float enhance_contrast(float value, float factor)
|
||||
{
|
||||
U_CHAR tan_pos = (U_CHAR) (factor * 127 + 127);
|
||||
return (value - 0.5) * (TAN[tan_pos]) + 0.5;
|
||||
}
|
||||
|
||||
/* Return the ARGB content of one pixel at index in data. */
|
||||
static inline void set_pixel_content(U_CHAR* data, int index, U_CHAR* content)
|
||||
{
|
||||
for (U_SHORT i = 0; i < 4; i++)
|
||||
content[i] = data[index + i];
|
||||
}
|
||||
|
||||
/* Read pixel data of specific size and enhance brightness and contrast
|
||||
according to the two functions above. Change the values in which
|
||||
is of type char* so one pixel is equal to one byte allowing to create a
|
||||
python memoryview obect directly from memory. */
|
||||
void enhance_bc_c(U_CHAR* data, const int size, float brightness, float contrast)
|
||||
{
|
||||
float value;
|
||||
|
||||
for (int pixel = 0; pixel < size; pixel++) {
|
||||
/* Skip alpha channel */
|
||||
if (pixel % 4 != ALPHA_CHANNEL) {
|
||||
value = ((float) data[pixel]) / 255.;
|
||||
value = enhance_brightness(value, brightness);
|
||||
value = enhance_contrast(value, contrast);
|
||||
value = clamp(value);
|
||||
data[pixel] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
/*******************************************************************************
|
||||
* C extension for vimiv
|
||||
* simple add-on to enhance brightness and contrast of an image on the pixel
|
||||
* scale.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "definitions.h"
|
||||
|
||||
/**********************************
|
||||
* Plain C function declarations *
|
||||
**********************************/
|
||||
static inline U_CHAR clamp(float value);
|
||||
static inline float enhance_brightness(float value, float factor);
|
||||
static inline float enhance_contrast(float value, float factor);
|
||||
static void enhance_bc_c(U_CHAR* data, const int size, float brightness, float contrast);
|
|
@ -2,6 +2,8 @@
|
|||
* C extension for vimiv
|
||||
* values of used math functions as array to greatly speed up computation
|
||||
*******************************************************************************/
|
||||
#ifndef math_func_eval_h__
|
||||
#define math_func_eval_h__
|
||||
|
||||
/* Corresponds to tan(0) ... tan(pi/2) */
|
||||
const float TAN[256] = {
|
||||
|
@ -262,3 +264,5 @@ const float TAN[256] = {
|
|||
162.33598862000602,
|
||||
1.633123935319537e+16
|
||||
};
|
||||
|
||||
#endif // ifndef math_func_eval_h__
|
||||
|
|
Loading…
Reference in New Issue