From 9e37733f7642ef623ca87cf4dbde218a8c8f1731 Mon Sep 17 00:00:00 2001 From: Ivan Azoyan Date: Mon, 5 Oct 2020 16:20:57 +0400 Subject: [PATCH] first commit --- README.md | 104 ++- geopattern.lua | 2 + geopattern/base64.lua | 201 ++++++ geopattern/init.lua | 3 + geopattern/lib.lua | 1105 ++++++++++++++++++++++++++++++ geopattern/sha1/bit32_ops.lua | 24 + geopattern/sha1/bit_ops.lua | 24 + geopattern/sha1/common.lua | 20 + geopattern/sha1/init.lua | 195 ++++++ geopattern/sha1/lua53_ops.lua | 29 + geopattern/sha1/pure_lua_ops.lua | 144 ++++ geopattern/svg.lua | 93 +++ main.lua | 5 + 13 files changed, 1948 insertions(+), 1 deletion(-) create mode 100644 geopattern.lua create mode 100644 geopattern/base64.lua create mode 100644 geopattern/init.lua create mode 100644 geopattern/lib.lua create mode 100644 geopattern/sha1/bit32_ops.lua create mode 100644 geopattern/sha1/bit_ops.lua create mode 100644 geopattern/sha1/common.lua create mode 100644 geopattern/sha1/init.lua create mode 100644 geopattern/sha1/lua53_ops.lua create mode 100644 geopattern/sha1/pure_lua_ops.lua create mode 100644 geopattern/svg.lua create mode 100644 main.lua 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. +![geopattern example](https://github.com/azoyan/geopattern/blob/media/geopattern.gif) + +**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 = '' + return header:format(self.width, self.height) +end + +function Svg:closer() + return '' +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