normal_map 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in normalmapper.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Colin MacKenzie IV
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Normalmapper
2
+
3
+ Command line tool and Ruby library for generating normal maps.
4
+
5
+ Generates DOT3 bump maps, also known as normal maps, for use in 3D computing.
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'normal_map'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install normal_map
21
+
22
+
23
+ ## Command Line Usage
24
+
25
+ Generate a normal map from a regular full-color image:
26
+
27
+ Usage:
28
+ normal-map generate [options] COLORS NORMALS
29
+
30
+ Options:
31
+ -s, [--smooth] # Average adjacent pixels to produce a smoother map
32
+ -d, [--diagonal] # Consider diagonally-adjacent pixels in normal
33
+ calculations
34
+ -w, [--wrap] # Wrap edge pixels to the opposite edge, to make the
35
+ map more tile-friendly
36
+
37
+ Generates a normal map from COLORS and saves it to NORMALS
38
+
39
+
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/normal-map ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'normal_map'
4
+ NormalMap::CLI.start ARGV
data/lib/normal_map.rb ADDED
@@ -0,0 +1,193 @@
1
+ require 'RMagick'
2
+
3
+ class NormalMap
4
+ autoload :CLI, 'normal_map/cli'
5
+ autoload :Version, 'normal_map/version'
6
+ autoload :VERSION, 'normal_map/version'
7
+
8
+ include Magick
9
+ attr_reader :colors
10
+
11
+ def initialize filename, opts = {}
12
+ @colors = ImageList.new(filename)[0]
13
+ opts.each do |k, v|
14
+ instance_variable_set :"@#{k}", v
15
+ end
16
+ convert!
17
+ end
18
+
19
+ def to_blob *a, &b
20
+ to_image.to_blob *a, &b
21
+ end
22
+
23
+ def print *args
24
+ out.print *args unless silent?
25
+ end
26
+
27
+ def puts *args
28
+ out.puts *args unless silent?
29
+ end
30
+
31
+ # If true, calculated normals will be averaged with adjacent normals
32
+ # to produce a smoother, but less accurate, normal map.
33
+ def smooth?
34
+ !!@smooth
35
+ end
36
+
37
+ # If true, pixel coordinates out of range will be wrapped to the other
38
+ # side of the texture; otherwise, they will be clamped to the edge of
39
+ # the texture.
40
+ def wrap?
41
+ !!@wrap
42
+ end
43
+
44
+ # If true, nothing will be output during generation.
45
+ def silent?
46
+ !!@silent
47
+ end
48
+
49
+ # If true, normals will be calculated from diagonally adjacent pixels
50
+ # as well as vertically and horizontally adjacent. If `smooth` is also
51
+ # true, diagonal pixels will be considered in the smoothing algorithm
52
+ # as well.
53
+ def diagonal?
54
+ !!@diagonal
55
+ end
56
+
57
+ # The output stream to receive status updates. Defaults to `$stdout`.
58
+ def out
59
+ @out ||= $stdout
60
+ end
61
+
62
+ def to_image
63
+ normals
64
+ end
65
+
66
+ def normals
67
+ @normals ||= Image.new width, height
68
+ end
69
+
70
+ def convert!
71
+ total = width * height
72
+ increment = 0.05
73
+ current = 0
74
+ frac = 1.0 / total
75
+ pixels = Array.new total
76
+ width.times do |x|
77
+ height.times do |y|
78
+ current += frac
79
+ increment = progress current, increment
80
+ normal = calculate_normal x, y
81
+ normal[0] = normal[0] * 0.5 + 0.5
82
+ normal[1] = normal[1] * 0.5 + 0.5
83
+ normal[2] = normal[2] * 0.5 + 0.5
84
+ pixels[offset x, y] = Pixel.new normal[0] * QuantumRange,
85
+ normal[1] * QuantumRange,
86
+ normal[2] * QuantumRange,
87
+ QuantumRange
88
+ end
89
+ end
90
+ normals.store_pixels 0, 0, width, height, pixels
91
+ puts
92
+ end
93
+
94
+ def progress percent, increment
95
+ if percent >= increment
96
+ print '.'
97
+ increment += 0.05
98
+ end
99
+ percent = (percent*100).to_i.to_s
100
+ print " => ", percent, "%", "\b" * (percent.length + 6)
101
+ increment
102
+ end
103
+
104
+ def pixels
105
+ @pixels ||= colors.get_pixels 0, 0, width, height
106
+ end
107
+
108
+ def calculate_normal x, y
109
+ sx, sy = width, height
110
+
111
+ # vertical/horizontal
112
+ az = intensity x+1, y
113
+ bz = intensity x, y+1
114
+ cz = intensity x-1, y
115
+ dz = intensity x, y-1
116
+ normal = normalize [cz - az, dz - bz, 2 ]
117
+
118
+ if diagonal?
119
+ # diagonal
120
+ az = intensity x+1, y+1
121
+ bz = intensity x-1, y+1
122
+ cz = intensity x+1, y-1
123
+ dz = intensity x-1, y-1
124
+ normal2 = normalize [cz - az, dz - bz, 2 ]
125
+
126
+ # average them together
127
+ normal[0] = (normal[0] + normal2[0]) * 0.5
128
+ normal[1] = (normal[1] + normal2[1]) * 0.5
129
+ normal[2] = (normal[2] + normal2[2]) * 0.5
130
+ end
131
+
132
+ normal
133
+ end
134
+
135
+ def intensity(x, y)
136
+ # return intensity averaged with all adjacent pixels to produce
137
+ # a smoother result. Weight the current pixel to give it a little
138
+ # more influence.
139
+ if smooth?
140
+ sum = 0.0
141
+ sum += raw_intensity x-1, y
142
+ sum += raw_intensity x+1, y
143
+ sum += raw_intensity x, y-1
144
+ sum += raw_intensity x, y+1
145
+ if diagonal?
146
+ sum += raw_intensity x-1, y-1
147
+ sum += raw_intensity x-1, y+1
148
+ sum += raw_intensity x+1, y-1
149
+ sum += raw_intensity x+1, y+1
150
+ sum /= 8.0
151
+ else
152
+ sum / 4.0
153
+ end
154
+ else
155
+ raw_intensity x, y
156
+ end
157
+ end
158
+
159
+ def raw_intensity(x, y)
160
+ if wrap?
161
+ x += width if x < 0
162
+ x -= width if x >= width
163
+ y += height if y < 0
164
+ y -= height if y >= height
165
+ else
166
+ x = 0 if x < 0
167
+ x = width - 1 if x >= width
168
+ y = 0 if y < 0
169
+ y = height - 1 if y >= height
170
+ end
171
+ pixels[offset x, y].intensity.to_f / QuantumRange.to_f
172
+ end
173
+
174
+ def offset x, y
175
+ y * width + x
176
+ end
177
+
178
+ def normalize vec
179
+ mag = 1.0 / Math.sqrt(vec[0]**2 + vec[1]**2 + vec[2]**2)
180
+ vec[0] *= mag
181
+ vec[1] *= mag
182
+ vec[2] *= mag
183
+ vec
184
+ end
185
+
186
+ def width
187
+ @width ||= colors.columns
188
+ end
189
+
190
+ def height
191
+ @height ||= colors.rows
192
+ end
193
+ end
@@ -0,0 +1,16 @@
1
+ require 'thor'
2
+
3
+ class NormalMap::CLI < Thor
4
+ include Thor::Actions
5
+
6
+ desc "generate [options] COLORS NORMALS",
7
+ "Generates a normal map from COLORS and saves it to NORMALS"
8
+ method_option :smooth, :type => :boolean, :default => false, :aliases => '-s', :desc => "Average adjacent pixels to produce a smoother accurate map"
9
+ method_option :diagonal, :type => :boolean, :default => false, :aliases => '-d', :desc => "Consider diagonally-adjacent pixels in normal calculations"
10
+ method_option :wrap, :type => :boolean, :default => false, :aliases => '-w', :desc => "Wrap edge pixels to the opposite edge, to make the map more tile-friendly"
11
+ def generate colors_fn, normals_fn
12
+ create_file normals_fn do
13
+ NormalMap.new(colors_fn, options).to_blob { |f| f.format = "PNG" }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ class NormalMap
2
+ module Version
3
+ MAJOR, MINOR, TINY = 0, 0, 1
4
+ STRING = [MAJOR, MINOR, TINY].join('.')
5
+ end
6
+
7
+ VERSION = Version::STRING
8
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/normal_map/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Colin MacKenzie IV"]
6
+ gem.email = ["sinisterchipmunk@gmail.com"]
7
+ gem.description = %q{Generates DOT3 bump maps, also known as normal maps, for use in 3D computing.}
8
+ gem.summary = %q{Command line tool and Ruby library for generating normal maps.}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "normal_map"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = NormalMap::VERSION
17
+ gem.add_dependency 'thor'
18
+ gem.add_dependency 'rmagick'
19
+ gem.add_development_dependency 'rspec'
20
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe NormalMap do
4
+ let(:filename) { fixture_path 'in.png' }
5
+ let(:options) { {} }
6
+ subject { NormalMap.new filename, options }
7
+
8
+ shared_examples_for "expected result" do
9
+ it "should produce expected result" do
10
+ subject.to_blob { self.format = 'PNG' }.should == expected_result
11
+ end
12
+ end
13
+
14
+ describe "with no options" do
15
+ let(:expected_result) { fixture 'out_defaults.png' }
16
+ it_should_behave_like "expected result"
17
+ end
18
+
19
+ describe "with --smooth" do
20
+ let(:options) { { smooth: true } }
21
+ let(:expected_result) { fixture 'out_smooth.png' }
22
+ it_should_behave_like "expected result"
23
+ end
24
+
25
+ describe "with --diagonal" do
26
+ let(:options) { { diagonal: true } }
27
+ let(:expected_result) { fixture 'out_diagonal.png' }
28
+ it_should_behave_like "expected result"
29
+ end
30
+
31
+ describe "with --wrap" do
32
+ let(:options) { { wrap: true } }
33
+ let(:expected_result) { fixture 'out_wrap.png' }
34
+ it_should_behave_like "expected result"
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ require 'normal_map'
2
+
3
+ Dir[File.expand_path('support/**/*.rb', File.dirname(__FILE__))].each do |f|
4
+ require f
5
+ end
6
+
7
+ RSpec.configure do |conf|
8
+ conf.include Fixtures
9
+ end
@@ -0,0 +1,12 @@
1
+ module Fixtures
2
+ def fixture_path relative
3
+ File.expand_path File.join('../fixtures', relative),
4
+ File.dirname(__FILE__)
5
+ end
6
+
7
+ def fixture relative
8
+ File.open fixture_path(relative), 'rb' do |f|
9
+ f.read
10
+ end
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: normal_map
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Colin MacKenzie IV
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rmagick
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Generates DOT3 bump maps, also known as normal maps, for use in 3D computing.
63
+ email:
64
+ - sinisterchipmunk@gmail.com
65
+ executables:
66
+ - normal-map
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - bin/normal-map
76
+ - lib/normal_map.rb
77
+ - lib/normal_map/cli.rb
78
+ - lib/normal_map/version.rb
79
+ - normal_map.gemspec
80
+ - spec/fixtures/in.png
81
+ - spec/fixtures/out_defaults.png
82
+ - spec/fixtures/out_diagonal.png
83
+ - spec/fixtures/out_smooth.png
84
+ - spec/fixtures/out_wrap.png
85
+ - spec/lib/normal_map_spec.rb
86
+ - spec/spec_helper.rb
87
+ - spec/support/fixtures.rb
88
+ homepage: ''
89
+ licenses: []
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 1.8.24
109
+ signing_key:
110
+ specification_version: 3
111
+ summary: Command line tool and Ruby library for generating normal maps.
112
+ test_files:
113
+ - spec/fixtures/in.png
114
+ - spec/fixtures/out_defaults.png
115
+ - spec/fixtures/out_diagonal.png
116
+ - spec/fixtures/out_smooth.png
117
+ - spec/fixtures/out_wrap.png
118
+ - spec/lib/normal_map_spec.rb
119
+ - spec/spec_helper.rb
120
+ - spec/support/fixtures.rb