Skip to content

Commit

Permalink
Initial checkin
Browse files Browse the repository at this point in the history
  • Loading branch information
willglynn committed Aug 30, 2010
1 parent 9855e2f commit fae4c3f
Show file tree
Hide file tree
Showing 12 changed files with 375 additions and 9 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2009 Will Glynn
Copyright (c) 2010 Will Glynn

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Expand Down
44 changes: 43 additions & 1 deletion README.rdoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,48 @@
= zbar
by Will Glynn

Description goes here.
http://github.com/delta407/ruby-zbar

== DESCRIPTION:

Ruby bindings for ZBar, a barcode recognition library.

== SYNOPSIS:

require 'zbar'

ZBar::Image.from_jpeg(File.read('test.jpg')).process
=> [#<Zbar::Symbol:0x10147c668
@addon="",
@data="9876543210128",
@location= [...],
@quality=15,
@symbology="EAN-13">]

== REQUIREMENTS:

* FFI
* ZBar, probably 0.10 or more

ZBar can be obtained from http://zbar.sourceforge.net.

== DOWNLOAD/INSTALL:

From gemcutter:

[sudo] gem install zbar

From github:

git clone git://github.com/delta407/ruby-zbar.git
cd ruby-zbar
rake install

== LIMITATIONS:

Doesn't expose all ZBar functionality, including:
* No video functions
* No low-level interfaces (scanner, decoder)

== Note on Patches/Pull Requests

Expand Down
8 changes: 4 additions & 4 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ begin
require 'jeweler'
Jeweler::Tasks.new do |gem|
gem.name = "zbar"
gem.summary = %Q{TODO: one-line summary of your gem}
gem.description = %Q{TODO: longer description of your gem}
gem.summary = "Ruby bindings for ZBar, a barcode recognition library"
gem.description ="Ruby bindings for ZBar, a barcode recognition library. Uses FFI to interact with the underlying C library, but has no other dependencies."
gem.email = "will@willglynn.com"
gem.homepage = "http://github.com/delta407/zbar"
gem.homepage = "http://github.com/delta407/ruby-zbar"
gem.authors = ["Will Glynn"]
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
gem.add_dependency "ffi", ">= 0"
end
Jeweler::GemcutterTasks.new
rescue LoadError
Expand Down
6 changes: 6 additions & 0 deletions lib/zbar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require 'ffi'

require 'zbar/lib'
require 'zbar/symbol'
require 'zbar/processor'
require 'zbar/image'
102 changes: 102 additions & 0 deletions lib/zbar/image.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
module ZBar

# Encapsulates a ZBar Image data structure.
class Image
# Instantiates a new Image object, either by creating an empty one,
# or wrapping the supplied pointer.
def initialize(pointer=nil)
@img = FFI::AutoPointer.new(
pointer || ZBar.zbar_image_create,
ZBar.method(:zbar_image_destroy)
)
end

# Instantiates an Image given JPEG data.
#
# This function uses the internal ZBar conversion function to decode the JPEG
# and convert it into a greyscale image suitable for further processing.
# This conversion may fail if ZBar was not built with <tt>--with-jpeg</tt>.
def self.from_jpeg(io_or_string)
if io_or_string.respond_to?(:read)
io_or_string = io_or_string.read
end

jpeg_image = new()
jpeg_image.set_data(ZBar::Format::JPEG, io_or_string)
return jpeg_image.convert(ZBar::Format::Y800)
end

# Instantiates an Image given raw PGM data.
#
# PGM is a NetPBM format, encoding width, height, and greyscale data, one byte
# per pixel. It is therefore ideally suited for loading into ZBar, which
# operates natively on Y800 pixel data--identical to the data section of a PGM
# file.
#
# The data is described in greater detail at
# http://netpbm.sourceforge.net/doc/pgm.html.
def self.from_pgm(io_or_string)
if io_or_string.respond_to?(:read)
io_or_string = io_or_string.read
end

image_data = io_or_string.gsub(/^(P5)\s([0-9]+)\s([0-9]+)\s([0-9]+)\s/, '')
if $1 != 'P5'
raise ArgumentError, "input must be a PGM file"
end

width, height, max_val = $2.to_i, $3.to_i, $4.to_i

if max_val != 255
raise ArgumentError, "maximum value must be 255"
end

image = new()
image.set_data(ZBar::Format::Y800, image_data, width, height)
image
end

# Load arbitrary data from a string into the Image object.
#
# Format must be a ZBar::Format constant. See the ZBar documentation
# for what formats are supported, and how the data should be formatted.
#
# Most everyone should use one of the <tt>Image.from_*</tt> methods instead.
def set_data(format, data, width=nil, height=nil)
ZBar.zbar_image_set_format(@img, format)

# Note the @buffer jog: it's to keep Ruby GC from losing the last
# reference to the old @buffer before calling image_set_data.
new_buffer = FFI::MemoryPointer.from_string(data)
ZBar.zbar_image_set_data(@img, new_buffer, data.size, nil)
@buffer = new_buffer

if width && height
ZBar.zbar_image_set_size(@img, width.to_i, height.to_i)
end
end

# Ask ZBar to convert this image to a new format, returning a new Image.
#
# Not all conversions are possible: for example, if ZBar was built without
# JPEG support, it cannot convert JPEGs into anything else.
def convert(format)
ptr = ZBar.zbar_image_convert(@img, format)
if ptr.null?
raise ArgumentError, "conversion failed"
else
Image.new(ptr)
end
end

# Attempt to recognize barcodes in this image, using the supplied processor
# (if any), falling back to defaults.
#
# Returns an array of ZBar::Symbol objects.
def process(processor = nil)
processor ||= Processor.new
processor.process(self)
end
end

end
44 changes: 44 additions & 0 deletions lib/zbar/lib.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module ZBar
extend FFI::Library
ffi_lib 'zbar'

attach_function :zbar_symbol_get_type, [:pointer], :int
attach_function :zbar_symbol_get_data, [:pointer], :string
attach_function :zbar_symbol_get_type, [:pointer], :int
attach_function :zbar_symbol_get_quality, [:pointer], :int
attach_function :zbar_symbol_get_loc_size, [:pointer], :uint
attach_function :zbar_symbol_get_loc_x, [:pointer, :uint], :int
attach_function :zbar_symbol_get_loc_y, [:pointer, :uint], :int
attach_function :zbar_symbol_next, [:pointer], :pointer

attach_function :zbar_image_create, [], :pointer
attach_function :zbar_image_destroy, [:pointer], :void
attach_function :zbar_image_first_symbol, [:pointer], :pointer
attach_function :zbar_image_set_format, [:pointer, :ulong], :void
attach_function :zbar_image_convert, [:pointer, :ulong], :pointer
attach_function :zbar_image_set_size, [:pointer, :uint, :uint], :void
attach_function :zbar_image_set_data, [:pointer, :buffer_in, :uint, :pointer], :void

attach_function :zbar_processor_create, [:int], :pointer
attach_function :zbar_processor_destroy, [:pointer], :void
attach_function :zbar_processor_init, [:pointer, :string, :int], :int

attach_function :zbar_process_image, [:pointer, :pointer], :int

attach_function :zbar_set_verbosity, [:int], :void
attach_function :zbar_get_symbol_name, [:int], :string
attach_function :zbar_get_addon_name, [:int], :string
attach_function :_zbar_error_spew, [:pointer, :int], :int

module Format #:nodoc:
%w(JPEG Y800 GREY).each { |format|
const_set(format.to_sym, format.unpack('V')[0])
}
end

# Sets the verbosity of the underlying ZBar library, which writes
# directly to stderr.
def self.verbosity=(v)
zbar_set_verbosity(v.to_i)
end
end
37 changes: 37 additions & 0 deletions lib/zbar/processor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module ZBar
class Processor
# Create a new processor.
def initialize(threads = 0)
@processor = FFI::AutoPointer.new(
ZBar.zbar_processor_create(threads),
ZBar.method(:zbar_processor_destroy)
)

if ZBar.zbar_processor_init(@processor, nil, 0) > 0
ZBar._zbar_error_spew(@processor, 0)
raise "error!"
end
end

# Attempt to recognize barcodes in this image. Raises an exception if ZBar
# signals an error, otherwise returns an array of ZBar::Symbol objects.
def process(image)
raise ArgumentError, "process() operates only on ZBar::Image objects" unless image.kind_of?(ZBar::Image)
image = image.instance_variable_get(:@img)

if ZBar.zbar_process_image(@processor, image) != 0
raise ArgumentError, "processing failed"
end

out = []

sym = ZBar.zbar_image_first_symbol(image)
until sym.null?
out << Symbol.new(sym)
sym = ZBar.zbar_symbol_next(sym)
end

out
end
end
end
37 changes: 37 additions & 0 deletions lib/zbar/symbol.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module ZBar

class Symbol
attr_reader :symbology, :data, :addon, :quality, :location

def initialize(symbol=nil)
if symbol
type = ZBar.zbar_symbol_get_type(symbol)
@symbology = ZBar.zbar_get_symbol_name(type)
@data = ZBar.zbar_symbol_get_data(symbol)
@addon = ZBar.zbar_get_addon_name(type)
@quality = ZBar.zbar_symbol_get_quality(symbol)

@location = []
point_count = ZBar.zbar_symbol_get_loc_size(symbol)
i = 0
while i < point_count
@location << [ZBar.zbar_symbol_get_loc_x(symbol, i), ZBar.zbar_symbol_get_loc_y(symbol, i)]
i += 1
end
end
end

def ==(symbol)
return false unless symbol.kind_of?(Symbol)

(
self.symbology == symbol.symbology &&
self.data == symbol.data &&
self.addon == symbol.addon &&
self.quality == symbol.quality &&
self.location == symbol.location
)
end
end

end
Binary file added test/test.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/test.pgm
Binary file not shown.
41 changes: 38 additions & 3 deletions test/test_zbar.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
require 'helper'

class TestZbar < Test::Unit::TestCase
should "probably rename this file and start testing for real" do
flunk "hey buddy, you should probably rename this file and start testing for real"
Path = File.dirname(__FILE__)

class TestZBar < Test::Unit::TestCase
should "read the right barcode from a PGM blob" do
result = ZBar::Image.from_pgm(File.read("#{Path}/test.pgm")).process
assert_equal(result.size, 1)
assert_equal(result[0].data, '9876543210128')
assert_equal(result[0].symbology, 'EAN-13')
end

should "read a barcode from a PGM file" do
File.open("#{Path}/test.pgm") { |f|
result = ZBar::Image.from_pgm(f).process
assert_equal(result.size, 1)
}
end

should "be able to re-use a processor" do
processor = ZBar::Processor.new
pgm = File.read("#{Path}/test.pgm")

result1 = processor.process ZBar::Image.from_pgm(pgm)
result2 = processor.process ZBar::Image.from_pgm(pgm)
assert_equal(result1.size, 1)
assert_equal(result2.size, 1)
assert_equal(result1, result2)
end

should "read a barcode from a JPEG blob" do
result = ZBar::Image.from_jpeg(File.read("#{Path}/test.jpg")).process
assert_equal(result.size, 1)
end

should "read a barcode from a JPEG file" do
File.open("#{Path}/test.jpg") { |f|
result = ZBar::Image.from_jpeg(f).process
assert_equal(result.size, 1)
}
end
end
Loading

0 comments on commit fae4c3f

Please sign in to comment.