photomosaic 0.0.1

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.
@@ -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