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,43 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class Blur < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
scene = Scene.new
|
11
|
+
scene.sensitivity = 100
|
12
|
+
scene.subject_distance = 6.feet
|
13
|
+
scene.background_distance = 7.feet
|
14
|
+
flash_lux = 25
|
15
|
+
flash_seconds = 0.001
|
16
|
+
# flash_lux_seconds = flash_lux.to_f * (flash_seconds * 1000)
|
17
|
+
flash_lux_seconds = 25000 / 2
|
18
|
+
scene.brightness = PhotoUtils::Brightness.new_from_cdm2(flash_lux_seconds.to_f / ((scene.subject_distance / 1000) ** 2))
|
19
|
+
scene.camera = Camera[/Eastman/]
|
20
|
+
# scene.camera.lens = scene.camera.lenses.find { |l| l.focal_length == 12.inches }
|
21
|
+
scene.camera.lens.aperture = 32
|
22
|
+
scene.camera.shutter = nil
|
23
|
+
|
24
|
+
scene.print_camera
|
25
|
+
scene.print_exposure
|
26
|
+
scene.print_depth_of_field
|
27
|
+
|
28
|
+
1.feet.step(scene.subject_distance * 2, 1.feet).map { |d| Length.new(d) }.each do |d|
|
29
|
+
blur = scene.blur_at_distance(d)
|
30
|
+
puts "%12s: blur disk: %7s, blur/CoC: %6d%% -- %s" % [
|
31
|
+
d.to_s(:imperial),
|
32
|
+
blur.to_s(:metric),
|
33
|
+
(blur / scene.circle_of_confusion) * 100,
|
34
|
+
blur <= scene.circle_of_confusion ? 'in focus' : 'out of focus'
|
35
|
+
]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class Brightness < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
scene = Scene.new
|
11
|
+
scene.camera = Camera[/Rollei/]
|
12
|
+
scene.description = "Salon L'Orient"
|
13
|
+
scene.print_exposure
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class CalcAperture < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
|
11
|
+
# set up basic scene
|
12
|
+
|
13
|
+
basic_scene = Scene.new
|
14
|
+
basic_scene.camera = Camera[/hasselblad/i]
|
15
|
+
basic_scene.subject_distance = 12.feet
|
16
|
+
basic_scene.background_distance = 14.feet
|
17
|
+
basic_scene.camera.lens.aperture = 8
|
18
|
+
basic_scene.description = basic_scene.camera.name
|
19
|
+
|
20
|
+
puts "--- @ #{basic_scene.subject_distance.to_s(:imperial)}"
|
21
|
+
basic_scene.camera.lenses.each do |lens|
|
22
|
+
scene = basic_scene.dup
|
23
|
+
scene.camera.shutter = nil
|
24
|
+
scene.camera.lens = lens
|
25
|
+
# scene.aperture = scene.aperture_for_depth_of_field(scene.subject_distance - 9.inches, scene.subject_distance + 9.inches)
|
26
|
+
# next unless scene.aperture >= lens.max_aperture && scene.aperture <= lens.min_aperture
|
27
|
+
# background_fov = scene.field_of_view(scene.background_distance)
|
28
|
+
# next unless background_fov.width <= 4.feet && background_fov.height <= 4.feet
|
29
|
+
# subject_fov = scene.field_of_view(scene.subject_distance)
|
30
|
+
# next unless subject_fov.width >= 2.feet && subject_fov.height >= 2.feet
|
31
|
+
# next unless scene.time < 1.0/15
|
32
|
+
scene.description += ": #{scene.camera.lens.focal_length} @ #{scene.camera.lens.aperture}"
|
33
|
+
puts "#{scene.description}:"
|
34
|
+
scene.print_depth_of_field
|
35
|
+
scene.print_exposure
|
36
|
+
puts
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class Cameras < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
if Camera.cameras
|
11
|
+
Camera.cameras.each do |camera|
|
12
|
+
camera.print
|
13
|
+
puts
|
14
|
+
end
|
15
|
+
else
|
16
|
+
warn "No cameras found."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class ChartDOF < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
|
11
|
+
# set up basic scene
|
12
|
+
|
13
|
+
basic_scene = Scene.new
|
14
|
+
basic_scene.subject_distance = 8.feet
|
15
|
+
basic_scene.sensitivity = 400
|
16
|
+
basic_scene.brightness = 8
|
17
|
+
|
18
|
+
scenes = []
|
19
|
+
|
20
|
+
if false
|
21
|
+
|
22
|
+
scene = basic_scene.dup
|
23
|
+
scene.format = Format['35']
|
24
|
+
scene.focal_length = 50.mm
|
25
|
+
scene.aperture = 2
|
26
|
+
scene.description = "#{scene.format}: #{scene.focal_length} @ #{scene.aperture}"
|
27
|
+
scenes << scene
|
28
|
+
|
29
|
+
scene = basic_scene.dup
|
30
|
+
scene.format = Format['6x6']
|
31
|
+
scene.focal_length = 92.mm
|
32
|
+
scene.aperture = 8
|
33
|
+
scene.description = "#{scene.format}: #{scene.focal_length} @ #{scene.aperture}"
|
34
|
+
scenes << scene
|
35
|
+
|
36
|
+
scene = basic_scene.dup
|
37
|
+
scene.format = Format['5x7']
|
38
|
+
scene.focal_length = 253.mm
|
39
|
+
scene.aperture = 64
|
40
|
+
scene.description = "#{scene.format}: #{scene.focal_length} @ #{scene.aperture}"
|
41
|
+
scenes << scene
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
if false
|
46
|
+
|
47
|
+
scene = basic_scene.dup
|
48
|
+
scene.format = Format['35']
|
49
|
+
scene.focal_length = 90.mm
|
50
|
+
scene.aperture = 2.8
|
51
|
+
scene.description = "#{scene.format}: #{scene.focal_length} @ #{scene.aperture}"
|
52
|
+
scenes << scene
|
53
|
+
|
54
|
+
scene = basic_scene.dup
|
55
|
+
scene.format = Format['35']
|
56
|
+
scene.focal_length = 90.mm
|
57
|
+
scene.aperture = 4
|
58
|
+
scene.description = "#{scene.format}: #{scene.focal_length} @ #{scene.aperture}"
|
59
|
+
scenes << scene
|
60
|
+
|
61
|
+
scene = basic_scene.dup
|
62
|
+
scene.format = Format['35']
|
63
|
+
scene.focal_length = 90.mm
|
64
|
+
scene.aperture = 5.6
|
65
|
+
scene.description = "#{scene.format}: #{scene.focal_length} @ #{scene.aperture}"
|
66
|
+
scenes << scene
|
67
|
+
|
68
|
+
scene = basic_scene.dup
|
69
|
+
scene.format = Format['35']
|
70
|
+
scene.focal_length = 85.mm
|
71
|
+
scene.aperture = 4
|
72
|
+
scene.description = "#{scene.format}: #{scene.focal_length} @ #{scene.aperture}"
|
73
|
+
scenes << scene
|
74
|
+
|
75
|
+
scene = basic_scene.dup
|
76
|
+
scene.format = Format['35']
|
77
|
+
scene.focal_length = 85.mm
|
78
|
+
scene.aperture = 5.6
|
79
|
+
scene.description = "#{scene.format}: #{scene.focal_length} @ #{scene.aperture}"
|
80
|
+
scenes << scene
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
if true
|
85
|
+
|
86
|
+
camera = Camera[/eastman/i] or raise "Can't find camera"
|
87
|
+
basic_scene.description = camera.name
|
88
|
+
basic_scene.camera = camera
|
89
|
+
|
90
|
+
aperture = camera.lens.max_aperture
|
91
|
+
while aperture <= camera.lens.min_aperture
|
92
|
+
scene = basic_scene.dup
|
93
|
+
camera.lens.aperture = aperture
|
94
|
+
# break if scene.time > 1.0/30
|
95
|
+
scene.description += ": #{camera.lens.focal_length} @ #{camera.lens.aperture}"
|
96
|
+
scenes << scene
|
97
|
+
aperture = Aperture.new_from_v(aperture.to_v + 1)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
scenes.each do |scene|
|
103
|
+
scene.print; puts
|
104
|
+
end
|
105
|
+
|
106
|
+
# max_distance = scenes.map { |s| s.depth_of_field.far }.max
|
107
|
+
# max_distance = scenes.map { |s| s.hyperfocal_distance }.max
|
108
|
+
max_distance = 50.feet
|
109
|
+
|
110
|
+
camera_width = scenes.map { |s| s.focal_length }.max
|
111
|
+
camera_height = scenes.map { |s| [s.absolute_aperture, s.frame.height].max }.max
|
112
|
+
|
113
|
+
html = Builder::XmlMarkup.new(indent: 2)
|
114
|
+
html.declare!(:DOCTYPE, :html)
|
115
|
+
html.html do
|
116
|
+
html.head {}
|
117
|
+
html.body do
|
118
|
+
html.table do
|
119
|
+
scenes.each do |scene|
|
120
|
+
scene_view = SceneView.new(scene,
|
121
|
+
max_distance: max_distance,
|
122
|
+
camera_width: camera_width,
|
123
|
+
camera_height: camera_height)
|
124
|
+
html.tr do
|
125
|
+
html.td do
|
126
|
+
html << scene.description
|
127
|
+
end
|
128
|
+
html.td do
|
129
|
+
html << scene_view.to_svg
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
raise "Usage: #{$0} output-file.html" unless ARGV.first
|
138
|
+
File.open(ARGV.first, 'w') { |f| f.write(html.target!) }
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
# require 'mini_exiftool'
|
4
|
+
# require 'pathname2'
|
5
|
+
|
6
|
+
module PhotoUtils
|
7
|
+
|
8
|
+
class Tools
|
9
|
+
|
10
|
+
class Compare < Tool
|
11
|
+
|
12
|
+
def run(args)
|
13
|
+
# given:
|
14
|
+
# an image file with EXIF data
|
15
|
+
# extract:
|
16
|
+
# aperture/speed/sensitivity/bias
|
17
|
+
# format (eg, APS-C)
|
18
|
+
# lens focal length (in that format)
|
19
|
+
# post-process exposure adjustment (from XMP data)
|
20
|
+
# estimate width/height
|
21
|
+
# desired minimum shutter
|
22
|
+
# calculate
|
23
|
+
# Ev100
|
24
|
+
# subject distance
|
25
|
+
# depth of field
|
26
|
+
# height or width (not given)
|
27
|
+
# focal length required in 35mm
|
28
|
+
# aperture required for equivalent depth of field
|
29
|
+
# (warn if shutter changes)
|
30
|
+
#
|
31
|
+
# scenes:
|
32
|
+
# stage performance at medium / far
|
33
|
+
# portrait at close / medium
|
34
|
+
|
35
|
+
#FIXME: Ugh
|
36
|
+
|
37
|
+
$min_sensitivity = Sensitivity.new(100)
|
38
|
+
$max_sensitivity = Sensitivity.new(1600)
|
39
|
+
$max_angle_of_view_delta = Angle.new(5)
|
40
|
+
$max_subject_distance_delta = 6.feet
|
41
|
+
|
42
|
+
base = Path.new('/Users/johnl/Pictures/Lightroom Burned Exports')
|
43
|
+
|
44
|
+
shots = %q{
|
45
|
+
|
46
|
+
# file width DoF description
|
47
|
+
|
48
|
+
3787022130_ce334b0c20_o.jpg 56" 3' OCF poem typist: W/A closeup
|
49
|
+
3894979751_8ef3683c78_o.jpg 54" 3' OCF poem typist: medium
|
50
|
+
3711708327_917d493f1a_o.jpg 23" 1' OCF man with hat: torso
|
51
|
+
58970238_dfe52a2c77_o.jpg 96" 3' backlit dancer: medium from medium
|
52
|
+
3043470502_9b3406f4dd_o.jpg 81" 3' winged stripper
|
53
|
+
3084813136_180bda6d84_o.jpg 75" 3' stripper moving away: close from close
|
54
|
+
IMG_2664.jpg 233" 10' yarddogs dancers: wide from far
|
55
|
+
IMG_2672.jpg 95" 10' yarddogs horns: medium from far
|
56
|
+
IMG_2864.jpg 49" 2' sideshow bug eating: close from near
|
57
|
+
IMG_2790.jpg 63" 3' sideshow ass: medium from near
|
58
|
+
|
59
|
+
060512.031.jpg 233" 6' Japan: man on street at night
|
60
|
+
060512.051.jpg 150' 50' Japan: Tokyo cityscape
|
61
|
+
060512.139.jpg 15' 10' Japan: man in street
|
62
|
+
060514.058.jpg 4' 1' Japan: bookstore
|
63
|
+
060515.081.jpg 7' 4' Japan: plants at corner
|
64
|
+
060519.001.jpg 14" 3" Japan: eggs
|
65
|
+
060520.012.jpg 20' 10' Japan: haystack
|
66
|
+
060524.023.jpg 9' 3' Japan: plants & rust
|
67
|
+
060527.051.jpg 8' 3' Japan: racoon & bicycle
|
68
|
+
060528.003.jpg 18" 6" Japan: tiny buddhas
|
69
|
+
|
70
|
+
}.split(/\n/).map { |line|
|
71
|
+
line.gsub!(/^\s+|\s+$/, '')
|
72
|
+
line.sub!(/#.*/, '')
|
73
|
+
if line.empty?
|
74
|
+
nil
|
75
|
+
else
|
76
|
+
file, width, dof, type = line.split(/\s+/, 4)
|
77
|
+
dof = Length.new(dof)
|
78
|
+
width = Length.new(width)
|
79
|
+
HashStruct.new(
|
80
|
+
type: type,
|
81
|
+
file: file,
|
82
|
+
subject_width: width,
|
83
|
+
desired_dof: dof)
|
84
|
+
end
|
85
|
+
}.compact
|
86
|
+
|
87
|
+
# FIXME: Ugh
|
88
|
+
|
89
|
+
def validate(scene, camera, lens, subject_distance_delta, angle_of_view_delta)
|
90
|
+
|
91
|
+
#
|
92
|
+
# validate subject distance delta
|
93
|
+
#
|
94
|
+
|
95
|
+
if subject_distance_delta > $max_subject_distance_delta
|
96
|
+
raise "subject distance delta too different (#{subject_distance_delta.to_s(:imperial)} > #{$max_subject_distance_delta.to_s(:imperial)})"
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# validate aperture
|
101
|
+
#
|
102
|
+
|
103
|
+
if scene.aperture > lens.min_aperture || scene.aperture < lens.max_aperture
|
104
|
+
raise "aperture out of range (#{scene.aperture} != #{lens.max_aperture} .. #{lens.min_aperture})"
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# validate angle of view
|
109
|
+
#
|
110
|
+
|
111
|
+
if angle_of_view_delta > $max_angle_of_view_delta
|
112
|
+
raise "angle of view too different (#{angle_of_view_delta} > #{$max_angle_of_view_delta})"
|
113
|
+
end
|
114
|
+
|
115
|
+
# if scene.angle_of_view - scene.angle_of_view > 5
|
116
|
+
# raise "angle of view too wide (#{scene2.angle_of_view} > #{scene.angle_of_view})"
|
117
|
+
# next
|
118
|
+
# end
|
119
|
+
|
120
|
+
#
|
121
|
+
# validate shutter
|
122
|
+
#
|
123
|
+
|
124
|
+
if scene.time > camera.max_shutter
|
125
|
+
raise "shutter too slow (#{scene.time} < #{camera.max_shutter})"
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
successes = {}
|
131
|
+
|
132
|
+
shots.each do |shot|
|
133
|
+
|
134
|
+
img = MiniExiftool.new(base + shot.file, numerical: true, timestamps: DateTime)
|
135
|
+
|
136
|
+
scene = Scene.new
|
137
|
+
|
138
|
+
model = img['Model']
|
139
|
+
scene.format = Format[model] or raise "Can't determine frame for model #{model.inspect} (#{shot.file})"
|
140
|
+
|
141
|
+
scene.description = "#{shot[:type]} [#{shot[:seq]}]"
|
142
|
+
scene.aperture = img['Aperture']
|
143
|
+
if img['ISO'].kind_of?(Numeric)
|
144
|
+
scene.sensitivity = img['ISO']
|
145
|
+
else # "0 800"
|
146
|
+
scene.sensitivity = img['ISO'].split(' ').last.to_f
|
147
|
+
end
|
148
|
+
scene.time = img['ExposureTime']
|
149
|
+
scene.focal_length = img['FocalLength']
|
150
|
+
|
151
|
+
exp_comp = img['ExposureCompensation'].to_f
|
152
|
+
if exp_comp != 0
|
153
|
+
scene.sensitivity = Sensitivity.new_from_v(scene.sensitivity.to_v + exp_comp)
|
154
|
+
end
|
155
|
+
|
156
|
+
# d = w * f / s
|
157
|
+
scene.subject_distance = Length.new(shot.subject_width * (scene.focal_length / scene.format.width))
|
158
|
+
|
159
|
+
puts
|
160
|
+
puts "--- #{scene.description}"
|
161
|
+
puts
|
162
|
+
|
163
|
+
scene.print_exposure
|
164
|
+
scene.print_depth_of_field
|
165
|
+
puts
|
166
|
+
|
167
|
+
# now compute equivalent scene for each camera
|
168
|
+
|
169
|
+
cameras.each do |camera|
|
170
|
+
|
171
|
+
scene2 = Scene.new
|
172
|
+
scene2.format = camera.format or raise "Unknown format: #{camera.format.inspect}"
|
173
|
+
scene2.aperture = scene.aperture
|
174
|
+
scene2.brightness = scene.brightness
|
175
|
+
# scene2.sensitivity = 400
|
176
|
+
|
177
|
+
# find the lens that would best fit
|
178
|
+
|
179
|
+
found = false
|
180
|
+
|
181
|
+
# NOTE: #uniq doesn't work well with delegate classes, so we cast the focal length to a float first
|
182
|
+
focal_lengths = camera.lenses.collect { |lens| lens.focal_length.to_f }.sort.reverse.uniq
|
183
|
+
|
184
|
+
focal_lengths.each do |focal_length|
|
185
|
+
|
186
|
+
lenses = camera.lenses.select { |lens| lens.focal_length == focal_length }.sort_by { |lens| lens.max_aperture }.reverse
|
187
|
+
|
188
|
+
lenses.each do |lens|
|
189
|
+
|
190
|
+
scene2.focal_length = lens.focal_length
|
191
|
+
|
192
|
+
# keeping subject width the same, compute new distance from given focal length
|
193
|
+
|
194
|
+
# o i
|
195
|
+
# --- = ---
|
196
|
+
# d f
|
197
|
+
#
|
198
|
+
# f = focal length
|
199
|
+
# d = subject distance
|
200
|
+
# o = subject dimension
|
201
|
+
# i = frame dimension
|
202
|
+
|
203
|
+
scene2.subject_distance = 1 / ((scene2.frame.width / scene2.focal_length) / shot.subject_width)
|
204
|
+
|
205
|
+
#
|
206
|
+
# calculate depth of field
|
207
|
+
#
|
208
|
+
|
209
|
+
near_limit = scene2.subject_distance - (shot.desired_dof / 2)
|
210
|
+
far_limit = scene2.subject_distance + (shot.desired_dof / 2)
|
211
|
+
scene2.aperture = scene2.aperture_for_depth_of_field(near_limit, far_limit)
|
212
|
+
|
213
|
+
#
|
214
|
+
# adjust aperture
|
215
|
+
#
|
216
|
+
|
217
|
+
# ;;a = scene2.aperture
|
218
|
+
# round aperture to closest 1/2 stop
|
219
|
+
scene2.aperture = Aperture.new_from_v((scene2.aperture.to_v / 0.5).round * 0.5)
|
220
|
+
# ;;puts "[1] #{a} => #{scene2.aperture}"
|
221
|
+
# clamp to maximum aperture
|
222
|
+
scene2.aperture = [scene2.aperture, lens.max_aperture].max
|
223
|
+
# ;;puts "[2] #{a} => #{scene2.aperture}"
|
224
|
+
|
225
|
+
#
|
226
|
+
# calculate sensitivity
|
227
|
+
#
|
228
|
+
|
229
|
+
# start with minimum shutter time
|
230
|
+
scene2.time = camera.max_shutter
|
231
|
+
# round up to the next ISO value
|
232
|
+
scene2.sensitivity = Sensitivity.new_from_v(scene2.sensitivity.to_v.ceil)
|
233
|
+
scene2.sensitivity = [scene2.sensitivity, $max_sensitivity].min
|
234
|
+
scene2.sensitivity = [scene2.sensitivity, $min_sensitivity].max
|
235
|
+
# # force recalculation of shutter
|
236
|
+
# scene2.time = nil
|
237
|
+
|
238
|
+
#
|
239
|
+
# compute subject distance difference
|
240
|
+
#
|
241
|
+
|
242
|
+
subject_distance_delta = scene.subject_distance - scene2.subject_distance
|
243
|
+
|
244
|
+
#
|
245
|
+
# compute angle of view difference
|
246
|
+
#
|
247
|
+
|
248
|
+
angle_of_view_delta = Angle.new(scene.angle_of_view - scene2.angle_of_view)
|
249
|
+
|
250
|
+
#
|
251
|
+
# validate
|
252
|
+
#
|
253
|
+
|
254
|
+
begin
|
255
|
+
validate(scene2, camera, lens, subject_distance_delta, angle_of_view_delta)
|
256
|
+
rescue => e
|
257
|
+
failure = e
|
258
|
+
end
|
259
|
+
|
260
|
+
puts " %-15.15s | %-19.19s | %10s @ %5s @ %8s | dist: %6s (%s%6s) | dof: %6s (-%5s .. +%5s) | %s" % [
|
261
|
+
camera.name,
|
262
|
+
lens.name,
|
263
|
+
scene2.aperture,
|
264
|
+
scene2.time,
|
265
|
+
scene2.sensitivity,
|
266
|
+
scene2.subject_distance.to_s(:imperial),
|
267
|
+
subject_distance_delta < 0 ? '-' : '+',
|
268
|
+
subject_distance_delta.abs.to_s(:imperial),
|
269
|
+
scene2.total_depth_of_field.to_s(:imperial),
|
270
|
+
scene2.near_distance_from_subject.to_s(:imperial),
|
271
|
+
scene2.far_distance_from_subject.to_s(:imperial),
|
272
|
+
failure || 'GOOD'
|
273
|
+
] if options[:verbose] || !failure
|
274
|
+
|
275
|
+
successes[camera.name] ||= {}
|
276
|
+
|
277
|
+
if !failure
|
278
|
+
successes[camera.name][lens.name] ||= 0
|
279
|
+
successes[camera.name][lens.name] += 1
|
280
|
+
found = true
|
281
|
+
break
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
break if found
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
unless found
|
291
|
+
successes[camera.name]['FAILED'] ||= 0
|
292
|
+
successes[camera.name]['FAILED'] += 1
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
;;pp successes
|
300
|
+
# ;;successes.sort_by { |k,v| v }.each { |k,v| puts "%2d: %s" % [v, k] }
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|