├── .gitignore ├── test ├── img │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ ├── 9.jpg │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ └── .DS_Store └── test.js ├── puzzle.js ├── binding.gyp ├── package.json ├── README.md └── puzzle.cc /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | node_modules 4 | -------------------------------------------------------------------------------- /test/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/1.jpg -------------------------------------------------------------------------------- /test/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/2.jpg -------------------------------------------------------------------------------- /test/img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/3.jpg -------------------------------------------------------------------------------- /test/img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/4.jpg -------------------------------------------------------------------------------- /test/img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/5.jpg -------------------------------------------------------------------------------- /test/img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/6.jpg -------------------------------------------------------------------------------- /test/img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/7.jpg -------------------------------------------------------------------------------- /test/img/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/8.jpg -------------------------------------------------------------------------------- /test/img/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/9.jpg -------------------------------------------------------------------------------- /test/img/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/10.jpg -------------------------------------------------------------------------------- /test/img/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/11.jpg -------------------------------------------------------------------------------- /test/img/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/12.jpg -------------------------------------------------------------------------------- /test/img/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/13.jpg -------------------------------------------------------------------------------- /test/img/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/14.jpg -------------------------------------------------------------------------------- /test/img/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/15.jpg -------------------------------------------------------------------------------- /test/img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reklis/node-puzzle/HEAD/test/img/.DS_Store -------------------------------------------------------------------------------- /puzzle.js: -------------------------------------------------------------------------------- 1 | var libpuzzle = require('./build/Release/puzzle'); 2 | 3 | exports.context = function () { 4 | return new libpuzzle.PuzzleContextWrapper(); 5 | }; -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "puzzle", 5 | "sources": [ "puzzle.cc" ], 6 | "link_settings": { 7 | "libraries": [ 8 | "/opt/local/lib/libgd.dylib", 9 | "/opt/local/lib/libpuzzle.dylib" 10 | ], 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); // yeah yeah, I know I should be using a framework... 2 | var puzzle = require('../puzzle'); 3 | 4 | var context = puzzle.context(); 5 | var distance = context.compare('./test/img/1.jpg', './test/img/2.jpg'); 6 | 7 | console.log(distance); 8 | 9 | assert.ok(0 < distance); 10 | assert.ok(1 > distance); 11 | 12 | var didthrow = false; 13 | 14 | try { 15 | var shouldthrow = context.compare(); 16 | } catch (err) { 17 | assert.ok(null !== err); 18 | didthrow = true; 19 | } 20 | 21 | assert.ok(didthrow); 22 | 23 | console.log('OK'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puzzle", 3 | "version": "0.0.1", 4 | "author": "Steven Fusco ", 5 | "description": "c++ wrapper for libpuzzle", 6 | "contributors": [ 7 | { 8 | "name": "Steven Fusco (node module)", 9 | "email": "github@stevenfusco.com" 10 | }, 11 | { 12 | "name" : "Frank Denis (original author of libpuzzle)" 13 | } 14 | ], 15 | "scripts": { 16 | "test": "node test/test.js" 17 | }, 18 | "main": "./puzzle.js", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/reklis/node-puzzle.git" 22 | }, 23 | "keywords": [ 24 | "image", 25 | "comparison", 26 | "libpuzzle", 27 | "libgd" 28 | ], 29 | "license": "MIT", 30 | "engines": { 31 | "node": ">=0.6" 32 | } 33 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | System Dependencies 2 | ------------------- 3 | 4 | - [libpuzzle](http://www.pureftpd.org/project/libpuzzle) 5 | - [libgd](http://www.boutell.com/gd/) 6 | 7 | Installation 8 | ------------ 9 | 10 | $ npm install puzzle 11 | 12 | then... 13 | 14 | var puzzle = require('puzzle'); 15 | var context = puzzle.context(); // create a context 16 | var distance = context.compare('./test/img/1.jpg', './test/img/2.jpg'); // compare two images 17 | 18 | `distance` will be 0 if the images are equal, the larger the number, the more "different" the images are. 19 | Generally, a number below .6 means they are considered similar. See the official libpuzzle docs for details. 20 | 21 | Build and Test 22 | -------------- 23 | 24 | node-gyp clean configure build && node test/test.js 25 | 26 | More 27 | ---- 28 | 29 | This is very raw. It exposes exactly one method, there is a lot more to do. Contributions welcome. 30 | 31 | --- 32 | 33 | [![gittip](http://img.shields.io/gittip/reklis.svg)](https://www.gittip.com/reklis/) 34 | -------------------------------------------------------------------------------- /puzzle.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" { 4 | #include 5 | } 6 | 7 | #include 8 | #include 9 | using namespace v8; 10 | 11 | #include 12 | using namespace std; 13 | 14 | class PuzzleContextWrapper : public node::ObjectWrap { 15 | public: 16 | static void Init(v8::Handle target); 17 | 18 | private: 19 | PuzzleContextWrapper(); 20 | ~PuzzleContextWrapper(); 21 | 22 | static v8::Handle New(const v8::Arguments& args); 23 | static v8::Handle Compare(const v8::Arguments& args); 24 | 25 | PuzzleContext context; 26 | }; 27 | 28 | PuzzleContextWrapper::PuzzleContextWrapper() { 29 | puzzle_init_context(&context); 30 | 31 | // TODO: 32 | // puzzle_set_contrast_barrier_for_cropping(context, atof(optarg)); 33 | // puzzle_set_autocrop(context, 0); 34 | // puzzle_set_max_cropping_ratio(context, atof(optarg)); 35 | // puzzle_set_max_height(context, strtoul(optarg, NULL, 10)); 36 | // puzzle_set_lambdas(context, strtoul(optarg, NULL, 10)); 37 | // puzzle_set_noise_cutoff(context, atof(optarg)); 38 | // puzzle_set_p_ratio(context, atof(optarg)); 39 | // puzzle_set_max_width(context, strtoul(optarg, NULL, 10)); 40 | }; 41 | 42 | PuzzleContextWrapper::~PuzzleContextWrapper() { 43 | puzzle_free_context(&context); 44 | }; 45 | 46 | void PuzzleContextWrapper::Init(Handle target) { 47 | Local tpl = FunctionTemplate::New(New); 48 | tpl->SetClassName(String::NewSymbol("PuzzleContextWrapper")); 49 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 50 | tpl->PrototypeTemplate()->Set(String::NewSymbol("compare"), FunctionTemplate::New(Compare)->GetFunction()); 51 | 52 | Persistent constructor = Persistent::New(tpl->GetFunction()); 53 | target->Set(String::NewSymbol("PuzzleContextWrapper"), constructor); 54 | } 55 | 56 | Handle PuzzleContextWrapper::New(const Arguments& args) { 57 | HandleScope scope; 58 | 59 | PuzzleContextWrapper* obj = new PuzzleContextWrapper(); 60 | obj->Wrap(args.This()); 61 | 62 | return args.This(); 63 | } 64 | 65 | Handle PuzzleContextWrapper::Compare(const Arguments& args) { 66 | HandleScope scope; 67 | 68 | PuzzleContextWrapper* obj = ObjectWrap::Unwrap(args.This()); 69 | PuzzleContext context = obj->context; 70 | 71 | PuzzleCvec cvec1, cvec2; 72 | puzzle_init_cvec(&context, &cvec1); 73 | puzzle_init_cvec(&context, &cvec2); 74 | 75 | if (args.Length() < 2) { 76 | ThrowException(Exception::TypeError(String::New("Wrong number of arguments"))); 77 | return scope.Close(Undefined()); 78 | } 79 | 80 | if ((args.Length() > 2) && !args[3]->IsBoolean()) { 81 | ThrowException(Exception::TypeError(String::New("fix_for_texts parameter must be true or false"))); 82 | return scope.Close(Undefined()); 83 | } 84 | 85 | if (!args[0]->IsString()) { 86 | ThrowException(Exception::TypeError(String::New("file1 must be a string path to a file"))); 87 | return scope.Close(Undefined()); 88 | } 89 | 90 | if (!args[1]->IsString()) { 91 | ThrowException(Exception::TypeError(String::New("file2 must be a string path to a file"))); 92 | return scope.Close(Undefined()); 93 | } 94 | 95 | Local file1 = args[0]->ToString(); 96 | Local file2 = args[1]->ToString(); 97 | bool fix_for_texts = args[2]->IsUndefined() ? true : args[2]->BooleanValue(); // default to ON 98 | 99 | String::AsciiValue f1_ascii(file1); 100 | String::AsciiValue f2_ascii(file2); 101 | const int S_OK = 0; 102 | double distance = -1; 103 | 104 | int vec1_result = puzzle_fill_cvec_from_file(&context, &cvec1, *f1_ascii); 105 | if (S_OK != vec1_result) { 106 | ThrowException(Exception::TypeError(String::New("error loading file 1"))); 107 | } else { 108 | int vec2_result = puzzle_fill_cvec_from_file(&context, &cvec2, *f2_ascii); 109 | if (S_OK != vec2_result) { 110 | ThrowException(Exception::TypeError(String::New("error loading file 2"))); 111 | } else { 112 | distance = puzzle_vector_normalized_distance(&context, &cvec1, &cvec2, fix_for_texts ? 1 : 0); 113 | puzzle_free_cvec(&context, &cvec2); 114 | } 115 | puzzle_free_cvec(&context, &cvec1); 116 | } 117 | 118 | return scope.Close(Number::New(distance)); 119 | } 120 | 121 | void InitAll(Handle target) { 122 | PuzzleContextWrapper::Init(target); 123 | } 124 | 125 | NODE_MODULE(puzzle, InitAll) 126 | --------------------------------------------------------------------------------