foggy-mirror 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: 1cc9dc0f12ce500219fb0488fb7b8e54c24819a611645a0072a56695ced1336a
4
+ data.tar.gz: 883c91418982a115fd45411d80159260c78bb0798078ee6b7a0efa1f85934c63
5
+ SHA512:
6
+ metadata.gz: 01bcab41ac44cc5305863dc22486cea86cccf94558f1d397942d1a214fbf2ad6fcc6e420312e2cacc9efe4b07064b349fc43769c42ad51aca4623e361d647dd4
7
+ data.tar.gz: 788d0af0500e35d613a9108ba04f0b22da5dc33d9ea3cebb69cea1fdeda7e226a57766be6623ae48eff088dc4349c225bdc639a6c9f49257fbfcb76ed4291ab1
@@ -0,0 +1,24 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ specs:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ ruby: [2.7, '3.0', 3.1]
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - name: Install ImageMagick and libvips
17
+ run: sudo apt install imagemagick libvips42
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby }}
22
+ bundler-cache: true
23
+ - name: Run specs
24
+ run: bundle exec rspec spec --backtrace
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /data/
10
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in foggy-mirror.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,56 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ foggy-mirror (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ byebug (11.1.3)
10
+ coderay (1.1.3)
11
+ diff-lcs (1.5.0)
12
+ ffi (1.15.5)
13
+ method_source (1.0.0)
14
+ nokogiri (1.13.3-x86_64-darwin)
15
+ racc (~> 1.4)
16
+ nokogiri (1.13.3-x86_64-linux)
17
+ racc (~> 1.4)
18
+ pry (0.13.1)
19
+ coderay (~> 1.1)
20
+ method_source (~> 1.0)
21
+ pry-byebug (3.9.0)
22
+ byebug (~> 11.0)
23
+ pry (~> 0.13.0)
24
+ racc (1.6.0)
25
+ rake (13.0.6)
26
+ rspec (3.11.0)
27
+ rspec-core (~> 3.11.0)
28
+ rspec-expectations (~> 3.11.0)
29
+ rspec-mocks (~> 3.11.0)
30
+ rspec-core (3.11.0)
31
+ rspec-support (~> 3.11.0)
32
+ rspec-expectations (3.11.0)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.11.0)
35
+ rspec-mocks (3.11.0)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.11.0)
38
+ rspec-support (3.11.0)
39
+ ruby-vips (2.1.4)
40
+ ffi (~> 1.12)
41
+
42
+ PLATFORMS
43
+ x86_64-darwin-19
44
+ x86_64-darwin-21
45
+ x86_64-linux
46
+
47
+ DEPENDENCIES
48
+ foggy-mirror!
49
+ nokogiri
50
+ pry-byebug
51
+ rake (~> 13.0)
52
+ rspec (~> 3.1)
53
+ ruby-vips (~> 2.1)
54
+
55
+ BUNDLED WITH
56
+ 2.3.8
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Pedro Carbajal and Beezwax Datatools, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # foggy-mirror
2
+
3
+ ![CI](https://github.com/beezwax/foggy-mirror/workflows/CI/badge.svg)
4
+
5
+ foggy-mirror is a small Ruby tool that creates faux blurry versions of raster
6
+ images using radial gradients, exported as either SVG or CSS (using
7
+ `background-image`).
8
+
9
+ This is useful as a poor man's replacement for CSS's `backdrop-filter: blur()`,
10
+ as that CSS feature isn't fully supported by browsers, and sometimes you want
11
+ an element with a "frosted glass" effect on top of a crispy background.
12
+
13
+ Using gradients we can achieve very smooth and infinitely scalable graphics at
14
+ very low file size (e.g. the example SVG below is only 814 bytes after gzip).
15
+ If you need a less blurry version of the original picture, then a regular blur
16
+ effect saved to JPEG/WebP may be a better value proposition.
17
+
18
+ ## Example
19
+
20
+ Original raster image:
21
+
22
+ ![Photo by Marek Piwnicki (@marekpiwnicki) / Unsplash](/img/unsplash-sq.webp)
23
+
24
+ SVG:
25
+
26
+ <img src="/img/unsplash.svg" alt="foggy-mirror SVG" width="500" height="500" />
27
+
28
+ ## Installation
29
+
30
+ In your Gemfile:
31
+
32
+ ```ruby
33
+ gem 'foggy-mirror'
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ Within Ruby:
39
+
40
+ ```ruby
41
+ require 'foggy-mirror'
42
+
43
+ # All keyword arguments are optional, only the filename is required
44
+ p = FoggyMirror::Processor.new("/path/to/image.jpg",
45
+ resolution: 5,
46
+ overlap: 0.5,
47
+ distribution: :shuffle,
48
+ random_offset: 0.5,
49
+ random: Random.new,
50
+ adapter: FoggyMirror::ImageMagick,
51
+ adapter_options: {}
52
+ )
53
+
54
+ p.to_svg # Outputs SVG
55
+
56
+ p.to_css # Outputs CSS properties
57
+ ```
58
+
59
+ Options are:
60
+
61
+ * `resolution` (Integer): How many radial gradients to use per dimension (X/Y).
62
+ Defaults to 5.
63
+ * `overlap` (Float): How much radial gradients overlap each other (0 means no
64
+ overlap). Defaults to 0.5.
65
+ * `distribution` (Symbol/String): How to distribute the radial gradients. Since
66
+ they can overlap each other, their position on the Z-axis affects the final
67
+ result. Accepted values are `:scan` (default), `:scan_reverse`, `:suffle`,
68
+ `:spiral_in` and `:spiral_out`.
69
+ * `random_offset` (Float): How much to randomly offset the center of each
70
+ radial gradient, which can create a more natural looking result (0 means no
71
+ offset). Defaults to 0.5.
72
+ * `random` (Random instance): The Random instance to use for generating random
73
+ values, in case you need it to be deterministic.
74
+ * `adapter` (Class): the adapter to use for reading and processing image files.
75
+ Supported adapters are `FoggyMirror::ImageMagick` (uses CLI commands) and
76
+ `FoggyMirror::Vips` (fastest, but requires installing `ruby-vips` gem).
77
+ * `adapter_options` (Hash): options to pass to the adapter on initialization.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "foggy-mirror"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/foggy-mirror ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # frozen_string_literal: true
4
+ require 'foggy-mirror'
5
+
6
+ FoggyMirror::CLI.new.run
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/foggy-mirror/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "foggy-mirror"
7
+ spec.version = FoggyMirror::VERSION
8
+ spec.authors = ["Pedro Carbajal"]
9
+ spec.email = ["pedro_c@beezwax.net"]
10
+
11
+ spec.summary = "Tool to create gradient-based blurred versions of raster images"
12
+ spec.description = "foggy-mirror takes a raster image and outputs a blurred version of it using CSS or SVG radial-gradients"
13
+ spec.homepage = "https://github.com/beezwax/foggy-mirror"
14
+ spec.required_ruby_version = ">= 2.4.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|img)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_development_dependency "ruby-vips", "~> 2.1"
29
+ spec.add_development_dependency "rake", "~> 13.0"
30
+ spec.add_development_dependency "rspec", "~> 3.1"
31
+ spec.add_development_dependency "nokogiri"
32
+ spec.add_development_dependency "pry-byebug"
33
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require 'shellwords'
3
+
4
+ module FoggyMirror
5
+ class ImageMagick
6
+ def initialize(path, command: 'convert')
7
+ @path = path
8
+ @command = command
9
+ end
10
+
11
+ def dominant_color
12
+ out = `#{@command} #{@path.shellescape} -depth 8 -colors 1 -resize 1x1 txt:-`
13
+ validate_pixel_enumeration!(out)
14
+ out.match(HTML_COLOR_MATCHER)[0]
15
+ end
16
+
17
+ def color_samples(res)
18
+ out = `#{@command} #{@path.shellescape} -depth 8 -colors 256 -resize #{res}x#{res}! txt:-`
19
+ validate_pixel_enumeration!(out)
20
+ out.scan(HTML_COLOR_MATCHER)
21
+ end
22
+
23
+ private
24
+
25
+ def validate_pixel_enumeration!(output)
26
+ raise Error, "convert command didn't return as expected" unless output.start_with?("# ImageMagick pixel")
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ begin
3
+ require 'vips'
4
+ rescue LoadError
5
+ raise LoadError, "Couldn't load 'vips' library, do you have ruby-vips in your Gemfile?"
6
+ end
7
+
8
+ module FoggyMirror
9
+ class Vips
10
+ include Utils
11
+
12
+ def initialize(path)
13
+ @path = path
14
+ end
15
+
16
+ def dominant_color
17
+ html_color *color_avg(load_image(DEFAULT_RESOLUTION))
18
+ end
19
+
20
+ def color_samples(res)
21
+ square_image(res).to_a.flatten(1).map { |rgb| html_color *rgb }
22
+ end
23
+
24
+ private
25
+
26
+ def square_image(res)
27
+ # We load the image as a thumbnail, shrinking it to double the resolution
28
+ # requested, making use of libvips' shrink-on-load optimization, which
29
+ # should be way faster than loading the entire image and shrinking.
30
+ #
31
+ # This optimization assumes the original image isn't more than twice as
32
+ # wide as it is high, or vice-versa.
33
+ im = load_image(res * 2)
34
+
35
+ # Make the image square
36
+ if im.width > im.height
37
+ im = im.reduceh(im.width.to_f / im.height)
38
+ elsif im.height > im.width
39
+ im = im.reducev(im.height.to_f / im.width)
40
+ end
41
+
42
+ im.resize(res.to_f / im.width)
43
+ end
44
+
45
+ def load_image(res)
46
+ ::Vips::Image.thumbnail(@path, res)
47
+ end
48
+
49
+ def color_avg(image)
50
+ image.stats.to_a[1..-1].map { |b| b[4].first.to_i }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+ require 'optparse'
3
+
4
+ module FoggyMirror
5
+ class CLI
6
+ DEFAULT_EXTENSION = '.foggy.svg'
7
+
8
+ def initialize(args = ARGV)
9
+ @args = args.dup
10
+ end
11
+
12
+ def run
13
+ @options = {}
14
+ @extension = DEFAULT_EXTENSION
15
+ @stdout = false
16
+ @target_dir = nil
17
+
18
+ parser.parse!(@args)
19
+
20
+ # OptionParser.parse removes options from the args, so we're left with
21
+ # filenames
22
+ @args.each do |path|
23
+ p = Processor.new(path, **@options)
24
+
25
+ unless @stdout
26
+ foggy_file = File.join(@target_dir || File.dirname(path), File.basename(path, '.*') + @extension)
27
+ IO.write(foggy_file, p.to_svg)
28
+ else
29
+ puts p.to_svg
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def parser
37
+ @parser ||=
38
+ OptionParser.new do |opts|
39
+ opts.banner = 'Usage: foggy-mirror [options] [--] image_file ...'
40
+
41
+ opts.on('--res=RESOLUTION', Integer, 'The output resolution (how many radial gradients per dimension)') do |r|
42
+ @options[:resolution] = r
43
+ end
44
+
45
+ opts.on('--overlap=OVERLAP', Float, 'How much to overlap radial gradients') do |o|
46
+ @options[:overlap] = o
47
+ end
48
+
49
+ opts.on('--dist=DISTRIBUTION', %w[shuffle spiral_in spiral_out scan scan_reverse], 'Distribution strategy for radial gradients') do |d|
50
+ @options[:distribution] = d.to_s
51
+ end
52
+
53
+ opts.on('--random-offset=OFFSET', Float, 'Upper limit for how much to randomly offset each radial gradient') do |r|
54
+ @options[:random_offset] = r.to_f
55
+ end
56
+
57
+ opts.on('--random-seed=SEED', Integer, 'The random seed to use (for deterministic results)') do |s|
58
+ @options[:random] = Random.new(s)
59
+ end
60
+
61
+ opts.on('--adapter=ADAPTER', ADAPTERS.keys, 'Which graphics library adapter to use') do |a|
62
+ @options[:adapter] = a
63
+ end
64
+
65
+ opts.on('--extension=EXTENSION', String, "The extension to use for created files (default: #{DEFAULT_EXTENSION})") do |e|
66
+ @extension = e
67
+ end
68
+
69
+ opts.on('--stdout', 'Output to STDOUT instead of writing to files') do
70
+ @stdout = true
71
+ end
72
+
73
+ opts.on('--target-dir=DIR', String, 'Directory to write files to (defaults to same as input files)') do |d|
74
+ @target_dir = d
75
+ end
76
+
77
+ opts.on_tail('-h', '--help', 'Print help') do
78
+ puts opts
79
+ exit
80
+ end
81
+
82
+ opts.on_tail("--version", "Show version") do
83
+ require 'foggy-mirror/version'
84
+ puts VERSION
85
+ exit
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module FoggyMirror
6
+ class Exporter
7
+ extend Forwardable
8
+
9
+ def_delegators :@processor, :base_color, :resolution, :blobs
10
+
11
+ def initialize(processor)
12
+ @processor = processor
13
+ end
14
+
15
+ def render
16
+ raise NotImplementedError
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoggyMirror
4
+ class CSS < Exporter
5
+ def render(hash: false)
6
+ if hash
7
+ return {
8
+ 'background-color' => base_color,
9
+ 'background-image' => radial_gradients.join(', ')
10
+ }
11
+ end
12
+
13
+ render(hash: true).map { |k, v| "#{k}: #{v}" }.join(";\n") + ';'
14
+ end
15
+
16
+ private
17
+
18
+ def radial_gradients
19
+ blobs.map do |b|
20
+ x, y, r = [b.x, b.y, b.r].map { |c| (c * 100).round(1) }
21
+ "radial-gradient(circle at #{x}% #{y}%, #{b.color} 0%, #{b.color}00 #{r}%)"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoggyMirror
4
+ class SVG < Exporter
5
+ XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>'
6
+
7
+ ID_START = 'a'
8
+
9
+ VIEWBOX_SIZE = 1000
10
+
11
+ def render
12
+ "#{header}<defs>#{radial_gradients.join}</defs>#{circles.join}</svg>"
13
+ end
14
+
15
+ def header
16
+ %{#{XML_HEADER}<svg viewBox="0 0 #{VIEWBOX_SIZE} #{VIEWBOX_SIZE}" xmlns="http://www.w3.org/2000/svg" version="1.1" style="background-color:#{base_color}">}
17
+ end
18
+
19
+ def radial_gradients
20
+ color_ids.map do |color, id|
21
+ %{<radialGradient id="#{id}"><stop offset="0" stop-color="#{color}"/><stop offset="100%" stop-color="#{color}" stop-opacity="0"/></radialGradient>}
22
+ end
23
+ end
24
+
25
+ def circles
26
+ blobs.map.with_index do |b, i|
27
+ id = color_ids[b.color]
28
+ x, y, r = [b.x, b.y, b.r].map { |c| (c * VIEWBOX_SIZE).to_i }
29
+ %{<circle cx="#{x}" cy="#{y}" r="#{r}" fill="url(##{id})"/>}
30
+ end
31
+ end
32
+
33
+ def color_ids
34
+ @color_ids ||=
35
+ begin
36
+ id = String.new(ID_START)
37
+ Hash[blobs.map(&:color).uniq.map { |c| [c, id.dup].tap { id.succ! } }]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoggyMirror
4
+ class Processor
5
+ attr_reader :image_path, :resolution, :overlap, :random_offset, :random, :adapter
6
+
7
+ def initialize(image_path, resolution: DEFAULT_RESOLUTION, overlap: 0.5, distribution: nil, random_offset: 0.5, random: Random.new, adapter: default_adapter, adapter_options: {})
8
+ @image_path = image_path
9
+
10
+ @resolution = resolution
11
+ @overlap = overlap
12
+ @distribution = distribution
13
+ @random_offset = random_offset
14
+
15
+ @random = random
16
+ @adapter = resolve_adapter(adapter).new(image_path, **adapter_options)
17
+ end
18
+
19
+ def base_color
20
+ @base_color ||= adapter.dominant_color
21
+ end
22
+
23
+ def color_samples(res)
24
+ @color_samples ||= adapter.color_samples(res)
25
+ end
26
+
27
+ def to_css(hash: false)
28
+ CSS.new(self).render(hash: hash)
29
+ end
30
+
31
+ def to_svg
32
+ SVG.new(self).render
33
+ end
34
+
35
+ def blobs
36
+ samples = color_samples(resolution)
37
+
38
+ increment = 1.0 / (resolution - 1)
39
+
40
+ blobs = resolution.times.with_object([]) do |y, blobs|
41
+ resolution.times do |x|
42
+ xp = x * increment + increment * random_offset * (random.rand - 0.5)
43
+ yp = y * increment + increment * random_offset * (random.rand - 0.5)
44
+ r = increment * (1 + overlap)
45
+ color = samples[y * resolution + x]
46
+
47
+ blobs << Blob.new(x: xp, y: yp, r: r, color: color)
48
+ end
49
+ end
50
+
51
+ case @distribution.to_s.downcase
52
+ when 'shuffle'
53
+ blobs.shuffle(random: random)
54
+ when 'spiral_in'
55
+ spiral_in(blobs)
56
+ when 'spiral_out'
57
+ spiral_in(blobs).reverse
58
+ when 'scan_reverse'
59
+ blobs.reverse
60
+ else
61
+ blobs
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def spiral_in(array)
68
+ matrix = array.each_slice(Math.sqrt(array.size).to_i).to_a
69
+
70
+ actions = [
71
+ -> { matrix.shift }, # top
72
+ -> { matrix.map { |f| f.pop } }, # right
73
+ -> { matrix.pop.reverse }, # bottom
74
+ -> { matrix.map { |f| f.shift }.reverse } # left
75
+ ]
76
+
77
+ peel = actions.cycle
78
+
79
+ [].tap do |r|
80
+ r.concat(peel.next.call) until matrix.empty?
81
+ end
82
+ end
83
+
84
+ def default_adapter
85
+ require 'vips'
86
+ Vips
87
+ rescue LoadError
88
+ ImageMagick
89
+ end
90
+
91
+ def resolve_adapter(adapter)
92
+ if adapter.kind_of?(Symbol) || adapter.kind_of?(String)
93
+ return ADAPTERS.fetch(adapter.to_sym).call
94
+ end
95
+
96
+ return adapter if adapter.kind_of?(Class)
97
+
98
+ raise Error, "`adapter' must be an adapter class or an adapter name (#{ADAPTERS.keys.join(', ')})"
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoggyMirror
4
+ module Utils
5
+ PADDED_HEX_BYTE = '%02x'
6
+
7
+ def html_color(r, g, b)
8
+ '#' + [r, g, b].map { |c| PADDED_HEX_BYTE % c }.join.upcase
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoggyMirror
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FoggyMirror
4
+ class Error < StandardError; end
5
+
6
+ HTML_COLOR_MATCHER = /#[A-F0-9]{6}/.freeze
7
+
8
+ DEFAULT_RESOLUTION = 5
9
+
10
+ ADAPTERS = {
11
+ vips: -> { FoggyMirror::Vips },
12
+ magick: -> { FoggyMirror::ImageMagick }
13
+ }.freeze
14
+
15
+ Blob = Struct.new(:x, :y, :r, :color, keyword_init: true)
16
+
17
+ autoload :Processor, 'foggy-mirror/processor'
18
+ autoload :CLI, 'foggy-mirror/cli'
19
+ autoload :Utils, 'foggy-mirror/utils'
20
+ autoload :ImageMagick, 'foggy-mirror/adapters/image_magick'
21
+ autoload :Vips, 'foggy-mirror/adapters/vips'
22
+ autoload :Exporter, 'foggy-mirror/exporter'
23
+ autoload :CSS, 'foggy-mirror/exporters/css'
24
+ autoload :SVG, 'foggy-mirror/exporters/svg'
25
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foggy-mirror
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Pedro Carbajal
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-03-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-vips
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nokogiri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: foggy-mirror takes a raster image and outputs a blurred version of it
84
+ using CSS or SVG radial-gradients
85
+ email:
86
+ - pedro_c@beezwax.net
87
+ executables:
88
+ - foggy-mirror
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - ".github/workflows/main.yml"
93
+ - ".gitignore"
94
+ - ".rspec"
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - LICENSE.txt
98
+ - README.md
99
+ - Rakefile
100
+ - bin/console
101
+ - bin/setup
102
+ - exe/foggy-mirror
103
+ - foggy-mirror.gemspec
104
+ - lib/foggy-mirror.rb
105
+ - lib/foggy-mirror/adapters/image_magick.rb
106
+ - lib/foggy-mirror/adapters/vips.rb
107
+ - lib/foggy-mirror/cli.rb
108
+ - lib/foggy-mirror/exporter.rb
109
+ - lib/foggy-mirror/exporters/css.rb
110
+ - lib/foggy-mirror/exporters/svg.rb
111
+ - lib/foggy-mirror/processor.rb
112
+ - lib/foggy-mirror/utils.rb
113
+ - lib/foggy-mirror/version.rb
114
+ homepage: https://github.com/beezwax/foggy-mirror
115
+ licenses: []
116
+ metadata:
117
+ homepage_uri: https://github.com/beezwax/foggy-mirror
118
+ source_code_uri: https://github.com/beezwax/foggy-mirror
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: 2.4.0
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.3.3
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Tool to create gradient-based blurred versions of raster images
138
+ test_files: []