/** * @param $asset * @param Config $config * @param null $fileSystemPath * @param bool $deferred deferred means that the image will be generated on-the-fly (details see below) * @return mixed|string */ public static function process($asset, Config $config, $fileSystemPath = null, $deferred = false) { $format = strtolower($config->getFormat()); $contentOptimizedFormat = false; if (!$fileSystemPath && $asset instanceof Asset) { $fileSystemPath = $asset->getFileSystemPath(); } if ($asset instanceof Asset) { $id = $asset->getId(); } else { $id = "dyn~" . crc32($fileSystemPath); } if (!file_exists($fileSystemPath)) { return "/pimcore/static/img/filetype-not-supported.png"; } $modificationDate = filemtime($fileSystemPath); $fileExt = File::getFileExtension(basename($fileSystemPath)); // simple detection for source type if SOURCE is selected if ($format == "source" || empty($format)) { $format = self::getAllowedFormat($fileExt, array("jpeg", "gif", "png"), "png"); $contentOptimizedFormat = true; // format can change depending of the content (alpha-channel, ...) } if ($format == "print") { $format = self::getAllowedFormat($fileExt, array("svg", "jpeg", "png", "tiff"), "png"); if (($format == "tiff" || $format == "svg") && \Pimcore\Tool::isFrontentRequestByAdmin()) { // return a webformat in admin -> tiff cannot be displayed in browser $format = "png"; } else { if ($format == "tiff") { $transformations = $config->getItems(); if (is_array($transformations) && count($transformations) > 0) { foreach ($transformations as $transformation) { if (!empty($transformation)) { if ($transformation["method"] == "tifforiginal") { return str_replace(PIMCORE_DOCUMENT_ROOT, "", $fileSystemPath); } } } } } else { if ($format == "svg") { return str_replace(PIMCORE_DOCUMENT_ROOT, "", $fileSystemPath); } } } } $thumbDir = $asset->getImageThumbnailSavePath() . "/thumb__" . $config->getName(); $filename = preg_replace("/\\." . preg_quote(File::getFileExtension($asset->getFilename())) . "/", "", $asset->getFilename()); // add custom suffix if available if ($config->getFilenameSuffix()) { $filename .= "~-~" . $config->getFilenameSuffix(); } // add high-resolution modifier suffix to the filename if ($config->getHighResolution() > 1) { $filename .= "@" . $config->getHighResolution() . "x"; } $filename .= "." . $format; $fsPath = $thumbDir . "/" . $filename; if (!is_dir(dirname($fsPath))) { File::mkdir(dirname($fsPath)); } $path = str_replace(PIMCORE_DOCUMENT_ROOT, "", $fsPath); // check for existing and still valid thumbnail if (is_file($fsPath) and filemtime($fsPath) >= $modificationDate) { return $path; } // deferred means that the image will be generated on-the-fly (when requested by the browser) // the configuration is saved for later use in Pimcore\Controller\Plugin\Thumbnail::routeStartup() // so that it can be used also with dynamic configurations if ($deferred) { $configId = "thumb_" . $id . "__" . md5($path); TmpStore::add($configId, $config, "thumbnail_deferred"); return $path; } // transform image $image = Asset\Image::getImageTransformInstance(); if (!$image->load($fileSystemPath)) { return "/pimcore/static/img/filetype-not-supported.png"; } $image->setUseContentOptimizedFormat($contentOptimizedFormat); $startTime = StopWatch::microtime_float(); $transformations = $config->getItems(); // check if the original image has an orientation exif flag // if so add a transformation at the beginning that rotates and/or mirrors the image if (function_exists("exif_read_data")) { $exif = @exif_read_data($fileSystemPath); if (is_array($exif)) { if (array_key_exists("Orientation", $exif)) { $orientation = intval($exif["Orientation"]); if ($orientation > 1) { $angleMappings = [2 => 180, 3 => 180, 4 => 180, 5 => 90, 6 => 90, 7 => 90, 8 => 270]; if (array_key_exists($orientation, $angleMappings)) { array_unshift($transformations, ["method" => "rotate", "arguments" => ["angle" => $angleMappings[$orientation]]]); } // values that have to be mirrored, this is not very common, but should be covered anyway $mirrorMappings = [2 => "vertical", 4 => "horizontal", 5 => "vertical", 7 => "horizontal"]; if (array_key_exists($orientation, $mirrorMappings)) { array_unshift($transformations, ["method" => "mirror", "arguments" => ["mode" => $mirrorMappings[$orientation]]]); } } } } } if (is_array($transformations) && count($transformations) > 0) { foreach ($transformations as $transformation) { if (!empty($transformation)) { $arguments = array(); $mapping = self::$argumentMapping[$transformation["method"]]; if (is_array($transformation["arguments"])) { foreach ($transformation["arguments"] as $key => $value) { $position = array_search($key, $mapping); if ($position !== false) { // high res calculations if enabled if (!in_array($transformation["method"], ["cropPercent"]) && in_array($key, array("width", "height", "x", "y"))) { if ($config->getHighResolution() && $config->getHighResolution() > 1) { $value *= $config->getHighResolution(); } } $arguments[$position] = $value; } } } ksort($arguments); call_user_func_array(array($image, $transformation["method"]), $arguments); } } } $image->save($fsPath, $format, $config->getQuality()); if ($contentOptimizedFormat) { $tmpStoreKey = str_replace(PIMCORE_TEMPORARY_DIRECTORY . "/", "", $fsPath); TmpStore::add($tmpStoreKey, "-", "image-optimize-queue"); } clearstatcache(); \Logger::debug("Thumbnail " . $path . " generated in " . (StopWatch::microtime_float() - $startTime) . " seconds"); // set proper permissions @chmod($fsPath, File::getDefaultMode()); // quick bugfix / workaround, it seems that imagemagick / image optimizers creates sometimes empty PNG chunks (total size 33 bytes) // no clue why it does so as this is not continuous reproducible, and this is the only fix we can do for now // if the file is corrupted the file will be created on the fly when requested by the browser (because it's deleted here) if (is_file($fsPath) && filesize($fsPath) < 50) { unlink($fsPath); } return $path; }
/** * @param $asset * @param Config $config * @param null $fileSystemPath * @param bool $deferred deferred means that the image will be generated on-the-fly (details see below) * @param bool $returnAbsolutePath * @param bool $generated * @return mixed|string */ public static function process($asset, Config $config, $fileSystemPath = null, $deferred = false, $returnAbsolutePath = false, &$generated = false) { $generated = false; $errorImage = PIMCORE_PATH . "/static6/img/filetype-not-supported.png"; $format = strtolower($config->getFormat()); $contentOptimizedFormat = false; if (!$fileSystemPath && $asset instanceof Asset) { $fileSystemPath = $asset->getFileSystemPath(); } if ($asset instanceof Asset) { $id = $asset->getId(); } else { $id = "dyn~" . crc32($fileSystemPath); } $fileExt = File::getFileExtension(basename($fileSystemPath)); // simple detection for source type if SOURCE is selected if ($format == "source" || empty($format)) { $format = self::getAllowedFormat($fileExt, ["jpeg", "gif", "png"], "png"); $contentOptimizedFormat = true; // format can change depending of the content (alpha-channel, ...) } if ($format == "print") { $format = self::getAllowedFormat($fileExt, ["svg", "jpeg", "png", "tiff"], "png"); if (($format == "tiff" || $format == "svg") && \Pimcore\Tool::isFrontentRequestByAdmin()) { // return a webformat in admin -> tiff cannot be displayed in browser $format = "png"; $deferred = false; // deferred is default, but it's not possible when using isFrontentRequestByAdmin() } elseif ($format == "tiff") { $transformations = $config->getItems(); if (is_array($transformations) && count($transformations) > 0) { foreach ($transformations as $transformation) { if (!empty($transformation)) { if ($transformation["method"] == "tifforiginal") { return self::returnPath($fileSystemPath, $returnAbsolutePath); } } } } } elseif ($format == "svg") { return self::returnPath($fileSystemPath, $returnAbsolutePath); } } elseif ($format == "tiff") { if (\Pimcore\Tool::isFrontentRequestByAdmin()) { // return a webformat in admin -> tiff cannot be displayed in browser $format = "png"; $deferred = false; // deferred is default, but it's not possible when using isFrontentRequestByAdmin() } } $thumbDir = $asset->getImageThumbnailSavePath() . "/thumb__" . $config->getName(); $filename = preg_replace("/\\." . preg_quote(File::getFileExtension($asset->getFilename())) . "/", "", $asset->getFilename()); // add custom suffix if available if ($config->getFilenameSuffix()) { $filename .= "~-~" . $config->getFilenameSuffix(); } // add high-resolution modifier suffix to the filename if ($config->getHighResolution() > 1) { $filename .= "@" . $config->getHighResolution() . "x"; } $fileExtension = $format; if ($format == "original") { $fileExtension = \Pimcore\File::getFileExtension($fileSystemPath); } $filename .= "." . $fileExtension; $fsPath = $thumbDir . "/" . $filename; // deferred means that the image will be generated on-the-fly (when requested by the browser) // the configuration is saved for later use in Pimcore\Controller\Plugin\Thumbnail::routeStartup() // so that it can be used also with dynamic configurations if ($deferred) { // only add the config to the TmpStore if necessary (the config is auto-generated) if (!Config::getByName($config->getName())) { $configId = "thumb_" . $id . "__" . md5(str_replace(PIMCORE_TEMPORARY_DIRECTORY, "", $fsPath)); TmpStore::add($configId, $config, "thumbnail_deferred"); } return self::returnPath($fsPath, $returnAbsolutePath); } // all checks on the file system should be below the deferred part for performance reasons (remote file systems) if (!file_exists($fileSystemPath)) { return self::returnPath($errorImage, $returnAbsolutePath); } if (!is_dir(dirname($fsPath))) { File::mkdir(dirname($fsPath)); } $path = self::returnPath($fsPath, false); // check for existing and still valid thumbnail if (is_file($fsPath) and filemtime($fsPath) >= filemtime($fileSystemPath)) { return self::returnPath($fsPath, $returnAbsolutePath); } // transform image $image = Asset\Image::getImageTransformInstance(); $image->setPreserveColor($config->isPreserveColor()); $image->setPreserveMetaData($config->isPreserveMetaData()); if (!$image->load($fileSystemPath)) { return self::returnPath($errorImage, $returnAbsolutePath); } $image->setUseContentOptimizedFormat($contentOptimizedFormat); $startTime = StopWatch::microtime_float(); $transformations = $config->getItems(); // check if the original image has an orientation exif flag // if so add a transformation at the beginning that rotates and/or mirrors the image if (function_exists("exif_read_data")) { $exif = @exif_read_data($fileSystemPath); if (is_array($exif)) { if (array_key_exists("Orientation", $exif)) { $orientation = intval($exif["Orientation"]); if ($orientation > 1) { $angleMappings = [2 => 180, 3 => 180, 4 => 180, 5 => 90, 6 => 90, 7 => 90, 8 => 270]; if (array_key_exists($orientation, $angleMappings)) { array_unshift($transformations, ["method" => "rotate", "arguments" => ["angle" => $angleMappings[$orientation]]]); } // values that have to be mirrored, this is not very common, but should be covered anyway $mirrorMappings = [2 => "vertical", 4 => "horizontal", 5 => "vertical", 7 => "horizontal"]; if (array_key_exists($orientation, $mirrorMappings)) { array_unshift($transformations, ["method" => "mirror", "arguments" => ["mode" => $mirrorMappings[$orientation]]]); } } } } } if (is_array($transformations) && count($transformations) > 0) { $sourceImageWidth = PHP_INT_MAX; $sourceImageHeight = PHP_INT_MAX; if ($asset instanceof Asset\Image) { $sourceImageWidth = $asset->getWidth(); $sourceImageHeight = $asset->getHeight(); } $highResFactor = $config->getHighResolution(); $calculateMaxFactor = function ($factor, $original, $new) { $newFactor = $factor * $original / $new; if ($newFactor < 1) { // don't go below factor 1 $newFactor = 1; } return $newFactor; }; // sorry for the goto/label - but in this case it makes life really easier and the code more readable prepareTransformations: foreach ($transformations as $transformation) { if (!empty($transformation)) { $arguments = []; $mapping = self::$argumentMapping[$transformation["method"]]; if (is_array($transformation["arguments"])) { foreach ($transformation["arguments"] as $key => $value) { $position = array_search($key, $mapping); if ($position !== false) { // high res calculations if enabled if (!in_array($transformation["method"], ["cropPercent"]) && in_array($key, ["width", "height", "x", "y"])) { if ($highResFactor && $highResFactor > 1) { $value *= $highResFactor; $value = (int) ceil($value); // check if source image is big enough otherwise adjust the high-res factor if (in_array($key, ["width", "x"])) { if ($sourceImageWidth < $value) { $highResFactor = $calculateMaxFactor($highResFactor, $sourceImageWidth, $value); goto prepareTransformations; } } elseif (in_array($key, ["height", "y"])) { if ($sourceImageHeight < $value) { $highResFactor = $calculateMaxFactor($highResFactor, $sourceImageHeight, $value); goto prepareTransformations; } } } } $arguments[$position] = $value; } } } ksort($arguments); if (method_exists($image, $transformation["method"])) { call_user_func_array([$image, $transformation["method"]], $arguments); } } } } $image->save($fsPath, $format, $config->getQuality()); $generated = true; if ($contentOptimizedFormat) { $tmpStoreKey = str_replace(PIMCORE_TEMPORARY_DIRECTORY . "/", "", $fsPath); TmpStore::add($tmpStoreKey, "-", "image-optimize-queue"); } clearstatcache(); Logger::debug("Thumbnail " . $path . " generated in " . (StopWatch::microtime_float() - $startTime) . " seconds"); // set proper permissions @chmod($fsPath, File::getDefaultMode()); // quick bugfix / workaround, it seems that imagemagick / image optimizers creates sometimes empty PNG chunks (total size 33 bytes) // no clue why it does so as this is not continuous reproducible, and this is the only fix we can do for now // if the file is corrupted the file will be created on the fly when requested by the browser (because it's deleted here) if (is_file($fsPath) && filesize($fsPath) < 50) { unlink($fsPath); } return self::returnPath($fsPath, $returnAbsolutePath); }