foggy-mirror 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []