forked from wolfgangw/digital_cinema_tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
chromatic_adaptation.rb
executable file
·153 lines (140 loc) · 5.78 KB
/
chromatic_adaptation.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/ruby
#
# Wolfgang Woehl 2010
# Calculate chromatic adaptation transformation matrix
# Math details: see http://www.brucelindbloom.com/
#
# Usage: chromatic_adaptation.rb --help
# chromatic_adaptation.rb -s d50
# chromatic_adaptation.rb -s d65 -d dci_calibration_white
# chromatic_adaptation.rb --source d65 --destination rec709 --method bradford
require 'optparse'
require 'ostruct'
require 'matrix'
class Optparser
def self.parse(args)
# defaults
options = OpenStruct.new
options.source = "d65"
options.destination = "dci_calibration_white"
options.crd_definition = "bradford"
options.output = 'line'
opts = OptionParser.new do |opts|
opts.banner = "Usage: chromatic_adaptation.rb [ -s, --source | --d, --destination | -m, --method ]"
opts.on( "-s", "--source k5900 | d50 | d55 | d65 (Default) | rec709 | d75 | dci_calibration_white", "White point reference of source" ) do |p|
options.source = p
end
opts.on( "-d", "--destination k5900 | d50 | d55 | d65 | rec709 | d75 | dci_calibration_white (Default)", "White point reference of destination" ) do |p|
options.destination = p
end
opts.on( "-m", "--method xyzscaling | bradford (Default) | vonkries", "Cone response domain definition" ) do |p|
options.crd_definition = p
end
opts.on( "-o", "--output block | line (Default)", "Format output as a 3x3 block or as a line" ) do |p|
options.output = p
end
opts.on_tail( '-h', '--help', 'Display this screen' ) do
puts opts
exit
end
end # Optionparser.new
opts.parse!(args)
options
end # parse()
end # class Optparser
options = Optparser.parse(ARGV)
def t_to_xyY( t )
if 4000 <= t and t <= 7000
x = -4.6070 * 10 ** 9 / t ** 3 + 2.9678 * 10 ** 6 / t ** 2 + 0.09911 * 10 ** 3 / t + 0.244063
elsif 7000 < t and t <= 25000
x = -2.0064 * 10 ** 9 / t ** 3 + 1.9018 * 10 ** 6 / t ** 2 + 0.24748 * 10 ** 3 / t + 0.237040
end
y = -3.000 * x ** 2 + 2.870 * x - 0.275
_Y = 1.0
return x, y, _Y
end
def xyY_to_XYZ( x, y, _Y)
_X = x * _Y / y
_Y = _Y
_Z = ( 1 - x - y ) * _Y / y
return _X, _Y, _Z
end
def XYZ_to_xyY( _X, _Y, _Z )
x = _X / ( _X + _Y + _Z )
y = _Y / ( _X + _Y + _Z )
_Y = _Y
return x, y, _Y
end
# White points. Take with a grain of salt wrt rounding errors (D65/ITU Rec. 709)
EE_X, EE_Y, EE_Z = 1.0, 1.0, 1.0
D50_X, D50_Y, D50_Z = 0.96422, 1.00000, 0.82521 # x = 0.3457, y = 0.3585
D55_X, D55_Y, D55_Z = 0.95682, 1.00000, 0.92149 # x = 0.3324, y = 0.3474
D65_X, D65_Y, D65_Z = 0.95047, 1.00000, 1.08883 # x = 0.3127, y = 0.329
# ITU Rec. 709: x = 0.3127, y = 0.3290
REC709_X, REC709_Y, REC709_Z = 0.950455927051672, 1.0, 1.08905775075988
D75_X, D75_Y, D75_Z = 0.94972, 1.00000, 1.22638
# DCI Calibration White: x = 0.3140, y = 0.3510, Y=1.0 (48 cd/m**2)
DCI_Calibration_White_X, DCI_Calibration_White_Y, DCI_Calibration_White_Z = 0.894583, 1.00000, 0.954417
# Offered by Adobe CS4 ("DCDM X'Y'Z' (gamma 2.6) 5900K (by Adobe)")
tmp_x, tmp_y, tmp_Y = t_to_xyY( 5900 ) # x = 0.324, y = 0.340
K5900_X, K5900_Y, K5900_Z = xyY_to_XYZ( tmp_x, tmp_y, tmp_Y )
# Cone response domain definitions, 3 methods:
M_A_XYZScaling = Matrix[
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]
]
M_A_Bradford = Matrix[
[0.8951000, 0.2664000, -0.1614000],
[-0.7502000, 1.7135000, 0.0367000],
[0.0389000, -0.0685000, 1.0296000]
]
M_A_Von_Kries = Matrix[
[0.4002400, 0.7076000, -0.0808100],
[-0.2263000, 1.1653200, 0.0457000],
[0.0000000, 0.0000000, 0.9182200]
]
case options.source
when "k5900" : X_WS, Y_WS, Z_WS = K5900_X, K5900_Y, K5900_Z
when "d50" : X_WS, Y_WS, Z_WS = D50_X, D50_Y, D50_Z
when "d55" : X_WS, Y_WS, Z_WS = D55_X, D55_Y, D55_Z
when "d65" : X_WS, Y_WS, Z_WS = D65_X, D65_Y, D65_Z
when "rec709" : X_WS, Y_WS, Z_WS = REC709_X, REC709_Y, REC709_Z
when "d75" : X_WS, Y_WS, Z_WS = D75_X, D75_Y, D75_Z
when "dci_calibration_white": X_WS, Y_WS, Z_WS = DCI_Calibration_White_X, DCI_Calibration_White_Y, DCI_Calibration_White_Z
end
case options.destination
when "k5900" : X_WD, Y_WD, Z_WD = K5900_X, K5900_Y, K5900_Z
when "d50" : X_WD, Y_WD, Z_WD = D50_X, D50_Y, D50_Z
when "d55" : X_WD, Y_WD, Z_WD = D55_X, D55_Y, D55_Z
when "d65" : X_WD, Y_WD, Z_WD = D65_X, D65_Y, D65_Z
when "rec709" : X_WD, Y_WD, Z_WD = REC709_X, REC709_Y, REC709_Z
when "d75" : X_WD, Y_WD, Z_WD = D75_X, D75_Y, D75_Z
when "dci_calibration_white": X_WD, Y_WD, Z_WD = DCI_Calibration_White_X, DCI_Calibration_White_Y, DCI_Calibration_White_Z
end
case options.crd_definition
when "xyzscaling" : M_A = M_A_XYZScaling
when "bradford" : M_A = M_A_Bradford
when "vonkries" : M_A = M_A_Von_Kries
end
# CRD, Cone response domain
CRD_S = M_A * Matrix[ [X_WS], [Y_WS], [Z_WS] ]
CRD_D = M_A * Matrix[ [X_WD], [Y_WD], [Z_WD] ]
rho_S, gamma_S, beta_S = CRD_S.row(0)[0], CRD_S.row(1)[0], CRD_S.row(2)[0]
rho_D, gamma_D, beta_D = CRD_D.row(0)[0], CRD_D.row(1)[0], CRD_D.row(2)[0]
# CAT, Chromatic adaptation transform
CAT = M_A ** -1 * Matrix[
[rho_D / rho_S, 0, 0],
[0, gamma_D / gamma_S, 0],
[0, 0, beta_D / beta_S] ] * M_A
# Print result
def j(row)
row.to_a.join(' ')
end
puts "Chromatic adaptation transformation for #{options.source} -> #{options.destination} (CRD definition: #{options.crd_definition}):"
case options.output
when 'block'
puts [ j( CAT.row(0) ), j( CAT.row(1) ), j( CAT.row(2) ) ].join "\n"
else
puts [ j( CAT.row(0) ), j( CAT.row(1) ), j( CAT.row(2) ) ].join " "
end