1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
14: class Ansel_Faces_Base
15: {
16: 17:
18: public function canAutogenerate()
19: {
20: return false;
21: }
22:
23: 24: 25: 26: 27:
28: protected function _getFaces($file)
29: {
30: return array();
31: }
32:
33: 34: 35: 36: 37: 38: 39:
40: public function getFaces(Ansel_Image $image)
41: {
42: if ($image instanceof Ansel_Image) {
43:
44: $image->load('screen');
45:
46:
47: $file = $GLOBALS['injector']
48: ->getInstance('Horde_Core_Factory_Vfs')
49: ->create('images')
50: ->readFile(
51: $image->getVFSPath('screen'),
52: $image->getVFSName('screen'));
53: } else {
54: $file = $image;
55: }
56: if (empty($file)) {
57: return array();
58: }
59:
60:
61: $faces = $this->_getFaces($file);
62: if (empty($faces)) {
63: return array();
64: }
65:
66:
67:
68: foreach ($faces as $face) {
69: $id = $this->_isInFace($face, $faces);
70: if ($id !== false) {
71: unset($faces[$id]);
72: }
73: }
74:
75: return $faces;
76: }
77:
78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
92: public function getImageFacesData($image_id, $full = false)
93: {
94: $sql = 'SELECT face_id, face_name, image_id';
95: if ($full) {
96: $sql .= ', gallery_id, face_x1, face_y1, face_x2, face_y2';
97: }
98: $sql .= ' FROM ansel_faces WHERE image_id = ' . (int)$image_id
99: . ' ORDER BY face_id DESC';
100:
101: try {
102: return $GLOBALS['ansel_db']->selectAll($sql);
103: } catch (Horde_Db_Exception $e) {
104: throw new Ansel_Exception($e);
105: }
106: }
107:
108: 109: 110: 111: 112: 113: 114: 115:
116: public function getGalleryFaces($gallery_id)
117: {
118: $sql = 'SELECT face_id, image_id, gallery_id, face_name FROM ansel_faces '
119: . ' WHERE gallery_id = ' . (int)$gallery_id . ' ORDER BY face_id DESC';
120:
121: try {
122: return $GLOBALS['ansel_db']->selectAll($sql);
123: } catch (Horde_Db_Exception $e) {
124: throw new Ansel_Exception($e);
125: }
126: }
127:
128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139:
140: protected function _fetchFaces(array $info, $from = 0, $count = 0)
141: {
142: $galleries = $GLOBALS['injector']
143: ->getInstance('Ansel_Storage')
144: ->listGalleries(array('perm' => Horde_Perms::READ));
145:
146: $ids = array();
147: foreach ($galleries as $gallery) {
148: $ids[] = $gallery->id;
149: }
150: $sql = 'SELECT f.face_id, f.gallery_id, f.image_id, f.face_name FROM '
151: . 'ansel_faces f WHERE f.gallery_id IN (' . implode(',', $ids)
152: . ') ORDER BY '
153: . (isset($info['order']) ? $info['order'] : ' f.face_id DESC');
154:
155: $sql = $GLOBALS['ansel_db']->addLimitOffset(
156: $sql, array('offset' => $from, 'limit' => $count));
157: try {
158: return $GLOBALS['ansel_db']->selectAll($sql);
159: } catch (Horde_Db_Exception $e) {
160: throw new Ansel_Exception($e);
161: }
162: }
163:
164: 165: 166: 167: 168: 169: 170: 171:
172: protected function _countFaces(array $info)
173: {
174: $galleries = $GLOBALS['injector']
175: ->getInstance('Ansel_Storage')
176: ->listGalleries(array('perm' => Horde_Perms::READ));
177:
178: $ids = array();
179: foreach ($galleries as $gallery) {
180: $ids[] = $gallery->id;
181: }
182: $sql = 'SELECT COUNT(*) FROM ansel_faces f WHERE f.gallery_id IN ('
183: . implode(',', $ids) . ')';
184:
185: try {
186: return $GLOBALS['ansel_db']->selectValue($sql);
187: } catch (Horde_Db_Exception $e) {
188: throw new Ansel_Exception($e);
189: }
190: }
191:
192: 193: 194: 195: 196: 197: 198: 199:
200: public function allFaces($from = 0, $count = 0)
201: {
202: $info = array('order' => 'f.face_id DESC');
203: return $this->_fetchFaces($info, $from, $count);
204: }
205:
206: 207: 208: 209: 210: 211: 212: 213:
214: public function namedFaces($from = 0, $count = 0)
215: {
216: $info = array('filter' => 'f.face_name IS NOT NULL AND f.face_name <> \'\'');
217: return $this->_fetchFaces($info, $from, $count);
218: }
219:
220: 221: 222: 223: 224: 225: 226: 227: 228:
229: public function ownerFaces($owner, $from = 0, $count = 0)
230: {
231: $info = array(
232: 'filter' => 's.share_owner = ' . $GLOBALS['ansel_db']->quoteString($owner),
233: 'order' => 'f.face_id DESC'
234: );
235:
236: if (!$GLOBALS['registry']->getAuth() || $owner != $GLOBALS['registry']->getAuth()) {
237: $info['filter'] .= ' AND s.gallery_passwd IS NULL';
238: }
239:
240: return $this->_fetchFaces($info, $from, $count);
241: }
242:
243: 244: 245: 246: 247: 248: 249:
250: public function searchFaces($name, $from = 0, $count = 0)
251: {
252: $info = array('filter' => 'f.face_name LIKE ' . $GLOBALS['ansel_db']->quoteString("%$name%"));
253: return $this->_fetchFaces($info, $from, $count);
254: }
255:
256: 257: 258: 259: 260:
261: public function countOwnerFaces($owner)
262: {
263: $info = array('filter' => 's.share_owner = ' . $GLOBALS['ansel_db']->quoteString($owner));
264: if (!$GLOBALS['registry']->getAuth() || $owner != $GLOBALS['registry']->getAuth()) {
265: $info['filter'] .= ' AND s.gallery_passwd IS NULL';
266: }
267:
268: return $this->_countFaces($info);
269: }
270:
271: 272: 273:
274: public function countAllFaces()
275: {
276: return $this->_countFaces(array());
277: }
278:
279: 280: 281:
282: public function countNamedFaces()
283: {
284: $sql = 'SELECT COUNT(*) FROM ansel_faces WHERE face_name IS NOT NULL AND face_name <> \'\'';
285: return $GLOBALS['ansel_db']->selectValue($sql);
286: }
287:
288: 289: 290: 291: 292:
293: public function countSearchFaces($name)
294: {
295: $info = array('filter' => 'f.face_name LIKE ' . $GLOBALS['ansel_db']->quoteString("%$name%"));
296: return $this->_countFaces($info);
297: }
298:
299:
300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315:
316: public function viewExists($image_id, $face_id, $create = true)
317: {
318: $vfspath = Ansel_Faces::getVFSPath($image_id) . 'faces';
319: $vfsname = $face_id . Ansel_Faces::getExtension();
320: if (!$GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')->create('images')->exists($vfspath, $vfsname)) {
321: if (!$create) {
322: return false;
323: }
324: $data = $this->getFaceById($face_id, true);
325: $image = $GLOBALS['injector']
326: ->getInstance('Ansel_Storage')
327: ->getImage($image_id);
328:
329:
330: $this->createView(
331: $face_id,
332: $image,
333: $data['face_x1'],
334: $data['face_y1'],
335: $data['face_x2'],
336: $data['face_y2']);
337:
338: $this->saveSignature($image_id, $face_id);
339: }
340:
341: return true;
342: }
343:
344: 345: 346: 347: 348: 349: 350: 351:
352: public function getFaceImageObject($face_id)
353: {
354: $face = $this->getFaceById($face_id, true);
355:
356:
357: if (!$this->viewExists($face['image_id'], $face_id, true)) {
358: throw new Horde_Exception(sprintf("Unable to create or locate face_id %u", $face_id));
359: }
360: $vfspath = Ansel_Faces::getVFSPath($face['image_id']) . 'faces';
361: $vfsname = $face_id . Ansel_Faces::getExtension();
362: $img = Ansel::getImageObject();
363: try {
364: $data = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')
365: ->create('images')->read($vfspath, $vfsname);
366: } catch (Horde_Vfs_Exception $e) {
367: throw new Ansel_Exception($e);
368: }
369: $img->loadString($data);
370:
371: return $img;
372: }
373:
374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384:
385: public function getFaceUrl($image_id, $face_id, $full = false)
386: {
387: global $conf;
388:
389:
390:
391: if ($conf['vfs']['src'] != 'php') {
392: $this->viewExists($image_id, $face_id, true);
393: }
394:
395:
396: if ($conf['vfs']['src'] != 'direct') {
397: return Horde::url('faces/img.php', $full)->add('face', $face_id);
398: } else {
399: $path = substr(str_pad($image_id, 2, 0, STR_PAD_LEFT), -2) . '/faces';
400: return $GLOBALS['conf']['vfs']['path']
401: . htmlspecialchars($path . '/' . $face_id
402: . Ansel_Faces::getExtension());
403: }
404: }
405:
406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420:
421: public function saveCustomFace(
422: $face_id, $image_id, $x1, $y1, $x2, $y2, $name = '')
423: {
424: $image = $GLOBALS['injector']
425: ->getInstance('Ansel_Storage')
426: ->getImage($image_id);
427: $gallery = $GLOBALS['injector']
428: ->getInstance('Ansel_Storage')
429: ->getGallery($image->gallery);
430: if (!$gallery->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT)) {
431: throw new Horde_Exception_PermissionDenied('Access denied editing the photo.');
432: }
433:
434:
435: if (!empty($face_id)) {
436: $sql = 'UPDATE ansel_faces SET face_name = ?, face_x1 = ?, '
437: . 'face_y1 = ?, face_x2 = ?, face_y2 = ? WHERE face_id = ?';
438:
439: $params = array(
440: $name,
441: $x1,
442: $y1,
443: $x2,
444: $y2,
445: $face_id);
446:
447: try {
448: $GLOBALS['ansel_db']->update($sql, $params);
449: } catch (Horde_Db_Exception $e) {
450: throw new Ansel_Exception($e);
451: }
452: } else {
453: $sql = 'INSERT INTO ansel_faces (image_id, gallery_id, face_name, '
454: . ' face_x1, face_y1, face_x2, face_y2)'
455: . ' VALUES (?, ?, ?, ?, ?, ?, ?)';
456:
457: $params = array(
458: $image->id,
459: $image->gallery,
460: $name,
461: $x1,
462: $y1,
463: $x2,
464: $y2);
465:
466: try {
467: $face_id = $GLOBALS['ansel_db']->insert($sql, $params);
468: } catch (Horde_Db_Exception $e) {
469: throw new Ansel_Exception($e);
470: }
471: }
472:
473:
474: $this->createView(
475: $face_id,
476: $image,
477: $x1,
478: $y1,
479: $x2,
480: $y2);
481:
482:
483: try {
484: $GLOBALS['ansel_db']->update('UPDATE ansel_images SET image_faces = image_faces + 1 WHERE image_id = ' . $image->id);
485: $GLOBALS['ansel_db']->update('UPDATE ansel_shares SET attribute_faces = attribute_faces + 1 WHERE share_id = ' . $image->gallery);
486: } catch (Horde_Db_Exception $e) {
487: throw new Ansel_Exception($e);
488: }
489:
490:
491: $this->saveSignature($image->id, $face_id);
492:
493: return $face_id;
494: }
495:
496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506:
507: public function getFromPicture($image, $create = false)
508: {
509:
510: if (!($image instanceof Ansel_Image)) {
511: $image = $GLOBALS['injector']
512: ->getInstance('Ansel_Storage')
513: ->getImage($image);
514: $gallery = $GLOBALS['injector']
515: ->getInstance('Ansel_Storage')
516: ->getGallery($image->gallery);
517:
518: if (!$gallery->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT)) {
519: throw new Horde_Exception_PermissionDenied('Access denied editing the photo.');
520: }
521: }
522:
523:
524: $faces = $this->getFaces($image);
525: if (empty($faces)) {
526: return array();
527: }
528:
529:
530: Ansel_Faces::delete($image);
531:
532:
533: $fids = array();
534: foreach ($faces as $i => $rect) {
535:
536:
537: $sql = 'INSERT INTO ansel_faces (image_id, gallery_id, face_x1, '
538: . ' face_y1, face_x2, face_y2)'
539: . ' VALUES (?, ?, ?, ?, ?, ?)';
540:
541: $params = $this->_getParamsArray($image, $rect);
542: try {
543: $face_id = $GLOBALS['ansel_db']->insert($sql, $params);
544: } catch (Horde_Db_Exception $e) {
545: throw new Ansel_Exception($result);
546: }
547: if ($create) {
548: $this->_createView($face_id, $image, $rect);
549:
550: $image->reset();
551: $this->saveSignature($image->id, $face_id);
552: }
553: $fids[$face_id] = '';
554: }
555:
556:
557: try {
558: $GLOBALS['ansel_db']->update('UPDATE ansel_images SET image_faces = '
559: . count($fids) . ' WHERE image_id = ' . $image->id);
560: $GLOBALS['ansel_db']->update('UPDATE ansel_shares '
561: . 'SET attribute_faces = attribute_faces + ' . count($fids)
562: . ' WHERE share_id = ' . $image->gallery);
563: } catch (Horde_Db_Exception $e) {
564: throw new Ansel_Exception($e);
565: }
566:
567: if ($GLOBALS['conf']['ansel_cache']['usecache']) {
568: $GLOBALS['injector']->getInstance('Horde_Cache')
569: ->expire('Ansel_Gallery' . $gallery->id);
570: }
571:
572: return $fids;
573: }
574:
575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586:
587: public function createView($face_id, Ansel_Image $image, $x1, $y1, $x2, $y2)
588: {
589:
590: $image->load('screen');
591:
592:
593: try {
594: $image->crop($x1, $y1, $x2, $y2);
595: } catch (Horde_Image_Exception $e) {
596: throw new Ansel_Exception($e->getMessage());
597: }
598:
599:
600: $ext = Ansel_Faces::getExtension();
601: $path = Ansel_Faces::getVFSPath($image->id);
602: $image->resize(50, 50, false);
603: try {
604: $GLOBALS['injector']
605: ->getInstance('Horde_Core_Factory_Vfs')
606: ->create('images')
607: ->writeData(
608: $path . 'faces',
609: $face_id . $ext,
610: $image->raw(),
611: true);
612: } catch (Horde_Vfs_Exception $e) {
613: throw new Ansel_Exception($e);
614: }
615: }
616:
617: 618: 619: 620: 621: 622: 623: 624:
625: function saveSignature($image_id, $face_id)
626: {
627:
628: if (empty($GLOBALS['conf']['faces']['search']) ||
629: Horde_Util::loadExtension('libpuzzle') === false) {
630:
631: return;
632: }
633:
634:
635: $path = $GLOBALS['injector']
636: ->getInstance('Horde_Core_Factory_Vfs')
637: ->create('images')->readFile(
638: Ansel_Faces::getVFSPath($image_id) . '/faces',
639: $face_id . Ansel_Faces::getExtension());
640:
641: $signature = puzzle_fill_cvec_from_file($path);
642: if (empty($signature)) {
643: return;
644: }
645:
646: $sql = 'UPDATE ansel_faces SET face_signature = ? WHERE face_id = ?';
647: $params = array(new Horde_Db_Value_Binary(puzzle_compress_cvec($signature)), $face_id);
648: try {
649: $GLOBALS['ansel_db']->update($sql, $params);
650: } catch (Horde_Db_Exception $e) {
651: throw new Ansel_Exception($result);
652: }
653:
654:
655: $word_len = $GLOBALS['conf']['faces']['search'];
656: $str_len = strlen($signature);
657: $GLOBALS['ansel_db']->delete('DELETE FROM ansel_faces_index WHERE face_id = ' . $face_id);
658: $q = 'INSERT INTO ansel_faces_index (face_id, index_position, index_part) VALUES (?, ?, ?)';
659: $c = $str_len - $word_len;
660: for ($i = 0; $i <= $c; $i++) {
661: $data = array(
662: $face_id,
663: $i,
664: new Horde_Db_Value_Binary(substr($signature, $i, $word_len)));
665: try {
666: $GLOBALS['ansel_db']->insert($q, $data);
667: } catch (Horde_Db_Exception $e) {
668: throw new Ansel_Exception($e);
669: }
670: }
671: }
672:
673: 674: 675: 676: 677: 678: 679: 680:
681: public function getSignatureFromFile($filename)
682: {
683: if ($GLOBALS['conf']['faces']['search'] == 0 ||
684: Horde_Util::loadExtension('libpuzzle') === false) {
685:
686: return '';
687: }
688:
689: return puzzle_fill_cvec_from_file($filename);
690: }
691:
692: 693: 694: 695: 696: 697: 698: 699: 700: 701:
702: public function getFromGallery($gallery_id, $create = false, $force = false)
703: {
704: $gallery = $GLOBALS['injector']
705: ->getInstance('Ansel_Storage')
706: ->getGallery($gallery_id);
707: if (!$gallery->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT)) {
708: throw new Horde_Exception_PermissionDenied(sprintf("Access denied editing gallery \"%s\".", $gallery->get('name')));
709: }
710:
711: $images = $gallery->getImages();
712: $faces = array();
713: foreach ($images as $image) {
714: if ($image->facesCount && $force == false) {
715: continue;
716: }
717: $result = $this->getFromPicture($image, $create);
718: if (!empty($result)) {
719: $faces[$image->id] = $result;
720: }
721: unset($image);
722: }
723:
724: return $faces;
725: }
726:
727: 728: 729: 730: 731: 732: 733: 734:
735: public function setName($face, $name)
736: {
737: try {
738: return $GLOBALS['ansel_db']->update(
739: 'UPDATE ansel_faces SET face_name = ? WHERE face_id = ?',
740: array($name, $face));
741: } catch (Horde_Db_Exception $e) {
742: throw new Ansel_Exception($e);
743: }
744: }
745:
746: 747: 748: 749: 750: 751: 752: 753: 754:
755: public function getFaceById($face_id, $full = false)
756: {
757: $sql = 'SELECT face_id, image_id, gallery_id, face_name';
758: if ($full) {
759: $sql .= ', face_x1, face_y1, face_x2, face_y2, face_signature';
760: }
761: $sql .= ' FROM ansel_faces WHERE face_id = ?';
762: try {
763: $face = $GLOBALS['ansel_db']->selectOne($sql, array((int)$face_id));
764: } catch (Horde_Db_Exception $e) {
765: throw new Ansel_Exception($e);
766: }
767: if (empty($face)) {
768: throw new Ansel_Exception('Face does not exist');
769: }
770:
771: if ($full && $GLOBALS['conf']['faces']['search'] &&
772: function_exists('puzzle_uncompress_cvec')) {
773: $face['face_signature'] = puzzle_uncompress_cvec($face['face_signature']);
774: }
775:
776: if (empty($face['face_name'])) {
777: $face['galleries'][$face['gallery_id']][] = $face['image_id'];
778: return $face;
779: }
780:
781: $sql = 'SELECT gallery_id, image_id FROM ansel_faces WHERE face_name = ?';
782: try {
783: $galleries = $GLOBALS['ansel_db']->selectAll($sql, array($face['face_name']));
784: } catch (Horde_Db_Exception $e) {
785: throw new Ansel_Exception($e);
786: }
787: if (empty($galleries)) {
788: throw new Horde_Exception('Face does not exist');
789: }
790:
791: foreach ($galleries as $gallery) {
792: $face['galleries'][$gallery['gallery_id']][] = $gallery['image_id'];
793: }
794:
795: return $face;
796: }
797:
798: 799: 800: 801: 802: 803: 804: 805: 806: 807:
808: public function getSignatureMatches($signature, $face_id = 0, $from = 0, $count = 0)
809: {
810: $word_len = $GLOBALS['conf']['faces']['search'];
811: $str_len = strlen($signature);
812: $c = $str_len - $word_len;
813: $indexes = array();
814: for ($i = 0; $i <= $c; $i++) {
815: $sig = new Horde_Db_Value_Binary(substr($signature, $i, $word_len));
816: $indexes[] = '(index_position = ' . $i . ' AND index_part = ' . $sig->quote($GLOBALS['ansel_db']) . ')';
817: }
818:
819: $sql = 'SELECT i.face_id, f.face_name, '
820: . 'f.image_id, f.gallery_id, f.face_signature '
821: . 'FROM ansel_faces_index i, ansel_faces f '
822: . 'WHERE f.face_id = i.face_id';
823: if ($face_id) {
824: $sql .= ' AND i.face_id <> ' . (int)$face_id;
825: }
826: if ($indexes) {
827: $sql .= ' AND (' . implode(' OR ', $indexes) . ')';
828: }
829: $sql .= ' GROUP BY i.face_id, f.face_name, f.image_id, f.gallery_id, '
830: . 'f.face_signature HAVING count(i.face_id) > 0 '
831: . 'ORDER BY count(i.face_id) DESC';
832: $sql = $GLOBALS['ansel_db']->addLimitOffset(
833: $sql,
834: array(
835: 'limit' => $count,
836: 'offset' => $from
837: ));
838:
839: try {
840: $faces = $GLOBALS['ansel_db']->selectAll($sql);
841: } catch (Horde_Db_Exception $e) {
842: throw new Ansel_Exception($e);
843: }
844: if (empty($faces)) {
845: return array();
846: }
847:
848: foreach ($faces as &$face) {
849: $face['similarity'] = puzzle_vector_normalized_distance(
850: $signature,
851: puzzle_uncompress_cvec($face['face_signature']));
852: }
853: uasort($faces, array($this, '_getSignatureMatches'));
854:
855: return $faces;
856: }
857:
858: protected function _getParamsArray($image, $rect)
859: {
860: return array(
861: $image->id,
862: $image->gallery,
863: $rect['x'],
864: $rect['y'],
865: $rect['x'] + $rect['w'],
866: $rect['y'] + $rect['h']);
867: }
868:
869: 870: 871: 872: 873: 874:
875: protected function _getSignatureMatches($a, $b)
876: {
877: return $a['similarity'] > $b['similarity'];
878: }
879:
880: }
881: