photomosaic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7723e9d41a717de2d939a43a5f3e1adcf26ef177
4
+ data.tar.gz: b2dc7d9424dcf7da90c67c2b5adf712e702178a6
5
+ SHA512:
6
+ metadata.gz: d85772d554437ff18364f922d4149c8602d69e94367d1274c8ef139164da361b716982dc0bc3a844140e783303447581ce8b798d0dde29f796df826f73fd1f4f
7
+ data.tar.gz: 0a72143a5f51cf00854fe5233dee7d42fe40e2534d6e63233d25e6c15a93bad66a954fa58e60eb8f22514aa43f4fdb7d30d23750f1ba2123fb5be60735741a9d
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .envrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0
5
+ - 2.1
6
+ - 2.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in photomosaic.gemspec
4
+ gemspec
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 dtan4
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # Photomosaic
2
+ [![Build Status](https://travis-ci.org/dtan4/photomosaic.svg?branch=master)](https://travis-ci.org/dtan4/photomosaic)
3
+ [![Coverage Status](https://coveralls.io/repos/dtan4/photomosaic/badge.png)](https://coveralls.io/r/dtan4/photomosaic)
4
+ [![Code Climate](https://codeclimate.com/github/dtan4/photomosaic.png)](https://codeclimate.com/github/dtan4/photomosaic)
5
+
6
+ TODO: Write a gem description
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'photomosaic'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install photomosaic
21
+
22
+ ## Usage
23
+
24
+ TODO: Write usage instructions here
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it ( https://github.com/[my-github-username]/photomosaic/fork )
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create a new Pull Request
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "photomosaic"
4
+
5
+ Photomosaic::Client.new(ARGV).execute
Binary file
@@ -0,0 +1,70 @@
1
+ @startuml
2
+
3
+ actor User
4
+ participant Client
5
+ participant Bing
6
+ participant ImageDownloader
7
+ participant BaseImage
8
+ participant PixelImage
9
+ database Web
10
+
11
+ User -> Client : Execute command
12
+ activate Client
13
+ create Bing
14
+ Client -> Bing : << new >>
15
+ Client -> Bing : Request to search images
16
+ activate Bing
17
+ Bing -> Web : Request to search images
18
+ activate Web
19
+ Web -> Web : Search images
20
+ Web -> Bing : List of image urls
21
+ deactivate Web
22
+ Bing -> Client : List of image urls
23
+ deactivate Bing
24
+
25
+ create ImageDownloader
26
+ Client -> ImageDownloader : << new >>
27
+ Client -> ImageDownloader : Request to download images
28
+ activate ImageDownloader
29
+ ImageDownloader -> Web : Request images
30
+ activate Web
31
+ Web -> ImageDownloader : Download images
32
+ deactivate Web
33
+ ImageDownloader -> ImageDownloader : Save downloaded images
34
+ ImageDownloader -> Client: List of downloaded image paths
35
+ deactivate ImageDownloader
36
+
37
+ create BaseImage
38
+ Client -> BaseImage : << new >>
39
+ BaseImage -> BaseImage : Preprocess
40
+
41
+ create PixelImage
42
+ Client -> PixelImage : << new >>
43
+
44
+ Client -> BaseImage : Request to dispatch pixel images
45
+ activate BaseImage
46
+ BaseImage -> PixelImage : Request characteristic color
47
+ activate PixelImage
48
+ PixelImage -> PixelImage : Calculate characteristic color
49
+ PixelImage -> BaseImage : Characteristic color
50
+ deactivate PixelImage
51
+ BaseImage -> BaseImage : Pick up the "nearest" image of the pixel
52
+ BaseImage -> Client : Map of dispatched images
53
+ deactivate BaseImage
54
+
55
+ Client -> PixelImage : Request to resize to pixel size
56
+ activate PixelImage
57
+ PixelImage -> PixelImage : Resize to pixel size
58
+ PixelImage -> Client : Done
59
+ deactivate PixelImage
60
+
61
+ Client->PixelImage : Request to compose mosaic image
62
+ activate PixelImage
63
+ PixelImage->PixelImage : Compose mosaic image
64
+ PixelImage->Client : Done
65
+ deactivate PixelImage
66
+
67
+ Client->User : Done
68
+ deactivate Client
69
+
70
+ @enduml
Binary file
@@ -0,0 +1,21 @@
1
+ @startuml
2
+
3
+ package Photomosaic {
4
+ class Client
5
+ class Image
6
+ class ImageDownloader
7
+ class Options
8
+
9
+ Client o-- Options
10
+
11
+ package SearchEngine {
12
+ class Bing
13
+ }
14
+
15
+ package Color {
16
+ class HSV
17
+ class RGB
18
+ }
19
+ }
20
+
21
+ @enduml
@@ -0,0 +1,12 @@
1
+ require "photomosaic/client"
2
+ require "photomosaic/color/hsv"
3
+ require "photomosaic/color/rgb"
4
+ require "photomosaic/image"
5
+ require "photomosaic/image_downloader"
6
+ require "photomosaic/options"
7
+ require "photomosaic/search_engine/bing"
8
+ require "photomosaic/version"
9
+
10
+ module Photomosaic
11
+ # Your code goes here...
12
+ end
@@ -0,0 +1,57 @@
1
+ module Photomosaic
2
+ class Client
3
+ def initialize(argv)
4
+ @options = Photomosaic::Options.parse(argv)
5
+ end
6
+
7
+ def execute
8
+ @image_downloader = Photomosaic::ImageDownloader.new
9
+
10
+ begin
11
+ resized_images = Photomosaic::Image.resize_images(pixel_images, 40, 20)
12
+ Photomosaic::Image.create_mosaic_image(resized_images, @options.output_path)
13
+ ensure
14
+ @image_downloader.remove_save_dir
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def base_image
21
+ @base_image ||= Photomosaic::Image.preprocess_image(
22
+ @options.base_image,
23
+ @options.width,
24
+ @options.height,
25
+ @options.level,
26
+ @options.colors
27
+ )
28
+ end
29
+
30
+ def image_path_list
31
+ @image_path_list ||= @image_downloader.download_images(image_url_list)
32
+ end
33
+
34
+ def image_url_list
35
+ @image_url_list ||= search_engine.get_image_list(@options.keyword)
36
+ end
37
+
38
+ def image_list
39
+ @image_list ||= image_path_list.map do |path|
40
+ begin
41
+ Photomosaic::Image.new(path)
42
+ rescue Magick::ImageMagickError
43
+ nil
44
+ end
45
+ end.compact
46
+ end
47
+
48
+ def pixel_images
49
+ @pixel_images ||=
50
+ base_image.dispatch_images(image_list, 1, 2, @options.color_model)
51
+ end
52
+
53
+ def search_engine
54
+ @options.search_engine.new(@options.api_key, @options.results)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,27 @@
1
+ module Photomosaic
2
+ module Color
3
+ class HSV
4
+ attr_reader :hue, :saturation, :value
5
+
6
+ def initialize(hue, saturation, value)
7
+ @hue = hue
8
+ @saturation = saturation
9
+ @value = value
10
+ end
11
+
12
+ def calculate_distance(hsv)
13
+ Math.sqrt(squares_array(hsv).inject(&:+))
14
+ end
15
+
16
+ private
17
+
18
+ def squares_array(hsv)
19
+ [
20
+ (self.hue - hsv.hue)**2,
21
+ (self.saturation - hsv.saturation)**2,
22
+ (self.value - hsv.value)**2
23
+ ]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ module Photomosaic
2
+ module Color
3
+ class RGB
4
+ attr_reader :red, :green, :blue
5
+
6
+ def initialize(red, green, blue)
7
+ @red = red
8
+ @green = green
9
+ @blue = blue
10
+ end
11
+
12
+ def max
13
+ @max ||= [@red, @green, @blue].max
14
+ end
15
+
16
+ def min
17
+ @min ||= [@red, @green, @blue].min
18
+ end
19
+
20
+ def to_hsv
21
+ HSV.new(hue, saturation, value)
22
+ end
23
+
24
+ def calculate_distance(rgb)
25
+ Math.sqrt(squares_array(rgb).inject(&:+))
26
+ end
27
+
28
+ private
29
+
30
+ def hue
31
+ return 0 if max == min
32
+
33
+ _hue = case max
34
+ when @red
35
+ ((@green - @blue).to_f / (max - min)) % 6
36
+ when @green
37
+ (@blue - @red).to_f / (max - min) + 2
38
+ else
39
+ (@red - @green).to_f / (max - min) + 4
40
+ end
41
+
42
+ (_hue * 60).to_i
43
+ end
44
+
45
+ def saturation
46
+ max == 0 ? 0 : (max - min).to_f / max * 100
47
+ end
48
+
49
+ def squares_array(rgb)
50
+ [
51
+ (self.red - rgb.red)**2,
52
+ (self.green - rgb.green)**2,
53
+ (self.blue - rgb.blue)**2
54
+ ]
55
+ end
56
+
57
+ def value
58
+ (max * 100).to_f / 256
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,113 @@
1
+ require "RMagick"
2
+
3
+ module Photomosaic
4
+ class Image
5
+ def self.create_mosaic_image(image_list, output_path)
6
+ image_list.inject(Magick::ImageList.new) do |images, row|
7
+ images << row.inject(Magick::ImageList.new) do |col_images, image|
8
+ col_images << image.image.dup
9
+ col_images
10
+ end.append(false)
11
+
12
+ images
13
+ end.append(true).write(output_path)
14
+ end
15
+
16
+ def self.preprocess_image(image_path, width, height, level, colors)
17
+ image = Photomosaic::Image.new(image_path)
18
+ image.resize!(width, height, true)
19
+ image.posterize!(level)
20
+ image.reduce_colors!(colors)
21
+ image
22
+ end
23
+
24
+ def self.resize_images(images, width, height)
25
+ images.map do |row|
26
+ row.map { |image| image.resize!(width, height, false) }
27
+ end
28
+ end
29
+
30
+ def initialize(image_path)
31
+ @image = Magick::Image.read(image_path).first
32
+ end
33
+
34
+ def characteristic_color(color_model = :rgb)
35
+ @characteristic_color ||= get_characteristic_color(color_model)
36
+ end
37
+
38
+ def dispatch_images(source_image_list, row_step = 1, col_step = 1, color_model = :rgb)
39
+ (1..image_height).step(row_step).inject([]) do |images, y|
40
+ images << (1..image_width).step(col_step).inject([]) do |col_images, x|
41
+ col_images << nearest_image(source_image_list, x, y, color_model)
42
+ col_images
43
+ end
44
+
45
+ images
46
+ end
47
+ end
48
+
49
+ def image
50
+ @image
51
+ end
52
+
53
+ def posterize!(level)
54
+ @image = @image.posterize(level)
55
+ reload_image
56
+ self
57
+ end
58
+
59
+ def reduce_colors!(colors)
60
+ @image = @image.quantize(colors)
61
+ reload_image
62
+ self
63
+ end
64
+
65
+ def resize!(width, height, keep_ratio)
66
+ keep_ratio ? @image.resize_to_fit!(width, height) : @image.resize!(width, height)
67
+ reload_image
68
+ self
69
+ end
70
+
71
+ private
72
+
73
+ def get_characteristic_color(color_model = :rgb)
74
+ color = nil
75
+ original_image = @image.dup
76
+
77
+ begin
78
+ resize!(1, 1, false)
79
+ color = pixel_color(1, 1, color_model)
80
+ ensure
81
+ @image = original_image
82
+ end
83
+
84
+ color
85
+ end
86
+
87
+ def nearest_image(source_image_list, x, y, color_model)
88
+ pixel = pixel_color(x, y, color_model)
89
+
90
+ source_image_list.sort_by do |image|
91
+ image.characteristic_color(color_model).calculate_distance(pixel)
92
+ end.first
93
+ end
94
+
95
+ def pixel_color(x, y, color_model = :rgb)
96
+ pixel = @image.pixel_color(x, y)
97
+ rgb = Color::RGB.new(pixel.red / 257, pixel.green / 257, pixel.blue / 257)
98
+ color_model == :rgb ? rgb : rgb.to_hsv
99
+ end
100
+
101
+ def reload_image
102
+ @image = Magick::Image.from_blob(@image.to_blob).first
103
+ end
104
+
105
+ def image_height
106
+ @image.rows
107
+ end
108
+
109
+ def image_width
110
+ @image.columns
111
+ end
112
+ end
113
+ end