The Center of Interest (COI) is only ever applied server-side, inside i.php, and only for photo sizes that have crop enabled (max_crop > 0). Every other kind of cropping in Piwigo — and today that is most of what users actually see — happens client-side in the theme's CSS, where the COI is not available. The result is that a user sets a center of interest, the admin preview confirms it works, and the gallery still shows a centred crop.
This is not a rendering detail: for portrait photos in a square or fixed-ratio grid it is the difference between a face and a torso.
Concrete case
Photo sizes: only Square has crop enabled — which is the default. Thumbnail is IMG_THUMB => SizingParams::classic(144,144), i.e. max_crop = 0.
Set a COI on a portrait photo covering the person's head and upper body.
Admin → photo → Center of interest: the Photo sizes with crop preview shows the square derivative cropped correctly around the head. The feature demonstrably works.
The gallery index uses the Thumbnail size in a fixed-ratio grid. SizingParams::compute() skips the crop block entirely (if ($this->max_crop > 0)), so the derivative is the full 108×144 photo. The theme then crops it to a square tile with object-fit: cover — centred. Head cut off.
The admin page literally promises "For photo sizes with crop, such as 'Square', Piwigo will do its best to include the center of interest." Users reasonably read that as applying to the thumbnails they see, and there is no way for a theme to keep that promise.
Why themes can't fix this themselves today
grep -w coi over master returns hits in only four files: admin/picture_coi.php, i.php, include/derivative_params.inc.php and the schema migration. The template layer is not among them.
SrcImage drops the COI. SrcImage::__construct($infos) reads id, path, file, representative_ext, width, height, rotation — but not coi, and the class has no property for it. SrcImage is exactly the object themes are handed ($thumbnail.src_image, $pwg->derivative(...)), so the canonical path loses the information.
What does leak out is accidental and unusable. include/category_default.inc.php builds its template var with array_merge($row, array(...)) on a SELECT *, so {$thumbnail.coi} happens to contain the raw 4-character string ("aazu"). Same for album representatives via the SELECT * in include/category_cats.inc.php. That is an undocumented side effect of the query shape, it breaks the moment the SELECT is narrowed, and it is useless from Smarty anyway: decoding a–z into fractions needs char_to_fraction(), i.e. PHP.
So themes reimplement the core algorithm or give up. Modus re-derives the crop in its own themeconf.inc.php by instantiating ImageRect and calling crop_h() / crop_v() with $item['representative']['coi']. Bootstrap Darkroom, GdThumb and others simply crop centred. Honouring the COI should not be a burden placed on every theme author.
Make the COI a first-class, decoded value in the template layer. Nothing about the crop math or i.php changes; this is purely additive.
Carry the COI on SrcImage.
/** @var string|null 4-char encoded center of interest */public$coi=null;// in __construct():
$this->coi=isset($infos['coi'])?$infos['coi']:null;
Every existing consumer already passes the full image row into SrcImage, so this costs nothing. Note that SrcImage already normalises rotation, so it is the right place to eventually make the COI rotation-aware too.
Ship a helper that turns the COI into a CSS value.
/**
* Returns the center of the COI as a CSS object-position value, e.g. "38% 22%".
* Falls back to "50% 50%" when no COI is defined.
*/functionget_coi_object_position($coi){if(empty($coi))return'50% 50%';$x=50*(char_to_fraction($coi[0])+char_to_fraction($coi[2]));$y=50*(char_to_fraction($coi[1])+char_to_fraction($coi[3]));returnround($x).'% '.round($y).'%';}
Assign it, plus the decoded rectangle, as documented template variables — for index thumbnails (include/category_default.inc.php), album representatives (include/category_cats.inc.php) and the picture page:
'COI_POSITION'=>get_coi_object_position($row['coi']),// "38% 22%"
'coi'=>decode_coi($row['coi']),// array l,t,r,b as floats, or null
A theme then honours the COI with a single attribute:
and themes doing their own layout maths get the decoded rectangle instead of having to reimplement char_to_fraction().
Same value in the web API. Add the COI (decoded, and/or as an object_position string) to the image objects returned by pwg.categories.getImages and pwg.images.getInfo, so JS-driven layouts (masonry, carousels, the mobile apps) can use it as well. Without this, exposing it only to Smarty fixes half the themes.
Notes
Backwards compatible. No change to i.php, SizingParams::compute() or the crop rectangle computed for existing derivatives; no cached files need to be regenerated. Themes that ignore the new variables behave exactly as before.
COI_POSITION is deliberately the COI centre, not the rectangle, because that is what CSS cropping can express. object-position cannot guarantee a whole region stays visible — but centring on the COI is strictly better than the current unconditional centre of the frame, and it is what every other gallery/CMS with a focal point does.
Defaulting to 50% 50% when coi IS NULL means themes can apply the attribute unconditionally.
If this lands, the admin help text should be reworded: the COI would then influence all cropped presentations, not only "photo sizes with crop".
- Type: feature request
- Component: derivatives / template layer
- Version checked: master (17.0.0beta1)
Problem
The Center of Interest (COI) is only ever applied server-side, inside i.php, and only for photo sizes that have crop enabled (max_crop > 0). Every other kind of cropping in Piwigo — and today that is most of what users actually see — happens client-side in the theme's CSS, where the COI is not available. The result is that a user sets a center of interest, the admin preview confirms it works, and the gallery still shows a centred crop.
This is not a rendering detail: for portrait photos in a square or fixed-ratio grid it is the difference between a face and a torso.
Concrete case
Photo sizes: only Square has crop enabled — which is the default. Thumbnail is IMG_THUMB => SizingParams::classic(144,144), i.e. max_crop = 0.
Set a COI on a portrait photo covering the person's head and upper body.
Admin → photo → Center of interest: the Photo sizes with crop preview shows the square derivative cropped correctly around the head. The feature demonstrably works.
The gallery index uses the Thumbnail size in a fixed-ratio grid. SizingParams::compute() skips the crop block entirely (if ($this->max_crop > 0)), so the derivative is the full 108×144 photo. The theme then crops it to a square tile with object-fit: cover — centred. Head cut off.
The admin page literally promises "For photo sizes with crop, such as 'Square', Piwigo will do its best to include the center of interest." Users reasonably read that as applying to the thumbnails they see, and there is no way for a theme to keep that promise.
Why themes can't fix this themselves today
grep -w coi over master returns hits in only four files: admin/picture_coi.php, i.php, include/derivative_params.inc.php and the schema migration. The template layer is not among them.
SrcImage drops the COI. SrcImage::__construct($infos) reads id, path, file, representative_ext, width, height, rotation — but not coi, and the class has no property for it. SrcImage is exactly the object themes are handed ($thumbnail.src_image, $pwg->derivative(...)), so the canonical path loses the information.
What does leak out is accidental and unusable. include/category_default.inc.php builds its template var with array_merge($row, array(...)) on a SELECT *, so {$thumbnail.coi} happens to contain the raw 4-character string ("aazu"). Same for album representatives via the SELECT * in include/category_cats.inc.php. That is an undocumented side effect of the query shape, it breaks the moment the SELECT is narrowed, and it is useless from Smarty anyway: decoding a–z into fractions needs char_to_fraction(), i.e. PHP.
So themes reimplement the core algorithm or give up. Modus re-derives the crop in its own themeconf.inc.php by instantiating ImageRect and calling crop_h() / crop_v() with $item['representative']['coi']. Bootstrap Darkroom, GdThumb and others simply crop centred. Honouring the COI should not be a burden placed on every theme author.
Recurring reports, all the same root cause: https://piwigo.org/forum/viewtopic.php?id=31432 (Bootstrap Darkroom), https://piwigo.org/forum/viewtopic.php?id=25646 (GdThumb), https://piwigo.org/forum/viewtopic.php?id=24508 (carousel).
Proposal
Make the COI a first-class, decoded value in the template layer. Nothing about the crop math or i.php changes; this is purely additive.
1. Carry the COI on SrcImage.
```php
/** @var string|null 4-char encoded center of interest */
public $coi = null;
// in __construct():
$this->coi = isset($infos['coi']) ? $infos['coi'] : null;
```
Every existing consumer already passes the full image row into SrcImage, so this costs nothing. Note that SrcImage already normalises rotation, so it is the right place to eventually make the COI rotation-aware too.
2. Ship a helper that turns the COI into a CSS value.
```php
/**
* Returns the center of the COI as a CSS object-position value, e.g. "38% 22%".
* Falls back to "50% 50%" when no COI is defined.
*/
function get_coi_object_position($coi)
{
if (empty($coi))
return '50% 50%';
$x = 50 * (char_to_fraction($coi[0]) + char_to_fraction($coi[2]));
$y = 50 * (char_to_fraction($coi[1]) + char_to_fraction($coi[3]));
return round($x).'% '.round($y).'%';
}
```
3. Assign it, plus the decoded rectangle, as documented template variables — for index thumbnails (include/category_default.inc.php), album representatives (include/category_cats.inc.php) and the picture page:
```php
'COI_POSITION' => get_coi_object_position($row['coi']), // "38% 22%"
'coi' => decode_coi($row['coi']), // array l,t,r,b as floats, or null
```
A theme then honours the COI with a single attribute:
```smarty
<img src="{$derivative->get_url()}" alt="{$thumbnail.TN_ALT}"
style="object-fit:cover;object-position:{$thumbnail.COI_POSITION}">
```
and themes doing their own layout maths get the decoded rectangle instead of having to reimplement char_to_fraction().
4. Same value in the web API. Add the COI (decoded, and/or as an object_position string) to the image objects returned by pwg.categories.getImages and pwg.images.getInfo, so JS-driven layouts (masonry, carousels, the mobile apps) can use it as well. Without this, exposing it only to Smarty fixes half the themes.
Notes
Backwards compatible. No change to i.php, SizingParams::compute() or the crop rectangle computed for existing derivatives; no cached files need to be regenerated. Themes that ignore the new variables behave exactly as before.
COI_POSITION is deliberately the COI centre, not the rectangle, because that is what CSS cropping can express. object-position cannot guarantee a whole region stays visible — but centring on the COI is strictly better than the current unconditional centre of the frame, and it is what every other gallery/CMS with a focal point does.
Defaulting to 50% 50% when coi IS NULL means themes can apply the attribute unconditionally.
If this lands, the admin help text should be reworded: the COI would then influence all cropped presentations, not only "photo sizes with crop".
<!-- github-sync-id:5034068201 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
The Center of Interest (COI) is only ever applied server-side, inside i.php, and only for photo sizes that have crop enabled (max_crop > 0). Every other kind of cropping in Piwigo — and today that is most of what users actually see — happens client-side in the theme's CSS, where the COI is not available. The result is that a user sets a center of interest, the admin preview confirms it works, and the gallery still shows a centred crop.
This is not a rendering detail: for portrait photos in a square or fixed-ratio grid it is the difference between a face and a torso.
Concrete case
Photo sizes: only Square has crop enabled — which is the default. Thumbnail is IMG_THUMB => SizingParams::classic(144,144), i.e. max_crop = 0.
Set a COI on a portrait photo covering the person's head and upper body.
Admin → photo → Center of interest: the Photo sizes with crop preview shows the square derivative cropped correctly around the head. The feature demonstrably works.
The gallery index uses the Thumbnail size in a fixed-ratio grid. SizingParams::compute() skips the crop block entirely (if ($this->max_crop > 0)), so the derivative is the full 108×144 photo. The theme then crops it to a square tile with object-fit: cover — centred. Head cut off.
The admin page literally promises "For photo sizes with crop, such as 'Square', Piwigo will do its best to include the center of interest." Users reasonably read that as applying to the thumbnails they see, and there is no way for a theme to keep that promise.
Why themes can't fix this themselves today
grep -w coi over master returns hits in only four files: admin/picture_coi.php, i.php, include/derivative_params.inc.php and the schema migration. The template layer is not among them.
SrcImage drops the COI. SrcImage::__construct($infos) reads id, path, file, representative_ext, width, height, rotation — but not coi, and the class has no property for it. SrcImage is exactly the object themes are handed ($thumbnail.src_image, $pwg->derivative(...)), so the canonical path loses the information.
What does leak out is accidental and unusable. include/category_default.inc.php builds its template var with array_merge($row, array(...)) on a SELECT *, so {$thumbnail.coi} happens to contain the raw 4-character string ("aazu"). Same for album representatives via the SELECT * in include/category_cats.inc.php. That is an undocumented side effect of the query shape, it breaks the moment the SELECT is narrowed, and it is useless from Smarty anyway: decoding a–z into fractions needs char_to_fraction(), i.e. PHP.
So themes reimplement the core algorithm or give up. Modus re-derives the crop in its own themeconf.inc.php by instantiating ImageRect and calling crop_h() / crop_v() with $item['representative']['coi']. Bootstrap Darkroom, GdThumb and others simply crop centred. Honouring the COI should not be a burden placed on every theme author.
Recurring reports, all the same root cause: https://piwigo.org/forum/viewtopic.php?id=31432 (Bootstrap Darkroom), https://piwigo.org/forum/viewtopic.php?id=25646 (GdThumb), https://piwigo.org/forum/viewtopic.php?id=24508 (carousel).
Proposal
Make the COI a first-class, decoded value in the template layer. Nothing about the crop math or i.php changes; this is purely additive.
Every existing consumer already passes the full image row into SrcImage, so this costs nothing. Note that SrcImage already normalises rotation, so it is the right place to eventually make the COI rotation-aware too.
A theme then honours the COI with a single attribute:
and themes doing their own layout maths get the decoded rectangle instead of having to reimplement char_to_fraction().
Notes
Backwards compatible. No change to i.php, SizingParams::compute() or the crop rectangle computed for existing derivatives; no cached files need to be regenerated. Themes that ignore the new variables behave exactly as before.
COI_POSITION is deliberately the COI centre, not the rectangle, because that is what CSS cropping can express. object-position cannot guarantee a whole region stays visible — but centring on the COI is strictly better than the current unconditional centre of the frame, and it is what every other gallery/CMS with a focal point does.
Defaulting to 50% 50% when coi IS NULL means themes can apply the attribute unconditionally.
If this lands, the admin help text should be reworded: the COI would then influence all cropped presentations, not only "photo sizes with crop".