Commit 874bac0f authored by Andreas Knote's avatar Andreas Knote
Browse files

imgp: replace imagemagick with imgp for better performance

parent f0ef4d18
# Jekyll Img Srcset
Note: this plugin is being reworked.
In this branch, the resizing is done using `imgp` called through ruby, see the `scripts/` directory.
- andreas.knote@uni-wuerzburg.de
-------------------
Note: Old Readme contents
# Jekyll Img Srcset
This plugin resizes images to offer lower resolution images for devices with restricted screen size such as mobile.
## Installation
......@@ -37,4 +49,4 @@ Use the Liquid tag as shown below to generate a figure tag with the requested re
```
{% imgsrcset src="general/map.jpg" dimension="wide" %}
```
\ No newline at end of file
```
#!/usr/bin/env ruby
require 'watch-and-resize'
puts WatchAndResize.resize()
#!/usr/bin/env ruby
require 'watchAndResize'
puts WatchAndResize.resizeOnly(ARGV[0])
\ No newline at end of file
#!/usr/bin/env ruby
require 'watchAndResize'
puts WatchAndResize.awesome(ARGV[0])
\ No newline at end of file
#!/usr/bin/env ruby
require 'watch-and-resize'
puts WatchAndResize.watch()
......@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
spec.homepage = "https://gitlab2.informatik.uni-wuerzburg.de/hci-development/jekyll-img-srcset"
spec.license = "Apache-2.0"
spec.files = ['lib/jekyll-img-srcset.rb', 'lib/watchAndResize.rb']
spec.files = ['lib/jekyll-img-srcset.rb', 'lib/watch-and-resize.rb']
spec_require_paths = ['lib']
spec.required_ruby_version = '>= 2.4.0'
......@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "bundler", "~> 1.16"
spec.add_development_dependency "rake", "~> 12.0"
spec.executables << 'war'
spec.executables << 'resizeOnly'
spec.executables << 'watch-and-resize'
spec.executables << 'resize'
end
require 'listen'
class WatchAndResize
def self.resize(input_dir = "/website/assets/images",
output_dir = "/website/public/assets/images",
widths = [320, 640, 844, 1208, 1688])
puts "[resize] resizing #{input_dir} ..."
STDOUT.flush
start = Time.now
for width in widths do
width_output_dir="#{output_dir}/#{width}"
puts "[resize] processing #{width_output_dir} ..."
if(!system("mkdir -p #{width_output_dir}"))
puts "[resize] Error: #{$?}"
end
if(!system("rsync --recursive --delete -u #{input_dir}/* #{width_output_dir}/"))
puts "[resize] Error: #{$?}"
end
if(!system("imgp -x #{width}x0 -rwkm #{width_output_dir}"))
puts "[resize] Error: #{$?}"
end
end
delta = Time.now - start
puts "[resize] finished"
puts "[resize] total time: #{delta} seconds"
STDOUT.flush
end
def self.watch(input_dir = "/website/assets/images",
output_dir = "/website/public/assets/images",
widths = [320, 640, 844, 1208, 1688])
puts "[resize] watching #{input_dir}"
STDOUT.flush
listener = Listen.to(input_dir, only: [/\.jpg$/, /\.jpeg$/, /\.png$/, /\.JPG$/, /\.JPEG$/, /\.PNG$/]) do |modified, added, removed|
needs_resize = false
for m in modified do
needs_resize = true
break
end
for a in added do
needs_resize = true
break
end
if (needs_resize)
self.resize(output_dir, input_dir, widths)
end
end
listener.start # not blocking
sleep
end
end
require 'listen'
require 'mini_magick'
require 'parallel'
class WatchAndResize
def self.resizeOnly(argv)
images = Dir.glob("./assets/images/**/*.{jpg,JPG,jpeg,JPEG,png,PNG}")
puts "Checking and resizing './assets/images' if necessary withOUT watching files."
STDOUT.flush
start = Time.now
Parallel.each(images) do |source_path|
destinations = generate_destinations(source_path.gsub("assets/images/", ""), [320, 640, 844, 1208, 1688], "./public", "./assets/images")
min_width_path = destinations[0][1]
#puts "processing: #{source_path}, mtimes: #{File.mtime(min_width_path)} ... #{File.mtime(source_path)}"
puts "checking: #{source_path}"
if (not File.file?(min_width_path)) || (File.mtime(min_width_path) < File.mtime(source_path))
resize_image(source_path.gsub("assets/images/", ""), [320, 640, 844, 1208, 1688], "./public", "./assets/images")
end
#puts "destinations: #{min_width_path}"
#STDOUT.flush
end
delta = Time.now - start
puts "Checked and resized './assets/images' if necessary in #{delta} seconds."
STDOUT.flush
end
def self.awesome(argv)
images = Dir.glob("./assets/images/**/*.{jpg,JPG,jpeg,JPEG,png,PNG}")
start = Time.now
Parallel.each(images) do |source_path|
destinations = generate_destinations(source_path.gsub("assets/images/", ""), [320, 640, 844, 1208, 1688], "./public", "./assets/images")
min_width_path = destinations[0][1]
#puts "processing: #{source_path}, mtimes: #{File.mtime(min_width_path)} ... #{File.mtime(source_path)}"
puts "checking: #{source_path}"
if (not File.file?(min_width_path)) || (File.mtime(min_width_path) < File.mtime(source_path))
resize_image(source_path.gsub("assets/images/", ""), [320, 640, 844, 1208, 1688], "./public", "./assets/images")
end
#puts "destinations: #{min_width_path}"
#STDOUT.flush
end
delta = Time.now - start
puts "Checked and resized './assets/images' if necessary in #{delta} seconds."
STDOUT.flush
#Filewatcher.new('/website/').watch do |filename, event|
# puts "#{filename} #{event}"
# STDOUT.flush
#end
#sleep
listener = Listen.to('./assets/images', only: [/\.jpg$/, /\.jpeg$/, /\.png$/, /\.JPG$/, /\.JPEG$/, /\.PNG$/]) do |modified, added, removed|
for m in modified do
resize_image(m.gsub("website/assets/images/", ""), [320, 640, 844, 1208, 1688], "./public", "./assets/images")
end
for a in added do
resize_image(a.gsub("website/assets/images/", ""), [320, 640, 844, 1208, 1688], "./public", "./assets/images")
end
#system 'ls /website/public/assets/images'
#system 'ls /website/public/assets/images/320/people'
#puts "modified absolute path: #{modified}"
#puts "added absolute path: #{added}"
#puts "removed absolute path: #{removed}"
#STDOUT.flush
end
listener.start # not blocking
sleep
end
end
def generate_destinations(url, widths, dest, base_image_path)
width_to_create = widths.map do |width|
[width, File.join(dest, base_image_path, "#{width}", url)]
end
return width_to_create
end
# Resizes an image to provide different versions for multiple
# display resolutions
#
# @param url [String] the image's url
# @param widths [Array<Numeric>]
# target sizes to resize to;
# only widths smaller than the original width are generated
# @param base_image_path [String] the base image path. Usually assets/images
# @param cache [Jekyll::Cache]
# cache object to not resize if the computation was already done
# @return [Array<Numeric>, Numeric]
# the generated widths and the original iamge width
def resize_image(url, widths, dest, base_image_path) #, cache
src_path = File.join(base_image_path, url)
puts "Resizing: #{src_path}"
STDOUT.flush
#src_mtime = File.new(src_path).mtime
#if cache.key? src_path
# _, _, modified_time = cache[src_path]
# if src_mtime > modified_time
# cache.delete src_path
# end
#end
# resize images if they aren't already in the cache
#new_widths, w, modified_time = cache.getset(src_path) do
#modified_time = File.new(src_path).mtime
#image = MiniMagick::Image.open(src_path)
#w = image.dimensions[0]
#aspect = image.dimensions[1].to_f / w
#[w, aspect]
#new_widths = widths.select {|x| x <= w}
width_to_create = generate_destinations(url, widths, dest, base_image_path)
#.select do |width, target|
# (not File.exists? target) or (File.new(target).mtime < src_mtime)
#end
image = MiniMagick::Image.open(src_path)
w = image.dimensions[0]
aspect = image.dimensions[1].to_f / w
#https://www.imagemagick.org/discourse-server/viewtopic.php?t=20784
#https://github.com/minimagick/minimagick#metal
#https://www.rubydoc.info/github/minimagick/minimagick/MiniMagick/Tool/Convert
#https://www.rubydoc.info/github/minimagick/minimagick/MiniMagick%2FTool:%3C%3C
#
### Idea
#convert = MiniMagick::Tool::Convert.new
#convert << "path/to/image.jpg"
#for
# convert << "\( -clone 0 -resize #{width}x#{width*aspect} -write #{target} \)"
#end
#convert.call
for jobInfo in width_to_create do
width = jobInfo[0]
target = jobInfo[1]
if not Dir.exists? File.dirname(target)
FileUtils.mkdir_p File.dirname(target)
end
if width < w
cloned_image = MiniMagick::Image.open(src_path)
cloned_image.resize "#{width}x#{width*aspect}"
cloned_image.write target
else
FileUtils.copy_file(src_path, target)
end
end
puts "Finished resizing: #{src_path}"
STDOUT.flush
end
# Watch and Resize
This minimalistic watch and resize implementation helps to efficiently resize many images and watch for filesystem changes.
It uses the command-line tool [imgp](https://github.com/jarun/imgp
), which internally uses [pillow](https://pillow.readthedocs.io/en/stable/) which has been [shown to be much faster than ImageMagick](https://uploadcare.com/blog/the-fastest-image-resize/).
The watch-resize command uses [inotify](https://en.wikipedia.org/wiki/Inotify).
A trade-off was made between performance and code simplicity.
## Usage
To resize all images (png or jpeg) in folder (recursively) and store the result in a different path, use `resize`:
`scripts/resize [SOURCE_PATH] [OUTPUT_PATH]`
To watch a folder and resize all changed images, use `watch-and-resize`:
`scripts/watch-and-resize [SOURCE_PATH] [OUTPUT_PATH] &`
## Under the Hood
1. Use rsync to copy all images that are newer than the target files to the destination folder.
- rsync is configured to skip files that are up-to-date (modification date newer than source image)
- imgp can then be used in-place without synchronization issues (see documentation of imgp)
- copying is fast
2. Use `imgp` in batch mode to resize the images in the destination folder.
- imgp is configured to skip files that do not need resizing
## Dependencies
- python3-pil
- imgp
- rsync
- inotify-tools
#!/bin/bash
source_dir=$1
target_dir=$2
declare -a widths=("1688" "1208" "844" "640" "320")
start=`date +%s`
for width in "${widths[@]}"; do
echo "[resize] resizing to ${width}"
# prepare output directory
width_target_dir="${target_dir}/${width}"
mkdir -p ${target_dir}/${width}
# duplicate original images (only if modified)
rsync --recursive --delete -u ${source_dir}/* ${width_target_dir}/
# batch-resize all images in-place
imgp -x ${width}x0 -rwkm "${width_target_dir}"
done
end=`date +%s`
runtime=$((end-start))
echo "[resize] finished in ${runtime}s"
#!/bin/bash
source_dir=$1
target_dir=$2
echo "[resize] watching: ${source_dir} -> ${target_dir}"
while inotifywait -q -e modify -r ${source_dir}; do
/website/scripts/batch_resize ${source_dir} ${target_dir}
echo "[resize] watching: ${source_dir} -> ${target_dir}"
done
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment