jekyll-images 0.1.0 → 0.2.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 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