diff options
author | Chris Xiong <chirs241097@gmail.com> | 2020-04-06 00:50:58 +0800 |
---|---|---|
committer | Chris Xiong <chirs241097@gmail.com> | 2020-04-06 00:50:58 +0800 |
commit | ed47c1557915bb2472f6959e723cd76155312a98 (patch) | |
tree | 85bc451630ebaa4f5ffce3043b4cbf948a912a66 /deduper/libpuzzle/php | |
parent | 0a094f28c2e2ebfaac91398ae62e40f00f09221b (diff) | |
download | oddities-ed47c1557915bb2472f6959e723cd76155312a98.tar.xz |
Add deduper (unfinished tool for finding image duplicates).
Diffstat (limited to 'deduper/libpuzzle/php')
27 files changed, 1191 insertions, 0 deletions
diff --git a/deduper/libpuzzle/php/Makefile.am b/deduper/libpuzzle/php/Makefile.am new file mode 100644 index 0000000..dc0165f --- /dev/null +++ b/deduper/libpuzzle/php/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + libpuzzle \ + examples diff --git a/deduper/libpuzzle/php/examples/Makefile.am b/deduper/libpuzzle/php/examples/Makefile.am new file mode 100644 index 0000000..82c81ba --- /dev/null +++ b/deduper/libpuzzle/php/examples/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = \ + similar diff --git a/deduper/libpuzzle/php/examples/similar/Makefile.am b/deduper/libpuzzle/php/examples/similar/Makefile.am new file mode 100644 index 0000000..126f6df --- /dev/null +++ b/deduper/libpuzzle/php/examples/similar/Makefile.am @@ -0,0 +1,6 @@ +EXTRA_DIST = \ + schema.sqlite3.sql \ + schema.pgsql.sql \ + similar.php \ + similar.inc.php \ + config.inc.php diff --git a/deduper/libpuzzle/php/examples/similar/config.inc.php b/deduper/libpuzzle/php/examples/similar/config.inc.php new file mode 100644 index 0000000..d4e3b41 --- /dev/null +++ b/deduper/libpuzzle/php/examples/similar/config.inc.php @@ -0,0 +1,9 @@ +<?php + +define('MAX_IMAGE_SIZE', 1024 * 1024 * 4); +define('MAX_URL_SIZE', 255); +define('DB_DSN', 'sqlite:similar.sqlite3'); +define('MAX_WORDS', 100); +define('MAX_WORD_LENGTH', 10); + +?> diff --git a/deduper/libpuzzle/php/examples/similar/schema.pgsql.sql b/deduper/libpuzzle/php/examples/similar/schema.pgsql.sql new file mode 100644 index 0000000..7dc6bc1 --- /dev/null +++ b/deduper/libpuzzle/php/examples/similar/schema.pgsql.sql @@ -0,0 +1,230 @@ +-- +-- PostgreSQL database dump +-- + +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = off; +SET check_function_bodies = false; +SET client_min_messages = warning; +SET escape_string_warning = off; + +SET SESSION AUTHORIZATION 'similar'; + +-- +-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: similar +-- + +COMMENT ON SCHEMA public IS 'Standard public schema'; + + +SET search_path = public, pg_catalog; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: pictures; Type: TABLE; Schema: public; Owner: similar; Tablespace: +-- + +CREATE TABLE pictures ( + id integer NOT NULL, + digest character(32) NOT NULL, + CONSTRAINT ck_digest CHECK ((char_length(digest) = 32)) +); + + +-- +-- Name: pictures_id_seq; Type: SEQUENCE; Schema: public; Owner: similar +-- + +CREATE SEQUENCE pictures_id_seq + START WITH 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; + + +-- +-- Name: pictures_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: similar +-- + +ALTER SEQUENCE pictures_id_seq OWNED BY pictures.id; + + +-- +-- Name: sentpictures; Type: TABLE; Schema: public; Owner: similar; Tablespace: +-- + +CREATE TABLE sentpictures ( + id integer NOT NULL, + url character varying(255) NOT NULL, + sender character varying(100) NOT NULL, + picture_id integer NOT NULL, + CONSTRAINT ck_url CHECK (((url)::text <> ''::text)) +); + + +-- +-- Name: sentpictures_id_seq; Type: SEQUENCE; Schema: public; Owner: similar +-- + +CREATE SEQUENCE sentpictures_id_seq + START WITH 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; + + +-- +-- Name: sentpictures_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: similar +-- + +ALTER SEQUENCE sentpictures_id_seq OWNED BY sentpictures.id; + + +-- +-- Name: signatures; Type: TABLE; Schema: public; Owner: similar; Tablespace: +-- + +CREATE TABLE signatures ( + id integer NOT NULL, + compressed_signature bytea NOT NULL, + picture_id integer NOT NULL, + CONSTRAINT ck_signature CHECK ((octet_length(compressed_signature) >= 182)) +); + + +-- +-- Name: signatures_id_seq; Type: SEQUENCE; Schema: public; Owner: similar +-- + +CREATE SEQUENCE signatures_id_seq + START WITH 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; + + +-- +-- Name: signatures_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: similar +-- + +ALTER SEQUENCE signatures_id_seq OWNED BY signatures.id; + + +-- +-- Name: words; Type: TABLE; Schema: public; Owner: similar; Tablespace: +-- + +CREATE TABLE words ( + pos_and_word bytea NOT NULL, + signature_id integer NOT NULL, + CONSTRAINT ck_pos_and_word CHECK ((octet_length(pos_and_word) >= 2)) +); + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: similar +-- + +ALTER TABLE pictures ALTER COLUMN id SET DEFAULT nextval('pictures_id_seq'::regclass); + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: similar +-- + +ALTER TABLE sentpictures ALTER COLUMN id SET DEFAULT nextval('sentpictures_id_seq'::regclass); + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: similar +-- + +ALTER TABLE signatures ALTER COLUMN id SET DEFAULT nextval('signatures_id_seq'::regclass); + + +-- +-- Name: pictures_pkey; Type: CONSTRAINT; Schema: public; Owner: similar; Tablespace: +-- + +ALTER TABLE ONLY pictures + ADD CONSTRAINT pictures_pkey PRIMARY KEY (id); + + +-- +-- Name: sentpictures_pkey; Type: CONSTRAINT; Schema: public; Owner: similar; Tablespace: +-- + +ALTER TABLE ONLY sentpictures + ADD CONSTRAINT sentpictures_pkey PRIMARY KEY (id); + + +-- +-- Name: signatures_pkey; Type: CONSTRAINT; Schema: public; Owner: similar; Tablespace: +-- + +ALTER TABLE ONLY signatures + ADD CONSTRAINT signatures_pkey PRIMARY KEY (id); + + +-- +-- Name: idx_digest; Type: INDEX; Schema: public; Owner: similar; Tablespace: +-- + +CREATE UNIQUE INDEX idx_digest ON pictures USING btree (digest); + + +-- +-- Name: idx_picture_id; Type: INDEX; Schema: public; Owner: similar; Tablespace: +-- + +CREATE INDEX idx_picture_id ON sentpictures USING btree (picture_id); + + +-- +-- Name: idx_pos_and_word; Type: INDEX; Schema: public; Owner: similar; Tablespace: +-- + +CREATE INDEX idx_pos_and_word ON words USING btree (pos_and_word); + + +-- +-- Name: idx_url; Type: INDEX; Schema: public; Owner: similar; Tablespace: +-- + +CREATE UNIQUE INDEX idx_url ON sentpictures USING btree (url); + + +-- +-- Name: sentpictures_picture_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: similar +-- + +ALTER TABLE ONLY sentpictures + ADD CONSTRAINT sentpictures_picture_id_fkey FOREIGN KEY (picture_id) REFERENCES pictures(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: signatures_picture_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: similar +-- + +ALTER TABLE ONLY signatures + ADD CONSTRAINT signatures_picture_id_fkey FOREIGN KEY (picture_id) REFERENCES pictures(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: words_signature_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: similar +-- + +ALTER TABLE ONLY words + ADD CONSTRAINT words_signature_id_fkey FOREIGN KEY (signature_id) REFERENCES signatures(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/deduper/libpuzzle/php/examples/similar/schema.sqlite3.sql b/deduper/libpuzzle/php/examples/similar/schema.sqlite3.sql new file mode 100644 index 0000000..dc5a6c3 --- /dev/null +++ b/deduper/libpuzzle/php/examples/similar/schema.sqlite3.sql @@ -0,0 +1,23 @@ +CREATE TABLE pictures ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + digest CHAR(32) NOT NULL +); +CREATE TABLE sentpictures ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + url VARCHAR(255) NOT NULL, + sender VARCHAR(100) NOT NULL, + picture_id INTEGER NOT NULL +); +CREATE TABLE signatures ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + compressed_signature CHAR(182) NOT NULL, + picture_id INTEGER NOT NULL +); +CREATE TABLE words ( + pos_and_word CHAR(5) NOT NULL, + signature_id INTEGER NOT NULL +); +CREATE UNIQUE INDEX idx_digest ON pictures(digest); +CREATE INDEX idx_picture_id ON sentpictures (picture_id); +CREATE INDEX idx_pos_and_word ON words(pos_and_word); +CREATE UNIQUE INDEX idx_url ON sentpictures (url); diff --git a/deduper/libpuzzle/php/examples/similar/similar.inc.php b/deduper/libpuzzle/php/examples/similar/similar.inc.php new file mode 100644 index 0000000..cfc806e --- /dev/null +++ b/deduper/libpuzzle/php/examples/similar/similar.inc.php @@ -0,0 +1,120 @@ +<?php + +function split_into_words($sig) { + $words = array(); + $u = 0; + do { + $words[$u] = substr($sig, $u, MAX_WORD_LENGTH); + } while (++$u < MAX_WORDS); + + return $words; +} + +function save_signature($url, $client_info, $md5, $cvec) { + $compressed_cvec = puzzle_compress_cvec($cvec); + $words = split_into_words($cvec); + $dbh = new PDO(DB_DSN); + $dbh->beginTransaction(); + try { + $st = $dbh->prepare + ('DELETE FROM sentpictures WHERE url = :url'); + $st->execute(array(':url' => $url)); + $st = $dbh->prepare + ('SELECT id FROM pictures WHERE digest = :digest'); + $st->execute(array(':digest' => $md5)); + $picture_id = $st->fetchColumn(); + $st->closeCursor(); + $duplicate = TRUE; + if ($picture_id === FALSE) { + $duplicate = FALSE; + $st = $dbh->prepare + ('INSERT INTO pictures (digest) VALUES (:digest)'); + $st->execute(array(':digest' => $md5)); + $picture_id = $dbh->lastInsertId('id'); + } + $st = $dbh->prepare + ('INSERT INTO sentpictures (url, sender, picture_id) ' . + 'VALUES (:url, :sender, :picture_id)'); + $st->execute(array(':url' => $url, ':sender' => $client_info, + ':picture_id' => $picture_id)); + if ($duplicate === TRUE) { + $dbh->commit(); + return TRUE; + } + $st = $dbh->prepare + ('INSERT INTO signatures (compressed_signature, picture_id) ' . + 'VALUES(:compressed_signature, :picture_id)'); + $st->execute(array(':compressed_signature' => $compressed_cvec, + ':picture_id' => $picture_id)); + $signature_id = $dbh->lastInsertId('id'); + $st = $dbh->prepare + ('INSERT INTO words (pos_and_word, signature_id) ' . + 'VALUES (:pos_and_word, :signature_id)'); + foreach ($words as $u => $word) { + $st->execute(array('pos_and_word' + => chr($u) . puzzle_compress_cvec($word), + 'signature_id' => $signature_id)); + } + $dbh->commit(); + } catch (Exception $e) { + var_dump($e); + $dbh->rollback(); + } + return TRUE; +} + +function find_similar_pictures($md5, $cvec, + $threshold = PUZZLE_CVEC_SIMILARITY_THRESHOLD) { + $compressed_cvec = puzzle_compress_cvec($cvec); + $words = split_into_words($cvec); + $dbh = new PDO(DB_DSN); + $dbh->beginTransaction(); + $sql = 'SELECT DISTINCT(signature_id) AS signature_id FROM words ' . + 'WHERE pos_and_word IN ('; + $coma = FALSE; + foreach ($words as $u => $word) { + if ($coma === TRUE) { + $sql .= ','; + } + $sql .= $dbh->quote(chr($u) . puzzle_compress_cvec($word)); + $coma = TRUE; + } + $sql .= ')'; + $res_words = $dbh->query($sql); + $scores = array(); + $st = $dbh->prepare('SELECT compressed_signature, picture_id ' . + 'FROM signatures WHERE id = :id'); + while (($signature_id = $res_words->fetchColumn()) !== FALSE) { + $st->execute(array(':id' => $signature_id)); + $row = $st->fetch(); + $found_compressed_signature = $row['compressed_signature']; + $picture_id = $row['picture_id']; + $found_cvec = puzzle_uncompress_cvec($found_compressed_signature); + $distance = puzzle_vector_normalized_distance($cvec, $found_cvec); + if ($distance < $threshold && $distance > 0.0) { + $scores[$picture_id] = $distance; + } + } + $sql = 'SELECT url FROM sentpictures WHERE picture_id IN ('; + $coma = FALSE; + foreach ($scores as $picture_id => $score) { + if ($coma === TRUE) { + $sql .= ','; + } + $sql .= $dbh->quote($picture_id); + $coma = TRUE; + } + $sql .= ')'; + $urls = array(); + if (!empty($scores)) { + $res_urls = $dbh->query($sql); + while (($url = $res_urls->fetchColumn()) !== FALSE) { + array_push($urls, $url); + } + } + $dbh->commit(); + + return $urls; +} + +?> diff --git a/deduper/libpuzzle/php/examples/similar/similar.php b/deduper/libpuzzle/php/examples/similar/similar.php new file mode 100644 index 0000000..4b3ad40 --- /dev/null +++ b/deduper/libpuzzle/php/examples/similar/similar.php @@ -0,0 +1,158 @@ +<html><!-- sample image search engine, part of the libpuzzle package --> +<head> +</head> +<body> +<h1>Similar images finder using <a href="http://libpuzzle.pureftpd.org">libpuzzle</a></h1> +<?php + +error_reporting(E_ALL); + +require_once 'config.inc.php'; +require_once 'similar.inc.php'; + +function display_form() { + echo '<form action="' . htmlspecialchars($_SERVER['REQUEST_URI']) . '" ' . + 'method="POST">' . "\n"; + echo 'Enter an image URL (http only):' . "\n"; + echo '<input type="text" size="100" value="" autocomplete="off" name="url" />' . "\n"; + echo '<input type="submit" />'; + echo '</form>' . "\n"; +} + +function display_error($err) { + echo '<div id="err"><strong>' . htmlspecialchars($err) . '</strong></div>' . "\n"; +} + +function display_loading() { + echo '<div id="loading">Loading...</div>' . "\n"; + @ob_flush(); flush(); +} + +function display_loaded() { + echo '<div id="loaded">Loaded.</div>' . "\n"; + @ob_flush(); flush(); +} + +function display_signature_ok() { + echo '<div id="sig-ok">Signature computed.</div>' . "\n"; + @ob_flush(); flush(); +} + +function remove_tmpfile($file) { + @unlink($file); +} + +function get_client_info() { + return @$_SERVER['REMOTE_ADDR'] . '/' . time(); +} + +function display_similar_pictures($urls) { + echo '<div id="images">' . "\n"; + foreach ($urls as $url) { + echo '<a href="' . htmlentities($url) . '" ' . + 'onclick="window.open(this.href); return false;">'; + echo ' <img src="' . htmlentities($url) . '" alt="" />'; + echo '</a>' . "\n"; + + } + echo '</div>' . "\n"; +} + +function record_url($url, &$md5, &$cvec) { + if (function_exists('sys_get_temp_dir')) { + $tmpdir = sys_get_temp_dir(); + } else { + $tmpdir = '/tmp'; + } + $dfn = tempnam($tmpdir, 'similar-' . md5(uniqid(mt_rand(), TRUE))); + register_shutdown_function('remove_tmpfile', $dfn); + if (($dfp = fopen($dfn, 'w')) == FALSE) { + display_form(); + display_error('Unable to create the temporary file'); + return FALSE; + } + if (($fp = fopen($url, 'r')) == FALSE) { + display_form(); + display_error('Unable to open: [' . $url . ']'); + return FALSE; + } + $f = fread($fp, 4096); + $written = strlen($f); + if (empty($f)) { + display_form(); + display_error('Unable to load: [' . $url . ']'); + return FALSE; + } + fwrite($dfp, $f); + $infos = @getimagesize($dfn); + if (empty($infos) || + ($infos[2] !== IMAGETYPE_GIF && $infos[2] !== IMAGETYPE_JPEG && + $infos[2] !== IMAGETYPE_PNG) || + $infos[0] < 50 || $infos[1] < 50) { + fclose($dfp); + display_form(); + display_error('Unsupported image format'); + return FALSE; + } + fseek($dfp, strlen($f)); + while (!feof($fp)) { + $max = MAX_IMAGE_SIZE - $written; + if ($max > 65536) { + $max = 65536; + } + $t = fread($fp, $max); + fwrite($dfp, $t); + $written += strlen($t); + if ($written > MAX_IMAGE_SIZE) { + fclose($dfp); + display_form(); + display_error('File too large'); + return FALSE; + } + } + unset($t); + fclose($dfp); + display_loaded(); + $md5 = @md5_file($dfn); + if (empty($md5)) { + display_form(); + display_error('Unable to get the MD5 of the file'); + return FALSE; + } + $cvec = puzzle_fill_cvec_from_file($dfn); + if (empty($cvec)) { + display_form(); + display_error('Unable to compute image signature'); + return FALSE; + } + display_signature_ok(); + save_signature($url, get_client_info(), $md5, $cvec); + + return TRUE; +} + +$url = trim(@$_POST['url']); +if (empty($url)) { + display_form(); + exit(0); +} +if (strlen($url) > MAX_URL_SIZE || + preg_match('£^http://([a-z0-9-]+[.])+[a-z]{2,}/.£i', $url) <= 0) { + display_form(); + display_error('Invalid URL, must be http://...'); + exit(1); +} +display_loading(); +$md5 = FALSE; +$cvec = FALSE; +if (record_url($url, $md5, $cvec) !== TRUE) { + exit(1); +} +$urls = find_similar_pictures($md5, $cvec); +unset($cvec); +display_form(); +display_similar_pictures($urls); + +?> +</body> +</html> diff --git a/deduper/libpuzzle/php/libpuzzle/CREDITS b/deduper/libpuzzle/php/libpuzzle/CREDITS new file mode 100644 index 0000000..bb6ecb3 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/CREDITS @@ -0,0 +1 @@ +Frank DENIS <j at pureftpd.org> diff --git a/deduper/libpuzzle/php/libpuzzle/EXPERIMENTAL b/deduper/libpuzzle/php/libpuzzle/EXPERIMENTAL new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/EXPERIMENTAL diff --git a/deduper/libpuzzle/php/libpuzzle/LICENSE b/deduper/libpuzzle/php/libpuzzle/LICENSE new file mode 100644 index 0000000..1ce2d05 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/LICENSE @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2007-2015 Frank DENIS <j at pureftpd.org> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/deduper/libpuzzle/php/libpuzzle/Makefile.am b/deduper/libpuzzle/php/libpuzzle/Makefile.am new file mode 100644 index 0000000..f582035 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/Makefile.am @@ -0,0 +1,15 @@ +EXTRA_DIST = \ + CREDITS \ + EXPERIMENTAL \ + LICENSE \ + README \ + config.m4 \ + libpuzzle.c \ + libpuzzle.php \ + php_libpuzzle.h + +SUBDIRS = \ + build \ + include \ + modules \ + tests diff --git a/deduper/libpuzzle/php/libpuzzle/README b/deduper/libpuzzle/php/libpuzzle/README new file mode 100644 index 0000000..7bb674f --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/README @@ -0,0 +1,4 @@ +This is a PHP extension for libpuzzle. + +Have a look at the README-PHP file on top of the libpuzzle distribution for +more info about that extension. diff --git a/deduper/libpuzzle/php/libpuzzle/build/Makefile.am b/deduper/libpuzzle/php/libpuzzle/build/Makefile.am new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/build/Makefile.am diff --git a/deduper/libpuzzle/php/libpuzzle/config.m4 b/deduper/libpuzzle/php/libpuzzle/config.m4 new file mode 100644 index 0000000..84f954a --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/config.m4 @@ -0,0 +1,49 @@ +dnl config.m4 for extension libpuzzle + +dnl If your extension references something external, use with: + +PHP_ARG_WITH(libpuzzle, for libpuzzle support, + [ --with-libpuzzle Include libpuzzle support]) + +if test "$PHP_LIBPUZZLE" != "no"; then + for i in $PHP_LIBPUZZLE /usr/local /usr; do + if test -x "$i/bin/gdlib-config"; then + GDLIB_CONFIG=$i/bin/gdlib-config + break + fi + done + GDLIB_LIBS=$($GDLIB_CONFIG --ldflags --libs) + GDLIB_INCS=$($GDLIB_CONFIG --cflags) + + PHP_EVAL_LIBLINE($GDLIB_LIBS, LIBPUZZLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($GDLIB_INCS) + + SEARCH_PATH="/usr/local /usr" # you might want to change this + SEARCH_FOR="/include/puzzle.h" # you most likely want to change this + if test -r $PHP_LIBPUZZLE/$SEARCH_FOR; then # path given as parameter + LIBPUZZLE_DIR=$PHP_LIBPUZZLE + else # search default path list + AC_MSG_CHECKING([for libpuzzle files in default path]) + for i in $SEARCH_PATH ; do + if test -r $i/$SEARCH_FOR; then + LIBPUZZLE_DIR=$i + AC_MSG_RESULT(found in $i) + fi + done + fi + + if test -z "$LIBPUZZLE_DIR"; then + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([Please reinstall the libpuzzle distribution]) + fi + + dnl # --with-libpuzzle -> add include path + PHP_ADD_INCLUDE($LIBPUZZLE_DIR/include) + + PHP_ADD_LIBRARY_WITH_PATH(puzzle, $LIBPUZZLE_DIR/lib, + LIBPUZZLE_SHARED_LIBADD) + + PHP_SUBST(LIBPUZZLE_SHARED_LIBADD) + + PHP_NEW_EXTENSION(libpuzzle, libpuzzle.c, $ext_shared) +fi diff --git a/deduper/libpuzzle/php/libpuzzle/include/Makefile.am b/deduper/libpuzzle/php/libpuzzle/include/Makefile.am new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/include/Makefile.am diff --git a/deduper/libpuzzle/php/libpuzzle/libpuzzle.c b/deduper/libpuzzle/php/libpuzzle/libpuzzle.c new file mode 100644 index 0000000..82e84c3 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/libpuzzle.c @@ -0,0 +1,410 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include <puzzle.h> +#include "php_libpuzzle.h" + +ZEND_DECLARE_MODULE_GLOBALS(libpuzzle) + +/* True global resources - no need for thread safety here */ +static int le_libpuzzle; + +/* {{{ libpuzzle_functions[] + */ +zend_function_entry libpuzzle_functions[] = { + PHP_FE(puzzle_set_max_width, NULL) + PHP_FE(puzzle_set_max_height, NULL) + PHP_FE(puzzle_set_lambdas, NULL) + PHP_FE(puzzle_set_noise_cutoff, NULL) + PHP_FE(puzzle_set_p_ratio, NULL) + PHP_FE(puzzle_set_contrast_barrier_for_cropping, NULL) + PHP_FE(puzzle_set_max_cropping_ratio, NULL) + PHP_FE(puzzle_set_autocrop, NULL) + + PHP_FE(puzzle_fill_cvec_from_file, NULL) + PHP_FE(puzzle_compress_cvec, NULL) + PHP_FE(puzzle_uncompress_cvec, NULL) + PHP_FE(puzzle_vector_normalized_distance, NULL) + + {NULL, NULL, NULL} /* Must be the last line in libpuzzle_functions[] */ +}; +/* }}} */ + +/* {{{ libpuzzle_module_entry + */ +zend_module_entry libpuzzle_module_entry = { +#if ZEND_MODULE_API_NO >= 20010901 + STANDARD_MODULE_HEADER, +#endif + "libpuzzle", + libpuzzle_functions, + PHP_MINIT(libpuzzle), + PHP_MSHUTDOWN(libpuzzle), + PHP_RINIT(libpuzzle), /* Replace with NULL if there's nothing to do at request start */ + PHP_RSHUTDOWN(libpuzzle), /* Replace with NULL if there's nothing to do at request end */ + PHP_MINFO(libpuzzle), +#if ZEND_MODULE_API_NO >= 20010901 + "0.10", /* Replace with version number for your extension */ +#endif + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +#ifdef COMPILE_DL_LIBPUZZLE +ZEND_GET_MODULE(libpuzzle) +#endif + + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(libpuzzle) +{ + REGISTER_DOUBLE_CONSTANT("PUZZLE_CVEC_SIMILARITY_THRESHOLD", + PUZZLE_CVEC_SIMILARITY_THRESHOLD, + CONST_CS | CONST_PERSISTENT); + REGISTER_DOUBLE_CONSTANT("PUZZLE_CVEC_SIMILARITY_HIGH_THRESHOLD", + PUZZLE_CVEC_SIMILARITY_HIGH_THRESHOLD, + CONST_CS | CONST_PERSISTENT); + REGISTER_DOUBLE_CONSTANT("PUZZLE_CVEC_SIMILARITY_LOW_THRESHOLD", + PUZZLE_CVEC_SIMILARITY_LOW_THRESHOLD, + CONST_CS | CONST_PERSISTENT); + REGISTER_DOUBLE_CONSTANT("PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD", + PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD, + CONST_CS | CONST_PERSISTENT); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(libpuzzle) +{ + return SUCCESS; +} +/* }}} */ + +/* Remove if there's nothing to do at request start */ +/* {{{ PHP_RINIT_FUNCTION + */ +PHP_RINIT_FUNCTION(libpuzzle) +{ + puzzle_init_context(&LIBPUZZLE_G(global_context)); + return SUCCESS; +} +/* }}} */ + +/* Remove if there's nothing to do at request end */ +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +PHP_RSHUTDOWN_FUNCTION(libpuzzle) +{ + puzzle_free_context(&LIBPUZZLE_G(global_context)); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(libpuzzle) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "libpuzzle support", "enabled"); + php_info_print_table_end(); +} +/* }}} */ + +/* {{{ proto string puzzle_fill_cvec_from_file(string filename) + * Creates a signature out of an image file */ +PHP_FUNCTION(puzzle_fill_cvec_from_file) +{ + char *arg = NULL; + int arg_len; + PuzzleContext *context; + PuzzleCvec cvec; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "s", &arg, &arg_len) == FAILURE || + arg_len <= 0) { + RETURN_FALSE; + } + puzzle_init_cvec(context, &cvec); + if (puzzle_fill_cvec_from_file(context, &cvec, arg) != 0) { + puzzle_free_cvec(context, &cvec); + RETURN_FALSE; + } + RETVAL_STRINGL(cvec.vec, cvec.sizeof_vec, 1); + puzzle_free_cvec(context, &cvec); +} +/* }}} */ + +/* {{{ proto string puzzle_compress_cvec(string cvec) + * Compress a signature to save storage space */ +PHP_FUNCTION(puzzle_compress_cvec) +{ + char *arg = NULL; + int arg_len; + PuzzleContext *context; + PuzzleCompressedCvec compressed_cvec; + PuzzleCvec cvec; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "s", &arg, &arg_len) == FAILURE || + arg_len <= 0) { + RETURN_FALSE; + } + puzzle_init_compressed_cvec(context, &compressed_cvec); + puzzle_init_cvec(context, &cvec); + cvec.vec = arg; + cvec.sizeof_vec = (size_t) arg_len; + if (puzzle_compress_cvec(context, &compressed_cvec, &cvec) != 0) { + puzzle_free_compressed_cvec(context, &compressed_cvec); + cvec.vec = NULL; + puzzle_free_cvec(context, &cvec); + RETURN_FALSE; + } + RETVAL_STRINGL(compressed_cvec.vec, + compressed_cvec.sizeof_compressed_vec, 1); + puzzle_free_compressed_cvec(context, &compressed_cvec); + cvec.vec = NULL; + puzzle_free_cvec(context, &cvec); +} +/* }}} */ + +/* {{{ proto string puzzle_uncompress_cvec(string compressed_cvec) + * Uncompress a compressed signature so that it can be used for computations */ +PHP_FUNCTION(puzzle_uncompress_cvec) +{ + char *arg = NULL; + int arg_len; + PuzzleContext *context; + PuzzleCompressedCvec compressed_cvec; + PuzzleCvec cvec; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, + "s", &arg, &arg_len) == FAILURE || + arg_len <= 0) { + RETURN_FALSE; + } + puzzle_init_compressed_cvec(context, &compressed_cvec); + puzzle_init_cvec(context, &cvec); + compressed_cvec.vec = arg; + compressed_cvec.sizeof_compressed_vec = (size_t) arg_len; + if (puzzle_uncompress_cvec(context, &compressed_cvec, &cvec) != 0) { + puzzle_free_cvec(context, &cvec); + compressed_cvec.vec = NULL; + puzzle_free_compressed_cvec(context, &compressed_cvec); + RETURN_FALSE; + } + RETVAL_STRINGL(cvec.vec, cvec.sizeof_vec, 1); + puzzle_free_cvec(context, &cvec); + compressed_cvec.vec = NULL; + puzzle_free_compressed_cvec(context, &compressed_cvec); +} +/* }}} */ + +/* {{{ proto double puzzle_vector_normalized_distance(string cvec1, string cvec2 [, bool fix_for_texts]) + * Computes the distance between two signatures. Result is between 0.0 and 1.0 */ +PHP_FUNCTION(puzzle_vector_normalized_distance) +{ + char *vec1 = NULL, *vec2 = NULL; + int vec1_len, vec2_len; + PuzzleContext *context; + PuzzleCvec cvec1, cvec2; + double d; + zend_bool fix_for_texts; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters + (ZEND_NUM_ARGS() TSRMLS_CC, "ss|b", + &vec1, &vec1_len, &vec2, &vec2_len, &fix_for_texts) == FAILURE || + vec1_len <= 0 || vec2_len <= 0) { + RETURN_FALSE; + } + if (ZEND_NUM_ARGS() TSRMLS_CC < 3) { + fix_for_texts = (zend_bool) 1; + } + puzzle_init_cvec(context, &cvec1); + puzzle_init_cvec(context, &cvec2); + cvec1.vec = vec1; + cvec1.sizeof_vec = (size_t) vec1_len; + cvec2.vec = vec2; + cvec2.sizeof_vec = (size_t) vec2_len; + d = puzzle_vector_normalized_distance(context, &cvec1, &cvec2, + (int) fix_for_texts); + cvec1.vec = cvec2.vec = NULL; + puzzle_free_cvec(context, &cvec1); + puzzle_free_cvec(context, &cvec2); + RETVAL_DOUBLE(d); +} +/* }}} */ + +/* {{{ proto bool puzzle_set_max_width(int width) + * Set the maximum picture width */ +PHP_FUNCTION(puzzle_set_max_width) +{ + PuzzleContext *context; + long width; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters + (ZEND_NUM_ARGS() TSRMLS_CC, "l", &width) == FAILURE || + width <= 0L || width > INT_MAX) { + RETURN_FALSE; + } + if (puzzle_set_max_width(context, (unsigned int) width) != 0) { + RETURN_FALSE; + } + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool puzzle_set_max_height(int height) + * Set the maximum picture height */ +PHP_FUNCTION(puzzle_set_max_height) +{ + PuzzleContext *context; + long height; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters + (ZEND_NUM_ARGS() TSRMLS_CC, "l", &height) == FAILURE || + height <= 0L || height > INT_MAX) { + RETURN_FALSE; + } + if (puzzle_set_max_height(context, (unsigned int) height) != 0) { + RETURN_FALSE; + } + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool puzzle_set_lambdas(int lambdas) + * Set the size of the computation grid */ +PHP_FUNCTION(puzzle_set_lambdas) +{ + PuzzleContext *context; + long lambdas; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters + (ZEND_NUM_ARGS() TSRMLS_CC, "l", &lambdas) == FAILURE || + lambdas <= 0L || lambdas > INT_MAX) { + RETURN_FALSE; + } + if (puzzle_set_lambdas(context, (unsigned int) lambdas) != 0) { + RETURN_FALSE; + } + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool puzzle_set_noise_cutoff(double cutoff) + * Set the noise cutoff level */ +PHP_FUNCTION(puzzle_set_noise_cutoff) +{ + PuzzleContext *context; + double cutoff; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters + (ZEND_NUM_ARGS() TSRMLS_CC, "d", &cutoff) == FAILURE) { + RETURN_FALSE; + } + if (puzzle_set_noise_cutoff(context, cutoff) != 0) { + RETURN_FALSE; + } + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool puzzle_set_p_ratio(double ratio) + * Set the p_ratio */ +PHP_FUNCTION(puzzle_set_p_ratio) +{ + PuzzleContext *context; + double p_ratio; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters + (ZEND_NUM_ARGS() TSRMLS_CC, "d", &p_ratio) == FAILURE) { + RETURN_FALSE; + } + if (puzzle_set_p_ratio(context, p_ratio) != 0) { + RETURN_FALSE; + } + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool puzzle_set_contrast_barrier_for_cropping(double barrier) + * Set the tolerance level for cropping */ +PHP_FUNCTION(puzzle_set_contrast_barrier_for_cropping) +{ + PuzzleContext *context; + double barrier; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters + (ZEND_NUM_ARGS() TSRMLS_CC, "d", &barrier) == FAILURE) { + RETURN_FALSE; + } + if (puzzle_set_contrast_barrier_for_cropping(context, barrier) != 0) { + RETURN_FALSE; + } + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool puzzle_set_max_cropping_ratio(double ratio) + * Set the maximum ratio between the cropped area and the whole picture */ +PHP_FUNCTION(puzzle_set_max_cropping_ratio) +{ + PuzzleContext *context; + double ratio; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters + (ZEND_NUM_ARGS() TSRMLS_CC, "d", &ratio) == FAILURE) { + RETURN_FALSE; + } + if (puzzle_set_max_cropping_ratio(context, ratio) != 0) { + RETURN_FALSE; + } + RETVAL_TRUE; +} +/* }}} */ + +/* {{{ proto bool puzzle_set_autocrop(bool autocrop) + * TRUE to enable autocropping, FALSE to disable */ +PHP_FUNCTION(puzzle_set_autocrop) +{ + PuzzleContext *context; + zend_bool autocrop; + + context = &LIBPUZZLE_G(global_context); + if (zend_parse_parameters + (ZEND_NUM_ARGS() TSRMLS_CC, "b", &autocrop) == FAILURE) { + RETURN_FALSE; + } + if (puzzle_set_autocrop(context, (int) autocrop) != 0) { + RETURN_FALSE; + } + RETVAL_TRUE; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/deduper/libpuzzle/php/libpuzzle/libpuzzle.php b/deduper/libpuzzle/php/libpuzzle/libpuzzle.php new file mode 100644 index 0000000..415273b --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/libpuzzle.php @@ -0,0 +1,21 @@ +<?php +$br = (php_sapi_name() == "cli")? "":"<br>"; + +if(!extension_loaded('libpuzzle')) { + dl('libpuzzle.' . PHP_SHLIB_SUFFIX); +} +$module = 'libpuzzle'; +$functions = get_extension_funcs($module); +echo "Functions available in the test extension:$br\n"; +foreach($functions as $func) { + echo $func."$br\n"; +} +echo "$br\n"; +$function = 'confirm_' . $module . '_compiled'; +if (extension_loaded($module)) { + $str = $function($module); +} else { + $str = "Module $module is not compiled into PHP"; +} +echo "$str\n"; +?> diff --git a/deduper/libpuzzle/php/libpuzzle/modules/Makefile.am b/deduper/libpuzzle/php/libpuzzle/modules/Makefile.am new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/modules/Makefile.am diff --git a/deduper/libpuzzle/php/libpuzzle/php_libpuzzle.h b/deduper/libpuzzle/php/libpuzzle/php_libpuzzle.h new file mode 100644 index 0000000..1fae819 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/php_libpuzzle.h @@ -0,0 +1,66 @@ +#ifndef PHP_LIBPUZZLE_H +#define PHP_LIBPUZZLE_H + +extern zend_module_entry libpuzzle_module_entry; +#define phpext_libpuzzle_ptr &libpuzzle_module_entry + +#ifdef PHP_WIN32 +#define PHP_LIBPUZZLE_API __declspec(dllexport) +#else +#define PHP_LIBPUZZLE_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +PHP_MINIT_FUNCTION(libpuzzle); +PHP_MSHUTDOWN_FUNCTION(libpuzzle); +PHP_RINIT_FUNCTION(libpuzzle); +PHP_RSHUTDOWN_FUNCTION(libpuzzle); +PHP_MINFO_FUNCTION(libpuzzle); + +PHP_FUNCTION(puzzle_set_max_width); +PHP_FUNCTION(puzzle_set_max_height); +PHP_FUNCTION(puzzle_set_lambdas); +PHP_FUNCTION(puzzle_set_noise_cutoff); +PHP_FUNCTION(puzzle_set_p_ratio); +PHP_FUNCTION(puzzle_set_contrast_barrier_for_cropping); +PHP_FUNCTION(puzzle_set_max_cropping_ratio); +PHP_FUNCTION(puzzle_set_autocrop); + +PHP_FUNCTION(puzzle_fill_cvec_from_file); +PHP_FUNCTION(puzzle_compress_cvec); +PHP_FUNCTION(puzzle_uncompress_cvec); +PHP_FUNCTION(puzzle_vector_normalized_distance); + +ZEND_BEGIN_MODULE_GLOBALS(libpuzzle) + PuzzleContext global_context; +ZEND_END_MODULE_GLOBALS(libpuzzle) + +/* In every utility function you add that needs to use variables + in php_libpuzzle_globals, call TSRMLS_FETCH(); after declaring other + variables used by that function, or better yet, pass in TSRMLS_CC + after the last function argument and declare your utility function + with TSRMLS_DC after the last declared argument. Always refer to + the globals in your function as LIBPUZZLE_G(variable). You are + encouraged to rename these macros something shorter, see + examples in any other php module directory. +*/ + +#ifdef ZTS +#define LIBPUZZLE_G(v) TSRMG(libpuzzle_globals_id, zend_libpuzzle_globals *, v) +#else +#define LIBPUZZLE_G(v) (libpuzzle_globals.v) +#endif + +#endif /* PHP_LIBPUZZLE_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/deduper/libpuzzle/php/libpuzzle/tests/001.phpt b/deduper/libpuzzle/php/libpuzzle/tests/001.phpt new file mode 100644 index 0000000..5a5f5b5 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/tests/001.phpt @@ -0,0 +1,10 @@ +--TEST-- +Check for libpuzzle presence +--SKIPIF-- +<?php if (!extension_loaded("libpuzzle")) print "skip"; ?> +--FILE-- +<?php +echo "libpuzzle extension is available"; +?> +--EXPECT-- +libpuzzle extension is available diff --git a/deduper/libpuzzle/php/libpuzzle/tests/002.phpt b/deduper/libpuzzle/php/libpuzzle/tests/002.phpt new file mode 100644 index 0000000..d675145 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/tests/002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Check for distance between similar images +--SKIPIF-- +<?php if (!extension_loaded("libpuzzle")) print "skip"; ?> +--FILE-- +<?php + +$cvec1 = puzzle_fill_cvec_from_file(dirname(__FILE__) . '/pics/pic-a-0.jpg'); +$cvec2 = puzzle_fill_cvec_from_file(dirname(__FILE__) . '/pics/pic-a-1.jpg'); +$d = puzzle_vector_normalized_distance($cvec1, $cvec2); +exit((int) ($d < PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD)); + +?> +--EXPECT-- +1 diff --git a/deduper/libpuzzle/php/libpuzzle/tests/003.phpt b/deduper/libpuzzle/php/libpuzzle/tests/003.phpt new file mode 100644 index 0000000..ba7d5aa --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/tests/003.phpt @@ -0,0 +1,24 @@ +--TEST-- +Check the puzzle_set(3) interface +--SKIPIF-- +<?php if (!extension_loaded("libpuzzle")) print "skip"; ?> +--FILE-- +<?php + +$cvec1 = puzzle_fill_cvec_from_file(dirname(__FILE__) . '/pics/pic-a-0.jpg'); +$cvec2 = puzzle_fill_cvec_from_file(dirname(__FILE__) . '/pics/pic-a-1.jpg'); +puzzle_set_max_width(1500); +puzzle_set_max_height(1500); +puzzle_set_lambdas(11); +puzzle_set_noise_cutoff(1.0); +puzzle_set_p_ratio(2.0); +puzzle_set_contrast_barrier_for_cropping(0.1); +puzzle_set_max_cropping_ratio(0.1); +puzzle_set_autocrop(FALSE); + +$d = puzzle_vector_normalized_distance($cvec1, $cvec2); +exit((int) ($d < PUZZLE_CVEC_SIMILARITY_LOWER_THRESHOLD)); + +?> +--EXPECT-- +1 diff --git a/deduper/libpuzzle/php/libpuzzle/tests/Makefile.am b/deduper/libpuzzle/php/libpuzzle/tests/Makefile.am new file mode 100644 index 0000000..14ded39 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/tests/Makefile.am @@ -0,0 +1,7 @@ +EXTRA_DIST = \ + 001.phpt \ + 002.phpt \ + 003.phpt + +SUBDIRS = \ + pics diff --git a/deduper/libpuzzle/php/libpuzzle/tests/pics/Makefile.am b/deduper/libpuzzle/php/libpuzzle/tests/pics/Makefile.am new file mode 100644 index 0000000..0aacd9a --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/tests/pics/Makefile.am @@ -0,0 +1,3 @@ +EXTRA_DIST = \ + pic-a-0.jpg \ + pic-a-1.jpg diff --git a/deduper/libpuzzle/php/libpuzzle/tests/pics/pic-a-0.jpg b/deduper/libpuzzle/php/libpuzzle/tests/pics/pic-a-0.jpg Binary files differnew file mode 100644 index 0000000..3dd4a3b --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/tests/pics/pic-a-0.jpg diff --git a/deduper/libpuzzle/php/libpuzzle/tests/pics/pic-a-1.jpg b/deduper/libpuzzle/php/libpuzzle/tests/pics/pic-a-1.jpg Binary files differnew file mode 100644 index 0000000..95f0e77 --- /dev/null +++ b/deduper/libpuzzle/php/libpuzzle/tests/pics/pic-a-1.jpg |