Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Requires zxing compiled library. A forthcoming homebrew formula is on
its way.
  • Loading branch information
Dave Lyon and Joshua Davey authored and hashrocketeer committed Jun 9, 2011
0 parents commit 7ae176a
Show file tree
Hide file tree
Showing 21 changed files with 372 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.rvmrc
Gemfile.lock
mkmf.log
*.o
tmp/
*.bundle
Makefile
2 changes: 2 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--format documentation
--color
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
source :rubygems
gemspec
1 change: 1 addition & 0 deletions README.rdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

10 changes: 10 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require 'rake/extensiontask'
require "rspec/core/rake_task"

Rake::ExtensionTask.new('qrdecoder_ext') do |ext|
ext.lib_dir = 'lib/qrdecoder'
end

RSpec::Core::RakeTask.new

task :default => [:clean, :compile, :spec]
99 changes: 99 additions & 0 deletions ext/qrdecoder_ext/MagickBitmapSource.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* MagickBitmapSource.cpp
* zxing
*
* Copyright 2010 ZXing authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "MagickBitmapSource.h"

#include <iostream>

using namespace Magick;

namespace zxing {

MagickBitmapSource::MagickBitmapSource(Image& image) : image_(image) {
width = (int)image.columns();
height = (int)image.rows();

pixel_cache = image.getConstPixels(0, 0, width, height);
}

MagickBitmapSource::~MagickBitmapSource() {

}

int MagickBitmapSource::getWidth() const {
return width;
}

int MagickBitmapSource::getHeight() const {
return height;
}

unsigned char* MagickBitmapSource::getRow(int y, unsigned char* row) {
int width = getWidth();
if (row == NULL) {
row = new unsigned char[width];
}
for (int x = 0; x < width; x++) {
const PixelPacket* p = pixel_cache + y * width + x;
// We assume 16 bit values here
// 0x200 = 1<<9, half an lsb of the result to force rounding
row[x] = (unsigned char)((306 * ((int)p->red >> 8) + 601 * ((int)p->green >> 8) +
117 * ((int)p->blue >> 8) + 0x200) >> 10);
}
return row;

}

/** This is a more efficient implementation. */
unsigned char* MagickBitmapSource::getMatrix() {
int width = getWidth();
int height = getHeight();
unsigned char* matrix = new unsigned char[width*height];
unsigned char* m = matrix;
const Magick::PixelPacket* p = pixel_cache;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
*m = (unsigned char)((306 * ((int)p->red >> 8) + 601 * ((int)p->green >> 8) +
117 * ((int)p->blue >> 8) + 0x200) >> 10);
m++;
p++;
}
}
return matrix;
}

bool MagickBitmapSource::isRotateSupported() const {
return false;
}

Ref<LuminanceSource> MagickBitmapSource::rotateCounterClockwise() {
//TODO(flyashi): add rotated image support.
/* this segfaults. I tried a few things, none seemed to work. Perhaps the problem is elsewhere? */
/*
Magick::Image rotated(image_);
rotated.modifyImage();
rotated.rotate(90); // Image::rotate takes CCW degrees as an argument
rotated.syncPixels();
return Ref<MagickBitmapSource> (new MagickBitmapSource(rotated));
*/
return Ref<MagickBitmapSource> (NULL);
}

}

49 changes: 49 additions & 0 deletions ext/qrdecoder_ext/MagickBitmapSource.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#ifndef __MAGICK_BITMAP_SOURCE_H_
#define __MAGICK_BITMAP_SOURCE_H_
/*
* MagickBitmapSource.h
* zxing
*
* Copyright 2010 ZXing authors All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <Magick++.h>
#include <zxing/LuminanceSource.h>

namespace zxing {

class MagickBitmapSource : public LuminanceSource {
private:
Magick::Image& image_;
int width;
int height;
const Magick::PixelPacket* pixel_cache;

public:
MagickBitmapSource(Magick::Image& image);

~MagickBitmapSource();

int getWidth() const;
int getHeight() const;
unsigned char* getRow(int y, unsigned char* row);
unsigned char* getMatrix();
bool isRotateSupported() const;
Ref<LuminanceSource> rotateCounterClockwise();
};

}

#endif /* MAGICKMONOCHROMEBITMAPSOURCE_H_ */
42 changes: 42 additions & 0 deletions ext/qrdecoder_ext/extconf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require "mkmf"
require "date"

def exit_failure(msg)
Logging::message msg
message msg+"\n"
exit(1)
end

# C++ flags
cppflags = ENV["CPPFLAGS"] || $CPPFLAGS
$CPPFLAGS = cppflags.to_s + " " + `Magick-config --cppflags`.chomp

headers = %w{stdio.h stdlib.h math.h time.h}
headers << "stdint.h" if have_header("stdint.h")
headers << "sys/types.h" if have_header("sys/types.h")


if have_header("wand/MagickWand.h")
headers << "wand/MagickWand.h"
else
exit_failure "\nCan't install. Can't find MagickWand.h."
end


unless have_library("MagickCore", "InitializeMagick", headers) && have_library("Magick++","InitializeMagick",headers)
exit_failure "Can't find the ImageMagick library or one of the dependent libraries. " +
"Check the mkmf.log file for more detailed information.\n"
end

have_library("jpeg")
have_library("zxing")

have_func("snprintf", headers)

have_struct_member("Image", "type", headers)
have_type("MagickFunction", headers)
have_type("ImageLayerMethod", headers)

$defs = []

create_makefile("qrdecoder/qrdecoder_ext")
64 changes: 64 additions & 0 deletions ext/qrdecoder_ext/qrdecoder_ext.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include "ruby.h"

#include <iostream>
#include <fstream>
#include <string>
#include <Magick++.h>
#include "MagickBitmapSource.h"
#include <zxing/common/Counted.h>
#include <zxing/qrcode/QRCodeReader.h>
#include <zxing/Binarizer.h>
#include <zxing/MultiFormatReader.h>
#include <zxing/Result.h>
#include <zxing/Reader.h>
#include <zxing/ReaderException.h>
#include <zxing/common/GlobalHistogramBinarizer.h>
#include <zxing/common/HybridBinarizer.h>
#include <exception>
#include <zxing/Exception.h>
#include <zxing/common/IllegalArgumentException.h>
#include <zxing/BinaryBitmap.h>
#include <zxing/DecodeHints.h>
using namespace Magick;
using namespace std;
using namespace zxing;
using namespace zxing::qrcode;


VALUE mQRDecoder;

extern "C"
VALUE _decode(VALUE klass, VALUE path) {
VALUE r_str;
Image image;
char *filepath = StringValueCStr(path);
try {
image.read(filepath);
} catch (...) {
rb_raise(rb_eIOError, "unable to read file");
cerr << "Unable to open image, ignoring" << filepath << endl;
}
try {
Ref<MagickBitmapSource> source(new MagickBitmapSource(image));

Ref<Binarizer> binarizer(NULL);
binarizer = new GlobalHistogramBinarizer(source);

Ref<BinaryBitmap> image(new BinaryBitmap(binarizer));
Ref<Reader> reader(new MultiFormatReader);
Ref<Result> result(reader->decode(image));
const char *str = result->getText()->getText().c_str();
r_str = rb_str_new2( str );
} catch (zxing::Exception& e) {
r_str = Qnil;
}

return r_str;
}

extern "C"
void Init_qrdecoder_ext()
{
mQRDecoder = rb_define_module("QRDecoder");
rb_define_module_function(mQRDecoder, "decode", (VALUE(*)(...))_decode, 1);
}
5 changes: 5 additions & 0 deletions lib/qrdecoder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require 'qrdecoder/version'
require 'qrdecoder/qrdecoder_ext'

module QRDecoder
end
3 changes: 3 additions & 0 deletions lib/qrdecoder/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module QRDecoder
VERSION = '0.0.1'
end
29 changes: 29 additions & 0 deletions qrdecoder.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
lib = File.expand_path('../lib/', __FILE__)
$:.unshift lib unless $:.include?(lib)

require 'qrdecoder/version'

Gem::Specification.new do |s|
s.version = QRDecoder::VERSION
s.platform = Gem::Platform::RUBY

s.name = "qrdecoder"
s.summary = %Q{Wrapper around the C++ zxing library for decoding QR Codes}
s.description = %Q{This Gem is a wrapper around an useful open-source library for decoding QR
Codes, a two-dimensional bar code format popular in Japan created by the Denso-Wave Corporation in 1994.}
s.email = ['josh@joshuadavey.com', 'dave@davelyon.net']
s.authors = ['Joshua Davey', 'Dave Lyon']
s.date = '2011-06-08'

s.extensions = ["ext/qrdecoder_ext/extconf.rb"]

s.extra_rdoc_files = ["README.rdoc", "ext/qrdecoder_ext/qrdecoder_ext.c"]
s.files = Dir.glob("{bin,lib,ext}/**/*") + %w(History.txt README.rdoc) - ["lib/qrdecoder/qrdecoder_ext.bundle"]

s.require_path = 'lib'
s.required_rubygems_version = ">= 1.3.6"
s.add_development_dependency "rspec", "~> 2.6.0"
s.add_development_dependency "rake-compiler", "~> 0.7.5"

s.test_files = Dir.glob("spec/**/*_spec.rb") + %w{spec/spec_helper.rb}
end
Binary file added spec/data/bacon.png
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 spec/data/big-url.png
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 spec/data/colors.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 spec/data/image.png
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 spec/data/logo.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 spec/data/url.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions spec/qrdecoder_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require 'spec_helper'

describe QRDecoder do
describe ".decode" do
subject { QRDecoder.decode(path) }
context "with a path to a qrcode image" do
context "with an encoded phrase" do
let(:path) { qrcode_fixture("bacon.png") }
it { should be_kind_of String }
it { should == "Chunky Bacon!"}
end
context "with an encoded url" do
let(:path) { qrcode_fixture("url.png") }
it { should == "http://davelyon.net"}
end
context "with a large qrcode" do
let(:path) { qrcode_fixture("big-url.png") }
it { should == "http://joshuadavey.com"}
end
end
context "with a non-qrcode image" do
let(:path) { qrcode_fixture("image.png") }
it { should == nil }
end
context "with a 'logoized' qrcode image" do
let(:path) { qrcode_fixture("logo.jpg") }
it { subject.strip.should == "http://www.celebrate-originality.jp/" }
end
context "with a 'colorized' qrcode image" do
let(:path) { qrcode_fixture("colors.jpg") }
it { subject.strip.should == "http://www.havas.com" }
end
context "with no arguments" do
it "raises an error" do
expect {
QRDecoder.decode
}.to raise_error(ArgumentError)
end
end
context "with an invalid file type" do
it "raises an error" do
expect {
QRDecoder.decode(__FILE__)
}.to raise_error(IOError)
end
end
end
end
8 changes: 8 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require 'rubygems'
require 'bundler/setup'
require 'rspec'

root = File.expand_path('../..', __FILE__)
Dir["#{root}/spec/support/**/*.rb"].each {|f| require f}

Bundler.require
3 changes: 3 additions & 0 deletions spec/support/fixtures.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def qrcode_fixture(name)
File.expand_path("../data/#{name}", File.dirname(__FILE__))
end

0 comments on commit 7ae176a

Please sign in to comment.