Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

first commit

  • Loading branch information...
commit bfabe1293420eb57631809a31f204350ae8350be 0 parents
@swdyh authored
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 swdyh
+
+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.
59 README
@@ -0,0 +1,59 @@
+
+= quilt
+
+
+== Description
+a library for generating identicon.
+
+
+Identicon: http://en.wikipedia.org/wiki/Identicon
+
+== Installation
+Required RMagick or ruby-gd.
+
+=== Archive Installation
+
+ rake install
+
+=== Gem Installation
+
+ gem install quilt
+
+
+== Features/Problems
+* Output file type is PNG only.
+
+== Synopsis
+
+ # input: any string
+ # output: 15 * 15 png (default)
+ identicon = Quilt::Identicon.new 'sample'
+ identicon.write 'sample15_15.png'
+
+ # input: identicon code(32 bit integer)
+ # output: 15 * 15 png (default)
+ identicon = Quilt::Identicon.new 1, :type => :code
+ identicon.write 'sample15_15_code.png'
+
+ # input: ip address
+ identicon = Quilt::Identicon.new '100.100.100.100', :type => :ip
+ identicon.write 'sample15_15_ip.png'
+
+ # output: 150 * 150 png
+ identicon = Quilt::Identicon.new 'sample', :scale => 10
+ identicon.write 'sample150_150.png'
+
+ # output: blob
+ identicon = Quilt::Identicon.new 'sample'
+ print identicon.to_blob
+
+ # change image library to Rmagick to GD
+ Quilt::Identicon.image_lib = Quilt::ImageGD
+ identicon = Quilt::Identicon.new 'sample'
+ identicon.write 'sample15_15_gd.png'
+
+== Copyright
+
+Author:: swdyh <youhei@gmail.com>
+Copyright:: Copyright (c) 2008 swdyh
+License:: The MIT License
131 Rakefile
@@ -0,0 +1,131 @@
+require 'rubygems'
+require 'rake'
+require 'rake/clean'
+require 'rake/testtask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+require 'rake/rdoctask'
+require 'rake/contrib/rubyforgepublisher'
+require 'rake/contrib/sshpublisher'
+require 'fileutils'
+include FileUtils
+
+NAME = "quilt"
+AUTHOR = "swdyh"
+EMAIL = "youhei@gmail.com"
+DESCRIPTION = "a library for generating identicon."
+RUBYFORGE_PROJECT = "quilt"
+HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
+BIN_FILES = %w( )
+VERS = "0.0.1"
+
+REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
+CLEAN.include ['**/.*.sw?', '*.gem', '.config']
+RDOC_OPTS = [
+ '--title', "#{NAME} documentation",
+ "--charset", "utf-8",
+ "--opname", "index.html",
+ "--line-numbers",
+ "--main", "README",
+ "--inline-source",
+]
+
+task :default => [:test]
+task :package => [:clean]
+
+Rake::TestTask.new("test") do |t|
+ t.libs << "test"
+ t.pattern = "test/**/*_test.rb"
+ t.verbose = true
+end
+
+spec = Gem::Specification.new do |s|
+ s.name = NAME
+ s.version = VERS
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README", "ChangeLog", "MIT-LICENSE"]
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
+ s.summary = DESCRIPTION
+ s.description = DESCRIPTION
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPATH
+ s.executables = BIN_FILES
+ s.rubyforge_project = RUBYFORGE_PROJECT
+ s.bindir = "bin"
+ s.require_path = "lib"
+# s.autorequire = ""
+ s.test_files = Dir["test/test_*.rb"]
+
+ #s.add_dependency('activesupport', '>=1.3.1')
+ #s.required_ruby_version = '>= 1.8.2'
+
+ s.files = %w(README ChangeLog Rakefile) +
+ Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
+ Dir.glob("ext/**/*.{h,c,rb}") +
+ Dir.glob("examples/**/*.rb") +
+ Dir.glob("tools/*.rb")
+
+ s.extensions = FileList["ext/**/extconf.rb"].to_a
+end
+
+Rake::GemPackageTask.new(spec) do |p|
+ p.need_tar = true
+ p.gem_spec = spec
+end
+
+task :install do
+ name = "#{NAME}-#{VERS}.gem"
+ sh %{rake package}
+ sh %{sudo gem install pkg/#{name}}
+end
+
+task :uninstall => [:clean] do
+ sh %{sudo gem uninstall #{NAME}}
+end
+
+
+Rake::RDocTask.new do |rdoc|
+ rdoc.rdoc_dir = 'html'
+ rdoc.options += RDOC_OPTS
+ rdoc.template = "resh"
+ #rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+ if ENV['DOC_FILES']
+ rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
+ else
+ rdoc.rdoc_files.include('README', 'ChangeLog')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+ rdoc.rdoc_files.include('ext/**/*.c')
+ end
+end
+
+desc "Publish to RubyForge"
+task :rubyforge => [:rdoc, :package] do
+ require 'rubyforge'
+ Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, 'swdyh').upload
+end
+
+desc 'Package and upload the release to rubyforge.'
+task :release => [:clean, :package] do |t|
+ v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
+ abort "Versions don't match #{v} vs #{VERS}" unless v == VERS
+ pkg = "pkg/#{NAME}-#{VERS}"
+
+ rf = RubyForge.new
+ puts "Logging in"
+ rf.login
+
+ c = rf.userconfig
+# c["release_notes"] = description if description
+# c["release_changes"] = changes if changes
+ c["preformatted"] = true
+
+ files = [
+ "#{pkg}.tgz",
+ "#{pkg}.gem"
+ ].compact
+
+ puts "Releasing #{NAME} v. #{VERS}"
+ rf.add_release RUBYFORGE_PROJECT, NAME, VERS, *files
+end
243 lib/quilt.rb
@@ -0,0 +1,243 @@
+require 'rubygems'
+require 'digest/sha1'
+
+module Quilt
+ class ImageRmagick
+ def initialize width, height
+ require 'RMagick'
+ @image = Magick::Image.new width, height
+ @image.format = 'png'
+ end
+
+ def color r, g, b
+ sprintf('#%02x%02x%02x', r, g, b)
+ end
+
+ def transparent color
+ @image.transparent color
+ end
+
+ def fill_rect x, y, _x, _y, color
+ dr = Magick::Draw.new
+ dr.fill color
+ dr.rectangle x, y, _x, _y
+ dr.draw @image
+ end
+
+ def polygon points, color
+ unless points.empty?
+ dr = Magick::Draw.new
+ dr.fill color
+ dr.polygon *(points.flatten)
+ dr.draw @image
+ end
+ end
+
+ def write path
+ # @image.write path
+ open(path, 'w') {|f| f.puts @image.to_blob }
+ end
+
+ def to_blob
+ @image.to_blob
+ end
+ end
+
+ class ImageGD
+ def initialize width, height
+ require 'GD'
+ @image = GD::Image.new width, height
+ end
+
+ def color r, g, b
+ @image.colorAllocate r, g, b
+ end
+
+ def transparent color
+ @image.transparent color
+ end
+
+ def fill_rect x, y, _x, _y, color
+ @image.filledRectangle x, y, _x, _y, color
+ end
+
+ def polygon points, color
+ poly = GD::Polygon.new
+ points.each do |i|
+ poly.addPt i[0], i[1]
+ end
+ @image.filledPolygon poly, color
+ end
+
+ def write path
+ File.open(path, 'w') do |f|
+ @image.png f
+ end
+ end
+
+ def to_blob
+ @image.pngStr
+ end
+ end
+
+ class Identicon
+ PATCHES = [
+ [0, 4, 24, 20, 0],
+ [0, 4, 20, 0],
+ [2, 24, 20, 2],
+ [0, 2, 22, 20, 0],
+ [2, 14, 22, 10, 2],
+ [0, 14, 24, 22, 0],
+ [2, 24, 22, 13, 11, 22, 20, 2],
+ [0, 14, 22, 0],
+ [6, 8, 18, 16, 6],
+ [4, 20, 10, 12, 2, 4],
+ [0, 2, 12, 10, 0],
+ [10, 14, 22, 10],
+ [20, 12, 24, 20],
+ [10, 2, 12, 10],
+ [0, 2, 10, 0],
+ [],
+ ]
+ CENTER_PATCHES = [0, 4, 8, 15]
+ PATCH_SIZE = 5
+ @@image_lib = ImageRmagick
+ @@salt = ''
+ attr_reader :code
+
+ def initialize str = '', opt = {}
+ case opt[:type]
+ when :code
+ @code = str.to_i
+ when :ip
+ @code = Identicon.ip2code str
+ else
+ @code = Identicon.calc_code str.to_s
+ end
+ @decode = decode @code
+ @scale = opt[:scale] || 1
+ @patch_width = (PATCH_SIZE - 1) * @scale + 1
+ @image = @@image_lib.new @patch_width * 3, @patch_width * 3
+ @back_color = @image.color 255, 255, 255
+ @fore_color = @image.color @decode[:red], @decode[:green], @decode[:blue]
+ @image.transparent @back_color
+ render
+ end
+
+ def decode code
+ {
+ :center_type => (code & 0x3),
+ :center_invert => (((code >> 2) & 0x01) != 0),
+ :corner_type => ((code >> 3) & 0x0f),
+ :corner_invert => (((code >> 7) & 0x01) != 0),
+ :corner_turn => ((code >> 8) & 0x03),
+ :side_type => ((code >> 10) & 0x0f),
+ :side_invert => (((code >> 14) & 0x01) != 0),
+ :side_turn => ((code >> 15) & 0x03),
+ :red => (((code >> 16) & 0x01f) << 3),
+ :green => (((code >> 21) & 0x01f) << 3),
+ :blue => (((code >> 27) & 0x01f) << 3),
+ }
+ end
+
+ def render
+ center = [[1, 1]]
+ side = [[1, 0], [2, 1], [1, 2], [0, 1]]
+ corner = [[0, 0], [2, 0], [2, 2], [0, 2]]
+
+ draw_patches(center, CENTER_PATCHES[@decode[:center_type]],
+ 0, @decode[:center_invert])
+ draw_patches(side, @decode[:side_type],
+ @decode[:side_turn], @decode[:side_invert])
+ draw_patches(corner, @decode[:corner_type],
+ @decode[:corner_turn], @decode[:corner_invert])
+ end
+
+ def draw_patches list, patch, turn, invert
+ list.each do |i|
+ draw(:x => i[0], :y => i[1], :patch => patch,
+ :turn => turn, :invert => invert)
+ turn += 1
+ end
+ end
+
+ def draw opt = {}
+ x = opt[:x] * @patch_width
+ y = opt[:y] * @patch_width
+ patch = opt[:patch] % PATCHES.size
+ turn = opt[:turn] % 4
+
+ if opt[:invert]
+ fore, back = @back_color, @fore_color
+ else
+ fore, back = @fore_color, @back_color
+ end
+ @image.fill_rect(x, y, x + @patch_width, y + @patch_width, back)
+
+ points = []
+ PATCHES[patch].each do |pt|
+ dx = pt % PATCH_SIZE
+ dy = pt / PATCH_SIZE
+ px = dx.to_f / (PATCH_SIZE - 1) * @patch_width
+ py = dy.to_f / (PATCH_SIZE - 1) * @patch_width
+
+ case turn
+ when 1
+ px, py = @patch_width - py, px
+ when 2
+ px, py = @patch_width - px, @patch_width - py
+ when 3
+ px, py = py, @patch_width - px
+ end
+ points << [x + px, y + py]
+ end
+ @image.polygon points, fore
+ end
+
+ def write path = "#{@code}.png"
+ @image.write path
+ end
+
+ def to_blob
+ @image.to_blob
+ end
+
+ def self.calc_code str
+ extract_code Identicon.digest(str)
+ end
+
+ def self.ip2code ip
+ code_ip = extract_code(ip.split('.'))
+ extract_code Identicon.digest(code_ip.to_s)
+ end
+
+ def self.digest str
+ Digest::SHA1.digest(str + @@salt)
+ end
+
+ def self.extract_code list
+ tmp = [list[0].to_i << 24, list[1].to_i << 16,
+ list[2].to_i << 8, list[3].to_i]
+ tmp.inject(0) do |r, i|
+ r | ((i[31] == 1) ? -(i & 0x7fffffff) : i)
+ end
+ end
+
+ def self.image_lib= image_lib
+ @@image_lib = image_lib
+ end
+
+ def self.image_lib
+ @@image_lib
+ end
+
+ def self.salt= salt
+ @@salt = salt
+ end
+
+ def self.salt
+ @@salt
+ end
+ end
+end
+
73 test/quilt_test.rb
@@ -0,0 +1,73 @@
+require File.dirname(__FILE__) + '/test_helper.rb'
+require 'test/unit'
+require 'digest/md5'
+require 'fileutils'
+
+class QultTest < Test::Unit::TestCase
+ def setup
+ @tmp_dir = File.join 'test', 'tmp'
+ unless File.exist? @tmp_dir
+ Dir.mkdir @tmp_dir
+ end
+ end
+
+ def teardown
+ if File.exist? @tmp_dir
+ FileUtils.rm_r @tmp_dir
+ end
+ end
+
+ def test_identicon
+ identicon = Quilt::Identicon.new
+ assert_instance_of Quilt::Identicon, identicon
+ path = File.join 'test', 'tmp', 'test'
+ identicon.write path
+ assert File.exist?(path)
+ File.unlink path
+ end
+
+ def test_to_blob
+ identicon = Quilt::Identicon.new
+ path_b = File.join 'test', 'tmp', 'test_to_blob.png'
+ path_w = File.join 'test', 'tmp', 'test_write.png'
+
+ open(path_b, 'w') {|f| f.write identicon.to_blob }
+ identicon.write path_w
+
+ digest = Proc.new {|path| Digest::MD5.hexdigest(IO.read(path)) }
+ assert_equal digest.call(path_w), digest.call(path_w)
+
+ File.unlink path_b, path_w
+ end
+
+ def test_digest
+ digest = Quilt::Identicon.digest('foo')
+ assert_not_nil digest
+ Quilt::Identicon.salt = 'foo'
+ assert_not_equal digest, Quilt::Identicon.digest('foo')
+ end
+
+ def test_image_lib
+ image_other = Class.new do
+ def initialize a, b; end
+ def method_missing *arg; end
+ def write path; open(path, 'w') {|f| f.puts 'other' }; end
+ end
+
+ libs = [Quilt::ImageGD, Quilt::ImageRmagick, image_other]
+ libs.each do |lib|
+ Quilt::Identicon.image_lib = lib
+ assert_equal lib, Quilt::Identicon.image_lib
+ identicon = Quilt::Identicon.new
+ assert_equal lib, identicon.instance_variable_get(:@image).class
+ end
+ end
+
+ def test_salt
+ salts = ['foo', 'bar', 'baz']
+ salts.each do |salt|
+ Quilt::Identicon.salt = salt
+ assert_equal salt, Quilt::Identicon.salt
+ end
+ end
+end
3  test/test_helper.rb
@@ -0,0 +1,3 @@
+require 'test/unit'
+require File.dirname(__FILE__) + '/../lib/quilt'
+
Please sign in to comment.
Something went wrong with that request. Please try again.