jekyll-images 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07b509e1549cea19f69feb3dfc28a428ea64b28c671e4676293189948dbf898c
4
- data.tar.gz: c53c72a3b38da604f56c483e3f7ab196053b87f7fe2bf1ee4e7ff21ce08aaf0a
3
+ metadata.gz: b16d950e3f192f849e1577a16fc572c9bb698604de4bf31a64b1c62285ac00ef
4
+ data.tar.gz: 1072440b92d9e2351a799100d1aef666fe14441bc0d52c93ac40ed71eba14cfb
5
5
  SHA512:
6
- metadata.gz: 4a4385f6cd05bb4a0cfdd1f801a3a7271216b935f1a8c1442c6ddf5bd47918b06c9a7229acd1c84778168854eff87cba6c4bb5dc344b32747604a50b0afe26e9
7
- data.tar.gz: c216720295925557a364e0a57ddd22cb85e298adb220f95981c67e291a617ed4fb62b177517755a11e52dc3bea5d69e7f4d2631f8a98620a97909af8606c10df
6
+ metadata.gz: 6c87cae74ddaacce079c5a192af9c1e1315a198176730a8b6f0b82dbf5e1868c0cc179ef191519ee502c6c839928ea56339a2a212099c87a21f53cd69c6c14ae
7
+ data.tar.gz: a89803b7bc464ab3b1a27a135946406814016ca61374ad7c904c93144369d912249b06f6b1e32cbc3c988e1ecda7515cc0ecce0cea8f4524ee87a58a80479a66
data/README.md CHANGED
@@ -1,15 +1,27 @@
1
- # Jekyll::Image::Optimization
1
+ # Image optimization for Jekyll
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/jekyll/image/optimization`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ This Jekyll plugin helps you optimize images by thumbnailing them to
4
+ the sizes you specify in templates and in the configuration for images
5
+ in posts and collections.
4
6
 
5
- TODO: Delete this and the text above, and describe your gem
7
+ It uses [Libvips](https://libvips.github.io/libvips/) for performance
8
+ and low resource usage. It'll also cache the thumbnails so it only runs
9
+ once, but it'll update the thumbnail if the source image changes (or is
10
+ `touch`ed).
11
+
12
+ The thumbnails are smartly cropped by default to point of attention, so
13
+ they're more useful than just cropping at the center, but you can change
14
+ this (see below).
15
+
16
+ It will run `jpegoptim` and `oxipng` if it can find the binaries
17
+ installed for extra optimization.
6
18
 
7
19
  ## Installation
8
20
 
9
- Add this line to your application's Gemfile:
21
+ Add this line to your site's Gemfile:
10
22
 
11
23
  ```ruby
12
- gem 'jekyll-image-optimization'
24
+ gem 'jekyll-images'
13
25
  ```
14
26
 
15
27
  And then execute:
@@ -18,26 +30,86 @@ And then execute:
18
30
 
19
31
  Or install it yourself as:
20
32
 
21
- $ gem install jekyll-image-optimization
33
+ $ gem install jekyll-images
22
34
 
23
35
  ## Usage
24
36
 
25
- TODO: Write usage instructions here
37
+ ### Liquid templates
38
+
39
+ In your templates, you can use the `thumbnail` filter:
40
+
41
+ ```html
42
+ <img src="{{ page.image.path | thumbnail: 100 }}" />
43
+
44
+ <!-- responsive images -->
45
+
46
+ <picture>
47
+ {% for size in site.images.sizes %}
48
+ <source srcset="{{ page.image.path | thumbnail: size }}" media="(max-width: {{ size }}px)" />
49
+ {% endfor %}
50
+
51
+ <img src="{{ page.image.path | thumbnail: 750 }}" />
52
+ </picture>
53
+ ```
54
+
55
+ Options for this filter are in the following order:
26
56
 
27
- ## Development
57
+ * `width` (required), the desired width for the image
58
+
59
+ * `height`, if provided, the thumbnail will crop to this size. If not,
60
+ the image is scaled down proportionally to the width.
61
+
62
+ * `crop` the smart cropping algorithm. One of `none`, `centre`,
63
+ `entropy` or `attention` (default). See
64
+ [Vips::Interesting](https://www.rubydoc.info/gems/ruby-vips/Vips/Interesting)
65
+ for documentation.
66
+
67
+ * `auto_rotate`, a boolean (defaults to `true`). If the image has
68
+ orientation metadata, this controls if it's automatically rotated.
69
+
70
+ If you want to pass `crop` and `auto_rotate` but not `height`, just set
71
+ `height` to `0` or `''`.
72
+
73
+ ### Configuration and posts
74
+
75
+ Images in a post content need to be configured globally:
76
+
77
+ ```yaml
78
+ # _config.yml
79
+ images:
80
+ # These are bootstrap4 breakpoints in pixels
81
+ sizes:
82
+ - 576
83
+ - 768
84
+ - 992
85
+ - 1200
86
+ thumbnail:
87
+ width: 600
88
+ height: 0 # Leave this out for proportional thumbnailing
89
+ crop: attention # See Vips::Interesting
90
+ auto_rotate: true
91
+ ```
28
92
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
93
+ ## TODO
30
94
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
95
+ * Use `<picture>` and the `srcset` attribute automatically for posts
96
+ <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture>
32
97
 
33
98
  ## Contributing
34
99
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jekyll-image-optimization. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
100
+ Bug reports and pull requests are welcome on GitHub at
101
+ <https://0xacab.org/sutty/jekyll/jekyll-images>. This project is
102
+ intended to be a welcoming space for collaboration, and contributors are
103
+ expected to adhere to the [Sutty code of
104
+ conduct](https://sutty.nl/en/code-of-conduct/).
36
105
 
37
106
  ## License
38
107
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
108
+ The gem is available as free software under the terms of the GPL3
109
+ License.
40
110
 
41
111
  ## Code of Conduct
42
112
 
43
- Everyone interacting in the Jekyll::Image::Optimization project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/jekyll-image-optimization/blob/master/CODE_OF_CONDUCT.md).
113
+ Everyone interacting in the jekyll-images project’s codebases, issue
114
+ trackers, chat rooms and mailing lists is expected to follow the [code
115
+ of conduct](https://sutty.nl/en/code-of-conduct/).
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.require_paths = ['lib']
32
32
 
33
33
  spec.add_dependency 'ruby-vips', '~> 2'
34
+ spec.add_dependency 'ruby-filemagic', '~> 0.7'
34
35
 
35
36
  spec.add_development_dependency 'bundler', '~> 2.0'
36
37
  spec.add_development_dependency 'minitest', '~> 5.0'
data/lib/jekyll-images.rb CHANGED
@@ -1,4 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pry'
3
4
  require_relative 'jekyll/images/thumbnail'
4
5
  require_relative 'jekyll/filters/thumbnail'
6
+ require_relative 'jekyll/hooks/thumbnail'
7
+ require_relative 'jekyll/images/oxipng'
8
+ require_relative 'jekyll/images/jpeg_optim'
@@ -4,15 +4,21 @@ module Jekyll
4
4
  module Filters
5
5
  # Liquid filter for use in templates
6
6
  module Thumbnail
7
- # Generates a thumbnail and returns its destination
8
- def thumbnail(input, width, height, crop = :attention, auto_rotate = true)
9
- image = Jekyll::Images::Thumbnail.new(input,
7
+ # Generates a thumbnail and returns its alternate destination
8
+ def thumbnail(input, width, height = nil, crop = :attention, auto_rotate = true)
9
+ return unless input
10
+
11
+ height = height if height.to_i > 1
12
+ image = Jekyll::Images::Thumbnail.new(@context.registers[:site],
13
+ input,
10
14
  width: width,
11
15
  height: height,
12
16
  crop: crop,
13
17
  auto_rotate: auto_rotate)
14
- image.write
15
18
 
19
+ # XXX: This won't run optimizations more than once but it won't
20
+ # also optimize source images.
21
+ image.write && image.optimize
16
22
  image.dest
17
23
  end
18
24
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'runner'
4
+
5
+ module Jekyll
6
+ module Images
7
+ # Runs jpegoptim on JPEG files
8
+ class JpegOptim < Runner
9
+ BINARY = 'jpegoptim'.freeze
10
+
11
+ def command
12
+ [binary, '--strip-all', '--quiet', file]
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ Jekyll::Images::Runner.register('jpeg', Jekyll::Images::JpegOptim)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'runner'
4
+
5
+ module Jekyll
6
+ module Images
7
+ # Runs oxipng on PNG files
8
+ class Oxipng < Runner
9
+ BINARY = 'oxipng'.freeze
10
+
11
+ def command
12
+ [binary, '--strip', 'all', '--quiet', file]
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ Jekyll::Images::Runner.register('png', Jekyll::Images::Oxipng)
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'filemagic'
5
+
6
+ module Jekyll
7
+ module Images
8
+ # Runner for optimizations
9
+ class Runner
10
+ attr_reader :binary, :bytes_after, :bytes_before, :file
11
+
12
+ class << self
13
+ # XXX: This only allows one optimization per file type, we could
14
+ # make it an array but how do we garantee order? Maybe adding a
15
+ # priority field?
16
+ def register(mime, class_name)
17
+ @@runners ||= {}
18
+ @@runners[mime] = class_name
19
+ end
20
+
21
+ def runners
22
+ @@runners
23
+ end
24
+
25
+ def mime(file)
26
+ @@mime ||= FileMagic.new
27
+
28
+ @@mime.file(file, true)
29
+ end
30
+
31
+ def run(file)
32
+ type = mime(file)
33
+ runners[type].new(file).run if runners[type]
34
+ end
35
+ end
36
+
37
+ def initialize(file)
38
+ @file = file
39
+ @bytes_before = bytes_after
40
+ end
41
+
42
+ def bytes_after
43
+ File.size file
44
+ end
45
+
46
+ def exist?
47
+ @binary, _, status = Open3.capture3("which #{self.class::BINARY}")
48
+ @binary.chomp!
49
+
50
+ status.success?
51
+ end
52
+
53
+ def run
54
+ return unless exist?
55
+
56
+ _, status = Open3.capture2e(*command)
57
+
58
+ [bytes_before, bytes_after] if status.success?
59
+ end
60
+ end
61
+ end
62
+ end
@@ -8,38 +8,76 @@ module Jekyll
8
8
  #
9
9
  # We're assuming every image is going to be thumbnailed
10
10
  class Thumbnail
11
- attr_reader :image, :filename, :width, :height
11
+ attr_reader :site, :image, :thumbnail, :filename, :width, :height
12
12
 
13
- def initialize(filename, **args)
13
+ def initialize(site, filename, **args)
14
14
  unless File.exist? filename
15
15
  raise ArgumentError, "File not found: #{filename}"
16
16
  end
17
- raise ArgumentError, 'Missing width' unless width
18
- raise ArgumentError, 'Missing height' unless height
17
+ raise ArgumentError, 'Missing width' unless args[:width]
19
18
 
20
- @image = Image::Vips.thumbnail @filename, args[:width],
21
- height: args[:height],
22
- auto_rotate: args[:auto_rotate],
23
- crop: args[:crop].to_sym
19
+ @site = site
20
+ @filename = filename
21
+ @width = args[:width]
22
+ @height = args[:height]
23
+ @image = Vips::Image.new_from_file filename, access: :sequential
24
+ @thumbnail = image.thumbnail_image width,
25
+ height: height || proportional_height,
26
+ auto_rotate: args[:auto_rotate],
27
+ crop: args[:crop].to_sym
24
28
  end
25
29
 
26
- # Generates a destination from filename
30
+ # Finds a height that's proportional to the width
31
+ def proportional_height
32
+ @height = (image.height * (width / image.width.to_f)).round
33
+ end
34
+
35
+ # Generates a destination from filename only if we're downsizing
27
36
  def dest
28
- filename.gsub(/#{extname}\z/, "_#{height}x#{width}#{extname}")
37
+ @dest ||= if image.width > width
38
+ filename.gsub(/#{extname}\z/, "_#{width}x#{height}#{extname}")
39
+ else
40
+ Jekyll.logger.info "Not thumbnailing #{filename}"
41
+ filename
42
+ end
29
43
  end
30
44
 
31
45
  def extname
32
46
  @extname ||= File.extname filename
33
47
  end
34
48
 
35
- # Only write when the origin is newer than destination
49
+ # Only write when the source is newer than the thumbnail
36
50
  def write?
37
- File.mtime(filename) > File.mtime(dest)
51
+ !File.exist?(dest) || File.mtime(filename) > File.mtime(dest)
38
52
  end
39
53
 
40
- # Save the file into destination if needed
54
+ # Save the file into destination if needed and add to files to
55
+ # copy
41
56
  def write
42
- image.write_to_file(dest) if write?
57
+ return unless write?
58
+
59
+ Jekyll.logger.info "Thumbnailing #{filename} => #{dest}"
60
+ thumbnail.write_to_file(dest)
61
+
62
+ # Add it to the static files so Jekyll copies them. Once they
63
+ # are written they're copied when the site is loaded.
64
+ site.static_files << Jekyll::StaticFile.new(site, site.source,
65
+ File.dirname(dest),
66
+ File.basename(dest))
67
+
68
+ # The file was updated, so it exists and is newer than source
69
+ !write?
70
+ end
71
+
72
+ # Run optimizations
73
+ def optimize
74
+ before, after = Runner.run(dest)
75
+
76
+ return unless before
77
+
78
+ pct = ((after.to_f / before) * -100 + 100).round(2)
79
+
80
+ Jekyll.logger.info "Reduced #{filename} from #{before} to #{after} bytes (%#{pct})"
43
81
  end
44
82
  end
45
83
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module Images
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-images
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - f
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-19 00:00:00.000000000 Z
11
+ date: 2020-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-vips
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-filemagic
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.7'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -98,6 +112,9 @@ files:
98
112
  - jekyll-images.gemspec
99
113
  - lib/jekyll-images.rb
100
114
  - lib/jekyll/filters/thumbnail.rb
115
+ - lib/jekyll/images/jpeg_optim.rb
116
+ - lib/jekyll/images/oxipng.rb
117
+ - lib/jekyll/images/runner.rb
101
118
  - lib/jekyll/images/thumbnail.rb
102
119
  - lib/jekyll/images/version.rb
103
120
  homepage: https://0xacab.org/sutty/jekyll/jekyll-images