From d4aa7bcff0c552f372b079d0837b8409549a87b1 Mon Sep 17 00:00:00 2001 From: mawen1250 Date: Tue, 28 Oct 2014 21:10:36 +0800 Subject: [PATCH] Add new function MSRCR Add new function MSRCR. Add comments to the procedures. --- README.md | 67 +++++++- include/Helper.h | 56 ++++--- include/MSR.h | 113 +++++++++++++- include/MSRCP.h | 33 +++- include/MSRCR.h | 286 +++++++++++++++++++++++++++++++++++ msvc/Retinex.vcxproj | 2 + msvc/Retinex.vcxproj.filters | 6 + source/MSR.cpp | 118 +++++++-------- source/MSRCR.cpp | 77 ++++++++++ source/VSPlugin.cpp | 2 + 10 files changed, 669 insertions(+), 91 deletions(-) create mode 100644 include/MSRCR.h create mode 100644 source/MSRCR.cpp diff --git a/README.md b/README.md index c35bcf2..56d4ab2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ VapourSynth plugin namespace: retinex -functions: MSRCP +functions: MSRCP, MSRCR ## About Retinex @@ -94,3 +94,68 @@ i = core.lsmas.LWLibavSource(r'Image.png') i = core.fmtc.bitdepth(i, bits=16) i = core.retinex.MSRCP(i) ``` + +## MSRCR + +### Description + +MSRCR(Multi Scale Retinex with Color Restoration) is based on MSR. It applies MSR to each spectral channel (e.g. R, G and B), and modify the MSR output by multiplying it by a color restoration function of the chromaticity. + +When MSR is applied to each spectral channel, it assumes the image obey gray world assumption. Otherwise, if the image violates gray world assumption, the MSR will produce grayish image by decreasing the color saturation, thus the color restoration step is proposed to solve this problem. However, for images with nice color balance, MSRCR still produces a desaturated look. Hence it is recommended to use MSRCP in most cases, and only apply MSRCR to the images with color cast. Also since MSRCR applies MSR to each spectral channel instead of intensity channel, it is slower than MSRCP. + +This function only accept 8-16bit integer RGB input. + +### Usage + +```python +retinex.MSRCR(clip input, float[] sigmaS=[25,80,250], float lower_thr=0.001, float upper_thr=0.001, bool fulls=True, bool fulld=fulls, float restore=125) +``` + +- input:
+ clip to process + +- sigma: (Default: [25,80,250])
+ The same as MSRCP. + +- lower_thr: (Default: 0.001)
+ The same as MSRCP. + +- upper_thr: (Default: 0.001)
+ The same as MSRCP. + +- fulls: (Default: True)
+ The same as MSRCP. + +- fulld: (Default: fulls)
+ The same as MSRCP. + +- restore: (Default: 125)
+ The strength of the nonlinearity for color restoration function, larger value result in stronger restoration, available range is [0, +inf).
+ It is a multiplier in a log function, so try to adjust it in a large scale (e.g. multiply it by a power of 10) if you want to see any difference. + +### Example + +TV range YUV420P8 input, filtered in PC range RGB48, output PC range RGB48 + +```python +v = core.fmtc.resample(v, csp=vs.YUV444P16) +v = core.fmtc.matrix(v, mat="709", csp=vs.RGB48) +v = core.retinex.MSRCR(v) +``` + +JPEG image(PC range YUV420P8 with MPEG-1 chroma placement) input, filtered in PC range RGB48 without color restoration (pure MSR), output PC range RGB48 + +```python +i = core.lsmas.LWLibavSource(r'Image.jpg') +i = core.fmtc.resample(i, csp=vs.YUV444P16, fulls=True, cplace="MPEG1") +i = core.fmtc.matrix(i, mat="601", fulls=True, csp=vs.RGB48) +i = core.retinex.MSRCR(i, restore=0) +``` + +PNG image(PC range RGB24) input, filtered in PC range RGB48, output PC range RGB48 + +```python +i = core.lsmas.LWLibavSource(r'Image.png') +i = core.fmtc.bitdepth(i, bits=16) +i = core.retinex.MSRCR(i) +``` diff --git a/include/Helper.h b/include/Helper.h index c36234d..5741d72 100644 --- a/include/Helper.h +++ b/include/Helper.h @@ -192,22 +192,23 @@ class VSProcess const VSFormat *fi = nullptr; VSFrameRef *dst = nullptr; + int PlaneCount; int Bps; int bps; - int stride; - int width; int height; + int width; + int stride; int pcount; - int src_stride[VSMaxPlaneCount]; - int src_width[VSMaxPlaneCount]; int src_height[VSMaxPlaneCount]; + int src_width[VSMaxPlaneCount]; + int src_stride[VSMaxPlaneCount]; int src_pcount[VSMaxPlaneCount]; - int dst_stride[VSMaxPlaneCount]; - int dst_width[VSMaxPlaneCount]; int dst_height[VSMaxPlaneCount]; + int dst_width[VSMaxPlaneCount]; + int dst_stride[VSMaxPlaneCount]; int dst_pcount[VSMaxPlaneCount]; private: @@ -225,32 +226,37 @@ class VSProcess src = vsapi->getFrameFilter(n, d.node, frameCtx); fi = vsapi->getFrameFormat(src); + PlaneCount = fi->numPlanes; Bps = fi->bytesPerSample; bps = fi->bitsPerSample; - stride = vsapi->getStride(src, 0) / Bps; - width = vsapi->getFrameWidth(src, 0); height = vsapi->getFrameHeight(src, 0); + width = vsapi->getFrameWidth(src, 0); + stride = vsapi->getStride(src, 0) / Bps; pcount = stride * height; - const int planes[VSMaxPlaneCount] = { 0, 1, 2 }; - const VSFrameRef * cp_planes[VSMaxPlaneCount] = { d.process[0] ? nullptr : src, d.process[1] ? nullptr : src, d.process[2] ? nullptr : src }; - dst = vsapi->newVideoFrame2(fi, width, height, cp_planes, planes, src, core); + int planes[VSMaxPlaneCount]; + const VSFrameRef *cp_planes[VSMaxPlaneCount]; for (int i = 0; i < VSMaxPlaneCount; i++) { - if (d.process[i]) - { - src_stride[i] = vsapi->getStride(src, i) / Bps; - src_width[i] = vsapi->getFrameWidth(src, i); - src_height[i] = vsapi->getFrameHeight(src, i); - src_pcount[i] = src_stride[i] * src_height[i]; - - dst_stride[i] = vsapi->getStride(dst, i) / Bps; - dst_width[i] = vsapi->getFrameWidth(dst, i); - dst_height[i] = vsapi->getFrameHeight(dst, i); - dst_pcount[i] = dst_stride[i] * dst_height[i]; - } + planes[i] = i; + cp_planes[i] = d.process[i] ? nullptr : src; + } + + dst = vsapi->newVideoFrame2(fi, width, height, cp_planes, planes, src, core); + + for (int i = 0; i < PlaneCount; i++) + { + src_height[i] = vsapi->getFrameHeight(src, i); + src_width[i] = vsapi->getFrameWidth(src, i); + src_stride[i] = vsapi->getStride(src, i) / Bps; + src_pcount[i] = src_stride[i] * src_height[i]; + + dst_height[i] = vsapi->getFrameHeight(dst, i); + dst_width[i] = vsapi->getFrameWidth(dst, i); + dst_stride[i] = vsapi->getStride(dst, i) / Bps; + dst_pcount[i] = dst_stride[i] * dst_height[i]; } } @@ -263,11 +269,11 @@ class VSProcess { int i; - for (i = 0; i < VSMaxPlaneCount; i++) + for (i = 0; i < PlaneCount; i++) { if (d.process[i]) break; } - if (i >= VSMaxPlaneCount) return dst; + if (i >= PlaneCount) return dst; else if (Bps == 1) { diff --git a/include/MSR.h b/include/MSR.h index 679b91e..68a34b0 100644 --- a/include/MSR.h +++ b/include/MSR.h @@ -188,12 +188,123 @@ class MSRProcess virtual ~MSRProcess() {} - int SimplestColorBalance(FLType *odata, const FLType *idata) const; + // Multi Scale Retinex process kernel for floating point data int MSRKernel(FLType *odata, const FLType *idata) const; + + // Simplest color balance with pixel clipping on either side of the dynamic range + int SimplestColorBalance(FLType *odata, const FLType *idata) const; // odata as input and output, idata as source + template < typename T > + int SimplestColorBalance(T *dst, FLType *odata, const T *src, T dFloor, T dCeil) const; // odata as input, dst as output, src as source }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +template < typename T > +int MSRProcess::SimplestColorBalance(T *dst, FLType *odata, const T *src, T dFloor, T dCeil) const +{ + int i, j, upper; + + FLType offset, gain; + FLType min = FLType_MAX; + FLType max = -FLType_MAX; + + FLType dFloorFL = static_cast(dFloor); + FLType dCeilFL = static_cast(dCeil); + FLType dRangeFL = dCeilFL - dFloorFL; + + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + { + min = Min(min, odata[i]); + max = Max(max, odata[i]); + } + } + + if (max <= min) + { + memcpy(dst, src, sizeof(T)*pcount); + return 1; + } + + if (d.lower_thr > 0 || d.upper_thr > 0) + { + int h, HistBins = d.HistBins; + int Count, MaxCount; + + int *Histogram = vs_aligned_malloc(sizeof(int)*HistBins, Alignment); + memset(Histogram, 0, sizeof(int)*HistBins); + + gain = (HistBins - 1) / (max - min); + offset = -min * gain; + + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + { + Histogram[static_cast(odata[i] * gain + offset)]++; + } + } + + gain = (max - min) / (HistBins - 1); + offset = min; + + Count = 0; + MaxCount = static_cast(width*height*d.lower_thr + 0.5); + + for (h = 0; h < HistBins; h++) + { + Count += Histogram[h]; + if (Count > MaxCount) break; + } + + min = h * gain + offset; + + Count = 0; + MaxCount = static_cast(width*height*d.upper_thr + 0.5); + + for (h = HistBins - 1; h >= 0; h--) + { + Count += Histogram[h]; + if (Count > MaxCount) break; + } + + max = h * gain + offset; + + vs_aligned_free(Histogram); + } + + gain = dRangeFL / (max - min); + offset = -min * gain + dFloorFL + FLType(0.5); + + if (d.lower_thr > 0 || d.upper_thr > 0) + { + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + dst[i] = static_cast(Clip(odata[i] * gain + offset, dFloorFL, dCeilFL)); + } + } + else + { + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + dst[i] = static_cast(odata[i] * gain + offset); + } + } + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + #endif \ No newline at end of file diff --git a/include/MSRCP.h b/include/MSRCP.h index 0658a73..c806f5b 100644 --- a/include/MSRCP.h +++ b/include/MSRCP.h @@ -104,9 +104,12 @@ template < typename T > void MSRCPProcess::process_core() { int i, j, upper; + FLType gain, offset, scale; const T *Ysrcp; T *Ydstp; + // Calculate quantization parameters according to bit per sample and limited/full range + // Floor and Ceil for limited range src will be determined later according to minimum and maximum value in the frame T sFloor = 0; //T sFloorC = 0; int sNeutral = 128 << (bps - 8); @@ -137,16 +140,17 @@ void MSRCPProcess::process_core() FLType dRangeFL = static_cast(dRange); FLType dRangeCFL = static_cast(dRangeC); - FLType gain, offset, scale; - + // Allocate floating point data buff FLType *idata = vs_aligned_malloc(sizeof(FLType)*pcount, Alignment); FLType *odata = vs_aligned_malloc(sizeof(FLType)*pcount, Alignment); - if (fi->colorFamily == cmGray) + if (fi->colorFamily == cmGray) // Procedure for Gray color family { + // Get read and write pointer for src and dst Ysrcp = reinterpret_cast(vsapi->getReadPtr(src, 0)); Ydstp = reinterpret_cast(vsapi->getWritePtr(dst, 0)); + // Derive floating point intensity channel from integer Y channel if (d.fulls) { gain = 1 / sRangeFL; @@ -159,6 +163,7 @@ void MSRCPProcess::process_core() } else { + // If src is of limited range, determine the Floor and Ceil by the minimum and maximum value in the frame T min, max; min = sCeil; @@ -189,9 +194,12 @@ void MSRCPProcess::process_core() } } + // Apply MSR to floating point intensity channel MSRKernel(odata, idata); + // Simplest color balance with pixel clipping on either side of the dynamic range SimplestColorBalance(odata, idata); + // Convert floating point intensity channel to integer Y channel offset = dFloorFL + FLType(0.5); for (j = 0; j < height; j++) { @@ -200,8 +208,9 @@ void MSRCPProcess::process_core() Ydstp[i] = static_cast(odata[i] * dRangeFL + offset); } } - else if (fi->colorFamily == cmRGB) + else if (fi->colorFamily == cmRGB) // Procedure for RGB color family { + // Get read and write pointer for src and dst const T *Rsrcp, *Gsrcp, *Bsrcp; T *Rdstp, *Gdstp, *Bdstp; @@ -212,6 +221,7 @@ void MSRCPProcess::process_core() Bsrcp = reinterpret_cast(vsapi->getReadPtr(src, 2)); Bdstp = reinterpret_cast(vsapi->getWritePtr(dst, 2)); + // Derive floating point intensity channel from integer RGB channel if (d.fulls) { gain = 1 / (sRangeFL * 3); @@ -224,6 +234,7 @@ void MSRCPProcess::process_core() } else { + // If src is of limited range, determine the Floor and Ceil by the minimum and maximum value in the frame T min, max; min = sCeil; @@ -255,9 +266,12 @@ void MSRCPProcess::process_core() } } + // Apply MSR to floating point intensity channel MSRKernel(odata, idata); + // Simplest color balance with pixel clipping on either side of the dynamic range SimplestColorBalance(odata, idata); + // Adjust integer RGB channel according to filtering result in floating point intensity channel T Rval, Gval, Bval; if (sFloor == 0 && dFloorFL == 0 && sRangeFL == dRangeFL) @@ -300,8 +314,9 @@ void MSRCPProcess::process_core() } } } - else + else // Procedure for YUV or YCoCg color family { + // Get read and write pointer for src and dst const T *Usrcp, *Vsrcp; T *Udstp, *Vdstp; @@ -312,6 +327,7 @@ void MSRCPProcess::process_core() Vsrcp = reinterpret_cast(vsapi->getReadPtr(src, 2)); Vdstp = reinterpret_cast(vsapi->getWritePtr(dst, 2)); + // Derive floating point intensity channel from integer Y channel if (d.fulls) { gain = 1 / sRangeFL; @@ -324,6 +340,7 @@ void MSRCPProcess::process_core() } else { + // If src is of limited range, determine the Floor and Ceil by the minimum and maximum value in the frame T min, max; min = sCeil; @@ -354,9 +371,14 @@ void MSRCPProcess::process_core() } } + // Apply MSR to floating point intensity channel MSRKernel(odata, idata); + // Simplest color balance with pixel clipping on either side of the dynamic range SimplestColorBalance(odata, idata); + // Convert floating point intensity channel to integer Y channel + // Adjust integer UV channel according to filtering result in floating point intensity channel + // Chroma protect uses log function to attenuate the adjustment in UV channel FLType chroma_protect_mul1 = static_cast(d.chroma_protect - 1); FLType chroma_protect_mul2 = static_cast(1 / log(d.chroma_protect)); @@ -390,6 +412,7 @@ void MSRCPProcess::process_core() } } + // Free floating point data buff vs_aligned_free(idata); vs_aligned_free(odata); } diff --git a/include/MSRCR.h b/include/MSRCR.h new file mode 100644 index 0000000..fa94e91 --- /dev/null +++ b/include/MSRCR.h @@ -0,0 +1,286 @@ +/* +* Retinex filter - VapourSynth plugin +* Copyright (C) 2014 mawen1250 +* +* 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 3 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, see . +*/ + + +#ifndef MSRCR_H_ +#define MSRCR_H_ + + +#include "Helper.h" +#include "MSR.h" + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +const struct MSRCRPara +{ + double restore = 125.0; +} MSRCRDefault; + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +class MSRCRData + : public MSRData +{ +public: + double restore = MSRCRDefault.restore; + +public: + MSRCRData(const VSAPI *_vsapi = nullptr, std::string _FunctionName = "MSRCR") + : MSRData(_vsapi, _FunctionName) {} + + ~MSRCRData() {} + + virtual int arguments_process(const VSMap *in, VSMap *out) + { + MSRData::arguments_process(in, out); + + int error; + + if (vi->format->colorFamily != cmRGB) + { + setError(out, "Invalid input clip, only RGB format input supported"); + return 1; + } + + restore = vsapi->propGetFloat(in, "restore", 0, &error); + if (error) + restore = MSRCRDefault.restore; + if (restore < 0) + { + setError(out, "Invalid \"restore\" assigned, must be non-negative float number"); + return 1; + } + + return 0; + } +}; + + +void VS_CC MSRCRCreate(const VSMap *in, VSMap *out, void *userData, VSCore *core, const VSAPI *vsapi); + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +class MSRCRProcess + : public MSRProcess +{ +private: + const MSRCRData &d; + +private: + template < typename T > + void process_core(); + +protected: + virtual void process_core8() { process_core(); } + virtual void process_core16() { process_core(); } + +public: + MSRCRProcess(const MSRCRData &_d, int n, VSFrameContext *frameCtx, VSCore *core, const VSAPI *_vsapi) + : MSRProcess(_d, n, frameCtx, core, _vsapi), d(_d) {} + + virtual ~MSRCRProcess() {} +}; + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +template < typename T > +void MSRCRProcess::process_core() +{ + int i, j, upper; + FLType gain, offset; + + // Calculate quantization parameters according to bit per sample and limited/full range + // Floor and Ceil for limited range src will be determined later according to minimum and maximum value in the frame + T sFloor = 0; + T sCeil = (1 << bps) - 1; + T sRange = d.fulls ? (1 << bps) - 1 : 219 << (bps - 8); + T dFloor = d.fulld ? 0 : 16 << (bps - 8); + T dCeil = d.fulld ? (1 << bps) - 1 : 235 << (bps - 8); + //T dRange = d.fulld ? (1 << bps) - 1 : 219 << (bps - 8); + FLType sFloorFL = static_cast(sFloor); + //FLType sCeilFL = static_cast(sCeil); + FLType sRangeFL = static_cast(sRange); + //FLType dFloorFL = static_cast(dFloor); + //FLType dCeilFL = static_cast(dCeil); + //FLType dRangeFL = static_cast(dRange); + + // Allocate floating point data buff + FLType *idata = vs_aligned_malloc(sizeof(FLType)*pcount, Alignment); + FLType *odataR = vs_aligned_malloc(sizeof(FLType)*pcount, Alignment); + FLType *odataG = vs_aligned_malloc(sizeof(FLType)*pcount, Alignment); + FLType *odataB = vs_aligned_malloc(sizeof(FLType)*pcount, Alignment); + + // Get read and write pointer for src and dst + const T *Rsrcp, *Gsrcp, *Bsrcp; + T *Rdstp, *Gdstp, *Bdstp; + + Rsrcp = reinterpret_cast(vsapi->getReadPtr(src, 0)); + Rdstp = reinterpret_cast(vsapi->getWritePtr(dst, 0)); + Gsrcp = reinterpret_cast(vsapi->getReadPtr(src, 1)); + Gdstp = reinterpret_cast(vsapi->getWritePtr(dst, 1)); + Bsrcp = reinterpret_cast(vsapi->getReadPtr(src, 2)); + Bdstp = reinterpret_cast(vsapi->getWritePtr(dst, 2)); + + // If src is not of full range, determine the Floor and Ceil by the maximum and minimum value in the frame + if (!d.fulls) + { + T min, max; + + min = sCeil; + max = sFloor; + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + { + min = Min(min, Min(Rsrcp[i], Min(Gsrcp[i], Bsrcp[i]))); + max = Max(max, Max(Rsrcp[i], Max(Gsrcp[i], Bsrcp[i]))); + } + } + if (max > min) + { + sFloor = min; + sCeil = max; + sFloorFL = static_cast(sFloor); + //sCeilFL = static_cast(sCeil); + } + } + + // Derive floating point R channel from integer R channel + if (d.fulls) + { + gain = 1 / sRangeFL; + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + idata[i] = Rsrcp[i] * gain; + } + } + else + { + offset = -sFloorFL; + gain = 1 / static_cast(sCeil - sFloor); + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + idata[i] = (Rsrcp[i] + offset) * gain; + } + } + + // Apply MSR to floating point R channel + MSRKernel(odataR, idata); + + // Derive floating point G channel from integer G channel + if (d.fulls) + { + gain = 1 / sRangeFL; + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + idata[i] = Gsrcp[i] * gain; + } + } + else + { + offset = -sFloorFL; + gain = 1 / static_cast(sCeil - sFloor); + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + idata[i] = (Gsrcp[i] + offset) * gain; + } + } + + // Apply MSR to floating point G channel + MSRKernel(odataG, idata); + + // Derive floating point B channel from integer B channel + if (d.fulls) + { + gain = 1 / sRangeFL; + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + idata[i] = Bsrcp[i] * gain; + } + } + else + { + offset = -sFloorFL; + gain = 1 / static_cast(sCeil - sFloor); + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + idata[i] = (Bsrcp[i] + offset) * gain; + } + } + + // Apply MSR to floating point B channel + MSRKernel(odataB, idata); + + // Color restoration + FLType RvalFL, GvalFL, BvalFL; + FLType temp; + + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + { + RvalFL = Rsrcp[i] - sFloor; + GvalFL = Gsrcp[i] - sFloor; + BvalFL = Bsrcp[i] - sFloor; + temp = RvalFL + GvalFL + BvalFL; + temp = temp <= 0 ? 0 : d.restore / temp; + odataR[i] *= log(RvalFL * temp + 1); + odataG[i] *= log(GvalFL * temp + 1); + odataB[i] *= log(BvalFL * temp + 1); + } + } + + // Simplest color balance with pixel clipping on either side of the dynamic range + SimplestColorBalance(Rdstp, odataR, Rsrcp, dFloor, dCeil); + SimplestColorBalance(Gdstp, odataG, Gsrcp, dFloor, dCeil); + SimplestColorBalance(Bdstp, odataB, Bsrcp, dFloor, dCeil); + + // Free floating point data buff + vs_aligned_free(idata); + vs_aligned_free(odataR); + vs_aligned_free(odataG); + vs_aligned_free(odataB); +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +#endif \ No newline at end of file diff --git a/msvc/Retinex.vcxproj b/msvc/Retinex.vcxproj index 63f1ac2..de8ec70 100644 --- a/msvc/Retinex.vcxproj +++ b/msvc/Retinex.vcxproj @@ -123,11 +123,13 @@ + + diff --git a/msvc/Retinex.vcxproj.filters b/msvc/Retinex.vcxproj.filters index aa1a954..9451d7e 100644 --- a/msvc/Retinex.vcxproj.filters +++ b/msvc/Retinex.vcxproj.filters @@ -27,6 +27,9 @@ Header Files + + Header Files + @@ -41,5 +44,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/source/MSR.cpp b/source/MSR.cpp index 4396a04..72fa95a 100644 --- a/source/MSR.cpp +++ b/source/MSR.cpp @@ -25,6 +25,64 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int MSRProcess::MSRKernel(FLType *odata, const FLType *idata) const +{ + int i, j, upper; + + FLType FloorFL = 0; + FLType CeilFL = 1; + + FLType *gauss = vs_aligned_malloc(sizeof(FLType)*pcount, Alignment); + + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + odata[i] = 1; + } + + size_t s, scount = d.sigma.size(); + FLType B, B1, B2, B3; + + for (s = 0; s < scount; s++) + { + if (d.sigma[s] > 0) + { + Recursive_Gaussian_Parameters(d.sigma[s], B, B1, B2, B3); + Recursive_Gaussian2D_Horizontal(gauss, idata, height, width, stride, B, B1, B2, B3); + Recursive_Gaussian2D_Vertical(gauss, gauss, height, width, stride, B, B1, B2, B3); + + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + odata[i] *= gauss[i] <= 0 ? 1 : idata[i] / gauss[i] + 1; + } + } + else + { + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + odata[i] *= FLType(2); + } + } + } + + for (j = 0; j < height; j++) + { + i = stride * j; + for (upper = i + width; i < upper; i++) + odata[i] = log(odata[i]) / static_cast(scount); + } + + vs_aligned_free(gauss); + + return 0; +} + + int MSRProcess::SimplestColorBalance(FLType *odata, const FLType *idata) const { int i, j, upper; @@ -61,7 +119,7 @@ int MSRProcess::SimplestColorBalance(FLType *odata, const FLType *idata) const memset(Histogram, 0, sizeof(int)*HistBins); gain = (HistBins - 1) / (max - min); - offset = - min * gain; + offset = -min * gain; for (j = 0; j < height; j++) { @@ -126,62 +184,4 @@ int MSRProcess::SimplestColorBalance(FLType *odata, const FLType *idata) const } -int MSRProcess::MSRKernel(FLType *odata, const FLType *idata) const -{ - int i, j, upper; - - FLType FloorFL = 0; - FLType CeilFL = 1; - - FLType *gauss = vs_aligned_malloc(sizeof(FLType)*pcount, Alignment); - - for (j = 0; j < height; j++) - { - i = stride * j; - for (upper = i + width; i < upper; i++) - odata[i] = 1; - } - - size_t s, scount = d.sigma.size(); - FLType B, B1, B2, B3; - - for (s = 0; s < scount; s++) - { - if (d.sigma[s] > 0) - { - Recursive_Gaussian_Parameters(d.sigma[s], B, B1, B2, B3); - Recursive_Gaussian2D_Horizontal(gauss, idata, height, width, stride, B, B1, B2, B3); - Recursive_Gaussian2D_Vertical(gauss, gauss, height, width, stride, B, B1, B2, B3); - - for (j = 0; j < height; j++) - { - i = stride * j; - for (upper = i + width; i < upper; i++) - odata[i] *= gauss[i] <= 0 ? 1 : idata[i] / gauss[i] + 1; - } - } - else - { - for (j = 0; j < height; j++) - { - i = stride * j; - for (upper = i + width; i < upper; i++) - odata[i] *= FLType(2); - } - } - } - - for (j = 0; j < height; j++) - { - i = stride * j; - for (upper = i + width; i < upper; i++) - odata[i] = log(odata[i]) / static_cast(scount); - } - - vs_aligned_free(gauss); - - return 0; -} - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/source/MSRCR.cpp b/source/MSRCR.cpp new file mode 100644 index 0000000..65ce683 --- /dev/null +++ b/source/MSRCR.cpp @@ -0,0 +1,77 @@ +/* +* Retinex filter - VapourSynth plugin +* Copyright (C) 2014 mawen1250 +* +* 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 3 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, see . +*/ + + +#include "MSRCR.h" + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +void VS_CC MSRCRInit(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi) +{ + MSRCRData *d = reinterpret_cast(*instanceData); + + vsapi->setVideoInfo(d->vi, 1, node); +} + +const VSFrameRef *VS_CC MSRCRGetFrame(int n, int activationReason, void **instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) +{ + const MSRCRData *d = reinterpret_cast(*instanceData); + + if (activationReason == arInitial) + { + vsapi->requestFrameFilter(n, d->node, frameCtx); + } + else if (activationReason == arAllFramesReady) + { + MSRCRProcess p(*d, n, frameCtx, core, vsapi); + + return p.process(); + } + + return nullptr; +} + +void VS_CC MSRCRFree(void *instanceData, VSCore *core, const VSAPI *vsapi) +{ + MSRCRData *d = reinterpret_cast(instanceData); + + delete d; +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +void VS_CC MSRCRCreate(const VSMap *in, VSMap *out, void *userData, VSCore *core, const VSAPI *vsapi) +{ + MSRCRData *d = new MSRCRData(vsapi); + + if (d->arguments_process(in, out)) + { + delete d; + return; + } + + // Create filter + vsapi->createFilter(in, out, "MSRCR", MSRCRInit, MSRCRGetFrame, MSRCRFree, fmParallel, 0, d, core); +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/source/VSPlugin.cpp b/source/VSPlugin.cpp index 8fdbec8..109b7e8 100644 --- a/source/VSPlugin.cpp +++ b/source/VSPlugin.cpp @@ -18,6 +18,7 @@ #include "MSRCP.h" +#include "MSRCR.h" //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -30,6 +31,7 @@ VS_EXTERNAL_API(void) VapourSynthPluginInit(VSConfigPlugin configFunc, VSRegiste VAPOURSYNTH_API_VERSION, 1, plugin); registerFunc("MSRCP", "input:clip;sigma:float[]:opt;lower_thr:float:opt;upper_thr:float:opt;fulls:int:opt;fulld:int:opt;chroma_protect:float:opt", MSRCPCreate, nullptr, plugin); + registerFunc("MSRCR", "input:clip;sigma:float[]:opt;lower_thr:float:opt;upper_thr:float:opt;fulls:int:opt;fulld:int:opt;restore:float:opt", MSRCRCreate, nullptr, plugin); }