middleman-srcset_images 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/Gemfile +18 -0
- data/LICENSE +21 -0
- data/README.md +161 -0
- data/Rakefile +14 -0
- data/lib/middleman-srcset_images.rb +6 -0
- data/lib/middleman-srcset_images/create_image_version.rb +79 -0
- data/lib/middleman-srcset_images/dimensions_patch.rb +34 -0
- data/lib/middleman-srcset_images/extension.rb +83 -0
- data/lib/middleman-srcset_images/html_converter.rb +40 -0
- data/lib/middleman-srcset_images/image_version.rb +115 -0
- data/lib/middleman-srcset_images/img.rb +74 -0
- data/lib/middleman-srcset_images/srcset_config.rb +44 -0
- data/lib/middleman-srcset_images/version_resource.rb +20 -0
- data/lib/middleman-srcset_images/view_helpers.rb +54 -0
- data/lib/middleman-srcset_images/vips_create_image_version.rb +46 -0
- data/middleman-srcset_images.gemspec +26 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 47d0832736b0b4db47c6118f7db98a8e4f50e5ea5bb2629a0a181251f5c49822
|
4
|
+
data.tar.gz: cc58dd2ab388a3b991840294784e6c20bd3e1f9557fa409a806396d33f6473db
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 01ee0c374636bd776273c8ea06d5706bf5353e2c622b7e06b48e9100854d41e1e8eed410b0bddc12a6ddc909423303a375fed57386666db9410dedd233f0cb18
|
7
|
+
data.tar.gz: fdbe6fc29729677f9722c0d90bcaab54b2de7d50525662e8a86c5b09d7f986eeab3367c3c887831c422939978f0161a03bb8badbccda99dcdb57fa5b35be170e
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# If you do not have OpenSSL installed, update
|
2
|
+
# the following line to use "http://" instead
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in middleman-srcset_images.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
group :development do
|
9
|
+
gem 'rake'
|
10
|
+
gem 'rdoc'
|
11
|
+
gem 'yard'
|
12
|
+
end
|
13
|
+
|
14
|
+
group :test do
|
15
|
+
gem 'cucumber'
|
16
|
+
gem 'aruba'
|
17
|
+
gem 'rspec'
|
18
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Jens Krämer
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
# Srcset Image Tags for Middleman
|
2
|
+
|
3
|
+
## Prerequisites
|
4
|
+
|
5
|
+
Install [libvips](https://jcupitt.github.io/libvips) 8.6 or higher.
|
6
|
+
Unfortunately on Debian that means compiling it from source.
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
Add the gem to your site's Gemfile.
|
11
|
+
|
12
|
+
~~~~
|
13
|
+
gem 'middleman-srcset_images', github: 'jkraemer/middleman-srcset_images'
|
14
|
+
~~~~
|
15
|
+
|
16
|
+
Create a configuration file as outlined below.
|
17
|
+
|
18
|
+
In your Markdown files, use this syntax for images:
|
19
|
+
|
20
|
+
~~~
|
21
|
+
![Alt text](/path/to/image.jpg!half)
|
22
|
+
~~~
|
23
|
+
|
24
|
+
where _half_ is one of the configured image sizes (see below). The result will
|
25
|
+
be an image tag with _srcset_ and _sizes_ attributes. To create a linked image,
|
26
|
+
add another exclamation mark followed by the destination URL:
|
27
|
+
|
28
|
+
~~~
|
29
|
+
![Linked Image](/path/to/image.jpg!half!/path/or/url)
|
30
|
+
~~~
|
31
|
+
|
32
|
+
|
33
|
+
Relative image paths are assumed to be local to the article, and the file
|
34
|
+
extension is assumed to be `jpg` if not present. So when using the 'one
|
35
|
+
directory per page' approach where you have a file layout like this:
|
36
|
+
|
37
|
+
~~~
|
38
|
+
source/
|
39
|
+
some-article.html.md
|
40
|
+
some-article/
|
41
|
+
image.jpg
|
42
|
+
another_image.jpg
|
43
|
+
~~~
|
44
|
+
|
45
|
+
you can significantly shorten your markup like this:
|
46
|
+
|
47
|
+
~~~
|
48
|
+
![Lorem Ipsum](image!full)
|
49
|
+
~~~
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
## Configuration
|
54
|
+
|
55
|
+
Configuration takes place in `data/srcset_images.yml`.
|
56
|
+
|
57
|
+
### Sizes
|
58
|
+
|
59
|
+
The keys in this hash are the sizes that can be used in Markdown / with the
|
60
|
+
`image_tag` helper. The value is put into the _sizes_ attribute of the
|
61
|
+
resulting `img` tag. The purpose of this attribute is to give the browser a hint
|
62
|
+
about how big this image will actually be rendered (relative to the screen
|
63
|
+
size) due to your CSS rules.
|
64
|
+
|
65
|
+
The sample below is for a site which can display content images in 3
|
66
|
+
different sizes, and that has a separate config for teaser images. On small
|
67
|
+
devices, all images are rendered at full width, while on larger devices, they
|
68
|
+
only take a fraction of the actual screen width. The separate _teaser_ config
|
69
|
+
is only there to allow for different cropping rules in the image versions
|
70
|
+
config.
|
71
|
+
|
72
|
+
### Image Versions
|
73
|
+
|
74
|
+
Configure scaling options for landscape and portrait images. These rules
|
75
|
+
determine which sizes of images will be created when building your site, and
|
76
|
+
also what goes into the `srcset` attribute of the `img` tag.
|
77
|
+
|
78
|
+
Besides the _landscape_ and _portrait_ keys, which act as fallbacks for images
|
79
|
+
of landscape or portrait dimensions, you can add any other keys here for
|
80
|
+
different use cases (i.e. cropping to a fixed xy ratio for teaser images as is
|
81
|
+
done in the sample below).
|
82
|
+
|
83
|
+
When rendering an `img` tag, the image version config to be used is picked as
|
84
|
+
follows:
|
85
|
+
|
86
|
+
- if there is a key matching the _size_ parameter, use this config. This would
|
87
|
+
be the case for _teaser_ images in the sample below.
|
88
|
+
- otherwise, check the layout of the image and pick the _portrait_ config if
|
89
|
+
the image is higher than wide, and the _landscape_ otherwise.
|
90
|
+
|
91
|
+
|
92
|
+
### Sample data/srcset\_images.yml
|
93
|
+
|
94
|
+
~~~~
|
95
|
+
|
96
|
+
---
|
97
|
+
|
98
|
+
sizes:
|
99
|
+
full: "(min-width: 768px) 90vw, 100vw"
|
100
|
+
half: "(min-width: 768px) 45vw, 100vw"
|
101
|
+
third: "(min-width: 768px) 30vw, 100vw"
|
102
|
+
teaser: "(min-width: 768px) 30vw, 100vw"
|
103
|
+
|
104
|
+
images: posts/**/*.jpg
|
105
|
+
# in case you have symlinked directories to your actual photos, use something
|
106
|
+
# like this:
|
107
|
+
# images: posts/**{,/*/**}/*.jpg
|
108
|
+
|
109
|
+
image_versions:
|
110
|
+
# configuration for landscape and square images
|
111
|
+
landscape:
|
112
|
+
quality: 80
|
113
|
+
srcset:
|
114
|
+
-
|
115
|
+
width: 2000
|
116
|
+
-
|
117
|
+
width: 1400
|
118
|
+
is_default: true
|
119
|
+
-
|
120
|
+
width: 800
|
121
|
+
-
|
122
|
+
width: 400
|
123
|
+
|
124
|
+
# portrait content images, cropped to 3:4
|
125
|
+
portrait:
|
126
|
+
quality: 80
|
127
|
+
crop: true
|
128
|
+
srcset:
|
129
|
+
-
|
130
|
+
height: 1800
|
131
|
+
width: 1350
|
132
|
+
-
|
133
|
+
height: 1200
|
134
|
+
width: 900
|
135
|
+
is_default: true
|
136
|
+
-
|
137
|
+
height: 800
|
138
|
+
width: 600
|
139
|
+
|
140
|
+
# teaser image, cropped to landscape 3:2
|
141
|
+
teaser:
|
142
|
+
crop: true
|
143
|
+
quality: 80
|
144
|
+
srcset:
|
145
|
+
-
|
146
|
+
width: 1800
|
147
|
+
height: 1200
|
148
|
+
-
|
149
|
+
width: 1200
|
150
|
+
height: 800
|
151
|
+
is_default: true
|
152
|
+
-
|
153
|
+
width: 600
|
154
|
+
height: 400
|
155
|
+
|
156
|
+
~~~~
|
157
|
+
|
158
|
+
License
|
159
|
+
-------
|
160
|
+
|
161
|
+
MIT, see LICENSE
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'cucumber/rake/task'
|
5
|
+
|
6
|
+
Cucumber::Rake::Task.new(:cucumber, 'Run features that should pass') do |t|
|
7
|
+
t.cucumber_opts = '--color --tags ~@wip --strict'
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'rake/clean'
|
11
|
+
|
12
|
+
task test: ['cucumber']
|
13
|
+
|
14
|
+
task default: :test
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'mini_magick'
|
3
|
+
|
4
|
+
module SrcsetImages
|
5
|
+
class CreateImageVersion
|
6
|
+
|
7
|
+
# CreateImageVersion.(source, destination, width: 800, height: 600, ...)
|
8
|
+
def self.call(*_)
|
9
|
+
new(*_).call
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(source_path, destination_path, options = {})
|
13
|
+
@source = source_path
|
14
|
+
@destination = destination_path
|
15
|
+
|
16
|
+
@width = options[:width]
|
17
|
+
@height = options[:height]
|
18
|
+
@crop = !!options[:crop]
|
19
|
+
|
20
|
+
@gravity = options.fetch :gravity, 'Center'
|
21
|
+
@quality = options.fetch :quality, 90
|
22
|
+
@ratio = options.fetch :ratio, 1
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def call
|
27
|
+
FileUtils.mkdir_p File.dirname(@destination)
|
28
|
+
image = MiniMagick::Image.open(@source)
|
29
|
+
if @crop
|
30
|
+
crop image
|
31
|
+
else
|
32
|
+
resize image
|
33
|
+
end
|
34
|
+
image.write @destination
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def resize(img)
|
41
|
+
img.combine_options do |cmd|
|
42
|
+
cmd.resize "#{@width}x#{@height}>"
|
43
|
+
trim_down cmd
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def crop(img)
|
48
|
+
cols, rows = img[:dimensions]
|
49
|
+
|
50
|
+
img.combine_options do |cmd|
|
51
|
+
if @width != cols || @height != rows
|
52
|
+
scale_x = @width/cols.to_f
|
53
|
+
scale_y = @height/rows.to_f
|
54
|
+
if scale_x >= scale_y
|
55
|
+
cols = (scale_x * (cols + 0.5)).round
|
56
|
+
rows = (scale_x * (rows + 0.5)).round
|
57
|
+
cmd.resize "#{cols}"
|
58
|
+
else
|
59
|
+
cols = (scale_y * (cols + 0.5)).round
|
60
|
+
rows = (scale_y * (rows + 0.5)).round
|
61
|
+
cmd.resize "x#{rows}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
cmd.gravity @gravity
|
65
|
+
cmd.background "rgba(255,255,255,0.0)"
|
66
|
+
cmd.extent "#{@width}x#{@height}" if cols != @width || rows != @height
|
67
|
+
trim_down cmd
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def trim_down(cmd)
|
72
|
+
cmd.strip
|
73
|
+
cmd.quality @quality
|
74
|
+
cmd.depth "8"
|
75
|
+
cmd.interlace "plane"
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SrcsetImages
|
2
|
+
module DimensionsPatch
|
3
|
+
|
4
|
+
# from
|
5
|
+
# https://github.com/planio-gmbh/html2odt/commit/36335201cb5440a5ba3ca05f9eb6d8ac556a98ca
|
6
|
+
# Default implemenation of IO#peek from GEM_PATH/dimensions-1.3.0/lib/dimensions/io.rb:
|
7
|
+
#
|
8
|
+
# def peek
|
9
|
+
# unless no_peeking?
|
10
|
+
# read(pos + 1024) while @reader.width.nil? && pos < 6144
|
11
|
+
# rewind
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# It had two problems:
|
16
|
+
#
|
17
|
+
# a) if the file is shorter than 6144 bytes, it would keep reading infinitely
|
18
|
+
# b) if the width can only be detected after the 6144 limit, it would not work
|
19
|
+
# as expected
|
20
|
+
#
|
21
|
+
# Now we keep reading the file, until we can determine a width or until
|
22
|
+
# there's nothing left to read.
|
23
|
+
#
|
24
|
+
def peek
|
25
|
+
return if no_peeking?
|
26
|
+
|
27
|
+
while read(pos + 1024) && @reader.width.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
rewind
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'middleman-core'
|
3
|
+
require 'middleman-srcset_images/view_helpers'
|
4
|
+
|
5
|
+
# Extension namespace
|
6
|
+
module SrcsetImages
|
7
|
+
class Extension < ::Middleman::Extension
|
8
|
+
|
9
|
+
option :cache_dir, 'tmp/srcset_images-cache', 'Directory (relative to project root) for cached image versions.'
|
10
|
+
|
11
|
+
helpers ViewHelpers
|
12
|
+
|
13
|
+
attr_reader :image_versions, :scaled_images, :sizes
|
14
|
+
|
15
|
+
def initialize(app, options_hash={}, &block)
|
16
|
+
# Call super to build options from the options_hash
|
17
|
+
super
|
18
|
+
|
19
|
+
# Require libraries only when activated
|
20
|
+
require 'middleman-srcset_images/img'
|
21
|
+
require 'middleman-srcset_images/version_resource'
|
22
|
+
require 'middleman-srcset_images/html_converter'
|
23
|
+
require 'middleman-srcset_images/srcset_config'
|
24
|
+
|
25
|
+
# set up your extension
|
26
|
+
# puts options.my_option
|
27
|
+
|
28
|
+
@config = app.data['srcset_images'] || {}
|
29
|
+
@image_versions = @config['image_versions'] || {}
|
30
|
+
@images = @config['images']
|
31
|
+
@sizes = @config['sizes'] || {}
|
32
|
+
@scaled_images = Hash.new{|h,k| h[k] = []}
|
33
|
+
|
34
|
+
puts "Image versions: #{image_versions.keys.join ", "}"
|
35
|
+
|
36
|
+
HtmlConverter.install
|
37
|
+
end
|
38
|
+
|
39
|
+
def after_configuration
|
40
|
+
FileUtils.mkdir_p options.cache_dir
|
41
|
+
end
|
42
|
+
|
43
|
+
def manipulate_resource_list(resources)
|
44
|
+
basedir = File.absolute_path(File.join(app.root, app.config[:source]))
|
45
|
+
Dir.chdir(basedir) do
|
46
|
+
versions = []
|
47
|
+
cache_dir = File.absolute_path(options.cache_dir, app.root)
|
48
|
+
|
49
|
+
configurations = image_versions.map do |name, config|
|
50
|
+
SrcsetImages::SrcsetConfig.new name, config, cache_dir: cache_dir
|
51
|
+
end
|
52
|
+
|
53
|
+
images = Dir.glob(@images).map{|f| SrcsetImages::Img.new(f)}
|
54
|
+
|
55
|
+
# loop over configurations for landscape, portrait, teasers
|
56
|
+
configurations.each do |config|
|
57
|
+
|
58
|
+
#loop over original image files
|
59
|
+
images.each do |img|
|
60
|
+
|
61
|
+
# loop over different image sizes of configuration
|
62
|
+
config.image_versions(img).each do |v|
|
63
|
+
v.app = app
|
64
|
+
v.prepare_image
|
65
|
+
@scaled_images[img.path] << v
|
66
|
+
versions << VersionResource.new(app.sitemap, v)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
versions.flatten!
|
72
|
+
versions.compact!
|
73
|
+
puts "added #{versions.size} image versions"
|
74
|
+
resources + versions
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# helpers do
|
79
|
+
# def a_helper
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'middleman-core/renderers/kramdown'
|
4
|
+
|
5
|
+
module SrcsetImages
|
6
|
+
module HtmlConverter
|
7
|
+
|
8
|
+
def self.install
|
9
|
+
unless Middleman::Renderers::MiddlemanKramdownHTML < self
|
10
|
+
Middleman::Renderers::MiddlemanKramdownHTML.prepend self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def convert_img(el, indent)
|
15
|
+
attrs = el.attr.dup
|
16
|
+
|
17
|
+
attrs['title'] ||= attrs['alt']
|
18
|
+
|
19
|
+
src = attrs.delete "src"
|
20
|
+
|
21
|
+
path, size, link_to = src.split(?!)
|
22
|
+
# default to jpg as image file extension
|
23
|
+
path += ".jpg" unless path =~ /\.[a-z]{3}\z/i
|
24
|
+
|
25
|
+
if link_to
|
26
|
+
attrs[:link] = link_to
|
27
|
+
end
|
28
|
+
|
29
|
+
if size
|
30
|
+
attrs[:size] = size
|
31
|
+
|
32
|
+
%{<div class="item #{size}">} + scope.image_tag(path, attrs) + "</div>"
|
33
|
+
else
|
34
|
+
scope.image_tag path, attrs
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,115 @@
|
|
1
|
+
#require 'middleman-srcset_images/create_image_version'
|
2
|
+
require 'middleman-srcset_images/vips_create_image_version'
|
3
|
+
|
4
|
+
module SrcsetImages
|
5
|
+
class ImageVersion
|
6
|
+
|
7
|
+
attr_reader :img, :resized_img_path, :config, :name, :width
|
8
|
+
attr_accessor :app
|
9
|
+
|
10
|
+
# resized_img_path is the wrong path here
|
11
|
+
# (posts/2013/08-17-kilimanjaro/bay_ls_0.jpg instead of
|
12
|
+
# 2013/08/kilimanjaro/bay_ls_0.jpg)
|
13
|
+
# but it does not seem to matter since this is apparently fixed by
|
14
|
+
# middleman itself through the VersionResource
|
15
|
+
def initialize(img, resized_img_path, config)
|
16
|
+
@img = img
|
17
|
+
@resized_img_path = resized_img_path
|
18
|
+
@config = config
|
19
|
+
|
20
|
+
@default_for_orientation = config[:name] == img.orientation
|
21
|
+
|
22
|
+
@width = config[:width]
|
23
|
+
@height = config[:height]
|
24
|
+
|
25
|
+
if @width.nil? && @height.nil?
|
26
|
+
raise ArgumentError, "need at least width or height!\nconfig was: #{config}"
|
27
|
+
end
|
28
|
+
|
29
|
+
ratio = config[:ratio] || img.xy_ratio
|
30
|
+
if @width.blank?
|
31
|
+
@width = (@height.to_f * ratio).to_i
|
32
|
+
end
|
33
|
+
if @height.blank?
|
34
|
+
@height = (@width.to_f / ratio).to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
@crop = config.fetch :crop, false
|
38
|
+
@quality = config.fetch :quality, 80
|
39
|
+
@cache_dir = config[:cache_dir]
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def img_path
|
44
|
+
img.rel_path
|
45
|
+
end
|
46
|
+
|
47
|
+
def default?
|
48
|
+
!!config[:is_default]
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_for_orientation?
|
52
|
+
@default_for_orientation
|
53
|
+
end
|
54
|
+
|
55
|
+
def base64_data
|
56
|
+
Base64.strict_encode64 render
|
57
|
+
end
|
58
|
+
|
59
|
+
def render
|
60
|
+
prepare_image
|
61
|
+
File.read cached_resized_img_abs_path
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
#def middleman_resized_abs_path
|
66
|
+
# #middleman_abs_path.gsub(img.filename, resized_image_name)
|
67
|
+
# File.join File.dirname(img.abs_path), resized_image_name
|
68
|
+
#end
|
69
|
+
|
70
|
+
#def middleman_abs_path
|
71
|
+
# img_path.start_with?('/') ? img_path : File.join(images_dir, img_path)
|
72
|
+
#end
|
73
|
+
|
74
|
+
def cached_resized_img_abs_path
|
75
|
+
File.join(@cache_dir, resized_img_path).split('.').tap { |a|
|
76
|
+
a.insert(-2, img.checksum)
|
77
|
+
}.join('.')
|
78
|
+
end
|
79
|
+
|
80
|
+
def prepare_image
|
81
|
+
unless cached_image_available?
|
82
|
+
save_cached_image
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def source_dir
|
89
|
+
File.absolute_path(app.config[:source], @app.root)
|
90
|
+
end
|
91
|
+
|
92
|
+
def images_dir
|
93
|
+
app.config[:images_dir]
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_dir
|
97
|
+
app.config[:build_dir]
|
98
|
+
end
|
99
|
+
|
100
|
+
def save_cached_image
|
101
|
+
FileUtils.mkdir_p(File.dirname(cached_resized_img_abs_path))
|
102
|
+
VipsCreateImageVersion.(
|
103
|
+
img.vips, cached_resized_img_abs_path,
|
104
|
+
width: @width, height: @height, quality: @quality, crop: @crop
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
def cached_image_available?
|
109
|
+
File.exist?(cached_resized_img_abs_path)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'dimensions'
|
2
|
+
require 'image_processing/vips'
|
3
|
+
require 'middleman-srcset_images/dimensions_patch'
|
4
|
+
|
5
|
+
module SrcsetImages
|
6
|
+
class Img
|
7
|
+
|
8
|
+
attr_reader :path, :abs_path, :width, :height
|
9
|
+
|
10
|
+
def initialize(path)
|
11
|
+
@path = path
|
12
|
+
@abs_path = Pathname(path).absolute? ? path : File.join(Dir.pwd, path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def orientation
|
16
|
+
landscape? ? 'landscape' : 'portrait'
|
17
|
+
end
|
18
|
+
|
19
|
+
def rel_path
|
20
|
+
@rel_path ||= Pathname(abs_path).relative_path_from(Pathname(Dir.pwd))
|
21
|
+
end
|
22
|
+
|
23
|
+
# true if landscape or square
|
24
|
+
def landscape?
|
25
|
+
xy_ratio >= 1
|
26
|
+
end
|
27
|
+
|
28
|
+
# true if portrait
|
29
|
+
def portrait?
|
30
|
+
xy_ratio < 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def vips
|
34
|
+
@vips ||= ImageProcessing::Vips.source(abs_path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def path_for_version(cfg_name, idx)
|
38
|
+
"#{File.dirname rel_path}/#{basename}_#{cfg_name}_#{idx}#{ext}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def basename
|
42
|
+
@basename ||= File.basename path, ext
|
43
|
+
end
|
44
|
+
|
45
|
+
def filename
|
46
|
+
File.basename path
|
47
|
+
end
|
48
|
+
|
49
|
+
def ext
|
50
|
+
@ext ||= File.extname path
|
51
|
+
end
|
52
|
+
|
53
|
+
def checksum
|
54
|
+
@checksum ||= Digest::SHA2.file(abs_path).hexdigest[0..16]
|
55
|
+
end
|
56
|
+
|
57
|
+
# TODO can get dimensions from vips?
|
58
|
+
def xy_ratio
|
59
|
+
@xy_ratio ||= begin
|
60
|
+
File.open(abs_path, 'rb') do |io|
|
61
|
+
Dimensions(io)
|
62
|
+
io.extend DimensionsPatch
|
63
|
+
@width, @height = io.dimensions
|
64
|
+
@width.to_f / @height
|
65
|
+
end
|
66
|
+
end
|
67
|
+
rescue
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'middleman-srcset_images/image_version'
|
2
|
+
|
3
|
+
module SrcsetImages
|
4
|
+
class SrcsetConfig
|
5
|
+
|
6
|
+
attr_reader :name, :config
|
7
|
+
|
8
|
+
def initialize(name, config, cache_dir:)
|
9
|
+
@name = name
|
10
|
+
@config = config
|
11
|
+
|
12
|
+
@base_config = {
|
13
|
+
name: name,
|
14
|
+
crop: config.fetch(:crop, false),
|
15
|
+
quality: config.fetch(:quality, 80),
|
16
|
+
cache_dir: cache_dir
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def image_versions(img)
|
21
|
+
result = []
|
22
|
+
|
23
|
+
if applies_to?(img)
|
24
|
+
|
25
|
+
|
26
|
+
config.srcset.each_with_index do |config, idx|
|
27
|
+
result << ImageVersion.new(
|
28
|
+
img,
|
29
|
+
img.path_for_version(name, idx),
|
30
|
+
@base_config.merge(config.symbolize_keys)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def applies_to?(img)
|
39
|
+
not ((name == 'landscape' && img.portrait?) or
|
40
|
+
(name == 'portrait' && img.landscape?))
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module SrcsetImages
|
2
|
+
class VersionResource < ::Middleman::Sitemap::Resource
|
3
|
+
def initialize(store, image_version)
|
4
|
+
super store, image_version.resized_img_path, image_version.cached_resized_img_abs_path
|
5
|
+
end
|
6
|
+
|
7
|
+
def ignored?
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
def template?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def binary?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SrcsetImages
|
4
|
+
module ViewHelpers
|
5
|
+
|
6
|
+
# options can be:
|
7
|
+
#
|
8
|
+
# size: pick an image version
|
9
|
+
# link: Set to an url to link to
|
10
|
+
#
|
11
|
+
def image_tag(path, options = {})
|
12
|
+
# allow for images in article directories to be referenced just by file name
|
13
|
+
unless path[?/]
|
14
|
+
page_path = current_page.path
|
15
|
+
dir = File.dirname page_path
|
16
|
+
path = File.join dir, File.basename(page_path, '.html'), path
|
17
|
+
end
|
18
|
+
|
19
|
+
# collect srcset info
|
20
|
+
options = options.dup
|
21
|
+
ext = app.extensions[:srcset_images]
|
22
|
+
rel_path = path.sub(/\A\/?/, "")
|
23
|
+
versions = nil
|
24
|
+
|
25
|
+
if size = options.delete(:size)
|
26
|
+
options[:sizes] = ext.sizes[size]
|
27
|
+
|
28
|
+
scaled_images = ext.scaled_images[rel_path]
|
29
|
+
versions = scaled_images.select{|v| v.name == size.to_s}
|
30
|
+
unless versions.any?
|
31
|
+
versions = scaled_images.select{|v| v.default_for_orientation?}
|
32
|
+
end
|
33
|
+
|
34
|
+
if versions.any?
|
35
|
+
path = (versions.detect{|v|v.default?} || versions.first).resized_img_path
|
36
|
+
options[:srcset] = versions.map { |v|
|
37
|
+
"#{v.resized_img_path} #{v.width}w"
|
38
|
+
}.join ", "
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
link = options.delete(:link)
|
43
|
+
img = super path, options
|
44
|
+
|
45
|
+
if link
|
46
|
+
link_to img, link
|
47
|
+
else
|
48
|
+
img
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'image_processing/vips'
|
3
|
+
|
4
|
+
module SrcsetImages
|
5
|
+
class VipsCreateImageVersion
|
6
|
+
|
7
|
+
# VipsCreateImageVersion.(source, destination, width: 800, height: 600, ...)
|
8
|
+
def self.call(*_)
|
9
|
+
new(*_).call
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(img, destination_path, options = {})
|
13
|
+
@source = if img.is_a?(String) || img.is_a?(Pathname)
|
14
|
+
ImageProcessing::Vips.source(img)
|
15
|
+
else
|
16
|
+
img
|
17
|
+
end
|
18
|
+
|
19
|
+
@destination = destination_path
|
20
|
+
|
21
|
+
@width = options[:width]
|
22
|
+
@height = options[:height]
|
23
|
+
@crop = !!options[:crop]
|
24
|
+
|
25
|
+
@quality = options.fetch :quality, 90
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def call
|
30
|
+
img = if @crop
|
31
|
+
@source.resize_to_fill @width, @height, crop: :attention
|
32
|
+
else
|
33
|
+
@source.resize_to_limit @width, @height
|
34
|
+
end
|
35
|
+
processed = img
|
36
|
+
.saver(strip: true, quality: @quality, interlace: true)
|
37
|
+
.call
|
38
|
+
|
39
|
+
FileUtils.mkdir_p File.dirname(@destination)
|
40
|
+
FileUtils.mv processed, @destination
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "middleman-srcset_images"
|
6
|
+
s.version = "0.2.0"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Jens Kraemer"]
|
9
|
+
s.email = ["jk@jkraemer.net"]
|
10
|
+
s.homepage = "https://github.com/jkraemer/middleman-srcset_images"
|
11
|
+
s.summary = %q{Responsive images for Middleman}
|
12
|
+
s.description = %q{Middleman plugin for automatic img tags with proper srcset attributes. You can configure any number of image size sets for different use cases (i.e. different image sizes for teasers, portrait and landscape images). Scaled images are generated using libvips.}
|
13
|
+
s.license = 'MIT'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
|
19
|
+
# The version of middleman-core your extension depends on
|
20
|
+
s.add_runtime_dependency("middleman-core", ["~> 4.2"])
|
21
|
+
|
22
|
+
# Additional dependencies
|
23
|
+
s.add_runtime_dependency("dimensions", ["~> 1.3"])
|
24
|
+
s.add_runtime_dependency("image_processing", ["~> 1.0"])
|
25
|
+
s.add_runtime_dependency("ruby-vips", ["~> 2.0"])
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: middleman-srcset_images
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jens Kraemer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: middleman-core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dimensions
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: image_processing
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '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: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: ruby-vips
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
69
|
+
description: Middleman plugin for automatic img tags with proper srcset attributes.
|
70
|
+
You can configure any number of image size sets for different use cases (i.e. different
|
71
|
+
image sizes for teasers, portrait and landscape images). Scaled images are generated
|
72
|
+
using libvips.
|
73
|
+
email:
|
74
|
+
- jk@jkraemer.net
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- ".gitignore"
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- lib/middleman-srcset_images.rb
|
85
|
+
- lib/middleman-srcset_images/create_image_version.rb
|
86
|
+
- lib/middleman-srcset_images/dimensions_patch.rb
|
87
|
+
- lib/middleman-srcset_images/extension.rb
|
88
|
+
- lib/middleman-srcset_images/html_converter.rb
|
89
|
+
- lib/middleman-srcset_images/image_version.rb
|
90
|
+
- lib/middleman-srcset_images/img.rb
|
91
|
+
- lib/middleman-srcset_images/srcset_config.rb
|
92
|
+
- lib/middleman-srcset_images/version_resource.rb
|
93
|
+
- lib/middleman-srcset_images/view_helpers.rb
|
94
|
+
- lib/middleman-srcset_images/vips_create_image_version.rb
|
95
|
+
- middleman-srcset_images.gemspec
|
96
|
+
homepage: https://github.com/jkraemer/middleman-srcset_images
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.7.3
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Responsive images for Middleman
|
120
|
+
test_files: []
|