snowy 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []