diff --git a/README.md b/README.md
index 308a19e..fdd323b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,104 @@
# geopattern
-Generate beautiful SVG patterns from a string. This is a Python-port of Jason Long's Ruby library.
+
+
+**Lua** implemenation of [geopatterns (Ruby library)][2] by [Jason Long][1].
+
+Generate beautiful SVG patterns from a string.
+
+[1]: https://github.com/jasonlong/
+[2]: https://github.com/jasonlong/geopatterns/
+
+## Installation
+```shell
+git clone https://github.com/azoyan/geopattern.git
+```
+
+## Usage
+Create a new pattern by calling `GeoPattern:new()` with a string and a
+generator (the result of this string/generator pair is the above image).
+
+```Lua
+local GeoPattern = require "geopattern"
+
+local geo = GeoPattern:new("GitHub")
+print(geo:toSvg())
+```
+
+### API
+
+#### `GeoPattern:new(string, options)`
+
+Returns a newly-generated, tiling SVG Pattern.
+
+- `string` Will be hashed using the SHA1 algorithm, and the resulting hash will be used as the seed for generation.
+
+- `options.color` Specify an exact background color. This is a CSS hexadecimal color value.
+
+- `options.baseColor` Controls the relative background color of the generated image. The color is not identical to that used in the pattern because the hue is rotated by the generator. This is a CSS hexadecimal color value, which defaults to `#933c3c`.
+
+- `options.generator` Determines the pattern. [All of the original patterns](https://github.com/jasonlong/geo_pattern#available-patterns) are available in this port, and their names are camelCased.
+
+ Available Patterns:
+ - `"octogons"`
+ - `"overlappingCircles"`
+ - `"plusSigns"`
+ - `"xes'"`
+ - `"sineWaves"`
+ - `"hexagons"`
+ - `"overlappingRings"`
+ - `"plaid"`
+ - `"triangles"`
+ - `"squares"`
+ - `"concentricCircles"`
+ - `"diamonds"`
+ - `"tessellation"`
+ - `"nestedSquares'"`
+ - `"mosaicSquares"`
+ - `"chevrons"`
+
+```Lua
+local GeoPattern = require "geopattern"
+
+local pattern1 = GeoPattern:new("GitHub") -- without options
+local pattern3 = GeoPattern:new("GitHub", { color = "#00ffff" })
+local pattern2 = GeoPattern:new("GitHub", { generator = "concentricCircles" })
+
+local options = {
+ generator = "concentricCircles",
+ color = "#00ffff",
+ baseColor = "#af39b3"
+}
+local pattern4 = GeoPattern:new("GitHub", options) -- with all available options
+```
+
+#### `GeoPattern:toSvg()`
+Returns the SVG string representing the pattern.
+
+```Lua
+local GeoPattern = require "geopattern"
+
+local pattern = GeoPattern:new("GitHub")
+local svg = pattern:toSvg() -- string in SVG format
+
+print(svg)
+```
+#### Output:
+```xml
+
+```
+
+#### `GeoPattern:toBase64()`
+Returns Base64-encoded string representing the pattern.
+
+```Lua
+local GeoPattern = require "geopattern"
+
+local pattern = GeoPattern:new("GitHub")
+local base64 = pattern:toBae64() -- encode to Base64 string
+
+print(base64)
+```
+#### Output:
+```base64
+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjAiIGhlaWdodD0iMTYwIj48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiAgZmlsbD0icmdiKDY5LCA5MywgMTM3KSIvPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjA2MzMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iIzIyMiIvPjxyZWN0IHg9IjI2IiB5PSIwIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wNTQ2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiNkZGQiLz48cmVjdCB4PSI1MyIgeT0iMCIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMDU0NjY2NjY2NjY2NjY3IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiBmaWxsPSIjZGRkIi8+PHJlY3QgeD0iODAiIHk9IjAiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjAzNzMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iI2RkZCIvPjxyZWN0IHg9IjEwNiIgeT0iMCIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMTQxMzMzMzMzMzMzMzMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiNkZGQiLz48cmVjdCB4PSIxMzMiIHk9IjAiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjAzNzMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iI2RkZCIvPjxyZWN0IHg9IjAiIHk9IjI2IiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4xMTUzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iIzIyMiIvPjxyZWN0IHg9IjI2IiB5PSIyNiIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMDcyIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiBmaWxsPSIjZGRkIi8+PHJlY3QgeD0iNTMiIHk9IjI2IiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wNTQ2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiNkZGQiLz48cmVjdCB4PSI4MCIgeT0iMjYiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjE1IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiBmaWxsPSIjMjIyIi8+PHJlY3QgeD0iMTA2IiB5PSIyNiIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMTA2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiNkZGQiLz48cmVjdCB4PSIxMzMiIHk9IjI2IiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wMiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iI2RkZCIvPjxyZWN0IHg9IjAiIHk9IjUzIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wOTgiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiMyMjIiLz48cmVjdCB4PSIyNiIgeT0iNTMiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjA4MDY2NjY2NjY2NjY2NyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iIzIyMiIvPjxyZWN0IHg9IjUzIiB5PSI1MyIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMDcyIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiBmaWxsPSIjZGRkIi8+PHJlY3QgeD0iODAiIHk9IjUzIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wNTQ2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiNkZGQiLz48cmVjdCB4PSIxMDYiIHk9IjUzIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4xMTUzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iIzIyMiIvPjxyZWN0IHg9IjEzMyIgeT0iNTMiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjA5OCIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iIzIyMiIvPjxyZWN0IHg9IjAiIHk9IjgwIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4xNSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iIzIyMiIvPjxyZWN0IHg9IjI2IiB5PSI4MCIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMDYzMzMzMzMzMzMzMzMzIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiBmaWxsPSIjMjIyIi8+PHJlY3QgeD0iNTMiIHk9IjgwIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wOTgiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiMyMjIiLz48cmVjdCB4PSI4MCIgeT0iODAiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjA0NiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iIzIyMiIvPjxyZWN0IHg9IjEwNiIgeT0iODAiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjA4OTMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iI2RkZCIvPjxyZWN0IHg9IjEzMyIgeT0iODAiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjA3MiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iI2RkZCIvPjxyZWN0IHg9IjAiIHk9IjEwNiIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMDgwNjY2NjY2NjY2NjY3IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiBmaWxsPSIjMjIyIi8+PHJlY3QgeD0iMjYiIHk9IjEwNiIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMTQxMzMzMzMzMzMzMzMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiNkZGQiLz48cmVjdCB4PSI1MyIgeT0iMTA2IiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wNjMzMzMzMzMzMzMzMzMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiMyMjIiLz48cmVjdCB4PSI4MCIgeT0iMTA2IiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wOTgiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiMyMjIiLz48cmVjdCB4PSIxMDYiIHk9IjEwNiIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMTA2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiNkZGQiLz48cmVjdCB4PSIxMzMiIHk9IjEwNiIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMDk4IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiBmaWxsPSIjMjIyIi8+PHJlY3QgeD0iMCIgeT0iMTMzIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wODA2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiMyMjIiLz48cmVjdCB4PSIyNiIgeT0iMTMzIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wMzczMzMzMzMzMzMzMzMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiNkZGQiLz48cmVjdCB4PSI1MyIgeT0iMTMzIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wOTgiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiMyMjIiLz48cmVjdCB4PSI4MCIgeT0iMTMzIiB3aWR0aD0iMjYuNjY2NjY2NjY2NjY3IiBoZWlnaHQ9IjI2LjY2NjY2NjY2NjY2NyIgIGZpbGwtb3BhY2l0eT0iMC4wMzczMzMzMzMzMzMzMzMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIGZpbGw9IiNkZGQiLz48cmVjdCB4PSIxMDYiIHk9IjEzMyIgd2lkdGg9IjI2LjY2NjY2NjY2NjY2NyIgaGVpZ2h0PSIyNi42NjY2NjY2NjY2NjciICBmaWxsLW9wYWNpdHk9IjAuMTI0IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiBmaWxsPSIjZGRkIi8+PHJlY3QgeD0iMTMzIiB5PSIxMzMiIHdpZHRoPSIyNi42NjY2NjY2NjY2NjciIGhlaWdodD0iMjYuNjY2NjY2NjY2NjY3IiAgZmlsbC1vcGFjaXR5PSIwLjA4OTMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgZmlsbD0iI2RkZCIvPjwvc3ZnPg==
+```
diff --git a/geopattern.lua b/geopattern.lua
new file mode 100644
index 0000000..1bb2d77
--- /dev/null
+++ b/geopattern.lua
@@ -0,0 +1,2 @@
+local lib = require "geopattern.init"
+return lib
\ No newline at end of file
diff --git a/geopattern/base64.lua b/geopattern/base64.lua
new file mode 100644
index 0000000..cd74c1e
--- /dev/null
+++ b/geopattern/base64.lua
@@ -0,0 +1,201 @@
+--[[
+
+ base64 -- v1.5.2 public domain Lua base64 encoder/decoder
+ no warranty implied; use at your own risk
+
+ Needs bit32.extract function. If not present it's implemented using BitOp
+ or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua
+ implementation inspired by Rici Lake's post:
+ http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html
+
+ author: Ilya Kolbin (iskolbin@gmail.com)
+ url: github.com/iskolbin/lbase64
+
+ COMPATIBILITY
+
+ Lua 5.1, 5.2, 5.3, LuaJIT
+
+ LICENSE
+
+ See end of file for license information.
+
+--]]
+
+
+local base64 = {}
+
+local extract = _G.bit32 and _G.bit32.extract
+if not extract then
+ if _G.bit then
+ local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
+ extract = function( v, from, width )
+ return band( shr( v, from ), shl( 1, width ) - 1 )
+ end
+ elseif _G._VERSION >= "Lua 5.3" then
+ extract = load[[return function( v, from, width )
+ return ( v >> from ) & ((1 << width) - 1)
+ end]]()
+ else
+ extract = function( v, from, width )
+ local w = 0
+ local flag = 2^from
+ for i = 0, width-1 do
+ local flag2 = flag + flag
+ if v % flag2 >= flag then
+ w = w + 2^i
+ end
+ flag = flag2
+ end
+ return w
+ end
+ end
+end
+
+
+function base64.makeencoder( s62, s63, spad )
+ local encoder = {}
+ for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J',
+ 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',
+ 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
+ 'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',
+ '3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do
+ encoder[b64code] = char:byte()
+ end
+ return encoder
+end
+
+function base64.makedecoder( s62, s63, spad )
+ local decoder = {}
+ for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do
+ decoder[charcode] = b64code
+ end
+ return decoder
+end
+
+local DEFAULT_ENCODER = base64.makeencoder()
+local DEFAULT_DECODER = base64.makedecoder()
+
+local char, concat = string.char, table.concat
+
+function base64.encode( str, encoder, usecaching )
+ encoder = encoder or DEFAULT_ENCODER
+ local t, k, n = {}, 1, #str
+ local lastn = n % 3
+ local cache = {}
+ for i = 1, n-lastn, 3 do
+ local a, b, c = str:byte( i, i+2 )
+ local v = a*0x10000 + b*0x100 + c
+ local s
+ if usecaching then
+ s = cache[v]
+ if not s then
+ s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
+ cache[v] = s
+ end
+ else
+ s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
+ end
+ t[k] = s
+ k = k + 1
+ end
+ if lastn == 2 then
+ local a, b = str:byte( n-1, n )
+ local v = a*0x10000 + b*0x100
+ t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])
+ elseif lastn == 1 then
+ local v = str:byte( n )*0x10000
+ t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])
+ end
+ return concat( t )
+end
+
+function base64.decode( b64, decoder, usecaching )
+ decoder = decoder or DEFAULT_DECODER
+ local pattern = '[^%w%+%/%=]'
+ if decoder then
+ local s62, s63
+ for charcode, b64code in pairs( decoder ) do
+ if b64code == 62 then s62 = charcode
+ elseif b64code == 63 then s63 = charcode
+ end
+ end
+ pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )
+ end
+ b64 = b64:gsub( pattern, '' )
+ local cache = usecaching and {}
+ local t, k = {}, 1
+ local n = #b64
+ local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0
+ for i = 1, padding > 0 and n-4 or n, 4 do
+ local a, b, c, d = b64:byte( i, i+3 )
+ local s
+ if usecaching then
+ local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d
+ s = cache[v0]
+ if not s then
+ local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
+ s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
+ cache[v0] = s
+ end
+ else
+ local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
+ s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
+ end
+ t[k] = s
+ k = k + 1
+ end
+ if padding == 1 then
+ local a, b, c = b64:byte( n-3, n-1 )
+ local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
+ t[k] = char( extract(v,16,8), extract(v,8,8))
+ elseif padding == 2 then
+ local a, b = b64:byte( n-3, n-2 )
+ local v = decoder[a]*0x40000 + decoder[b]*0x1000
+ t[k] = char( extract(v,16,8))
+ end
+ return concat( t )
+end
+
+return base64
+
+--[[
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2018 Ilya Kolbin
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+--]]
diff --git a/geopattern/init.lua b/geopattern/init.lua
new file mode 100644
index 0000000..db9da92
--- /dev/null
+++ b/geopattern/init.lua
@@ -0,0 +1,3 @@
+local GeoPattern = require "geopattern.lib"
+
+return GeoPattern
\ No newline at end of file
diff --git a/geopattern/lib.lua b/geopattern/lib.lua
new file mode 100644
index 0000000..949ec2f
--- /dev/null
+++ b/geopattern/lib.lua
@@ -0,0 +1,1105 @@
+local GeoPattern = {}
+local Svg = require "geopattern.svg"
+local base64 = require "geopattern.base64"
+local sha1 = require "geopattern.sha1.init"
+
+local PATTERNS = {'octogons', 'overlappingCircles', 'plusSigns', 'xes', 'sineWaves', 'hexagons', 'overlappingRings',
+ 'plaid', 'triangles', 'squares', 'concentricCircles', 'diamonds', 'tessellation', 'nestedSquares',
+ 'mosaicSquares', 'chevrons'}
+
+local DEFAULTS = {
+ baseColor = "#933c3c"
+}
+
+local FILL_COLOR_DARK = '#222';
+local FILL_COLOR_LIGHT = '#ddd';
+local STROKE_COLOR = '#000';
+local STROKE_OPACITY = 0.02;
+local OPACITY_MIN = 0.02;
+local OPACITY_MAX = 0.15;
+
+local function hex2rgb(hex)
+ local hex = hex:gsub("#", "")
+ if hex:len() == 3 then
+ return {(tonumber("0x" .. hex:sub(1, 1)) * 17) / 255, (tonumber("0x" .. hex:sub(2, 2)) * 17) / 255,
+ (tonumber("0x" .. hex:sub(3, 3)) * 17) / 255}
+ else
+ return {tonumber("0x" .. hex:sub(1, 2)) / 255, tonumber("0x" .. hex:sub(3, 4)) / 255,
+ tonumber("0x" .. hex:sub(5, 6)) / 255}
+ end
+end
+
+local function rgb2hex(rgb)
+ local hexadecimal = '0X'
+ for key, value in pairs(rgb) do
+ local hex = ''
+
+ while (value > 0) do
+ local index = math.fmod(value, 16) + 1
+ value = math.floor(value / 16)
+ hex = string.sub('0123456789ABCDEF', index, index) .. hex
+ end
+
+ if (string.len(hex) == 0) then
+ hex = '00'
+
+ elseif (string.len(hex) == 1) then
+ hex = '0' .. hex
+ end
+
+ hexadecimal = hexadecimal .. hex
+ end
+
+ return hexadecimal
+end
+
+local function rgb2hsl(rgb)
+ local r, g, b, a = rgb[1], rgb[2], rgb[3], rgb[4] or 1
+ r, g, b = r / 255, g / 255, b / 255
+
+ local max, min = math.max(r, g, b), math.min(r, g, b)
+ local h, s, l = nil, nil, (max + min) / 2
+
+ if max == min then
+ h, s = 0, 0 -- achromatic
+ else
+ local d = max - min
+ if l > 0.5 then
+ s = d / (2 - max - min)
+ else
+ s = d / (max + min)
+ end
+ if max == r then
+ h = (g - b) / d
+ if g < b then
+ h = h + 6
+ end
+ elseif max == g then
+ h = (b - r) / d + 2
+ elseif max == b then
+ h = (r - g) / d + 4
+ end
+ h = h / 6
+ end
+
+ local hsl = {}
+ hsl.h = h
+ hsl.s = s
+ hsl.l = l
+ hsl.a = a
+ return hsl
+end
+
+--[[
+ * Converts an HSL color value to RGB. Conversion formula
+ * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
+ * Assumes h, s, and l are contained in the set [0, 1] and
+ * returns r, g, and b in the set [0, 255].
+ *
+ * @param Number h The hue
+ * @param Number s The saturation
+ * @param Number l The lightness
+ * @return Array The RGB representation
+ ]]
+local function hsl2rgb(hsl)
+ local h, s, l, a = hsl.h, hsl.s, hsl.l, hsl.a or 1
+ local r, g, b
+
+ if s == 0 then
+ r, g, b = l, l, l -- achromatic
+
+ else
+ function hue2rgb(p, q, t)
+ if t < 0 then
+ t = t + 1
+ end
+ if t > 1 then
+ t = t - 1
+ end
+ if t < 1 / 6 then
+ return p + (q - p) * 6 * t
+ end
+ if t < 1 / 2 then
+ return q
+ end
+ if t < 2 / 3 then
+ return p + (q - p) * (2 / 3 - t) * 6
+ end
+ return p
+ end
+
+ local q
+ if l < 0.5 then
+ q = l * (1 + s)
+ else
+ q = l + s - l * s
+ end
+ local p = 2 * l - q
+
+ r = hue2rgb(p, q, h + 1 / 3)
+ g = hue2rgb(p, q, h)
+ b = hue2rgb(p, q, h - 1 / 3)
+ end
+
+ return {r * 255, g * 255, b * 255, a * 255}
+end
+
+local function hexVal(hash, index, length)
+ local length = length or 1
+ length = length - 1
+
+ local index = index + 1
+ local substring = hash:sub(index, index + length)
+ local result = tonumber(substring, 16)
+ return result + 0.0
+end
+
+local function map(value, vMin, vMax, dMin, dMax)
+ local vValue = value + 0.0
+ local vRange = 0.0 + vMax - vMin
+ local dRange = 0.0 + dMax - dMin
+ return dMin + ((vValue - vMin) * dRange) / vRange
+end
+
+local function fillColor(val)
+ val = val + 0.0
+ return (val % 2 == 0) and FILL_COLOR_LIGHT or FILL_COLOR_DARK
+end
+
+local function fillOpacity(val)
+ local opacity = map(val, 0, 15, OPACITY_MIN, OPACITY_MAX)
+ return opacity
+end
+
+function GeoPattern:new(str, options)
+ self.opts = {
+ baseColor = DEFAULTS.baseColor
+ }
+
+ if options then
+ if options.generator then self.opts.generator = options.generator end
+ if options.color then self.opts.color = options.color end
+ if options.baseColor then self.opts.baseColor = options.baseColor end
+ end
+
+ self.hash = sha1.sha1(str)
+ self.svg = Svg()
+ self:generateBackground()
+ self:generatePattern()
+ return self
+end
+
+function GeoPattern:generateBackground()
+ local baseColor = {}
+ local hueOffset = {}
+ local rgb = {}
+ local satOffset = {}
+
+ if self.opts.color then
+ rgb = hex2rgb(self.opts.color)
+ else
+ local hue = hexVal(self.hash, 14, 3)
+ hueOffset = map(hue, 0, 4095, 0, 359)
+ satOffset = hexVal(self.hash, 17)
+
+ baseColor = rgb2hsl(hex2rgb(self.opts.baseColor))
+ baseColor.h = (((baseColor.h * 360 - hueOffset) + 360) % 360) / 360;
+
+ if satOffset % 2 == 0 then
+ baseColor.s = math.min(1, ((baseColor.s * 100) + satOffset) / 100);
+ else
+ baseColor.s = math.max(0, ((baseColor.s * 100) - satOffset) / 100);
+ end
+ rgb = hsl2rgb(baseColor)
+ end
+
+ self.color = rgb
+ self.svg:rect(0, 0, '100%', '100%', {
+ fill = string.format('rgb(%d, %d, %d)', rgb[1] * 255, rgb[2] * 255, rgb[3] * 255)
+ })
+end
+
+function table.indexOf(t, object)
+ if type(t) ~= "table" then
+ error("table expected, got " .. type(t), 2)
+ end
+
+ for i, v in pairs(t) do
+ if object == v then
+ return i
+ end
+ end
+end
+
+function GeoPattern:toSvg()
+ return self.svg:toString()
+end
+
+function GeoPattern:toBase64()
+ local str = self:toSvg():gsub("\n", "")
+ return base64.encode(str)
+end
+
+function GeoPattern:generatePattern()
+ local generator = self.opts.generator
+
+ if generator then
+ if table.indexOf(PATTERNS, generator) < 0 then
+ error("Generator doesn't exist", 1)
+ end
+ else
+ local index = hexVal(self.hash, 20) % #PATTERNS + 1
+ generator = PATTERNS[index]
+ end
+
+ return self[generator](self);
+end
+
+local function buildHexagonShape(sideLength)
+ local c = sideLength
+ local a = c / 2
+ local b = math.sin(60 * math.pi / 180) * c
+
+ return string.format('0, %f, %f, 0, %f, 0, %f, %f, %f, %f, %f, %f, 0, %f', b, a, a + c, 2 * c, b, a + c, 2 * b, a,
+ 2 * b, b)
+end
+
+function GeoPattern:hexagons()
+ local scale = hexVal(self.hash, 0)
+ local sideLength = map(scale, 0, 15, 8, 60)
+ local hexHeight = sideLength * math.sqrt(3)
+ local hexWidth = sideLength * 2
+ local hex = buildHexagonShape(sideLength)
+
+ self.svg:setWidth(hexWidth * 3 + sideLength * 3)
+ self.svg:setHeight(hexHeight * 6)
+
+ local dy, fill, opacity, styles, val = {}, {}, {}, {}, {}
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ val = hexVal(self.hash, i)
+ dy = x % 2 == 0 and y * hexHeight or y * hexHeight + hexHeight / 2
+ opacity = fillOpacity(val)
+ fill = fillColor(val)
+ styles = {
+ ['fill'] = fill,
+ ['opacity'] = opacity,
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['transform'] = string.format('translate(%f, %f)', x * sideLength * 1.5 - hexWidth / 2,
+ dy - hexHeight / 2)
+ };
+ self.svg:polyline(hex, styles)
+
+ if x == 0 then
+ styles.transform = string.format('translate(%f, %f)', 6 * sideLength * 1.5 - hexWidth / 2,
+ dy - hexHeight / 2)
+ self.svg:polyline(hex, styles)
+ end
+
+ if y == 0 then
+ dy = x % 2 == 0 and 6 * hexHeight or 6 * hexHeight + hexHeight / 2
+ styles.transform = string.format('translate(%f, %f)', x * sideLength * 1.5 - hexWidth / 2,
+ dy - hexHeight / 2)
+ self.svg:polyline(hex, styles)
+ end
+
+ if x == 0 and y == 0 then
+ styles.transform = string.format('translate(%f, %f)', 6 * sideLength * 1.5 - hexWidth / 2,
+ 5 * hexHeight + hexHeight / 2)
+ self.svg:polyline(hex, styles)
+ end
+ i = i + 1
+ end
+ end
+end
+
+function GeoPattern:sineWaves()
+ local period = math.floor(map(hexVal(self.hash, 0), 0, 15, 100, 400));
+ local amplitude = math.floor(map(hexVal(self.hash, 1), 0, 15, 30, 100));
+ local waveWidth = math.floor(map(hexVal(self.hash, 2), 0, 15, 3, 30));
+
+ self.svg:setWidth(period)
+ self.svg:setHeight(waveWidth * 36)
+
+ for i = 0, 35 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+ local xOffset = period / 4 * 0.7
+
+ local str =
+ 'M0 ' .. amplitude .. ' C ' .. xOffset .. ' 0, ' .. (period / 2 - xOffset) .. ' 0, ' .. (period / 2) .. ' ' ..
+ amplitude .. ' S ' .. (period - xOffset) .. ' ' .. (amplitude * 2) .. ', ' .. period .. ' ' .. amplitude ..
+ ' S ' .. (period * 1.5 - xOffset) .. ' 0, ' .. (period * 1.5) .. ', ' .. amplitude
+
+ local styles = {
+ ['fill'] = 'none',
+ ['stroke'] = fill,
+ ['transform'] = string.format("translate(-%f, %f)", period / 4, waveWidth * i - amplitude * 1.5),
+ ['style'] = {
+ ['opacity'] = opacity,
+ ['stroke-width'] = string.format("%fpx", waveWidth)
+ }
+ }
+ self.svg:path(str, styles)
+
+ styles['transform'] = string.format("translate(-%f, %f)", period / 4,
+ waveWidth * i - amplitude * 1.5 + waveWidth * 36)
+ self.svg:path(str, styles)
+ end
+end
+
+function GeoPattern:overlappingCircles()
+ local scale = hexVal(self.hash, 0)
+ local diameter = map(scale, 0, 15, 25, 200)
+ local radius = diameter / 2
+
+ self.svg:setWidth(radius * 6)
+ self.svg:setHeight(radius * 6)
+
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+
+ local styles = {
+ ['fill'] = fill,
+ ['opacity'] = opacity
+ }
+
+ self.svg:circle(x * radius, y * radius, radius, styles)
+
+ if x == 0 then
+ self.svg:circle(x * radius, 6 * radius, radius, styles)
+ end
+
+ if x == 0 and y == 0 then
+ self.svg:circle(6 * radius, 6 * radius, radius, styles)
+ end
+
+ i = i + 1
+ end
+ end
+end
+
+function GeoPattern:squares()
+ local squareSize = map(hexVal(self.hash, 0), 0, 15, 10, 60);
+
+ self.svg:setWidth(squareSize * 6)
+ self.svg:setHeight(squareSize * 6)
+
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+
+ local styles = {
+ ['fill'] = fill,
+ ['fill-opacity'] = opacity,
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY
+ }
+ self.svg:rect(x * squareSize, y * squareSize, squareSize, squareSize, styles)
+
+ i = i + 1
+ end
+ end
+end
+
+local function buildOctogonShape(squareSize)
+ local s = squareSize
+ local c = s * 0.33
+ return string.format("%f, %f, %f, %f, %f, %f, %f, %f,%f, %f, %f, %f, %f, %f, %f, %f, %f,%f", c, 0, s - c, 0, s, c,
+ s, s - c, s - c, s, c, s, 0, s - c, 0, c, c, 0)
+end
+
+function GeoPattern:octogons()
+ local squareSize = map(hexVal(self.hash, 0), 0, 15, 10, 60);
+ local tile = buildOctogonShape(squareSize)
+
+ self.svg:setWidth(squareSize * 6)
+ self.svg:setHeight(squareSize * 6)
+
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+
+ local styles = {
+ ['fill'] = fill,
+ ['fill-opacity'] = opacity,
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['transform'] = string.format("translate(%f, %f)", x * squareSize, y * squareSize)
+ }
+
+ self.svg:polyline(tile, styles)
+ i = i + 1
+ end
+ end
+end
+
+local function buildTriangleShape(sideLength, height)
+ local halfWidth = sideLength / 2
+ return string.format("%f, %f, %f, %f, %f, %f, %f, %f", halfWidth, 0, sideLength, height, 0, height, halfWidth, 0)
+end
+
+function GeoPattern:triangles()
+ local scale = hexVal(self.hash, 0)
+ local sideLength = map(scale, 0, 15, 15, 80)
+ local triangleHeight = sideLength / 2 * math.sqrt(3)
+ local triangle = buildTriangleShape(sideLength, triangleHeight)
+
+ self.svg:setWidth(sideLength * 3)
+ self.svg:setHeight(triangleHeight * 6)
+
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+
+ local rotation = 0
+ if y % 2 == 0 then
+ rotation = x % 2 == 0 and 180 or 0
+ else
+ rotation = x % 2 ~= 0 and 180 or 0
+ end
+
+ local styles = {
+ ['fill'] = fill,
+ ['fill-opacity'] = opacity,
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)",
+ x * sideLength * 0.5 - sideLength / 2, triangleHeight * y, rotation, sideLength / 2,
+ triangleHeight / 2)
+ }
+
+ self.svg:polyline(triangle, styles)
+
+ if x == 0 then
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)",
+ 6 * sideLength * 0.5 - sideLength / 2, triangleHeight * y, rotation,
+ sideLength / 2, triangleHeight / 2)
+ self.svg:polyline(triangle, styles)
+ end
+
+ i = i + 1
+ end
+ end
+end
+
+local function buildPlusShape(squareSize)
+ return {string.format('', squareSize, 0, squareSize,
+ squareSize * 3),
+ string.format('', 0, squareSize, squareSize * 3,
+ squareSize)}
+end
+
+function GeoPattern:plusSigns()
+ local squareSize = map(hexVal(self.hash, 0), 0, 15, 10, 25)
+ local plusSize = squareSize * 3
+ local plusShape = buildPlusShape(squareSize)
+
+ self.svg:setWidth(squareSize * 12)
+ self.svg:setHeight(squareSize * 12)
+
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+ local dx = y % 2 == 0 and 0 or 1
+
+ local styles = {
+ ['fill'] = fill,
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['fill-opacity'] = opacity,
+ ['transform'] = string.format("translate(%f, %f)",
+ x * plusSize - x * squareSize + dx * squareSize - squareSize,
+ y * plusSize - y * squareSize - plusSize / 2)
+ }
+
+ self.svg:group(plusShape, styles)
+
+ if x == 0 then
+ styles['transform'] = string.format("translate(%f, %f)",
+ 4 * plusSize - x * squareSize + dx * squareSize - squareSize,
+ y * plusSize - y * squareSize - plusSize / 2)
+ self.svg:group(plusShape, styles)
+ end
+
+ if y == 0 then
+ styles['transform'] = string.format("translate(%f, %f)",
+ x * plusSize - x * squareSize + dx * squareSize - squareSize,
+ 4 * plusSize - y * squareSize - plusSize / 2)
+ self.svg:group(plusShape, styles)
+ end
+
+ if x == 0 and y == 0 then
+ styles['transform'] = string.format("translate(%f, %f)",
+ 4 * plusSize - x * squareSize + dx * squareSize - squareSize,
+ 4 * plusSize - y * squareSize - plusSize / 2)
+ self.svg:group(plusShape, styles)
+ end
+
+ i = i + 1
+ end
+ end
+end
+
+function GeoPattern:xes()
+ local squareSize = map(hexVal(self.hash, 0), 0, 15, 10, 25)
+ local xShape = buildPlusShape(squareSize)
+ local xSize = squareSize * 3 * 0.943
+
+ self.svg:setWidth(xSize * 3)
+ self.svg:setHeight(xSize * 3)
+
+ local i = 0
+ for x = 0, 5 do
+ for y = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+ local dy = x % 2 == 0 and y * xSize - xSize * 0.5 or y * xSize - xSize * 0.5 + xSize / 4
+
+ local styles = {
+ ['fill'] = fill,
+ ['opacity'] = opacity,
+ ['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)", x * xSize / 2 - xSize / 2,
+ dy - y * xSize / 2, 45, xSize / 2, xSize / 2)
+ }
+
+ self.svg:group(xShape, styles)
+
+ if x == 0 then
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)", 6 * xSize / 2 - xSize / 2,
+ dy - y * xSize / 2, 45, xSize / 2, xSize / 2)
+ self.svg:group(xShape, styles)
+ end
+
+ if y == 0 then
+ dy = x % 2 == 0 and 6 * xSize - xSize / 2 or 6 * xSize - xSize / 2 + xSize / 4
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)", x * xSize / 2 - xSize / 2,
+ dy - 6 * xSize / 2, 45, xSize / 2, xSize / 2)
+ self.svg:group(xShape, styles)
+ end
+
+ if y == 5 then
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)", x * xSize / 2 - xSize / 2,
+ dy - 11 * xSize / 2, 45, xSize / 2, xSize / 2)
+ self.svg:group(xShape, styles)
+ end
+
+ if x == 0 and y == 0 then
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)", 6 * xSize / 2 - xSize / 2,
+ dy - 6 * xSize / 2, 45, xSize / 2, xSize / 2)
+ self.svg:group(xShape, styles)
+ end
+
+ i = i + 1
+ end
+ end
+end
+
+function GeoPattern:overlappingRings()
+ local scale = hexVal(self.hash, 0)
+ local ringSize = map(scale, 0, 15, 10, 60)
+ local strokeWidth = ringSize / 4
+
+ self.svg:setWidth(ringSize * 6)
+ self.svg:setHeight(ringSize * 6)
+
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+
+ local styles = {
+ ['fill'] = 'none',
+ ['stroke'] = fill,
+ ['opacity'] = opacity,
+ ['stroke-width'] = string.format("%fpx", strokeWidth)
+ }
+
+ self.svg:circle(x * ringSize, y * ringSize, ringSize - strokeWidth / 2, styles)
+
+ if x == 0 then
+ self.svg:circle(6 * ringSize, y * ringSize, ringSize - strokeWidth / 2, styles)
+ end
+
+ if y == 0 then
+ self.svg:circle(x * ringSize, 6 * ringSize, ringSize - strokeWidth / 2, styles)
+ end
+
+ if x == 0 and y == 0 then
+ self.svg:circle(6 * ringSize, 6 * ringSize, ringSize - strokeWidth / 2, styles)
+ end
+
+ i = i + 1
+ end
+ end
+end
+
+function GeoPattern:concentricCircles()
+ local scale = hexVal(self.hash, 0)
+ local ringSize = map(scale, 0, 15, 10, 60)
+ local strokeWidth = ringSize / 5
+
+ self.svg:setWidth((ringSize + strokeWidth) * 6)
+ self.svg:setHeight((ringSize + strokeWidth) * 6)
+
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+
+ local styles = {
+ ['fill'] = 'none',
+ ['stroke'] = fill,
+ ['opacity'] = opacity,
+ ['stroke-width'] = string.format("%fpx", strokeWidth)
+ }
+
+ self.svg:circle(x * ringSize + x * strokeWidth + (ringSize + strokeWidth) / 2,
+ y * ringSize + y * strokeWidth + (ringSize + strokeWidth) / 2, ringSize / 2, styles)
+
+ val = hexVal(self.hash, 39 - i)
+ opacity = fillOpacity(val)
+ fill = fillColor(val)
+
+ styles['fill'] = fill
+ styles['fill-opacity'] = opacity
+ self.svg:circle(x * ringSize + x * strokeWidth + (ringSize + strokeWidth) / 2,
+ y * ringSize + y * strokeWidth + (ringSize + strokeWidth) / 2, ringSize / 4, styles)
+
+ i = i + 1
+ end
+ end
+end
+
+local function buildChevronShape(width, height)
+ local e = height * 0.66
+ return string.format(
+ '',
+ 0, 0, width / 2, height - e, width / 2, height, 0, e, 0, 0, width / 2, height - e, width, 0, width, e,
+ width / 2, height, width / 2, height - e)
+end
+
+function GeoPattern:chevrons()
+ local chevronWidth = map(hexVal(self.hash, 0), 0, 15, 30, 80)
+ local chevronHeight = map(hexVal(self.hash, 0), 0, 15, 30, 80)
+ local chevron = buildChevronShape(chevronWidth, chevronHeight)
+
+ self.svg:setWidth(chevronWidth * 6)
+ self.svg:setHeight(chevronWidth * 6 * 0.66)
+
+ local i = 0
+
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+
+ local styles = {
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['fill'] = fill,
+ ['fill-opacity'] = opacity,
+ ['stroke-width'] = 1,
+ ['transform'] = string.format("translate(%f, %f)", x * chevronWidth,
+ y * chevronHeight * 0.66 - chevronHeight / 2)
+ }
+ self.svg:group(chevron, styles)
+
+ if y == 0 then
+ styles['transform'] = string.format("translate(%f, %f)", x * chevronWidth,
+ 6 * chevronHeight * 0.66 - chevronHeight / 2)
+ self.svg:group(chevron, styles)
+ end
+
+ i = i + 1
+ end
+ end
+end
+
+local function buildDiamondShape(width, height)
+ return string.format('%f, %f, %f, %f, %f, %f, %f, %f', width / 2, 0, width, height / 2, width / 2, height, 0,
+ height / 2)
+end
+
+function GeoPattern:diamonds()
+ local diamondWidth = map(hexVal(self.hash, 0), 0, 15, 10, 50)
+ local diamondHeight = map(hexVal(self.hash, 1), 0, 15, 10, 50)
+ local diamond = buildDiamondShape(diamondWidth, diamondHeight)
+
+ self.svg:setWidth(diamondWidth * 6)
+ self.svg:setHeight(diamondHeight * 3)
+
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+ local dx = y % 2 == 0 and 0 or diamondWidth / 2
+
+ local styles = {
+ ['fill'] = fill,
+ ['fill-opacity'] = opacity,
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['transform'] = string.format("translate(%f, %f)", x * diamondWidth - diamondWidth / 2 + dx,
+ diamondHeight / 2 * y - diamondHeight / 2)
+ }
+
+ self.svg:polyline(diamond, styles)
+
+ if x == 0 then
+ styles['transform'] = string.format("translate(%f, %f)", 6 * diamondWidth - diamondWidth / 2 + dx,
+ diamondHeight / 2 * y - diamondHeight / 2)
+ self.svg:polyline(diamond, styles)
+ end
+
+ if y == 0 then
+ styles['transform'] = string.format("translate(%f, %f)", x * diamondWidth - diamondWidth / 2 + dx,
+ diamondHeight / 2 * 6 - diamondHeight / 2)
+ self.svg:polyline(diamond, styles)
+ end
+
+ if x == 0 and y == 0 then
+ styles['transform'] = string.format("translate(%f, %f)", 6 * diamondWidth - diamondWidth / 2 + dx,
+ diamondHeight / 2 * 6 - diamondHeight / 2)
+ self.svg:polyline(diamond, styles)
+ end
+
+ i = i + 1
+ end
+ end
+end
+
+function GeoPattern:nestedSquares()
+ local blockSize = map(hexVal(self.hash, 0), 0, 15, 4, 12)
+ local squareSize = blockSize * 7
+ local fill, i, opacity, styles, val, x, y
+
+ self.svg:setWidth((squareSize + blockSize) * 6 + blockSize * 6)
+ self.svg:setHeight((squareSize + blockSize) * 6 + blockSize * 6)
+
+ local i = 0
+ for y = 0, 5 do
+ for x = 0, 5 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+
+ local styles = {
+ ['fill'] = 'none',
+ ['stroke'] = fill,
+ ['opacity'] = opacity,
+ ['stroke-width'] = string.format('%fpx', blockSize)
+ }
+
+ self.svg:rect(x * squareSize + x * blockSize * 2 + blockSize / 2,
+ y * squareSize + y * blockSize * 2 + blockSize / 2, squareSize, squareSize, styles)
+
+ val = hexVal(self.hash, 39 - i);
+ opacity = fillOpacity(val);
+ fill = fillColor(val);
+
+ styles = {
+ ['fill'] = 'none',
+ ['stroke'] = fill,
+ ['opacity'] = opacity,
+ ['stroke-width'] = string.format('%fpx', blockSize)
+ }
+
+ self.svg:rect(x * squareSize + x * blockSize * 2 + blockSize / 2 + blockSize * 2,
+ y * squareSize + y * blockSize * 2 + blockSize / 2 + blockSize * 2, blockSize * 3, blockSize * 3, styles)
+
+ i = i + 1
+ end
+ end
+end
+
+function GeoPattern:plaid()
+ local height = 0
+ local width = 0
+
+ local i = 0
+ while i < 35 do
+ local space = hexVal(self.hash, i)
+ height = height + space + 5
+
+ local val = hexVal(self.hash, i + 1)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+ local stripeHeight = val + 5
+
+ self.svg:rect(0, height, '100%', stripeHeight, {
+ ['opacity'] = opacity,
+ ['fill'] = fill
+ });
+
+ height = height + stripeHeight
+ i = i + 2
+ end
+
+ i = 0
+ while i < 35 do
+ local space = hexVal(self.hash, i)
+ width = width + space + 5
+
+ local val = hexVal(self.hash, i + 1)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+ local stripeWidth = val + 5
+
+ self.svg:rect(width, 0, stripeWidth, '100%', {
+ ['opacity'] = opacity,
+ ['fill'] = fill
+ });
+
+ width = width + stripeWidth
+ i = i + 2
+ end
+
+ self.svg:setWidth(width)
+ self.svg:setHeight(height)
+end
+
+local function buildRotatedTriangleShape(sideLength, triangleWidth)
+ local halfHeight = sideLength / 2
+ return string.format('%f, %f, %f, %f, %f, %f, %f, %f', 0, 0, triangleWidth, halfHeight, 0, sideLength, 0, 0)
+end
+
+function GeoPattern:tessellation()
+ local sideLength = map(hexVal(self.hash, 0), 0, 15, 5, 40)
+ local hexHeight = sideLength * math.sqrt(3)
+ local hexWidth = sideLength * 2
+ local triangleHeight = sideLength / 2 * math.sqrt(3)
+ local triangle = buildRotatedTriangleShape(sideLength, triangleHeight)
+ local tileWidth = sideLength * 3 + triangleHeight * 2
+ local tileHeight = (hexHeight * 2) + (sideLength * 2)
+
+ self.svg:setWidth(tileWidth)
+ self.svg:setHeight(tileHeight)
+
+ for i = 0, 19 do
+ local val = hexVal(self.hash, i)
+ local opacity = fillOpacity(val)
+ local fill = fillColor(val)
+
+ local styles = {
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['fill'] = fill,
+ ['fill-opacity'] = opacity,
+ ['stroke-width'] = 1
+ }
+
+ if i == 0 then
+ self.svg:rect(-sideLength / 2, -sideLength / 2, sideLength, sideLength, styles)
+ self.svg:rect(tileWidth - sideLength / 2, -sideLength / 2, sideLength, sideLength, styles)
+ self.svg:rect(-sideLength / 2, tileHeight - sideLength / 2, sideLength, sideLength, styles)
+ self.svg:rect(tileWidth - sideLength / 2, tileHeight - sideLength / 2, sideLength, sideLength, styles)
+ elseif i == 1 then
+ self.svg:rect(hexWidth / 2 + triangleHeight, hexHeight / 2, sideLength, sideLength, styles)
+ elseif i == 2 then
+ self.svg:rect(-sideLength / 2, tileHeight / 2 - sideLength / 2, sideLength, sideLength, styles)
+ self.svg:rect(tileWidth - sideLength / 2, tileHeight / 2 - sideLength / 2, sideLength, sideLength, styles)
+ elseif i == 3 then
+ self.svg:rect(hexWidth / 2 + triangleHeight, hexHeight * 1.5 + sideLength, sideLength, sideLength, styles)
+ elseif i == 4 then
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)", sideLength / 2, -sideLength / 2,
+ 0, sideLength / 2, triangleHeight / 2)
+ self.svg:polyline(triangle, styles)
+
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f) scale(%f, %f)", sideLength / 2,
+ tileHeight - -sideLength / 2, 0, sideLength / 2, triangleHeight / 2, 1, -1)
+ self.svg:polyline(triangle, styles)
+ elseif i == 5 then
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f) scale(%f, %f)",
+ tileWidth - sideLength / 2, -sideLength / 2, 0, sideLength / 2,
+ triangleHeight / 2, -1, 1)
+ self.svg:polyline(triangle, styles)
+
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f) scale(%f, %f)",
+ tileWidth - sideLength / 2, tileHeight + sideLength / 2, 0, sideLength / 2,
+ triangleHeight / 2, -1, -1)
+ self.svg:polyline(triangle, styles)
+ elseif i == 6 then
+ styles['transform'] = string.format("translate(%f, %f)", tileWidth / 2 + sideLength / 2, hexHeight / 2)
+ self.svg:polyline(triangle, styles)
+ elseif i == 7 then
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)",
+ tileWidth - tileWidth / 2 - sideLength / 2, hexHeight / 2, -1, 1)
+ self.svg:polyline(triangle, styles)
+ elseif i == 8 then
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)", tileWidth / 2 + sideLength / 2,
+ tileHeight - hexHeight / 2, 1, -1)
+ self.svg:polyline(triangle, styles)
+ elseif i == 9 then
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)",
+ tileWidth - tileWidth / 2 - sideLength / 2, tileHeight - hexHeight / 2, -1, -1)
+ self.svg:polyline(triangle, styles)
+ elseif i == 10 then
+ styles['transform'] = string.format("translate(%f, %f)", sideLength / 2, tileHeight / 2 - sideLength / 2)
+ self.svg:polyline(triangle, styles)
+ elseif i == 11 then
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)", tileWidth - sideLength / 2,
+ tileHeight / 2 - sideLength / 2, -1, 1)
+ self.svg:polyline(triangle, styles)
+ elseif i == 12 then
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)", sideLength / 2, sideLength / 2,
+ -30, 0, 0)
+ self.svg:rect(0, 0, sideLength, sideLength, styles)
+ elseif i == 13 then
+ styles['transform'] = string.format("scale(%f, %f) translate(%f, %f) rotate(%f, %f, %f)", -1, 1,
+ -tileWidth + sideLength / 2, sideLength / 2, -30, 0, 0)
+ self.svg:rect(0, 0, sideLength, sideLength, styles)
+ elseif i == 14 then
+ styles['transform'] = string.format("translate(%f, %f) rotate(%f, %f, %f)", sideLength / 2,
+ tileHeight / 2 - sideLength / 2 - sideLength, 30, 0, sideLength)
+ self.svg:rect(0, 0, sideLength, sideLength, styles)
+ elseif i == 15 then
+ styles['transform'] = string.format("scale(%f, %f) translate(%f, %f) rotate(%f, %f, %f)", -1, 1,
+ -tileWidth + sideLength / 2, tileHeight / 2 - sideLength / 2 - sideLength, 30, 0,
+ sideLength)
+ self.svg:rect(0, 0, sideLength, sideLength, styles)
+ elseif i == 16 then
+ styles['transform'] = string.format("scale(%f, %f) translate(%f, %f) rotate(%f, %f, %f)", 1, -1,
+ sideLength / 2, -tileHeight + tileHeight / 2 - sideLength / 2 - sideLength, 30, 0,
+ sideLength)
+ self.svg:rect(0, 0, sideLength, sideLength, styles)
+ elseif i == 17 then
+ styles['transform'] = string.format("scale(%f, %f) translate(%f, %f) rotate(%f, %f, %f)", -1, -1,
+ -tileWidth + sideLength / 2,
+ -tileHeight + tileHeight / 2 - sideLength / 2 - sideLength, 30, 0, sideLength)
+ self.svg:rect(0, 0, sideLength, sideLength, styles)
+ elseif i == 18 then
+ styles['transform'] = string.format("scale(%f, %f) translate(%f, %f) rotate(%f, %f, %f)", 1, -1,
+ sideLength / 2, -tileHeight + sideLength / 2, -30, 0, 0)
+ self.svg:rect(0, 0, sideLength, sideLength, styles)
+ elseif i == 19 then
+ styles['transform'] = string.format("scale(%f, %f) translate(%f, %f) rotate(%f, %f, %f)", -1, -1,
+ -tileWidth + sideLength / 2, -tileHeight + sideLength / 2, -30, 0, 0)
+ self.svg:rect(0, 0, sideLength, sideLength, styles)
+ end
+ end
+end
+
+local function buildRightTriangleShape(sideLength)
+ return string.format('%f, %f, %f, %f, %f, %f, %f, %f', 0, 0, sideLength, sideLength, 0, sideLength, 0, 0)
+end
+
+local function drawInnerMosaicTile(svg, x, y, triangleSize, vals)
+ local triangle = buildRightTriangleShape(triangleSize)
+ local opacity = fillOpacity(vals[1])
+ local fill = fillColor(vals[1])
+ local styles = {
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['fill-opacity'] = opacity,
+ ['fill'] = fill,
+ ['transform'] = string.format("translate(%f, %f) scale(%f, %f)", x + triangleSize, y, -1, 1)
+ }
+ svg:polyline(triangle, styles)
+
+ styles['transform'] =
+ string.format("translate(%f, %f) scale(%f, %f)", x + triangleSize, y + triangleSize * 2, 1, -1)
+ svg:polyline(triangle, styles)
+
+ opacity = fillOpacity(vals[2]);
+ fill = fillColor(vals[2]);
+ styles = {
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['fill-opacity'] = opacity,
+ ['fill'] = fill
+ }
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)", x + triangleSize, y + triangleSize * 2, -1,
+ -1)
+ svg:polyline(triangle, styles)
+
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)", x + triangleSize, y, 1, 1)
+ svg:polyline(triangle, styles)
+end
+
+local function drawOuterMosaicTile(svg, x, y, triangleSize, val)
+ local opacity = fillOpacity(val);
+ local fill = fillColor(val);
+ local triangle = buildRightTriangleShape(triangleSize);
+ local styles = {
+ ['stroke'] = STROKE_COLOR,
+ ['stroke-opacity'] = STROKE_OPACITY,
+ ['fill-opacity'] = opacity,
+ ['fill'] = fill
+ }
+
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)", x, y + triangleSize, 1, -1)
+ svg:polyline(triangle, styles)
+
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)", x + triangleSize * 2, y + triangleSize, -1,
+ -1)
+ svg:polyline(triangle, styles)
+
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)", x, y + triangleSize, 1, 1)
+ svg:polyline(triangle, styles)
+
+ styles['transform'] = string.format("translate(%f, %f) scale(%f, %f)", x + triangleSize * 2, y + triangleSize, 1, 1)
+ svg:polyline(triangle, styles)
+end
+
+function GeoPattern:mosaicSquares()
+ local triangleSize = map(hexVal(self.hash, 0), 0, 15, 15, 50)
+
+ self.svg:setWidth(triangleSize * 8)
+ self.svg:setHeight(triangleSize * 8)
+
+ local i = 0
+ for y = 0, 3 do
+ for x = 0, 3 do
+ if x % 2 == 0 then
+ if y % 2 == 0 then
+ drawOuterMosaicTile(self.svg, x * triangleSize * 2, y * triangleSize * 2, triangleSize,
+ hexVal(self.hash, i))
+ else
+ drawInnerMosaicTile(self.svg, x * triangleSize * 2, y * triangleSize * 2, triangleSize,
+ {hexVal(self.hash, i), hexVal(self.hash, i + 1)})
+ end
+ else
+ if y % 2 == 0 then
+ drawInnerMosaicTile(self.svg, x * triangleSize * 2, y * triangleSize * 2, triangleSize,
+ {hexVal(self.hash, i), hexVal(self.hash, i + 1)})
+ else
+ drawOuterMosaicTile(self.svg, x * triangleSize * 2, y * triangleSize * 2, triangleSize,
+ hexVal(self.hash, i))
+ end
+ end
+
+ i = i + 1
+ end
+ end
+
+end
+
+return GeoPattern
diff --git a/geopattern/sha1/bit32_ops.lua b/geopattern/sha1/bit32_ops.lua
new file mode 100644
index 0000000..344b0f3
--- /dev/null
+++ b/geopattern/sha1/bit32_ops.lua
@@ -0,0 +1,24 @@
+local bit32 = require "bit32"
+
+local ops = {}
+
+local band = bit32.band
+local bor = bit32.bor
+local bxor = bit32.bxor
+
+ops.uint32_lrot = bit32.lrotate
+ops.byte_xor = bxor
+ops.uint32_xor_3 = bxor
+ops.uint32_xor_4 = bxor
+
+function ops.uint32_ternary(a, b, c)
+ -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
+ return bxor(c, band(a, bxor(b, c)))
+end
+
+function ops.uint32_majority(a, b, c)
+ -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
+ return bor(band(a, bor(b, c)), band(b, c))
+end
+
+return ops
diff --git a/geopattern/sha1/bit_ops.lua b/geopattern/sha1/bit_ops.lua
new file mode 100644
index 0000000..5fbaf05
--- /dev/null
+++ b/geopattern/sha1/bit_ops.lua
@@ -0,0 +1,24 @@
+local bit = require "bit"
+
+local ops = {}
+
+local band = bit.band
+local bor = bit.bor
+local bxor = bit.bxor
+
+ops.uint32_lrot = bit.rol
+ops.byte_xor = bxor
+ops.uint32_xor_3 = bxor
+ops.uint32_xor_4 = bxor
+
+function ops.uint32_ternary(a, b, c)
+ -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
+ return bxor(c, band(a, bxor(b, c)))
+end
+
+function ops.uint32_majority(a, b, c)
+ -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
+ return bor(band(a, bor(b, c)), band(b, c))
+end
+
+return ops
diff --git a/geopattern/sha1/common.lua b/geopattern/sha1/common.lua
new file mode 100644
index 0000000..c31ffef
--- /dev/null
+++ b/geopattern/sha1/common.lua
@@ -0,0 +1,20 @@
+local common = {}
+
+-- Merges four bytes into a uint32 number.
+function common.bytes_to_uint32(a, b, c, d)
+ return a * 0x1000000 + b * 0x10000 + c * 0x100 + d
+end
+
+-- Splits a uint32 number into four bytes.
+function common.uint32_to_bytes(a)
+ local a4 = a % 256
+ a = (a - a4) / 256
+ local a3 = a % 256
+ a = (a - a3) / 256
+ local a2 = a % 256
+ local a1 = (a - a2) / 256
+ return a1, a2, a3, a4
+end
+
+
+return common
diff --git a/geopattern/sha1/init.lua b/geopattern/sha1/init.lua
new file mode 100644
index 0000000..f9cdc42
--- /dev/null
+++ b/geopattern/sha1/init.lua
@@ -0,0 +1,195 @@
+local common = require "geopattern.sha1.common"
+
+local sha1 = {
+ -- Meta fields retained for compatibility.
+ _VERSION = "sha.lua 0.6.0",
+ _URL = "https://github.com/mpeterv/sha1",
+ _DESCRIPTION = [[
+SHA-1 secure hash and HMAC-SHA1 signature computation in Lua,
+using bit and bit32 modules and Lua 5.3 operators when available
+and falling back to a pure Lua implementation on Lua 5.1.
+Based on code orignally by Jeffrey Friedl and modified by
+Eike Decker and Enrique García Cota.]],
+ _LICENSE = [[
+MIT LICENSE
+
+Copyright (c) 2013 Enrique García Cota, Eike Decker, Jeffrey Friedl
+Copyright (c) 2018 Peter Melnichenko
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.]]
+}
+
+sha1.version = "0.6.0"
+
+local function choose_ops()
+ if _VERSION:find("5%.3") then
+ return "lua53_ops"
+ elseif pcall(require, "bit") then
+ return "bit_ops"
+ elseif pcall(require, "bit32") then
+ return "bit32_ops"
+ else
+ return "pure_lua_ops"
+ end
+end
+
+local ops = require("geopattern.sha1." .. choose_ops())
+local uint32_lrot = ops.uint32_lrot
+local byte_xor = ops.byte_xor
+local uint32_xor_3 = ops.uint32_xor_3
+local uint32_xor_4 = ops.uint32_xor_4
+local uint32_ternary = ops.uint32_ternary
+local uint32_majority = ops.uint32_majority
+
+local bytes_to_uint32 = common.bytes_to_uint32
+local uint32_to_bytes = common.uint32_to_bytes
+
+local sbyte = string.byte
+local schar = string.char
+local sformat = string.format
+local srep = string.rep
+
+local function hex_to_binary(hex)
+ return (hex:gsub("..", function(hexval)
+ return schar(tonumber(hexval, 16))
+ end))
+end
+
+-- Calculates SHA1 for a string, returns it encoded as 40 hexadecimal digits.
+function sha1.sha1(str)
+ -- Input preprocessing.
+ -- First, append a `1` bit and seven `0` bits.
+ local first_append = schar(0x80)
+
+ -- Next, append some zero bytes to make the length of the final message a multiple of 64.
+ -- Eight more bytes will be added next.
+ local non_zero_message_bytes = #str + 1 + 8
+ local second_append = srep(schar(0), -non_zero_message_bytes % 64)
+
+ -- Finally, append the length of the original message in bits as a 64-bit number.
+ -- Assume that it fits into the lower 32 bits.
+ local third_append = schar(0, 0, 0, 0, uint32_to_bytes(#str * 8))
+
+ str = str .. first_append .. second_append .. third_append
+ assert(#str % 64 == 0)
+
+ -- Initialize hash value.
+ local h0 = 0x67452301
+ local h1 = 0xEFCDAB89
+ local h2 = 0x98BADCFE
+ local h3 = 0x10325476
+ local h4 = 0xC3D2E1F0
+
+ local w = {}
+
+ -- Process the input in successive 64-byte chunks.
+ for chunk_start = 1, #str, 64 do
+ -- Load the chunk into W[0..15] as uint32 numbers.
+ local uint32_start = chunk_start
+
+ for i = 0, 15 do
+ w[i] = bytes_to_uint32(sbyte(str, uint32_start, uint32_start + 3))
+ uint32_start = uint32_start + 4
+ end
+
+ -- Extend the input vector.
+ for i = 16, 79 do
+ w[i] = uint32_lrot(uint32_xor_4(w[i - 3], w[i - 8], w[i - 14], w[i - 16]), 1)
+ end
+
+ -- Initialize hash value for this chunk.
+ local a = h0
+ local b = h1
+ local c = h2
+ local d = h3
+ local e = h4
+
+ -- Main loop.
+ for i = 0, 79 do
+ local f
+ local k
+
+ if i <= 19 then
+ f = uint32_ternary(b, c, d)
+ k = 0x5A827999
+ elseif i <= 39 then
+ f = uint32_xor_3(b, c, d)
+ k = 0x6ED9EBA1
+ elseif i <= 59 then
+ f = uint32_majority(b, c, d)
+ k = 0x8F1BBCDC
+ else
+ f = uint32_xor_3(b, c, d)
+ k = 0xCA62C1D6
+ end
+
+ local temp = (uint32_lrot(a, 5) + f + e + k + w[i]) % 4294967296
+ e = d
+ d = c
+ c = uint32_lrot(b, 30)
+ b = a
+ a = temp
+ end
+
+ -- Add this chunk's hash to result so far.
+ h0 = (h0 + a) % 4294967296
+ h1 = (h1 + b) % 4294967296
+ h2 = (h2 + c) % 4294967296
+ h3 = (h3 + d) % 4294967296
+ h4 = (h4 + e) % 4294967296
+ end
+
+ return sformat("%08x%08x%08x%08x%08x", h0, h1, h2, h3, h4)
+end
+
+function sha1.binary(str)
+ return hex_to_binary(sha1.sha1(str))
+end
+
+-- Precalculate replacement tables.
+local xor_with_0x5c = {}
+local xor_with_0x36 = {}
+
+for i = 0, 0xff do
+ xor_with_0x5c[schar(i)] = schar(byte_xor(0x5c, i))
+ xor_with_0x36[schar(i)] = schar(byte_xor(0x36, i))
+end
+
+-- 512 bits.
+local BLOCK_SIZE = 64
+
+function sha1.hmac(key, text)
+ if #key > BLOCK_SIZE then
+ key = sha1.binary(key)
+ end
+
+ local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. srep(schar(0x36), BLOCK_SIZE - #key)
+ local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. srep(schar(0x5c), BLOCK_SIZE - #key)
+
+ return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text))
+end
+
+function sha1.hmac_binary(key, text)
+ return hex_to_binary(sha1.hmac(key, text))
+end
+
+setmetatable(sha1, {__call = function(_, str) return sha1.sha1(str) end})
+
+return sha1
diff --git a/geopattern/sha1/lua53_ops.lua b/geopattern/sha1/lua53_ops.lua
new file mode 100644
index 0000000..d5312e6
--- /dev/null
+++ b/geopattern/sha1/lua53_ops.lua
@@ -0,0 +1,29 @@
+local ops = {}
+
+function ops.uint32_lrot(a, bits)
+ return ((a << bits) & 0xFFFFFFFF) | (a >> (32 - bits))
+end
+
+function ops.byte_xor(a, b)
+ return a ~ b
+end
+
+function ops.uint32_xor_3(a, b, c)
+ return a ~ b ~ c
+end
+
+function ops.uint32_xor_4(a, b, c, d)
+ return a ~ b ~ c ~ d
+end
+
+function ops.uint32_ternary(a, b, c)
+ -- c ~ (a & (b ~ c)) has less bitwise operations than (a & b) | (~a & c).
+ return c ~ (a & (b ~ c))
+end
+
+function ops.uint32_majority(a, b, c)
+ -- (a & (b | c)) | (b & c) has less bitwise operations than (a & b) | (a & c) | (b & c).
+ return (a & (b | c)) | (b & c)
+end
+
+return ops
diff --git a/geopattern/sha1/pure_lua_ops.lua b/geopattern/sha1/pure_lua_ops.lua
new file mode 100644
index 0000000..d05697c
--- /dev/null
+++ b/geopattern/sha1/pure_lua_ops.lua
@@ -0,0 +1,144 @@
+local common = require "geopattern.sha1.common"
+
+local ops = {}
+
+local bytes_to_uint32 = common.bytes_to_uint32
+local uint32_to_bytes = common.uint32_to_bytes
+
+function ops.uint32_lrot(a, bits)
+ local power = 2 ^ bits
+ local inv_power = 4294967296 / power
+ local lower_bits = a % inv_power
+ return (lower_bits * power) + ((a - lower_bits) / inv_power)
+end
+
+-- Build caches for bitwise `and` and `xor` over bytes to speed up uint32 operations.
+-- Building the cache by simply applying these operators over all pairs is too slow and
+-- duplicates a lot of work over different bits of inputs.
+-- Instead, when building a cache over bytes, for each pair of bytes split both arguments
+-- into two 4-bit numbers, calculate values over these two halves, then join the results into a byte again.
+-- While there are 256 * 256 = 65536 pairs of bytes, there are only 16 * 16 = 256 pairs
+-- of 4-bit numbers, so that building an 8-bit cache given a 4-bit cache is rather efficient.
+-- The same logic is applied recursively to make a 4-bit cache from a 2-bit cache and a 2-bit
+-- cache from a 1-bit cache, which is calculated given the 1-bit version of the operator.
+
+-- Returns a cache containing all values of a bitwise operator over numbers with given number of bits,
+-- given an operator over single bits.
+-- Value of `op(a, b)` is stored in `cache[a * (2 ^ bits) + b]`.
+local function make_op_cache(bit_op, bits)
+ if bits == 1 then
+ return {[0] = bit_op(0, 0), bit_op(0, 1), bit_op(1, 0), bit_op(1, 1)}
+ end
+
+ local half_bits = bits / 2
+ local size = 2 ^ bits
+ local half_size = 2 ^ half_bits
+ local half_cache = make_op_cache(bit_op, half_bits)
+
+ local cache = {}
+
+ -- The implementation used is an optimized version of the following reference one,
+ -- with intermediate calculations reused and moved to the outermost loop possible.
+ -- It's possible to reorder the loops and move the calculation of one of the
+ -- half-results one level up, but then the cache is not filled in a proper array order
+ -- and its access performance suffers.
+
+ -- for a1 = 0, half_size - 1 do
+ -- for a2 = 0, half_size - 1 do
+ -- for b1 = 0, half_size - 1 do
+ -- for b2 = 0, half_size - 1 do
+ -- local a = a1 * half_size + a2
+ -- local b = b1 * half_size + b2
+ -- local v1 = half_cache[a1 * half_size + b1]
+ -- local v2 = half_cache[a2 * half_size + b2]
+ -- local v = v1 * half_size + v2
+ -- cache[a * size + b] = v
+ -- end
+ -- end
+ -- end
+ -- end
+
+ for a1 = 0, half_size - 1 do
+ local a1_half_size = a1 * half_size
+
+ for a2 = 0, half_size - 1 do
+ local a2_size = a2 * half_size
+ local a_size = (a1_half_size + a2) * size
+
+ for b1 = 0, half_size - 1 do
+ local a_size_plus_b1_half_size = a_size + b1 * half_size
+ local v1_half_size = half_cache[a1_half_size + b1] * half_size
+
+ for b2 = 0, half_size - 1 do
+ cache[a_size_plus_b1_half_size + b2] = v1_half_size + half_cache[a2_size + b2]
+ end
+ end
+ end
+ end
+
+ return cache
+end
+
+local byte_and_cache = make_op_cache(function(a, b) return a * b end, 8)
+local byte_xor_cache = make_op_cache(function(a, b) return a == b and 0 or 1 end, 8)
+
+function ops.byte_xor(a, b)
+ return byte_xor_cache[a * 256 + b]
+end
+
+function ops.uint32_xor_3(a, b, c)
+ local a1, a2, a3, a4 = uint32_to_bytes(a)
+ local b1, b2, b3, b4 = uint32_to_bytes(b)
+ local c1, c2, c3, c4 = uint32_to_bytes(c)
+
+ return bytes_to_uint32(
+ byte_xor_cache[a1 * 256 + byte_xor_cache[b1 * 256 + c1]],
+ byte_xor_cache[a2 * 256 + byte_xor_cache[b2 * 256 + c2]],
+ byte_xor_cache[a3 * 256 + byte_xor_cache[b3 * 256 + c3]],
+ byte_xor_cache[a4 * 256 + byte_xor_cache[b4 * 256 + c4]]
+ )
+end
+
+function ops.uint32_xor_4(a, b, c, d)
+ local a1, a2, a3, a4 = uint32_to_bytes(a)
+ local b1, b2, b3, b4 = uint32_to_bytes(b)
+ local c1, c2, c3, c4 = uint32_to_bytes(c)
+ local d1, d2, d3, d4 = uint32_to_bytes(d)
+
+ return bytes_to_uint32(
+ byte_xor_cache[a1 * 256 + byte_xor_cache[b1 * 256 + byte_xor_cache[c1 * 256 + d1]]],
+ byte_xor_cache[a2 * 256 + byte_xor_cache[b2 * 256 + byte_xor_cache[c2 * 256 + d2]]],
+ byte_xor_cache[a3 * 256 + byte_xor_cache[b3 * 256 + byte_xor_cache[c3 * 256 + d3]]],
+ byte_xor_cache[a4 * 256 + byte_xor_cache[b4 * 256 + byte_xor_cache[c4 * 256 + d4]]]
+ )
+end
+
+function ops.uint32_ternary(a, b, c)
+ local a1, a2, a3, a4 = uint32_to_bytes(a)
+ local b1, b2, b3, b4 = uint32_to_bytes(b)
+ local c1, c2, c3, c4 = uint32_to_bytes(c)
+
+ -- (a & b) + (~a & c) has less bitwise operations than (a & b) | (~a & c).
+ return bytes_to_uint32(
+ byte_and_cache[b1 * 256 + a1] + byte_and_cache[c1 * 256 + 255 - a1],
+ byte_and_cache[b2 * 256 + a2] + byte_and_cache[c2 * 256 + 255 - a2],
+ byte_and_cache[b3 * 256 + a3] + byte_and_cache[c3 * 256 + 255 - a3],
+ byte_and_cache[b4 * 256 + a4] + byte_and_cache[c4 * 256 + 255 - a4]
+ )
+end
+
+function ops.uint32_majority(a, b, c)
+ local a1, a2, a3, a4 = uint32_to_bytes(a)
+ local b1, b2, b3, b4 = uint32_to_bytes(b)
+ local c1, c2, c3, c4 = uint32_to_bytes(c)
+
+ -- (a & b) + (c & (a ~ b)) has less bitwise operations than (a & b) | (a & c) | (b & c).
+ return bytes_to_uint32(
+ byte_and_cache[a1 * 256 + b1] + byte_and_cache[c1 * 256 + byte_xor_cache[a1 * 256 + b1]],
+ byte_and_cache[a2 * 256 + b2] + byte_and_cache[c2 * 256 + byte_xor_cache[a2 * 256 + b2]],
+ byte_and_cache[a3 * 256 + b3] + byte_and_cache[c3 * 256 + byte_xor_cache[a3 * 256 + b3]],
+ byte_and_cache[a4 * 256 + b4] + byte_and_cache[c4 * 256 + byte_xor_cache[a4 * 256 + b4]]
+ )
+end
+
+return ops
diff --git a/geopattern/svg.lua b/geopattern/svg.lua
new file mode 100644
index 0000000..d132954
--- /dev/null
+++ b/geopattern/svg.lua
@@ -0,0 +1,93 @@
+local Svg = {}
+
+Svg.__index = Svg
+
+setmetatable(Svg, {
+ __call = function(cls, ...)
+ local self = setmetatable({}, cls)
+ self:_new(...)
+ return self
+ end
+})
+
+function Svg:_new()
+ self.width = 100
+ self.height = 100
+ self.svg_string = ""
+end
+
+function Svg:height()
+ return self.height
+end
+
+function Svg:setHeight(height)
+ self.height = math.floor(height)
+end
+
+function Svg:setWidth(width)
+ self.width = math.floor(width)
+end
+
+function Svg:header()
+ local header = ''
+end
+
+function Svg:toString()
+ return table.concat({self:header(), self.svg_string, self:closer()}, '')
+end
+
+function Svg:rect(x, y, width, height, args)
+ local str = string.format('', x, y, width, height,
+ self:write_args(args))
+ self.svg_string = self.svg_string .. str
+end
+
+function Svg:circle(x, y, radius, args)
+ local str = string.format('', x, y, radius, self:write_args(args))
+ self.svg_string = self.svg_string .. str
+end
+
+function Svg:path(str, args)
+ self.svg_string = self.svg_string .. string.format('', str, self:write_args(args))
+end
+
+function Svg:polyline_str(str, args)
+ return string.format('', str, self:write_args(args))
+end
+
+function Svg:polyline(str, args)
+ self.svg_string = self.svg_string .. string.format('', str, self:write_args(args))
+end
+
+function Svg:group(elements, args)
+ self.svg_string = self.svg_string .. string.format('', self:write_args(args))
+ if type(elements) == "string" then
+ self.svg_string = self.svg_string .. elements
+ else
+ for key, value in pairs(elements) do
+ self.svg_string = self.svg_string .. ' ' .. value
+ end
+ end
+ self.svg_string = self.svg_string .. ''
+end
+
+function Svg:write_args(args)
+ local str = ''
+ for key, value in pairs(args) do
+ if type(value) == "table" then
+ for k, v in pairs(value) do
+ str = str .. string.format(' %s="%s"', k, v)
+ end
+ else
+ str = str .. string.format(' %s="%s"', key, value)
+ end
+ end
+ return str
+end
+
+return Svg
diff --git a/main.lua b/main.lua
new file mode 100644
index 0000000..f03c9ad
--- /dev/null
+++ b/main.lua
@@ -0,0 +1,5 @@
+
+local GeoPattern = require "geopattern"
+
+local geo = GeoPattern:new("GitHubrahab")
+print(geo:toSvg())
\ No newline at end of file