Overview

Packages

  • Ansel
  • None

Classes

  • Ansel
  • Ansel_Ajax_Application
  • Ansel_Ajax_Imple_EditCaption
  • Ansel_Ajax_Imple_EditFaces
  • Ansel_Ajax_Imple_EditGalleryFaces
  • Ansel_Ajax_Imple_Embed
  • Ansel_Ajax_Imple_GallerySlugCheck
  • Ansel_Ajax_Imple_ImageSaveGeotag
  • Ansel_Ajax_Imple_LocationAutoCompleter
  • Ansel_Ajax_Imple_MapLayerSelect
  • Ansel_Ajax_Imple_TagActions
  • Ansel_Ajax_Imple_ToggleGalleryActions
  • Ansel_Ajax_Imple_ToggleOtherGalleries
  • Ansel_Ajax_Imple_UploadNotification
  • Ansel_Api
  • Ansel_Exception
  • Ansel_Faces
  • Ansel_Faces_Base
  • Ansel_Faces_Facedetect
  • Ansel_Faces_User
  • Ansel_Factory_Faces
  • Ansel_Factory_Storage
  • Ansel_Factory_Styles
  • Ansel_Form_Ecard
  • Ansel_Form_Image
  • Ansel_Form_ImageDate
  • Ansel_Form_Upload
  • Ansel_Gallery
  • Ansel_Gallery_Decorator_Date
  • Ansel_GalleryMode_Base
  • Ansel_GalleryMode_Date
  • Ansel_GalleryMode_Normal
  • Ansel_Image
  • Ansel_ImageGenerator
  • Ansel_ImageGenerator_Mini
  • Ansel_ImageGenerator_PolaroidThumb
  • Ansel_ImageGenerator_PolaroidThumbStack
  • Ansel_ImageGenerator_RoundedThumb
  • Ansel_ImageGenerator_RoundedThumbStack
  • Ansel_ImageGenerator_Screen
  • Ansel_ImageGenerator_ShadowThumb
  • Ansel_ImageGenerator_ShadowThumbStack
  • Ansel_ImageGenerator_SquareThumb
  • Ansel_ImageGenerator_Thumb
  • Ansel_LoginTasks_SystemTask_Upgrade
  • Ansel_Report
  • Ansel_Report_letter
  • Ansel_Report_mail
  • Ansel_Report_tickets
  • Ansel_Search
  • Ansel_Search_exif
  • Ansel_Search_Tag
  • Ansel_Storage
  • Ansel_Style
  • Ansel_Tagger
  • Ansel_Test
  • Ansel_Tile_DateGallery
  • Ansel_Tile_Gallery
  • Ansel_Tile_Image
  • Ansel_View_Ansel
  • Ansel_View_Base
  • Ansel_View_EmbeddedRenderer_GalleryLink
  • Ansel_View_EmbeddedRenderer_Mini
  • Ansel_View_EmbeddedRenderer_Slideshow
  • Ansel_View_Gallery
  • Ansel_View_GalleryProperties
  • Ansel_View_GalleryRenderer_Base
  • Ansel_View_GalleryRenderer_Gallery
  • Ansel_View_GalleryRenderer_GalleryLightbox
  • Ansel_View_Image
  • Ansel_View_List
  • Ansel_View_Results
  • Ansel_View_Slideshow
  • Ansel_View_Upload
  • Ansel_Widget
  • Ansel_Widget_Actions
  • Ansel_Widget_Base
  • Ansel_Widget_GalleryFaces
  • Ansel_Widget_Geotag
  • Ansel_Widget_ImageFaces
  • Ansel_Widget_Links
  • Ansel_Widget_OtherGalleries
  • Ansel_Widget_OwnerFaces
  • Ansel_Widget_SimilarPhotos
  • Ansel_Widget_Tags
  • Ansel_XPPublisher
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Face recognition class
  4:  *
  5:  * Copyright 2007-2012 Horde LLC (http://www.horde.org/)
  6:  *
  7:  * See the enclosed file COPYING for license information (GPL). If you
  8:  * did not receive this file, see http://www.horde.org/licenses/gpl.
  9:  * @author  Duck <duck@obala.net>
 10:  * @category Horde
 11:  * @license  http://www.horde.org/licenses/gpl GPL
 12:  * @package  Ansel
 13:  */
 14: class Ansel_Faces_Base
 15: {
 16:     /**
 17:      */
 18:     public function canAutogenerate()
 19:     {
 20:         return false;
 21:     }
 22: 
 23:     /**
 24:      * Get faces
 25:      *
 26:      * @param string $file Picture filename
 27:      */
 28:     protected function _getFaces($file)
 29:     {
 30:         return array();
 31:     }
 32: 
 33:     /**
 34:      * Get all the coordinates for faces in an image.
 35:      *
 36:      * @param mixed $image  The Ansel_Image or a path to the image to check.
 37:      *
 38:      * @return mixed  Array of face data
 39:      */
 40:     public function getFaces(Ansel_Image $image)
 41:     {
 42:         if ($image instanceof Ansel_Image) {
 43:             // First check if screen view exists
 44:             $image->load('screen');
 45: 
 46:             // Make sure we have an on-disk copy of the file.
 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:         // Get faces from driver
 61:         $faces = $this->_getFaces($file);
 62:         if (empty($faces)) {
 63:             return array();
 64:         }
 65: 
 66:         // Remove faces containg faces
 67:         // for example when 2 are together we can have 3 faces
 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:      * Get existing faces data from storage for the given image.
 80:      *
 81:      * Used if we need to build the face image at some point after it is
 82:      * detected.
 83:      *
 84:      * @param integer $image_id  The image_id of the Ansel_Image these faces are
 85:      *                           for.
 86:      * @param boolean $full      Get full face data or just face_id and
 87:      *                           face_name.
 88:      *
 89:      * @return array  An array of faces data.
 90:      * @throws Ansel_Exception
 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:      * Get existing faces data for an entire gallery.
110:      *
111:      * @param integer $gallery_id  gallery_id to get data for.
112:      *
113:      * @return array  An array of faces data.
114:      * @throws Ansel_Exception
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:      * Fetchs all faces from all galleries the current user has READ access to
130:      *
131:      * @param array $info     Array of select criteria
132:      * @param integer $from   Offset
133:      * @param integer $count  Limit
134:      *
135:      * @return mixed  An array of face hashes containing face_id, gallery_id,
136:      *                image_id, face_name.
137:      *
138:      * @throws Ansel_Exception
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:      * Count faces
166:      *
167:      * @param array $info Array of select criteria
168:      *
169:      * @return integer  The count of faces
170:      * @throws Ansel_Exception
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:      * Get all faces
194:      *
195:      * @param integer $from Offset
196:      * @param integer $count Limit
197:      *
198:      * @return array  Array of face hashes.
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:      * Get named faces
208:      *
209:      * @param integer $from Offset
210:      * @param integer $count Limit
211:      *
212:      * @return array An array of face hashes
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:      * Get faces owned by user
222:      *
223:      * @param string  $owner User
224:      * @param integer $from Offset
225:      * @param integer $count Limit
226:      *
227:      * @return array  An array of face hashes.
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:      * Seach faces for a name
245:      *
246:      * @param string  $name   Search string
247:      * @param integer $from   Offset
248:      * @param integer $count  Limit
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:      * Get faces owned by owner
258:      *
259:      * @param string  $owner User
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:      * Count all faces
273:      */
274:     public function countAllFaces()
275:     {
276:         return $this->_countFaces(array());
277:     }
278: 
279:     /**
280:      * Get named faces
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:      * Seach faces for a name
290:      *
291:      * @param string  $name Search string
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:      * Checks to see that a given face image exists in the VFS.
302:      *
303:      * If $create is true, the image is created if it does not
304:      * exist. Otherwise false is returned if the image does not exist. True is
305:      * returned both if the image already existed OR if it did not exist, but
306:      * was successfully created.
307:      *
308:      * @param integer $image_id  The image_id the face belongs to.
309:      * @param integer $face_id   The face_id we are checking for.
310:      * @param boolean $create    Automatically create the image if it is not
311:      *                           found.
312:      *
313:      * @return boolean  True if image exists at end of function call, false
314:      *                  otherwise.
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:             // Actually create the image.
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:      * Get a Horde_Image object representing the requested face.
346:      *
347:      * @param integer $face_id  The requested face_id
348:      *
349:      * @return Horde_Image  The requested Horde_Image object
350:      * @throws Ansel_Exception
351:      */
352:     public function getFaceImageObject($face_id)
353:     {
354:         $face = $this->getFaceById($face_id, true);
355: 
356:         // Load the image for this face
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:      * Get a URL for a face image suitable for using as the src attribute in an
376:      * image tag.
377:      *
378:      * @param integer $image_id  Image ID to get url for
379:      * @param integer $face_id   Face ID to get url for
380:      * @param boolean $full      Should we generate a full URL?
381:      *
382:      * @return string  The URL for the face image suitable for use as the src
383:      *                 attribute in an <img> tag.
384:      */
385:     public function getFaceUrl($image_id, $face_id, $full = false)
386:     {
387:         global $conf;
388: 
389:         // If we won't be using img.php to generate it, make sure the image
390:         // is generated before returning a url to access it.
391:         if ($conf['vfs']['src'] != 'php') {
392:             $this->viewExists($image_id, $face_id, true);
393:         }
394: 
395:         // If not viewing directly out of the VFS, hand off to img.php
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:      * Associates a given rectangle with the given image and creates the face
408:      * image. Used for setting a face range explicitly.
409:      *
410:      * @param integer $face_id   Face id to save
411:      * @param integer $image_id  Image face belongs to
412:      * @param integer $x1        The top left corner of the cropped image.
413:      * @param integer $y1        The top right corner of the cropped image.
414:      * @param integer $x2        The bottom left corner of the cropped image.
415:      * @param integer $y2        The bottom right corner of the cropped image.
416:      * @param string  $name      Face name
417:      *
418:      * @return array Faces found
419:      * @throws Ansel_Exception, Horde_Exception_PermissionDenied
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:         // Store face id db
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:         // Process the image
474:         $this->createView(
475:             $face_id,
476:             $image,
477:             $x1,
478:             $y1,
479:             $x2,
480:             $y2);
481: 
482:         // Update gallery and image counts
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:         // Save signature
491:         $this->saveSignature($image->id, $face_id);
492: 
493:         return $face_id;
494:     }
495: 
496:     /**
497:      * Look for and save faces in a picture, and optionally create the face
498:      * image.
499:      *
500:      * @param mixed $image Image Object/ID to check
501:      * @param boolen $create Create images or store data?
502:      *
503:      * @return array Faces found
504:      * @throws Horde_Exception_PermissionDenied
505:      * @throws Ansel_Exception
506:      */
507:     public function getFromPicture($image, $create = false)
508:     {
509:         // get image if ID is passed
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:         // Get the rectangles for any faces in this image.
524:         $faces = $this->getFaces($image);
525:         if (empty($faces)) {
526:             return array();
527:         }
528: 
529:         // Clean up any existing faces we may have had in this image.
530:         Ansel_Faces::delete($image);
531: 
532:         // Process faces
533:         $fids = array();
534:         foreach ($faces as $i => $rect) {
535: 
536:             // Store face id db
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:                 // Clear any loaded views to save on memory usage.
550:                 $image->reset();
551:                 $this->saveSignature($image->id, $face_id);
552:             }
553:             $fids[$face_id] = '';
554:         }
555: 
556:         // Update gallery and image counts
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:         // Expire gallery cache
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:      * Create a face image from the given data.
577:      *
578:      * @param integer $face_id    Face id to generate
579:      * @param Ansel_Image $image  Image face belongs to
580:      * @param integer $x1         The top left corner of the cropped image.
581:      * @param integer $y1         The top right corner of the cropped image.
582:      * @param integer $x2         The bottom left corner of the cropped image.
583:      * @param integer $y2         The bottom right corner of the cropped image.
584:      *
585:      * @throws Ansel_Exception
586:      */
587:     public function createView($face_id, Ansel_Image $image, $x1, $y1, $x2, $y2)
588:     {
589:         // Make sure the image data is fresh
590:         $image->load('screen');
591: 
592:         // Crop to the face
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:         // Resize and save
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:      * Get face signature from an existing face image.
619:      *
620:      * @param integer $image_id Image ID face belongs to
621:      * @param integer $face_id Face ID to check
622:      *
623:      * @throws Ansel_Exception
624:      */
625:     function saveSignature($image_id, $face_id)
626:     {
627:         // can we get it?
628:         if (empty($GLOBALS['conf']['faces']['search']) ||
629:             Horde_Util::loadExtension('libpuzzle') === false) {
630: 
631:             return;
632:         }
633: 
634:         // Ensure we have an on-disk file to read the signature from.
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:         // save compressed signature
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:         // create index
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:      * Get an image signature from an arbitrary file. Currently used when
675:      * searching for faces that appear in a user-supplied image.
676:      *
677:      * @param integer $filename Image filename to check
678:      *
679:      * @return binary vector signature
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:      * Get faces for all images in a gallery
694:      *
695:      * @param integer $gallery_id  The share_id/gallery_id of the gallery to
696:      *                             check.
697:      * @param boolen $create       Create faces and signatures or just store coordniates?
698:      * @param boolen $force Force recreation even if image has faces
699:      *
700:      * @return array Faces found
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:      * Set face name
729:      *
730:      * @param integer $face  Face id
731:      * @param string $name  Face name
732:      *
733:      * @throws Ansel_Exception
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:      * Get face data
748:      *
749:      * @param integer $face_id  Face id
750:      * @param boolean $full     Retreive full face data?
751:      *
752:      * @return array  A face information hash
753:      * @throws Ansel_Exception
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:      * Get possible matches from sql index
800:      *
801:      * @param binary $signature Image signature
802:      * @param integer $from Offset
803:      * @param integer $count Limit
804:      *
805:      * @return binary vector signature
806:      * @throws Ansel_Exception
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:      * Compare faces by similarity.
871:      *
872:      * @param array $a
873:      * @param array $b
874:      */
875:     protected function _getSignatureMatches($a, $b)
876:     {
877:         return $a['similarity'] > $b['similarity'];
878:     }
879: 
880: }
881: 
API documentation generated by ApiGen