gaussian_blur_generator 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4c08daccf74b7ab85e80ee4ade6521684f0ffbe2276b7a8b465578be3aad3703
4
+ data.tar.gz: 783e49eb582aa2bdffc954bda03ffcc4a888e290b14f89614dfdc0b216518d96
5
+ SHA512:
6
+ metadata.gz: a84070cf8fcc524d4da0be924258e278e444280edfb63bf45c9efcfae1efea02d12a5e7302042305ba9368ac95b80b60ab9f0a28f00c8d9592b87a9bd245ad32
7
+ data.tar.gz: ded31a9a5b06cb81647411e31cdbf1a393abddd0b6892466e87c6b982891950595a51c9a095cb94524d46f30f26d78e4c848786fde21120677a96fe8e5a9884a
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ ## Gaussian Blur Generator
2
+
3
+ Generates fragment shaders that apply a Gaussian blur in an efficient manner
4
+ based on
5
+ [this article](http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/).
6
+
7
+ ### Overview
8
+
9
+ Normally, a blur shader would require N^2 texture reads for a kernel of size N.
10
+ By splitting the shader in two (horizontal, then vertical) and applying a linear
11
+ sampling technique, we can reduce the number of texture reads to approximately
12
+ N, vastly improving the performance of a Gaussian blur. This can be reduced even
13
+ further by ignoring values at the edges of kernels that virtually no difference
14
+ to the output image.
15
+
16
+ ### What is this gem?
17
+
18
+ This gem applies the technique from the article but generates many different
19
+ shaders with different kernel sizes. It writes these shaders to files in the
20
+ output directory, for example:
21
+
22
+ - [output/blur/x/17.frag](output/blur/x/17.frag)
23
+ - [output/blue/y/17.frag](output/blue/y/17.frag)
24
+
25
+ You can run `./bin/generate` yourself by cloning the repository or use one of
26
+ the pre-generated shaders in the [`output/blur`](output/blur) directory. These
27
+ have generated with an epsilon value of 0.05 which roughly corresponds to the
28
+ article, but you can set this yourself if you'd like:
29
+
30
+ ```sh
31
+ $ ./bin/generate 0.07
32
+ ```
33
+
34
+ This would set the threshold a little higher than the default 0.05.
35
+
36
+ ### How do I use it?
37
+
38
+ 1. Copy the x and y fragment shaders into your project for the kernel size you want.
39
+ 2. Compile a shader program for each of the fragment shaders
40
+ 3. Set `u_texture` to the input texture you want to blur
41
+ 4. Set `u_dimensions` to the pixel size of the output: `[width, height, 1/width, 1/height]`
42
+ 5. Draw your scene to a framebuffer/texture instead of directly to the screen
43
+ 6. Apply the first shader program, then draw to the screen with the second
44
+
45
+ You can apply the shader to regions of the texture if you'd wish, but the
46
+ simplest approach is to apply it to a full-screen quad. In which case, a simple
47
+ vertex shader like this one will suffice:
48
+
49
+ ```glsl
50
+ attribute vec4 a_position;
51
+
52
+ void main() {
53
+ gl_Position = a_position;
54
+ }
55
+ ```
56
+
57
+ You may need to edit the [`glsl.rb` file](lib/gaussian_blur_generator/glsl.rb)
58
+ to produce shader code that's more appropriate for your project.
59
+
60
+ ### Multiple passes?
61
+
62
+ You may find it's more efficient to apply multiple passes of a smaller blur
63
+ shader than a single pass of a larger one. This depends on a number of factors
64
+ including GPU memory, fill rate, etc. In general, two blurs of size N is
65
+ equivalent to a single blur of size `sqrt(2) * N`.
66
+
67
+ Also, since Gaussian blur is a low-pass filter, it works very well with
68
+ downsampling. See the article above for more information.
69
+
70
+ ### License
71
+
72
+ MIT
data/bin/generate ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ LIB = File.expand_path(File.join(__dir__, "..", "lib"))
4
+ $LOAD_PATH.unshift(LIB)
5
+
6
+ EPSILON = Float(ARGV[0] || 0.05)
7
+
8
+ require "gaussian_blur_generator"
9
+ require "fileutils"
10
+
11
+ FileUtils.mkdir_p("output/blur/x")
12
+ FileUtils.mkdir_p("output/blur/y")
13
+
14
+ GaussianBlurGenerator.generate(EPSILON) do |values|
15
+ ["x", "y"].each do |axis|
16
+ glsl = GaussianBlurGenerator.glsl(values, axis)
17
+ path = "output/blur/#{axis}/#{values.texture_reads}.frag"
18
+
19
+ File.write(path, glsl)
20
+ end
21
+
22
+ puts "Written output/blur/{x,y}/#{values.texture_reads}.frag"
23
+ puts " discarded values < #{EPSILON} of peak"
24
+ puts " number of texture reads = #{values.texture_reads}"
25
+ puts " effective kernel size = #{values.effective_size}"
26
+ puts " pascal's triangle row = #{values.row_number}"
27
+ puts
28
+ end
@@ -0,0 +1,4 @@
1
+ require "bigdecimal/util"
2
+
3
+ require "gaussian_blur_generator/base"
4
+ require "gaussian_blur_generator/glsl"
@@ -0,0 +1,51 @@
1
+ class GaussianBlurGenerator
2
+ Values = Struct.new(:offsets_d, :weights_d, :offsets_l, :weights_l, :texture_reads, :effective_size, :row_number)
3
+
4
+ def self.generate(epsilon)
5
+ row_number = 0
6
+ last_size = 0
7
+
8
+ loop do
9
+ row_number += 2
10
+
11
+ row = pascal(row_number)
12
+ sum = row.sum
13
+ peak = row[row.size / 2].to_d / sum
14
+
15
+ # Remove weights that make negligible difference to the output image.
16
+ row = row.reject { |n| n.to_d / sum < peak * epsilon }
17
+ sum = row.sum
18
+
19
+ # If we've already seen a row of this size, skip those that are more truncated.
20
+ next if row.size == last_size
21
+ last_size = row.size
22
+
23
+ midpoint = row.size / 2
24
+ next if midpoint.odd?
25
+
26
+ weights_d = row.map { |n| n.to_d / sum }[midpoint..]
27
+ offsets_d = (0..midpoint).map(&:to_d)
28
+
29
+ break if weights_d[0] == 0 # Precision limit reached
30
+
31
+ weights_l = weights_d[1..].each_slice(2).map(&:sum)
32
+ offsets_l = offsets_d[1..].each_slice(2).map do |t1, t2|
33
+ numerator = offsets_d[t1] * weights_d[t1] + offsets_d[t2] * weights_d[t2]
34
+ denominator = weights_d[t1] + weights_d[t2]
35
+
36
+ numerator / denominator
37
+ end
38
+
39
+ weights_l.unshift(weights_d.first)
40
+ offsets_l.unshift(0.0)
41
+ texture_reads = offsets_l.size * 2 - 1
42
+
43
+ yield Values.new(offsets_d, weights_d, offsets_l, weights_l, texture_reads, row.size, row_number)
44
+ end
45
+ end
46
+
47
+ def self.pascal(n)
48
+ @cache ||= {}
49
+ @cache[n] ||= (n == 0 ? [1] : [1, *pascal(n - 1).each_cons(2).map(&:sum), 1])
50
+ end
51
+ end
@@ -0,0 +1,47 @@
1
+ class GaussianBlurGenerator
2
+ def self.glsl(values, axis)
3
+ offsets = values.offsets_l[1..].map.with_index do |offset, index|
4
+ "float offset#{index + 1} = one_pixel * #{offset.to_f};"
5
+ end
6
+
7
+ if axis == "x"
8
+ template = "coords.x + offset@, coords.y"
9
+ else
10
+ template = "coords.x, coords.y + offset@"
11
+ end
12
+
13
+ positives = values.weights_l[1..].map.with_index do |weight, index|
14
+ "#{weight.to_f} * texture2D(u_texture, vec2(#{template.sub("@", (index + 1).to_s)}))"
15
+ end
16
+
17
+ negatives = positives.map { |s| s.sub("+", "-") }
18
+
19
+ summation = negatives.reverse + [
20
+ "#{values.weights_l[0].to_f} * texture2D(u_texture, coords)"
21
+ ] + positives
22
+
23
+ <<~GLSL
24
+ #ifdef GL_FRAGMENT_PRECISION_HIGH
25
+ precision highp float;
26
+ #else
27
+ precision mediump float;
28
+ #endif
29
+
30
+ uniform sampler2D u_texture;
31
+ uniform vec4 u_dimensions;
32
+
33
+ // Generated by https://github.com/tuzz/gaussian_blur_generator
34
+
35
+ void main() {
36
+ vec2 coords = gl_FragCoord.xy / u_dimensions.xy;
37
+
38
+ float one_pixel = u_dimensions.#{axis == "x" ? "z" : "w"};
39
+ #{offsets.join("\n ")}
40
+
41
+ gl_FragColor = (
42
+ #{summation.join(" +\n ")}
43
+ );
44
+ }
45
+ GLSL
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gaussian_blur_generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Patuzzo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Generates fragment shaders that apply a Gaussian blur in an efficient
14
+ manner.
15
+ email: chris@patuzzo.co.uk
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - bin/generate
22
+ - lib/gaussian_blur_generator.rb
23
+ - lib/gaussian_blur_generator/base.rb
24
+ - lib/gaussian_blur_generator/glsl.rb
25
+ homepage: https://github.com/tuzz/gaussian_blur_generator
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubygems_version: 3.1.2
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Gaussian Blur Generator
48
+ test_files: []