photo-utils 0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +4 -0
- data/README.rdoc +1 -0
- data/Rakefile +1 -0
- data/TODO.txt +34 -0
- data/bin/photo-util +39 -0
- data/lib/photo_utils.rb +28 -0
- data/lib/photo_utils/angle.rb +17 -0
- data/lib/photo_utils/aperture.rb +62 -0
- data/lib/photo_utils/apex.rb +210 -0
- data/lib/photo_utils/brightness.rb +49 -0
- data/lib/photo_utils/camera.rb +82 -0
- data/lib/photo_utils/compensation.rb +29 -0
- data/lib/photo_utils/extensions/array.rb +11 -0
- data/lib/photo_utils/extensions/float.rb +18 -0
- data/lib/photo_utils/extensions/math.rb +11 -0
- data/lib/photo_utils/extensions/numeric.rb +23 -0
- data/lib/photo_utils/formats.rb +146 -0
- data/lib/photo_utils/frame.rb +36 -0
- data/lib/photo_utils/illuminance.rb +48 -0
- data/lib/photo_utils/length.rb +92 -0
- data/lib/photo_utils/lens.rb +50 -0
- data/lib/photo_utils/scene.rb +180 -0
- data/lib/photo_utils/scene_view.rb +134 -0
- data/lib/photo_utils/sensitivity.rb +44 -0
- data/lib/photo_utils/time.rb +53 -0
- data/lib/photo_utils/tool.rb +17 -0
- data/lib/photo_utils/tools/blur.rb +43 -0
- data/lib/photo_utils/tools/brightness.rb +20 -0
- data/lib/photo_utils/tools/calc_aperture.rb +45 -0
- data/lib/photo_utils/tools/cameras.rb +24 -0
- data/lib/photo_utils/tools/chart_dof.rb +146 -0
- data/lib/photo_utils/tools/compare.rb +305 -0
- data/lib/photo_utils/tools/dof.rb +42 -0
- data/lib/photo_utils/tools/dof_table.rb +45 -0
- data/lib/photo_utils/tools/film_test.rb +57 -0
- data/lib/photo_utils/tools/focal_length.rb +25 -0
- data/lib/photo_utils/tools/reciprocity.rb +36 -0
- data/lib/photo_utils/tools/test.rb +91 -0
- data/lib/photo_utils/value.rb +37 -0
- data/lib/photo_utils/version.rb +5 -0
- data/photo-utils.gemspec +29 -0
- data/test/aperture_test.rb +40 -0
- data/test/apex_test.rb +28 -0
- data/test/brightness_test.rb +36 -0
- data/test/length_test.rb +40 -0
- data/test/scene_test.rb +25 -0
- data/test/sensitivity_test.rb +36 -0
- data/test/time_test.rb +42 -0
- metadata +185 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
module PhotoUtils
|
2
|
+
|
3
|
+
class Camera
|
4
|
+
|
5
|
+
CAMERAS_PATH = Path.new(ENV['HOME']) / '.cameras.rb'
|
6
|
+
|
7
|
+
def self.cameras
|
8
|
+
unless class_variable_defined?('@@cameras')
|
9
|
+
if CAMERAS_PATH.exist?
|
10
|
+
@@cameras = eval(CAMERAS_PATH.read)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
@@cameras
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find(params)
|
17
|
+
if (sel = params[:name])
|
18
|
+
cameras.find { |c| sel === c.name }
|
19
|
+
else
|
20
|
+
raise "Don't know how to search for camera with params: #{params.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.[](name)
|
25
|
+
find(name: name)
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_accessor :name
|
29
|
+
attr_accessor :formats
|
30
|
+
attr_accessor :format
|
31
|
+
attr_accessor :min_shutter
|
32
|
+
attr_accessor :max_shutter
|
33
|
+
attr_accessor :shutter
|
34
|
+
attr_accessor :lenses
|
35
|
+
attr_accessor :lens
|
36
|
+
|
37
|
+
def initialize(params={})
|
38
|
+
if params[:format]
|
39
|
+
params[:formats] = [params.delete(:format)]
|
40
|
+
end
|
41
|
+
params.each { |k, v| send("#{k}=", v) }
|
42
|
+
@format ||= @formats.first
|
43
|
+
normal = @format.frame.diagonal
|
44
|
+
# set the lens to the one closest to normal (diagonal of frame)
|
45
|
+
@lens = @lenses.sort_by { |l| (normal - l.focal_length).abs }.first
|
46
|
+
@shutter = @max_shutter
|
47
|
+
end
|
48
|
+
|
49
|
+
def min_shutter=(t)
|
50
|
+
@min_shutter = t ? Time.new(t) : nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def max_shutter=(t)
|
54
|
+
@max_shutter = t ? Time.new(t) : nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def shutter=(t)
|
58
|
+
@shutter = t ? Time.new(t) : nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def angle_of_view
|
62
|
+
raise "Need focal length and format size to determine angle of view" unless @lens && @lens.focal_length && @format
|
63
|
+
@format.angle_of_view(@lens.focal_length)
|
64
|
+
end
|
65
|
+
|
66
|
+
def print(io=STDOUT)
|
67
|
+
io.puts "#{@name}: format: #{@format}, shutter: #{@max_shutter} - #{@min_shutter}"
|
68
|
+
@lenses.sort_by(&:focal_length).each do |lens|
|
69
|
+
io.puts "\t%s %s: focal length: %s (35mm equiv: %s), aperture: %s~%s" % [
|
70
|
+
lens == self.lens ? '*' : ' ',
|
71
|
+
lens.name,
|
72
|
+
lens.focal_length.format_metric(1),
|
73
|
+
@format.focal_length_equivalent(lens.focal_length).format_metric(1),
|
74
|
+
lens.max_aperture,
|
75
|
+
lens.min_aperture,
|
76
|
+
]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module PhotoUtils
|
2
|
+
|
3
|
+
class Compensation < Value
|
4
|
+
|
5
|
+
# amount specified in stops
|
6
|
+
|
7
|
+
def self.new_from_v(v)
|
8
|
+
new(v)
|
9
|
+
end
|
10
|
+
|
11
|
+
def format_value
|
12
|
+
"Cv:%s%s" % [
|
13
|
+
(self < 0) ? '-' : '+',
|
14
|
+
abs.format(10)
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s(format=:value)
|
19
|
+
case format
|
20
|
+
when :value
|
21
|
+
format_value
|
22
|
+
else
|
23
|
+
raise "Unknown format: #{format.inspect}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'photo_utils'
|
2
|
+
|
3
|
+
INCHES_PER_METER = 39.3700787402
|
4
|
+
|
5
|
+
class Numeric
|
6
|
+
|
7
|
+
def feet
|
8
|
+
PhotoUtils::Length.new(self.to_f * 12 / INCHES_PER_METER * 1000)
|
9
|
+
end
|
10
|
+
|
11
|
+
def inches
|
12
|
+
PhotoUtils::Length.new(self.to_f / INCHES_PER_METER * 1000)
|
13
|
+
end
|
14
|
+
|
15
|
+
def meters
|
16
|
+
PhotoUtils::Length.new(self * 1000)
|
17
|
+
end
|
18
|
+
|
19
|
+
def mm
|
20
|
+
PhotoUtils::Length.new(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module PhotoUtils
|
2
|
+
|
3
|
+
class Format
|
4
|
+
|
5
|
+
# http://photo.net/medium-format-photography-forum/00LZPS
|
6
|
+
# http://www.largeformatphotography.info/forum/showthread.php?t=2503
|
7
|
+
# http://www.kenrockwell.com/tech/format.htm#120
|
8
|
+
# http://www.mamiya.com/rb67-pro-sd-accessories-film-magazines,-holders-inserts-roll-film-magazines.html
|
9
|
+
# from http://en.wikipedia.org/wiki/Image_sensor_format
|
10
|
+
|
11
|
+
# name height width aliases
|
12
|
+
FormatDescriptions = %q{
|
13
|
+
1/6 2.4 1.8
|
14
|
+
1/4 2.7 3.6
|
15
|
+
1/3.6 3 4
|
16
|
+
1/3.2 3.42 4.54
|
17
|
+
1/3 3.6 4.8
|
18
|
+
1/2.7 4.04 5.37
|
19
|
+
1/2.5 4.29 5.76
|
20
|
+
1/2 4.8 6.4
|
21
|
+
1/1.8 5.32 7.18
|
22
|
+
1/1.7 5.7 7.6 Canon PowerShot G9, Canon PowerShot G10
|
23
|
+
1/1.6 6.01 8.08
|
24
|
+
2/3 9.6 12.8
|
25
|
+
1 9.6 12.8
|
26
|
+
4/3 13 17.3
|
27
|
+
|
28
|
+
APS-C 14.8 22.2 Canon EOS DIGITAL REBEL XT, Canon EOS DIGITAL REBEL XTi, Canon EOS DIGITAL REBEL XSi, Canon EOS 450D
|
29
|
+
APS-H 19.1 28.7
|
30
|
+
R-D1 15.6 23.7 Epson R-D1, Epson R-D1s, Epson R-D1g, Epson R-D1x
|
31
|
+
|
32
|
+
DX 15.5 23.6 NIKON D70
|
33
|
+
|
34
|
+
Leica S2 30 45
|
35
|
+
|
36
|
+
35 24 36 FF, Canon EOS 5D
|
37
|
+
|
38
|
+
6x4.5 56 42 645
|
39
|
+
6x4.5 short 56 40.5
|
40
|
+
6x4.5 Mamiya RB/RZ67 56 41.5
|
41
|
+
6x6 56 56
|
42
|
+
6x6 short 56 54
|
43
|
+
6x7 56 72
|
44
|
+
6x7 short 56 69.5
|
45
|
+
6x7 Mamiya RB/RZ67 56 69.5
|
46
|
+
6x7 Toyo 56 67
|
47
|
+
6x7 Horseman 56 68
|
48
|
+
6x7 Pentax 55 70
|
49
|
+
6x7 Cambo-Sinar-Wista 56 70
|
50
|
+
6x7 Linhof Super Rollex 56 72
|
51
|
+
6x7 Linhof Rapid Rollex 57 76
|
52
|
+
6x8 56 76
|
53
|
+
6x9 56 84
|
54
|
+
6x9 23 Graphic 56 83
|
55
|
+
6x9 Cambo-Horseman-Wista 56 82
|
56
|
+
6x9 Toyo 56 84
|
57
|
+
6x9 Linhof 56 85
|
58
|
+
6x9 Sinar 57 88
|
59
|
+
6x10 56 92
|
60
|
+
6x12 56 112
|
61
|
+
6x12 Linhof 57 120
|
62
|
+
6x17 56 168
|
63
|
+
6x17 56 168
|
64
|
+
|
65
|
+
Polaroid 660 73 95
|
66
|
+
Polaroid 660 on Mamiya RB/RZ67 73 73
|
67
|
+
|
68
|
+
Polaroid 550 92 126
|
69
|
+
Polaroid 545 95 122
|
70
|
+
4x5 Quickload 95 120
|
71
|
+
4x5 Fidelity 97 120 4x5
|
72
|
+
|
73
|
+
5x7 127 178
|
74
|
+
|
75
|
+
8x10 203 254
|
76
|
+
}
|
77
|
+
|
78
|
+
def self.load_formats
|
79
|
+
@@formats = {}
|
80
|
+
FormatDescriptions.split("\n").each do |line|
|
81
|
+
case line.sub(/#.*/, '').strip
|
82
|
+
when /^(.*?)\s{2,}([\d\.]+)\s+([\d\.]+)\s*(.*?)$/
|
83
|
+
name, height, width, aliases = $1, $2, $3, $4
|
84
|
+
frame = Frame.new(width.to_f, height.to_f)
|
85
|
+
format = Format.new(name: name, frame: frame)
|
86
|
+
@@formats[name] = format
|
87
|
+
aliases.split(/,\s*/).each { |a| @@formats[a] = format }
|
88
|
+
when ''
|
89
|
+
# ignore blank line
|
90
|
+
else
|
91
|
+
raise "Can't parse format line: #{line.inspect}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.[](name)
|
97
|
+
load_formats unless class_variable_defined?('@@formats')
|
98
|
+
@@formats[name]
|
99
|
+
end
|
100
|
+
|
101
|
+
attr_accessor :name
|
102
|
+
attr_accessor :frame
|
103
|
+
|
104
|
+
def initialize(params={})
|
105
|
+
params.each { |k, v| send("#{k}=", v) }
|
106
|
+
end
|
107
|
+
|
108
|
+
def inspect
|
109
|
+
"<#{self.class} name=#{@name.inspect} frame=#{@frame.inspect}>"
|
110
|
+
end
|
111
|
+
|
112
|
+
def to_s(short=true)
|
113
|
+
if short
|
114
|
+
@name
|
115
|
+
else
|
116
|
+
"#{@name} (#{@frame})"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def focal_length_equivalent(focal_length, other=Format['35'])
|
121
|
+
f = focal_length * crop_factor(other)
|
122
|
+
Length.new(f)
|
123
|
+
end
|
124
|
+
|
125
|
+
def crop_factor(reference=Format['35'])
|
126
|
+
# http://en.wikipedia.org/wiki/Crop_factor
|
127
|
+
reference.frame.diagonal / @frame.diagonal
|
128
|
+
end
|
129
|
+
|
130
|
+
def angle_of_view(focal_length)
|
131
|
+
# http://imaginatorium.org/stuff/angle.htm
|
132
|
+
# http://en.wikipedia.org/wiki/Angle_of_view
|
133
|
+
a = Math.arcdeg(2 * Math.atan(@frame.diagonal / (2 * focal_length)))
|
134
|
+
Angle.new(a)
|
135
|
+
end
|
136
|
+
|
137
|
+
def field_of_view(focal_length, subject_distance)
|
138
|
+
# http://en.wikipedia.org/wiki/Field_of_view
|
139
|
+
Frame.new(
|
140
|
+
subject_distance * (@frame.height / focal_length),
|
141
|
+
subject_distance * (@frame.width / focal_length))
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module PhotoUtils
|
2
|
+
|
3
|
+
# http://photo.net/medium-format-photography-forum/00QiiV
|
4
|
+
|
5
|
+
class Frame
|
6
|
+
|
7
|
+
attr_accessor :height
|
8
|
+
attr_accessor :width
|
9
|
+
|
10
|
+
def initialize(height, width)
|
11
|
+
# correct swapped values
|
12
|
+
width, height = height, width if height < width
|
13
|
+
@height = Length.new(height)
|
14
|
+
@width = Length.new(width)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"<#{self.class} height=#{@height.inspect} width=#{@width.inspect}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s(format=:metric)
|
22
|
+
if @height == Math::Infinity && @width == Math::Infinity
|
23
|
+
"n/a"
|
24
|
+
else
|
25
|
+
"#{@height.to_s(format)} x #{@width.to_s(format)}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def diagonal
|
30
|
+
d = Math.sqrt((@height ** 2) + (@width ** 2))
|
31
|
+
Length.new(d)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module PhotoUtils
|
2
|
+
|
3
|
+
class Illuminance < Value
|
4
|
+
|
5
|
+
N = 2 ** Rational(-7, 4)
|
6
|
+
C = 224
|
7
|
+
NC = N * C
|
8
|
+
|
9
|
+
# amount specified in lux
|
10
|
+
|
11
|
+
def self.new_from_lux(lux)
|
12
|
+
new(lux)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.new_from_v(v)
|
16
|
+
new((2 ** v.to_f) * NC)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_v
|
20
|
+
Math.log2(self / NC)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_lux
|
24
|
+
to_f
|
25
|
+
end
|
26
|
+
|
27
|
+
def format_lux
|
28
|
+
to_lux.format(10) + ' lux'
|
29
|
+
end
|
30
|
+
|
31
|
+
def format_value
|
32
|
+
"Iv:#{to_v.format(10)}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s(format=:lux)
|
36
|
+
case format
|
37
|
+
when :lux
|
38
|
+
format_lux
|
39
|
+
when :value
|
40
|
+
format_value
|
41
|
+
else
|
42
|
+
raise "Unknown format: #{format.inspect}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Length < DelegateClass(Float)
|
6
|
+
|
7
|
+
def initialize(x)
|
8
|
+
case x
|
9
|
+
when Length
|
10
|
+
super(x)
|
11
|
+
when Numeric
|
12
|
+
super(x.to_f)
|
13
|
+
when '∞'
|
14
|
+
super(Math::Infinity)
|
15
|
+
when String
|
16
|
+
n = 0
|
17
|
+
s = x.dup
|
18
|
+
until s.empty?
|
19
|
+
if s.gsub!(/^\s*(\d+(\.\d+)?)\s*/, '')
|
20
|
+
n2 = $1.to_f
|
21
|
+
n2 = if s.gsub!(/^('|ft\b)/, '')
|
22
|
+
n2.feet
|
23
|
+
elsif s.gsub!(/^("|in\b)/, '')
|
24
|
+
n2.inches
|
25
|
+
elsif s.gsub!(/^\s*m\b/, '')
|
26
|
+
n2.meters
|
27
|
+
elsif s.gsub!(/^\s*(mm\b)?/, '')
|
28
|
+
n2.mm
|
29
|
+
else
|
30
|
+
raise "Can't parse unit: #{s.inspect}"
|
31
|
+
end
|
32
|
+
n += n2
|
33
|
+
else
|
34
|
+
raise "Can't parse number: #{s.inspect}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
super(n)
|
38
|
+
else
|
39
|
+
raise "Can't make length from #{x.class}: #{x.inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def format_imperial
|
44
|
+
inches = self * INCHES_PER_METER / 1000
|
45
|
+
if inches.floor >= 12
|
46
|
+
feet = (inches / 12).floor
|
47
|
+
inches %= 12
|
48
|
+
else
|
49
|
+
feet = 0
|
50
|
+
end
|
51
|
+
if inches > 0
|
52
|
+
inches = inches.ceil
|
53
|
+
if inches == 12
|
54
|
+
feet += 1
|
55
|
+
inches = 0
|
56
|
+
end
|
57
|
+
end
|
58
|
+
(feet > 0 ? "#{feet}'" : '') + (inches > 0 ? "#{inches}\"" : '')
|
59
|
+
end
|
60
|
+
|
61
|
+
def format_metric(precision=nil)
|
62
|
+
if self >= 1000
|
63
|
+
"#{(self / 1000).format(precision)}m"
|
64
|
+
else
|
65
|
+
"#{format(precision)}mm"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s(format=:metric)
|
70
|
+
if self == Math::Infinity
|
71
|
+
'∞'
|
72
|
+
else
|
73
|
+
case format
|
74
|
+
when :imperial
|
75
|
+
format_imperial
|
76
|
+
when :metric
|
77
|
+
format_metric
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def -(other)
|
83
|
+
self.class.new(super(other))
|
84
|
+
end
|
85
|
+
|
86
|
+
def abs
|
87
|
+
self.class.new(super)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|