22
33namespace Wacton . Unicolour . Icc ;
44
5+ // multidimensional lookup
6+ // e.g. with 4 input channels, 25 grid points, 3 output channels = 5D-CLUT of [25, 25, 25, 25, 3]
57internal class Clut
68{
7- private readonly Array clutGrid ;
9+ private readonly double [ ] values ;
10+ private readonly List < int [ ] > inputBinaryVectors ;
811
912 internal int InputChannels { get ; }
1013 internal int GridPoints { get ; }
@@ -13,39 +16,11 @@ internal class Clut
1316 // NOTE: # of values should = gridPoints ^ inputChannels * outputChannels (e.g. 37 ^ 3 * 4 = 202,612)
1417 internal Clut ( double [ ] values , int inputChannels , int gridPoints , int outputChannels )
1518 {
19+ this . values = values ;
1620 InputChannels = inputChannels ;
1721 GridPoints = gridPoints ;
1822 OutputChannels = outputChannels ;
19- clutGrid = InitialiseClutGrid ( values ) ;
20- }
21-
22- private Array InitialiseClutGrid ( double [ ] values )
23- {
24- // e.g. CLUT grid with 4 input channels, 25 grid points, 3 output channels
25- // = 5D-CLUT of [25, 25, 25, 25, 3]
26- var dimensionLengths = new List < int > ( ) ;
27- for ( var i = 0 ; i < InputChannels ; i ++ )
28- {
29- dimensionLengths . Add ( GridPoints ) ;
30- }
31-
32- dimensionLengths . Add ( OutputChannels ) ;
33-
34- var grid = Array . CreateInstance ( typeof ( double ) , dimensionLengths . ToArray ( ) ) ;
35- var inputGridCoordinates = GenerateVectorsOfBaseN ( InputChannels , GridPoints ) ;
36- foreach ( var inputGridCoordinate in inputGridCoordinates )
37- {
38- for ( var outputChannel = 0 ; outputChannel < OutputChannels ; outputChannel ++ )
39- {
40- var index = GetIndex ( inputGridCoordinate , outputChannel ) ;
41-
42- var indexes = inputGridCoordinate . ToList ( ) ;
43- indexes . Add ( outputChannel ) ;
44- grid . SetValue ( values [ index ] , indexes . ToArray ( ) ) ;
45- }
46- }
47-
48- return grid ;
23+ inputBinaryVectors = GenerateVectorsOfBaseN ( inputChannels , 2 ) ;
4924 }
5025
5126 /*
@@ -71,36 +46,70 @@ private Array InitialiseClutGrid(double[] values)
7146 */
7247 internal double [ ] Lookup ( double [ ] clutInputs )
7348 {
74- var clutIndexes = clutInputs . Select ( clutInput => new ClutIndex ( clutInput , GridPoints ) ) . ToList ( ) ;
49+ var clutIndexes = clutInputs . Select ( clutInput => new ClutIndex ( clutInput , GridPoints ) ) . ToArray ( ) ;
7550
76- var binaryVectors = GenerateVectorsOfBaseN ( InputChannels , 2 ) ;
77- var weightedOutputs = new List < double [ ] > ( ) ;
78- foreach ( var binaryVector in binaryVectors )
51+ var result = new double [ OutputChannels ] ;
52+ foreach ( var inputBinaryVector in inputBinaryVectors )
7953 {
80- var inputChannelIndexes = new List < int > ( ) ;
81- var distanceComponents = new List < double > ( ) ;
82- for ( var i = 0 ; i < binaryVector . Length ; i ++ )
54+ var inputIndexes = new int [ InputChannels ] ;
55+ var distances = new double [ InputChannels ] ;
56+ for ( var i = 0 ; i < InputChannels ; i ++ )
8357 {
84- var binary = binaryVector [ i ] ;
58+ var binary = inputBinaryVector [ i ] ;
8559 var clutIndex = clutIndexes [ i ] ;
86- inputChannelIndexes . Add ( binary == 0 ? clutIndex . Lower : clutIndex . Upper ) ;
87- distanceComponents . Add ( binary == 0 ? clutIndex . DistanceToLower : clutIndex . DistanceToUpper ) ;
60+ inputIndexes [ i ] = binary == 0 ? clutIndex . Lower : clutIndex . Upper ;
61+ distances [ i ] = binary == 0 ? clutIndex . DistanceToLower : clutIndex . DistanceToUpper ;
8862 }
8963
90- var output = GetOutput ( inputChannelIndexes . ToArray ( ) ) ;
91- var distance = distanceComponents . Aggregate ( ( accumulate , item ) => accumulate * item ) ;
92- var weightedOutput = output . Select ( x => x * distance ) . ToArray ( ) ;
93- weightedOutputs . Add ( weightedOutput ) ;
64+ var output = GetOutput ( inputIndexes ) ;
65+ var distance = Product ( distances ) ;
66+ for ( var i = 0 ; i < OutputChannels ; i ++ )
67+ {
68+ result [ i ] += output [ i ] * distance ;
69+ }
9470 }
9571
96- var outputComponents = Enumerable . Range ( 0 , OutputChannels ) . Select ( outputChannel => weightedOutputs . Select ( x => x [ outputChannel ] ) ) . ToArray ( ) ;
97- var outputSummed = outputComponents . Select ( x => x . Sum ( ) ) . ToArray ( ) ;
98- return outputSummed ;
72+ return result ;
73+ }
74+
75+ private double [ ] GetOutput ( int [ ] inputIndexes )
76+ {
77+ var output = new double [ OutputChannels ] ;
78+ var outputIndex = GetOutputIndex ( inputIndexes ) ;
79+ for ( var i = 0 ; i < OutputChannels ; i ++ )
80+ {
81+ output [ i ] = values [ outputIndex + i ] ;
82+ }
83+
84+ return output ;
9985 }
86+
87+ /*
88+ * e.g. Fogra39 CMYK -> LAB: 4 input channels, 25 grid points, 3 output channels
89+ * index = (cIndex * 25^3 * 3) + (mIndex * 25^2 * 3) + (yIndex * 25^1 * 3) + (kIndex * 25^0 * 3)
90+ * L = index + 0, A = index + 1, B = index + 2
91+ *
92+ * e.g. Fogra55 CMYKOGV -> LAB: 7 input channels, 5 grid points, 3 output channels
93+ * index = (cIndex * 5^6 * 3) + (mIndex * 5^5 * 3) + (yIndex * 5^4 * 3) + (kIndex * 5^3 * 3) +
94+ * (oIndex * 5^2 * 3) + (gIndex * 5^1 * 3) + (vIndex * 5^0 * 3)
95+ * L = index + 0, A = index + 1, B = index + 2
96+ */
97+ private int GetOutputIndex ( int [ ] gridIndexes )
98+ {
99+ var outputIndex = 0 ;
100+ for ( var i = 0 ; i < gridIndexes . Length ; i ++ )
101+ {
102+ var gridIndex = gridIndexes [ i ] ;
103+ var power = gridIndexes . Length - 1 - i ;
104+ outputIndex += gridIndex * Power ( GridPoints , power ) * OutputChannels ;
105+ }
100106
107+ return outputIndex ;
108+ }
109+
101110 private static List < int [ ] > GenerateVectorsOfBaseN ( int n , int @base )
102111 {
103- var totalVectors = ( int ) Math . Pow ( @base , n ) ;
112+ var totalVectors = Power ( @base , n ) ;
104113
105114 var vectors = new List < int [ ] > ( ) ;
106115 for ( var i = 0 ; i < totalVectors ; i ++ )
@@ -123,39 +132,27 @@ private static List<int[]> GenerateVectorsOfBaseN(int n, int @base)
123132 return vectors ;
124133 }
125134
126- /*
127- * e.g. Fogra39 CMYK -> LAB: 4 input channels, 25 grid points, 3 output channels
128- * index = (cGrid * 25^3 * 3) + (mGrid * 25^2 * 3) + (yGrid * 25^1 * 3) + (kGrid * 25^0 * 3)
129- *
130- * e.g. Fogra55 CMYKOGV -> LAB: 7 input channels, 5 grid points, 3 output channels
131- * index = (cGrid * 5^6 * 3) + (mGrid * 5^5 * 3) + (yGrid * 5^4 * 3) + (kGrid * 5^3 * 3) +
132- * (oGrid * 5^2 * 3) + (gGrid * 5^1 * 3) + (vGrid * 5^0 * 3)
133- */
134- private int GetIndex ( int [ ] gridPointInputs , int outputChannel )
135+ // avoiding .Aggregate((accumulate, item) => accumulate * item) to improve performance
136+ private static double Product ( double [ ] values )
135137 {
136- var index = 0 ;
137- for ( var gridPointInputIndex = 0 ; gridPointInputIndex < gridPointInputs . Length ; gridPointInputIndex ++ )
138+ var result = 1. 0;
139+ foreach ( var value in values )
138140 {
139- var gridPointInput = gridPointInputs [ gridPointInputIndex ] ;
140- var power = gridPointInputs . Length - 1 - gridPointInputIndex ;
141- index += gridPointInput * ( int ) Math . Pow ( GridPoints , power ) * OutputChannels ;
141+ result *= value ;
142142 }
143143
144- index += outputChannel ;
145- return index ;
144+ return result ;
146145 }
147-
148- private double [ ] GetOutput ( int [ ] inputChannelIndexes )
146+
147+ private static int Power ( int number , int exponent )
149148 {
150- var output = new double [ OutputChannels ] ;
151- for ( var i = 0 ; i < OutputChannels ; i ++ )
149+ var result = 1 ;
150+ for ( var i = 1 ; i <= exponent ; i ++ )
152151 {
153- var indexes = inputChannelIndexes . Concat ( new [ ] { i } ) . ToArray ( ) ;
154- var value = ( double ) clutGrid . GetValue ( indexes ) ;
155- output [ i ] = value ;
152+ result *= number ;
156153 }
157154
158- return output ;
155+ return result ;
159156 }
160157
161158 private class ClutIndex
0 commit comments