snowy 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 88efa9809a87f18c9a1a0cac09ac97cad8b79517
4
+ data.tar.gz: 297a08a7dd949d0da1bc7082c4da98c697de0f56
5
+ SHA512:
6
+ metadata.gz: 87702bc961ce392404cad01d2ce482c1fe6c30ecad2cbe8c3739fd695c785d84288c078da64c3d699ce607c12438ffe0460900238f54db4303f2650ca330543c
7
+ data.tar.gz: 72e89848974cb49e0e750a9d8bff96986428ee2954fde7c0bff10133d96d6f57fa2f6230dab63311fd0abbcef1df51c6965bab8c68682a108771b9b8ea779de2
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2016, dearblue. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or
4
+ without modification, are permitted provided that the following
5
+ conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright
10
+ notice, this list of conditions and the following disclaimer in
11
+ the documentation and/or other materials provided with the
12
+ distribution.
13
+
14
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,41 @@
1
+ # snowy
2
+
3
+ "snowy" is an identicon implements with the snow crystal motif.
4
+
5
+ * package name: snowy
6
+ * version: 0.1
7
+ * software quality: EXPERIMENTAL
8
+ * license: BSD-2-clause License
9
+ * author: dearblue <dearblue@users.noreply.github.com>
10
+ * report issue to: <https://github.com/dearblue/ruby-snowy/issues/>
11
+ * dependency ruby: ruby-2.1+
12
+ * dependency ruby gems: (none)
13
+ * dependency library: (none)
14
+ * bundled external C library: (none)
15
+
16
+ ![snowy demonstration](snowy-demo.png)
17
+
18
+
19
+
20
+ ## How to usage
21
+
22
+ ``` ruby:ruby
23
+ require "snowy"
24
+ require "zlib"
25
+
26
+ str = "abcdefg"
27
+ salt = 0
28
+ id = Zlib.crc32(str, salt) # transcode to 32-bits integer
29
+ pngdata = Snowy.generate_to_png(id, size: 256)
30
+ File.binwrite("snowy.png", pngdata)
31
+ ```
32
+
33
+
34
+ ## Demonstration with web browser
35
+
36
+ ``` shell
37
+ $ gem install sinatra haml
38
+ $ ruby snowy-demo.rb
39
+ ```
40
+
41
+ And, access to http://localhost:4567/ on web browser.
@@ -0,0 +1,212 @@
1
+
2
+ require "pathname"
3
+ require "rake/clean"
4
+
5
+ docnames = "{README,LICENSE,CHANGELOG,Changelog,HISTORY}"
6
+ doctypes = "{,.txt,.rd,.rdoc,.md,.markdown}"
7
+ cexttypes = "{c,C,cc,cxx,cpp,h,H,hh}"
8
+
9
+ DOC = FileList["#{docnames}{,.ja}#{doctypes}"] +
10
+ FileList["{contrib,ext}/**/#{docnames}{,.ja}#{doctypes}"] +
11
+ FileList["ext/**/*.#{cexttypes}"]
12
+ EXT = FileList["ext/**/*"]
13
+ BIN = FileList["bin/*"]
14
+ LIB = FileList["lib/**/*.rb"]
15
+ SPEC = FileList["spec/**/*"]
16
+ TEST = FileList["test/**/*"]
17
+ EXAMPLE = FileList["examples/**/*"]
18
+ GEMSTUB_SRC = "gemstub.rb"
19
+ RAKEFILE = [File.basename(__FILE__), GEMSTUB_SRC]
20
+ EXTRA = []
21
+ EXTCONF = FileList["ext/**/extconf.rb"]
22
+ EXTCONF.reject! { |n| !File.file?(n) }
23
+ EXTMAP = {}
24
+
25
+ load GEMSTUB_SRC
26
+
27
+ EXTMAP.dup.each_pair do |dir, name|
28
+ EXTMAP[Pathname.new(dir).cleanpath.to_s] = Pathname.new(name).cleanpath.to_s
29
+ end
30
+
31
+ GEMSTUB.extensions += EXTCONF
32
+ GEMSTUB.executables += FileList["bin/*"].map { |n| File.basename n }
33
+ GEMSTUB.executables.sort!
34
+
35
+ PACKAGENAME = "#{GEMSTUB.name}-#{GEMSTUB.version}"
36
+ GEMFILE = "#{PACKAGENAME}.gem"
37
+ GEMSPEC = "#{PACKAGENAME}.gemspec"
38
+
39
+ GEMSTUB.files += DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + RAKEFILE + EXTRA
40
+ GEMSTUB.files.sort!
41
+ if GEMSTUB.rdoc_options.nil? || GEMSTUB.rdoc_options.empty?
42
+ readme = %W(.md .markdown .rd .rdoc .txt #{""}).map { |ext| "README#{ext}" }.find { |m| DOC.find { |n| n == m } }
43
+ GEMSTUB.rdoc_options = %w(--charset UTF-8) + (readme ? %W(-m #{readme}) : [])
44
+ end
45
+ GEMSTUB.extra_rdoc_files += DOC + LIB + EXT.reject { |n| n.include?("/externals/") || !%w(.h .hh .c .cc .cpp .cxx).include?(File.extname(n)) }
46
+ GEMSTUB.extra_rdoc_files.sort!
47
+
48
+ GEMSTUB_TRYOUT = GEMSTUB.dup
49
+ GEMSTUB_TRYOUT.version = "#{GEMSTUB.version}#{Time.now.strftime(".TRYOUT.%Y%m%d.%H%M%S")}"
50
+ PACKAGENAME_TRYOUT = "#{GEMSTUB.name}-#{GEMSTUB_TRYOUT.version}"
51
+ GEMFILE_TRYOUT = "#{PACKAGENAME_TRYOUT}.gem"
52
+ GEMSPEC_TRYOUT = "#{PACKAGENAME_TRYOUT}.gemspec"
53
+
54
+ CLEAN << GEMSPEC << GEMSPEC_TRYOUT
55
+ CLOBBER << GEMFILE
56
+
57
+ task :default => :tryout do
58
+ $stderr.puts <<-EOS
59
+ #{__FILE__}:#{__LINE__}:
60
+ \ttype ``rake release'' to build release package.
61
+ EOS
62
+ end
63
+
64
+ desc "build tryout package"
65
+ task :tryout
66
+
67
+ desc "build release package"
68
+ task :release => :all
69
+
70
+ unless EXTCONF.empty?
71
+ RUBYSET ||= (ENV["RUBYSET"] || "").split(",")
72
+
73
+ if RUBYSET.nil? || RUBYSET.empty?
74
+ $stderr.puts <<-EOS
75
+ #{__FILE__}:
76
+ |
77
+ | If you want binary gem package, launch rake with ``RUBYSET`` enviroment
78
+ | variable for set ruby interpreters by comma separated.
79
+ |
80
+ | e.g.) $ rake RUBYSET=ruby
81
+ | or) $ rake RUBYSET=ruby21,ruby22,ruby23
82
+ |
83
+ EOS
84
+ else
85
+ platforms = RUBYSET.map { |ruby| `#{ruby} --disable-gems -e "puts RUBY_PLATFORM"`.chomp }
86
+ platforms1 = platforms.uniq
87
+ unless platforms1.size == 1 && !platforms1[0].empty?
88
+ abort <<-EOS
89
+ #{__FILE__}:#{__LINE__}: different platforms:
90
+ #{RUBYSET.zip(platforms).map { |ruby, platform| "%24s => %s" % [ruby, platform] }.join("\n")}
91
+ ABORTED.
92
+ EOS
93
+ end
94
+ PLATFORM = platforms1[0]
95
+
96
+ RUBY_VERSIONS = RUBYSET.map do |ruby|
97
+ ver = `#{ruby} --disable-gems -e "puts RUBY_VERSION"`.slice(/\d+\.\d+/)
98
+ raise "failed ruby checking - ``#{ruby}''" unless $?.success?
99
+ [ver, ruby]
100
+ end
101
+
102
+ SOFILES_SET = RUBY_VERSIONS.map { |(ver, ruby)|
103
+ EXTCONF.map { |extconf|
104
+ extdir = Pathname.new(extconf).cleanpath.dirname.to_s
105
+ case
106
+ when soname = EXTMAP[extdir.sub(/^ext\//i, "")]
107
+ soname = soname.sub(/\.so$/i, "")
108
+ when extdir == "ext" || extdir == "."
109
+ soname = GEMSTUB.name
110
+ else
111
+ soname = File.basename(extdir)
112
+ end
113
+
114
+ [ruby, File.join("lib", "#{soname.sub(/(?<=\/)|^(?!.*\/)/, "#{ver}/")}.so"), extconf]
115
+ }
116
+ }.flatten(1)
117
+ SOFILES = SOFILES_SET.map { |(ruby, sopath, extconf)| sopath }
118
+
119
+ GEMSTUB_NATIVE = GEMSTUB.dup
120
+ GEMSTUB_NATIVE.files += SOFILES
121
+ GEMSTUB_NATIVE.platform = Gem::Platform.new(PLATFORM).to_s
122
+ GEMSTUB_NATIVE.extensions.clear
123
+ GEMFILE_NATIVE = "#{GEMSTUB_NATIVE.name}-#{GEMSTUB_NATIVE.version}-#{GEMSTUB_NATIVE.platform}.gem"
124
+ GEMSPEC_NATIVE = "#{GEMSTUB_NATIVE.name}-#{GEMSTUB_NATIVE.platform}.gemspec"
125
+
126
+ task :all => ["native-gem", GEMFILE]
127
+
128
+ desc "build binary gem package"
129
+ task "native-gem" => GEMFILE_NATIVE
130
+
131
+ desc "generate binary gemspec"
132
+ task "native-gemspec" => GEMSPEC_NATIVE
133
+
134
+ file GEMFILE_NATIVE => DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + SOFILES + RAKEFILE + [GEMSPEC_NATIVE] do
135
+ sh "gem build #{GEMSPEC_NATIVE}"
136
+ end
137
+
138
+ file GEMSPEC_NATIVE => RAKEFILE do
139
+ File.write(GEMSPEC_NATIVE, GEMSTUB_NATIVE.to_ruby, mode: "wb")
140
+ end
141
+
142
+ desc "build c-extension libraries"
143
+ task "sofiles" => SOFILES
144
+
145
+ SOFILES_SET.each do |(ruby, soname, extconf)|
146
+ sodir = File.dirname(soname)
147
+ makefile = File.join(sodir, "Makefile")
148
+
149
+ CLEAN << GEMSPEC_NATIVE << sodir
150
+ CLOBBER << GEMFILE_NATIVE
151
+
152
+ directory sodir
153
+
154
+ desc "generate Makefile for binary extension library"
155
+ file makefile => [sodir, extconf] do
156
+ rel_extconf = Pathname.new(extconf).relative_path_from(Pathname.new(sodir)).to_s
157
+ cd sodir do
158
+ sh *%W"#{ruby} #{rel_extconf} --ruby=#{ruby} #{ENV["EXTCONF"]}"
159
+ end
160
+ end
161
+
162
+ desc "build binary extension library"
163
+ file soname => [makefile] + EXT do
164
+ cd sodir do
165
+ sh "make"
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+
173
+ task :all => GEMFILE
174
+ task :tryout => GEMFILE_TRYOUT
175
+
176
+ desc "generate local rdoc"
177
+ task :rdoc => DOC + LIB do
178
+ sh *(%w(rdoc) + GEMSTUB.rdoc_options + DOC + LIB)
179
+ end
180
+
181
+ desc "launch rspec"
182
+ task rspec: :all do
183
+ sh "rspec"
184
+ end
185
+
186
+ desc "build gem package"
187
+ task gem: GEMFILE
188
+
189
+ desc "generate gemspec"
190
+ task gemspec: GEMSPEC
191
+
192
+ desc "print package name"
193
+ task "package-name" do
194
+ puts PACKAGENAME
195
+ end
196
+
197
+ file GEMFILE => DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + RAKEFILE + [GEMSPEC] do
198
+ sh "gem build #{GEMSPEC}"
199
+ end
200
+
201
+ file GEMFILE_TRYOUT => DOC + EXT + EXTCONF + BIN + LIB + SPEC + TEST + EXAMPLE + RAKEFILE + [GEMSPEC_TRYOUT] do
202
+ #file GEMFILE_TRYOUT do
203
+ sh "gem build #{GEMSPEC_TRYOUT}"
204
+ end
205
+
206
+ file GEMSPEC => RAKEFILE do
207
+ File.write(GEMSPEC, GEMSTUB.to_ruby, mode: "wb")
208
+ end
209
+
210
+ file GEMSPEC_TRYOUT => RAKEFILE do
211
+ File.write(GEMSPEC_TRYOUT, GEMSTUB_TRYOUT.to_ruby, mode: "wb")
212
+ end
@@ -0,0 +1,25 @@
1
+ verreg = /^\s*\*\s+version:\s*(\d+(?:\.\w+)+)\s*$/i
2
+ unless File.read("README.md", mode: "rt") =~ verreg
3
+ raise "``version'' is not defined or bad syntax in ``README.md''"
4
+ end
5
+
6
+ version = String($1)
7
+
8
+ GEMSTUB = Gem::Specification.new do |s|
9
+ s.name = "snowy"
10
+ s.version = version
11
+ s.summary = "an identicon implements"
12
+ s.description = <<EOS
13
+ Pure ruby identicon implement with the snow crystal motif
14
+ EOS
15
+ s.homepage = "https://github.com/dearblue/ruby-snowy/"
16
+ s.license = "BSD-2-Clause"
17
+ s.author = "dearblue"
18
+ s.email = "dearblue@users.noreply.github.com"
19
+
20
+ #s.required_ruby_version = ">= 2.1"
21
+ s.add_development_dependency "rake", "~> 11"
22
+ end
23
+
24
+ EXTRA << "snowy-demo.png"
25
+ EXTRA << "snowy-demo.rb"
@@ -0,0 +1,529 @@
1
+ #!ruby
2
+
3
+ require_relative "snowy/common"
4
+
5
+ module Snowy
6
+ using Extentions
7
+
8
+ module DefaultDriver
9
+ def self.render(size, triangles, background, color, outline, angle)
10
+ img = Canvas.new(size, size, 0, 1, plt = [background, color])
11
+ plt << outline if outline
12
+
13
+ img.instance_exec do
14
+ translate(width / 2.0, height / 2.0)
15
+ scale(width / 32.0, height / 32.0)
16
+ rotate_deg(angle)
17
+ sqrt3 = Math.sqrt(3)
18
+ [30, 90, 150, 210, 270, 330].each do |deg|
19
+ push_matrix do
20
+ rotate_deg(deg)
21
+ scale(1, sqrt3)
22
+ triangles.each do |t|
23
+ triangle(*t)
24
+ end
25
+ end
26
+ end
27
+
28
+ # plot outline
29
+ if outline
30
+ w = width
31
+ h = height
32
+ pix = pixels
33
+ (1 ... (h - 1)).step(1) do |py|
34
+ py0 = py * w
35
+ py1 = (py + 1) * w
36
+ py2 = (py - 1) * w
37
+ (1 ... (w - 1)).step(1) do |px|
38
+ px1 = px + 1
39
+ px2 = px - 1
40
+ if pix.getbyte(py0 + px) == 0
41
+ if pix.getbyte(py0 + px1) == 1 ||
42
+ pix.getbyte(py0 + px2) == 1 ||
43
+ pix.getbyte(py1 + px ) == 1 ||
44
+ pix.getbyte(py2 + px ) == 1
45
+ pix.setbyte(py0 + px, 2)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ export_to_png
52
+ end
53
+ end
54
+ end
55
+
56
+ @@driver = DefaultDriver
57
+
58
+
59
+ BLACK = rgba(0, 0, 0)
60
+ WHITE = rgba(255, 255, 255)
61
+
62
+ #
63
+ # 256 indexed canvas
64
+ #
65
+ class Canvas
66
+ attr_reader :width, :height, :pixels, :matrix, :palette, :color
67
+
68
+ MIN_PIXEL = 1
69
+ MAX_PIXEL = 4096
70
+
71
+ def initialize(width, height, background = 0, color = 1, palette = [WHITE, BLACK])
72
+ @width = width.to_i
73
+ @height = height.to_i
74
+ if @width < MIN_PIXEL || @width > MAX_PIXEL ||
75
+ @height < MIN_PIXEL || @height > MAX_PIXEL
76
+ raise ArgumentError,
77
+ "width and height are too small or large (given %d x %d, expect %d..%d)" %
78
+ [@width, @height, MIN_PIXEL, MAX_PIXEL]
79
+ end
80
+
81
+ @pixels = [background].pack("C") * (@width * @height)
82
+ @matrix = Matrix.new
83
+ @palette = palette
84
+ @color = color
85
+ end
86
+
87
+ ## primitive operators
88
+ ;
89
+
90
+ def getpixel(x, y)
91
+ x = x.to_i
92
+ y = y.to_i
93
+ if validate_point(x, y)
94
+ getpixel!(x, y)
95
+ else
96
+ nil
97
+ end
98
+ end
99
+
100
+ alias [] getpixel
101
+
102
+ def setpixel(x, y, level)
103
+ x = x.to_i
104
+ y = y.to_i
105
+ validate_point(x, y) and setpixel!(x, y, level)
106
+ self
107
+ end
108
+
109
+ alias []= setpixel
110
+
111
+ ## lowlevel operations
112
+ ;
113
+
114
+ def fill(color = @color)
115
+ @pixels.bytesize.times do |i|
116
+ @pixels.setbyte(i, color)
117
+ end
118
+ self
119
+ end
120
+
121
+ def validate_point(x, y)
122
+ if x < 0 || x >= @width || y < 0 || y >= @height
123
+ false
124
+ else
125
+ true
126
+ end
127
+ end
128
+
129
+ def test_point(x, y)
130
+ unless validate_point(x, y)
131
+ raise ArgumentError,
132
+ "x and y are out of canvas (given (%d, %d), expect (0, 0) .. (%d, %d))" %
133
+ [x, y, @width - 1, @height - 1]
134
+ end
135
+
136
+ nil
137
+ end
138
+
139
+ def getpixel!(x, y)
140
+ @pixels.getbyte(x + y * @width)
141
+ end
142
+
143
+ def setpixel!(x, y, color)
144
+ @pixels.setbyte(x + y * @width, color)
145
+ end
146
+
147
+ def plotscanline!(y, x0, x1)
148
+ w = width
149
+ px = pixels
150
+ i = color
151
+ x0.upto(x1 - 1) { |x| px.setbyte(x + y * w, i) }
152
+ self
153
+ end
154
+
155
+ ## primitive figures
156
+ ;
157
+
158
+ def plotscanline(y, x0, x1)
159
+ y = y.round
160
+ x0 = x0.round
161
+ x1 = x1.round
162
+
163
+ if y >= 0 && y < height
164
+ (x0, x1) = x1, x0 if x0 > x1
165
+ if x0 < width && x1 >= 0
166
+ x0 = [0, x0].max
167
+ x1 = [x1, width].min
168
+ plotscanline! y, x0, x1
169
+ end
170
+ end
171
+
172
+ self
173
+ end
174
+
175
+ ## additional operators
176
+ ;
177
+
178
+ #
179
+ # plot dot by char
180
+ #
181
+ # example:
182
+ # # plot dot "Error(TIMEDOUT)"
183
+ # canv.dot_by_char(5, 5, <<-CODE, { "*" => 3 })
184
+ # *** * *** *** * * *** ** *** * * *** *
185
+ # * * * * ** ** * * * * * * * * *
186
+ # *** *** *** *** *** * * * * * * *** * * * * * * * *
187
+ # * * * * * * * * * * * * * * * * * * * * *
188
+ # *** * * *** * * * *** * * *** ** *** *** * *
189
+ # CODE
190
+ #
191
+ def dot_by_char(x, y, seq, colormap = { "*" => 2 })
192
+ x0 = x
193
+ seq.each_char do |ch|
194
+ case ch
195
+ when "\n"
196
+ x = x0
197
+ y += 1
198
+ when " "
199
+ x += 1
200
+ else
201
+ if color = colormap[ch]
202
+ setpixel(x, y, color)
203
+ end
204
+ x += 1
205
+ end
206
+ end
207
+
208
+ self
209
+ end
210
+
211
+ ## transform operators
212
+ ;
213
+
214
+ def init_matrix
215
+ matrix.init
216
+ self
217
+ end
218
+
219
+ def push_matrix
220
+ save = matrix.dup
221
+ return save unless block_given?
222
+
223
+ begin
224
+ yield
225
+ ensure
226
+ matrix.load(save)
227
+ end
228
+ end
229
+
230
+ def pop_matrix(saved_matrix)
231
+ matrix.load(saved_matrix)
232
+ self
233
+ end
234
+
235
+ def mult_matrix(matrix1)
236
+ matrix.mult(matrix1)
237
+ self
238
+ end
239
+
240
+ def transform(x, y, w = 1)
241
+ matrix.transform(x, y, w)
242
+ end
243
+
244
+ def translate(dx, dy, dw = 1)
245
+ matrix.translate(dx, dy, dw)
246
+ self
247
+ end
248
+
249
+ def scale(ax, ay, aw = 1)
250
+ matrix.scale(ax, ay, aw)
251
+ self
252
+ end
253
+
254
+ def rotate(rad)
255
+ matrix.rotate(rad)
256
+ self
257
+ end
258
+
259
+ def rotate_deg(deg)
260
+ matrix.rotate(Math::PI * deg / 180)
261
+ self
262
+ end
263
+
264
+ ## transformed figures
265
+ ;
266
+
267
+ def triangle(x0, y0, x1, y1, x2, y2)
268
+ (x0, y0) = transform(x0, y0)
269
+ (x1, y1) = transform(x1, y1)
270
+ (x2, y2) = transform(x2, y2)
271
+
272
+ x0 = x0.round
273
+ x1 = x1.round
274
+ x2 = x2.round
275
+ y0 = y0.round
276
+ y1 = y1.round
277
+ y2 = y2.round
278
+
279
+ (x0, y0, x1, y1) = x1, y1, x0, y0 if y0 > y1
280
+ (x1, y1, x2, y2) = x2, y2, x1, y1 if y1 > y2
281
+ (x0, y0, x1, y1) = x1, y1, x0, y0 if y0 > y1
282
+
283
+ if y0 == y2
284
+ (a, *, b) = [x0, x1, x2].sort
285
+ plotscanline(y0, a, b)
286
+ else
287
+ d1 = (x1 - x0) / (y1 - y0).to_f
288
+ d2 = (x2 - x0) / (y2 - y0).to_f
289
+ (y0 ... y1).step(1) do |py|
290
+ dy = py - y0
291
+ plotscanline(py, x0 + dy * d1, x0 + dy * d2)
292
+ end
293
+
294
+ if y1 == y2
295
+ plotscanline(y1, x1, x2)
296
+ else
297
+ d0 = (x0 - x2) / (y0 - y2).to_f
298
+ d1 = (x1 - x2) / (y1 - y2).to_f
299
+ (y1 .. y2).step(1) do |py|
300
+ dy = (py - y2)
301
+ plotscanline(py, x2 + dy * d1, x2 + dy * d0)
302
+ end
303
+ end
304
+ end
305
+
306
+ self
307
+ end
308
+
309
+ ## extra
310
+ ;
311
+
312
+ def halfdown!
313
+ pixels = @pixels
314
+ height = @height
315
+ width2 = width >> 1
316
+ height2 = height >> 1
317
+ height2.times do |y|
318
+ width2.times do |x|
319
+ xx = x * 2
320
+ yy = y * 2
321
+ xx1 = xx + 1
322
+ yy1 = (yy + 1) * height
323
+ yy = yy * height
324
+ pixels.setbyte(x + y * width2,
325
+ (pixels.getbyte(xx + yy) +
326
+ pixels.getbyte(xx1 + yy) +
327
+ pixels.getbyte(xx + yy1) +
328
+ pixels.getbyte(xx1 + yy1)) / 4)
329
+ end
330
+ end
331
+
332
+ @width = width2
333
+ @height = height2
334
+ @pixels[(width2 * height2) .. -1] = ""
335
+
336
+ self
337
+ end
338
+
339
+ ## export to png
340
+ ;
341
+
342
+ def export_to_png(io = "".b, level: Zlib::DEFAULT_COMPRESSION)
343
+ io << [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A].pack("C*")
344
+ storechunk = ->(code, chunk) {
345
+ crc = Zlib.crc32(chunk, Zlib.crc32(code))
346
+ io << [chunk.bytesize].pack("N")
347
+ io << code << chunk
348
+ io << [crc].pack("N")
349
+ }
350
+
351
+ ## IDAT
352
+ ## color
353
+ ## type bit depth
354
+ ## 0 1,2,4,8,16 immediate grayscale sample
355
+ ## 2 8,16 immediate RGB sample
356
+ ## 3 1,2,4,8 palette sample (need PLTE chunk)
357
+ ## 4 8,16 immediate grayscale sample with alpha
358
+ ## 6 8,16 immediate RGB sample with alpha
359
+ colorbits = 8
360
+ colortype = 3
361
+ storechunk["IHDR", [width, height, colorbits, colortype, 0, 0, 0].pack("NNCCCCC")]
362
+
363
+ ## PLTE
364
+ plte = palette.reduce("".b) { |a, e|
365
+ a << e.pack_rgb
366
+ }
367
+ storechunk["PLTE", plte]
368
+
369
+ ## tRNS
370
+ ## Alpha for palette index 0: 1 byte
371
+ ## Alpha for palette index 1: 1 byte
372
+ ## ...etc...
373
+ trns = palette.reduce([]) { |a, e| a << e.get_alpha }
374
+ storechunk["tRNS", trns.pack("C*")]
375
+
376
+ ## iTXt
377
+ ## Keyword: 1-79 bytes (character string)
378
+ ## Null separator: 1 byte
379
+ ## Compression flag: 1 byte
380
+ ## Compression method: 1 byte
381
+ ## Language tag: 0 or more bytes (character string)
382
+ ## Null separator: 1 byte
383
+ ## Translated keyword: 0 or more bytes
384
+ ## Null separator: 1 byte
385
+ ## Text: 0 or more bytes
386
+ storechunk["iTXt", ["snowy", 0, 0, <<-'SNOWY'].pack("a*xCCxxa*")]
387
+ This image is generated by snowy <https://rubygems.org/gems/snowy>
388
+ SNOWY
389
+ storechunk["iTXt", ["LICENSING", 0, 0, <<-'LICENSING'].pack("a*xCCxxa*")]
390
+ Creative Commons License Zero (CC0 / Public Domain)
391
+ See <https://creativecommons.org/publicdomain/zero/1.0/>
392
+ LICENSING
393
+
394
+ ## IDAT
395
+ scanline = ->(lines, line) {
396
+ lines << "\0" << line
397
+ }
398
+ scanline ||= ->(lines, line) {
399
+ v = 0
400
+ line.each_byte.with_index { |vv, i|
401
+ line.setbyte(i, vv - v)
402
+ v = vv
403
+ }
404
+ lines << "\1" << line
405
+ }
406
+ lines = height.times.reduce("".b) { |a, h|
407
+ line = pixels.byteslice(h * width, width)
408
+ scanline[a, line]
409
+ }
410
+ storechunk["IDAT", Zlib.deflate(lines, level)]
411
+
412
+ storechunk["IEND", ""]
413
+
414
+ io
415
+ end
416
+ end
417
+
418
+ class Matrix
419
+ attr_reader :matrix
420
+
421
+ def self.[](matrix)
422
+ case matrix
423
+ when self
424
+ matrix
425
+ else
426
+ new matrix
427
+ end
428
+ end
429
+
430
+ def initialize(mat = nil)
431
+ @matrix = [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]
432
+
433
+ if mat
434
+ load mat
435
+ else
436
+ reset
437
+ end
438
+ end
439
+
440
+ def initialize_copy(mat)
441
+ @matrix = [[nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]
442
+ load mat
443
+ end
444
+
445
+ def reset
446
+ load [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
447
+ end
448
+
449
+ def load(mat)
450
+ case mat
451
+ when Matrix
452
+ matrix[0][0, 3] = mat.matrix[0]
453
+ matrix[1][0, 3] = mat.matrix[1]
454
+ matrix[2][0, 3] = mat.matrix[2]
455
+ when Array
456
+ if mat.size == 3 &&
457
+ mat[0].kind_of?(Array) && mat[0].size == 3 &&
458
+ mat[1].kind_of?(Array) && mat[1].size == 3 &&
459
+ mat[2].kind_of?(Array) && mat[2].size == 3
460
+ matrix[0][0, 3] = mat[0]
461
+ matrix[1][0, 3] = mat[1]
462
+ matrix[2][0, 3] = mat[2]
463
+ else
464
+ if mat.size == 9
465
+ matrix[0][0, 3] = mat[0, 3]
466
+ matrix[1][0, 3] = mat[3, 3]
467
+ matrix[2][0, 3] = mat[6, 3]
468
+ else
469
+ raise ArgumentError, "wrong element number (given #{mat.size} elements, expect 9 elements)"
470
+ end
471
+ end
472
+ else
473
+ raise ArgumentError, "wrong argument type (expect Snowy::Matrix or Array)"
474
+ end
475
+
476
+ self
477
+ end
478
+
479
+ def mult(mat)
480
+ mult! self.class[mat].matrix
481
+ end
482
+
483
+ def mult!(mat)
484
+ 3.times do |i|
485
+ m0 = matrix[i]
486
+ mm = m0.dup
487
+ 3.times do |j|
488
+ m0[j] = mm[0] * mat[0][j] +
489
+ mm[1] * mat[1][j] +
490
+ mm[2] * mat[2][j]
491
+ end
492
+ end
493
+
494
+ self
495
+ end
496
+
497
+ def transform2(x, y, w = 1)
498
+ mx = matrix[0]
499
+ my = matrix[1]
500
+ [x * mx[0] + y * mx[1] + w * mx[2],
501
+ x * my[0] + y * my[1] + w * my[2]]
502
+ end
503
+
504
+ alias transform transform2
505
+
506
+ def transform3(x, y, w = 1)
507
+ mx = matrix[0]
508
+ my = matrix[1]
509
+ mw = matrix[2]
510
+ [x * mx[0] + y * mx[1] + w * mx[2],
511
+ x * my[0] + y * my[1] + w * my[2],
512
+ x * mw[0] + y * mw[1] + w * mw[2]]
513
+ end
514
+
515
+ def translate(dx, dy, dw = 1)
516
+ mult!([[1, 0, dx], [0, 1, dy], [0, 0, dw]])
517
+ end
518
+
519
+ def scale(ax, ay, aw = 1)
520
+ mult!([[ax, 0, 0], [0, ay, 0], [0, 0, aw]])
521
+ end
522
+
523
+ def rotate(rad)
524
+ cos = Math.cos(rad)
525
+ sin = Math.sin(rad)
526
+ mult!([[cos, -sin, 0], [sin, cos, 0], [0, 0, 1]])
527
+ end
528
+ end
529
+ end
@@ -0,0 +1,50 @@
1
+ require_relative "common"
2
+ require "cairo"
3
+
4
+ module Snowy
5
+ using Extentions
6
+
7
+ module CairoDriver
8
+ def self.render(size, triangles, background, color, outline, angle)
9
+ deg2rad = Math::PI / 180
10
+ surface = Cairo::ImageSurface.new(Cairo::Format::ARGB32, size, size)
11
+ Cairo::Context.new(surface) do |context|
12
+ context.instance_eval do
13
+ set_line_width 0.5
14
+ set_source_color [background.get_red / 255.0, background.get_green / 255.0, background.get_blue / 255.0, background.get_alpha / 255.0]
15
+ paint
16
+ translate(size / 2.0, size / 2.0)
17
+ scale(size / 32.0, size / 32.0)
18
+ rotate(angle * deg2rad) unless deg2rad == 0
19
+ sqrt3 = Math.sqrt(3)
20
+ [30, 90, 150, 210, 270, 330].each do |deg|
21
+ save do
22
+ rotate(-deg * deg2rad)
23
+ scale(1, sqrt3)
24
+ triangles.each do |(x1, y1, x2, y2, x3, y3)|
25
+ move_to(x1, y1)
26
+ line_to(x2, y2)
27
+ line_to(x3, y3)
28
+ close_path
29
+ end
30
+ end
31
+ end
32
+ if outline
33
+ set_source_rgba outline.get_red / 255.0, outline.get_green / 255.0, outline.get_blue / 255.0, 255 / 255.0
34
+ stroke true
35
+ end
36
+ set_source_rgba color.get_red / 255.0, color.get_green / 255.0, color.get_blue / 255.0, 255 / 255.0
37
+ fill
38
+ end
39
+ end
40
+
41
+ buffer = "".b
42
+ outport = Object.new
43
+ outport.define_singleton_method(:write, ->(d) { buffer << d; d.bytesize })
44
+ surface.write_to_png outport
45
+ buffer
46
+ end
47
+ end
48
+
49
+ @@driver = CairoDriver
50
+ end
@@ -0,0 +1,154 @@
1
+ require "zlib"
2
+
3
+ module Snowy
4
+ module Extentions
5
+ refine Numeric do
6
+ unless 0.respond_to?(:clamp)
7
+ def clamp(min, max)
8
+ case
9
+ when self < min
10
+ return min
11
+ when self > max
12
+ return max
13
+ else
14
+ return self
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ refine Integer do
21
+ def get_red
22
+ 0xff & (self >> 24)
23
+ end
24
+
25
+ def get_green
26
+ 0xff & (self >> 16)
27
+ end
28
+
29
+ def get_blue
30
+ 0xff & (self >> 8)
31
+ end
32
+
33
+ def get_alpha
34
+ 0xff & self
35
+ end
36
+
37
+ def pack_rgb
38
+ [self >> 24, self >> 16, self >> 8].pack("C3")
39
+ end
40
+
41
+ def pack_rgba
42
+ [self].pack("N")
43
+ end
44
+ end
45
+ end
46
+
47
+ using Extentions
48
+
49
+
50
+ def self.rgba(r, g, b, a = 255)
51
+ return (r.to_i.clamp(0, 255) << 24) |
52
+ (g.to_i.clamp(0, 255) << 16) |
53
+ (b.to_i.clamp(0, 255) << 8) |
54
+ (a.to_i.clamp(0, 255) )
55
+ end
56
+
57
+
58
+ #
59
+ # call-seq:
60
+ # generate_to_png(code, size = 128)
61
+ #
62
+ # @return
63
+ # string object
64
+ #
65
+ # @param [Integer] code
66
+ # 32 bits integer
67
+ #
68
+ # @param [Integer] size
69
+ # output png image size
70
+ #
71
+ def self.generate_to_png(code, size: 128, cap: true, extendcap: true, angle: 0, color: nil, outline: nil)
72
+ if code.kind_of?(String)
73
+ code = Zlib.crc32(code)
74
+ end
75
+
76
+ if color
77
+ if outline.nil?
78
+ r = color.get_red
79
+ g = color.get_green
80
+ b = color.get_blue
81
+ a = color.get_alpha
82
+ end
83
+ else
84
+ r = (code >> 28) & 0x0f
85
+ g = (code >> 24) & 0x0f
86
+ b = (code >> 20) & 0x0f
87
+ r = (r << 3) | 0x80
88
+ g = (g << 3) | 0x80
89
+ b = (b << 3) | 0x80
90
+ color = rgba(r, g, b)
91
+ end
92
+
93
+ if outline.nil?
94
+ outline = rgba(r * 7 / 8, g * 7 / 8, b * 7 / 8, a || 0xff)
95
+ end
96
+
97
+ code = code ^ (code >> 16) ^ ((code & 0xffff) << 16) if extendcap
98
+
99
+ depth = extendcap ? 7 : 6
100
+ triangles = [] # [[x1, y1, x2, y2, x3, y3], ...]
101
+ depth.times do |level|
102
+ # level # 現在の階層
103
+ # total # 現在の階層までの総要素数
104
+ # layer # 現在の階層の要素数
105
+ level_1 = level + 1
106
+ total = level_1 ** 2
107
+ layer = level * 2 + 1
108
+ offbase = (level * level_1) / 2
109
+ offpivot = (layer + 1) / 2 - 1
110
+ layer.times do |i|
111
+ if !extendcap && level_1 == depth
112
+ i += 1
113
+ break if (i + 1) == layer
114
+ #break if i > layer
115
+ end
116
+
117
+ #if (i + 1) > (layer + 1) / 2
118
+ if i > offpivot
119
+ # mirror
120
+ off = offbase + (layer - i - 1)
121
+ else
122
+ off = offbase + i
123
+ end
124
+
125
+ off -= 1 if !extendcap && level_1 == depth
126
+ next if code[off] == 0
127
+
128
+ m_level_i = -level + i
129
+ if i.even?
130
+ triangles << [m_level_i, level, m_level_i + 1, level_1, m_level_i - 1, level_1]
131
+ else
132
+ triangles << [m_level_i, level_1, m_level_i + 1, level, m_level_i - 1, level]
133
+ end
134
+ end
135
+ end
136
+
137
+ # 一番外側に三角形を配置する
138
+ if cap
139
+ if extendcap
140
+ triangles << [-5, 7, -3, 7, -4, 8]
141
+ triangles << [5, 7, 4, 8, 3, 7]
142
+ else
143
+ triangles << [-4, 6, -2, 6, -3, 7]
144
+ triangles << [4, 6, 3, 7, 2, 6]
145
+ end
146
+ end
147
+
148
+ driver.render(size, triangles, rgba(255, 255, 255, 0), color, outline, angle)
149
+ end
150
+
151
+ def self.driver
152
+ @@driver
153
+ end
154
+ end
Binary file
@@ -0,0 +1,50 @@
1
+ #!ruby
2
+
3
+ require "sinatra"
4
+ require "haml"
5
+ require_relative "lib/snowy.rb"
6
+ #require_relative "lib/snowy/cairo.rb"
7
+
8
+ if $-d
9
+ require "sinatra/reloader"
10
+ also_reload "lib/snowy/common.rb"
11
+ also_reload "lib/snowy.rb"
12
+ #also_reload "lib/snowy/cairo.rb"
13
+ end
14
+
15
+ get "/" do
16
+ haml <<-HAML
17
+ !!! 5
18
+ %title Demonstration for snowy
19
+ :css
20
+ body
21
+ {
22
+ background: url("snowy/#{"%08X" % (0xeef00000 | rand(0x100000))}.png?size=256&angle=-10&extendcap=true");
23
+ }
24
+
25
+ %div{style: "text-align: center"}
26
+ %div{style: "padding: 1em; font-size: 200%"}
27
+ "snowy" is an identicon implements with the snow crystal motif.
28
+ %div
29
+ #{20.times.map { %(<img src="snowy/%08X.png?size=131&angle=5&extendcap=true" alt="">) % [rand(0xffffffff)] }.join}
30
+ %div
31
+ #{20.times.map { %(<img src="snowy/%08X.png?size=131&angle=0&extendcap=true" alt="">) % [rand(0x00100000) | 0x69f00000] }.join}
32
+ HAML
33
+ end
34
+
35
+ get "/snowy/*.png" do |id|
36
+ id = id.hex
37
+ size = (params["size"] || 128).to_i
38
+ size = [32, size, 4096].sort[1]
39
+ cap = (params["nocap"]) ? false : true
40
+ angle = (params["angle"] || 0).to_i
41
+ if params["monotone"]
42
+ id = (id & 0x000fffff) | 0x9cf00000
43
+ end
44
+ extendcap = (params["extendcap"] || "false") == "false" ? false : true
45
+ bin = Snowy.generate_to_png(id, size: size, cap: cap, extendcap: extendcap, angle: -angle)
46
+
47
+ status 200
48
+ headers "Content-Type" => "image/png"
49
+ body bin
50
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snowy
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - dearblue
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '11'
27
+ description: 'Pure ruby identicon implement with the snow crystal motif
28
+
29
+ '
30
+ email: dearblue@users.noreply.github.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files:
34
+ - LICENSE
35
+ - README.md
36
+ - lib/snowy.rb
37
+ - lib/snowy/cairo.rb
38
+ - lib/snowy/common.rb
39
+ files:
40
+ - LICENSE
41
+ - README.md
42
+ - Rakefile
43
+ - gemstub.rb
44
+ - lib/snowy.rb
45
+ - lib/snowy/cairo.rb
46
+ - lib/snowy/common.rb
47
+ - snowy-demo.png
48
+ - snowy-demo.rb
49
+ homepage: https://github.com/dearblue/ruby-snowy/
50
+ licenses:
51
+ - BSD-2-Clause
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options:
55
+ - "--charset"
56
+ - UTF-8
57
+ - "-m"
58
+ - README.md
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 2.6.4
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: an identicon implements
77
+ test_files: []