qttk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +6 -0
  4. data/Gemfile.lock +36 -0
  5. data/README.markdown +131 -0
  6. data/Rakefile +17 -0
  7. data/TODO.markdown +236 -0
  8. data/bin/qt +7 -0
  9. data/etc/gutenprint/gp-tool.rb +56 -0
  10. data/etc/gutenprint/gutenprint-filter.c +400 -0
  11. data/etc/gutenprint/gutenprint.rb +86 -0
  12. data/etc/gutenprint/stp-test +326 -0
  13. data/etc/images/3551599565_db282cf840_o.jpg +0 -0
  14. data/etc/images/4843122063_d582c569e9_o.jpg +0 -0
  15. data/etc/images/4843128953_83c1770907_o.jpg +0 -0
  16. data/lib/quadtone.rb +56 -0
  17. data/lib/quadtone/cgats.rb +137 -0
  18. data/lib/quadtone/cluster_calculator.rb +81 -0
  19. data/lib/quadtone/color.rb +83 -0
  20. data/lib/quadtone/color/cmyk.rb +112 -0
  21. data/lib/quadtone/color/device_n.rb +23 -0
  22. data/lib/quadtone/color/gray.rb +46 -0
  23. data/lib/quadtone/color/lab.rb +150 -0
  24. data/lib/quadtone/color/qtr.rb +71 -0
  25. data/lib/quadtone/color/rgb.rb +71 -0
  26. data/lib/quadtone/color/xyz.rb +80 -0
  27. data/lib/quadtone/curve.rb +138 -0
  28. data/lib/quadtone/curve_set.rb +196 -0
  29. data/lib/quadtone/descendants.rb +9 -0
  30. data/lib/quadtone/environment.rb +5 -0
  31. data/lib/quadtone/extensions/math.rb +11 -0
  32. data/lib/quadtone/extensions/pathname3.rb +11 -0
  33. data/lib/quadtone/printer.rb +106 -0
  34. data/lib/quadtone/profile.rb +217 -0
  35. data/lib/quadtone/quad_file.rb +59 -0
  36. data/lib/quadtone/renderer.rb +139 -0
  37. data/lib/quadtone/run.rb +10 -0
  38. data/lib/quadtone/sample.rb +32 -0
  39. data/lib/quadtone/separator.rb +36 -0
  40. data/lib/quadtone/target.rb +277 -0
  41. data/lib/quadtone/tool.rb +61 -0
  42. data/lib/quadtone/tools/add_printer.rb +73 -0
  43. data/lib/quadtone/tools/characterize.rb +43 -0
  44. data/lib/quadtone/tools/chart.rb +31 -0
  45. data/lib/quadtone/tools/check.rb +16 -0
  46. data/lib/quadtone/tools/dir.rb +15 -0
  47. data/lib/quadtone/tools/edit.rb +23 -0
  48. data/lib/quadtone/tools/init.rb +82 -0
  49. data/lib/quadtone/tools/install.rb +15 -0
  50. data/lib/quadtone/tools/linearize.rb +28 -0
  51. data/lib/quadtone/tools/list.rb +19 -0
  52. data/lib/quadtone/tools/print.rb +38 -0
  53. data/lib/quadtone/tools/printer_options.rb +40 -0
  54. data/lib/quadtone/tools/rename.rb +17 -0
  55. data/lib/quadtone/tools/render.rb +43 -0
  56. data/lib/quadtone/tools/rewrite.rb +15 -0
  57. data/lib/quadtone/tools/separate.rb +71 -0
  58. data/lib/quadtone/tools/show.rb +15 -0
  59. data/lib/quadtone/tools/test.rb +26 -0
  60. data/qttk.gemspec +34 -0
  61. metadata +215 -0
@@ -0,0 +1,150 @@
1
+ module Color
2
+
3
+ class Lab < Base
4
+
5
+ DeltaEMethods = [
6
+ :density,
7
+ :cie76,
8
+ :cie94,
9
+ :cmclc
10
+ ]
11
+
12
+ def self.component_names
13
+ [:l, :a, :b]
14
+ end
15
+
16
+ def self.cgats_fields
17
+ %w{LAB_L LAB_A LAB_B}
18
+ end
19
+
20
+ def l
21
+ @components[0]
22
+ end
23
+
24
+ def a
25
+ @components[1]
26
+ end
27
+
28
+ def b
29
+ @components[2]
30
+ end
31
+
32
+ def value
33
+ 1 - (l / 100.0)
34
+ end
35
+
36
+ def chroma
37
+ # http://www.brucelindbloom.com/Eqn_Lab_to_LCH.html
38
+ sqrt((a * a) + (b * b))
39
+ end
40
+
41
+ def hue
42
+ # http://www.brucelindbloom.com/Eqn_Lab_to_LCH.html
43
+ if a == 0 && b == 0
44
+ 0
45
+ else
46
+ rad2deg(atan2(b, a)) % 360
47
+ end
48
+ end
49
+
50
+ def delta_e(other, method=:cmclc)
51
+ # http://en.wikipedia.org/wiki/Color_difference
52
+ # http://www.brucelindbloom.com/iPhone/ColorDiff.html
53
+ l1, a1, b1 = self.l, self.a, self.b
54
+ l2, a2, b2 = other.l, other.a, other.b
55
+ dl = l2 - l1
56
+ da = a1 - a2
57
+ db = b1 - b2
58
+ if method == :cie94 || method == :cmclc
59
+ c1, c2 = self.chroma, other.chroma
60
+ h1, h2 = self.hue, other.hue
61
+ dc = c1 - c2
62
+ dh2 = da**2 + db**2 - dc**2
63
+ return Float::NAN if dh2 < 0
64
+ dh = sqrt(dh2)
65
+ end
66
+ case method
67
+ when :density
68
+ dl.abs
69
+ when :cie76
70
+ sqrt(dl**2 + da**2 + db**2)
71
+ when :cie94
72
+ kl, k1, k2 = 1, 0.045, 0.015
73
+ sqrt(
74
+ (dl / kl)**2 +
75
+ (dc / (1 + k1*c1))**2 +
76
+ (dh / (1 + k2*c2)**2)
77
+ )
78
+ when :cmclc
79
+ l, c = 2, 1
80
+ sl = (l1 < 16) ?
81
+ 0.511 :
82
+ 0.040975 * l1 / (1 + 0.01765 * l1)
83
+ sc = 0.0638 * c1 / (1 + 0.0131 * c1) + 0.638
84
+ f = sqrt(
85
+ (c1 ** 4) / ((c1 ** 4) + 1900)
86
+ )
87
+ t = (h1 >= 164 && h1 <= 345) ?
88
+ 0.56 + (0.2 * cos(deg2rad(h1 + 168))).abs :
89
+ 0.36 + (0.4 * cos(deg2rad(h1 + 35))).abs
90
+ sh = sc * ((f * t) + 1 - f)
91
+ sqrt(
92
+ (dl / (l * sl)) ** 2 +
93
+ (dc / (c * sc)) ** 2 +
94
+ (dh / sh) ** 2
95
+ )
96
+ else
97
+ raise "Unknown deltaE method: #{method.inspect}"
98
+ end
99
+ end
100
+
101
+ def to_xyz
102
+ # after http://www.easyrgb.com/index.php?X=MATH&H=08#text8
103
+
104
+ y = (l + 16) / 116.0
105
+ x = (a / 500.0) + y
106
+ z = y - (b / 200.0)
107
+
108
+ x, y, z = [x, y, z].map do |n|
109
+ if (n3 = n**3) > 0.008856
110
+ n3
111
+ else
112
+ (n - 16 / 116.0) / 7.787
113
+ end
114
+ end
115
+
116
+ ref = Color::XYZ.standard_reference
117
+ x *= ref.x
118
+ y *= ref.y
119
+ z *= ref.z
120
+
121
+ Color::XYZ.new([x, y, z])
122
+ end
123
+
124
+ def to_rgb
125
+ to_xyz.to_rgb
126
+ end
127
+
128
+ end
129
+
130
+ end
131
+
132
+ if $0 == __FILE__
133
+
134
+ pairs = [
135
+ [Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(50,0,0)],
136
+ [Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(51,0,0)],
137
+ [Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(50,0,1)],
138
+ [Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(50,1,0)],
139
+ [Color::Lab.from_lab(50,0,0), Color::Lab.from_lab(50,1,1)],
140
+ ]
141
+
142
+ pairs.each do |pair|
143
+ l1, l2 = *pair
144
+ puts "#{l1.inspect} ~ #{l2.inspect}"
145
+ [:density, :cie76, :cie94, :cmclc].each do |method|
146
+ puts "\t" + "#{method}: %.4f" % l1.delta_e(l2, method)
147
+ end
148
+ end
149
+
150
+ end
@@ -0,0 +1,71 @@
1
+ # "Color" in QTR calibration mode.
2
+
3
+ module Color
4
+
5
+ class QTR < Base
6
+
7
+ =begin
8
+
9
+ QTR channel map (in calibration mode):
10
+
11
+ R: inverse bitmask for channels
12
+
13
+ 7: K = 127 (01111111)
14
+ 6: C = 191 (10111111)
15
+ 5: M = 223 (11011111)
16
+ 4: Y = 239 (11101111)
17
+ 3: LC = 247 (11110111)
18
+ 2: LM = 251 (11111011)
19
+ 1: LK = 253 (11111101)
20
+ 0: LLK = 254 (11111110)
21
+
22
+ G: value (0-255)
23
+
24
+ B: unused -- should always be 255
25
+
26
+ For background, use R=127 / G=255 / B=255
27
+ =end
28
+
29
+ Channels = [:llk, :lk, :lm, :lc, :y, :m, :c, :k]
30
+
31
+ def self.component_names
32
+ [:channel, :value]
33
+ end
34
+
35
+ def self.cgats_fields
36
+ %w{QTR_CHANNEL QTR_VALUE}
37
+ end
38
+
39
+ def channel
40
+ @components[0]
41
+ end
42
+
43
+ def channel_num
44
+ Channels.index(channel)
45
+ end
46
+
47
+ def value
48
+ @components[1]
49
+ end
50
+
51
+ def to_rgb
52
+ Color::RGB.new(
53
+ r: (255 - (1 << channel_num)) / 255.0,
54
+ g: (1 - value) / 100,
55
+ b: 1)
56
+ end
57
+
58
+ def to_gray
59
+ Color::Gray.new(k: value)
60
+ end
61
+
62
+ def to_cgats
63
+ {
64
+ 'QTR_CHANNEL' => channel,
65
+ 'QTR_VALUE' => value,
66
+ }
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -0,0 +1,71 @@
1
+ module Color
2
+
3
+ class RGB < Base
4
+
5
+ def self.component_names
6
+ [:r, :g, :b]
7
+ end
8
+
9
+ def self.cgats_fields
10
+ %w{RGB_R RGB_G RGB_B}
11
+ end
12
+
13
+ def self.from_cgats(set)
14
+ new(*set.values_at(*cgats_fields).map { |n| n / 100.0 })
15
+ end
16
+
17
+ def r
18
+ @components[0]
19
+ end
20
+
21
+ def g
22
+ @components[1]
23
+ end
24
+
25
+ def b
26
+ @components[2]
27
+ end
28
+
29
+ def to_cgats
30
+ {
31
+ 'RGB_R' => r * 100,
32
+ 'RGB_G' => g * 100,
33
+ 'RGB_B' => b * 100,
34
+ }
35
+ end
36
+
37
+ def to_xyz
38
+ # after http://www.easyrgb.com/index.php?X=MATH&H=02#text2
39
+
40
+ r0, g0, b0 = [r, g, b].map do |n|
41
+ if n > 0.04045
42
+ ((n + 0.055) / 1.055) ** 2.4
43
+ else
44
+ n / 12.92
45
+ end
46
+ end
47
+
48
+ r0 *= 100
49
+ g0 *= 100
50
+ b0 *= 100
51
+
52
+ # Observer. = 2°, Illuminant = D65
53
+
54
+ x = (r0 * 0.4124) + (g0 * 0.3576) + (b0 * 0.1805)
55
+ y = (r0 * 0.2126) + (g0 * 0.7152) + (b0 * 0.0722)
56
+ z = (r0 * 0.0193) + (g0 * 0.1192) + (b0 * 0.9505)
57
+
58
+ Color::XYZ.new([x, y, z])
59
+ end
60
+
61
+ def to_a
62
+ [r, g, b]
63
+ end
64
+
65
+ def to_pixel
66
+ Magick::Pixel.new(*to_a.map { |n| n * Magick::QuantumRange })
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -0,0 +1,80 @@
1
+ module Color
2
+
3
+ class XYZ < Base
4
+
5
+ # Observer= 2°, Illuminant= D65
6
+ def self.standard_reference
7
+ @@reference ||= new([95.047, 100.000, 108.883])
8
+ end
9
+
10
+ def self.component_names
11
+ [:x, :y, :z]
12
+ end
13
+
14
+ def self.cgats_fields
15
+ %w{XYZ_X XYZ_Y XYZ_Z}
16
+ end
17
+
18
+ def x
19
+ @components[0]
20
+ end
21
+
22
+ def y
23
+ @components[1]
24
+ end
25
+
26
+ def z
27
+ @components[2]
28
+ end
29
+
30
+ def to_lab
31
+ # http://www.easyrgb.com/index.php?X=MATH&H=07#text7
32
+
33
+ ref = self.class.standard_reference
34
+
35
+ x = self.x / ref.x
36
+ y = self.y / ref.y
37
+ z = self.z / ref.z
38
+
39
+ x, y, z = [x, y, z].map do |n|
40
+ if n > 0.008856
41
+ n ** (1.0 / 3)
42
+ else
43
+ (7.787 * n) + (16 / 116)
44
+ end
45
+ end
46
+
47
+ l = (116 * y) - 16
48
+ a = 500 * (x - y)
49
+ b = 200 * (y - z)
50
+
51
+ Color::Lab.new([l, a, b])
52
+ end
53
+
54
+ def to_rgb
55
+ rgb = [
56
+ (x * 3.2406) + (y * -1.5372) + (z * -0.4986),
57
+ (x * -0.9689) + (y * 1.8758) + (z * 0.0415),
58
+ (x * 0.0557) + (y * -0.2040) + (z * 1.0570),
59
+ ]
60
+ rgb = rgb.map do |n|
61
+ if n > 0.0031308
62
+ 1.055 * (n ** (1 / 2.4)) - 0.055
63
+ else
64
+ 12.92 * rgb[0]
65
+ end
66
+ end
67
+ Color::RGB.new(rgb)
68
+ end
69
+
70
+ def to_cgats
71
+ {
72
+ 'XYZ_X' => x,
73
+ 'XYZ_Y' => y,
74
+ 'XYZ_Z' => z,
75
+ }
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,138 @@
1
+ module Quadtone
2
+
3
+ class Curve
4
+
5
+ DeltaETolerance = 1.0
6
+ DeltaEMethod = :density
7
+
8
+ attr_accessor :channel
9
+ attr_accessor :samples
10
+
11
+ def initialize(params={})
12
+ params.each { |key, value| send("#{key}=", value) }
13
+ end
14
+
15
+ def samples=(samples)
16
+ @samples = samples.sort_by(&:input_value)
17
+ end
18
+
19
+ def output_for_input(input)
20
+ @output_spliner ||= Spliner::Spliner.new(@samples.map(&:input_value), @samples.map(&:output_value))
21
+ @output_spliner[input]
22
+ end
23
+
24
+ def input_for_output(output)
25
+ @input_spliner ||= Spliner::Spliner.new(@samples.map(&:output_value), @samples.map(&:input_value))
26
+ @input_spliner[output]
27
+ end
28
+
29
+ def num_samples
30
+ @samples.length
31
+ end
32
+
33
+ def grayscale(steps)
34
+ spliner = Spliner::Spliner.new(Hash[ @samples.map { |s| [s.input.value, s.output.l] } ])
35
+ scale = (0 .. 1).step(1.0 / (steps - 1))
36
+ spliner[scale].map { |l| Color::Lab.new([l]) }
37
+ end
38
+
39
+ def ink_limit(method=DeltaEMethod)
40
+ # ;;@samples.each { |s| warn "%s-%s: %.2f => %.2f" % [@channel, s.id, s.input.value, s.output.value] }
41
+ @samples.each_with_index do |sample, i|
42
+ if i > 0
43
+ previous_sample = @samples[i - 1]
44
+ return previous_sample if sample.output_value < previous_sample.output_value
45
+ return previous_sample if previous_sample.output.delta_e(sample.output, method) < DeltaETolerance
46
+ end
47
+ end
48
+ @samples.last
49
+ end
50
+
51
+ def trim_to_limit
52
+ limit = ink_limit
53
+ i = @samples.index(limit)
54
+ ;;warn "trimming curve #{@channel} to sample \##{i}: #{limit}"
55
+ @samples.slice!(i + 1 .. -1)
56
+ @input_spliner = @output_spliner = nil
57
+ end
58
+
59
+ def normalize_inputs
60
+ scale = 1 / @samples.last.input.value
61
+ @samples.each do |sample|
62
+ sample.input = Color::Gray.new(k: sample.input.k * scale)
63
+ end
64
+ @input_spliner = @output_spliner = nil
65
+ end
66
+
67
+ def verify_increasing_values
68
+ @samples.each_with_index do |sample, i|
69
+ if i > 0
70
+ previous_sample = @samples[i - 1]
71
+ if sample.output_value < previous_sample.output_value
72
+ raise "Samples not in increasing order (#{sample.label} [#{i}]: #{sample.output_value} < #{previous_sample.output_value})"
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def dmin
79
+ @samples.first.output_value
80
+ end
81
+
82
+ def dmax
83
+ @samples.last.output_value
84
+ end
85
+
86
+ def dynamic_range
87
+ [dmin, dmax]
88
+ end
89
+
90
+ def draw_svg(svg, options={})
91
+ size = options[:size] || 500
92
+
93
+ # draw interpolated curve
94
+ svg.g(fill: 'none', stroke: 'green', :'stroke-width' => 1) do
95
+ samples = (0..1).step(1.0 / size).map do |n|
96
+ [size * n, size * output_for_input(n)]
97
+ end
98
+ svg.polyline(points: samples.map { |pt| pt.join(',') }.join(' '))
99
+ end
100
+
101
+ # draw markers for ink limits
102
+ {
103
+ density: 'gray',
104
+ # cie76: 'red',
105
+ # cie94: 'green',
106
+ # cmclc: 'blue',
107
+ }.each do |method, color|
108
+ limit = ink_limit(method)
109
+ x, y = size * limit.input_value, size * limit.output_value
110
+ svg.g(stroke: color, :'stroke-width' => 3) do
111
+ svg.line(x1: x, y1: y + 8, x2: x, y2: y - 8)
112
+ end
113
+ end
114
+
115
+ # if (limit = ink_limit)
116
+ # x, y = size * limit.input_value, size * limit.output_value
117
+ # svg.g(stroke: 'black', :'stroke-width' => 3) do
118
+ # svg.line(x1: x, y1: y + 15, x2: x, y2: y - 15)
119
+ # end
120
+ # end
121
+
122
+ # draw individual samples
123
+ @samples.each_with_index do |sample, i|
124
+ svg.circle(
125
+ cx: size * sample.input_value,
126
+ cy: size * sample.output_value,
127
+ r: 3,
128
+ stroke: 'none',
129
+ fill: "rgb(#{sample.output.to_rgb.to_a.join(',')})",
130
+ title: sample.label)
131
+ end
132
+
133
+ svg.target!
134
+ end
135
+
136
+ end
137
+
138
+ end