photo-cook 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +19 -0
- data/Rakefile +32 -0
- data/lib/photo-cook/carrierwave.rb +9 -0
- data/lib/photo-cook/engine.rb +9 -0
- data/lib/photo-cook/magick-photo.rb +6 -0
- data/lib/photo-cook/middleware.rb +107 -0
- data/lib/photo-cook/resizer.rb +91 -0
- data/lib/photo-cook/version.rb +3 -0
- data/lib/photo-cook.rb +41 -0
- data/photo-cook.gemspec +16 -0
- data/vendor/assets/javascripts/photo-cook/photo-cook.js.erb +49 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6607cf1acfc43a8d2f3ec8877856f3f0e947726d
|
4
|
+
data.tar.gz: 21d67faa51dd1040a7c22250af93d20b5e61d51d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8d3fcf83ba40a11f12940f229e9ce7d5b0b94ae5530360bf23e0453cdff7a31bc9f1e465a7ce24181b9d9858d93af28b51fbcbe1f4bbc457728ccacd960183d1
|
7
|
+
data.tar.gz: 1f25bb170da95d5bf5275ea7aa6eb42ead54c531cba7527dc388ed65349c7267a946f5fd1d58d46543d4842580e99d2ac3a106790ea0f875d9507716b928b79d
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "https://rubygems.org"
|
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
|
+
gemspec
|
7
|
+
|
8
|
+
# Declare any dependencies that are still in development here instead of in
|
9
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
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'
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
photo-cook (1.0.0)
|
5
|
+
mini_magick (~> 4.0)
|
6
|
+
rack (~> 1.5)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
mini_magick (4.1.0)
|
12
|
+
rack (1.5.5)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
ruby
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
mini_magick
|
19
|
+
photo-cook!
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
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
|
+
Bundler::GemHelper.install_tasks
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
|
24
|
+
Rake::TestTask.new(:test) do |t|
|
25
|
+
t.libs << 'lib'
|
26
|
+
t.libs << 'test'
|
27
|
+
t.pattern = 'test/**/*_test.rb'
|
28
|
+
t.verbose = false
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# To use this middleware you should configure application:
|
2
|
+
# application.config.middleware.insert_before(Rack::Sendfile, PhotoCook::Middleware, Rails.root)
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module PhotoCook
|
6
|
+
class Middleware
|
7
|
+
|
8
|
+
def initialize(app, root)
|
9
|
+
@app, @root = app, root
|
10
|
+
end
|
11
|
+
|
12
|
+
# The example will be concentrated around uri:
|
13
|
+
# /uploads/photos/resized/car-640x320crop.png
|
14
|
+
def call(env)
|
15
|
+
uri = extract_uri(env)
|
16
|
+
|
17
|
+
return default_actions(env) unless
|
18
|
+
|
19
|
+
# Lets ensure that directory matches PhotoCook.resize_dir
|
20
|
+
# dir = /uploads/photos/resized
|
21
|
+
# File::SEPARATOR + PhotoCook.resize_dir = /resized
|
22
|
+
# dir.chomp! = /uploads/photos
|
23
|
+
(dir = File.dirname(uri)).chomp!(File::SEPARATOR + PhotoCook.resize_dir) &&
|
24
|
+
|
25
|
+
# Lets ensure that photo_basename ends with resize command
|
26
|
+
# photo_name = car-640x320crop.png
|
27
|
+
# photo_basename = car-640x320crop
|
28
|
+
# photo_basename.sub! = car.png
|
29
|
+
(photo_name = File.basename(uri)) &&
|
30
|
+
(photo_basename = File.basename(photo_name, '.*')).sub!(command_regex, '')
|
31
|
+
|
32
|
+
return default_actions(env) if requested_file_exists?(uri)
|
33
|
+
|
34
|
+
# Regex match: _640x320crop
|
35
|
+
command = Regexp.last_match
|
36
|
+
|
37
|
+
# At this point we are sure that this request is targeting to resize photo
|
38
|
+
|
39
|
+
# Lets assemble path of the source photo
|
40
|
+
source_path = assemble_source_path(dir, photo_basename + File.extname(photo_name))
|
41
|
+
|
42
|
+
# Finally resize photo
|
43
|
+
resizer = PhotoCook::Resizer.instance
|
44
|
+
|
45
|
+
# Resizer will store photo in resize directory
|
46
|
+
photo = resizer.resize source_path, command[:width].to_i, command[:height].to_i, !!command[:crop]
|
47
|
+
|
48
|
+
if photo
|
49
|
+
log(photo, source_path, uri, command) if defined?(Rails)
|
50
|
+
|
51
|
+
# http://rubylogs.com/writing-rails-middleware/
|
52
|
+
# https://viget.com/extend/refactoring-patterns-the-rails-middleware-response-handler
|
53
|
+
status, headers, body = Rack::File.new(File.join(@root, PhotoCook.public_dir)).call(env)
|
54
|
+
response = Rack::Response.new(body, status, headers)
|
55
|
+
response.finish
|
56
|
+
else
|
57
|
+
default_actions(env)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def assemble_source_path(dir, photo_name)
|
64
|
+
URI.decode File.join(@root, PhotoCook.public_dir, dir, photo_name)
|
65
|
+
end
|
66
|
+
|
67
|
+
def requested_file_exists?(uri)
|
68
|
+
# /my_awesome_project_root/public/uploads/photos/resized/car-640x320crop.png
|
69
|
+
File.exists? File.join(@root, PhotoCook.public_dir, uri)
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_uri(env)
|
73
|
+
Rack::Utils.unescape(env['PATH_INFO'])
|
74
|
+
end
|
75
|
+
|
76
|
+
def default_actions(env)
|
77
|
+
@app.call(env)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Proportional support
|
81
|
+
# http://stackoverflow.com/questions/7200909/imagemagick-convert-to-fixed-width-proportional-height
|
82
|
+
def command_regex
|
83
|
+
unless @r_command
|
84
|
+
w = /(?<width>\d+)/
|
85
|
+
h = /(?<height>\d+)/
|
86
|
+
@r_command = %r{
|
87
|
+
\- (?:(?:#{w}x#{h}) | (?:#{w}x) | (?:x#{h})) (?<crop>crop)? \z
|
88
|
+
}x
|
89
|
+
end
|
90
|
+
@r_command
|
91
|
+
end
|
92
|
+
|
93
|
+
def log(photo, source_path, resized_path, command)
|
94
|
+
w = command[:width].to_i
|
95
|
+
h = command[:height].to_i
|
96
|
+
crop = !!command[:crop]
|
97
|
+
Rails.logger.info %{
|
98
|
+
[PhotoCook] Resized photo.
|
99
|
+
Source file: "#{source_path}".
|
100
|
+
Resized file: "#{resized_path}".
|
101
|
+
Width: #{w == 0 ? 'auto': "#{w}px"}.
|
102
|
+
Height: #{h == 0 ? 'auto': "#{h}px"}.
|
103
|
+
Crop: #{crop ? 'yes' : 'no'}.
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Resize algorithms from
|
2
|
+
# https://github.com/carrierwaveuploader/carrierwave/blob/71cb18bba4a2078524d1ea683f267d3a97aa9bc8/lib/carrierwave/processing/rmagick.rb
|
3
|
+
|
4
|
+
module PhotoCook
|
5
|
+
class Resizer
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
CENTER_GRAVITY = 'Center'
|
9
|
+
TRANSPARENT_BACKGROUND = 'rgba(255,255,255,0.0)'
|
10
|
+
PHOTO_QUALITY = 100
|
11
|
+
|
12
|
+
def resize(photo_path, width, height, crop = false)
|
13
|
+
if crop
|
14
|
+
resize_to_fill photo_path, width, height
|
15
|
+
else
|
16
|
+
resize_to_fit photo_path, width, height
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Resize the photo to fit within the specified dimensions:
|
21
|
+
# - the original aspect ratio will be kept
|
22
|
+
# - new dimensions will be not larger then the specified
|
23
|
+
def resize_to_fit(photo_path, width, height)
|
24
|
+
# Do nothing if photo is not valid so exceptions will be not thrown
|
25
|
+
return unless (photo = open(photo_path)).try(:valid?)
|
26
|
+
|
27
|
+
photo.combine_options do |cmd|
|
28
|
+
cmd.quality PHOTO_QUALITY
|
29
|
+
cmd.resize "#{width == 0 ? nil : width}x#{height == 0 ? nil : height}"
|
30
|
+
end
|
31
|
+
|
32
|
+
store photo, PhotoCook.assemble_path(photo_path, width, height, false)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Resize the photo to fit within the specified dimensions:
|
36
|
+
# - new dimensions will be the same as specified
|
37
|
+
# - the photo will be cropped if necessary
|
38
|
+
def resize_to_fill(photo_path, width, height)
|
39
|
+
# Do nothing if photo is not valid so exceptions will be not thrown
|
40
|
+
return unless (photo = open(photo_path)).try(:valid?)
|
41
|
+
|
42
|
+
cols, rows = photo[:dimensions]
|
43
|
+
photo.combine_options do |cmd|
|
44
|
+
if width != cols || height != rows
|
45
|
+
scale_x = width / cols.to_f
|
46
|
+
scale_y = height / rows.to_f
|
47
|
+
if scale_x >= scale_y
|
48
|
+
cols = (scale_x * (cols + 0.5)).round
|
49
|
+
rows = (scale_x * (rows + 0.5)).round
|
50
|
+
cmd.resize "#{cols}"
|
51
|
+
else
|
52
|
+
cols = (scale_y * (cols + 0.5)).round
|
53
|
+
rows = (scale_y * (rows + 0.5)).round
|
54
|
+
cmd.resize "x#{rows}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
cmd.gravity CENTER_GRAVITY
|
58
|
+
cmd.background TRANSPARENT_BACKGROUND
|
59
|
+
cmd.quality PHOTO_QUALITY
|
60
|
+
cmd.extent "#{width}x#{height}" if cols != width || rows != height
|
61
|
+
end
|
62
|
+
|
63
|
+
store photo, PhotoCook.assemble_path(photo_path, width, height, true)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def open(photo_path)
|
69
|
+
begin
|
70
|
+
# MiniMagick::Image.open creates a temporary file for us and protects original
|
71
|
+
photo = MagickPhoto.open(photo_path)
|
72
|
+
photo.source_path = photo_path
|
73
|
+
photo
|
74
|
+
rescue
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def store(resized_photo, path_to_store_at)
|
80
|
+
dir = File.dirname path_to_store_at
|
81
|
+
Dir.mkdir dir unless File.exists?(dir)
|
82
|
+
resized_photo.write path_to_store_at
|
83
|
+
resized_photo.resized_path = path_to_store_at
|
84
|
+
resized_photo
|
85
|
+
end
|
86
|
+
|
87
|
+
def normalize_dimensions(w, h)
|
88
|
+
[w.to_i == 0 ? nil : w.to_i, h.to_i == 0 ? nil : h.to_i]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/photo-cook.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module PhotoCook
|
2
|
+
|
3
|
+
mattr_writer :public_dir
|
4
|
+
mattr_writer :resize_dir
|
5
|
+
|
6
|
+
def self.public_dir
|
7
|
+
@public_dir || 'public'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.resize_dir
|
11
|
+
@resize_dir || 'resized'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.resize(*args)
|
15
|
+
assemble_path(*args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.assemble_path(path, width, height, crop = false)
|
19
|
+
File.join PhotoCook.assemble_dir(path), PhotoCook.assemble_name(path, width, height, crop)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.assemble_dir(path)
|
23
|
+
File.join File.dirname(path), PhotoCook.resize_dir
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.assemble_name(path, width, height, crop = false)
|
27
|
+
File.basename(path, '.*') + PhotoCook.assemble_command(width, height, crop) + File.extname(path)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.assemble_command(width, height, crop = false)
|
31
|
+
prefix = "-#{width == 0 ? '' : width}x#{height == 0 ? '' : height}"
|
32
|
+
prefix + (crop ? 'crop' : '')
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
require 'photo-cook/engine' if defined?(Rails)
|
38
|
+
require 'photo-cook/resizer'
|
39
|
+
require 'photo-cook/middleware'
|
40
|
+
require 'photo-cook/carrierwave'
|
41
|
+
require 'photo-cook/magick-photo'
|
data/photo-cook.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path('../lib/photo-cook/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'photo-cook'
|
5
|
+
s.version = PhotoCook::VERSION
|
6
|
+
s.authors = ['Yaroslav Konoplov']
|
7
|
+
s.email = ['yaroslav@inbox.com']
|
8
|
+
s.homepage = 'http://github.com/yivo/photo-cook'
|
9
|
+
s.summary = 'Simple solution for photo resizing'
|
10
|
+
s.description = 'This is a simple solution for photo resizing.'
|
11
|
+
s.license = 'MIT'
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
|
14
|
+
s.add_dependency 'rack', '~> 1.5'
|
15
|
+
s.add_dependency 'mini_magick', '~> 4.0'
|
16
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
window.PhotoCook = {
|
2
|
+
resizeDir: function() {
|
3
|
+
return <%= PhotoCook.resize_dir.to_json %>;
|
4
|
+
},
|
5
|
+
|
6
|
+
pixelRatio: (function() {
|
7
|
+
var mediaQuery = '(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)';
|
8
|
+
return function() {
|
9
|
+
var ratio = window.devicePixelRatio;
|
10
|
+
|
11
|
+
// If no ratio found check if screen is retina
|
12
|
+
// and if so return 2x ratio
|
13
|
+
if (ratio == null && typeof window.matchMedia === 'function') {
|
14
|
+
if (window.matchMedia(mediaQuery).matches) {
|
15
|
+
ratio = 2;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
return ratio != null ? ratio : 1;
|
20
|
+
};
|
21
|
+
})()
|
22
|
+
};
|
23
|
+
|
24
|
+
window.PhotoCook.resize = function(path, width, height, crop) {
|
25
|
+
if (path == null) { return path; }
|
26
|
+
|
27
|
+
var ratio = PhotoCook.pixelRatio();
|
28
|
+
var command = '-' + (width ? Math.round(width * ratio) : '')
|
29
|
+
+ 'x'
|
30
|
+
+ (height ? Math.round(height * ratio) : '')
|
31
|
+
+ (crop ? 'crop' : '');
|
32
|
+
|
33
|
+
var directory = path.slice(0, Math.max(path.lastIndexOf('/'), 0));
|
34
|
+
|
35
|
+
var index = path.lastIndexOf('.');
|
36
|
+
var extension = index >= 0 ? path.slice(index) : '';
|
37
|
+
|
38
|
+
var basename = path.slice(
|
39
|
+
directory ? directory.length + 1 : 0,
|
40
|
+
extension ? 0 - extension.length : path.length
|
41
|
+
);
|
42
|
+
|
43
|
+
return (directory ? directory + '/' : directory)
|
44
|
+
+ PhotoCook.resizeDir()
|
45
|
+
+ '/'
|
46
|
+
+ basename
|
47
|
+
+ command
|
48
|
+
+ extension;
|
49
|
+
};
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: photo-cook
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yaroslav Konoplov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mini_magick
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
description: This is a simple solution for photo resizing.
|
42
|
+
email:
|
43
|
+
- yaroslav@inbox.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- Gemfile
|
50
|
+
- Gemfile.lock
|
51
|
+
- Rakefile
|
52
|
+
- lib/photo-cook.rb
|
53
|
+
- lib/photo-cook/carrierwave.rb
|
54
|
+
- lib/photo-cook/engine.rb
|
55
|
+
- lib/photo-cook/magick-photo.rb
|
56
|
+
- lib/photo-cook/middleware.rb
|
57
|
+
- lib/photo-cook/resizer.rb
|
58
|
+
- lib/photo-cook/version.rb
|
59
|
+
- photo-cook.gemspec
|
60
|
+
- vendor/assets/javascripts/photo-cook/photo-cook.js.erb
|
61
|
+
homepage: http://github.com/yivo/photo-cook
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.4.8
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: Simple solution for photo resizing
|
85
|
+
test_files: []
|