Skip to content

Make it possible to reduce available patterns #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jan 26, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ pattern = GeoPattern.generate('Mastering Markdown', color: '#fc0')
To use a specific [pattern generator](#available-patterns):

```ruby
pattern = GeoPattern.generate('Mastering Markdown', generator: GeoPattern::SineWavePattern)
pattern = GeoPattern.generate('Mastering Markdown', patterns: GeoPattern::SineWavePattern)
```

To use a subset of the [available patterns](#available-patterns):

```ruby
pattern = GeoPattern.generate('Mastering Markdown', patterns: [GeoPattern::SineWavePattern, GeoPattern::XesPattern])
```

Get the SVG string:
Expand Down
5 changes: 5 additions & 0 deletions lib/geo_pattern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
require 'color'

require 'geo_pattern/version'
require 'geo_pattern/errors'
require 'geo_pattern/svg'
require 'geo_pattern/pattern_helpers'
require 'geo_pattern/helpers'
require 'geo_pattern/pattern_store'
require 'geo_pattern/pattern_validator'
require 'geo_pattern/pattern_sieve'
require 'geo_pattern/hash_store'

require 'geo_pattern/pattern/base_pattern'
require 'geo_pattern/pattern/chevron_pattern'
Expand Down
7 changes: 7 additions & 0 deletions lib/geo_pattern/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module GeoPattern
# user errors
class UserError < StandardError; end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using this kind of pattern for exceptions is a question of style. If you do not like it, I can remove the "intermediate" error.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have an opinion on this. @ttaylorr, do you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use this pattern all the time -- looks great.


# raised if an invalid pattern is requested
class InvalidPatternError < UserError; end
end
29 changes: 29 additions & 0 deletions lib/geo_pattern/hash_store.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module GeoPattern
class HashStore
private

attr_reader :hash

public

def initialize(hash)
@hash = hash.each_with_object({}) { |(key, value), a| a[key.to_sym] = value }
end

def key?(obj)
hash.key? obj.to_sym
end

def values
hash.values
end

def value?(obj)
hash.value? obj
end

def [](obj)
hash[obj.to_sym]
end
end
end
83 changes: 26 additions & 57 deletions lib/geo_pattern/pattern_generator.rb
Original file line number Diff line number Diff line change
@@ -1,41 +1,26 @@
module GeoPattern
class PatternGenerator
DEFAULTS = {
:base_color => '#933c3c'
base_color: '#933c3c'
}

PATTERNS = {
'chevrons' => ChevronPattern,
'concentric_circles' => ConcentricCirclesPattern,
'diamonds' => DiamondPattern,
'hexagons' => HexagonPattern,
'mosaic_squares' => MosaicSquaresPattern,
'nested_squares' => NestedSquaresPattern,
'octagons' => OctagonPattern,
'overlapping_circles' => OverlappingCirclesPattern,
'overlapping_rings' => OverlappingRingsPattern,
'plaid' => PlaidPattern,
'plus_signs' => PlusSignPattern,
'sine_waves' => SineWavePattern,
'squares' => SquarePattern,
'tessellation' => TessellationPattern,
'triangles' => TrianglePattern,
'xes' => XesPattern,
}.freeze

FILL_COLOR_DARK = "#222"
FILL_COLOR_LIGHT = "#ddd"
STROKE_COLOR = "#000"
STROKE_OPACITY = 0.02
OPACITY_MIN = 0.02
OPACITY_MAX = 0.15

private

attr_reader :opts, :hash, :svg

public

def initialize(string, opts={})
@opts = DEFAULTS.merge(opts)
@hash = Digest::SHA1.hexdigest string
@svg = SVG.new
@opts = DEFAULTS.merge(opts)
@hash = Digest::SHA1.hexdigest string
@svg = SVG.new

generate_background
generate_pattern
Expand All @@ -55,43 +40,27 @@ def uri_image
end

def generate_background
if opts[:color]
rgb = Color::RGB.from_html(opts[:color])
else
hue_offset = PatternHelpers.map(PatternHelpers.hex_val(hash, 14, 3), 0, 4095, 0, 359)
sat_offset = PatternHelpers.hex_val(hash, 17, 1)
base_color = Color::RGB.from_html(opts[:base_color]).to_hsl
base_color.hue = base_color.hue - hue_offset;

if (sat_offset % 2 == 0)
base_color.saturation = base_color.saturation + sat_offset
else
base_color.saturation = base_color.saturation - sat_offset
end
rgb = base_color.to_rgb
end
r = (rgb.r * 255).round
g = (rgb.g * 255).round
b = (rgb.b * 255).round
svg.rect(0, 0, "100%", "100%", {"fill" => "rgb(#{r}, #{g}, #{b})"})
color = if opts[:color]
PatternHelpers.html_to_rgb(opts[:color])
else
PatternHelpers.html_to_rgb_for_string(hash, opts[:base_color])
end

svg.rect(0, 0, "100%", "100%", "fill" => color)
end

def generate_pattern
unless opts[:generator].nil?
if opts[:generator].is_a? String
generator = PATTERNS[opts[:generator]]
puts SVG.as_comment("String pattern references are deprecated as of 1.3.0")
elsif opts[:generator] < BasePattern
if PATTERNS.values.include? opts[:generator]
generator = opts[:generator]
else
abort("Error: the requested generator is invalid")
generator = nil
end
end
end

generator ||= PATTERNS.values[[PatternHelpers.hex_val(hash, 20, 1), PATTERNS.length - 1].min]
puts SVG.as_comment('Using generator key is deprecated as of 1.3.1') if opts.key? :generator

requested_patterns = (Array(opts[:generator]) | Array(opts[:patterns])).flatten.compact

validator = PatternValidator.new
validator.validate(requested_patterns)

sieve = PatternSieve.new
patterns = sieve.fetch(requested_patterns)

generator = patterns[[PatternHelpers.hex_val(hash, 20, 1), patterns.length - 1].min]

# Instantiate the generator with the needed references
# and render the pattern to the svg object
Expand Down
33 changes: 31 additions & 2 deletions lib/geo_pattern/pattern_helpers.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,46 @@
module GeoPattern
module PatternHelpers
def self.hex_val(hash, index, length)
def hex_val(hash, index, length)
hash[index, length || 1].to_i(16)
end

# Ruby implementation of Processing's map function
# http://processing.org/reference/map_.html
def self.map(value, v_min, v_max, d_min, d_max) # v for value, d for desired
def map(value, v_min, v_max, d_min, d_max) # v for value, d for desired
v_value = value.to_f # so it returns float

v_range = v_max - v_min
d_range = d_max - d_min
(v_value - v_min) * d_range / v_range + d_min
end

def html_to_rgb(color)
generate_rgb_string(Color::RGB.from_html(color))
end

def html_to_rgb_for_string(hash, base_color)
hue_offset = map(hex_val(hash, 14, 3), 0, 4095, 0, 359)
sat_offset = hex_val(hash, 17, 1)
base_color = Color::RGB.from_html(base_color).to_hsl
base_color.hue = base_color.hue - hue_offset;

if (sat_offset % 2 == 0)
base_color.saturation = base_color.saturation + sat_offset
else
base_color.saturation = base_color.saturation - sat_offset
end

generate_rgb_string(base_color.to_rgb)
end

def generate_rgb_string(rgb)
r = (rgb.r * 255).round
g = (rgb.g * 255).round
b = (rgb.b * 255).round

format("rgb(%d, %d, %d)", r, g, b)
end

module_function :hex_val, :map, :html_to_rgb, :html_to_rgb_for_string, :generate_rgb_string
end
end
21 changes: 21 additions & 0 deletions lib/geo_pattern/pattern_sieve.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module GeoPattern
class PatternSieve
private

attr_reader :pattern_store

public

def initialize(pattern_store = PatternStore.new)
@pattern_store = pattern_store
end

def fetch(requested_patterns)
requested_patterns = Array(requested_patterns).compact

return pattern_store.all if requested_patterns.empty?

requested_patterns.map { |p| pattern_store[p] }.compact
end
end
end
60 changes: 60 additions & 0 deletions lib/geo_pattern/pattern_store.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module GeoPattern
class PatternStore
private

attr_reader :store

public

def initialize(
store = HashStore.new(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of a HashWithIndifferentAccess. So there's no need to use strings here anymore.

chevrons: ChevronPattern,
concentric_circles: ConcentricCirclesPattern,
diamonds: DiamondPattern,
hexagons: HexagonPattern,
mosaic_squares: MosaicSquaresPattern,
nested_squares: NestedSquaresPattern,
octagons: OctagonPattern,
overlapping_circles: OverlappingCirclesPattern,
overlapping_rings: OverlappingRingsPattern,
plaid: PlaidPattern,
plus_signs: PlusSignPattern,
sine_waves: SineWavePattern,
squares: SquarePattern,
tessellation: TessellationPattern,
triangles: TrianglePattern,
xes: XesPattern
)
)

@store = store
end

def [](pattern)
if pattern.kind_of?(String) || pattern.kind_of?(Symbol)
$stderr.puts 'String pattern references are deprecated as of 1.3.0' if pattern.kind_of?(String)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the deprecation warnings for string + class. I can revert this change @jasonlong. It's just a single commit.


return store[pattern]
end

if store.value? pattern
$stderr.puts 'Class pattern references are deprecated as of 1.3.0'

return pattern
end

nil
end

def all
store.values
end

def known?(pattern)
return store.key?(pattern) if pattern.kind_of?(String) || pattern.kind_of?(Symbol)
return store.value?(pattern) if pattern.kind_of? Class

false
end
end
end
27 changes: 27 additions & 0 deletions lib/geo_pattern/pattern_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module GeoPattern
class PatternValidator
private

attr_reader :pattern_store

public

def initialize(pattern_store = PatternStore.new)
@pattern_store = pattern_store
end

def validate(requested_patterns)
message = "Error: At least one of the requested patterns \"#{requested_patterns.join(", ")}\" is invalid"

fail InvalidPatternError, message unless valid?(requested_patterns)

self
end

private

def valid?(requested_patterns)
requested_patterns.all? { |p| pattern_store.known? p }
end
end
end
Loading