active_storage-blurhash 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +65 -0
- data/Rakefile +8 -0
- data/app/assets/config/active_storage_blurhash_manifest.js +0 -0
- data/app/helpers/blurhash_image_helper.rb +38 -0
- data/config/routes.rb +2 -0
- data/lib/active_storage/blurhash/analyzer/image_magick.rb +16 -0
- data/lib/active_storage/blurhash/analyzer/vips.rb +16 -0
- data/lib/active_storage/blurhash/analyzing.rb +32 -0
- data/lib/active_storage/blurhash/engine.rb +13 -0
- data/lib/active_storage/blurhash/thumbnail/image_magick.rb +19 -0
- data/lib/active_storage/blurhash/thumbnail/vips.rb +19 -0
- data/lib/active_storage/blurhash/version.rb +5 -0
- data/lib/active_storage/blurhash.rb +7 -0
- data/lib/generators/active_storage/blurhash/install/USAGE +8 -0
- data/lib/generators/active_storage/blurhash/install/install_generator.rb +21 -0
- data/lib/generators/active_storage/blurhash/install/templates/javascript/index.js +107 -0
- data/lib/tasks/active_storage/blurhash_tasks.rake +15 -0
- metadata +189 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b2017d1770807244bbd6a7c5478f4ecb59a5f13ee5b7eb5a086c2f09856973f0
|
4
|
+
data.tar.gz: 9f086097d1d55813b5c0edae2e221a9470af3260e442622ca294c05646fcf48e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0e47a24da41f183e833387d0d2d03eac0c22dfb032044a333e328be4f04d41c79d638966adc0af9672e30ec5bd464c08e44a595d4e9d45b322891d2f20723578
|
7
|
+
data.tar.gz: 4700ccb58f8bc1e8e0b93e8f7207a09e3ebac00e0773fe8675fa2f88489fe2c4c2ff852c22a68b41670cb7db30559d1b056b3b19ef864cd11d7cfd33102e79d0
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright Julian Rubisch
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# ActiveStorage::Blurhash
|
2
|
+
|
3
|
+
A [blurhash](https://blurha.sh/) integration for images stored in ActiveStorage.
|
4
|
+
|
5
|
+
## Motivation
|
6
|
+
Elimination of layout shift and speeding up First/Largest Contentful Paint are among the primary goals for improving Core Web Vitals. For both scenarios, lazy loading images while displaying a temporary Blurhash before swapping it out for the actual image is a great way to enhance the loading experience and perceived performance.
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
Suppose we have a simple `Person` model containing an `avatar` attachment:
|
11
|
+
|
12
|
+
```rb
|
13
|
+
class Person < ApplicationRecord
|
14
|
+
has_one_attached :avatar
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
Simply swap out `image_tag` for `blurhash_image_tag` when displaying it:
|
19
|
+
|
20
|
+
```erb
|
21
|
+
<%= blurhash_image_tag person.avatar %>
|
22
|
+
```
|
23
|
+
|
24
|
+
This will create a wrapper `<div>` containing a `<canvas>` that is going to be painted with the respective blurhash.
|
25
|
+
|
26
|
+
Make sure to run the install generator and backfill any existing attachments (see below). New attachments should be automatically analyzed to include the blurhash metadata.
|
27
|
+
|
28
|
+
|
29
|
+
## Installation
|
30
|
+
Add this line to your application's Gemfile:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
gem "active_storage-blurhash"
|
34
|
+
```
|
35
|
+
|
36
|
+
And then execute:
|
37
|
+
```bash
|
38
|
+
$ bundle
|
39
|
+
```
|
40
|
+
|
41
|
+
|
42
|
+
Install the JavaScript packages and blurhash snippet:
|
43
|
+
|
44
|
+
```bash
|
45
|
+
$ bin/rails g active_storage:blurhash:install
|
46
|
+
```
|
47
|
+
|
48
|
+
## Backfilling
|
49
|
+
|
50
|
+
Chances are you already have a large assortment of ActiveStorage attachments. In this case, we can help you out with a backfill rake task that re-analyzes all existing image blobs:
|
51
|
+
|
52
|
+
```bash
|
53
|
+
$ bin/rails active_storage_blurhash:backfill
|
54
|
+
```
|
55
|
+
|
56
|
+
If you want to throttle it, you can change the batch size using the `BATCH_SIZE` environment variable (default is 1000):
|
57
|
+
|
58
|
+
```bash
|
59
|
+
$ BATCH_SIZE=50 bin/rails active_storage_blurhash:backfill
|
60
|
+
```
|
61
|
+
|
62
|
+
Note that for each blob an `AnalyzeJob` will be appended to your job processor queue.
|
63
|
+
|
64
|
+
## License
|
65
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
File without changes
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BlurhashImageHelper
|
4
|
+
def blurhash_image_tag(source, options = {})
|
5
|
+
case source
|
6
|
+
when String
|
7
|
+
# if a URL is passed, we have to manually re-hydrate the blob from it
|
8
|
+
path_parameters = Rails.application.routes.recognize_path(source)
|
9
|
+
blob = ActiveStorage::Blob.find_signed!(path_parameters[:signed_blob_id] || path_parameters[:signed_id])
|
10
|
+
when ActiveStorage::Blob
|
11
|
+
blob = source
|
12
|
+
when ActiveStorage::Attached::One
|
13
|
+
blob = source.blob
|
14
|
+
when ActiveStorage::VariantWithRecord
|
15
|
+
blob = source.blob
|
16
|
+
# dimensions of the transformation need not represent the actual aspect ratio, like in `resize_to_fit`
|
17
|
+
# size = source.variation.transformations[:resize]
|
18
|
+
end
|
19
|
+
|
20
|
+
blurhash = blob.metadata["blurhash"]
|
21
|
+
size ||= "#{blob.metadata["width"]}x#{blob.metadata["height"]}"
|
22
|
+
|
23
|
+
if !!blurhash
|
24
|
+
options[:loading] = "lazy"
|
25
|
+
options[:size] = size
|
26
|
+
|
27
|
+
wrapper_class = options.delete(:wrapper_class)
|
28
|
+
canvas_class = options.delete(:canvas_class)
|
29
|
+
tag.div class: wrapper_class, data: {blurhash: blurhash}, style: "position: relative" do
|
30
|
+
image_tag(source, options) + tag.canvas(style: "position: absolute; inset: 0; transition-property: opacity; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms;", class: canvas_class)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
image_tag(source, options)
|
34
|
+
end
|
35
|
+
rescue ActionController::RoutingError
|
36
|
+
image_tag(source, options)
|
37
|
+
end
|
38
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "active_storage/blurhash/analyzing"
|
2
|
+
require "active_storage/blurhash/thumbnail/image_magick"
|
3
|
+
|
4
|
+
module ActiveStorage
|
5
|
+
module Blurhash
|
6
|
+
module Analyzer
|
7
|
+
class ImageMagick < ActiveStorage::Analyzer::ImageAnalyzer::ImageMagick
|
8
|
+
include ActiveStorage::Blurhash::Analyzing
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def processor = "ImageMagick"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "active_storage/blurhash/analyzing"
|
2
|
+
require "active_storage/blurhash/thumbnail/vips"
|
3
|
+
|
4
|
+
module ActiveStorage
|
5
|
+
module Blurhash
|
6
|
+
module Analyzer
|
7
|
+
class Vips < ActiveStorage::Analyzer::ImageAnalyzer::Vips
|
8
|
+
include ActiveStorage::Blurhash::Analyzing
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def processor = "Vips"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "blurhash"
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
module Blurhash
|
5
|
+
module Analyzing
|
6
|
+
attr_accessor :thumbnail
|
7
|
+
|
8
|
+
def metadata
|
9
|
+
# we could also re-implement #metadata, so that the image is only read once, but it's much less DRY
|
10
|
+
read_image do |image|
|
11
|
+
build_thumbnail(image)
|
12
|
+
super.merge blurhash
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def blurhash
|
17
|
+
{
|
18
|
+
blurhash: ::Blurhash.encode(
|
19
|
+
thumbnail.width,
|
20
|
+
thumbnail.height,
|
21
|
+
thumbnail.pixels
|
22
|
+
)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_thumbnail(image)
|
27
|
+
# we scale down the image for faster blurhash processing
|
28
|
+
@thumbnail ||= "ActiveStorage::Blurhash::Thumbnail::#{processor}".constantize.new(image)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "active_storage/blurhash/analyzer/image_magick"
|
2
|
+
require "active_storage/blurhash/analyzer/vips"
|
3
|
+
|
4
|
+
module ActiveStorage
|
5
|
+
module Blurhash
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
initializer "active_storage-blurhash.analyze" do
|
8
|
+
Rails.application.config.active_storage.analyzers.prepend ActiveStorage::Blurhash::Analyzer::ImageMagick
|
9
|
+
Rails.application.config.active_storage.analyzers.prepend ActiveStorage::Blurhash::Analyzer::Vips
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveStorage
|
2
|
+
module Blurhash
|
3
|
+
module Thumbnail
|
4
|
+
class ImageMagick
|
5
|
+
delegate_missing_to :@thumbnail
|
6
|
+
|
7
|
+
def initialize(image)
|
8
|
+
@thumbnail = MiniMagick::Image.open(
|
9
|
+
::ImageProcessing::MiniMagick.source(image.path).resize_to_limit(200, 200).call.path
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def pixels
|
14
|
+
@thumbnail.get_pixels.flatten
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveStorage
|
2
|
+
module Blurhash
|
3
|
+
module Thumbnail
|
4
|
+
class Vips
|
5
|
+
delegate_missing_to :@thumbnail
|
6
|
+
|
7
|
+
def initialize(image)
|
8
|
+
@thumbnail = ::Vips::Image.new_from_file(
|
9
|
+
::ImageProcessing::Vips.source(image.filename).resize_to_limit(200, 200).call.path
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def pixels
|
14
|
+
@thumbnail.write_to_memory.unpack("C*")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class ActiveStorage::Blurhash::InstallGenerator < Rails::Generators::Base
|
2
|
+
source_root File.expand_path("templates", __dir__)
|
3
|
+
|
4
|
+
def install_javascript_deps
|
5
|
+
if File.exist? Rails.root.join("config", "importmap.rb")
|
6
|
+
say "Pinning blurhash"
|
7
|
+
run "bin/importmap pin blurhash"
|
8
|
+
else
|
9
|
+
say "Installing blurhash"
|
10
|
+
run "yarn add blurhash"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def copy_application_javascript
|
15
|
+
directory "javascript", "app/javascript/blurhash"
|
16
|
+
end
|
17
|
+
|
18
|
+
def append_to_main_javascript_entrypoint
|
19
|
+
append_to_file "app/javascript/application.js", "import \"./blurhash\";\n"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
import { decode } from "blurhash";
|
2
|
+
|
3
|
+
window.ActiveStorageBlurhash = {
|
4
|
+
observer: new MutationObserver((mutationList, observer) => {
|
5
|
+
mutationList.forEach(({ type, target, addedNodes, attributeName }) => {
|
6
|
+
switch (type) {
|
7
|
+
case "childList":
|
8
|
+
if (addedNodes.length === 0) return;
|
9
|
+
|
10
|
+
Array.from(addedNodes)
|
11
|
+
.filter((node) => {
|
12
|
+
try {
|
13
|
+
return "blurhash" in node.dataset;
|
14
|
+
} catch (e) {
|
15
|
+
return false;
|
16
|
+
}
|
17
|
+
})
|
18
|
+
.forEach((node) => {
|
19
|
+
window.ActiveStorageBlurhash.renderAndLoad(node);
|
20
|
+
});
|
21
|
+
break;
|
22
|
+
case "attributes":
|
23
|
+
window.ActiveStorageBlurhash.renderAndLoad(target);
|
24
|
+
break;
|
25
|
+
}
|
26
|
+
});
|
27
|
+
}),
|
28
|
+
renderAndLoad(wrapper) {
|
29
|
+
const image = wrapper.querySelector("img");
|
30
|
+
|
31
|
+
// the image might already be completely loaded. In this case we need to do nothing
|
32
|
+
if (image.complete) return;
|
33
|
+
|
34
|
+
// if the image comes in with empty dimensions, we can't assign canvas data
|
35
|
+
if (image.width === 0 || image.height === 0) return;
|
36
|
+
|
37
|
+
const width = image.width;
|
38
|
+
const height = image.height;
|
39
|
+
|
40
|
+
const canvas = wrapper.querySelector("canvas");
|
41
|
+
|
42
|
+
canvas.width = width;
|
43
|
+
canvas.height = height;
|
44
|
+
|
45
|
+
const pixels = decode(wrapper.dataset.blurhash, width, height);
|
46
|
+
const ctx = canvas.getContext("2d");
|
47
|
+
const imageData = ctx.createImageData(width, height);
|
48
|
+
imageData.data.set(pixels);
|
49
|
+
ctx.putImageData(imageData, 0, 0);
|
50
|
+
|
51
|
+
sessionStorage.setItem(
|
52
|
+
`active-storage-blurhash-${wrapper.dataset.blurhash}`,
|
53
|
+
canvas.toDataURL(),
|
54
|
+
);
|
55
|
+
|
56
|
+
const swap = () => {
|
57
|
+
canvas.style.opacity = "0";
|
58
|
+
};
|
59
|
+
|
60
|
+
if (image.complete) {
|
61
|
+
// the image might already have been loaded
|
62
|
+
swap();
|
63
|
+
} else {
|
64
|
+
// else we need to wait for it to load
|
65
|
+
image.onload = swap;
|
66
|
+
}
|
67
|
+
},
|
68
|
+
restoreCanvases() {
|
69
|
+
const inNamespace = ([key, _payload]) =>
|
70
|
+
key.startsWith("active-storage-blurhash-");
|
71
|
+
Object.entries(sessionStorage)
|
72
|
+
.filter(inNamespace)
|
73
|
+
.forEach(([key, _payload]) => {
|
74
|
+
const match = /^active-storage-blurhash-(.*)/.exec(key);
|
75
|
+
const targetElement = document.querySelector(
|
76
|
+
`[data-blurhash="${match[1]}"]`,
|
77
|
+
);
|
78
|
+
|
79
|
+
if (targetElement) {
|
80
|
+
const canvas = targetElement.querySelector("canvas");
|
81
|
+
canvas.style.opacity = "100%";
|
82
|
+
|
83
|
+
// right now we don't do anything with the payload, since setting the opacity to 100% apparently correctly prepares the elements for caching
|
84
|
+
sessionStorage.removeItem(key);
|
85
|
+
}
|
86
|
+
});
|
87
|
+
},
|
88
|
+
};
|
89
|
+
|
90
|
+
document.addEventListener("turbo:load", () => {
|
91
|
+
document.querySelectorAll("div[data-blurhash]").forEach((wrapper) => {
|
92
|
+
window.ActiveStorageBlurhash.renderAndLoad(wrapper);
|
93
|
+
});
|
94
|
+
|
95
|
+
window.ActiveStorageBlurhash.observer.disconnect();
|
96
|
+
window.ActiveStorageBlurhash.observer.observe(document.body, {
|
97
|
+
subtree: true,
|
98
|
+
childList: true,
|
99
|
+
attributes: true,
|
100
|
+
attributeFilter: ["data-blurhash"],
|
101
|
+
});
|
102
|
+
});
|
103
|
+
|
104
|
+
document.addEventListener(
|
105
|
+
"turbo:before-cache",
|
106
|
+
window.ActiveStorageBlurhash.restoreCanvases,
|
107
|
+
);
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :active_storage_blurhash do
|
4
|
+
desc "Backfill blurhash metadata for existing ActiveStorage image attachments"
|
5
|
+
task backfill: :environment do
|
6
|
+
batch_size = ENV["BATCH_SIZE"]&.to_i || 1000
|
7
|
+
|
8
|
+
ActiveStorage::Attachment
|
9
|
+
.joins(:blob)
|
10
|
+
.where("content_type LIKE ?", "image/%")
|
11
|
+
.find_each(batch_size: batch_size) do |attachment|
|
12
|
+
attachment.analyze_later
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_storage-blurhash
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julian Rubisch
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-08-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activestorage
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 7.1.3.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 7.1.3.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 7.1.3.4
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 7.1.3.4
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: blurhash
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: image_processing
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.0.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.0.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 7.1.3.4
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 7.1.3.4
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mini_magick
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: nokogiri
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: ruby-vips
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: standard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Use blurhashes to lazy load ActiveStorage images
|
140
|
+
email:
|
141
|
+
- julian@julianrubisch.at
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- MIT-LICENSE
|
147
|
+
- README.md
|
148
|
+
- Rakefile
|
149
|
+
- app/assets/config/active_storage_blurhash_manifest.js
|
150
|
+
- app/helpers/blurhash_image_helper.rb
|
151
|
+
- config/routes.rb
|
152
|
+
- lib/active_storage/blurhash.rb
|
153
|
+
- lib/active_storage/blurhash/analyzer/image_magick.rb
|
154
|
+
- lib/active_storage/blurhash/analyzer/vips.rb
|
155
|
+
- lib/active_storage/blurhash/analyzing.rb
|
156
|
+
- lib/active_storage/blurhash/engine.rb
|
157
|
+
- lib/active_storage/blurhash/thumbnail/image_magick.rb
|
158
|
+
- lib/active_storage/blurhash/thumbnail/vips.rb
|
159
|
+
- lib/active_storage/blurhash/version.rb
|
160
|
+
- lib/generators/active_storage/blurhash/install/USAGE
|
161
|
+
- lib/generators/active_storage/blurhash/install/install_generator.rb
|
162
|
+
- lib/generators/active_storage/blurhash/install/templates/javascript/index.js
|
163
|
+
- lib/tasks/active_storage/blurhash_tasks.rake
|
164
|
+
homepage: https://github.com/avo-hq/active_storage-blurhash
|
165
|
+
licenses:
|
166
|
+
- MIT
|
167
|
+
metadata:
|
168
|
+
homepage_uri: https://github.com/avo-hq/active_storage-blurhash
|
169
|
+
source_code_uri: https://github.com/avo-hq/active_storage-blurhash
|
170
|
+
post_install_message:
|
171
|
+
rdoc_options: []
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
requirements: []
|
185
|
+
rubygems_version: 3.4.10
|
186
|
+
signing_key:
|
187
|
+
specification_version: 4
|
188
|
+
summary: Use blurhashes to lazy load ActiveStorage images
|
189
|
+
test_files: []
|