-
We have pictures of people in various aspect ratios and want to automatically create square avatars of them by "zooming" onto the faces. That's done (face detection by OpenCV with their pre-trained DNN) and works like a charm. But now we need to create the actual image transform that generates the zoomed version of the image. What I thought was the easy part doesn't look that easy after I scoured the CraftCMS source code for the right extension point. Option 1: Create a new
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
The custom image transformer functionality is half-baked at the moment. We’re planning to revisit and make the API simpler in a 4.x release (or 5.0 if it ends up involving breaking changes). In the meantime, Another thing to consider, though, is whether you need to override transform generation in the first place? If the only reason you’re doing it is because you want control over where the transform centers its crops, that’s what focal points are for. You could start setting those programmatically on the assets ahead of time, and Craft will respect them for all its native transforms (including control panel thumbnails). $myAsset->setFocalPoint(['x' => 0.35, 'y' => 0.25]);
$myAsset->setScenario(\craft\base\Element::SCENARIO_ESSENTIALS);
Craft::$app->elements->saveElement($myAsset); |
Beta Was this translation helpful? Give feedback.
-
As @brandonkelly suggested, I took the route to hook into For anyone stumbling onto this wanting to do the same: here is how this approximately looks like: Event::on(
Asset::class,
Asset::EVENT_BEFORE_GENERATE_TRANSFORM,
function (GenerateTransformEvent $event) {
if ($event->transform && preg_match('~^authorSquare(\d+)$~', $event->transform->handle ?? '')) {
$event->url = $this->getAuthorSquareAssetUrl($event->asset, $event->transform);
}
}
);
public function getAuthorSquareAssetUrl(Asset $asset, ImageTransform $transform): ?string
{
$match = [];
if (! preg_match('~^authorSquare(\d+)$~', $transform->handle ?? '', $match)) {
throw new \InvalidArgumentException('$transform needs to be a authorSquare transform');
}
$size = min(1024, max(24, intval($match[1])));
$fs = $asset->getVolume()->getTransformFs();
if (! $fs instanceof LocalFsInterface) {
throw new \InvalidArgumentException('$asset transformFs needs to be a local file system');
}
if (! in_array($asset->getMimeType(), ['image/png', 'image/jpeg'])) {
Craft::warning(
'tried to create an author crop for source image with mime type '.$asset->getMimeType().' ID: '.$asset->id,
__METHOD__
);
return null;
}
if (empty($transform->format)) {
$transform->format = 'jpg';
}
$subPath = $asset->getVolume()->transformSubpath;
$subPath = StringHelper::removeRight($subPath, '/');
$transformBasePath = ($subPath ? $subPath.DIRECTORY_SEPARATOR : '').$asset->folderPath;
$transformString = '_'.$transform->handle;
$transformFilename = $asset->getFilename(false).'.'.$transform->format;
$uri = str_replace('\\', '/', $transformBasePath).$transformString.DIRECTORY_SEPARATOR.$transformFilename;
$destPath = $fs->getRootPath().DIRECTORY_SEPARATOR.$uri;
$assetTimeStamp = $asset->dateUpdated->getTimestamp();
clearstatcache(true, $destPath);
if (! file_exists($destPath) || ($destTimeStamp = filemtime($destPath)) < $assetTimeStamp || filesize($destPath) < 1) {
$sourcePath = $asset->getImageTransformSourcePath();
$imageDimension = getimagesize($sourcePath);
$tmpPath = $this->cropAvatar($sourcePath, $asset, $size); // this does the actual transform creation
FileHelper::createDirectory(pathinfo($destPath, PATHINFO_DIRNAME));
rename($tmpPath, $destPath);
$destTimeStamp = filemtime($destPath);
// synthesise an ImageTransformIndex for our transform if it is missing
// needed for Craft::$app->getImageTransforms()->deleteCreatedTransformsForAsset($asset); to work
$existing = (new Query())->select([
'id',
'fileExists',
'inProgress',
'error',
'filename',
'format',
])->from(Table::IMAGETRANSFORMINDEX)->where([
'assetId' => $asset->id,
'format' => $transform->format,
'transformString' => $transformString,
])->one();
if (is_array($existing) && (! $existing['fileExists'] || $existing['inProgress'] || $existing['error'])) {
Db::delete(Table::IMAGETRANSFORMINDEX, ['id' => $existing['id']]);
$existing = null;
}
if (! $existing) {
Db::insert(Table::IMAGETRANSFORMINDEX, [
'assetId' => $asset->id,
'transformer' => ImageTransform::DEFAULT_TRANSFORMER,
'filename' => $transformFilename,
'format' => $transform->format,
'transformString' => $transformString,
'fileExists' => 1,
'inProgress' => 0,
'error' => 0,
]);
}
}
return UrlHelper::urlWithParams(
$fs->getRootUrl().$uri,
AssetsHelper::revParams($asset, new \DateTime('@'.$destTimeStamp)),
);
} |
Beta Was this translation helpful? Give feedback.
As @brandonkelly suggested, I took the route to hook into
Asset::EVENT_BEFORE_GENERATE_TRANSFORM
until using a custom ImageTransformer is feasible.For anyone stumbling onto this wanting to do the same: here is how this approximately looks like: