/* This file is an image processing operation for GEGL * * GEGL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * GEGL 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with GEGL; if not, see . * * Copyright 2008 Jan Heller */ #include "config.h" #include #define LOWEST_TEMPERATURE 1000 #define HIGHEST_TEMPERATURE 12000 #ifdef GEGL_CHANT_PROPERTIES gegl_chant_double (original_temperature, _("Original temperature"), LOWEST_TEMPERATURE, HIGHEST_TEMPERATURE, 6500, _("Estimated temperature of the light source in Kelvin the image was taken with.")) gegl_chant_double (intended_temperature, _("Intended temperature"), LOWEST_TEMPERATURE, HIGHEST_TEMPERATURE, 6500, _("Corrected estimation of the temperature of the light source in Kelvin.")) #else #define GEGL_CHANT_TYPE_POINT_FILTER #define GEGL_CHANT_C_FILE "color-temperature.c" #include "gegl-chant.h" static const gfloat rgb_r55[3][12]; static void convert_k_to_rgb (gfloat temperature, gfloat *rgb) { gint channel; if (temperature < LOWEST_TEMPERATURE) temperature = LOWEST_TEMPERATURE; if (temperature > HIGHEST_TEMPERATURE) temperature = HIGHEST_TEMPERATURE; /* Evaluation of an approximation of the Planckian locus in linear RGB space * by rational functions of degree 5 using Horner's scheme * f(x) = (p1*x^5 + p2*x^4 + p3*x^3 + p4*x^2 + p5*x + p6) / * (x^5 + q1*x^4 + q2*x^3 + q3*x^2 + q4*x + q5) */ for (channel = 0; channel < 3; channel++) { gfloat nomin, denom; gint deg; nomin = rgb_r55[channel][0]; for (deg = 1; deg < 6; deg++) nomin = nomin * temperature + rgb_r55[channel][deg]; denom = rgb_r55[channel][6]; for (deg = 1; deg < 6; deg++) denom = denom * temperature + rgb_r55[channel][6 + deg]; rgb[channel] = nomin / denom; } } static void prepare (GeglOperation *operation) { const Babl *format = babl_format ("RGBA float"); gegl_operation_set_format (operation, "input", format); gegl_operation_set_format (operation, "output", format); } static void finalize (GObject *object) { GeglChantO *o = GEGL_CHANT_PROPERTIES (object); if (o->chant_data) { g_free (o->chant_data); o->chant_data = NULL; } G_OBJECT_CLASS (gegl_chant_parent_class)->finalize (object); } static void notify (GObject *object, GParamSpec *pspec) { if (strcmp (pspec->name, "original-temperature") == 0 || strcmp (pspec->name, "intended-temperature") == 0) { GeglChantO *o = GEGL_CHANT_PROPERTIES (object); /* one of the properties has changed, * invalidate the preprocessed coefficients */ if (o->chant_data) { g_free (o->chant_data); o->chant_data = NULL; } } if (G_OBJECT_CLASS (gegl_chant_parent_class)->notify) G_OBJECT_CLASS (gegl_chant_parent_class)->notify (object, pspec); } static gfloat * preprocess (GeglChantO *o) { gfloat *coeffs = g_new (gfloat, 3); gfloat original_temperature_rgb[3]; gfloat intended_temperature_rgb[3]; convert_k_to_rgb (o->original_temperature, original_temperature_rgb); convert_k_to_rgb (o->intended_temperature, intended_temperature_rgb); coeffs[0] = original_temperature_rgb[0] / intended_temperature_rgb[0]; coeffs[1] = original_temperature_rgb[1] / intended_temperature_rgb[1]; coeffs[2] = original_temperature_rgb[2] / intended_temperature_rgb[2]; return coeffs; } /* GeglOperationPointFilter gives us a linear buffer to operate on * in our requested pixel format */ static gboolean process (GeglOperation *op, void *in_buf, void *out_buf, glong n_pixels, const GeglRectangle *roi, gint level) { GeglChantO *o = GEGL_CHANT_PROPERTIES (op); gfloat *in_pixel = in_buf; gfloat *out_pixel = out_buf; const gfloat *coeffs = o->chant_data; in_pixel = in_buf; out_pixel = out_buf; if (! coeffs) { coeffs = o->chant_data = preprocess (o); } while (n_pixels--) { out_pixel[0] = in_pixel[0] * coeffs[0]; out_pixel[1] = in_pixel[1] * coeffs[1]; out_pixel[2] = in_pixel[2] * coeffs[2]; out_pixel[3] = in_pixel[3]; in_pixel += 4; out_pixel += 4; } return TRUE; } #include "opencl/gegl-cl.h" static const char* kernel_source = "__kernel void kernel_temp(__global const float4 *in, \n" " __global float4 *out, \n" " float coeff1, \n" " float coeff2, \n" " float coeff3) \n" "{ \n" " int gid = get_global_id(0); \n" " float4 in_v = in[gid]; \n" " float4 out_v; \n" " out_v.xyz = in_v.xyz * (float3) (coeff1, coeff2, coeff3); \n" " out_v.w = in_v.w; \n" " out[gid] = out_v; \n" "} \n"; static gegl_cl_run_data *cl_data = NULL; /* OpenCL processing function */ static cl_int cl_process (GeglOperation *op, cl_mem in_tex, cl_mem out_tex, size_t global_worksize, const GeglRectangle *roi, int level) { /* Retrieve a pointer to GeglChantO structure which contains all the * chanted properties */ GeglChantO *o = GEGL_CHANT_PROPERTIES (op); const gfloat *coeffs = o->chant_data; cl_int cl_err = 0; if (! coeffs) { coeffs = o->chant_data = preprocess (o); } if (!cl_data) { const char *kernel_name[] = {"kernel_temp", NULL}; cl_data = gegl_cl_compile_and_build (kernel_source, kernel_name); } if (!cl_data) return 1; cl_err |= gegl_clSetKernelArg(cl_data->kernel[0], 0, sizeof(cl_mem), (void*)&in_tex); cl_err |= gegl_clSetKernelArg(cl_data->kernel[0], 1, sizeof(cl_mem), (void*)&out_tex); cl_err |= gegl_clSetKernelArg(cl_data->kernel[0], 2, sizeof(cl_float), (void*)&coeffs[0]); cl_err |= gegl_clSetKernelArg(cl_data->kernel[0], 3, sizeof(cl_float), (void*)&coeffs[1]); cl_err |= gegl_clSetKernelArg(cl_data->kernel[0], 4, sizeof(cl_float), (void*)&coeffs[2]); if (cl_err != CL_SUCCESS) return cl_err; cl_err = gegl_clEnqueueNDRangeKernel(gegl_cl_get_command_queue (), cl_data->kernel[0], 1, NULL, &global_worksize, NULL, 0, NULL, NULL); if (cl_err != CL_SUCCESS) return cl_err; return cl_err; } static void gegl_chant_class_init (GeglChantClass *klass) { GObjectClass *object_class; GeglOperationClass *operation_class; GeglOperationPointFilterClass *point_filter_class; object_class = G_OBJECT_CLASS (klass); operation_class = GEGL_OPERATION_CLASS (klass); point_filter_class = GEGL_OPERATION_POINT_FILTER_CLASS (klass); object_class->finalize = finalize; object_class->notify = notify; operation_class->prepare = prepare; point_filter_class->process = process; point_filter_class->cl_process = cl_process; operation_class->opencl_support = TRUE; gegl_operation_class_set_keys (operation_class, "name" , "gegl:color-temperature", "categories" , "color", "description", _("Allows changing the color temperature of an image."), NULL); } /* Coefficients of rational functions of degree 5 fitted per color channel to * the linear RGB coordinates of the range 1000K-12000K of the Planckian locus * with the 20K step. Original CIE-xy data from * * http://www.aim-dtp.net/aim/technology/cie_xyz/k2xy.txt * * converted to the linear RGB space assuming the ITU-R BT.709-5/sRGB primaries */ static const gfloat rgb_r55[3][12] = { { 6.9389923563552169e-01, 2.7719388100974670e+03, 2.0999316761104289e+07, -4.8889434162208414e+09, -1.1899785506796783e+07, -4.7418427686099203e+04, 1.0000000000000000e+00, 3.5434394338546258e+03, -5.6159353379127791e+05, 2.7369467137870544e+08, 1.6295814912940913e+08, 4.3975072422421846e+05 }, { 9.5417426141210926e-01, 2.2041043287098860e+03, -3.0142332673634286e+06, -3.5111986367681120e+03, -5.7030969525354260e+00, 6.1810926909962016e-01, 1.0000000000000000e+00, 1.3728609973644000e+03, 1.3099184987576159e+06, -2.1757404458816318e+03, -2.3892456292510311e+00, 8.1079012401293249e-01 }, { -7.1151622540856201e+10, 3.3728185802339764e+16, -7.9396187338868539e+19, 2.9699115135330123e+22, -9.7520399221734228e+22, -2.9250107732225114e+20, 1.0000000000000000e+00, 1.3888666482167408e+16, 2.3899765140914549e+19, 1.4583606312383295e+23, 1.9766018324502894e+22, 2.9395068478016189e+18 } }; #endif