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,137 @@
1
+ module Quadtone
2
+
3
+ class CGATS
4
+
5
+ attr_accessor :sections
6
+
7
+ def self.new_from_file(file)
8
+ cgats = new
9
+ section_index = 0
10
+ state = :header
11
+ Pathname.new(file).readlines.each do |line|
12
+ line.chomp!
13
+ line.sub!(/#.*/, '')
14
+ line.strip!
15
+ next if line.empty?
16
+ section = cgats.sections[section_index] || cgats.add_section
17
+ case state
18
+ when :header
19
+ case line
20
+ when 'BEGIN_DATA_FORMAT'
21
+ state = :data_format
22
+ when 'BEGIN_DATA'
23
+ state = :data
24
+ else
25
+ key, value = line.split(/\s+/, 2)
26
+ if section.header[key]
27
+ if !section.header[key].kind_of?(Array)
28
+ section.header[key] = [section.header[key]]
29
+ end
30
+ section.header[key] << value
31
+ else
32
+ section.header[key] = value
33
+ end
34
+ end
35
+ when :data_format
36
+ case line
37
+ when 'END_DATA_FORMAT'
38
+ state = :header
39
+ else
40
+ line.split(/\s+/).each { |f| section.data_fields << f }
41
+ end
42
+ when :data
43
+ case line
44
+ when 'END_DATA'
45
+ # Emission data (BEGIN_DATA_EMISSION) may come after here, but we don't handle it.
46
+ section_index += 1
47
+ state = :header
48
+ else
49
+ values = line.split(/\s+/).map do |v|
50
+ case v
51
+ when /^-?\d+$/
52
+ v.to_i
53
+ when /^-?\d+\.\d+$/
54
+ v.to_f
55
+ else
56
+ v.to_s
57
+ end
58
+ end
59
+ set = {}
60
+ values.each_with_index do |value, i|
61
+ set[section.data_fields[i]] = value
62
+ end
63
+ section.data << set
64
+ end
65
+ end
66
+ end
67
+ cgats
68
+ end
69
+
70
+ def initialize
71
+ @sections = []
72
+ end
73
+
74
+ def add_section
75
+ @sections << Section.new
76
+ @sections[-1]
77
+ end
78
+
79
+ def write(io)
80
+ @sections.each do |section|
81
+ section.write(io)
82
+ io.puts
83
+ end
84
+ nil
85
+ end
86
+
87
+ class Section
88
+
89
+ attr_accessor :header
90
+ attr_accessor :data
91
+ attr_accessor :data_fields
92
+
93
+ def initialize
94
+ @header = {}
95
+ @data = []
96
+ @data_fields = []
97
+ end
98
+
99
+ def <<(set)
100
+ @data << set
101
+ end
102
+
103
+ def write(io)
104
+ # header
105
+ @header.each { |k, v| io.puts k.to_s + (v ? " \"#{v}\"" : '') }
106
+ # data format
107
+ io.puts
108
+ io.puts "NUMBER_OF_FIELDS #{@data_fields.length}"
109
+ io.puts 'BEGIN_DATA_FORMAT'
110
+ io.puts @data_fields.join(' ')
111
+ io.puts 'END_DATA_FORMAT'
112
+ # data
113
+ io.puts
114
+ io.puts "NUMBER_OF_SETS #{@data.length}"
115
+ io.puts 'BEGIN_DATA'
116
+ @data.each do |set|
117
+ fields = @data_fields.map do |f|
118
+ case (d = set[f])
119
+ when Float
120
+ '%.05f' % d
121
+ when String
122
+ '"' + d + '"'
123
+ else
124
+ d
125
+ end
126
+ end
127
+ io.puts fields.join(' ')
128
+ end
129
+ io.puts 'END_DATA'
130
+ nil
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+
137
+ end
@@ -0,0 +1,81 @@
1
+ # after:
2
+ # http://colinfdrake.com/2011/05/28/clustering-in-ruby.html
3
+ # http://m635j520.blogspot.com/2013/02/implementing-k-means-clustering-in-ruby.html
4
+
5
+ class ClusterCalculator
6
+
7
+ class Cluster
8
+
9
+ attr_accessor :center
10
+ attr_accessor :samples
11
+ attr_accessor :moved
12
+
13
+ def initialize(center)
14
+ @center = center
15
+ @samples = []
16
+ @moved = true
17
+ end
18
+
19
+ def add_sample(sample)
20
+ @samples << sample
21
+ end
22
+
23
+ def clear_samples
24
+ @samples = []
25
+ end
26
+
27
+ def distance_to(sample)
28
+ @center.delta_e(sample.output)
29
+ end
30
+
31
+ def update_center(delta=0.001)
32
+ @moved = false
33
+ average, error = Color::Lab.average(@samples.map(&:output))
34
+ unless average.delta_e(@center) < delta
35
+ @center = average
36
+ @moved = true
37
+ end
38
+ end
39
+
40
+ def size
41
+ @samples.length
42
+ end
43
+
44
+ end
45
+
46
+ attr_accessor :samples
47
+ attr_accessor :max_clusters
48
+ attr_accessor :delta
49
+ attr_accessor :clusters
50
+
51
+ def initialize(params={})
52
+ @delta = 0.001
53
+ params.each { |k, v| send("#{k}=", v) }
54
+ raise "Must specify samples" unless @samples
55
+ raise "Must specify max_clusters" unless @max_clusters
56
+ @max_clusters = @samples.length if @max_clusters > @samples.length
57
+ end
58
+
59
+ def cluster!
60
+ @clusters = @max_clusters.times.map { Cluster.new(@samples.sample.output) }
61
+ while @clusters.any?(&:moved)
62
+ @clusters.each(&:clear_samples)
63
+ @samples.each do |sample|
64
+ shortest = Float::INFINITY
65
+ cluster_found = nil
66
+ @clusters.each do |cluster|
67
+ distance = cluster.distance_to(sample)
68
+ if distance < shortest
69
+ cluster_found = cluster
70
+ shortest = distance
71
+ end
72
+ end
73
+ cluster_found.add_sample(sample) if cluster_found
74
+ end
75
+ @clusters.delete_if { |c| c.size == 0 }
76
+ @clusters.each { |c| c.update_center(@delta) }
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,83 @@
1
+ module Color
2
+
3
+ class Base
4
+
5
+ include Math
6
+
7
+ attr_accessor :components
8
+
9
+ def self.component_names
10
+ raise NotImplementedError, "\#component_names not implemented in #{self}"
11
+ end
12
+
13
+ def self.cgats_fields
14
+ raise NotImplementedError, "\#cgats_fields not implemented in #{self}"
15
+ end
16
+
17
+ def self.num_components
18
+ component_names.length
19
+ end
20
+
21
+ def self.colorspace_name
22
+ self.to_s.split('::').last.downcase
23
+ end
24
+
25
+ def self.from_cgats(set)
26
+ new(set.values_at(*cgats_fields))
27
+ end
28
+
29
+ def self.average(colors)
30
+ avg_components = []
31
+ errors = []
32
+ component_names.each_with_index do |comp, i|
33
+ avg_components << colors.map { |c| c.components[i] }.mean
34
+ errors << colors.map { |c| c.components[i] }.standard_deviation
35
+ end
36
+ [new(avg_components), errors.max]
37
+ end
38
+
39
+ def initialize(arg)
40
+ components = case arg
41
+ when String
42
+ arg =~ /^(\w+)\((.+)\)$/ or raise "Can't initialize #{self.class}: bad color string: #{arg.inspect}"
43
+ raise "Expected #{self.class.colorspace_name.inspect} but got #{$1.inspect}" if $1.downcase != self.class.colorspace_name
44
+ $2.split(/,\s+/).map(&:to_f)
45
+ when Hash
46
+ self.class.component_names.map { |n| arg[n] }
47
+ when Array
48
+ arg.map(&:to_f)
49
+ else
50
+ raise "Can't initialize #{self.class}: unknown object: #{arg.inspect}"
51
+ end
52
+ raise "Can't initialize #{self.class}: too many components specified: #{components.inspect}" if components.length > self.class.num_components
53
+ @components = [0] * self.class.num_components
54
+ components.each_with_index { |n, i| @components[i] = n if n }
55
+ end
56
+
57
+ def to_s
58
+ "#{self.class.colorspace_name}(#{@components.map { |n| '%3.1f' % n }.join(', ')})"
59
+ end
60
+
61
+ def to_str
62
+ to_s
63
+ end
64
+
65
+ def to_cgats
66
+ @components
67
+ end
68
+
69
+ def hash
70
+ @components.hash
71
+ end
72
+
73
+ def eql?(other)
74
+ @components == other.components
75
+ end
76
+
77
+ def <=>(other)
78
+ @components <=> other.components
79
+ end
80
+
81
+ end
82
+
83
+ end
@@ -0,0 +1,112 @@
1
+ module Color
2
+
3
+ class CMYK < Base
4
+
5
+ def self.component_names
6
+ [:c, :m, :y, :k, :lc, :lm, :lk, :llk]
7
+ end
8
+
9
+ def self.cgats_fields
10
+ %w{CMYKcmk1k_C CMYKcmk1k_M CMYKcmk1k_Y CMYKcmk1k_K
11
+ CMYKcmk1k_c CMYKcmk1k_m CMYKcmk1k_k CMYKcmk1k_1k}
12
+ end
13
+
14
+ def self.cgats_color_rep
15
+ 'CMYKcmk1k'
16
+ end
17
+
18
+ def c
19
+ @components[0]
20
+ end
21
+
22
+ def m
23
+ @components[1]
24
+ end
25
+
26
+ def y
27
+ @components[2]
28
+ end
29
+
30
+ def k
31
+ @components[3]
32
+ end
33
+
34
+ def lc
35
+ @components[4]
36
+ end
37
+
38
+ def lm
39
+ @components[5]
40
+ end
41
+
42
+ def lk
43
+ @components[6]
44
+ end
45
+
46
+ def llk
47
+ @components[7]
48
+ end
49
+
50
+ def to_cgats
51
+ {
52
+ 'CMYKcmk1k_C' => c,
53
+ 'CMYKcmk1k_M' => m,
54
+ 'CMYKcmk1k_Y' => y,
55
+ 'CMYKcmk1k_K' => k,
56
+ 'CMYKcmk1k_c' => lc,
57
+ 'CMYKcmk1k_m' => lm,
58
+ 'CMYKcmk1k_k' => lk,
59
+ 'CMYKcmk1k_1k' => llk,
60
+ }
61
+ end
62
+
63
+ def to_cmyk
64
+ # estimates for light & light-light inks
65
+ l_factor = 0.5
66
+ ll_factor = 0.25
67
+
68
+ # first adjust for light inks
69
+ c0 = c + (lc * l_factor)
70
+ m0 = m + (lm * l_factor)
71
+ y0 = y
72
+ k0 = k + (lk * l_factor) + (llk * ll_factor)
73
+
74
+ Color::CMYK.new([c0, m0, y0, k0])
75
+ end
76
+
77
+ def to_cmy
78
+ # after http://www.easyrgb.com/index.php?X=MATH&H=14#text14
79
+
80
+ c0, m0, y0, k0 = *to_cmyk
81
+ c0 /= 100.0
82
+ m0 /= 100.0
83
+ y0 /= 100.0
84
+ k0 /= 100.0
85
+
86
+ c0 = (c0 * (1 - k0)) + k0
87
+ m0 = (m0 * (1 - k0)) + k0
88
+ y0 = (y0 * (1 - k0)) + k0
89
+
90
+ Color::CMYK.new([c0 * 100, m0 * 100, y0 * 100])
91
+ end
92
+
93
+ def to_rgb
94
+ cmy = to_cmy
95
+ Color::RGB.new([1 - (cmy.c / 100), 1 - (cmy.m / 100), 1 - (cmy.y / 100)])
96
+ end
97
+
98
+ def to_lab
99
+ to_xyz.to_lab
100
+ end
101
+
102
+ def to_xyz
103
+ to_rgb.to_xyz
104
+ end
105
+
106
+ def to_a
107
+ @components
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,23 @@
1
+ module Color
2
+
3
+ class DeviceN < Base
4
+
5
+ Components = (0..7).to_a
6
+
7
+ def self.component_names
8
+ Components
9
+ end
10
+
11
+ def self.cgats_fields
12
+ Components.map do |i|
13
+ "DEVICE_#{i}"
14
+ end
15
+ end
16
+
17
+ def to_gray
18
+ Color::Gray.new(@components.find { |c| c != 0 } || 0)
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,46 @@
1
+ module Color
2
+
3
+ class Gray < Base
4
+
5
+ def self.component_names
6
+ [:k]
7
+ end
8
+
9
+ def self.cgats_fields
10
+ %w{GRAY_K}
11
+ end
12
+
13
+ def self.cgats_color_rep
14
+ 'K'
15
+ end
16
+
17
+ def k
18
+ @components[0]
19
+ end
20
+
21
+ def value
22
+ k / 100.0
23
+ end
24
+
25
+ def to_cgats
26
+ {
27
+ 'GRAY_K' => k,
28
+ }
29
+ end
30
+
31
+ def to_rgb
32
+ n = 1 - value
33
+ Color::RGB.new([n, n, n])
34
+ end
35
+
36
+ def to_lab
37
+ Color::Lab.new(l: 100 - k, a: 0, b: 0)
38
+ end
39
+
40
+ def to_xyz
41
+ to_lab.to_xyz
42
+ end
43
+
44
+ end
45
+
46
+ end