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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +4 -0
  4. data/README.rdoc +1 -0
  5. data/Rakefile +1 -0
  6. data/TODO.txt +34 -0
  7. data/bin/photo-util +39 -0
  8. data/lib/photo_utils.rb +28 -0
  9. data/lib/photo_utils/angle.rb +17 -0
  10. data/lib/photo_utils/aperture.rb +62 -0
  11. data/lib/photo_utils/apex.rb +210 -0
  12. data/lib/photo_utils/brightness.rb +49 -0
  13. data/lib/photo_utils/camera.rb +82 -0
  14. data/lib/photo_utils/compensation.rb +29 -0
  15. data/lib/photo_utils/extensions/array.rb +11 -0
  16. data/lib/photo_utils/extensions/float.rb +18 -0
  17. data/lib/photo_utils/extensions/math.rb +11 -0
  18. data/lib/photo_utils/extensions/numeric.rb +23 -0
  19. data/lib/photo_utils/formats.rb +146 -0
  20. data/lib/photo_utils/frame.rb +36 -0
  21. data/lib/photo_utils/illuminance.rb +48 -0
  22. data/lib/photo_utils/length.rb +92 -0
  23. data/lib/photo_utils/lens.rb +50 -0
  24. data/lib/photo_utils/scene.rb +180 -0
  25. data/lib/photo_utils/scene_view.rb +134 -0
  26. data/lib/photo_utils/sensitivity.rb +44 -0
  27. data/lib/photo_utils/time.rb +53 -0
  28. data/lib/photo_utils/tool.rb +17 -0
  29. data/lib/photo_utils/tools/blur.rb +43 -0
  30. data/lib/photo_utils/tools/brightness.rb +20 -0
  31. data/lib/photo_utils/tools/calc_aperture.rb +45 -0
  32. data/lib/photo_utils/tools/cameras.rb +24 -0
  33. data/lib/photo_utils/tools/chart_dof.rb +146 -0
  34. data/lib/photo_utils/tools/compare.rb +305 -0
  35. data/lib/photo_utils/tools/dof.rb +42 -0
  36. data/lib/photo_utils/tools/dof_table.rb +45 -0
  37. data/lib/photo_utils/tools/film_test.rb +57 -0
  38. data/lib/photo_utils/tools/focal_length.rb +25 -0
  39. data/lib/photo_utils/tools/reciprocity.rb +36 -0
  40. data/lib/photo_utils/tools/test.rb +91 -0
  41. data/lib/photo_utils/value.rb +37 -0
  42. data/lib/photo_utils/version.rb +5 -0
  43. data/photo-utils.gemspec +29 -0
  44. data/test/aperture_test.rb +40 -0
  45. data/test/apex_test.rb +28 -0
  46. data/test/brightness_test.rb +36 -0
  47. data/test/length_test.rb +40 -0
  48. data/test/scene_test.rb +25 -0
  49. data/test/sensitivity_test.rb +36 -0
  50. data/test/time_test.rb +42 -0
  51. 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
@@ -0,0 +1,5 @@
1
+ module PhotoUtils
2
+
3
+ VERSION = '0.2'
4
+
5
+ end
@@ -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
@@ -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