photo-cook 1.1.2 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +16 -0
- data/Gemfile +3 -13
- data/README.md +2 -2
- data/Rakefile +2 -15
- data/app/assets/javascripts/photo-cook/photo-cook.coffee +90 -0
- data/app/helpers/photo_cook_helper.rb +14 -0
- data/lib/photo-cook/device-pixel-ratio-spy.rb +17 -0
- data/lib/photo-cook/device-pixel-ratio.rb +67 -0
- data/lib/photo-cook/engine.rb +15 -2
- data/lib/photo-cook/events.rb +15 -0
- data/lib/photo-cook/logger.rb +37 -0
- data/lib/photo-cook/optimization/__api__.rb +21 -0
- data/lib/photo-cook/optimization/carrierwave.rb +10 -0
- data/lib/photo-cook/optimization/image-optim.rb +131 -0
- data/lib/photo-cook/optimization/job.rb +12 -0
- data/lib/photo-cook/optimization/logging.rb +21 -0
- data/lib/photo-cook/pixels.rb +56 -0
- data/lib/photo-cook/resize/__api__.rb +93 -0
- data/lib/photo-cook/resize/assemble.rb +125 -0
- data/lib/photo-cook/resize/calculations.rb +63 -0
- data/lib/photo-cook/resize/carrierwave.rb +22 -0
- data/lib/photo-cook/resize/command.rb +28 -0
- data/lib/photo-cook/resize/logging.rb +25 -0
- data/lib/photo-cook/resize/magick-photo.rb +34 -0
- data/lib/photo-cook/resize/middleware.rb +108 -0
- data/lib/photo-cook/resize/mode.rb +36 -0
- data/lib/photo-cook/resize/resizer.rb +88 -0
- data/lib/photo-cook/utils.rb +71 -0
- data/lib/photo-cook/version.rb +3 -2
- data/lib/photo-cook.rb +32 -23
- data/photo-cook.gemspec +14 -8
- data/srcset.rb +34 -0
- data/test/fixtures/dog.png +0 -0
- data/test/helper.rb +12 -0
- data/test/test-photo-cook-assemble.rb +23 -0
- data/test/test-photo-cook-calculations.rb +45 -0
- data/test/test-photo-cook-resize.rb +62 -0
- metadata +86 -25
- data/app/assets/javascripts/photo-cook/photo-cook.js.erb +0 -85
- data/lib/photo-cook/abstract-optimizer.rb +0 -9
- data/lib/photo-cook/assemble.rb +0 -97
- data/lib/photo-cook/carrierwave.rb +0 -15
- data/lib/photo-cook/command.rb +0 -49
- data/lib/photo-cook/dimensions.rb +0 -36
- data/lib/photo-cook/dirs.rb +0 -8
- data/lib/photo-cook/image-optim.rb +0 -24
- data/lib/photo-cook/logging.rb +0 -85
- data/lib/photo-cook/magick-photo.rb +0 -6
- data/lib/photo-cook/middleware.rb +0 -139
- data/lib/photo-cook/optimization-api.rb +0 -20
- data/lib/photo-cook/optimization-job.rb +0 -9
- data/lib/photo-cook/pixel-ratio-spy.rb +0 -19
- data/lib/photo-cook/pixel-ratio.rb +0 -36
- data/lib/photo-cook/resize-api.rb +0 -94
- data/lib/photo-cook/resizer.rb +0 -111
- data/lib/photo-cook/size-formatting.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54e3edc9dd1cca1b0bb175a4d1015b220f01d12e
|
4
|
+
data.tar.gz: 83bd26d3887b920c1a289d92cc4a274bada08c8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc6465991aac047ceb19817f305ade9d39949e6aa737b7fa82419b6bed5a3c5203907c29516a852472247155371112eefd823082a9d701315bf72158edd54059
|
7
|
+
data.tar.gz: a58e5f69881238958e46fd6b8379cbc49de2d67c94b417b3bccb45464558693660941d452ea2023515734acd373b5dd1146745d0f52d634685229dd9061b20e4
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -1,16 +1,6 @@
|
|
1
|
-
source
|
1
|
+
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Declare your gem's dependencies in photo_cook.gemspec.
|
4
|
-
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
-
# development dependencies will be added by default to the :development group.
|
6
3
|
gemspec
|
7
4
|
|
8
|
-
|
9
|
-
|
10
|
-
# Git. Remember to move these dependencies to your gemspec before releasing
|
11
|
-
# your gem to rubygems.org.
|
12
|
-
|
13
|
-
# To use debugger
|
14
|
-
# gem 'debugger'
|
15
|
-
|
16
|
-
gem 'mini_magick'
|
5
|
+
gem 'test-unit', '~> 3.1'
|
6
|
+
gem 'shoulda-context', '~> 1.2'
|
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -4,19 +4,6 @@ rescue LoadError
|
|
4
4
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
5
|
end
|
6
6
|
|
7
|
-
require 'rdoc/task'
|
8
|
-
|
9
|
-
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
-
rdoc.rdoc_dir = 'rdoc'
|
11
|
-
rdoc.title = 'PhotoCook'
|
12
|
-
rdoc.options << '--line-numbers'
|
13
|
-
rdoc.rdoc_files.include('README.rdoc')
|
14
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
-
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
7
|
Bundler::GemHelper.install_tasks
|
21
8
|
|
22
9
|
require 'rake/testtask'
|
@@ -24,9 +11,9 @@ require 'rake/testtask'
|
|
24
11
|
Rake::TestTask.new(:test) do |t|
|
25
12
|
t.libs << 'lib'
|
26
13
|
t.libs << 'test'
|
27
|
-
t.pattern = 'test
|
14
|
+
t.pattern = 'test/**/test*.rb'
|
28
15
|
t.verbose = false
|
29
16
|
end
|
30
17
|
|
31
|
-
|
18
|
+
desc 'Run test suite'
|
32
19
|
task default: :test
|
@@ -0,0 +1,90 @@
|
|
1
|
+
@PhotoCook =
|
2
|
+
|
3
|
+
initialize: -> try PhotoCook.persistDevicePixelRatio()
|
4
|
+
|
5
|
+
resizeCacheDir: do ->
|
6
|
+
if document? and (head = document.getElementsByTagName('head')[0])?
|
7
|
+
for el in head.getElementsByTagName('meta')
|
8
|
+
if el.getAttribute('name') is 'photo_cook:resize:cache_dir'
|
9
|
+
break if value = el.getAttribute('content')
|
10
|
+
value or 'resize-cache'
|
11
|
+
|
12
|
+
resizeCommandRegex: /^fit|fill\-\d+x\d+$/
|
13
|
+
|
14
|
+
# Returns device pixel ratio (float)
|
15
|
+
# If no ratio could be determined will return normal ratio (1.0)
|
16
|
+
devicePixelRatio: do ->
|
17
|
+
# https://gist.github.com/marcedwards/3446599
|
18
|
+
mediaQuery = [
|
19
|
+
'(-webkit-min-device-pixel-ratio: 1.3)'
|
20
|
+
'(-o-min-device-pixel-ratio: 13/10)'
|
21
|
+
'min-resolution: 120dpi'
|
22
|
+
].join(', ')
|
23
|
+
|
24
|
+
ratio = window?.devicePixelRatio
|
25
|
+
|
26
|
+
# If no ratio found check if screen is retina
|
27
|
+
# and if so return 2x ratio
|
28
|
+
if not ratio and window?.matchMedia?(mediaQuery).matches
|
29
|
+
ratio = 2.0
|
30
|
+
|
31
|
+
parseFloat(ratio) or 1.0
|
32
|
+
|
33
|
+
resizeMultiplier: @devicePixelRatio
|
34
|
+
|
35
|
+
persistDevicePixelRatio: ->
|
36
|
+
date = new Date()
|
37
|
+
|
38
|
+
# Expires in 1 year
|
39
|
+
date.setTime(date.getTime() + 365 * 24 * 60 * 60 * 1000)
|
40
|
+
expires = 'expires=' + date.toUTCString()
|
41
|
+
document.cookie = 'DevicePixelRatio=' + PhotoCook.devicePixelRatio + '; ' + expires
|
42
|
+
return
|
43
|
+
|
44
|
+
resize: (path, width, height, mode, options) ->
|
45
|
+
multiplier = options?.multiplier or PhotoCook.resizeMultiplier
|
46
|
+
command = "#{mode or 'fit'}-#{Math.floor(width * multiplier)}x#{Math.floor(height * multiplier)}"
|
47
|
+
pathTokens = path.split('/');
|
48
|
+
pathTokens.splice(-1, 0, PhotoCook.resizeCacheDir, command)
|
49
|
+
pathTokens.join('/')
|
50
|
+
|
51
|
+
strip: (uri) ->
|
52
|
+
sections = uri.split('/')
|
53
|
+
length = sections.length
|
54
|
+
return uri if length < 3
|
55
|
+
|
56
|
+
cacheDir = sections[length - 3];
|
57
|
+
command = sections[length - 2];
|
58
|
+
|
59
|
+
return uri unless cacheDir is PhotoCook.resizeCacheDir
|
60
|
+
return uri unless PhotoCook.resizeCommandRegex.test(command)
|
61
|
+
|
62
|
+
sections.splice(length - 3, 2)
|
63
|
+
sections.join('/')
|
64
|
+
|
65
|
+
uriRegex: /^[-a-z]+:\/\/|^(?:cid|data):|^\/\//i
|
66
|
+
|
67
|
+
# Returns true if given URL can produce request to PhotoCook middleware on server
|
68
|
+
# This is important thing you probably should override when using CDN or different assets delivery method
|
69
|
+
isServableURL: (url) ->
|
70
|
+
# By default check that URL is relative
|
71
|
+
!PhotoCook.uriRegex.test(url)
|
72
|
+
|
73
|
+
sizeToFit: (maxw, maxh, reqw, reqh, round) ->
|
74
|
+
outw = maxw
|
75
|
+
outh = maxh
|
76
|
+
|
77
|
+
scale = if outw > reqw then reqw / outw else 1.0
|
78
|
+
outw *= scale
|
79
|
+
outh *= scale
|
80
|
+
|
81
|
+
scale = if outh > reqh then reqh / outh else 1.0
|
82
|
+
outw *= scale
|
83
|
+
outh *= scale
|
84
|
+
|
85
|
+
if !round? or round # (null or undefined) OR true-value
|
86
|
+
[Math.floor(outw), Math.floor(outh)]
|
87
|
+
else
|
88
|
+
[outw, outh]
|
89
|
+
|
90
|
+
PhotoCook.initialize()
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module PhotoCookHelper
|
3
|
+
def device_pixel_ratio
|
4
|
+
PhotoCook.device_pixel_ratio
|
5
|
+
end
|
6
|
+
|
7
|
+
def resize_multiplier
|
8
|
+
PhotoCook::Resize.multiplier
|
9
|
+
end
|
10
|
+
|
11
|
+
def photo_cook_cache_key
|
12
|
+
"resize_multiplier:#{resize_multiplier}"
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module PhotoCook
|
3
|
+
module DevicePixelRatioSpy
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included { before_action :pass_device_pixel_ratio }
|
7
|
+
|
8
|
+
def pass_device_pixel_ratio
|
9
|
+
ratio = PhotoCook::DevicePixelRatio.parse(cookies[:DevicePixelRatio])
|
10
|
+
PhotoCook.device_pixel_ratio = if PhotoCook::DevicePixelRatio.valid?(ratio)
|
11
|
+
ratio
|
12
|
+
else
|
13
|
+
PhotoCook::DevicePixelRatio::DEFAULT
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module PhotoCook
|
3
|
+
module DevicePixelRatio
|
4
|
+
DEFAULT = 1.0.freeze
|
5
|
+
MAX = 4.0.freeze
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def parse(x)
|
9
|
+
typecast(x)
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse!(x)
|
13
|
+
check!(x = parse(x))
|
14
|
+
x
|
15
|
+
end
|
16
|
+
|
17
|
+
def check!(x)
|
18
|
+
x = typecast(x)
|
19
|
+
raise Invalid, x if !x.kind_of?(Integer) && (x.nan? || x.infinite?)
|
20
|
+
raise OutOfBounds, x if x < DEFAULT || x > MAX
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Do not produce various number of pixel ratios:
|
25
|
+
# 1.0 => 1
|
26
|
+
# 2.5 => 3
|
27
|
+
# 2.1 => 3
|
28
|
+
# 3.1 => 4
|
29
|
+
def unify(x)
|
30
|
+
typecast(x).ceil
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid?(x)
|
34
|
+
check!(x)
|
35
|
+
rescue Invalid, OutOfBounds
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def typecast(x)
|
40
|
+
x.kind_of?(Numeric) ? x : x.to_f
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Invalid < ArgumentError
|
45
|
+
def initialize(*)
|
46
|
+
super 'Device pixel ratio is invalid (NaN) or infinite number'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class OutOfBounds < ArgumentError
|
51
|
+
def initialize(*)
|
52
|
+
super 'Device pixel ratio must be positive number (integer or float) ' +
|
53
|
+
"which satisfies #{DEFAULT} <= x <= #{MAX}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
def device_pixel_ratio
|
60
|
+
@device_pixel_ratio || DevicePixelRatio::DEFAULT
|
61
|
+
end
|
62
|
+
|
63
|
+
def device_pixel_ratio=(x)
|
64
|
+
@device_pixel_ratio = DevicePixelRatio.parse!(x)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/photo-cook/engine.rb
CHANGED
@@ -1,7 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module PhotoCook
|
2
3
|
class Engine < ::Rails::Engine
|
3
|
-
initializer :
|
4
|
+
initializer :photo_cook_root do |app|
|
5
|
+
PhotoCook.root_path = Rails.root.to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
initializer :photo_cook_logger do |app|
|
9
|
+
PhotoCook.logger = Rails.logger
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer :photo_cook_assets do |app|
|
4
13
|
app.config.assets.paths << File.join(PhotoCook::Engine.root, 'app/assets/javascripts')
|
5
14
|
end
|
15
|
+
|
16
|
+
config.before_initialize do |app|
|
17
|
+
app.config.middleware.insert_before(Rack::Sendfile, PhotoCook::Resize::Middleware)
|
18
|
+
end
|
6
19
|
end
|
7
|
-
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module PhotoCook
|
3
|
+
class << self
|
4
|
+
def notify(event, *params)
|
5
|
+
blocks = @events && @events[event.to_s]
|
6
|
+
blocks && blocks.each { |blk| Utils.call_block_with_floating_arguments(blk, params) }
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def subscribe(event, &block)
|
11
|
+
((@events ||= {})[event.to_s] ||= []) << block
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module PhotoCook
|
3
|
+
class << self
|
4
|
+
attr_accessor :logger
|
5
|
+
attr_accessor :logger_evaluator
|
6
|
+
|
7
|
+
def log(&block)
|
8
|
+
logger_evaluator.instance_eval do
|
9
|
+
log "\n"
|
10
|
+
log '--- PhotoCook ---'
|
11
|
+
instance_eval(&block)
|
12
|
+
log '---'
|
13
|
+
end if @logging_enabled
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def enable_logging!
|
18
|
+
@logging_enabled = true
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def disable_logging!
|
23
|
+
@logging_enabled = false
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class LoggerEvaluator
|
29
|
+
def log(msg)
|
30
|
+
PhotoCook.logger.info(msg)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
self.logger = Logger.new(STDOUT)
|
35
|
+
self.logger_evaluator = LoggerEvaluator.new
|
36
|
+
enable_logging!
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module PhotoCook
|
3
|
+
module Optimization
|
4
|
+
class << self
|
5
|
+
# TODO PhotoCook::Optimization.optimizer = :image_optim
|
6
|
+
attr_accessor :optimizer
|
7
|
+
|
8
|
+
def perform(path)
|
9
|
+
if File.readable?(path) && (optimizer = self.optimizer)
|
10
|
+
result, msec = PhotoCook::Utils.measure { optimizer.optimize(path) }
|
11
|
+
params = [path]
|
12
|
+
params.push(result[:before], result[:after], msec) if result
|
13
|
+
PhotoCook.notify("optimization:#{result ? 'success' : 'failure'}", *params)
|
14
|
+
!!result
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module PhotoCook
|
3
|
+
module Optimization
|
4
|
+
class ImageOptim
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def optimize(path)
|
8
|
+
result = image_optim.optimize_image!(path)
|
9
|
+
if result.kind_of?(::ImageOptim::ImagePath::Optimized)
|
10
|
+
{ before: result.original_size, after: result.size }
|
11
|
+
else
|
12
|
+
false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
# https://github.com/toy/image_optim
|
18
|
+
def image_optim
|
19
|
+
@image_optim ||= ::ImageOptim.new(
|
20
|
+
|
21
|
+
# Nice level (defaults to 10)
|
22
|
+
nice: 10,
|
23
|
+
|
24
|
+
# Number of threads or disable (defaults to number of processors)
|
25
|
+
threads: begin
|
26
|
+
match = if OS.osx?
|
27
|
+
`sysctl -a | grep machdep.cpu | grep thread_count`
|
28
|
+
else
|
29
|
+
`cat /proc/cpuinfo | grep "cpu cores"`
|
30
|
+
end
|
31
|
+
$~.to_s.to_i if match.to_s =~ /\d{1,2}/
|
32
|
+
end,
|
33
|
+
|
34
|
+
# Verbose output (defaults to false)
|
35
|
+
verbose: false,
|
36
|
+
|
37
|
+
# Require image_optim_pack or disable it, by default image_optim_pack will be used if available, will turn on :skip-missing-workers unless explicitly disabled (defaults to nil)
|
38
|
+
pack: nil,
|
39
|
+
|
40
|
+
# Skip workers with missing or problematic binaries (defaults to false)
|
41
|
+
skip_missing_workers: nil,
|
42
|
+
|
43
|
+
# Allow lossy workers and optimizations (defaults to false)
|
44
|
+
allow_lossy: false,
|
45
|
+
|
46
|
+
advpng: {
|
47
|
+
# Compression level: 0 - don't compress, 1 - fast, 2 - normal, 3 - extra, 4 - extreme (defaults to 4)
|
48
|
+
level: 1
|
49
|
+
},
|
50
|
+
gifsicle: {
|
51
|
+
# Interlace: true - interlace on, false - interlace off, nil - as is in original image (defaults to running two instances, one with interlace off and one with on)
|
52
|
+
interlace: nil,
|
53
|
+
|
54
|
+
# Compression level: 1 - light and fast, 2 - normal, 3 - heavy (slower) (defaults to 3)
|
55
|
+
level: 1,
|
56
|
+
|
57
|
+
# Avoid bugs with some software (defaults to false)
|
58
|
+
careful: false
|
59
|
+
},
|
60
|
+
jhead: {},
|
61
|
+
jpegoptim: {
|
62
|
+
# List of extra markers to strip: :comments, :exif, :iptc, :icc or :all (defaults to :all)
|
63
|
+
strip: :all,
|
64
|
+
|
65
|
+
# Maximum image quality factor 0..100, ignored in default/lossless mode (defaults to 100)
|
66
|
+
max_quality: 100
|
67
|
+
},
|
68
|
+
jpegrecompress: {
|
69
|
+
# JPEG quality preset: 0 - low, 1 - medium, 2 - high, 3 - veryhigh (defaults to 3)
|
70
|
+
quality: 3
|
71
|
+
},
|
72
|
+
jpegtran: {
|
73
|
+
|
74
|
+
# Copy all chunks
|
75
|
+
copy_chunks: false,
|
76
|
+
|
77
|
+
# Create progressive JPEG file
|
78
|
+
progressive: true,
|
79
|
+
|
80
|
+
# Use jpegtran through jpegrescan, ignore progressive option
|
81
|
+
jpegrescan: false
|
82
|
+
},
|
83
|
+
optipng: {
|
84
|
+
# Optimization level preset: 0 is least, 7 is best
|
85
|
+
level: 3,
|
86
|
+
|
87
|
+
# Interlace: true - interlace on, false - interlace off, nil - as is in original image
|
88
|
+
interlace: false,
|
89
|
+
|
90
|
+
# Remove all auxiliary chunks (defaults to true)
|
91
|
+
strip: true
|
92
|
+
},
|
93
|
+
pngcrush: {
|
94
|
+
# List of chunks to remove or :alla - all except tRNS/transparency or :allb - all except tRNS and gAMA/gamma (defaults to :alla)
|
95
|
+
chunks: :alla,
|
96
|
+
|
97
|
+
# Fix otherwise fatal conditions such as bad CRCs (defaults to false)
|
98
|
+
fix: false,
|
99
|
+
|
100
|
+
# Brute force try all methods, very time-consuming and generally not worthwhile (defaults to false)
|
101
|
+
brute: false,
|
102
|
+
|
103
|
+
# Blacken fully transparent pixels (defaults to true)
|
104
|
+
blacken: true
|
105
|
+
},
|
106
|
+
pngout: {
|
107
|
+
# Copy optional chunks (defaults to false)
|
108
|
+
copy_chunks: false,
|
109
|
+
|
110
|
+
# Strategy: 0 - xtreme, 1 - intense, 2 - longest Match, 3 - huffman Only, 4 - uncompressed (defaults to 0)
|
111
|
+
strategy: 2
|
112
|
+
},
|
113
|
+
pngquant: {
|
114
|
+
# min..max - don't save below min, use less colors below max (both in range 0..100; in yaml - !ruby/range 0..100), ignored in default/lossless mode (defaults to 100..100, 0..100 in lossy mode)
|
115
|
+
quality: 100..100,
|
116
|
+
|
117
|
+
# speed/quality trade-off: 1 - slow, 3 - default, 11 - fast & rough (defaults to 3)
|
118
|
+
speed: 11
|
119
|
+
},
|
120
|
+
svgo: {
|
121
|
+
# List of plugins to disable (defaults to [])
|
122
|
+
disable_plugins: [],
|
123
|
+
|
124
|
+
# List of plugins to enable (defaults to [])
|
125
|
+
enable_plugins: []
|
126
|
+
}
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
PhotoCook.subscribe 'optimization:success' do |path, original_size, new_size, msec|
|
3
|
+
PhotoCook.log do
|
4
|
+
diff = original_size - new_size
|
5
|
+
log "Optimization successfully performed"
|
6
|
+
log "File path: #{path}"
|
7
|
+
log "Original size: #{PhotoCook::Utils.format_size(original_size)}"
|
8
|
+
log "New size: #{PhotoCook::Utils.format_size(new_size)}"
|
9
|
+
log "Saved: #{PhotoCook::Utils.format_size(diff)} / #{diff} bytes / #{(diff / original_size.to_f * 100.0).round(2)}%"
|
10
|
+
log "Completed in: #{msec.round(1)}ms"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
PhotoCook.subscribe 'optimization:failure' do |path|
|
15
|
+
PhotoCook.log do
|
16
|
+
log "Optimization failed because one of the following:"
|
17
|
+
log "1) photo is already optimized;"
|
18
|
+
log "2) some problem occurred with optimization utility."
|
19
|
+
log "Related to: #{path}"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module PhotoCook
|
3
|
+
module Pixels
|
4
|
+
MAX = 4256.freeze
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def parse(x)
|
8
|
+
round(typecast(x))
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse!(x)
|
12
|
+
check!(x = parse(x))
|
13
|
+
x
|
14
|
+
end
|
15
|
+
|
16
|
+
def check!(x)
|
17
|
+
x = typecast(x)
|
18
|
+
raise Invalid, x if !x.kind_of?(Integer) && (x.nan? || x.infinite?)
|
19
|
+
raise OutOfBounds, x unless in_bounds?(x)
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def in_bounds?(x)
|
24
|
+
x = typecast(x)
|
25
|
+
0 < x && x <= MAX
|
26
|
+
end
|
27
|
+
|
28
|
+
# Standardize how dimensions are rounded in PhotoCook
|
29
|
+
def round(x)
|
30
|
+
x.floor
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns Imagemagick dimension-string
|
34
|
+
def to_magick_dimensions(width, height)
|
35
|
+
"#{width}x#{height}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def typecast(x)
|
39
|
+
x.kind_of?(Numeric) ? x : x.to_f
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class OutOfBounds < ArgumentError
|
44
|
+
def initialize(*)
|
45
|
+
super 'Size must be positive number (integer or float) ' +
|
46
|
+
"which satisfies 0 < x <= #{MAX}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Invalid < ArgumentError
|
51
|
+
def initialize(*)
|
52
|
+
super 'Size is invalid (NaN) or infinite number'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|