photo-cook 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|