ixti/jekyll-assets

View on GitHub
lib/jekyll/assets/plugins/proxy/vips.rb

Summary

Maintainability
C
1 day
Test Coverage
# Frozen-string-literal: true
# Copyright: 2019 - MIT License
# Author: Andrew Heberle
# Encoding: utf-8

module Jekyll
  module Assets
    module Plugins
      class Vips < Proxy
        content_types %r@^image/(?!x-icon$)[a-zA-Z0-9\-_+]+$@
        arg_keys :vips

        # handles the vips process
        # by default images are stripped
        # some defaults for certain image formats are set as follows:
        #  JPEG = interlace (true), optimized encoding (true)
        #  WEBP = lossless (via "vips:lossless"), near_lossless (via
        #    "vips:near_lossless")
        #  PNG  = compression (via "vips:compression=X"), interlace (via
        #    "vips:interlace"
        #
        # libvips builds a "pipeline" of processes which are executed on write
        #
        # write_opts is added to and then used via "img.write_to_buffer"

        def process
          in_file = @file
          in_ext = in_file.extname

          img = ::Vips::Image.new_from_file in_file

          if args[:vips].key?(:format)
            format = ".#{args[:vips][:format].sub(%r!^\.!, '')}"
            ext = env.mime_exts.find { |k, v| k == format || v == format }
            out_ext, type = ext
          else
            out_ext = in_ext
          end

          write_opts = {}

          case out_ext
          when ".jpg", ".jpeg"
            write_opts[:interlace] = true
            write_opts[:optimize_coding] = true
          when ".webp"
            write_opts[:lossless] = args[:vips].key?(:lossless)
            write_opts[:near_lossless] = args[:vips].key?(:near_lossless)
          when ".png"
            if args[:vips].key?(:compression)
              write_opts[:compression] = args[:vips][:compression]
            end
            write_opts[:interlace] = args[:vips].key?(:interlace)
          end

          if args[:vips].key?(:quality)
            write_opts = vips_quality(write_opts)
          end

          if args[:vips].key?(:strip)
            write_opts[:strip] = vips_strip(write_opts)
          else
            write_opts[:strip] = true
          end

          if args[:vips].key?(:resize)
            img = vips_resize(img)
          elsif args[:vips].key?(:double)
            img = vips_double(img)
          elsif args[:vips].key?(:half)
            img = vips_half(img)
          end

          out_file = in_file.sub_ext(out_ext)
          buf = img.write_to_buffer out_ext, write_opts

          @file.binwrite(buf)

          if @file != out_file
            @file.rename(out_file)
          end

          out_file
        end

        # vips:compression=<compression>
        # sets the compression for the operation
        # only makes sense for losseless image formats
        private
        def vips_compression(opts)
          opts[:compression] = args[:vips][:compression].to_i
          opts
        end

        # vips:quality=<quality>
        # sets the quality for the operation
        # only makes sense for lossy image formats
        private
        def vips_quality(opts)
          opts[:Q] = args[:vips][:quality].to_i
          opts
        end

        # vips:strip
        # strips metadata from an image
        private
        def vips_strip(opts)
          opts[:strip] = args[:vips][:strip]
          opts
        end

        # vips:resize='<width>[x<height>]'
        # resizes an image via vips
        # will resize to  sepcified width, height or both
        # can also crop to that size or fill "extra" space
        # extra space is filled with blurred version of image
        # that is expanded to fill space
        private
        def vips_resize(img)
          resize_opts = {}
          if args[:vips][:resize].is_a? Integer
            width = args[:vips][:resize]
          else
            if args[:vips][:resize].include? "x"
              width, height = args[:vips][:resize].split("x")
              if width == "" or width == nil
                width = 0
              else
                width = width.to_i
              end
              if height == "" or height == nil
                height = width
              else
                height = height.to_i
                resize_opts[:height] = height
                if width == 0
                  width = height.to_i
                end
              end
            else
              width = args[:vips][:resize].to_i
            end
            if args[:vips].key?(:crop)
              if args[:vips][:crop] == "fill"
                do_fill = true
              else
                do_fill = false
                resize_opts[:crop] = args[:vips][:crop]
              end
            end
          end

          newimg = img.thumbnail_image width, resize_opts

          if do_fill
            actual_width = newimg.width
            actual_height = newimg.height
            if actual_width != width || actual_height != height
              if actual_width > actual_height
                ratio = actual_width.to_f / actual_height.to_f
                crop_y = 0
              elsif actual_height > actual_width
                crop_x = 0
                ratio = actual_height.to_f / actual_width.to_f
              else
                ratio = 1
              end
              blur_w = (ratio * actual_width).round
              blur_h = (ratio * actual_height).round
              crop_x = (blur_w - width) / 2
              crop_y = (blur_h - height) / 2
              blur = img.thumbnail_image blur_w, height: blur_h
              blur = blur.gaussblur 10
              x_origin = ((blur.width - actual_width) / 2).floor
              y_origin = ((blur.height - actual_height) / 2).floor
              newimg = blur.draw_image newimg, x_origin, y_origin
              if crop_x + width > blur.width || crop_y + height > blur.height
                width = width - 1
                height = height - 1
              end
              newimg = newimg.extract_area crop_x, crop_y, width, height
            end
          end

          newimg
        end

        # vips:double
        # doubles the size of the image
        private
        def vips_double(img)
          newimg = img.resize(2)
          newimg
        end

        # vips:half
        # halves the size of the image
        private
        def vips_half(img)
          newimg = img.resize(0.5)
          newimg
        end
      end
    end
  end
end