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:
karlch 2019-06-20 22:05:50 +02:00
parent 32c868f6db
commit 2eab4db8cd
7 changed files with 311 additions and 75 deletions

View File

@ -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;
}
}
}

View File

@ -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__

72
c-extension/helper_func.h Normal file
View File

@ -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__

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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__