diff --git a/Curve/Curve.cpp b/Curve/Curve.cpp index a3a18b0..de7d1de 100644 --- a/Curve/Curve.cpp +++ b/Curve/Curve.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -50,23 +51,16 @@ struct keypoint { std::shared_ptr next; }; -static void parsePoints(const char * s, std::shared_ptr & points, const int scale) { - char * p = const_cast(s); +static void parsePoints(const std::vector & p, std::shared_ptr & points, const int scale) { std::shared_ptr last; - // construct a linked list based on the key points string - while (p && *p) { + // construct a linked list based on the key points + for (size_t i = 0; i < p.size(); i += 2) { std::shared_ptr point = std::make_shared(); + point->x = p[i + 0]; + point->y = p[i + 1]; - point->x = std::strtod(p, &p); - if (p && *p) - p++; - - point->y = std::strtod(p, &p); - if (p && *p) - p++; - - if (point->x < 0. || point->x > 1. || point->y < 0. || point->y > 1.) + if (point->x < 0.0 || point->x > 1.0 || point->y < 0.0 || point->y > 1.0) throw std::string{ "invalid key point coordinates, x and y must be in the [0;1] range" }; if (!points) @@ -100,7 +94,7 @@ static int getNumPoints(const keypoint * d) noexcept { */ static void interpolate(const keypoint * points, uint16_t * VS_RESTRICT y, const int lutSize, const int scale) { const keypoint * point = points; - double xPrev = 0.; + double xPrev = 0.0; const int n = getNumPoints(points); // number of splines @@ -110,7 +104,7 @@ static void interpolate(const keypoint * points, uint16_t * VS_RESTRICT y, const return; } - double (*matrix)[3] = reinterpret_cast(calloc(n, sizeof(*matrix))); + double (*matrix)[3] = reinterpret_cast(calloc(n, sizeof(*matrix))); double * h = reinterpret_cast(malloc((n - 1) * sizeof(*h))); double * r = reinterpret_cast(calloc(n, sizeof(*r))); if (!matrix || !h || !r) @@ -150,7 +144,7 @@ static void interpolate(const keypoint * points, uint16_t * VS_RESTRICT y, const // tridiagonal solving of the linear system for (i = 1; i < n; i++) { const double den = matrix[i][MD] - matrix[i][BD] * matrix[i - 1][AD]; - const double k = den ? 1. / den : 1.; + const double k = den ? 1.0 / den : 1.0; matrix[i][AD] *= k; r[i] = (r[i] - matrix[i][BD] * r[i - 1]) * k; } @@ -170,9 +164,9 @@ static void interpolate(const keypoint * points, uint16_t * VS_RESTRICT y, const const double yn = point->next->y; const double a = yc; - const double b = (yn - yc) / h[i] - h[i] * r[i] / 2. - h[i] * (r[i + 1] - r[i]) / 6.; - const double c = r[i] / 2.; - const double d = (r[i + 1] - r[i]) / (6. * h[i]); + const double b = (yn - yc) / h[i] - h[i] * r[i] / 2.0 - h[i] * (r[i + 1] - r[i]) / 6.0; + const double c = r[i] / 2.0; + const double d = (r[i + 1] - r[i]) / (6.0 * h[i]); const int xStart = static_cast(point->x * scale + 0.5); const int xEnd = static_cast(point->next->x * scale + 0.5); @@ -207,7 +201,7 @@ static void filter(const VSFrameRef * src, VSFrameRef * dst, const CurveData * c for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) - dstp[x] = d->graph[plane][srcp[x]]; + dstp[x] = static_cast(d->graph[plane][srcp[x]]); srcp += stride; dstp += stride; @@ -216,12 +210,12 @@ static void filter(const VSFrameRef * src, VSFrameRef * dst, const CurveData * c } } -static void VS_CC curveInit(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi) { +static void VS_CC curveInit(VSMap * in, VSMap * out, void ** instanceData, VSNode * node, VSCore * core, const VSAPI * vsapi) { CurveData * d = static_cast(*instanceData); vsapi->setVideoInfo(d->vi, 1, node); } -static const VSFrameRef *VS_CC curveGetFrame(int n, int activationReason, void **instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) { +static const VSFrameRef * VS_CC curveGetFrame(int n, int activationReason, void ** instanceData, void ** frameData, VSFrameContext * frameCtx, VSCore * core, const VSAPI * vsapi) { const CurveData * d = static_cast(*instanceData); if (activationReason == arInitial) { @@ -244,15 +238,15 @@ static const VSFrameRef *VS_CC curveGetFrame(int n, int activationReason, void * return nullptr; } -static void VS_CC curveFree(void *instanceData, VSCore *core, const VSAPI *vsapi) { +static void VS_CC curveFree(void * instanceData, VSCore * core, const VSAPI * vsapi) { CurveData * d = static_cast(instanceData); vsapi->freeNode(d->node); delete d; } -static void VS_CC curveCreate(const VSMap *in, VSMap *out, void *userData, VSCore *core, const VSAPI *vsapi) { +static void VS_CC curveCreate(const VSMap * in, VSMap * out, void * userData, VSCore * core, const VSAPI * vsapi) { std::unique_ptr d = std::make_unique(); - int err = 0; + int err; d->node = vsapi->propGetNode(in, "clip", 0, nullptr); d->vi = vsapi->getVideoInfo(d->node); @@ -265,41 +259,66 @@ static void VS_CC curveCreate(const VSMap *in, VSMap *out, void *userData, VSCor const int preset = int64ToIntS(vsapi->propGetInt(in, "preset", 0, &err)); - int numCurves = vsapi->propNumElements(in, "curve"); + const double * r = vsapi->propGetFloatArray(in, "r", &err); + const int numR = vsapi->propNumElements(in, "r"); + + const double * g = vsapi->propGetFloatArray(in, "g", &err); + const int numG = vsapi->propNumElements(in, "g"); + + const double * b = vsapi->propGetFloatArray(in, "b", &err); + const int numB = vsapi->propNumElements(in, "b"); + + const double * master = vsapi->propGetFloatArray(in, "master", &err); + const int numMaster = vsapi->propNumElements(in, "master"); const char * acv = vsapi->propGetData(in, "acv", 0, &err); - const int m = vsapi->propNumElements(in, "planes"); + const int numPlanes = vsapi->propNumElements(in, "planes"); for (int i = 0; i < 3; i++) - d->process[i] = (m <= 0); + d->process[i] = (numPlanes <= 0); - for (int i = 0; i < m; i++) { - const int n = int64ToIntS(vsapi->propGetInt(in, "planes", i, nullptr)); + for (int i = 0; i < numPlanes; i++) { + const int plane = int64ToIntS(vsapi->propGetInt(in, "planes", i, nullptr)); - if (n < 0 || n >= d->vi->format->numPlanes) + if (plane < 0 || plane >= d->vi->format->numPlanes) throw std::string{ "plane index out of range" }; - if (d->process[n]) + if (d->process[plane]) throw std::string{ "plane specified twice" }; - d->process[n] = true; + d->process[plane] = true; } if (preset < 0 || preset > 10) throw std::string{ "preset must be 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, or 10" }; - if (numCurves > d->vi->format->numPlanes) - throw std::string{ "more curves given than there are planes" }; + if (r && (numR & 1)) + throw std::string{ "the number of elements in r must be a multiple of 2" }; + + if (g && (numG & 1)) + throw std::string{ "the number of elements in g must be a multiple of 2" }; + + if (b && (numB & 1)) + throw std::string{ "the number of elements in b must be a multiple of 2" }; + + if (master && (numMaster & 1)) + throw std::string{ "the number of elements in master must be a multiple of 2" }; + + std::vector curve[4]; - const char * curve[4] = {}; + if (r) + curve[0].assign(r, r + numR); - for (int i = 0; i < numCurves; i++) - curve[i] = vsapi->propGetData(in, "curve", i, nullptr); + if (g) + curve[1].assign(g, g + numG); - curve[3] = vsapi->propGetData(in, "master", 0, &err); + if (b) + curve[2].assign(b, b + numB); + + if (master) + curve[3].assign(master, master + numMaster); - std::unique_ptr acvCurve[4]; if (acv) { FILE * acvFile = nullptr; @@ -336,95 +355,89 @@ static void VS_CC curveCreate(const VSMap *in, VSMap *out, void *userData, VSCor std::fclose(acvFile); -#define READ16(dst) do { \ - if (size < 2) \ - throw std::string{ "invalid acv file" }; \ - dst = (reinterpret_cast(buf)[0] << 8) | reinterpret_cast(buf)[1]; \ - buf += 2; \ - size -= 2; \ -} while (0) - #if defined(__GNUC__) || defined(__clang__) #define UNUSED __attribute__((unused)) #else #define UNUSED #endif +#define READ16(dst) do { \ + if (size < 2) \ + throw std::string{ "invalid acv file" }; \ + dst = (buf[0] << 8) | buf[1]; \ + buf += 2; \ + size -= 2; \ +} while (0) + + int version UNUSED = 0, numCurves = 0; constexpr int curveIndex[] = { 3, 0, 1, 2 }; - int version UNUSED = 0; - numCurves = 0; READ16(version); READ16(numCurves); + for (int i = 0; i < std::min(numCurves, 4); i++) { int numPoints = 0; READ16(numPoints); - std::string str; + std::vector acvCurve; + const int j = curveIndex[i]; for (int n = 0; n < numPoints; n++) { int y = 0, x = 0; READ16(y); READ16(x); - char tmp[64] = {}; - std::snprintf(tmp, 64, "%.20f/%.20f ", x / 255., y / 255.); - str += std::string{ tmp }; + acvCurve.push_back(x / 255.0); + acvCurve.push_back(y / 255.0); } - if (!str.empty()) { - const int j = curveIndex[i]; - if (!(curve[j] && *curve[j])) { - acvCurve[j] = std::make_unique(str.length() + 1); - std::strcpy(acvCurve[j].get(), str.c_str()); - curve[j] = acvCurve[j].get(); - } - } + if (curve[j].empty() && !acvCurve.empty()) + curve[j] = acvCurve; } -#undef READ16 #undef UNUSED +#undef READ16 } if (preset == 1) { - if (!(curve[0] && *curve[0])) - curve[0] = "0.129/1 0.466/0.498 0.725/0"; - if (!(curve[1] && *curve[1])) - curve[1] = "0.109/1 0.301/0.498 0.517/0"; - if (!(curve[2] && *curve[2])) - curve[2] = "0.098/1 0.235/0.498 0.423/0"; + if (curve[0].empty()) + curve[0] = { 0.129,1, 0.466,0.498, 0.725,0 }; + if (curve[1].empty()) + curve[1] = { 0.109,1, 0.301,0.498, 0.517,0 }; + if (curve[2].empty()) + curve[2] = { 0.098,1, 0.235,0.498, 0.423,0 }; } else if (preset == 2) { - if (!(curve[0] && *curve[0])) - curve[0] = "0/0 0.25/0.156 0.501/0.501 0.686/0.745 1/1"; - if (!(curve[1] && *curve[1])) - curve[1] = "0/0 0.25/0.188 0.38/0.501 0.745/0.815 1/0.815"; - if (!(curve[2] && *curve[2])) - curve[2] = "0/0 0.231/0.094 0.709/0.874 1/1"; + if (curve[0].empty()) + curve[0] = { 0,0, 0.25,0.156, 0.501,0.501, 0.686,0.745, 1,1 }; + if (curve[1].empty()) + curve[1] = { 0,0, 0.25,0.188, 0.38,0.501, 0.745,0.815, 1,0.815 }; + if (curve[2].empty()) + curve[2] = { 0,0, 0.231,0.094, 0.709,0.874, 1,1 }; } else if (preset == 3) { - if (!(curve[3] && *curve[3])) - curve[3] = "0/0 0.5/0.4 1/1"; + if (curve[3].empty()) + curve[3] = { 0,0, 0.5,0.4, 1,1 }; } else if (preset == 4) { - if (!(curve[3] && *curve[3])) - curve[3] = "0/0 0.149/0.066 0.831/0.905 0.905/0.98 1/1"; + if (curve[3].empty()) + curve[3] = { 0,0, 0.149,0.066, 0.831,0.905, 0.905,0.98, 1,1 }; } else if (preset == 5) { - if (!(curve[3] && *curve[3])) - curve[3] = "0/0 0.4/0.5 1/1"; + if (curve[3].empty()) + curve[3] = { 0,0, 0.4,0.5, 1,1 }; } else if (preset == 6) { - if (!(curve[3] && *curve[3])) - curve[3] = "0/0 0.305/0.286 0.694/0.713 1/1"; + if (curve[3].empty()) + curve[3] = { 0,0, 0.305,0.286, 0.694,0.713, 1,1 }; } else if (preset == 7) { - if (!(curve[3] && *curve[3])) - curve[3] = "0/0 0.286/0.219 0.639/0.643 1/1"; + if (curve[3].empty()) + curve[3] = { 0,0, 0.286,0.219, 0.639,0.643, 1,1 }; } else if (preset == 8) { - if (!(curve[3] && *curve[3])) - curve[3] = "0/1 1/0"; + if (curve[3].empty()) + curve[3] = { 0,1, 1,0 }; } else if (preset == 9) { - if (!(curve[3] && *curve[3])) - curve[3] = "0/0 0.301/0.196 0.592/0.6 0.686/0.737 1/1"; + if (curve[3].empty()) + curve[3] = { 0,0, 0.301,0.196, 0.592,0.6, 0.686,0.737, 1,1 }; } else if (preset == 10) { - if (!(curve[0] && *curve[0])) - curve[0] = "0/0.11 0.42/0.51 1/0.95"; - if (!(curve[1] && *curve[1])) - curve[1] = "0/0 0.50/0.48 1/1"; - if (!(curve[2] && *curve[2])) - curve[2] = "0/0.22 0.49/0.44 1/0.8"; + if (curve[0].empty()) + curve[0] = { 0,0.11, 0.42,0.51, 1,0.95 }; + if (curve[1].empty()) + curve[1] = { 0,0, 0.5,0.48, 1,1 }; + if (curve[2].empty()) + curve[2] = { 0,0.22, 0.49,0.44, 1,0.8 }; } const int lutSize = 1 << d->vi->format->bitsPerSample; @@ -437,7 +450,7 @@ static void VS_CC curveCreate(const VSMap *in, VSMap *out, void *userData, VSCor interpolate(points[i].get(), d->graph[i].get(), lutSize, scale); } - if (curve[3]) { + if (!curve[3].empty()) { for (int i = 0; i < 3; i++) { for (int j = 0; j < lutSize; j++) d->graph[i][j] = d->graph[3][d->graph[i][j]]; @@ -455,13 +468,15 @@ static void VS_CC curveCreate(const VSMap *in, VSMap *out, void *userData, VSCor ////////////////////////////////////////// // Init -VS_EXTERNAL_API(void) VapourSynthPluginInit(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin *plugin) { +VS_EXTERNAL_API(void) VapourSynthPluginInit(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin * plugin) { configFunc("com.holywu.curve", "curve", "Apply color adjustments using curves", VAPOURSYNTH_API_VERSION, 1, plugin); registerFunc("Curve", "clip:clip;" "preset:int:opt;" - "curve:data[]:opt;" - "master:data:opt;" + "r:float[]:opt;" + "g:float[]:opt;" + "b:float[]:opt;" + "master:float[]:opt;" "acv:data:opt;" "planes:int[]:opt;", curveCreate, nullptr, plugin); diff --git a/README.md b/README.md index a6f7b27..b3e3f7c 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ The filter allows you to redefine these two points and add some more. A new curv Usage ===== - curve.Curve(clip clip[, int preset=0, string[] curve=['', '', ''], string master='', string acv='', int[] planes=[0, 1, 2]]) + curve.Curve(clip clip[, int preset=0, float[] r=None, float[] g=None, float[] b=None, float[] master=None, string acv=None, int[] planes=[0, 1, 2]]) * clip: Clip to process. Any planar format with integer sample type of 8-16 bit depth is supported. -* preset: Selects one of the available color presets. Can be used in addition to the `curve` parameter. In this case, the later parameter takes priority on the preset values. Note that the values of preset 1, 2, 10 are defined in RGB and should not be applied to YUV clip. The other presets can be used on YUV clip, but only the first plane should be applied except preset 8. +* preset: Selects one of the available color presets. This parameter can be used in addition to the `r`, `g`, `b` parameters; in this case, the later parameters takes priority on the preset values. Note that the values of preset 1, 2, 10 are defined in RGB and should not be applied to YUV clip. The other presets can be used on YUV clip, but only the first plane should be applied, except preset 8. * 0 = none * 1 = color negative * 2 = cross process @@ -30,15 +30,27 @@ Usage * 9 = strong contrast * 10 = vintage -* curve: Sets the key points for each plane. The syntax of key points list is `x0/y0 x1/y1 x2/y2 ...`. However, the delimiter character is arbitrary. `x0;y0 x1:y1 x2-y2 ...` works just fine. +* r: Sets the key points for the red/y plane. -* master: Sets the master key points. These points will define a second pass mapping. It is sometimes called a "luminance" or "value" mapping. It can be used with `curve` since it acts like a post-processing LUT. +* g: Sets the key points for the green/u plane. + +* b: Sets the key points for the blue/v plane. + +* master: Sets the master key points. These points will define a second pass mapping. It is sometimes called a "luminance" or "value" mapping. It can be used with `r`, `g`, `b` since it acts like a post-processing LUT. * acv: Specifies a Photoshop curves file (.acv) to import the settings from. * planes: Sets which planes will be processed. Any unprocessed planes will be simply copied. +Examples +======== + +* Increase slightly the middle level of blue: ```curve.Curve(clip, b=[0,0, 0.5,0.58, 1,1])``` + +* Vintage effect: ```curve.Curve(clip, r=[0,0.11, 0.42,0.51, 1,0.95], g=[0,0, 0.5,0.48, 1,1], b=[0,0.22, 0.49,0.44, 1,0.8])``` + + Compilation =========== diff --git a/meson.build b/meson.build index 2340a90..4538f9a 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('Curve', 'cpp', default_options : ['buildtype=release', 'b_ndebug=if-release', 'cpp_std=c++14'], meson_version : '>=0.48.0', - version : '2' + version : '3' ) add_project_arguments('-ffast-math', language : 'cpp')