diff --git a/composer.json b/composer.json index 821b9b7..6f9f25c 100644 --- a/composer.json +++ b/composer.json @@ -1,23 +1,31 @@ { "name": "jansenfelipe/omr", - "description": "Optical Mark Recognition from PHP", + "description": "Optical Mark Recognition from PHP with support of barcodes and qrcodes", "type": "library", "license": "MIT", "authors": [ { "name": "Jansen Felipe", "email": "jansen.felipe@gmail.com" + }, + { + "name": "Geoffroy de Corbiac", + "email": "geoffroydecorbiac@gmail.com" } ], "keywords": [ "omr", "optical mark recognition", - "survey" + "survey", + "barcode", + "zbar", + "qrcode" ], "require": { "php": "7.*", "ext-imagick": "*", - "symfony/console": "2.7.*" + "symfony/console": "2.7.*", + "robbiep/zbar-qrdecoder": "dev-master" }, "bin": ["bin/omr"], "require-dev": { diff --git a/example/example_2.jpg b/example/example_2.jpg new file mode 100644 index 0000000..1983634 Binary files /dev/null and b/example/example_2.jpg differ diff --git a/example/example_2.json b/example/example_2.json new file mode 100644 index 0000000..caa687b --- /dev/null +++ b/example/example_2.json @@ -0,0 +1,110 @@ +{ + "dpi": 150, + "width": 1275, + "height": 1753, + "limits": { + "topRight": { + "x": 1152, + "y": 70 + }, + "bottomLeft": { + "x": 70, + "y": 1671 + } + }, + "targets": [ + { + "x1": 642, + "y1": 118, + "x2": 665, + "y2": 141, + "id": "Rectangle : 1", + "type": "rectangle", + "tolerance": 60 + }, + { + "x1": 712, + "y1": 118, + "x2": 736, + "y2": 141, + "id": "Rectangle : 2", + "type": "rectangle", + "tolerance": 60 + }, + { + "x1": 788, + "y1": 118, + "x2": 811, + "y2": 141, + "id": "Rectangle : 3", + "type": "rectangle", + "tolerance": 60 + }, + { + "x1": 866, + "y1": 118, + "x2": 889, + "y2": 141, + "id": "Rectangle : 4", + "type": "rectangle", + "tolerance": 60 + }, + { + "x1": 944, + "y1": 118, + "x2": 968, + "y2": 141, + "id": "Rectangle : 5", + "type": "rectangle", + "tolerance": 60 + }, + { + "x1": 619, + "y1": 277, + "x2": 968, + "y2": 433, + "id": "Text area :", + "type": "text" + }, + { + "x1": 619, + "y1": 500, + "x2": 968, + "y2": 600, + "id": "Fake barcode zone", + "type": "barcode" + }, + { + "x1": 138, + "y1": 615, + "x2": 650, + "y2": 740, + "id": "Code 39", + "type": "barcode" + }, + { + "x1": 138, + "y1": 857, + "x2": 644, + "y2": 980, + "id": "Code 128", + "type": "barcode" + }, + { + "x1": 163, + "y1": 1140, + "x2": 484, + "y2": 1279, + "id": "EAN 13", + "type": "barcode" + }, + { + "x1": 147, + "y1": 1424, + "x2": 325, + "y2": 1584, + "id": "QRCode", + "type": "barcode" + } + ] + } \ No newline at end of file diff --git a/src/Commands/ScanCommand.php b/src/Commands/ScanCommand.php index 3364f1c..0d09150 100644 --- a/src/Commands/ScanCommand.php +++ b/src/Commands/ScanCommand.php @@ -61,8 +61,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $result = $scanner->scan($map); $io = new SymfonyStyle($input, $output); - - $io->table(['id', 'marked'], $result->toArray()['targets']); + $io->table(['id', 'marked', 'result'], $result->toArray()['targets']); } diff --git a/src/Contracts/Scanner.php b/src/Contracts/Scanner.php index a0ff89a..db46f1b 100644 --- a/src/Contracts/Scanner.php +++ b/src/Contracts/Scanner.php @@ -9,6 +9,8 @@ use JansenFelipe\OMR\Targets\CircleTarget; use JansenFelipe\OMR\Targets\RectangleTarget; use JansenFelipe\OMR\Targets\TextTarget; +use JansenFelipe\OMR\Targets\ZBarTarget; +use RobbieP\ZbarQrdecoder\Result\ErrorResult; abstract class Scanner { @@ -47,6 +49,15 @@ protected abstract function topRight(Point $near); */ protected abstract function bottomLeft(Point $near); + /** + * Returns barcode analysis in a rectangular area + * + * @param Point $a + * @param Point $b + * @return mixed + */ + protected abstract function barcodeArea(Point $a, Point $b); + /** * Returns pixel analysis in a rectangular area * @@ -191,28 +202,7 @@ public function scan(Map $map) foreach($map->targets() as $target) { - if ($target instanceof TextTarget) - { - $target->setImageBlob($this->textArea($target->getA()->moveX($ajustX)->moveY($ajustY), $target->getB()->moveX($ajustX)->moveY($ajustY))); - - }else { - - if ($target instanceof RectangleTarget) - { - $area = $this->rectangleArea($target->getA()->moveX($ajustX)->moveY($ajustY), $target->getB()->moveX($ajustX)->moveY($ajustY), $target->getTolerance()); - } - - if ($target instanceof CircleTarget) - { - $area = $this->circleArea($target->getPoint()->moveX($ajustX)->moveY($ajustY), $target->getRadius(), $target->getTolerance()); - } - - $target->setBlackPixelsPercent($area->percentBlack()); - - $target->setMarked($area->percentBlack() >= $target->getTolerance()); - } - - $result->addTarget($target); + $result->addTarget($this->analyzeTarget($target, $ajustX, $ajustY)); } @@ -280,4 +270,40 @@ protected function createResult($imagePath){ return $result; } + private function analyzeTarget(Target $target, $ajustX, $ajustY) + { + if ($target instanceof TextTarget) { + $target->setImageBlob($this->textArea($target->getA()->moveX($ajustX)->moveY($ajustY), $target->getB()->moveX($ajustX)->moveY($ajustY))); + return $target; + } + + if ($target instanceof ZBarTarget) { + try { + $result = $this->barcodeArea($target->getA()->moveX($ajustX)->moveY($ajustY), $target->getB()->moveX($ajustX)->moveY($ajustY)); + if ($result instanceof ErrorResult) { + $target->setResult($result->text); + } else { + $target->setResult($result->text); + $target->setFormat($result->format); + $target->setMarked(true); + } + } catch (\Throwable $th) { + $target->setResult($th->getMessage()); + } + return $target; + } + + if ($target instanceof RectangleTarget) { + $area = $this->rectangleArea($target->getA()->moveX($ajustX)->moveY($ajustY), $target->getB()->moveX($ajustX)->moveY($ajustY), $target->getTolerance()); + } + + if ($target instanceof CircleTarget) { + $area = $this->circleArea($target->getPoint()->moveX($ajustX)->moveY($ajustY), $target->getRadius(), $target->getTolerance()); + } + + $target->setBlackPixelsPercent($area->percentBlack()); + $target->setMarked($area->percentBlack() >= $target->getTolerance()); + + return $target; + } } diff --git a/src/Maps/MapJson.php b/src/Maps/MapJson.php index 21b7b89..6644872 100644 --- a/src/Maps/MapJson.php +++ b/src/Maps/MapJson.php @@ -8,6 +8,7 @@ use JansenFelipe\OMR\Targets\CircleTarget; use JansenFelipe\OMR\Targets\RectangleTarget; use JansenFelipe\OMR\Targets\TextTarget; +use JansenFelipe\OMR\Targets\ZBarTarget; class MapJson implements Map { @@ -96,6 +97,11 @@ public function targets() $t = new TextTarget($target['id'], new Point($target['x1'], $target['y1']), new Point($target['x2'], $target['y2'])); } + if($target['type'] == 'barcode') + { + $t = new ZBarTarget($target['id'], new Point($target['x1'], $target['y1']), new Point($target['x2'], $target['y2'])); + } + if($target['type'] == 'rectangle') { $t = new RectangleTarget($target['id'], new Point($target['x1'], $target['y1']), new Point($target['x2'], $target['y2'])); diff --git a/src/Result.php b/src/Result.php index 34f6818..bcc6b54 100644 --- a/src/Result.php +++ b/src/Result.php @@ -4,6 +4,7 @@ use JansenFelipe\OMR\Contracts\Target; use JansenFelipe\OMR\Targets\TextTarget; +use JansenFelipe\OMR\Targets\ZBarTarget; class Result { @@ -109,7 +110,8 @@ public function toArray() $targets = array_map(function (Target $item) { return [ 'id' => $item->getId(), - 'marked' => $item->isMarked() ? 'yes' : 'no' + 'marked' => $item->isMarked() ? 'yes' : 'no', + 'result' => $item instanceof ZBarTarget ? $item->getResult() : ' - ' ]; }, $filtered); diff --git a/src/Scanners/ImagickScanner.php b/src/Scanners/ImagickScanner.php index d15dd04..2817a1f 100644 --- a/src/Scanners/ImagickScanner.php +++ b/src/Scanners/ImagickScanner.php @@ -7,6 +7,7 @@ use JansenFelipe\OMR\Area; use JansenFelipe\OMR\Contracts\Scanner; use JansenFelipe\OMR\Point; +use RobbieP\ZbarQrdecoder\ZbarDecoder; class ImagickScanner extends Scanner { @@ -269,6 +270,25 @@ protected function textArea(Point $a, Point $b) return $region->getImageBlob(); } + /** + * Returns barcode analysis of a rectangular area + * + * @param Point $a + * @param Point $b + * @return mixed + */ + protected function barcodeArea(Point $a, Point $b) + { + $imageBlob = $this->textArea($a, $b); + $image = new \Imagick(); + $image->readImageBlob($imageBlob); + $tmpImagePath = tempnam(sys_get_temp_dir(), 'CODE'); + $image->writeImage($tmpImagePath); + + $ZbarDecoder = new ZbarDecoder(); + return $ZbarDecoder->make($tmpImagePath); + } + /** * Finish processes * diff --git a/src/Targets/ZBarTarget.php b/src/Targets/ZBarTarget.php new file mode 100644 index 0000000..8012747 --- /dev/null +++ b/src/Targets/ZBarTarget.php @@ -0,0 +1,117 @@ +id = $id; + $this->a = $a; + $this->b = $b; + } + + /** + * Get Pointer Top/Left + * + * @return Point + */ + public function getA() + { + return $this->a; + } + + /** + * Get Pointer Bottom/Right + * + * @return Point + */ + public function getB() + { + return $this->b; + } + + /** + * Get the barcode format + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Set the barcode format + * + * @return string + */ + public function setFormat($format) + { + $this->format = $format; + } + + /** + * @param string $result + */ + public function setResult($result) + { + $this->result = $result; + } + + /** + * Get the decoded barcode result + * + * @return string + */ + public function getResult() + { + return $this->result; + } +} \ No newline at end of file