├── .coveralls.yml ├── .gitattributes ├── .github └── stale.yml ├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── compositor.json ├── features ├── extra-image-generation.feature ├── image-generation.feature ├── image-hashes.feature ├── plugin.feature ├── responsive-image-block.feature ├── responsive-image-tag.feature ├── save-to-source.feature ├── step_definitions │ └── jekyll_steps.rb ├── support │ ├── env.rb │ └── hooks.rb └── test-site │ ├── _includes │ ├── base-url.html │ ├── custom-template.html │ ├── hash.html │ └── responsive-image.html │ └── assets │ ├── everybody-loves-jalapeño-pineapple-cornbread.png │ ├── exif-rotation.jpeg │ ├── progressive.jpeg │ └── subdir │ └── test.png ├── jekyll-responsive-image.gemspec ├── lib ├── jekyll-responsive-image.rb └── jekyll-responsive-image │ ├── block.rb │ ├── config.rb │ ├── extra_image_generator.rb │ ├── image_processor.rb │ ├── render_cache.rb │ ├── renderer.rb │ ├── resize_handler.rb │ ├── tag.rb │ ├── utils.rb │ └── version.rb └── sample-templates ├── imager-js.html ├── picture.html ├── srcset-resized-fallback.html └── srcset.html /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | features/test-site/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | daysUntilStale: 18 2 | daysUntilClose: 3 3 | staleLabel: stale 4 | exemptLabels: 5 | - pinned 6 | markComment: > 7 | This issue has been automatically marked as stale because it has not had 8 | recent activity. It will be closed if no further activity occurs. Thank you 9 | for your contributions. 10 | closeComment: false 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | Gemfile.lock 3 | jekyll-responsive_image-*.gem 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: ruby 4 | bundler_args: --without debug 5 | before_script: bundle exec jekyll --version 6 | script: bundle exec rake features_with_coveralls 7 | rvm: 8 | - '2.6' 9 | - '2.5' 10 | - '2.4' 11 | - '2.3' 12 | - '2.2' 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org/' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'rake' 7 | gem 'cucumber', '~> 3.1' 8 | gem 'test-unit', '~> 3.2' 9 | 10 | gem 'simplecov', :require => false 11 | gem 'coveralls', :require => false 12 | end 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Joseph Wynn 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jekyll-responsive-image 2 | 3 | A [Jekyll](http://jekyllrb.com/) plugin for automatically resizing images. Fully configurable and unopinionated, jekyll-responsive-image allows you to display responsive images however you like: using [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Img#attr-srcset), [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture), or [Imager.js](https://github.com/BBC-News/Imager.js/). 4 | 5 | [![Build Status](https://img.shields.io/travis/wildlyinaccurate/jekyll-responsive-image.svg?style=flat-square)](https://travis-ci.org/wildlyinaccurate/jekyll-responsive-image) 6 | [![Coverage Status](https://img.shields.io/coveralls/wildlyinaccurate/jekyll-responsive-image.svg?style=flat-square)](https://coveralls.io/repos/github/wildlyinaccurate/jekyll-responsive-image/badge.svg?branch=master) 7 | 8 | ## Installation 9 | 10 | This plugin can be installed in three steps: 11 | 12 | ### 1. Install the gem 13 | 14 | Either add `jekyll-responsive-image` to your Gemfile, or run the following command to install the gem: 15 | 16 | ``` 17 | $ gem install jekyll-responsive-image 18 | ``` 19 | 20 | Then you can either add `jekyll-responsive-image` to the `plugins` section of your `_config.yml`: 21 | 22 | ```yaml 23 | plugins: 24 | - jekyll-responsive-image 25 | ``` 26 | Note: If you are using a Jekyll version less than 3.5.0, use the `gems` key instead of `plugins`. 27 | 28 | Or you can copy the contents of [`responsive_image.rb`](lib/jekyll-responsive-image.rb) into your `_plugins` directory. 29 | 30 | ### 2. Create an image template file 31 | 32 | You will need to create a template in order to use the `responsive_image` and `responsive_image_block` tags. Normally the template lives in your `_includes/` directory. Not sure where to start? [Take a look at the sample templates](sample-templates). 33 | 34 | For more advanced templates, see the [**Templates**](#templates) section below. 35 | 36 | ### 3. Configure the plugin 37 | 38 | You **must** have a `responsive_image` block in your `_config.yml` for this plugin to work. At a minimum, your `responsive_image` configuration should have a template path and a list of sizes. 39 | 40 | ```yaml 41 | responsive_image: 42 | template: _includes/responsive-image.html 43 | sizes: 44 | - width: 320 45 | - width: 480 46 | - width: 800 47 | ``` 48 | 49 | For a list of all the available configuration options, see the [**All configuration options**](#all-configuration-options) section below. 50 | 51 | ## Usage 52 | 53 | Replace your images with the `responsive_image` tag, specifying the path to the image in the `path` attribute. 54 | 55 | ```twig 56 | {% responsive_image path: assets/my-file.jpg %} 57 | ``` 58 | 59 | You can override the template on a per-image basis by specifying the `template` attribute. 60 | 61 | ```twig 62 | {% responsive_image path: assets/my-file.jpg template: _includes/another-template.html %} 63 | ``` 64 | 65 | Any extra attributes will be passed straight to the template as variables. 66 | 67 | ```twig 68 | {% responsive_image path: assets/image.jpg alt: "Lorem ipsum..." title: "Lorem ipsum..." %} 69 | ``` 70 | 71 | ### Liquid variables as attributes 72 | 73 | You can use Liquid variables as attributes with the `responsive_image_block` tag. This tag works in exactly the same way as the `responsive_image` tag, but is implemented as a block tag to allow for more complex logic. 74 | 75 | > **Important!** The attributes in the `responsive_image_block` tag are parsed as YAML, so whitespace and indentation are significant! 76 | 77 | ```twig 78 | {% assign path = 'assets/test.png' %} 79 | {% assign alt = 'Lorem ipsum...' %} 80 | 81 | {% responsive_image_block %} 82 | path: {{ path }} 83 | alt: {{ alt }} 84 | {% if title %} 85 | title: {{ title }} 86 | {% endif %} 87 | {% endresponsive_image_block %} 88 | ``` 89 | 90 | ## Templates 91 | 92 | It's easy to build your own custom templates to render images however you want using the template variables provided by jekyll-responsive-image. 93 | 94 | ### Template Variables 95 | 96 | The following variables are available in the template: 97 | 98 | | Variable | Type | Description | 99 | |------------|---------------|------------------------------------------------------------------------------------------------------| 100 | | `path` | String | The path of the unmodified image. This is always the same as the `path` attribute passed to the tag. | 101 | | `resized` | Array | An array of all the resized images. Each image is an **Image Object**. | 102 | | `original` | Object | An **Image Object** containing information about the original image. | 103 | | `*` | String | Any other attributes will be passed to the template verbatim as strings (see below). | 104 | 105 | Any other attributes that are given to the `responsive_image` or `responsive_image_block` tags will be available in the template. For example the following tag will provide an `{{ alt }}` variable to the template: 106 | 107 | ```twig 108 | {% responsive_image path: assets/my-file.jpg alt: "A description of the image" %} 109 | ``` 110 | 111 | #### Image Objects 112 | 113 | Image objects (like `original` and each object in `resized`) contain the following properties: 114 | 115 | | Variable | Type | Description | 116 | |-------------|---------|----------------------------------------------------------------------------------------------| 117 | | `path` | String | The path to the image. | 118 | | `width` | Integer | The width of the image. | 119 | | `height` | Integer | The height of the image. | 120 | | `basename` | String | Basename of the file (`assets/some-file.jpg` => `some-file.jpg`). | 121 | | `dirname` | String | Directory of the file relative to `base_path` (`assets/sub/dir/some-file.jpg` => `sub/dir`). | 122 | | `filename` | String | Basename without the extension (`assets/some-file.jpg` => `some-file`). | 123 | | `extension` | String | Extension of the file (`assets/some-file.jpg` => `jpg`). | 124 | 125 | ## All configuration options 126 | 127 | A full list of all of the available configuration options is below. 128 | 129 | ```yaml 130 | responsive_image: 131 | # [Required] 132 | # Path to the image template. 133 | template: _includes/responsive-image.html 134 | 135 | # [Optional, Default: 85] 136 | # Quality to use when resizing images. 137 | default_quality: 90 138 | 139 | # [Optional, Default: []] 140 | # An array of resize configuration objects. Each object must contain at least 141 | # a `width` value. 142 | sizes: 143 | - width: 480 # [Required] How wide the resized image will be. 144 | quality: 80 # [Optional] Overrides default_quality for this size. 145 | - width: 800 146 | - width: 1400 147 | quality: 90 148 | 149 | # [Optional, Default: false] 150 | # Rotate resized images depending on their EXIF rotation attribute. Useful for 151 | # working with JPGs directly from digital cameras and smartphones 152 | auto_rotate: false 153 | 154 | # [Optional, Default: false] 155 | # Strip EXIF and other JPEG profiles. Helps to minimize JPEG size and win friends 156 | # at Google PageSpeed. 157 | strip: false 158 | 159 | # [Optional, Default: assets] 160 | # The base directory where assets are stored. This is used to determine the 161 | # `dirname` value in `output_path_format` below. 162 | base_path: assets 163 | 164 | # [Optional, Default: assets/resized/%{filename}-%{width}x%{height}.%{extension}] 165 | # The template used when generating filenames for resized images. Must be a 166 | # relative path. 167 | # 168 | # Parameters available are: 169 | # %{dirname} Directory of the file relative to `base_path` (assets/sub/dir/some-file.jpg => sub/dir) 170 | # %{basename} Basename of the file (assets/some-file.jpg => some-file.jpg) 171 | # %{filename} Basename without the extension (assets/some-file.jpg => some-file) 172 | # %{extension} Extension of the file (assets/some-file.jpg => jpg) 173 | # %{width} Width of the resized image 174 | # %{height} Height of the resized image 175 | # 176 | output_path_format: assets/resized/%{width}/%{basename} 177 | 178 | # [Optional, Default: true] 179 | # Whether or not to save the generated assets into the source folder. 180 | save_to_source: false 181 | 182 | # [Optional, Default: false] 183 | # Cache the result of {% responsive_image %} and {% responsive_image_block %} 184 | # tags. See the "Caching" section of the README for more information. 185 | cache: false 186 | 187 | # [Optional, Default: []] 188 | # By default, only images referenced by the responsive_image and responsive_image_block 189 | # tags are resized. Here you can set a list of paths or path globs to resize other 190 | # images. This is useful for resizing images which will be referenced from stylesheets. 191 | extra_images: 192 | - assets/foo/bar.png 193 | - assets/bgs/*.png 194 | - assets/avatars/*.{jpeg,jpg} 195 | ``` 196 | 197 | ## Troubleshooting 198 | 199 | ### Error: Can't install RMagick 200 | 201 | `jekyll-responsive-image` uses `rmagick` which is currently incompatible with ImageMagick 7. If you get an error like: 202 | 203 | ``` 204 | Can't install RMagick 2.16.0. Can't find MagickWand.h 205 | ``` 206 | 207 | Then you will need to install ImageMagick 6. If you are using Homebrew on Mac OS, this can be done with the following commands: 208 | 209 | ``` 210 | $ brew uninstall imagemagick 211 | $ brew install imagemagick@6 && brew link imagemagick@6 --force 212 | ``` 213 | 214 | ## Caching 215 | 216 | You may be able to speed up the build of large sites by enabling render caching. This is usually only effective when the same image is used many times, for example a header image that is rendered in every post. 217 | 218 | The recommended way to enable caching is on an image-by-image basis, by adding `cache: true` to the tag: 219 | 220 | ```twig 221 | {% responsive_image path: 'assets/my-file.jpg' cache: true %} 222 | 223 | {% responsive_image_block %} 224 | path: assets/my-file.jpg 225 | cache: true 226 | {% endresponsive_image_block %} 227 | ``` 228 | 229 | You can also enable it for all images by adding `cache: true` to your `_config.yml`: 230 | 231 | ```yaml 232 | responsive_image: 233 | cache: true 234 | template: _includes/responsive-image.html 235 | sizes: 236 | - ... 237 | ``` 238 | 239 | ## Development 240 | 241 | If you'd like to contribute to this repository, here's how you can set it up for development: 242 | 243 | 1. Fork this repository 244 | 2. Clone the fork to your local machine 245 | 3. Install [ImageMagick](http://www.imagemagick.org/) (if you haven't already) 246 | 4. Run `bundle install` 247 | 5. Run the tests like this: `cucumber` 248 | 249 | If you'd like your Jekyll project to use your local fork directly, you can add the `:path` parameter to your gem command in the project's Gemfile: 250 | 251 | ```ruby 252 | gem 'jekyll-responsive-image', :path => "/your/local/path/to/jekyll-responsive-image" 253 | ``` 254 | 255 | If you'd like your changes to be considered for the original repository, simply submit a pull request after you've made your changes. Please make sure all tests pass. 256 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | 3 | begin 4 | Bundler.setup(:default, :development) 5 | rescue Bundler::BundlerError => e 6 | $stderr.puts e.message 7 | $stderr.puts "Run `bundle install` to install missing gems" 8 | exit e.status_code 9 | end 10 | 11 | require 'rake' 12 | require 'jekyll-responsive-image/version' 13 | require 'cucumber/rake/task' 14 | require 'coveralls/rake/task' 15 | 16 | Cucumber::Rake::Task.new(:features) 17 | 18 | Coveralls::RakeTask.new 19 | task :features_with_coveralls => [:features, 'coveralls:push'] 20 | 21 | task :default => [:features] 22 | 23 | task :release do |t| 24 | system "gem build jekyll-responsive-image.gemspec" 25 | system "git tag v#{Jekyll::ResponsiveImage::VERSION} -a -m 'Tagged release of jekyll-responsive-image-#{Jekyll::ResponsiveImage::VERSION}.gem'" 26 | system "git push --tags" 27 | system "gem push jekyll-responsive-image-#{Jekyll::ResponsiveImage::VERSION}.gem" 28 | end 29 | -------------------------------------------------------------------------------- /compositor.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wildlyinaccurate/jekyll-responsive-image", 3 | "version": "0.1.4", 4 | "libraries": { 5 | "xv": "^1.1.21" 6 | }, 7 | "title": "", 8 | "branch": "master", 9 | "style": { 10 | "name": "Default", 11 | "componentSet": { 12 | "nav": "nav/BasicNav", 13 | "header": "header/BannerHeader", 14 | "article": "article/BasicArticle", 15 | "footer": "footer/BasicFooter" 16 | }, 17 | "fontFamily": "-apple-system, BlinkMacSystemFont, sans-serif", 18 | "fontWeight": 400, 19 | "bold": 600, 20 | "lineHeight": 1.5, 21 | "typeScale": [ 22 | 72, 23 | 48, 24 | 24, 25 | 20, 26 | 16, 27 | 14, 28 | 12 29 | ], 30 | "monospace": "Menlo, monospace", 31 | "heading": { 32 | "fontFamily": null, 33 | "fontStyle": null, 34 | "fontWeight": 600, 35 | "lineHeight": 1.25, 36 | "textTransform": null, 37 | "letterSpacing": null, 38 | "h0": {}, 39 | "h1": {}, 40 | "h2": {}, 41 | "h3": {}, 42 | "h4": {}, 43 | "h5": {}, 44 | "h6": {} 45 | }, 46 | "alternativeText": {}, 47 | "space": [ 48 | 0, 49 | 8, 50 | 16, 51 | 32, 52 | 48, 53 | 64, 54 | 96 55 | ], 56 | "layout": { 57 | "maxWidth": 1024, 58 | "centered": false 59 | }, 60 | "colors": { 61 | "text": "#111", 62 | "background": "#fff", 63 | "primary": "#08e", 64 | "secondary": "#059", 65 | "highlight": "#e08", 66 | "border": "#ddd", 67 | "muted": "#eee" 68 | }, 69 | "border": { 70 | "width": 1, 71 | "radius": 2 72 | }, 73 | "link": {}, 74 | "button": { 75 | "hover": { 76 | "boxShadow": "inset 0 0 0 999px rgba(0, 0, 0, .125)" 77 | } 78 | }, 79 | "input": {}, 80 | "body": { 81 | "margin": 0 82 | }, 83 | "breakpoints": { 84 | "xs": "@media screen and (max-width:40em)", 85 | "sm": "@media screen and (min-width:40em)", 86 | "md": "@media screen and (min-width:52em)", 87 | "lg": "@media screen and (min-width:64em)" 88 | } 89 | }, 90 | "content": [ 91 | { 92 | "component": "nav", 93 | "links": [ 94 | { 95 | "href": "https://github.com/wildlyinaccurate/jekyll-responsive-image", 96 | "text": "GitHub" 97 | } 98 | ] 99 | }, 100 | { 101 | "component": "header", 102 | "heading": "jekyll-responsive-image", 103 | "subhead": "An unopinionated Jekyll plugin for generating and using responsive images", 104 | "children": [ 105 | { 106 | "component": "ui/TweetButton", 107 | "text": "jekyll-responsive-image: An unopinionated Jekyll plugin for generating and using responsive images", 108 | "url": "" 109 | }, 110 | { 111 | "component": "ui/GithubButton", 112 | "user": "wildlyinaccurate", 113 | "repo": "jekyll-responsive-image" 114 | } 115 | ] 116 | }, 117 | { 118 | "component": "article", 119 | "metadata": { 120 | "source": "github.readme" 121 | }, 122 | "html": "

Jekyll Responsive Images

\n

Jekyll Responsive Images is a Jekyll plugin and utility for automatically resizing images. Its intended use is for sites which want to display responsive images using something like srcset or Imager.js.

\n

\n\n

\n

Installation

\n

First, install the gem:

\n
$ gem install jekyll-responsive_image

Then you can either add it to the gems section of your _config.yml:

\n
gems: [jekyll-responsive-image]

Or you can copy the contents of responsive_image.rb into your _plugins directory.

\n

Configuration

\n

An example configuration is below.

\n
responsive_image:\n  # [Required]\n  # Path to the image template.\n  template: _includes/responsive-image.html\n\n  # [Optional, Default: 85]\n  # Quality to use when resizing images.\n  default_quality: 90\n\n  # [Optional, Default: []]\n  # An array of resize configuration objects. Each object must contain at least\n  # a `width` value.\n  sizes:\n    - width: 480  # [Required] How wide the resized image will be.\n      quality: 80 # [Optional] Overrides default_quality for this size.\n    - width: 800\n    - width: 1400\n      quality: 90\n\n  # [Optional, Default: assets]\n  # The base directory where assets are stored. This is used to determine the\n  # `dirname` value in `output_path_format` below.\n  base_path: assets\n\n  # [Optional, Default: assets/resized/%{filename}-%{width}x%{height}.%{extension}]\n  # The template used when generating filenames for resized images. Must be a\n  # relative path.\n  #\n  # Parameters available are:\n  #   %{dirname}     Directory of the file relative to `base_path` (assets/sub/dir/some-file.jpg => sub/dir)\n  #   %{basename}    Basename of the file (assets/some-file.jpg => some-file.jpg)\n  #   %{filename}    Basename without the extension (assets/some-file.jpg => some-file)\n  #   %{extension}   Extension of the file (assets/some-file.jpg => jpg)\n  #   %{width}       Width of the resized image\n  #   %{height}      Height of the resized image\n  #\n  output_path_format: assets/resized/%{width}/%{basename}\n\n  # By default, only images referenced by the responsive_image and responsive_image_block\n  # tags are resized. Here you can set a list of paths or path globs to resize other\n  # images. This is useful for resizing images which will be referenced from stylesheets.\n  extra_images:\n    - assets/foo/bar.png\n    - assets/bgs/*.png\n    - assets/avatars/*.{jpeg,jpg}

Usage

\n

Replace your images with the responsive_image tag, specifying the path to the image in the path attribute.

\n
{% responsive_image path: assets/my-file.jpg %}

You can override the template on a per-image basis by specifying the template attribute.

\n
{% responsive_image path: assets/my-file.jpg template: _includes/another-template.html %}

Any extra attributes will be passed straight to the template as variables.

\n
{% responsive_image path: assets/image.jpg alt: "Lorem ipsum..." title: "Lorem ipsum..." %}

Liquid variables as attributes

\n

You can use Liquid variables as attributes with the responsive_image_block tag. This tag works in exactly the same way as the responsive_image tag, but is implemented as a block tag to allow for more complex logic.

\n
\n

Important! The attributes in the responsive_image_block tag are parsed as YAML, so whitespace and indentation are significant!

\n
\n
{% assign path = 'assets/test.png' %}\n{% assign alt = 'Lorem ipsum...' %}\n\n{% responsive_image_block %}\n    path: {{ path }}\n    alt: {{ alt }}\n    {% if title %}\n    title: {{ title }}\n    {% endif %}\n{% endresponsive_image_block %}

Template

\n

You will need to create a template in order to use the responsive_image tag. Below are some sample templates to get you started.

\n

Responsive images with srcset

\n
{% capture srcset %}\n    {% for i in resized %}\n        /{{ i.path }} {{ i.width }}w,\n    {% endfor %}\n{% endcapture %}\n\n<img src="/{{ path }}" alt="{{ alt }}" srcset="{{ srcset | strip_newlines }} /{{ original.path }} {{ original.width }}w">

Responsive images with <picture>

\n
<picture>\n    {% for i in resized %}\n        <source media="(min-width: {{ i.width }}px)" srcset="/{{ i.path }}">\n    {% endfor %}\n    <img src="/{{ path }}">\n</picture>

Responsive images using Imager.js

\n
\n

Note: This template assumes an output_path_format of assets/resized/%{width}/%{basename}

\n
\n
{% assign smallest = resized | sort: 'width' | first %}\n\n<div class="responsive-image">\n    <img class="responsive-image__placeholder" src="/{{ smallest.path }}">\n    <div class="responsive-image__delayed" data-src="/assets/resized/{width}/{{ original.basename }}"></div>\n</div>\n\n<script>\n    new Imager('.responsive-image__delayed', {\n        availableWidths: [{{ resized | map: 'width' | join: ', ' }}]\n        onImagesReplaced: function() {\n            $('.responsive-image__placeholder').hide();\n        }\n    });\n</script>

Template Variables

\n

The following variables are available in the template:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VariableTypeDescription
pathStringThe path of the unmodified image. This is always the same as the path attribute passed to the tag.
resizedArrayAn array of all the resized images. Each image is an Image Object.
originalObjectAn Image Object containing information about the original image.
*StringAny other attributes will be passed to the template verbatim as strings.
\n

Image Objects

\n

Image objects (like original and each object in resized) contain the following properties:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VariableTypeDescription
pathStringThe path to the image.
widthIntegerThe width of the image.
heightIntegerThe height of the image.
basenameStringBasename of the file (assets/some-file.jpg => some-file.jpg).
dirnameStringDirectory of the file relative to base_path (assets/sub/dir/some-file.jpg => sub/dir).
filenameStringBasename without the extension (assets/some-file.jpg => some-file).
extensionStringExtension of the file (assets/some-file.jpg => jpg).
\n" 123 | }, 124 | { 125 | "component": "footer", 126 | "links": [ 127 | { 128 | "href": "https://github.com/wildlyinaccurate/jekyll-responsive-image", 129 | "text": "GitHub" 130 | }, 131 | { 132 | "href": "https://github.com/wildlyinaccurate", 133 | "text": "wildlyinaccurate" 134 | } 135 | ] 136 | } 137 | ] 138 | } 139 | -------------------------------------------------------------------------------- /features/extra-image-generation.feature: -------------------------------------------------------------------------------- 1 | Feature: Extra image generation 2 | Scenario: Specifying a single image and glob patterns 3 | Given I have a responsive_image configuration with: 4 | """ 5 | sizes: 6 | - width: 100 7 | extra_images: 8 | - assets/everybody-loves-jalapeño-pineapple-cornbread.png 9 | - assets/*.jpeg 10 | """ 11 | And I have a file "index.html" with "Hello, world!" 12 | When I run Jekyll 13 | Then the image "assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100x50.png" should have the dimensions "100x50" 14 | And the image "assets/resized/progressive-100x50.jpeg" should have the dimensions "100x50" 15 | And the file "_site/assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100x50.png" should exist 16 | And the file "_site/assets/resized/progressive-100x50.jpeg" should exist 17 | 18 | Scenario: Specifying a recursive glob pattern 19 | Given I have a responsive_image configuration with: 20 | """ 21 | sizes: 22 | - width: 100 23 | extra_images: 24 | - assets/**/* 25 | """ 26 | And I have a file "index.html" with "Hello, world!" 27 | When I run Jekyll 28 | Then the image "assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100x50.png" should have the dimensions "100x50" 29 | And the image "assets/resized/exif-rotation-100x50.jpeg" should have the dimensions "100x50" 30 | And the image "assets/resized/progressive-100x50.jpeg" should have the dimensions "100x50" 31 | And the image "assets/resized/test-100x50.png" should have the dimensions "100x50" 32 | And the file "_site/assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100x50.png" should exist 33 | And the file "_site/assets/resized/exif-rotation-100x50.jpeg" should exist 34 | And the file "_site/assets/resized/progressive-100x50.jpeg" should exist 35 | And the file "_site/assets/resized/test-100x50.png" should exist 36 | 37 | Scenario: Honouring Jekyll 'source' configuration 38 | Given I have copied my site to "sub-dir/my-site-copy" 39 | And I have a configuration with: 40 | """ 41 | source: sub-dir/my-site-copy 42 | responsive_image: 43 | sizes: 44 | - width: 100 45 | extra_images: 46 | - assets/*.png 47 | """ 48 | And I have a file "index.html" with "Hello, world!" 49 | When I run Jekyll 50 | Then the image "sub-dir/my-site-copy/assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100x50.png" should have the dimensions "100x50" 51 | And the file "_site/assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100x50.png" should exist 52 | And the file "_site/assets/resized/progressive-100x50.jpeg" should not exist 53 | -------------------------------------------------------------------------------- /features/image-generation.feature: -------------------------------------------------------------------------------- 1 | Feature: Responsive image generation 2 | Scenario: Resizing images 3 | Given I have a responsive_image configuration with: 4 | """ 5 | template: _includes/responsive-image.html 6 | sizes: 7 | - width: 100 8 | """ 9 | And I have a file "index.html" with "{% responsive_image path: assets/everybody-loves-jalapeño-pineapple-cornbread.png alt: Foobar %}" 10 | When I run Jekyll 11 | Then the image "assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100x50.png" should have the dimensions "100x50" 12 | And the file "_site/assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100x50.png" should exist 13 | 14 | Scenario: Handling subdirectories 15 | Given I have a responsive_image configuration with: 16 | """ 17 | template: _includes/responsive-image.html 18 | output_path_format: assets/resized/%{dirname}/%{filename}-%{width}.%{extension} 19 | sizes: 20 | - width: 100 21 | """ 22 | And I have a file "index.html" with: 23 | """ 24 | {% responsive_image path: assets/everybody-loves-jalapeño-pineapple-cornbread.png %} 25 | {% responsive_image path: assets/subdir/test.png %} 26 | {% responsive_image path: assets/everybody-loves-jalapeño-pineapple-cornbread.png cache: true %} 27 | """ 28 | When I run Jekyll 29 | Then the file "assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100.png" should exist 30 | And the file "_site/assets/resized/everybody-loves-jalapeño-pineapple-cornbread-100.png" should exist 31 | And the file "assets/resized/subdir/test-100.png" should exist 32 | And the file "_site/assets/resized/subdir/test-100.png" should exist 33 | 34 | Scenario: Honouring source image interlace mode 35 | Given I have a responsive_image configuration with: 36 | """ 37 | template: _includes/responsive-image.html 38 | sizes: 39 | - width: 100 40 | """ 41 | And I have a file "index.html" with "{% responsive_image path: assets/progressive.jpeg %}" 42 | When I run Jekyll 43 | Then the image "assets/resized/progressive-100x50.jpeg" should be interlaced 44 | 45 | Scenario: Honouring Jekyll 'source' configuration 46 | Given I have copied my site to "my-site-copy/src" 47 | And I have a configuration with: 48 | """ 49 | source: my-site-copy/src 50 | responsive_image: 51 | base_path: assets 52 | template: _includes/responsive-image.html 53 | output_path_format: assets/resized/%{dirname}/%{width}/%{basename} 54 | sizes: 55 | - width: 100 56 | """ 57 | And I have a file "my-site-copy/src/index.html" with "{% responsive_image path: assets/subdir/test.png %}" 58 | When I run Jekyll 59 | Then the image "my-site-copy/src/assets/resized/subdir/100/test.png" should have the dimensions "100x50" 60 | And the file "_site/assets/resized/subdir/100/test.png" should exist 61 | 62 | Scenario: Images can be auto-rotated based on EXIF rotation 63 | Given I have a responsive_image configuration with: 64 | """ 65 | template: _includes/responsive-image.html 66 | sizes: 67 | - width: 100 68 | auto_rotate: true 69 | """ 70 | And I have a file "index.html" with "{% responsive_image path: assets/exif-rotation.jpeg %}" 71 | When I run Jekyll 72 | Then the file "_site/assets/resized/exif-rotation-100x200.jpeg" should exist 73 | 74 | Scenario: Images aren't auto-rotated by default 75 | Given I have a responsive_image configuration with: 76 | """ 77 | template: _includes/responsive-image.html 78 | sizes: 79 | - width: 100 80 | """ 81 | And I have a file "index.html" with: 82 | """ 83 | {% responsive_image path: assets/exif-rotation.jpeg %} 84 | {% responsive_image path: assets/progressive.jpeg %} 85 | """ 86 | When I run Jekyll 87 | Then the file "_site/assets/resized/exif-rotation-100x50.jpeg" should exist 88 | Then the file "_site/assets/resized/progressive-100x50.jpeg" should exist 89 | 90 | Scenario: Images should not be stripped of EXIF info by default 91 | Given I have a responsive_image configuration with: 92 | """ 93 | template: _includes/responsive-image.html 94 | sizes: 95 | - width: 100 96 | """ 97 | And I have a file "index.html" with: 98 | """ 99 | {% responsive_image path: assets/exif-rotation.jpeg %} 100 | """ 101 | When I run Jekyll 102 | Then the file "_site/assets/resized/exif-rotation-100x50.jpeg" should exist 103 | Then the image "_site/assets/resized/exif-rotation-100x50.jpeg" should have an EXIF orientation 104 | 105 | Scenario: Images can be stripped of EXIF info 106 | Given I have a responsive_image configuration with: 107 | """ 108 | template: _includes/responsive-image.html 109 | sizes: 110 | - width: 100 111 | strip: true 112 | """ 113 | And I have a file "index.html" with: 114 | """ 115 | {% responsive_image path: assets/exif-rotation.jpeg %} 116 | """ 117 | When I run Jekyll 118 | Then the file "_site/assets/resized/exif-rotation-100x50.jpeg" should exist 119 | Then the image "_site/assets/resized/exif-rotation-100x50.jpeg" should have no EXIF orientation 120 | -------------------------------------------------------------------------------- /features/image-hashes.feature: -------------------------------------------------------------------------------- 1 | Feature: Image hashes inside responsive image templates 2 | Scenario: Using the {% responsive_image %} tag 3 | Given I have copied my site to "my-site-copy/src" 4 | And I have a configuration with: 5 | """ 6 | source: my-site-copy/src 7 | responsive_image: 8 | template: _includes/hash.html 9 | output_path_format: assets/resized/%{dirname}/%{width}/%{basename} 10 | sizes: 11 | - width: 100 12 | """ 13 | And I have a file "my-site-copy/src/index.html" with "{% responsive_image path: assets/subdir/test.png %}" 14 | When I run Jekyll 15 | Then the file "_site/index.html" should contain: 16 | """ 17 | path: assets/subdir/test.png 18 | width: 300 19 | height: 150 20 | basename: test.png 21 | dirname: subdir 22 | filename: test 23 | extension: png 24 | 25 | path: assets/resized/subdir/100/test.png 26 | width: 100 27 | height: 50 28 | basename: test.png 29 | dirname: resized/subdir/100 30 | filename: test 31 | extension: png 32 | """ 33 | -------------------------------------------------------------------------------- /features/plugin.feature: -------------------------------------------------------------------------------- 1 | Feature: General plugin usage 2 | Scenario: No config at all 3 | Given I have no configuration 4 | When I run Jekyll 5 | Then there should be no errors 6 | 7 | Scenario: Config with empty responsive_image block 8 | Given I have a responsive_image configuration with: 9 | """ 10 | """ 11 | When I run Jekyll 12 | Then there should be no errors 13 | -------------------------------------------------------------------------------- /features/responsive-image-block.feature: -------------------------------------------------------------------------------- 1 | Feature: Jekyll responsive_image_block tag 2 | Scenario: Simple image tag 3 | Given I have a responsive_image configuration with "template" set to "_includes/responsive-image.html" 4 | And I have a file "index.html" with: 5 | """ 6 | {% assign path = 'assets/everybody-loves-jalapeño-pineapple-cornbread.png' %} 7 | {% responsive_image_block %} 8 | path: {{ path }} 9 | title: Magic rainbow adventure! 10 | alt: Lorem ipsum 11 | {% endresponsive_image_block %} 12 | """ 13 | When I run Jekyll 14 | Then I should see "\"Lorem" in "_site/index.html" 45 | 46 | Scenario: More complex logic in the block tag 47 | Given I have a responsive_image configuration with "template" set to "_includes/responsive-image.html" 48 | And I have a file "index.html" with: 49 | """ 50 | {% assign path = 'assets/everybody-loves-jalapeño-pineapple-cornbread.png' %} 51 | {% assign alt = 'Lorem ipsum' %} 52 | {% responsive_image_block %} 53 | path: {{ path }} 54 | 55 | {% if another_alt %} 56 | alt: {{ another_alt }} 57 | {% else %} 58 | alt: {{ alt }} 59 | {% endif %} 60 | {% endresponsive_image_block %} 61 | """ 62 | When I run Jekyll 63 | Then I should see "\"Lorem" in "_site/index.html" 18 | 19 | Scenario: Adding custom attributes 20 | Given I have a responsive_image configuration with "template" set to "_includes/responsive-image.html" 21 | And I have a file "index.html" with "{% responsive_image path: assets/everybody-loves-jalapeño-pineapple-cornbread.png alt: 'Foobar bazbar' title: 'Lorem Ipsum' %}" 22 | When I run Jekyll 23 | Then I should see "\"Foobar 2 | -------------------------------------------------------------------------------- /features/test-site/_includes/custom-template.html: -------------------------------------------------------------------------------- 1 | [{{ resized | map: 'width' | join: ', ' }}] 2 | -------------------------------------------------------------------------------- /features/test-site/_includes/hash.html: -------------------------------------------------------------------------------- 1 | path: {{ original.path }} 2 | width: {{ original.width }} 3 | height: {{ original.height }} 4 | basename: {{ original.basename }} 5 | dirname: {{ original.dirname }} 6 | filename: {{ original.filename }} 7 | extension: {{ original.extension }} 8 | {% for i in resized %} 9 | path: {{ i.path }} 10 | width: {{ i.width }} 11 | height: {{ i.height }} 12 | basename: {{ i.basename }} 13 | dirname: {{ i.dirname }} 14 | filename: {{ i.filename }} 15 | extension: {{ i.extension }} 16 | {% endfor %} 17 | -------------------------------------------------------------------------------- /features/test-site/_includes/responsive-image.html: -------------------------------------------------------------------------------- 1 | {% assign largest = resized | sort: 'width' | last %} 2 | 3 | {{ alt }} 4 | -------------------------------------------------------------------------------- /features/test-site/assets/everybody-loves-jalapeño-pineapple-cornbread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wildlyinaccurate/jekyll-responsive-image/dda6d748c8b070b37900d9e0b528d11701808f76/features/test-site/assets/everybody-loves-jalapeño-pineapple-cornbread.png -------------------------------------------------------------------------------- /features/test-site/assets/exif-rotation.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wildlyinaccurate/jekyll-responsive-image/dda6d748c8b070b37900d9e0b528d11701808f76/features/test-site/assets/exif-rotation.jpeg -------------------------------------------------------------------------------- /features/test-site/assets/progressive.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wildlyinaccurate/jekyll-responsive-image/dda6d748c8b070b37900d9e0b528d11701808f76/features/test-site/assets/progressive.jpeg -------------------------------------------------------------------------------- /features/test-site/assets/subdir/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wildlyinaccurate/jekyll-responsive-image/dda6d748c8b070b37900d9e0b528d11701808f76/features/test-site/assets/subdir/test.png -------------------------------------------------------------------------------- /jekyll-responsive-image.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib/', __FILE__) 3 | $:.unshift lib unless $:.include?(lib) 4 | 5 | require 'jekyll-responsive-image/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'jekyll-responsive-image' 9 | spec.version = Jekyll::ResponsiveImage::VERSION 10 | spec.authors = ['Joseph Wynn'] 11 | spec.email = ['joseph@wildlyinaccurate.com'] 12 | spec.summary = 'Responsive image management for Jekyll' 13 | spec.homepage = 'https://github.com/wildlyinaccurate/jekyll-responsive-image' 14 | spec.licenses = ['MIT'] 15 | spec.description = %q{ 16 | Highly configurable Jekyll plugin for managing responsive images. Automatically 17 | resizes images and provides a Liquid template tag for loading the images with 18 | picture, img srcset, Imager.js, etc. 19 | } 20 | 21 | spec.files = `git ls-files -z lib/`.split("\u0000") 22 | spec.executables = [] 23 | spec.require_paths = ['lib'] 24 | 25 | spec.add_runtime_dependency 'jekyll', ['>= 2.0', "< 5.0"] 26 | spec.add_runtime_dependency 'rmagick', ['>= 2.0', '< 5.0'] 27 | end 28 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'yaml' 3 | 4 | require 'jekyll' 5 | require 'rmagick' 6 | 7 | require 'jekyll-responsive-image/version' 8 | require 'jekyll-responsive-image/config' 9 | require 'jekyll-responsive-image/utils' 10 | require 'jekyll-responsive-image/render_cache' 11 | require 'jekyll-responsive-image/image_processor' 12 | require 'jekyll-responsive-image/resize_handler' 13 | require 'jekyll-responsive-image/renderer' 14 | require 'jekyll-responsive-image/tag' 15 | require 'jekyll-responsive-image/block' 16 | require 'jekyll-responsive-image/extra_image_generator' 17 | 18 | Liquid::Template.register_tag('responsive_image', Jekyll::ResponsiveImage::Tag) 19 | Liquid::Template.register_tag('responsive_image_block', Jekyll::ResponsiveImage::Block) 20 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/block.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module ResponsiveImage 3 | class Block < Liquid::Block 4 | include Jekyll::ResponsiveImage::Utils 5 | 6 | def render(context) 7 | content = super 8 | 9 | if content.include?("\t") 10 | content = content.lines.map {|line| line.gsub(/\G[\t ]/, " ")}.join("\n") 11 | end 12 | 13 | attributes = YAML.load(content) 14 | Renderer.new(context.registers[:site], attributes).render_responsive_image 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/config.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module ResponsiveImage 3 | class Config 4 | DEFAULTS = { 5 | 'default_quality' => 85, 6 | 'base_path' => 'assets', 7 | 'output_path_format' => 'assets/resized/%{filename}-%{width}x%{height}.%{extension}', 8 | 'sizes' => [], 9 | 'extra_images' => [], 10 | 'auto_rotate' => false, 11 | 'save_to_source' => true, 12 | 'cache' => false, 13 | 'strip' => false 14 | } 15 | 16 | def initialize(site) 17 | @site = site 18 | end 19 | 20 | def valid_config(config) 21 | config.has_key?('responsive_image') && config['responsive_image'].is_a?(Hash) 22 | end 23 | 24 | def to_h 25 | config = {} 26 | 27 | if valid_config(@site.config) 28 | config = @site.config['responsive_image'] 29 | end 30 | 31 | 32 | DEFAULTS.merge(config) 33 | .merge(site_source: @site.source, site_dest: @site.dest) 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/extra_image_generator.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module ResponsiveImage 3 | class ExtraImageGenerator < Jekyll::Generator 4 | include Jekyll::ResponsiveImage::Utils 5 | include FileTest 6 | 7 | def generate(site) 8 | config = Config.new(site).to_h 9 | site_source = Pathname.new(site.source) 10 | 11 | config['extra_images'].each do |pathspec| 12 | Dir.glob(site.in_source_dir(pathspec)) do |image_path| 13 | if FileTest.file?(image_path) 14 | path = Pathname.new(image_path) 15 | relative_image_path = path.relative_path_from(site_source) 16 | 17 | result = ImageProcessor.process(relative_image_path, config) 18 | result[:resized].each { |image| keep_resized_image!(site, image) } 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/image_processor.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module ResponsiveImage 3 | class ImageProcessor 4 | include ResponsiveImage::Utils 5 | 6 | def process(image_path, config) 7 | absolute_image_path = File.expand_path(image_path.to_s, config[:site_source]) 8 | 9 | Jekyll.logger.warn "Invalid image path specified: #{image_path.inspect}" unless File.file?(absolute_image_path) 10 | 11 | resize_handler = ResizeHandler.new(absolute_image_path, config) 12 | 13 | { 14 | original: image_hash(config, image_path, resize_handler.original_image.columns, resize_handler.original_image.rows), 15 | resized: resize_handler.resize_image, 16 | } 17 | end 18 | 19 | def self.process(image_path, config) 20 | self.new.process(image_path, config) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/render_cache.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module ResponsiveImage 3 | class RenderCache 4 | attr_accessor :store 5 | 6 | class << self 7 | def store 8 | @store ||= {} 9 | end 10 | 11 | def get(key) 12 | store[key] 13 | end 14 | 15 | def set(key, val) 16 | store[key] = val 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/renderer.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module ResponsiveImage 3 | class Renderer 4 | include Jekyll::ResponsiveImage::Utils 5 | 6 | def initialize(site, attributes) 7 | @site = site 8 | @attributes = attributes 9 | end 10 | 11 | def render_responsive_image 12 | config = Config.new(@site).to_h 13 | use_cache = config['cache'] || @attributes['cache'] 14 | cache_key = @attributes.to_s 15 | result = use_cache ? RenderCache.get(cache_key) : nil 16 | 17 | if result.nil? 18 | image = ImageProcessor.process(@attributes['path'], config) 19 | @attributes['original'] = image[:original] 20 | @attributes['resized'] = image[:resized] 21 | 22 | @attributes['resized'].each { |resized| keep_resized_image!(@site, resized) } 23 | 24 | image_template = @site.in_source_dir(@attributes['template'] || config['template']) 25 | partial = File.read(image_template) 26 | template = Liquid::Template.parse(partial) 27 | 28 | info = { 29 | registers: { site: @site } 30 | } 31 | 32 | result = template.render!(@attributes.merge(@site.site_payload), info) 33 | 34 | RenderCache.set(cache_key, result) 35 | end 36 | 37 | result 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/resize_handler.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module ResponsiveImage 3 | class ResizeHandler 4 | include ResponsiveImage::Utils 5 | 6 | attr_reader :original_image 7 | 8 | def initialize(original_image_absolute_path, config) 9 | @config = config 10 | 11 | @original_image_absolute_path = original_image_absolute_path 12 | 13 | if @config['auto_rotate'] 14 | load_full_image 15 | @original_image.auto_orient! 16 | else 17 | load_image_properties_only 18 | end 19 | end 20 | 21 | def resize_image 22 | resized = [] 23 | 24 | @config['sizes'].each do |size| 25 | width = size['width'] 26 | ratio = width.to_f / @original_image.columns.to_f 27 | height = (@original_image.rows.to_f * ratio).round 28 | 29 | next unless needs_resizing?(width) 30 | 31 | image_path = @original_image.filename.force_encoding(Encoding::UTF_8) 32 | filepath = format_output_path(@config['output_path_format'], @config, image_path, width, height) 33 | resized.push(image_hash(@config, filepath, width, height)) 34 | 35 | site_source_filepath = File.expand_path(filepath, @config[:site_source]) 36 | site_dest_filepath = File.expand_path(filepath, @config[:site_dest]) 37 | 38 | if @config['save_to_source'] 39 | target_filepath = site_source_filepath 40 | else 41 | target_filepath = site_dest_filepath 42 | end 43 | 44 | # Don't resize images more than once 45 | next if File.exist?(target_filepath) 46 | 47 | ensure_output_dir_exists!(target_filepath) 48 | ensure_output_dir_exists!(site_dest_filepath) 49 | 50 | Jekyll.logger.info "Generating #{target_filepath}" 51 | 52 | load_full_image unless @original_image_pixels_loaded 53 | 54 | if @config['strip'] 55 | @original_image.strip! 56 | end 57 | 58 | i = @original_image.scale(ratio) 59 | 60 | quality = size['quality'] || @config['default_quality'] 61 | 62 | i.write(target_filepath) do |f| 63 | f.interlace = i.interlace 64 | f.quality = quality 65 | end 66 | 67 | if @config['save_to_source'] 68 | # Ensure the generated file is copied to the _site directory 69 | Jekyll.logger.info "Copying resized image to #{site_dest_filepath}" 70 | FileUtils.copy_file(site_source_filepath, site_dest_filepath) 71 | end 72 | 73 | i.destroy! 74 | end 75 | 76 | @original_image.destroy! 77 | 78 | resized 79 | end 80 | 81 | def format_output_path(format, config, image_path, width, height) 82 | params = symbolize_keys(image_hash(config, image_path, width, height)) 83 | 84 | Pathname.new(format % params).cleanpath.to_s 85 | end 86 | 87 | def needs_resizing?(width) 88 | @original_image.columns > width 89 | end 90 | 91 | def load_full_image 92 | @original_image = Magick::Image::read(@original_image_absolute_path).first 93 | @original_image_pixels_loaded = true 94 | end 95 | 96 | def load_image_properties_only 97 | @original_image = Magick::Image::ping(@original_image_absolute_path).first 98 | @original_image_pixels_loaded = false 99 | end 100 | 101 | def ensure_output_dir_exists!(path) 102 | dir = File.dirname(path) 103 | 104 | unless Dir.exist?(dir) 105 | Jekyll.logger.info "Creating output directory #{dir}" 106 | FileUtils.mkdir_p(dir) 107 | end 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/tag.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module ResponsiveImage 3 | class Tag < Liquid::Tag 4 | def initialize(tag_name, markup, tokens) 5 | super 6 | 7 | @attributes = {} 8 | 9 | markup.scan(::Liquid::TagAttributes) do |key, value| 10 | # Strip quotes from around attribute values 11 | @attributes[key] = value.gsub(/^['"]|['"]$/, '') 12 | end 13 | end 14 | 15 | def render(context) 16 | Renderer.new(context.registers[:site], @attributes).render_responsive_image 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/utils.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | module Jekyll 4 | module ResponsiveImage 5 | module Utils 6 | def keep_resized_image!(site, image) 7 | keep_dir = File.dirname(image['path']) 8 | site.config['keep_files'] << keep_dir unless site.config['keep_files'].include?(keep_dir) 9 | end 10 | 11 | def symbolize_keys(hash) 12 | result = {} 13 | hash.each_key do |key| 14 | result[key.to_sym] = hash[key] 15 | end 16 | result 17 | end 18 | 19 | # Build a hash containing image information 20 | def image_hash(config, image_path, width, height) 21 | { 22 | 'path' => image_path, 23 | 'dirname' => relative_dirname(config, image_path), 24 | 'basename' => File.basename(image_path), 25 | 'filename' => File.basename(image_path, '.*'), 26 | 'extension' => File.extname(image_path).delete('.'), 27 | 'width' => width, 28 | 'height' => height, 29 | } 30 | end 31 | 32 | def relative_dirname(config, image_path) 33 | path = Pathname.new(File.expand_path(image_path, config[:site_source])) 34 | base = Pathname.new(File.expand_path(config['base_path'], config[:site_source])) 35 | 36 | path.relative_path_from(base).dirname.to_s.delete('.') 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/jekyll-responsive-image/version.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | module ResponsiveImage 3 | VERSION = '1.6.0'.freeze 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /sample-templates/imager-js.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Render your responsive images using Imager.js (https://github.com/BBC-News/Imager.js/), with the smallest resized image used as a fallback. 3 | 4 | Usage: 5 | 6 | {% responsive_image path: assets/image.jpg alt: "A description of the image" %} 7 | 8 | (P.S. You can safely delete this comment block) 9 | {% endcomment %} 10 | 11 | {% assign smallest = resized | sort: 'width' | first %} 12 | 13 |
14 | 15 |
16 |
17 | 18 | 26 | -------------------------------------------------------------------------------- /sample-templates/picture.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Render your responsive images using , with the original asset used as a fallback. Note: If your original assets are not web-friendly (e.g. they are very large), you can use a resized image as the fallback instead. See the srcset-resized-fallback.html template for how to do this. 3 | 4 | Usage: 5 | 6 | {% responsive_image path: assets/image.jpg alt: "A description of the image" %} 7 | 8 | (P.S. You can safely delete this comment block) 9 | {% endcomment %} 10 | 11 | 12 | {% for i in resized %} 13 | 14 | {% endfor %} 15 | 16 | 17 | -------------------------------------------------------------------------------- /sample-templates/srcset-resized-fallback.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Render your responsive images using , with the largest resized image used as a fallback. 3 | 4 | Usage: 5 | 6 | {% responsive_image path: assets/image.jpg alt: "A description of the image" %} 7 | 8 | (P.S. You can safely delete this comment block) 9 | {% endcomment %} 10 | 11 | {% assign largest = resized | sort: 'width' | last %} 12 | {% capture srcset %} 13 | {% for i in resized %} 14 | /{{ i.path }} {{ i.width }}w, 15 | {% endfor %} 16 | {% endcapture %} 17 | 18 | {{ alt }} 19 | -------------------------------------------------------------------------------- /sample-templates/srcset.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Render your responsive images using , with the original asset used as a fallback. Note: If your original assets are not web-friendly (e.g. they are very large), you might prefer to use the srcset-resized-fallback.html template. 3 | 4 | Usage: 5 | 6 | {% responsive_image path: assets/image.jpg alt: "A description of the image" %} 7 | 8 | (P.S. You can safely delete this comment block) 9 | {% endcomment %} 10 | 11 | {% capture srcset %} 12 | {% for i in resized %} 13 | /{{ i.path }} {{ i.width }}w, 14 | {% endfor %} 15 | {% endcapture %} 16 | 17 | {{ alt }} 18 | --------------------------------------------------------------------------------