issue #2560 people avatars on the photo page, highlight the face on the photo

This commit is contained in:
Linty committed 2026-10-29 17:17:37 +01:00
1 parent 91f25e98ab
commit 8e818c9af4
7 files changed
+261 -1

No files matched your search

+61
View File
@@ -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
+2
View File
@@ -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';
+2
View File
@@ -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';
+17 -1
View File
@@ -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
+89
View File
@@ -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;
}
+72
View File
@@ -0,0 +1,72 @@
jQuery(function($) {
const $avatars = $('.people-avatar[data-box]');
if (!$avatars.length) {
return;
}
const $frame = $('<div class="people-frame"><span class="people-frame-name"></span></div>').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);
});
+18
View File
@@ -215,6 +215,24 @@ y.callService(
</dd>
</div>
{/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'}
<div id="People" class="imageInfo">
<dt>{'People'|@translate}</dt>
<dd><div class="people-avatars">
{foreach from=$related_people item=person}
{if isset($person.face)}
<span class="people-avatar" tabindex="0" data-name="{$person.name|escape:html}" data-box="{$person.box_x1},{$person.box_y1},{$person.box_x2},{$person.box_y2}" data-width="{$person.image_width}" style="background-image:url('{$current.selected_derivative->get_url()}');background-size:{$person.face.size}% auto;background-position:{$person.face.x}% {$person.face.y}%;">
{else}
<span class="people-avatar people-avatar-empty">{$person.initial}
{/if}
<span class="people-tooltip"><b>{$person.name}</b>{if isset($person.face)}<i>{'Click to highlight on the photo'|@translate}</i>{/if}</span>
</span>
{/foreach}
</div></dd>
</div>
{/if}
{if $display_info.categories and isset($related_categories)}
<div id="Categories" class="imageInfo">
<dt>{'Albums'|@translate}</dt>