photo-cook 1.0.0
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 +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: []
|