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,42 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class DOF < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
|
11
|
+
cameras = []
|
12
|
+
cameras << Camera[/Bronica/]
|
13
|
+
cameras << Camera[/Leica/]
|
14
|
+
cameras << Camera[/Hasselblad/]
|
15
|
+
|
16
|
+
cameras.each do |camera|
|
17
|
+
|
18
|
+
puts "#{camera.name}"
|
19
|
+
|
20
|
+
scene = Scene.new
|
21
|
+
scene.camera = camera
|
22
|
+
scene.camera.shutter = 1.0 / 125
|
23
|
+
scene.subject_distance = 30.feet
|
24
|
+
dof = 1.feet
|
25
|
+
aperture = scene.aperture_for_depth_of_field(scene.subject_distance - (dof / 2), scene.subject_distance + (dof / 2))
|
26
|
+
scene.camera.lens.aperture = [aperture, camera.lens.max_aperture].max
|
27
|
+
scene.sensitivity = 1600
|
28
|
+
scene.set_exposure
|
29
|
+
|
30
|
+
puts "\t" + "FOV: #{scene.field_of_view(scene.subject_distance).to_s(:imperial)}"
|
31
|
+
puts "\t" + "DOF: #{scene.total_depth_of_field.to_s(:imperial)} (-#{scene.near_distance_from_subject.to_s(:imperial)}/+#{scene.far_distance_from_subject.to_s(:imperial)})"
|
32
|
+
puts
|
33
|
+
|
34
|
+
scene.print_exposure
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class DOFTable < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
scene = Scene.new
|
11
|
+
scene.camera = Camera[/Eastman/]
|
12
|
+
|
13
|
+
puts
|
14
|
+
scene.print_camera
|
15
|
+
puts
|
16
|
+
|
17
|
+
# Av equivalents of f/4 -- f/64
|
18
|
+
apertures = (4..12).map { |av| Aperture.new_from_v(av) }
|
19
|
+
|
20
|
+
first = true
|
21
|
+
|
22
|
+
1.upto(30) do |s|
|
23
|
+
scene.subject_distance = s.feet
|
24
|
+
if first
|
25
|
+
first = false
|
26
|
+
puts (['', ''] + apertures.map { |a| "Av #{a.to_v.to_i}" }).join("\t")
|
27
|
+
puts (['', ''] + apertures.map { |a| a.to_s(:us) }).join("\t")
|
28
|
+
puts (['Distance', 'Field of view'] + apertures).join("\t")
|
29
|
+
end
|
30
|
+
print scene.subject_distance.to_s(:imperial)
|
31
|
+
print "\t" + "#{scene.field_of_view(scene.subject_distance).height.to_s(:imperial)}H x #{scene.field_of_view(scene.subject_distance).width.to_s(:imperial)}W"
|
32
|
+
apertures.each do |a|
|
33
|
+
scene.camera.lens.aperture = a
|
34
|
+
# print "\t" + "%s (%s ~ %s)" % [scene.total_depth_of_field, scene.near_distance_from_subject, scene.far_distance_from_subject].map { |d| d.to_s(:imperial) }
|
35
|
+
print "\t" + "%s" % [scene.total_depth_of_field].map { |d| d.to_s(:imperial) }
|
36
|
+
end
|
37
|
+
puts
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class FilmTest < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
camera = Camera['Hasselblad']
|
11
|
+
|
12
|
+
scene = Scene.new
|
13
|
+
scene.camera = camera
|
14
|
+
scene.sensitivity = 100
|
15
|
+
scene.camera.shutter = 1.0/60
|
16
|
+
scene.camera.lens.aperture = 5.6
|
17
|
+
# scene.description = "film: Acros 100; flash: Metz 60 at 1/128~1/256 power; dev: 11m in HC-110 (H) @ 68"
|
18
|
+
|
19
|
+
scene.print_exposure
|
20
|
+
|
21
|
+
zone_offset_from_mg = -4
|
22
|
+
|
23
|
+
# recommendation from Adams' "The Negative"
|
24
|
+
# steps = [0, -1.0/3, -2.0/3, -1, 1.0/3, 2.0/3, 1]
|
25
|
+
steps = [-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2]
|
26
|
+
|
27
|
+
scenes = []
|
28
|
+
|
29
|
+
base_fog = Scene.new
|
30
|
+
base_fog.description = "base + fog"
|
31
|
+
scenes << base_fog
|
32
|
+
|
33
|
+
steps.each do |i|
|
34
|
+
scene2 = scene.dup
|
35
|
+
scene2.brightness = PhotoUtils::Brightness.new_from_v(scene.brightness.to_v - zone_offset_from_mg)
|
36
|
+
scene2.sensitivity = Sensitivity.new_from_v(scene.sensitivity.to_v + i)
|
37
|
+
# FIXME: scene2.calculate_best_exposure
|
38
|
+
scene2.description = i.to_s
|
39
|
+
scenes << scene2
|
40
|
+
end
|
41
|
+
|
42
|
+
scenes.each_with_index do |scene2, i|
|
43
|
+
puts "%2d | %5s | %10s | %10s | %12s | %12s" % [
|
44
|
+
i + 1,
|
45
|
+
scene2.description,
|
46
|
+
scene2.sensitivity,
|
47
|
+
scene2.exposure.aperture,
|
48
|
+
scene2.exposure.time,
|
49
|
+
]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class FocalLength < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
from_focal_length, from_format = args.shift.split(':')
|
11
|
+
from_focal_length = from_focal_length.to_i
|
12
|
+
from_format = (Format[from_format || '35'] or raise "Unknown format: #{from_format.inspect}")
|
13
|
+
to_formats = args.first ? args.shift.split(',') : %w{1/1.7 APS-C APS-H 35 6x4.5 6x6 6x7 5x7}
|
14
|
+
to_formats.map { |f| Format[f] or raise "Unknown format: #{f.inspect}" }.each do |to_format|
|
15
|
+
scene = Scene.new
|
16
|
+
scene.format = from_format
|
17
|
+
puts "%10s: %6s" % [to_format.name, scene.format.focal_length_equivalent(from_focal_length, to_format)]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class Reciprocity < Tool
|
8
|
+
|
9
|
+
def usage
|
10
|
+
%q{
|
11
|
+
-r 1 compute single value
|
12
|
+
-r 2-60 compute values of 2 to 60
|
13
|
+
-r compute default range of 1-100 secs
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(args)
|
18
|
+
if args.first
|
19
|
+
r = args.shift.split('-', 2).map { |n| n ? n.to_i : nil }
|
20
|
+
else
|
21
|
+
r = 1..100
|
22
|
+
end
|
23
|
+
range = Range.new(r.first, r.last)
|
24
|
+
ti = range.first
|
25
|
+
until ti > range.last
|
26
|
+
tc = Time.new(ti).reciprocity
|
27
|
+
puts "#{ti} => #{tc.round.to_i}"
|
28
|
+
ti *= 2
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'photo_utils/tool'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Tools
|
6
|
+
|
7
|
+
class Test < Tool
|
8
|
+
|
9
|
+
def run(args)
|
10
|
+
|
11
|
+
# field_of_view = Frame.new(5.feet, 8.feet)
|
12
|
+
subject_distance = 3.feet
|
13
|
+
# subject_distance = nil
|
14
|
+
# depth_of_field = 2.feet
|
15
|
+
depth_of_field = 0.1.feet
|
16
|
+
sensitivity = 1600
|
17
|
+
# brightness = PhotoUtils::Brightness.new_from_lux(2000)
|
18
|
+
# brightness = nil
|
19
|
+
# shutter = 1.0/125
|
20
|
+
shutter = nil
|
21
|
+
# aperture = 4
|
22
|
+
|
23
|
+
header = "%-15.15s | %-15.15s | %-50.50s | %7s | %8s | %14s | %7s | %s"
|
24
|
+
|
25
|
+
puts header % %w{
|
26
|
+
camera
|
27
|
+
format
|
28
|
+
lens
|
29
|
+
35mm
|
30
|
+
dist
|
31
|
+
FOV
|
32
|
+
DOF
|
33
|
+
aperture
|
34
|
+
}
|
35
|
+
|
36
|
+
Camera.cameras.each do |camera|
|
37
|
+
# next unless camera.name =~ /Eastman/
|
38
|
+
# next unless camera.name =~ /Hasselblad|Bronica|Leica/
|
39
|
+
camera.formats.each do |format|
|
40
|
+
# next unless format.name =~ /5x7/
|
41
|
+
puts
|
42
|
+
camera.format = format
|
43
|
+
camera.lenses.sort_by(&:focal_length).each do |lens|
|
44
|
+
# next unless lens.name =~ /12"/
|
45
|
+
camera.lens = lens
|
46
|
+
scene = Scene.new
|
47
|
+
# scene.sensitivity = sensitivity
|
48
|
+
# scene.brightness = brightness
|
49
|
+
scene.camera = camera
|
50
|
+
scene.camera.shutter = shutter
|
51
|
+
scene.subject_distance = subject_distance || scene.subject_distance_for_field_of_view(field_of_view)
|
52
|
+
if depth_of_field
|
53
|
+
aperture = scene.aperture_for_depth_of_field(scene.subject_distance - (depth_of_field / 2), scene.subject_distance + (depth_of_field / 2))
|
54
|
+
end
|
55
|
+
scene.camera.lens.aperture = [aperture, scene.camera.lens.max_aperture].max
|
56
|
+
scene.camera.lens.aperture = [scene.camera.lens.aperture, scene.camera.lens.min_aperture].min
|
57
|
+
scene.set_exposure
|
58
|
+
|
59
|
+
# if scene.working_aperture > 0 && scene.working_aperture < camera.lens.min_aperture
|
60
|
+
# exp_comp = scene.working_aperture.to_v - camera.lens.aperture.to_v
|
61
|
+
# exp_comp = (exp_comp * 2).to_i / 2
|
62
|
+
# exp_comp = if exp_comp < 0
|
63
|
+
# "-#{exp_comp.abs}"
|
64
|
+
# elsif exp_comp > 0
|
65
|
+
# "+#{exp_comp}"
|
66
|
+
# else
|
67
|
+
# nil
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
exp_comp = nil
|
71
|
+
|
72
|
+
puts header % [
|
73
|
+
scene.camera.name,
|
74
|
+
scene.camera.format.to_s(true),
|
75
|
+
scene.camera.lens,
|
76
|
+
scene.camera.format.focal_length_equivalent(scene.camera.lens.focal_length),
|
77
|
+
scene.subject_distance.to_s(:imperial),
|
78
|
+
scene.field_of_view(scene.subject_distance).to_s(:imperial),
|
79
|
+
scene.total_depth_of_field.to_s(:imperial),
|
80
|
+
scene.camera.lens.aperture,
|
81
|
+
]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module PhotoUtils
|
4
|
+
|
5
|
+
class Value < DelegateClass(Float)
|
6
|
+
|
7
|
+
def self.new_from_v(v)
|
8
|
+
raise NotImplementedError, "Subclass has not implemented \#new_from_v"
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(n)
|
12
|
+
super(n.to_f)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_v
|
16
|
+
raise NotImplementedError, "Subclass has not implemented \#to_v"
|
17
|
+
end
|
18
|
+
|
19
|
+
def incr
|
20
|
+
self.class.new_from_v(self.to_v + 1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def decr
|
24
|
+
self.class.new_from_v(self.to_v - 1)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
self.to_v == self.class.new(other).to_v
|
29
|
+
end
|
30
|
+
|
31
|
+
def <=>(other)
|
32
|
+
self.to_v <=> self.class.new(other).to_v
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/photo-utils.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH << 'lib'
|
2
|
+
|
3
|
+
require 'photo_utils/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
|
7
|
+
s.name = 'photo-utils'
|
8
|
+
s.author = 'John Labovitz'
|
9
|
+
s.email = 'johnl@johnlabovitz.com'
|
10
|
+
s.homepage = 'http://johnlabovitz.com'
|
11
|
+
s.version = PhotoUtils::VERSION
|
12
|
+
s.summary = 'Models, formulas, and utilties for photography optics, etc.'
|
13
|
+
s.description = %q{
|
14
|
+
PhotoUtils provides Models, formulas, and utilties for photography optics, etc.
|
15
|
+
}
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_path = 'lib'
|
21
|
+
|
22
|
+
s.add_dependency 'hashstruct'
|
23
|
+
s.add_dependency 'builder'
|
24
|
+
s.add_dependency 'path'
|
25
|
+
|
26
|
+
s.add_development_dependency 'bundler'
|
27
|
+
s.add_development_dependency 'rake'
|
28
|
+
s.add_development_dependency 'wrong'
|
29
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'wrong'
|
3
|
+
require 'photo_utils'
|
4
|
+
|
5
|
+
class TestAperture < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include Wrong
|
8
|
+
include PhotoUtils
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@a = Aperture.new(8)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_zero_value
|
15
|
+
assert { Aperture.new(1).to_v == 0 }
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_has_correct_native_value
|
19
|
+
assert { @a.to_f == 8 }
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_format_as_string
|
23
|
+
assert { @a.to_s == 'f/8' }
|
24
|
+
assert { @a.to_s(:value) == 'Av:6' }
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_converts_to_value
|
28
|
+
assert { @a.to_v == Aperture.new_from_v(6).to_v }
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_increments_and_decrements
|
32
|
+
assert { @a.incr.round == Aperture.new(11).round }
|
33
|
+
assert { @a.decr.round == Aperture.new(5.6).round }
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_absolute
|
37
|
+
assert { @a.absolute(50) == 6.25 }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
data/test/apex_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'wrong'
|
3
|
+
require 'photo_utils'
|
4
|
+
|
5
|
+
class TestAPEX < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include Wrong
|
8
|
+
include PhotoUtils
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@exposure = Exposure.new(
|
12
|
+
time: nil,
|
13
|
+
aperture: 8,
|
14
|
+
sensitivity: 200,
|
15
|
+
brightness: Brightness.new_from_v(5))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_apex
|
19
|
+
assert { @exposure.ev == @exposure.av + @exposure.tv }
|
20
|
+
assert { @exposure.bv + @exposure.sv == @exposure.av + @exposure.tv }
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_exposure
|
24
|
+
assert { @exposure.ev == 11 }
|
25
|
+
assert { @exposure.ev100 == 0 }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|