Gets one transformed image based on source image and transform

@param LocalSourceImageModel $sourceModel
@param array                 $transform

@return CraftTransformedImageModel|null

@throws ImagerException
4026
private function getTransformedImage($sourceModel, $transform)
    {
        /** @var ConfigModel $settings */
        $config = ImagerService::getConfig();

        if ($config->getSetting('noop', $transform)) {
            // todo : just return source image unmodified
        }

        if ($this->imagineInstance === null) {
            $msg = Craft::t('imager', 'Imagine instance was not created for driver “{driver}”.', ['driver' => ImagerService::$imageDriver]);
            Craft::error($msg, __METHOD__);
            throw new ImagerException($msg);
        }

        // Create target model
        $targetModel = new LocalTargetImageModel($sourceModel, $transform);

        // Set save options
        $saveOptions = $this->getSaveOptions($targetModel->extension, $transform);

        // Do transform if transform doesn't exist, cache is disabled, or cache expired
        if (!$config->getSetting('cacheEnabled', $transform) ||
            !file_exists($targetModel->getFilePath()) ||
            (($config->getSetting('cacheDuration', $transform) !== false) && (FileHelper::lastModifiedTime($targetModel->getFilePath()) + $config->getSetting('cacheDuration', $transform) < time()))
        ) {
            // Make sure that we have a local copy.
            $sourceModel->getLocalCopy();

            // Check all the things that could go wrong(tm)
            if (!realpath($sourceModel->path)) {
                $msg = Craft::t('imager', 'Source folder “{sourcePath}” does not exist', ['sourcePath' => $sourceModel->path]);
                Craft::error($msg, __METHOD__);
                throw new ImagerException($msg);
            }

            if (!realpath($targetModel->path)) {
                try {
                    FileHelper::createDirectory($targetModel->path);
                } catch (Exception $e) {
                    // ignore for now, trying to create
                }

                if (!realpath($targetModel->path)) {
                    $msg = Craft::t('imager', 'Target folder “{targetPath}” does not exist and could not be created', ['targetPath' => $targetModel->path]);
                    Craft::error($msg, __METHOD__);
                    throw new ImagerException($msg);
                }

                $targetModel->path = realpath($targetModel->path);
            }

            try {
                $targetPathIsWriteable = FileHelper::isWritable($targetModel->path);
            } catch (ErrorException $e) {
                $targetPathIsWriteable = false;
            }

            if ($targetModel->path && !$targetPathIsWriteable) {
                $msg = Craft::t('imager', 'Target folder “{targetPath}” is not writeable', ['targetPath' => $targetModel->path]);
                Craft::error($msg, __METHOD__);
                throw new ImagerException($msg);
            }

            if (!file_exists($sourceModel->getFilePath())) {
                $msg = Craft::t('imager', 'Requested image “{fileName}” does not exist in path “{sourcePath}”', ['fileName' => $sourceModel->filename, 'sourcePath' => $sourceModel->path]);
                Craft::error($msg, __METHOD__);
                throw new ImagerException($msg);
            }

            if (!Craft::$app->images->checkMemoryForImage($sourceModel->getFilePath())) {
                $msg = Craft::t('imager', 'Not enough memory available to perform this image operation.');
                Craft::error($msg, __METHOD__);
                throw new ImagerException($msg);
            }

            // Create the imageInstance. only once if reuse is enabled, or always
            if ($this->imageInstance === null || !$config->getSetting('instanceReuseEnabled', $transform)) {
                $this->imageInstance = $this->imagineInstance->open($sourceModel->getFilePath());
            }

            $animated = false;

            // Check if this is an animated gif and we're using Imagick
            if ($sourceModel->extension === 'gif' && ImagerService::$imageDriver !== 'gd' && $this->imageInstance->layers()) {
                $animated = true;
            }

            // Run tranforms, either on each layer of an animated gif, or on the whole image.
            if ($animated) {
                if ($this->imageInstance->layers()) {
                    $this->imageInstance->layers()->coalesce();
                }

                // We need to create a new image instance with the target size, or letterboxing will be wrong.
                $originalSize = $this->imageInstance->getSize();
                $resizeSize = ImagerHelpers::getResizeSize($originalSize, $transform, $config->getSetting('allowUpscale', $transform));
                $layers = $this->imageInstance->layers() ?? [];
                $gif = $this->imagineInstance->create($resizeSize);

                if ($gif->layers()) {
                    $gif->layers()->remove(0);
                }

                list($startFrame, $endFrame, $interval) = $this->getFramesVars($layers, $transform);

                for ($i = $startFrame; $i <= $endFrame; $i += $interval) {
                    if (isset($layers[$i])) {
                        $layer = $layers[$i];
                        $this->transformLayer($layer, $transform, $sourceModel->extension);
                        $gif->layers()->add($layer);
                    }
                }

                $this->imageInstance = $gif;
            } else {
                $this->transformLayer($this->imageInstance, $transform, $sourceModel->extension);
            }

            // If Image Driver is imagick and removeMetadata is true, remove meta data
            if (ImagerService::$imageDriver === 'imagick' && $config->getSetting('removeMetadata', $transform)) {
                $this->imageInstance->strip();
            }

            // Convert the image to RGB before converting to webp/saving
            if ($config->getSetting('convertToRGB', $transform)) {
                $this->imageInstance->usePalette(new RGB());
            }

            // Save the transform
            if ($targetModel->extension === 'webp') {
                if (ImagerService::hasSupportForWebP()) {
                    $this->saveAsWebp($this->imageInstance, $targetModel->getFilePath(), $sourceModel->extension, $saveOptions);
                } else {
                    $msg = Craft::t('imager', 'This version of {imageDriver} does not support the webp format. You should use “craft.imager.serverSupportsWebp” in your templates to test for it.', ['imageDriver' => ImagerService::$imageDriver === 'gd' ? 'GD' : 'Imagick']);
                    Craft::error($msg, __METHOD__);
                    throw new ImagerException($msg);
                }
            } else {
                $this->imageInstance->save($targetModel->getFilePath(), $saveOptions);
            }

            $targetModel->isNew = true;
        }

        // create CraftTransformedImageModel for transformed image
        $imageModel = new CraftTransformedImageModel($targetModel, $sourceModel, $transform);

        return $imageModel;
    }
1177292
public function transformToResourceObject(ResourceTransformation $transformation, DataInterface $data): ?array
    {
        if ($transformation->object === null || $transformation->resource === null) {
            return null;
        }

        $transformation->resource->initializeTransformation(
            $transformation->request,
            $transformation->object,
            $transformation->exceptionFactory
        );

        $this->transformResourceIdentifier($transformation);
        $this->transformLinksObject($transformation);
        $this->transformAttributesObject($transformation);
        $this->transformRelationshipsObject($transformation, $data);

        $transformation->resource->clearTransformation();

        return $transformation->result;
    }
993809
public function cropTransparentImage($imageResource)
    {
        /** @var int $imageTransparency Transparent color */
        $imageTransparency = imagecolorallocatealpha($imageResource, 255, 255, 255, 127);
        /** @var resource $croppedImage Cropped image resource */
        $croppedImage = imagecreatetruecolor($_POST['crop_width'], $_POST['crop_height']);
        // Fill new image with transparent color
        imagefill($croppedImage, 0, 0, $imageTransparency);
        // Save Alpha chanel
        imagesavealpha($croppedImage, true);
        /** @var resource $rotatedImage Rotated image resource */
        $rotatedImage = imagerotate($imageResource, -($_POST['rotate']), $imageTransparency);
        // Copy rotated image to cropped one
        imagecopy(
            $croppedImage,
            $rotatedImage,
            0,
            0,
            $_POST['crop_x'],
            $_POST['crop_y'],
            $_POST['crop_width'],
            $_POST['crop_height']
        );
        // Delete temp image resource
        imagedestroy($rotatedImage);
        // Return result image
        return $croppedImage;
    }
811959
public function generateAttributeValue($sourceImageUrl, $filters = [])
    {
        // ['1x' => ['w' => 123, 'h' => 200], '2x' = > [..]]
        if (Utils::isMatrix($filters)) {
            $srcset = [];
            foreach ($filters as $format => $formatFilters) {
                $srcset[] = sprintf(
                    '%s %s',
                    $this->urlGenerator->generateUrl($sourceImageUrl, $formatFilters),
                    $format
                );
            }

            return implode(', ', $srcset);
        }

    // ['w' => 123, 'h' => 200]
        if (is_array($filters)) {
            return $this->urlGenerator->generateUrl($sourceImageUrl, $filters);
        }

        // '(min-width: 36em) 33.3vw, 100vw' or 'auto' (e.g. 'sizes')
        if (is_scalar($filters)) {
            return $filters;
        }

        throw new ConfigurationException('Filters should be either array or scalar');
    }
854499
public static function createTransformFilestring($transform): string
    {
        $r = '';

        foreach ($transform as $k => $v) {
            if ($k === 'effects' || $k === 'preEffects') {
                $effectString = '';
                foreach ($v as $eff => $param) {
                    if (\is_array($param)) {
                        if (\is_array($param[0])) {
                            $effectString .= '_' . $eff;
                            foreach ($param as $paramArr) {
                                $effectString .= '-' . implode('-', $paramArr);
                            }
                        } else {
                            $effectString .= '_' . $eff . '-' . implode('-', $param);
                        }
                    } else {
                        $effectString .= '_' . $eff . '-' . $param;
                    }
                }

                $r .= '_' . (ImagerService::$transformKeyTranslate[$k] ?? $k) . $effectString;
            } else {
                if ($k === 'watermark') {
                    $watermarkString = '';

                    foreach ($v as $eff => $param) {
                        $watermarkString .= $eff . '-' . (\is_array($param) ? implode('-', $param) : $param);
                    }

                    $r .= '_' . (ImagerService::$transformKeyTranslate[$k] ?? $k) . '_' . mb_substr(md5($watermarkString), 0, 10);
                } elseif ($k === 'webpImagickOptions') {
                    $optString = '';

                    foreach ($v as $optK => $optV) {
                        $optString .= ($optK . '-' . $optV . '-');
                    }

                    $r .= '_' . (ImagerService::$transformKeyTranslate[$k] ?? $k) . '_' . mb_substr($optString, 0, strlen($optString) - 1);
                } else {
                    $r .= '_' . (ImagerService::$transformKeyTranslate[$k] ?? $k) . (\is_array($v) ? implode('-', $v) : $v);
                }
            }
        }

        return str_replace([' ', '.', ',', '#', '(', ')'], ['-', '-', '-', '', '', ''], $r);
    }
388250
private function getFilterMethod($transform): string
    {
        /** @var ConfigModel $settings */
        $config = ImagerService::getConfig();

        return ImagerService::$imageDriver === 'imagick' ? ImagerService::$filterKeyTranslate[(string)$config->getSetting('resizeFilter', $transform)] : ImageInterface::FILTER_UNDEFINED;
    }
642296
public function transform($transformMode = '')
    {
        switch ($transformMode) {
            case ModelInterface::TRANSFORM_TO_JSON_ARRAY:
                return json_encode($this->objectToArray($this));
            default:
                return $this->objectToArray($this);
        }
    }
658857
public function transformToRelationshipObject(ResourceTransformation $transformation, DataInterface $data): ?array
    {
        if ($transformation->object === null || $transformation->resource === null) {
            return null;
        }

        $relationships = $transformation->resource->getRelationships($transformation->object);
        if (isset($relationships[$transformation->requestedRelationshipName]) === false) {
            throw $transformation->exceptionFactory->createRelationshipNotExistsException($transformation->requestedRelationshipName);
        }

        $defaultRelationships = $transformation->resource->getDefaultIncludedRelationships($transformation->object);

        $transformation->result = $this->transformRelationshipObject(
            $transformation,
            $data,
            $relationships[$transformation->currentRelationshipName],
            $defaultRelationships
        );

        return $transformation->result;
    }
1098517
public function transform($image, $transforms)
    {
        /** @var ConfigModel $settings */
        $config = ImagerService::getConfig();

        $sourceModel = new LocalSourceImageModel($image);

        $transformedImages = [];

        foreach ($transforms as $transform) {
            $transformedImages[] = $this->getTransformedImage($sourceModel, $transform);
        }

        $taskCreated = false;

        // Loop over transformed images and do post optimizations and upload to external storage 
        foreach ($transformedImages as $transformedImage) {
            /** @var CraftTransformedImageModel $transformedImage */
            if ($transformedImage->isNew) {
                $isFinalVersion = $this->optimize($transformedImage);
                $this->store($transformedImage, $isFinalVersion);

                if (!$isFinalVersion) {
                    $taskCreated = true;
                }
            }
        }

        // If ajax request, trigger jobs immediately
        if (!Craft::$app->getRequest()->isConsoleRequest && $taskCreated && $config->runJobsImmediatelyOnAjaxRequests && Craft::$app->getRequest()->getIsAjax()) {
            $this->triggerQueueNow();
        }

        return $transformedImages;
    }
218800
protected function pipe(callable $transformation) : FormatterAbstract
    {
        $that = clone $this; // Immutability!
        array_push($that->transformations, $transformation);

        return $that;
    }
203735
public function prepareTransform($transform)
    {
        // it can not be  empty
        if (!$transform) {
            return null;
        }

        // sections must be like scale(...)
        if (!preg_match_all('/([a-z]+)\(([^\)]*)\)/isU', $transform, $match)) {
            return null;
        }

        // prepare the list of the actions
        $actions = array();

        // for actions
        $amountMatches = count($match[0]);
        for ($k=0; $k < $amountMatches; $k++) {
            // get the name of the action
            $name = strtolower($match[1][$k]);

            // get the parameters of the action
            $values = [];
            $string = trim($match[2][$k]);
            if ($string !== '') {
                $values = explode(',', $string);
            }
            foreach ($values as $key => $value) {
                $value = trim($value);
                if ($value === '') {
                    unset($values[$key]);
                    continue;
                }

                $values[$key] = $value;
            }

            // prepare the matrix, depending on the action
            switch ($name) {
                case 'scale':
                    if (!array_key_exists(0, $values)) {
                        $values[0] = 1.;
                    }

                    if (!array_key_exists(1, $values)) {
                        $values[1] = $values[0];
                    }

                    $values[0] = floatval($values[0]);
                    $values[1] = floatval($values[1]);

                    $actions[] = array($values[0],0.,0.,$values[1],0.,0.);
                    break;

                case 'translate':
                    if (!array_key_exists(0, $values)) {
                        $values[0] = 0.;
                    }

                    if (!array_key_exists(1, $values)) {
                        $values[1] = 0.;
                    }

                    $values[0] = $this->cssConverter->convertToMM($values[0], $this->getProperty('w'));
                    $values[1] = $this->cssConverter->convertToMM($values[1], $this->getProperty('h'));

                    $actions[] = array(1.,0.,0.,1.,$values[0],$values[1]);
                    break;

                case 'rotate':
                    if (!array_key_exists(0, $values)) {
                        $values[0] = 0.;
                    }
                    if (!array_key_exists(1, $values)) {
                        $values[1] = 0.;
                    }
                    if (!array_key_exists(2, $values)) {
                        $values[2] = 0.;
                    }

                    $values[0] = $values[0]*M_PI/180.;
                    $values[1] = $this->cssConverter->convertToMM($values[1], $this->getProperty('w'));
                    $values[2] = $this->cssConverter->convertToMM($values[2], $this->getProperty('h'));

                    if ($values[1] || $values[2]) {
                        $actions[] = array(1.,0.,0.,1.,-$values[1],-$values[2]);
                    }

                    $actions[] = array(cos($values[0]),sin($values[0]),-sin($values[0]),cos($values[0]),0.,0.);

                    if ($values[1] || $values[2]) {
                        $actions[] = array(1.,0.,0.,1.,$values[1],$values[2]);
                    }
                    break;

                case 'skewx':
                    if (!array_key_exists(0, $values)) {
                        $values[0] = 0.;
                    }

                    $values[0] = $values[0]*M_PI/180.;

                    $actions[] = array(1.,0.,tan($values[0]),1.,0.,0.);
                    break;

                case 'skewy':
                    if (!array_key_exists(0, $values)) {
                        $values[0] = 0.;
                    }

                    $values[0] = $values[0]*M_PI/180.;

                    $actions[] = array(1.,tan($values[0]),0.,1.,0.,0.);
                    break;

                case 'matrix':
                    if (!array_key_exists(0, $values)) {
                        $values[0] = 0.;
                    }

                    if (!array_key_exists(1, $values)) {
                        $values[1] = 0.;
                    }

                    if (!array_key_exists(2, $values)) {
                        $values[2] = 0.;
                    }

                    if (!array_key_exists(3, $values)) {
                        $values[3] = 0.;
                    }

                    if (!array_key_exists(4, $values)) {
                        $values[4] = 0.;
                    }

                    if (!array_key_exists(5, $values)) {
                        $values[5] = 0.;
                    }

                    $values[0] = floatval($values[0]);
                    $values[1] = floatval($values[1]);
                    $values[2] = floatval($values[2]);
                    $values[3] = floatval($values[3]);
                    $values[4] = $this->cssConverter->convertToMM($values[4], $this->getProperty('w'));
                    $values[5] = $this->cssConverter->convertToMM($values[5], $this->getProperty('h'));

                    $actions[] = $values;
                    break;
            }
        }

        // if there are no actions => return
        if (!$actions) {
            return null;
        }

        // get the first matrix
        $m = $actions[0];
        unset($actions[0]);

        // foreach matrix => multiply to the last matrix
        foreach ($actions as $n) {
            $m = array(
                $m[0]*$n[0]+$m[2]*$n[1],
                $m[1]*$n[0]+$m[3]*$n[1],
                $m[0]*$n[2]+$m[2]*$n[3],
                $m[1]*$n[2]+$m[3]*$n[3],
                $m[0]*$n[4]+$m[2]*$n[5]+$m[4],
                $m[1]*$n[4]+$m[3]*$n[5]+$m[5]
            );
        }

        // return the matrix
        return $m;
    }
1174214
public function getOneReferenceByParameters(array $parameters, $transform = true, $keepChildren = false)
    {
        $result = $this->getReferencesByParameters($parameters, $transform, $keepChildren);
        if (count($result)) {
            return $result[0];
        }
    }
1337588
public function getTransformer(?array $sourcesTypes, ?array $targetTypes, MapperConfigurationInterface $mapperConfiguration): ?TransformerInterface
    {
        foreach ($this->factories as $factory) {
            $transformer = $factory->getTransformer($sourcesTypes, $targetTypes, $mapperConfiguration);

            if ($transformer !== null) {
                return $transformer;
            }
        }

        return null;
    }
525043
protected function getImageSource($source, array &$parameter)
    {
        $fragments  = preg_split('%:%', $source, -1, PREG_SPLIT_NO_EMPTY);
        $parameter['source'] = array_shift($fragments);
        $this->parseImageFilter($fragments, $parameter);
    }
377251
protected function transform($transforms)
    {
        if (!$transforms) {
            return null;
        }
        if (isset($transforms[0])) {
            $images = [];

            foreach ($transforms as $transform) {
                $transform = array_merge($this->defaultOptions, $transform);
                $transform = $this->calculateTargetSizeFromRatio($transform);
                $url       = $this->buildTransform($this->imagePath, $transform);
                $images[]  = array_merge($transform, ['url' => $url]);
            }

            $this->transformed = $images;
        }
        else {
            $transforms        = array_merge($this->defaultOptions, $transforms);
            $transforms        = $this->calculateTargetSizeFromRatio($transforms);
            $url               = $this->buildTransform($this->imagePath, $transforms);
            $image             = array_merge($transforms, ['url' => $url]);
            $this->transformed = $image;
        }
    }
573284
static function transform($modelOrCollection, $options = [])
    {
        $static = new static($options);

        if ($modelOrCollection instanceof Collection) {
            return $modelOrCollection->map([$static, 'transformModel'])->toArray();
        }

        return $static->transformModel($modelOrCollection);
    }
876152
public function addTransform(string $transform, bool $top = true): self
	{
		$this->assertTransform($transform);

		if ($top) {
			array_unshift($this->transforms, $transform);
		} else {
			$this->transforms[] = $transform;
		}

		$this->setOptions['transforms'] = null;

		return $this;
	}