imogen 0.0.3
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 +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: []
|