From ad0e7687428ef11625632c0ae1a283a095529d1a Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 3 Oct 2018 13:44:53 +1000 Subject: [PATCH] FEATURE: add support for responsive images in posts When creating lightboxes we will attempt to create 1.5x and 2x thumbnails for retina screens, this can be controlled with a new hidden site setting called responsice_post_image_sizes, if you wish to create 3x images run SiteSetting.responsive_post_image_sizes = "1|1.5|2|3" The default should be good for most of the setups as it balances filesize with quality. 3x thumbs can get big. --- config/site_settings.yml | 3 ++ lib/cooked_post_processor.rb | 50 +++++++++++++++++-- spec/components/cooked_post_processor_spec.rb | 45 +++++++++++++++++ 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/config/site_settings.yml b/config/site_settings.yml index 354bde003dc..1187c06beb6 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -914,6 +914,9 @@ files: default: 'gz' type: list list_type: compact + responsive_post_image_sizes: + default: "1|1.5|2" + hidden: true crawl_images: default: true max_image_width: diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index c64edce2854..8653f422382 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -231,6 +231,10 @@ class CookedPostProcessor end end + def add_to_size_cache(url, w, h) + @size_cache[url] = [w, h] + end + def get_size(url) return @size_cache[url] if @size_cache.has_key?(url) @@ -285,6 +289,15 @@ class CookedPostProcessor if upload = Upload.get_from_url(src) upload.create_thumbnail!(width, height, crop) + + each_responsive_ratio do |ratio| + resized_w = (width * ratio).to_i + resized_h = (height * ratio).to_i + + if upload.width && resized_w <= upload.width + upload.create_thumbnail!(resized_w, resized_h, crop) + end + end end add_lightbox!(img, original_width, original_height, upload) @@ -299,6 +312,15 @@ class CookedPostProcessor false end + def each_responsive_ratio + SiteSetting + .responsive_post_image_sizes + .split('|') + .map(&:to_f) + .sort + .each { |r| yield r if r > 1 } + end + def add_lightbox!(img, original_width, original_height, upload = nil) # first, create a div to hold our lightbox lightbox = create_node("div", "lightbox-wrapper") @@ -320,13 +342,31 @@ class CookedPostProcessor if upload thumbnail = upload.thumbnail(w, h) + if thumbnail && thumbnail.filesize.to_i < upload.filesize + img["src"] = upload.thumbnail(w, h).url - img["src"] = - if thumbnail && thumbnail.filesize.to_i < upload.filesize - upload.thumbnail(w, h).url - else - upload.url + srcset = +"" + + each_responsive_ratio do |ratio| + resized_w = (w * ratio).to_i + resized_h = (h * ratio).to_i + + if upload.width && resized_w > upload.width + cooked_url = UrlHelper.cook_url(upload.url) + srcset << ", #{cooked_url} #{ratio}x" + else + if t = upload.thumbnail(resized_w, resized_h) + cooked_url = UrlHelper.cook_url(t.url) + srcset << ", #{cooked_url} #{ratio}x" + end + end end + + img["srcset"] = "#{img["src"]}#{srcset}" if srcset.length > 0 + + else + img["src"] = upload.url + end end # then, some overlay informations diff --git a/spec/components/cooked_post_processor_spec.rb b/spec/components/cooked_post_processor_spec.rb index 8bf669506f3..748c258c0f5 100644 --- a/spec/components/cooked_post_processor_spec.rb +++ b/spec/components/cooked_post_processor_spec.rb @@ -53,6 +53,51 @@ describe CookedPostProcessor do context ".post_process_images" do + before do + SiteSetting.responsive_post_image_sizes = "" + end + + context "responsive images" do + it "includes responsive images on demand" do + + SiteSetting.responsive_post_image_sizes = "1|1.5|3" + + upload = Fabricate(:upload, width: 2000, height: 1500, filesize: 10000) + post = Fabricate(:post, raw: "hello ") + + # fake some optimized images + OptimizedImage.create!( + url: 'http://a.b.c/666x500.jpg', + width: 666, + height: 500, + upload_id: upload.id, + sha1: SecureRandom.hex, + extension: '.jpg', + filesize: 500 + ) + + # fake 3x optimized image, we lose 2 pixels here over original due to rounding on downsize + OptimizedImage.create!( + url: 'http://a.b.c/1998x1500.jpg', + width: 1998, + height: 1500, + upload_id: upload.id, + sha1: SecureRandom.hex, + extension: '.jpg', + filesize: 800 + ) + + cpp = CookedPostProcessor.new(post) + + cpp.add_to_size_cache(upload.url, 2000, 1500) + cpp.post_process_images + + # 1.5x is skipped cause we have a missing thumb + expect(cpp.html).to include('srcset="http://a.b.c/666x500.jpg, http://a.b.c/1998x1500.jpg 3.0x"') + + end + end + shared_examples "leave dimensions alone" do it "doesn't use them" do expect(cpp.html).to match(/src="http:\/\/foo.bar\/image.png" width="" height=""/)