diff --git a/MainWindow.xaml b/MainWindow.xaml index 398d4fd..b5672e9 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -128,8 +128,8 @@ sRGB sRGB Linear XYZ - CIELab - + CIELab + CIELCh @@ -175,7 +175,11 @@ 1.0 - Protect avg. luminance level + + + + 1.0 + Protect blacks Protect whites Protect neutrals diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index a87e1ce..fd08f4a 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -61,6 +61,9 @@ private void readGUISettings(string nameOfElement = "") case "boxBlur3dRadius_text": postMatchSmoothing3DBoxBlurRadius = int.Parse(boxBlur3dRadius_text.Text); break; + case "boxBlur3dProtectLuminance_text": + postMatchSmoothing3DBoxBlurLuminanceProtection = float.Parse(boxBlur3dProtectLuminance_text.Text); + break; case "boxBlur3dStrength_text": postMatchSmoothing3DBoxBlurStrength = float.Parse(boxBlur3dStrength_text.Text); break; @@ -133,7 +136,7 @@ enum InterpolationType { NONE, SINGLELINEAR, DUALLINEAR}; InterpolationType interpolationType = InterpolationType.SINGLELINEAR; enum AggregateColorSpace { SRGB, SRGBLINEAR, XYZ, CIELAB, CIELCHAB }; - AggregateColorSpace aggregateColorSpace = AggregateColorSpace.CIELCHAB; + AggregateColorSpace aggregateColorSpace = AggregateColorSpace.CIELAB; enum LowPassMatching { NONE, REFERENCETOTEST, TESTTOREFERENCE}; LowPassMatching lowPassMatching = LowPassMatching.NONE; @@ -153,6 +156,7 @@ enum PostMatchSmoothing { NONE, BOXBLUR3D}; PostMatchSmoothing postMatchSmoothing = PostMatchSmoothing.NONE; int postMatchSmoothing3DBoxBlurRadius = 2; float postMatchSmoothing3DBoxBlurStrength = 1.0f; + float postMatchSmoothing3DBoxBlurLuminanceProtection = 1.0f; const int R = 0; @@ -630,7 +634,7 @@ public struct Color float weight; float tmp1, tmp2, tmp3, tmp1sq,tmp2sq,tmp3sq; - int redQuadrant, greenQuadrant, blueQuadrant; + int redOctant, greenOctant, blueOctant; float sqrtOf3 = (float)Math.Sqrt(3); AverageData tmpAverageData = new AverageData(); @@ -646,9 +650,9 @@ public struct Color // This loop is currently the bottleneck. Edit: Is it still? foreach (ColorPairData collectCubeLinearHere in collectCubeLinear) { - redQuadrant = collectCubeLinearHere.nearestQuadrantR; - greenQuadrant = collectCubeLinearHere.nearestQuadrantG; - blueQuadrant = collectCubeLinearHere.nearestQuadrantB; + redOctant = collectCubeLinearHere.nearestQuadrantR; + greenOctant = collectCubeLinearHere.nearestQuadrantG; + blueOctant = collectCubeLinearHere.nearestQuadrantB; /*multiplyHelper.X = collectCubeLinearHere.RCORD; multiplyHelper.Y = collectCubeLinearHere.GCORD; @@ -661,25 +665,25 @@ public struct Color count++; for (rAround = -1; rAround <= 1; rAround++) { - if (cordNormalized.X > redQuadrant && rAround == -1) continue; //major speed improvement but slight quality degradation - if (cordNormalized.X < redQuadrant && rAround == 1) continue; //major speed improvement but slight quality degradation - trueStepR = Math.Max(0, Math.Min(steps, redQuadrant + rAround)); + if (cordNormalized.X > redOctant && rAround == -1) continue; //major speed improvement but slight quality degradation + if (cordNormalized.X < redOctant && rAround == 1) continue; //major speed improvement but slight quality degradation + trueStepR = Math.Max(0, Math.Min(steps, redOctant + rAround)); tmp1 = (trueStepR - cordNormalized.X); tmp1sq = tmp1 * tmp1; for (gAround = -1; gAround <= 1; gAround++) { - if (cordNormalized.Y > greenQuadrant && gAround == -1) continue; //major speed improvement but slight quality degradation - if (cordNormalized.Y < greenQuadrant && gAround == 1) continue; //major speed improvement but slight quality degradation - trueStepG = Math.Max(0, Math.Min(steps, greenQuadrant + gAround)); + if (cordNormalized.Y > greenOctant && gAround == -1) continue; //major speed improvement but slight quality degradation + if (cordNormalized.Y < greenOctant && gAround == 1) continue; //major speed improvement but slight quality degradation + trueStepG = Math.Max(0, Math.Min(steps, greenOctant + gAround)); tmp2 = (trueStepG - cordNormalized.Y); tmp2sq = tmp2 * tmp2; for (bAround = -1; bAround <= 1; bAround++) { - if (cordNormalized.Z > blueQuadrant && bAround == -1) continue; //major speed improvement but slight quality degradation - if (cordNormalized.Z < blueQuadrant && bAround == 1) continue; //major speed improvement but slight quality degradation - trueStepB = Math.Max(0, Math.Min(steps, blueQuadrant + bAround)); + if (cordNormalized.Z > blueOctant && bAround == -1) continue; //major speed improvement but slight quality degradation + if (cordNormalized.Z < blueOctant && bAround == 1) continue; //major speed improvement but slight quality degradation + trueStepB = Math.Max(0, Math.Min(steps, blueOctant + bAround)); tmp3 = (trueStepB - cordNormalized.Z); tmp3sq = tmp3 * tmp3; @@ -739,16 +743,16 @@ public struct Color // May seem slow from the code but actually only takes about 1 ms, it's ridiculously fast. AverageData averageHelper; Vector3 absCoord = new Vector3(0, 0, 0); - for (redQuadrant = 0; redQuadrant 0) + { + AverageData[] averageLuminancesOfLayerPreSmoothing = new AverageData[outputValueCount]; + AverageData[] averageLuminancesOfLayerPostSmoothing = new AverageData[outputValueCount]; + + int currentLayerTmp; + int currentLayer; + + // Analyze average luminances + for (redOctant = 0; redOctant < outputValueCount; redOctant++) + { + //absCoord.X = redQuadrant * stepSize; + for (greenOctant = 0; greenOctant < outputValueCount; greenOctant++) + { + //absCoord.Y = greenQuadrant * stepSize; + currentLayerTmp = Math.Max(redOctant, greenOctant); + for (blueOctant = 0; blueOctant < outputValueCount; blueOctant++) + { + //absCoord.Z = blueQuadrant * stepSize; + // "Layer" we mean like a "ring" around 0. Except not really a ring, but a cube layer. In avg. luminance protection, we try to maintain the same average luminance in each cube layer as in the unsmoothed cube + // It may not be technically correct since luminance isn't equally affected by R,G and B, but it's a simplified approach that hopefully works decently. + currentLayer = Math.Max(currentLayerTmp, blueOctant); + + averageLuminancesOfLayerPreSmoothing[currentLayer].color += cube[redOctant,greenOctant,blueOctant].color; + averageLuminancesOfLayerPreSmoothing[currentLayer].divisor += 1; + averageLuminancesOfLayerPostSmoothing[currentLayer].color += smoothedCube[redOctant,greenOctant,blueOctant].color; + averageLuminancesOfLayerPostSmoothing[currentLayer].divisor += 1; + } + } + } + + Vector3[] differences = new Vector3[outputValueCount]; + + // Compute differences in average luminances. + for(int i = 0; i < outputValueCount; i++) + { + averageLuminancesOfLayerPreSmoothing[i].color /= averageLuminancesOfLayerPreSmoothing[i].divisor; + averageLuminancesOfLayerPostSmoothing[i].color /= averageLuminancesOfLayerPostSmoothing[i].divisor; + + differences[i] = (averageLuminancesOfLayerPostSmoothing[i].color - averageLuminancesOfLayerPreSmoothing[i].color); + } + + // Apply compensation + for (redOctant = 0; redOctant < outputValueCount; redOctant++) + { + //absCoord.X = redQuadrant * stepSize; + for (greenOctant = 0; greenOctant < outputValueCount; greenOctant++) + { + //absCoord.Y = greenQuadrant * stepSize; + currentLayerTmp = Math.Max(redOctant, greenOctant); + for (blueOctant = 0; blueOctant < outputValueCount; blueOctant++) + { + //absCoord.Z = blueQuadrant * stepSize; + // "Layer" we mean like a "ring" around 0. Except not really a ring, but a cube layer. In avg. luminance protection, we try to maintain the same average luminance in each cube layer as in the unsmoothed cube + // It may not be technically correct since luminance isn't equally affected by R,G and B, but it's a simplified approach that hopefully works decently. + currentLayer = Math.Max(currentLayerTmp, blueOctant); + + smoothedCube[redOctant, greenOctant, blueOctant].color = ((smoothedCube[redOctant, greenOctant, blueOctant].color-differences[currentLayer]) * postMatchSmoothing3DBoxBlurLuminanceProtection)+ (smoothedCube[redOctant, greenOctant, blueOctant].color * (1-postMatchSmoothing3DBoxBlurLuminanceProtection)); + } } } } @@ -1055,15 +1125,15 @@ public struct Color if (aggregateWhat == AggregateVariable.VECTOR || aggregateColorSpace != AggregateColorSpace.SRGB) { Vector3 tmpColor = new Vector3(); - for (redQuadrant = 0; redQuadrant < outputValueCount; redQuadrant++) + for (redOctant = 0; redOctant < outputValueCount; redOctant++) { - absCoord.X = redQuadrant * stepSize; - for (greenQuadrant = 0; greenQuadrant < outputValueCount; greenQuadrant++) + absCoord.X = redOctant * stepSize; + for (greenOctant = 0; greenOctant < outputValueCount; greenOctant++) { - absCoord.Y = greenQuadrant * stepSize; - for (blueQuadrant = 0; blueQuadrant < outputValueCount; blueQuadrant++) + absCoord.Y = greenOctant * stepSize; + for (blueOctant = 0; blueOctant < outputValueCount; blueOctant++) { - absCoord.Z = blueQuadrant * stepSize; + absCoord.Z = blueOctant * stepSize; // TODO Find a way to not clip the result values with ColorMine. if(aggregateColorSpace == AggregateColorSpace.CIELAB) { @@ -1078,7 +1148,7 @@ public struct Color */ tmpColor = Helpers.sRGBToCIELab(absCoord); - tmpColor = tmpColor + cube[redQuadrant, greenQuadrant, blueQuadrant].color; + tmpColor = tmpColor + cube[redOctant, greenOctant, blueOctant].color; /* tmpLab.L = tmpColor.X; tmpLab.A = tmpColor.Y; @@ -1092,7 +1162,7 @@ public struct Color tmpColor = Helpers.XYZtoRGB(Helpers.CIELabToXYZ(tmpColor)); - cube[redQuadrant, greenQuadrant, blueQuadrant].color = tmpColor; + cube[redOctant, greenOctant, blueOctant].color = tmpColor; } else if(aggregateColorSpace == AggregateColorSpace.CIELCHAB) @@ -1108,7 +1178,7 @@ public struct Color */ tmpColor = Helpers.sRGBToCIELChab(absCoord); - tmpColor = tmpColor + cube[redQuadrant, greenQuadrant, blueQuadrant].color; + tmpColor = tmpColor + cube[redOctant, greenOctant, blueOctant].color; /* tmpLab.L = tmpColor.X; tmpLab.A = tmpColor.Y; @@ -1122,13 +1192,13 @@ public struct Color tmpColor = Helpers.CIELChabTosRGB(tmpColor); - cube[redQuadrant, greenQuadrant, blueQuadrant].color = tmpColor; + cube[redOctant, greenOctant, blueOctant].color = tmpColor; } else if (aggregateColorSpace == AggregateColorSpace.SRGB) { - cube[redQuadrant, greenQuadrant, blueQuadrant].color = absCoord + cube[redQuadrant, greenQuadrant, blueQuadrant].color; + cube[redOctant, greenOctant, blueOctant].color = absCoord + cube[redOctant, greenOctant, blueOctant].color; }