Skip to content

Commit

Permalink
Start with breaking up the avatar into multiple components
Browse files Browse the repository at this point in the history
  • Loading branch information
tomeric committed May 25, 2012
1 parent 9537854 commit 9d7ad5f
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 131 deletions.
20 changes: 18 additions & 2 deletions lib/cheers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

module Cheers
autoload :VERSION, 'cheers/version'
autoload :Color, 'cheers/color'
autoload :Avatar, 'cheers/avatar'

autoload :Avatar, 'cheers/avatar'
autoload :Background, 'cheers/background'
autoload :Color, 'cheers/color'
autoload :Component, 'cheers/component'
autoload :ContrastingColorPicker, 'cheers/contrasting_color_picker'
autoload :Decoration, 'cheers/decoration'
autoload :Eyes, 'cheers/eyes'
autoload :Face, 'cheers/face'
autoload :ImageComponent, 'cheers/image_component'
autoload :LowerGlow, 'cheers/lower_glow'
autoload :Mouth, 'cheers/mouth'
autoload :Texture, 'cheers/texture'
autoload :UpperGlow, 'cheers/upper_glow'

def self.root
File.expand_path('../../', __FILE__)
end
end
140 changes: 11 additions & 129 deletions lib/cheers/avatar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,13 @@

module Cheers
class Avatar
GEM_ROOT = File.expand_path('../../../', __FILE__)

BACKGROUND_COLORS = ['#cccccc', '#dddddd', '#bbbbbb', '#F1A800', '#FEF0CC', '#fd4238', '#fff0f0', '#14899d', '#c3ecee', '#42991a', '#f0fff0']
# SMILE_COLORS = ['#333', '#666', '#9e005d', '#ef8200', '#d8a129', '#db4640', '#0e788b', '#239340']
SMILE_COLORS = ['#333333', '#666666', '#9e005d', '#ef8200', '#db4640', '#0e788b', '#239340']
IMAGES = {

smiles: [
{ smile: 'components/mouths/1-smile.png',
smile_glow: 'components/mouths/1-smile-glow.png',
bg_mask: 'components/mouths/1-bgmask.png',
texture: 'components/texture.png' },
{ smile: 'components/mouths/2-smile.png',
smile_glow: 'components/mouths/2-smile-glow.png',
bg_mask: 'components/mouths/2-bgmask.png',
texture: 'components/texture.png' },
{ smile: 'components/mouths/3-smile.png',
smile_glow: 'components/mouths/3-smile-glow.png',
bg_mask: 'components/mouths/3-bgmask.png',
texture: 'components/texture.png' }
],
eyes: [
{ eyes: 'components/eyes/1.png' },
{ eyes: 'components/eyes/2.png' },
{ eyes: 'components/eyes/3.png' },
{ eyes: 'components/eyes/4.png' },
{ eyes: 'components/eyes/5.png' }
],
decorations: [
{ id: 1, decoration: 'components/decorations/1.png' },
{ id: 2, decoration: 'components/decorations/2.png' }
]
}
BACKGROUND_COLORS = %w(#cccccc #dddddd #bbbbbb #f1a800 #fef0cc
#fd4238 #fff0f0 #14899d #c3ecee #42991a
#f0fff0)

COMPONENT_COLORS = %w(#333333 #666666 #9e005d #ef8200 #db4640
#0e788b #239340)

# Creates a new avatar from the seed string
def initialize(seed)
Expand All @@ -55,107 +29,15 @@ def avatar_file(file_path)
private

def compose_avatar #:nodoc:
random_generator = Random.new(@seed)

# 0. Let's get some random colors
# We shoudn't change the order we call #rand to keep version changes minimal
avatar_bg_color = BACKGROUND_COLORS.sample(random: random_generator)
smile_components = IMAGES[:smiles].sample(random: random_generator)
smile_bg_color = BACKGROUND_COLORS.sample(random: random_generator)
# smile_color = SMILE_COLORS.sample(random: random_generator)
# eyes_color = SMILE_COLORS.sample(random: random_generator)
eyes_components = IMAGES[:eyes].sample(random: random_generator)
smile_upper_glow_color = SMILE_COLORS.sample(random: random_generator)
smile_lower_glow_color = SMILE_COLORS.sample(random: random_generator)
has_decoration = [true, true, false].sample(random: random_generator)
decoration_component = IMAGES[:decorations].sample(random: random_generator)
decoration_color = BACKGROUND_COLORS.sample(random: random_generator)

# Lets test if the color behind eyes is very similar to the eye color.
# Generate a new eye color if that's the case.
eyes_contrasting_color = if has_decoration and decoration_component[:id] == 1
decoration_color
else
avatar_bg_color
end

# Lets not use the same random generator for generating unknown amount of new colors
rng = Random.new(random_generator.rand(10000))
begin
eyes_color = SMILE_COLORS.sample(random: rng)
end while Color.new(eyes_color).similar?(eyes_contrasting_color, 0.2)

# Lets test if the background colors are very similar to the smile color.
# Change the smile color if that's the case, right?
rng = Random.new(random_generator.rand(10000))
begin
smile_color = SMILE_COLORS.sample(random: rng)
the_smile_color = Color.new(smile_color)
end while the_smile_color.similar?(avatar_bg_color, 0.2) or the_smile_color.similar?(smile_bg_color, 0.2)


# 1. Lets create upper background
upper_background = Magick::Image.new(512, 512) { self.background_color = avatar_bg_color }

# This is smile glow mask
smile_glow_image = Magick::Image.read(component_path(smile_components[:smile_glow]))[0]

# Create smile glow layer and put it on upper background
smile_upper_glow = Magick::Image.new(512, 512) { self.background_color = smile_upper_glow_color }
upper_background.add_compose_mask(smile_glow_image)
upper_background.composite!(smile_upper_glow, 0, 0, Magick::OverCompositeOp)
upper_background.delete_compose_mask
generator = Random.new(@seed)

@avatar = Magick::Image.new(512, 512)

# 2. Lets create lower background
lower_background = Magick::Image.new(512, 512) { self.background_color = smile_bg_color }

# Create another smile glow layer but put it on lower background
smile_lower_glow = Magick::Image.new(512, 512) { self.background_color = smile_lower_glow_color }
lower_background.add_compose_mask(smile_glow_image)
lower_background.composite!(smile_lower_glow, 0, 0, Magick::OverCompositeOp)
lower_background.delete_compose_mask


# 3. Lets compose both backgrounds together

# This will be the final avatar image.
# Because we will draw the lower background over the upper background,
# we can use the upper background as the starting image.
avatar = upper_background

# Lets mask and put the lower background on top of the image
avatar.add_compose_mask(Magick::Image.read(component_path(smile_components[:bg_mask]))[0])
avatar.composite!(lower_background, 0, 0, Magick::OverCompositeOp)
avatar.delete_compose_mask

# 4. Add decorations
if has_decoration
decoration = Magick::Image.new(512, 512) { self.background_color = decoration_color }
avatar.add_compose_mask(Magick::Image.read(component_path(decoration_component[:decoration]))[0])
avatar.composite!(decoration, 0, 0, Magick::OverCompositeOp)
avatar.delete_compose_mask
[Background, Face, Decoration, Eyes].each do |klass|
klass.new(@avatar, generator).apply
end

# 5. Add the texture
texture = Magick::Image.read(component_path(smile_components[:texture]))[0]
avatar.composite!(texture, 0, 0, Magick::OverCompositeOp)

smile = Magick::Image.new(512, 512) { self.background_color = smile_color }
avatar.add_compose_mask(Magick::Image.read(component_path(smile_components[:smile]))[0])
avatar.composite!(smile, 0, 0, Magick::OverCompositeOp)
avatar.delete_compose_mask

eyes = Magick::Image.new(512, 512) { self.background_color = eyes_color }
avatar.add_compose_mask(Magick::Image.read(component_path(eyes_components[:eyes]))[0])
avatar.composite!(eyes, 0, 0, Magick::OverCompositeOp)
avatar.delete_compose_mask

@avatar = avatar
end

def component_path(asset) #:nodoc:
[GEM_ROOT, asset].join('/')
@avatar
end
end
end
23 changes: 23 additions & 0 deletions lib/cheers/background.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Cheers
class Background < Component

attr_reader :color

def initialize(canvas, randomizer)
super

@color = Avatar::BACKGROUND_COLORS.sample(random: randomizer)
end

def apply
# Work around instance_eval wonkiness by declaring local variables:
color = self.color

background = Magick::Image.new(512, 512) do
self.background_color = color
end

canvas.composite!(background, 0, 0, Magick::OverCompositeOp)
end
end
end
12 changes: 12 additions & 0 deletions lib/cheers/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module Cheers
class Component

attr_reader :canvas, :randomizer

def initialize(canvas, randomizer)
@canvas = canvas
@randomizer = randomizer
end

end
end
25 changes: 25 additions & 0 deletions lib/cheers/contrasting_color_picker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Cheers
class ContrastingColorPicker

MAX_RETRIES = 10

attr_reader :palette, :colors

def initialize(palette, *colors)
@palette = palette.map { |c| Color.new(c) }
@colors = colors.map { |c| Color.new(c) }
end

def pick(randomizer = Random.new)
pick = palette.sample(random: randomizer)

try = 0
while colors.any? { |c| c.similar?(pick) } && try <= MAX_RETRIES
try += 1
pick = palette.sample(random: randomizer)
end

pick.to_s
end
end
end
35 changes: 35 additions & 0 deletions lib/cheers/decoration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Cheers
class Decoration < ImageComponent

IMAGES = %w( decorations/1.png
decorations/2.png )

attr_reader :color, :image

def initialize(canvas, color_randomizer, image_randomizer = nil)
super

@color = ContrastingColorPicker.new(Avatar::BACKGROUND_COLORS, canvas.background_color).pick
@image = IMAGES.sample random: self.image_randomizer
end

def apply?
[true, true, false].sample(random: image_randomizer)
end

def apply
return unless apply?

mask = Magick::Image.read(image_path(image))[0]
color = self.color

decoration = Magick::Image.new(512, 512) do
self.background_color = color
end

canvas.add_compose_mask(mask)
canvas.composite!(decoration, 0, 0, Magick::OverCompositeOp)
canvas.delete_compose_mask
end
end
end
32 changes: 32 additions & 0 deletions lib/cheers/eyes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Cheers
class Eyes < ImageComponent

IMAGES = %w( eyes/1.png
eyes/2.png
eyes/3.png
eyes/4.png
eyes/5.png )

attr_reader :color, :image

def initialize(canvas, color_randomizer, image_randomizer = nil)
super

@color = ContrastingColorPicker.new(Avatar::COMPONENT_COLORS, canvas.background_color).pick
@image = IMAGES.sample random: self.image_randomizer
end

def apply
mask = Magick::Image.read(image_path(image))[0]
color = self.color

eyes = Magick::Image.new(512, 512) do
self.background_color = color
end

canvas.add_compose_mask(mask)
canvas.composite!(eyes, 0, 0, Magick::OverCompositeOp)
canvas.delete_compose_mask
end
end
end
11 changes: 11 additions & 0 deletions lib/cheers/face.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Cheers
class Face < ImageComponent

def apply
[UpperGlow, LowerGlow, Texture, Mouth].each do |klass|
klass.new(canvas, color_randomizer, image_randomizer.dup).apply
end
end

end
end
18 changes: 18 additions & 0 deletions lib/cheers/image_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Cheers
class ImageComponent

attr_reader :canvas, :color_randomizer, :image_randomizer

def initialize(canvas, color_randomizer, image_randomizer = nil)
@canvas = canvas
@color_randomizer = color_randomizer
@image_randomizer = image_randomizer || color_randomizer
end

private

def image_path(component)
[Cheers.root, 'components', component].join '/'
end
end
end
50 changes: 50 additions & 0 deletions lib/cheers/lower_glow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module Cheers
class LowerGlow < ImageComponent

GLOW_IMAGES = %w( mouths/1-smile-glow.png
mouths/2-smile-glow.png
mouths/3-smile-glow.png )

MASK_IMAGES = %w( mouths/1-bgmask.png
mouths/2-bgmask.png
mouths/3-bgmask.png )

attr_reader :background_color, :glow_color, :glow_image, :mask_image

def initialize(canvas, color_randomizer, element_randomizer = nil)
super

@background_color = ContrastingColorPicker.new(Avatar::BACKGROUND_COLORS, canvas.background_color).pick
@glow_color = ContrastingColorPicker.new(Avatar::COMPONENT_COLORS, canvas.background_color, background_color).pick
@glow_image = GLOW_IMAGES.sample random: element_randomizer.dup
@mask_image = MASK_IMAGES.sample random: element_randomizer.dup
end

def apply
glow = Magick::Image.read(image_path(glow_image))[0]
mask = Magick::Image.read(image_path(mask_image))[0]

# Work around instance_eval wonkiness by declaring local variables:
glow_color = self.glow_color
background_color = self.background_color

# 2. Lets create lower background
background = Magick::Image.new(512, 512) do
self.background_color = background_color
end

# Create another smile glow layer but put it on lower background
glow_background = Magick::Image.new(512, 512) do
self.background_color = glow_color
end

background.add_compose_mask(glow)
background.composite!(glow_background, 0, 0, Magick::OverCompositeOp)
background.delete_compose_mask

canvas.add_compose_mask(mask)
canvas.composite!(background, 0, 0, Magick::OverCompositeOp)
canvas.delete_compose_mask
end
end
end
Loading

0 comments on commit 9d7ad5f

Please sign in to comment.