imogen 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/lib/imogen/auto_crop/box.rb +79 -0
- data/lib/imogen/auto_crop/edges.rb +63 -0
- data/lib/imogen/auto_crop.rb +20 -0
- data/lib/imogen/zoomable.rb +15 -0
- data/lib/imogen.rb +106 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NzMwYzg4MjlkMjE0MDBmYjYzMWZlZTZiOGZkYzcyYmE3OGU0YmQ2Yg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NTYzYjQ3ZDMyMzJjMjU1ODAxNmUyNDU4Mjc3YTFjZjY1NmIyYTM5Yw==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MmMwZWM3MDE1ZDlmZTU5Y2U3YjQ4ZWE0ZDIxMzc2ODc3NGM0NmVhYzg2YTdk
|
10
|
+
MmFiY2I4OTA2MGJkNmJlYmUxNWVlMGQ4MzZmZTQ1Yjg0ZjIwNTc3MDgyNjVh
|
11
|
+
ZGVmZTMxOGMxODc1YzBjMmI4ODU1YTkzZjdhNmYwM2JlNzYxYjc=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OTU3MWQ4MjI3NWY1ODAzNjQzZTg5YmI4MGMyZTU5MDYxOWI2MDAzZTgxODAz
|
14
|
+
YjlhYjQwOWUwN2ZjZGFmYjcxM2U0ODdiOTM1Njg3OGYxYjNhNTc3OTg3MGMw
|
15
|
+
NmNhZjc3YTQ2NGIwOWM0ZmRlNDY4N2RiMGEzOWJhMGNlOGYyODE=
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#!ruby
|
2
|
+
require 'opencv'
|
3
|
+
module Imogen::AutoCrop::Box
|
4
|
+
include OpenCV
|
5
|
+
class Best
|
6
|
+
def initialize(grayscale)
|
7
|
+
@corners = grayscale.good_features_to_track(0.3, 1.0, block_size: 3, max: 20)
|
8
|
+
if @corners.nil? or @corners.length == 0
|
9
|
+
@center = Center.new(grayscale)
|
10
|
+
else
|
11
|
+
@center = nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.distance(p1, p2)
|
16
|
+
dx = p1.x.to_i - p2.x.to_i
|
17
|
+
dy = p1.y.to_i - p2.y.to_i
|
18
|
+
return Math.sqrt((dx * dx) + (dy * dy))
|
19
|
+
end
|
20
|
+
|
21
|
+
def box()
|
22
|
+
return @center.box unless @center.nil?
|
23
|
+
c = median()
|
24
|
+
cp = BoxInfo.new(c[0], c[1],0)
|
25
|
+
total_distance = 0;
|
26
|
+
features = @corners.collect {|corner| d = Best.distance(corner, cp); total_distance += d; {x: corner.x, y: corner.y, d: d}}
|
27
|
+
mean_distance = total_distance/features.length
|
28
|
+
sigma = features.inject(0) {|memo, feature| v = feature[:d] - mean_distance; memo += (v*v)}
|
29
|
+
sigma = Math.sqrt(sigma.to_f/features.length)
|
30
|
+
# 2 sigmas would capture > 95% of normally distributed features
|
31
|
+
cp.radius = 2*sigma
|
32
|
+
cp
|
33
|
+
end
|
34
|
+
|
35
|
+
def median()
|
36
|
+
@median ||= begin
|
37
|
+
xs = []
|
38
|
+
ys = []
|
39
|
+
@corners.each {|c| xs << c.x.to_i; ys << c.y.to_i}
|
40
|
+
xs.sort!
|
41
|
+
ys.sort!
|
42
|
+
ix = 0
|
43
|
+
if (@corners.length % 2 == 0)
|
44
|
+
l = (@corners.length == 2) ? 0 : (@corners.length/2)
|
45
|
+
x = ((xs[l] + xs[l+1]) /2).floor
|
46
|
+
y = ((ys[l] + ys[l+1]) /2).floor
|
47
|
+
[x,y]
|
48
|
+
else
|
49
|
+
r = (@corners.length/2).ceil
|
50
|
+
[xs[r], ys[r]]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
class Center
|
56
|
+
def initialize(grayscale)
|
57
|
+
@center ||= [(grayscale.cols/2).floor, (grayscale.rows/2).floor]
|
58
|
+
@radius = @center.min
|
59
|
+
@ratio = @radius / @center.max
|
60
|
+
end
|
61
|
+
def box
|
62
|
+
return BoxInfo.new(@center[0],@center[1],@radius)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
class BoxInfo
|
66
|
+
attr_reader :x, :y
|
67
|
+
attr_accessor :radius
|
68
|
+
def initialize(x,y,r)
|
69
|
+
@x = x
|
70
|
+
@y = y
|
71
|
+
@radius = r
|
72
|
+
end
|
73
|
+
end
|
74
|
+
def self.info(grayscale)
|
75
|
+
dims = [grayscale.cols, grayscale.rows]
|
76
|
+
ratio = dims.min / dims.max
|
77
|
+
ratio < 0.84 ? Best.new(grayscale).box() : Center.new(grayscale).box()
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!ruby
|
2
|
+
require 'opencv'
|
3
|
+
require 'free-image'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module Imogen::AutoCrop
|
7
|
+
class Edges
|
8
|
+
include OpenCV
|
9
|
+
def initialize(src)
|
10
|
+
@xoffset = 0
|
11
|
+
@yoffset = 0
|
12
|
+
if src.is_a? FreeImage::Bitmap
|
13
|
+
img = src
|
14
|
+
@xoffset = img.width.to_f/6
|
15
|
+
@yoffset = img.height.to_f/6
|
16
|
+
@tempfile = Tempfile.new(['crop','.png'])
|
17
|
+
|
18
|
+
img.copy(@xoffset,@yoffset,img.width-@xoffset,img.height-@yoffset) do |crop|
|
19
|
+
crop.save(@tempfile.path, :png)
|
20
|
+
crop.free
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise src.class.name
|
24
|
+
end
|
25
|
+
# use bigger features on bigger images
|
26
|
+
@grayscale = CvMat.load(@tempfile.path, CV_LOAD_IMAGE_GRAYSCALE)
|
27
|
+
@xrange = (0..@grayscale.cols)
|
28
|
+
@yrange = (0..@grayscale.rows)
|
29
|
+
end
|
30
|
+
|
31
|
+
def bound_min(center)
|
32
|
+
[center.x - @xrange.min, @xrange.max - center.x, center.y - @yrange.min, @yrange.max - center.y].min
|
33
|
+
end
|
34
|
+
|
35
|
+
# returns leftX, topY, rightX, bottomY
|
36
|
+
def get(*args)
|
37
|
+
c = Imogen::AutoCrop::Box.info(@grayscale)
|
38
|
+
r = c.radius.floor
|
39
|
+
# adjust the box
|
40
|
+
coords = [c.x, c.y]
|
41
|
+
min_rad = args.max/2
|
42
|
+
unless r >= min_rad && r <= bound_min(c)
|
43
|
+
# first adjust to the lesser of max (half short dimension) and min (half requested length) radius
|
44
|
+
# this might require upscaling in rare situations to preserve bound safety
|
45
|
+
r = min_rad if r < min_rad
|
46
|
+
max_rad = [@xrange.max - @xrange.min, @yrange.max - @yrange.min].min / 2
|
47
|
+
r = max_rad if r > max_rad
|
48
|
+
# now move the center point minimally to accomodate the necessary radius
|
49
|
+
coords[0] = @xrange.max - r if (coords[0] + r) > @xrange.max
|
50
|
+
coords[0] = @xrange.min + r if (coords[0] - r) < @xrange.min
|
51
|
+
coords[1] = @yrange.max - r if (coords[1] + r) > @yrange.max
|
52
|
+
coords[1] = @yrange.min + r if (coords[1] - r) < @yrange.min
|
53
|
+
end
|
54
|
+
coords = [coords[0] + @xoffset, coords[1] + @yoffset].collect {|i| i.floor}
|
55
|
+
c = coords
|
56
|
+
|
57
|
+
return [c[0]-r, c[1]-r, c[0]+r, c[1] + r]
|
58
|
+
end
|
59
|
+
def unlink
|
60
|
+
@tempfile.unlink
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Imogen
|
2
|
+
module AutoCrop
|
3
|
+
autoload :Edges, 'imogen/auto_crop/edges'
|
4
|
+
autoload :Box, 'imogen/auto_crop/box'
|
5
|
+
def self.convert(img, dest_path, scale=768, format=:jpeg)
|
6
|
+
frame = Edges.new(img)
|
7
|
+
edges = frame.get(scale)
|
8
|
+
img.copy(*edges) do |crop|
|
9
|
+
crop.rescale(scale, scale) do |thumb|
|
10
|
+
t24 = thumb.convert_to_24bits
|
11
|
+
dst = FreeImage::File.new(dest_path)
|
12
|
+
dst.save(t24, format)
|
13
|
+
t24.free
|
14
|
+
thumb.free
|
15
|
+
end
|
16
|
+
end
|
17
|
+
frame.unlink
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#require 'image_science'
|
2
|
+
module Imogen
|
3
|
+
module Zoomable
|
4
|
+
def self.levels_for(*dims)
|
5
|
+
max = dims[0..1].max || 0
|
6
|
+
return 0 if max < 192
|
7
|
+
max_tiles = (max.to_f / 96)
|
8
|
+
Math.log2(max_tiles).floor
|
9
|
+
end
|
10
|
+
def self.convert(img, dest_path)
|
11
|
+
dst = FreeImage::File.new(dest_path)
|
12
|
+
dst.save(img, :jp2, 8)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/imogen.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'ffi'
|
3
|
+
require 'rbconfig'
|
4
|
+
require 'free-image'
|
5
|
+
module Imogen
|
6
|
+
|
7
|
+
def self.from(src_path)
|
8
|
+
FreeImage::Bitmap.open(src_path) do |img|
|
9
|
+
yield img
|
10
|
+
end
|
11
|
+
end
|
12
|
+
module Scaled
|
13
|
+
def self.convert(img, dest_path, scale=1500, format = :jpeg)
|
14
|
+
w = img.width
|
15
|
+
h = img.height
|
16
|
+
dims = (w > h) ? [1500, 1500*h/w] : [1500*w/h, 1500]
|
17
|
+
img.rescale(dims[0], dims[1]) do |scaled|
|
18
|
+
scaled = scaled.convert_to_24bits
|
19
|
+
dst = FreeImage::File.new(dest_path)
|
20
|
+
dst.save(scaled, format)
|
21
|
+
scaled.free
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
require 'imogen/auto_crop'
|
26
|
+
require 'imogen/zoomable'
|
27
|
+
|
28
|
+
def self.search_paths
|
29
|
+
@search_paths ||= begin
|
30
|
+
if ENV['FREE_IMAGE_LIBRARY_PATH']
|
31
|
+
[ ENV['FREE_IMAGE_LIBRARY_PATH'] ]
|
32
|
+
elsif FFI::Platform::IS_WINDOWS
|
33
|
+
ENV['PATH'].split(File::PATH_SEPARATOR)
|
34
|
+
else
|
35
|
+
[ '/usr/local/{lib64,lib32,lib}', '/opt/local/{lib64,lib32,lib}', '/usr/{lib64,lib32,lib}' ]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.find_lib(lib)
|
41
|
+
files = search_paths.inject(Array.new) do |array, path|
|
42
|
+
file_name = File.expand_path(File.join(path, "#{lib}.#{FFI::Platform::LIBSUFFIX}"))
|
43
|
+
array << Dir.glob(file_name)
|
44
|
+
array
|
45
|
+
end
|
46
|
+
files.flatten.compact.first
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.free_image_library_paths
|
50
|
+
@free_image_library_paths ||= begin
|
51
|
+
libs = %w{libfreeimage libfreeimage.3 FreeImage}
|
52
|
+
|
53
|
+
libs.map do |lib|
|
54
|
+
find_lib(lib)
|
55
|
+
end.compact
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
extend ::FFI::Library
|
60
|
+
|
61
|
+
if free_image_library_paths.any?
|
62
|
+
ffi_lib(*free_image_library_paths)
|
63
|
+
elsif FFI::Platform.windows?
|
64
|
+
ffi_lib("FreeImaged")
|
65
|
+
else
|
66
|
+
ffi_lib("freeimage")
|
67
|
+
end
|
68
|
+
|
69
|
+
ffi_convention :stdcall if FFI::Platform.windows?
|
70
|
+
|
71
|
+
def self.format_from(image_path)
|
72
|
+
result = FreeImage.FreeImage_GetFileType(image_path, 0)
|
73
|
+
FreeImage.check_last_error
|
74
|
+
|
75
|
+
if result == :unknown
|
76
|
+
# Try to guess the file format from the file extension
|
77
|
+
result = FreeImage.FreeImage_GetFIFFromFilename(image_path)
|
78
|
+
FreeImage.check_last_error
|
79
|
+
end
|
80
|
+
result
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.image(src_path)
|
84
|
+
|
85
|
+
flags = 0
|
86
|
+
|
87
|
+
fif = format_from(src_path)
|
88
|
+
if ((fif != :unknown) and FreeImage.FreeImage_FIFSupportsReading(fif))
|
89
|
+
ptr = FreeImage.FreeImage_Load(fif, src_path, flags)
|
90
|
+
FreeImage.check_last_error
|
91
|
+
return FreeImage::Bitmap.new(ptr, nil)
|
92
|
+
end
|
93
|
+
return nil
|
94
|
+
end
|
95
|
+
def self.with_image(src_path, &block)
|
96
|
+
|
97
|
+
flags = 0
|
98
|
+
|
99
|
+
fif = format_from(src_path)
|
100
|
+
if ((fif != :unknown) and FreeImage.FreeImage_FIFSupportsReading(fif))
|
101
|
+
ptr = FreeImage.FreeImage_Load(fif, src_path, flags)
|
102
|
+
FreeImage.check_last_error
|
103
|
+
FreeImage::Bitmap.new(ptr, nil, &block)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: imogen
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Armintor
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ruby-opencv
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description:
|
42
|
+
email: armintor@gmail.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- lib/imogen.rb
|
48
|
+
- lib/imogen/auto_crop.rb
|
49
|
+
- lib/imogen/auto_crop/box.rb
|
50
|
+
- lib/imogen/auto_crop/edges.rb
|
51
|
+
- lib/imogen/zoomable.rb
|
52
|
+
homepage: https://github.com/cul/imogen
|
53
|
+
licenses: []
|
54
|
+
metadata: {}
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 2.2.2
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: derivative generation via FreeImage and smart square thumbnail via OpenCV
|
75
|
+
test_files: []
|