diff --git a/include/functions_people.inc.php b/include/functions_people.inc.php index 38288c5e2..11c3849c9 100644 --- a/include/functions_people.inc.php +++ b/include/functions_people.inc.php @@ -808,6 +808,67 @@ SELECT return query2array($query); } +/** + * People of an image shown in the gallery. + */ +function get_image_visible_people(int $image_id) +{ + $query = ' +SELECT + p.id, + p.name, + p.url_name, + ip.box_x1, + ip.box_y1, + ip.box_x2, + ip.box_y2, + ip.image_width, + ip.image_height + FROM '.IMAGE_PERSON_TABLE.' AS ip + INNER JOIN '.PERSONS_TABLE.' AS p ON p.id = ip.person_id + WHERE ip.image_id = '.$image_id.' + AND p.is_visible = \'true\' + ORDER BY p.name +;'; + + return query2array($query); +} + +/** + * Background size and position (%) showing the face of an image_person in a square. + */ +function get_person_face_background(array $image_person) +{ + if (!isset($image_person['box_x1']) or empty($image_person['image_width']) or empty($image_person['image_height'])) + { + return null; + } + + $side = min( + $image_person['box_x2'] - $image_person['box_x1'], + $image_person['box_y2'] - $image_person['box_y1'] + ); + if ($side <= 0) + { + return null; + } + + $position = function($center, $image_size) use ($side) + { + if ($image_size <= $side) + { + return 50; + } + return max(0, min(100, ($center - $side / 2) / ($image_size - $side) * 100)); + }; + + return array( + 'size' => sprintf('%.2F', $image_person['image_width'] / $side * 100), + 'x' => sprintf('%.2F', $position(($image_person['box_x1'] + $image_person['box_x2']) / 2, $image_person['image_width'])), + 'y' => sprintf('%.2F', $position(($image_person['box_y1'] + $image_person['box_y2']) / 2, $image_person['image_height'])), + ); +} + /** * Returns the images of a person, with the association fields, the thumbnail * url and the medium url (to crop the face). Admin view: photo permissions diff --git a/language/en_UK/common.lang.php b/language/en_UK/common.lang.php index 5b49bcc81..2df7d787a 100644 --- a/language/en_UK/common.lang.php +++ b/language/en_UK/common.lang.php @@ -534,3 +534,5 @@ $lang['If you changed your password because you think it was stolen, we recommen $lang['Too many attempts, please try later..'] = 'Too many attempts, please try later..'; $lang['Verification successful! You can now choose a new password.'] = 'Verification successful! You can now choose a new password.'; $lang['If your account exists, a verification code has been sent to your email address.'] = 'If your account exists, a verification code has been sent to your email address.'; +$lang['People'] = 'People'; +$lang['Click to highlight on the photo'] = 'Click to highlight on the photo'; diff --git a/language/fr_FR/common.lang.php b/language/fr_FR/common.lang.php index df58054ef..3a3e32faa 100644 --- a/language/fr_FR/common.lang.php +++ b/language/fr_FR/common.lang.php @@ -533,3 +533,5 @@ $lang['If you changed your password because you think it was stolen, we recommen $lang['Too many attempts, please try later..'] = 'Trop de tentatives, veuillez réessayer plus tard..'; $lang['Verification successful! You can now choose a new password.'] = 'Vérification réussie ! Vous pouvez maintenant choisir un nouveau mot de passe.'; $lang['If your account exists, a verification code has been sent to your email address.'] = 'Si votre compte existe, un code de vérification a été envoyé à votre adresse email.'; +$lang['People'] = 'Personnes'; +$lang['Click to highlight on the photo'] = 'Cliquer pour repérer sur la photo'; diff --git a/picture.php b/picture.php index 067ee6d48..d13a3e75f 100644 --- a/picture.php +++ b/picture.php @@ -892,7 +892,8 @@ $infos['INFO_VISITS'] = $picture['current']['hit']; $infos['INFO_FILE'] = $picture['current']['file']; $template->assign($infos); -$template->assign('display_info', unserialize($conf['picture_informations'])); +$display_info = unserialize($conf['picture_informations']); +$template->assign('display_info', $display_info); // related tags $tags = get_common_tags( array($page['image_id']), -1); @@ -921,6 +922,21 @@ if ( count($tags) ) } } +// related people +if (!empty($display_info['people']) and can_see_people()) +{ + $people = get_image_visible_people($page['image_id']); + if (count($people)) + { + foreach ($people as $i => $person) + { + $people[$i]['face'] = get_person_face_background($person); + $people[$i]['initial'] = mb_strtoupper(mb_substr($person['name'], 0, 1)); + } + $template->assign('related_people', $people); + } +} + // related categories if ( count($related_categories)==1 and isset($page['category']) and diff --git a/themes/default/css/people.css b/themes/default/css/people.css new file mode 100644 index 000000000..b2ad696d9 --- /dev/null +++ b/themes/default/css/people.css @@ -0,0 +1,89 @@ +.people-avatars { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 3px 0; +} + +.people-avatar { + position: relative; + display: inline-block; + width: 36px; + height: 36px; + border: 3px solid #ffffff; + border-radius: 50%; + box-shadow: 0 0 0 1px #999999; + background-color: #cccccc; + background-repeat: no-repeat; + cursor: pointer; +} + +.people-avatar:focus { + outline: none; +} + +.people-avatar:hover, +.people-avatar:focus, +.people-avatar-selected { + border-color: #FFA646; +} + +.people-avatar-empty { + cursor: default; + color: #555555; + font-weight: bold; + font-size: 16px; + line-height: 36px; + text-align: center; +} + +.people-avatar-empty:hover { + border-color: #ffffff; +} + +.people-tooltip { + display: none; + position: absolute; + z-index: 100; + top: calc(100% + 8px); + left: 0; + padding: 6px 10px; + background: #e0e0e0; + color: #222222; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); + line-height: 18px; + font-size: 13px; + white-space: nowrap; + text-align: left; +} + +.people-tooltip b, +.people-tooltip i { + display: block; +} + +.people-avatar:hover .people-tooltip, +.people-avatar:focus .people-tooltip { + display: block; +} + +.people-frame { + position: absolute; + z-index: 50; + border: 3px solid #FFA646; + box-sizing: border-box; + pointer-events: none; +} + +.people-frame-name { + position: absolute; + bottom: 100%; + left: -3px; + padding: 0 6px; + background: #FFA646; + color: #493C21; + font-weight: bold; + font-size: 13px; + line-height: 20px; + white-space: nowrap; +} diff --git a/themes/default/js/picture_people.js b/themes/default/js/picture_people.js new file mode 100644 index 000000000..ebbb9e027 --- /dev/null +++ b/themes/default/js/picture_people.js @@ -0,0 +1,72 @@ +jQuery(function($) { + const $avatars = $('.people-avatar[data-box]'); + if (!$avatars.length) { + return; + } + + const $frame = $('
').hide().appendTo('body'); + let $selected = null; + + function show_frame($avatar) { + const img = document.getElementById('theMainImage'); + if (!img || img.hasAttribute('data-src')) { + $frame.hide(); + return; + } + + const rect = img.getBoundingClientRect(); + if (!rect.width) { + $frame.hide(); + return; + } + + const box = String($avatar.data('box')).split(',').map(Number); + const scale = rect.width / Number($avatar.data('width')); + + $frame.css({ + left: rect.left + window.scrollX + box[0] * scale, + top: rect.top + window.scrollY + box[1] * scale, + width: (box[2] - box[0]) * scale, + height: (box[3] - box[1]) * scale, + }); + $frame.find('.people-frame-name').text($avatar.data('name')); + $frame.show(); + } + + function refresh_frame() { + if ($selected) { + show_frame($selected); + } + else { + $frame.hide(); + } + } + + $avatars + .on('mouseenter focus', function() { + show_frame($(this)); + }) + .on('mouseleave blur', refresh_frame) + .on('click keydown', function(e) { + if ('keydown' == e.type && 'Enter' != e.key && ' ' != e.key) { + return; + } + e.preventDefault(); + + const $avatar = $(this); + $avatars.removeClass('people-avatar-selected'); + + if ($selected && $selected.is($avatar)) { + $selected = null; + } + else { + $selected = $avatar; + $avatar.addClass('people-avatar-selected'); + } + + show_frame($avatar); + }); + + $(window).on('resize', refresh_frame); + $('#theMainImage').on('load', refresh_frame); +}); diff --git a/themes/default/template/picture.tpl b/themes/default/template/picture.tpl index 8473bbfd0..136e67726 100644 --- a/themes/default/template/picture.tpl +++ b/themes/default/template/picture.tpl @@ -215,6 +215,24 @@ y.callService( {/if} + {if isset($related_people)} + {combine_css path="themes/default/css/people.css"} + {combine_script id='picture_people' load='footer' require='jquery' path='themes/default/js/picture_people.js'} +
+
{'People'|@translate}
+
+ {foreach from=$related_people item=person} + {if isset($person.face)} + + {else} + {$person.initial} + {/if} + {$person.name}{if isset($person.face)}{'Click to highlight on the photo'|@translate}{/if} + + {/foreach} +
+
+ {/if} {if $display_info.categories and isset($related_categories)}
{'Albums'|@translate}