diff --git a/typo3/sysext/core/Classes/Imaging/SvgManipulation.php b/typo3/sysext/core/Classes/Imaging/SvgManipulation.php
new file mode 100644
index 0000000000000000000000000000000000000000..6d002d306246b7b2b9fd84d7b178d58d436be4bd
--- /dev/null
+++ b/typo3/sysext/core/Classes/Imaging/SvgManipulation.php
@@ -0,0 +1,196 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Imaging;
+
+use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
+
+/**
+ * Performs SVG cropping by applying a wrapper SVG as view
+ *
+ *  A simple SVG with an input like this:
+ *
+ *  <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0"
+ *    viewBox="0 168.4 940.7 724" width="941" height="724">
+ *    <path id="path" d="M490.1 655.5c-9.4 1.2-16.9
+ *  </svg>
+ *
+ *  is wrapped with crop dimensions (i.e. "50 50 640 480") to something like this:
+ *
+ *  <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="50 50 640 480" width="640" height="480">
+ *    <svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0"
+ *      viewBox="0 168.4 940.7 724" width="941" height="724">
+ *      <path id="path" d="M490.1 655.5c-9.4 1.2-16.9
+ *    </svg>
+ *  </svg>
+ *
+ * @internal not part of TYPO3 Core API.
+ */
+class SvgManipulation
+{
+    private int $defaultSvgDimension = 64;
+
+    /**
+     * @throws \DOMException
+     */
+    public function cropScaleSvgString(string $svgString, Area $cropArea, ImageDimension $imageDimension): \DOMDocument
+    {
+        $offsetLeft = (int)$cropArea->getOffsetLeft();
+        $offsetTop = (int)$cropArea->getOffsetTop();
+        // Rounding is applied to preserve the same width/height that imageDimension calculates
+        $newWidth = (int)round($cropArea->getWidth());
+        $newHeight = (int)round($cropArea->getHeight());
+
+        // Load original SVG
+        $originalSvg = new \DOMDocument();
+        $originalSvg->loadXML($svgString);
+
+        // Create a fresh wrapping <svg> tag
+        $processedSvg = new \DOMDocument('1.0');
+        $processedSvg->preserveWhiteSpace = true;
+        $processedSvg->formatOutput = true;
+        $outerSvgElement = $processedSvg->createElement('svg');
+        $outerSvgElement->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
+
+        // Determine the SVG dimensions of the source SVG file contents
+        $dimensions = $this->determineSvgDimensions($originalSvg);
+
+        // Adjust the width/height attributes of the outer SVG proxy element, if they were empty before.
+        $this->adjustSvgDimensions($originalSvg, $dimensions);
+
+        // Set several attributes on the outer SVG proxy element (the "wrapper" of the real SVG)
+        $outerSvgElement->setAttribute('viewBox', $offsetLeft . ' ' . $offsetTop . ' ' . $newWidth . ' ' . $newHeight);
+        $outerSvgElement->setAttribute('width', (string)$imageDimension->getWidth());
+        $outerSvgElement->setAttribute('height', (string)$imageDimension->getHeight());
+
+        // Possibly prevent some attributes on the "inner svg" (original input) and transport them
+        // to the new root (outerSvgElement). Currently only 'preserveAspectRatio'.
+        if ($originalSvg->documentElement->getAttribute('preserveAspectRatio') != '') {
+            $outerSvgElement->setAttribute('preserveAspectRatio', $originalSvg->documentElement->getAttribute('preserveAspectRatio'));
+        }
+
+        // To enable some debugging for embeddding the original determined dimensions into the SVG, use:
+        // $outerSvgElement->setAttribute('data-inherit-width', (string)$dimensions['determined']['width']);
+        // $outerSvgElement->setAttribute('data-inherit-height', (string)$dimensions['determined']['height']);
+
+        // Attach the main source SVG element into our proxy SVG element.
+        $innerSvgElement = $processedSvg->importNode($originalSvg->documentElement, true);
+
+        // Stitch together the wrapper plus the old root element plus children,
+        // so that $processedSvg contains the full XML tree
+        $outerSvgElement->appendChild($innerSvgElement);
+        $processedSvg->appendChild($outerSvgElement);
+
+        return $processedSvg;
+    }
+
+    /**
+     * Ensure that the determined width and height settings are attributes on the original <svg>.
+     * If those were missing, cropping could not successfully be applied when getting
+     * embedded and adjusted within a <img> element.
+     *
+     * Returns true, if the determined width/height has been injected into the main <svg>
+     */
+    protected function adjustSvgDimensions(\DOMDocument $originalSvg, array $determinedDimensions): bool
+    {
+        $isAltered = false;
+
+        if ($determinedDimensions['original']['width'] === '') {
+            $originalSvg->documentElement->setAttribute('width', $determinedDimensions['determined']['width']);
+            $originalSvg->documentElement->setAttribute('data-manipulated-width', 'true');
+            $isAltered = true;
+        }
+
+        if ($determinedDimensions['original']['height'] === '') {
+            $originalSvg->documentElement->setAttribute('height', $determinedDimensions['determined']['height']);
+            $originalSvg->documentElement->setAttribute('data-manipulated-height', 'true');
+            $isAltered = true;
+        }
+
+        return $isAltered;
+    }
+
+    /**
+     * Check an input SVG element for its dimensions through
+     * width/height/viewBox attributes.
+     *
+     * Returns an array with the determined width/height.
+     */
+    protected function determineSvgDimensions(\DOMDocument $originalSvg): array
+    {
+        // A default used when SVG neither uses width, height nor viewBox
+        // Files falling back to this are probably broken.
+        $width = $height = null;
+
+        $originalSvgViewBox = $originalSvg->documentElement->getAttribute('viewBox');
+        $originalSvgWidth = $originalSvg->documentElement->getAttribute('width');
+        $originalSvgHeight = $originalSvg->documentElement->getAttribute('height');
+
+        // width/height can easily be used if they are numeric. Else, viewBox attribute dimensions
+        // are evaluated. These are used as better fallback here, overridden if width/height exist.
+        if ($originalSvgViewBox !== '') {
+            $viewBoxParts = explode(' ', $originalSvgViewBox);
+            if (isset($viewBoxParts[2]) && is_numeric($viewBoxParts[2])) {
+                $width = $viewBoxParts[2];
+            }
+
+            if (isset($viewBoxParts[3]) && is_numeric($viewBoxParts[3])) {
+                $height = $viewBoxParts[3];
+            }
+        }
+
+        // width/height may contain percentages or units like "mm", "cm"
+        // When non-numeric, we only use the width/height when no viewBox
+        // exists (because the size of a viewBox would be preferred
+        // to a non-numeric value), and then unify the unit as "1".
+        if ($originalSvgWidth !== '') {
+            if (is_numeric($originalSvgWidth)) {
+                $width = $originalSvgWidth;
+            } elseif ($width === null) {
+                // contains a unit like "cm", "mm", "%", ...
+                // Currently just stripped because without knowing the output
+                // device, no pixel size can be calculated (missing dpi).
+                // So we regard the unit to be "1" - this is how TYPO3
+                // already did it when SVG file metadata was evaluated (before
+                // cropping).
+                $width = (int)$originalSvgWidth;
+            }
+        }
+
+        if ($originalSvgHeight !== '') {
+            if (is_numeric($originalSvgHeight)) {
+                $height = $originalSvgHeight;
+            } elseif ($height === null) {
+                $height = (int)$originalSvgHeight;
+            }
+        }
+
+        return [
+            // The "proper" image dimensions (with viewBox preference)
+            'determined' => [
+                'width' => $width ?? $this->defaultSvgDimension,
+                'height' => $height ?? $this->defaultSvgDimension,
+            ],
+
+            // Possible original "width/height" attributes (may not correlate with the viewBox, could be empty)
+            'original' => [
+                'width' => $originalSvgWidth,
+                'height' => $originalSvgHeight,
+            ],
+        ];
+    }
+}
diff --git a/typo3/sysext/core/Classes/Resource/Processing/ImageCropScaleMaskTask.php b/typo3/sysext/core/Classes/Resource/Processing/ImageCropScaleMaskTask.php
index a04b59390220143a26254ac0074155c0bfa56d80..c3d15a0dbc0c316191daabd3d99b3965baa70a69 100644
--- a/typo3/sysext/core/Classes/Resource/Processing/ImageCropScaleMaskTask.php
+++ b/typo3/sysext/core/Classes/Resource/Processing/ImageCropScaleMaskTask.php
@@ -57,12 +57,10 @@ class ImageCropScaleMaskTask extends AbstractTask
     {
         if (!empty($this->configuration['fileExtension'])) {
             $targetFileExtension = $this->configuration['fileExtension'];
-        } elseif (in_array($this->getSourceFile()->getExtension(), ['jpg', 'jpeg', 'png', 'gif'], true)) {
+        } elseif (in_array($this->getSourceFile()->getExtension(), ['jpg', 'jpeg', 'png', 'gif', 'svg'], true)) {
             $targetFileExtension = $this->getSourceFile()->getExtension();
         } elseif ($this->getSourceFile()->getExtension() === 'webp' && GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] ?? '', 'webp')) {
             $targetFileExtension = $this->getSourceFile()->getExtension();
-        } elseif (empty($this->configuration['crop']) && $this->getSourceFile()->getExtension() === 'svg') {
-            $targetFileExtension = 'svg';
         } else {
             // Thumbnails from non-processable files will be converted to 'png'
             $targetFileExtension = 'png';
diff --git a/typo3/sysext/core/Classes/Resource/Processing/ImagePreviewTask.php b/typo3/sysext/core/Classes/Resource/Processing/ImagePreviewTask.php
index 1e2d92858dbfc4fe056d96061d12963c60d63ba0..59e87cf6401ddb3f19c5ea70aa290485c3e4909b 100644
--- a/typo3/sysext/core/Classes/Resource/Processing/ImagePreviewTask.php
+++ b/typo3/sysext/core/Classes/Resource/Processing/ImagePreviewTask.php
@@ -70,12 +70,10 @@ class ImagePreviewTask extends AbstractTask
     {
         if (!empty($this->configuration['fileExtension'])) {
             $targetFileExtension = $this->configuration['fileExtension'];
-        } elseif (in_array($this->getSourceFile()->getExtension(), ['jpg', 'jpeg', 'png', 'gif'], true)) {
+        } elseif (in_array($this->getSourceFile()->getExtension(), ['jpg', 'jpeg', 'png', 'gif', 'svg'], true)) {
             $targetFileExtension = $this->getSourceFile()->getExtension();
         } elseif ($this->getSourceFile()->getExtension() === 'webp' && GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] ?? '', 'webp')) {
             $targetFileExtension = $this->getSourceFile()->getExtension();
-        } elseif (empty($this->configuration['crop']) && $this->getSourceFile()->getExtension() === 'svg') {
-            $targetFileExtension = 'svg';
         } else {
             // Thumbnails from non-processable files will be converted to 'png'
             $targetFileExtension = 'png';
diff --git a/typo3/sysext/core/Classes/Resource/Processing/LocalImageProcessor.php b/typo3/sysext/core/Classes/Resource/Processing/LocalImageProcessor.php
index 614133b8078b1dc32a1aad2e57525d60f73ceb1c..8b8cb4a719d8c73e6927b494d7466236ff87135a 100644
--- a/typo3/sysext/core/Classes/Resource/Processing/LocalImageProcessor.php
+++ b/typo3/sysext/core/Classes/Resource/Processing/LocalImageProcessor.php
@@ -91,12 +91,10 @@ class LocalImageProcessor implements ProcessorInterface, LoggerAwareInterface
     }
 
     /**
-     * Check if the to be processed target file already exists
-     * if exist take info from that file and mark task as done
-     *
-     * @return bool
+     * Check if the target file that is to be processed already exists.
+     * If it exists, use the metadata from that file and mark task as done.
      */
-    protected function checkForExistingTargetFile(TaskInterface $task)
+    protected function checkForExistingTargetFile(TaskInterface $task): bool
     {
         // the storage of the processed file, not of the original file!
         $storage = $task->getTargetFile()->getStorage();
diff --git a/typo3/sysext/core/Classes/Resource/Processing/SvgImageProcessor.php b/typo3/sysext/core/Classes/Resource/Processing/SvgImageProcessor.php
index 94db206282832137a2c5467ec71e5c3ae42b4db1..d03b4d4fc8b680e9a4f3c54bb6805ab3f6c6c22e 100644
--- a/typo3/sysext/core/Classes/Resource/Processing/SvgImageProcessor.php
+++ b/typo3/sysext/core/Classes/Resource/Processing/SvgImageProcessor.php
@@ -19,35 +19,51 @@ namespace TYPO3\CMS\Core\Resource\Processing;
 
 use TYPO3\CMS\Core\Imaging\Exception\ZeroImageDimensionException;
 use TYPO3\CMS\Core\Imaging\ImageDimension;
+use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
+use TYPO3\CMS\Core\Imaging\ImageProcessingInstructions;
+use TYPO3\CMS\Core\Imaging\SvgManipulation;
+use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderReadPermissionsException;
+use TYPO3\CMS\Core\Type\File\ImageInfo;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
- * Processes (scales) SVG Images files, when no cropping is defined
+ * Processes (scales) SVG Images files or crops them via \DOMDocument
+ * and creates a new locally created processed file which is then pushed
+ * into FAL again.
  */
 class SvgImageProcessor implements ProcessorInterface
 {
+    private int $defaultSvgDimension = 64;
+
     public function canProcessTask(TaskInterface $task): bool
     {
         return $task->getType() === 'Image'
             && in_array($task->getName(), ['Preview', 'CropScaleMask'], true)
-            && empty($task->getConfiguration()['crop'])
             && $task->getTargetFileExtension() === 'svg';
     }
 
     /**
      * Processes the given task.
      *
-     * @throws \InvalidArgumentException
+     * @throws \InvalidArgumentException|InsufficientFolderReadPermissionsException
      */
     public function processTask(TaskInterface $task): void
     {
-        $task->setExecuted(true);
-        $task->getTargetFile()->setUsesOriginalFile();
         try {
-            $imageDimension = ImageDimension::fromProcessingTask($task);
-        } catch (ZeroImageDimensionException $e) {
+            $processingInstructions = ImageProcessingInstructions::fromProcessingTask($task);
+            $imageDimension = new ImageDimension($processingInstructions->width, $processingInstructions->height);
+        } catch (ZeroImageDimensionException) {
+            $processingInstructions = new ImageProcessingInstructions(
+                width: $this->defaultSvgDimension,
+                height: $this->defaultSvgDimension,
+            );
             // To not fail image processing, we just assume an SVG image dimension here
-            $imageDimension = new ImageDimension(64, 64);
+            $imageDimension = new ImageDimension(
+                width: $this->defaultSvgDimension,
+                height: $this->defaultSvgDimension
+            );
         }
+
         $task->getTargetFile()->updateProperties(
             [
                 'width' => $imageDimension->getWidth(),
@@ -56,5 +72,105 @@ class SvgImageProcessor implements ProcessorInterface
                 'checksum' => $task->getConfigurationChecksum(),
             ]
         );
+
+        if ($this->checkForExistingTargetFile($task)) {
+            return;
+        }
+
+        $cropArea = $processingInstructions->cropArea;
+        if ($cropArea === null || $cropArea->makeRelativeBasedOnFile($task->getSourceFile())->isEmpty()) {
+            $task->setExecuted(true);
+            $task->getTargetFile()->setUsesOriginalFile();
+            return;
+        }
+
+        $this->applyCropping($task, $cropArea, $imageDimension);
+    }
+
+    /**
+     * Create standalone wrapper files for SVGs.
+     * Cropped responsive images delivered via an <img> tag or
+     * as a URI for a background image, need to be self-contained.
+     * Therefore we wrap a <svg> container around the original SVG
+     * content.
+     * A viewBox() crop is then applied to that container.
+     * The processed file will contain all the viewBox cropping information
+     * and thus transports intrinsic sizes for all variants of CSS
+     * processing (max/min width/height).
+     */
+    protected function applyCropping(TaskInterface $task, Area $cropArea, ImageDimension $imageDimension): void
+    {
+        $processedSvg = GeneralUtility::makeInstance(SvgManipulation::class)->cropScaleSvgString(
+            $task->getSourceFile()->getContents(),
+            $cropArea,
+            $imageDimension
+        );
+        // Save the output as a new processed file.
+        $temporaryFilename = $this->getFilenameForSvgCropScaleMask($task);
+        GeneralUtility::writeFile($temporaryFilename, $processedSvg->saveXML());
+
+        $task->setExecuted(true);
+        $imageInformation = GeneralUtility::makeInstance(ImageInfo::class, $temporaryFilename);
+
+        $task->getTargetFile()->setName($task->getTargetFileName());
+
+        $task->getTargetFile()->updateProperties([
+            // @todo: Use round() instead of int-cast to avoid an implicit floor()?
+            'width' => (string)$imageDimension->getWidth(),
+            'height' => (string)$imageDimension->getHeight(),
+            'size' => $imageInformation->getSize(),
+            'checksum' => $task->getConfigurationChecksum(),
+        ]);
+        $task->getTargetFile()->updateWithLocalFile($temporaryFilename);
+        // The temporary file is removed again
+        GeneralUtility::unlink_tempfile($temporaryFilename);
     }
+
+    /**
+     * Check if the target file that is to be processed already exists.
+     * If it exists, use the metadata from that file and mark task as done.
+     *
+     * @throws InsufficientFolderReadPermissionsException
+     * @todo - Refactor this 80% duplicate code of LocalImageProcessor::checkForExistingTargetFile
+     */
+    protected function checkForExistingTargetFile(TaskInterface $task): bool
+    {
+        // the storage of the processed file, not of the original file!
+        $storage = $task->getTargetFile()->getStorage();
+        $processingFolder = $storage->getProcessingFolder($task->getSourceFile());
+
+        // explicitly check for the raw filename here, as we check for files that existed before we even started
+        // processing, i.e. that were processed earlier
+        if ($processingFolder->hasFile($task->getTargetFileName())) {
+            // When the processed file already exists set it as processed file
+            $task->getTargetFile()->setName($task->getTargetFileName());
+
+            // If the processed file is stored on a remote server, we must fetch a local copy of the file, as we
+            // have no API for fetching file metadata from a remote file.
+            $localProcessedFile = $storage->getFileForLocalProcessing($task->getTargetFile(), false);
+            $task->setExecuted(true);
+            $imageInformation = GeneralUtility::makeInstance(ImageInfo::class, $localProcessedFile);
+            $properties = [
+                'width' => $imageInformation->getWidth(),
+                'height' => $imageInformation->getHeight(),
+                'size' => $imageInformation->getSize(),
+                'checksum' => $task->getConfigurationChecksum(),
+            ];
+            $task->getTargetFile()->updateProperties($properties);
+
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the filename for a cropped/scaled/masked file which will be put
+     * in typo3temp for the time being.
+     */
+    protected function getFilenameForSvgCropScaleMask(TaskInterface $task): string
+    {
+        $targetFileExtension = $task->getTargetFileExtension();
+        return GeneralUtility::tempnam($task->getTargetFile()->generateProcessedFileNameWithoutExtension(), '.' . ltrim(trim($targetFileExtension)));
+    }
+
 }
diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php
index 925a454419f74544ca9e76ee22f7c52b8f79aaf1..bc66c8b027fdc0f686448dbaeefd00f257ee5aad 100644
--- a/typo3/sysext/core/Configuration/DefaultConfiguration.php
+++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php
@@ -283,6 +283,7 @@ return [
                     'className' => \TYPO3\CMS\Core\Resource\Processing\SvgImageProcessor::class,
                     'before' => [
                         'LocalImageProcessor',
+                        'DeferredBackendImageProcessor',
                     ],
                 ],
                 'DeferredBackendImageProcessor' => [
diff --git a/typo3/sysext/core/Documentation/Changelog/13.1/Features-93942-CropSVGImagesNatively.rst b/typo3/sysext/core/Documentation/Changelog/13.1/Features-93942-CropSVGImagesNatively.rst
new file mode 100644
index 0000000000000000000000000000000000000000..667c9cb2c12751968dc769b7decd85ab38d0c1f5
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/13.1/Features-93942-CropSVGImagesNatively.rst
@@ -0,0 +1,52 @@
+.. include:: /Includes.rst.txt
+
+.. _feature-93942-1709722341:
+
+==========================================
+Feature: #93942 - Crop SVG images natively
+==========================================
+
+See :issue:`93942`
+
+Description
+===========
+
+Cropping SVG images via backend image editing or specific Fluid ViewHelper via
+:html:`<f:image>` or :html:`<f:uri.image>` (via :html:`crop` attribute) now
+outputs native SVG files by default - which are processed but again stored
+as SVG, instead of rasterized PNG/JPG images like before.
+
+
+Impact
+======
+
+Editors and integrators can now crop SVG assets without an impact to their
+output quality.
+
+Forced rasterization of cropped SVG assets can still be performed by setting the
+:html:`fileExtension="png"` Fluid ViewHelper attribute or the TypoScript
+:typoscript:`file.ext = png` property.
+
+:html:`<f:image>` ViewHelper example:
+-------------------------------------
+
+.. code-block:: html
+
+    <f:image image="{image}" fileExtension="png" />
+
+This keeps forcing images to be generated as PNG image.
+
+`file.ext = png` TypoScript example:
+------------------------------------
+
+.. code-block:: typoscript
+
+    page.10 = IMAGE
+    page.10.file = 2:/myfile.svg
+    page.10.file.crop = 20,20,500,500
+    page.10.file.ext = png
+
+If no special hard-coded option for the file extension is set, SVGs are now
+processed and stored as SVGs again.
+
+.. index:: Backend, FAL, Fluid, Frontend, TypoScript, ext:fluid
diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest1.svg b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest1.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0724b4cedbf688ca572c6e049dc96ca151b69012
--- /dev/null
+++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest1.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1680 1050">
+  <rect width="100%" height="100%" fill="pink" stroke-width="2" stroke="black" />
+  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="30 0 64 68">
+    <path id="a" d="M14 27v-20c0-3.7-3.3-7-7-7s-7 3.3-7 7v41c0 8.2 9.2 17 20 17s20-9.2 20-20c0-13.3-13.4-21.8-26-18zm6 25c-4 0-7-3-7-7s3-7 7-7 7 3 7 7-3 7-7 7z"></path>
+  </svg>
+</svg>
diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest2.svg b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest2.svg
new file mode 100644
index 0000000000000000000000000000000000000000..28e9bd0f3cfc2cffd3e3ff3b41127dfcfea5f650
--- /dev/null
+++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest2.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="1958.3 -758.3 283.5 283.5" xml:space="preserve">
+<rect x="1958.3" y="-758.3" fill="blue" width="283.5" height="283.5"></rect>
+<path fill="#999999" d="M1976-740.5v248h248v-248H1976L1976-740.5z M1993.7-722.8h212.6v212.6h-212.6V-722.8z"></path>
+<g>
+  <path fill="#5599FF" d="M2174.9-641.5h-149.8v-50h129.4c11.3,0,20.5,9.2,20.5,20.5V-641.5z"></path>
+  <rect x="2025.1" y="-641.5" fill="#FFC857" width="149.8" height="50"></rect>
+  <path fill="#666666" d="M2145.2-653.7l18.1-15.8c0.5-0.4,0.2-1.2-0.4-1.2l-35.6-0.3c-0.6,0-0.9,0.8-0.5,1.2l17.5,16.1   C2144.5-653.5,2144.9-653.5,2145.2-653.7z"></path>
+  <path fill="#FF8700" d="M2157.3-541.5h-132.2v-50h149.8v32.4C2174.9-549.4,2167-541.5,2157.3-541.5z"></path>
+  <path fill="#666666" d="M2145.2-603.7l18.1-15.8c0.5-0.4,0.2-1.2-0.4-1.2l-35.6-0.3c-0.6,0-0.9,0.8-0.5,1.2l17.5,16.1   C2144.5-603.5,2144.9-603.5,2145.2-603.7z"></path>
+  <path fill="#666666" d="M2145.2-553.7l18.1-15.8c0.5-0.4,0.2-1.2-0.4-1.2l-35.6-0.3c-0.6,0-0.9,0.8-0.5,1.2l17.5,16.1   C2144.5-553.4,2144.9-553.4,2145.2-553.7z"></path>
+</g>
+</svg>
diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest3.svg b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest3.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d768def3922eecf0be501f708e574d6e65728865
--- /dev/null
+++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest3.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" viewBox="0 12 940.7 724" width="941" height="724">
+  <rect width="100%" height="100%" fill="yellow" stroke-width="2" stroke="black" />
+  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="15 0 64 68">
+    <path id="a" d="M14 27v-20c0-3.7-3.3-7-7-7s-7 3.3-7 7v41c0 8.2 9.2 17 20 17s20-9.2 20-20c0-13.3-13.4-21.8-26-18zm6 25c-4 0-7-3-7-7s3-7 7-7 7 3 7 7-3 7-7 7z"></path>
+  </svg>
+</svg>
diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest4.svg b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest4.svg
new file mode 100644
index 0000000000000000000000000000000000000000..f227bede140c5cf4585ab7c7dfc4ee1eee59aa5c
--- /dev/null
+++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest4.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="1680" height="1050">
+  <rect width="100%" height="100%" fill="green" stroke-width="2" stroke="black" />
+  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="30 -1 64 68">
+    <path id="a" d="M14 27v-20c0-3.7-3.3-7-7-7s-7 3.3-7 7v41c0 8.2 9.2 17 20 17s20-9.2 20-20c0-13.3-13.4-21.8-26-18zm6 25c-4 0-7-3-7-7s3-7 7-7 7 3 7 7-3 7-7 7z"></path>
+  </svg>
+</svg>
diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest5.svg b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest5.svg
new file mode 100644
index 0000000000000000000000000000000000000000..bd5186f83cf6e22449aa5aca6bac4a78f3923fec
--- /dev/null
+++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest5.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0">
+  <rect width="100%" height="100%" fill="red" stroke-width="2" stroke="black" /> 
+  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="5 -20 64 68">
+    <path id="a" d="M14 27v-20c0-3.7-3.3-7-7-7s-7 3.3-7 7v41c0 8.2 9.2 17 20 17s20-9.2 20-20c0-13.3-13.4-21.8-26-18zm6 25c-4 0-7-3-7-7s3-7 7-7 7 3 7 7-3 7-7 7z"></path>
+  </svg>
+</svg>
diff --git a/typo3/sysext/fluid/Tests/Functional/Fixtures/crops.csv b/typo3/sysext/fluid/Tests/Functional/Fixtures/crops.csv
new file mode 100644
index 0000000000000000000000000000000000000000..620ec8e6452a9bb575555dd070e1b77ba19fcfae
--- /dev/null
+++ b/typo3/sysext/fluid/Tests/Functional/Fixtures/crops.csv
@@ -0,0 +1,29 @@
+sys_file_storage,,,,,,,,,,,,,,
+,uid,pid,name,driver,configuration,is_browsable,is_public,is_writable,is_online,,,,,
+,1,0,fileadmin,Local,"<?xml version=""1.0"" encoding=""utf-8"" standalone=""yes"" ?><T3FlexForms><data><sheet index=""sDEF""><language index=""lDEF""><field index=""basePath""><value index=""vDEF"">fileadmin/</value></field><field index=""pathType""><value index=""vDEF"">relative</value></field><field index=""caseSensitive""><value index=""vDEF"">1</value></field></language></sheet></data></T3FlexForms>",1,1,1,1,,,,,
+sys_file,,,,,,,,,,,,,,
+,uid,pid,storage,type,identifier,identifier_hash,folder_hash,extension,mime_type,name,sha1,size,creation_date,modification_date
+,1,0,1,2,/user_upload/FALImageViewHelperTest1.svg,cd3af075d80618729520920802d0fd25b535b74c,19669f1e02c2f16705ec7587044c66443be70725,svg,image/svg+xml,FALImageViewHelperTest1.svg,b6bcefe7804cb2f0de8d5d2834c7c546c3766dd7,64735,1389878273,1389878273
+,2,0,1,2,/user_upload/FALImageViewHelperTest2.svg,a5d36e35adfe231b5c01778462ebabbf12defd0f,19669f1e02c2f16705ec7587044c66443be70725,svg,image/svg+xml,FALImageViewHelperTest2.svg,575d40f604e1b46792dd6566bf6ff52ffae5d6a8,1211,1389878273,1389878273
+,3,0,1,2,/user_upload/FALImageViewHelperTest3.svg,5755173832a12c5fc4855b891b550544c075a5bd,19669f1e02c2f16705ec7587044c66443be70725,svg,image/svg+xml,FALImageViewHelperTest3.svg,7f9d82e8dfd85f85debc7a07080b1e7fb8c72358,17468,1389878273,1389878273
+,4,0,1,2,/user_upload/FALImageViewHelperTest4.svg,c39b710aded127bf0e37430983cabc2b48db1f4a,19669f1e02c2f16705ec7587044c66443be70725,svg,image/svg+xml,FALImageViewHelperTest4.svg,0930d53140d0c229baaccb8aa35177861996225e,64738,1389878273,1389878273
+,5,0,1,2,/user_upload/FALImageViewHelperTest5.svg,241648cd6f15a2ed19c7affe44a91adf717494a2,19669f1e02c2f16705ec7587044c66443be70725,svg,image/svg+xml,FALImageViewHelperTest5.svg,f10070e21cb7c53bcbca8549423c691e37738bf9,64711,1389878273,1389878273
+sys_file_reference,,,,,,,,,,,,,,
+,uid,pid,uid_local,uid_foreign,tablenames,fieldname,crop,,,,,,,
+,1,1,1,1,pages,media,"{""default"":{""cropArea"":{""height"":0.22666666666666666,""width"":0.1375,""x"":0.04583333333333333,""y"":0.5533333333333333},""selectedRatio"":""NaN"",""focusArea"":null}}",,,,,,,
+,2,1,2,1,pages,media,"{""default"":{""cropArea"":{""height"":0.21333333333333335,""width"":0.8533333333333334,""x"":0.06666666666666667,""y"":0.22},""selectedRatio"":""NaN"",""focusArea"":null}}",,,,,,,
+,3,1,3,1,pages,media,"{""default"":{""cropArea"":{""x"":0.24973432518597238,""y"":0.4185082872928177,""width"":0.18703506907545164,""height"":0.4419889502762431},""selectedRatio"":""NaN"",""focusArea"":null}}",,,,,,,
+,4,1,4,1,pages,media,"{""default"":{""cropArea"":{""height"":0.12476190476190477,""width"":0.06785714285714285,""x"":0.03511904761904762,""y"":0.32857142857142857},""selectedRatio"":""NaN"",""focusArea"":null}}",,,,,,,
+,5,1,5,1,pages,media,"{""default"":{""cropArea"":{""height"":1,""width"":0.18,""x"":0.2,""y"":0},""selectedRatio"":""NaN"",""focusArea"":null}}",,,,,,,
+,6,1,1,1,pages,media,,,,,,,,
+,7,1,2,1,pages,media,,,,,,,,
+,8,1,3,1,pages,media,,,,,,,,
+,9,1,4,1,pages,media,,,,,,,,
+,10,1,5,1,pages,media,,,,,,,,
+sys_file_metadata,,,,,,,,,,,,,,
+,uid,pid,file,width,height,,,,,,,,,
+,1,0,1,1680,1050,,,,,,,,,
+,2,0,2,283,283,,,,,,,,,
+,3,0,3,941,724,,,,,,,,,
+,4,0,4,1680,1050,,,,,,,,,
+,5,0,5,64,64,,,,,,,,,
diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/SvgImageViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/SvgImageViewHelperTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..db6c0536e7c48c3c868670ebeeecc1658b4353bc
--- /dev/null
+++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/SvgImageViewHelperTest.php
@@ -0,0 +1,540 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers;
+
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Test;
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+use TYPO3Fluid\Fluid\View\TemplateView;
+
+final class SvgImageViewHelperTest extends FunctionalTestCase
+{
+    protected array $coreExtensionsToLoad = ['filemetadata'];
+
+    protected array $pathsToProvideInTestInstance = [
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest1.svg' => 'fileadmin/user_upload/FALImageViewHelperTest1.svg',
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest2.svg' => 'fileadmin/user_upload/FALImageViewHelperTest2.svg',
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest3.svg' => 'fileadmin/user_upload/FALImageViewHelperTest3.svg',
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest4.svg' => 'fileadmin/user_upload/FALImageViewHelperTest4.svg',
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest5.svg' => 'fileadmin/user_upload/FALImageViewHelperTest5.svg',
+    ];
+
+    protected array $additionalFoldersToCreate = [
+        '/fileadmin/user_upload',
+    ];
+
+    public function setUp(): void
+    {
+        parent::setUp();
+        $this->importCSVDataSet(__DIR__ . '/../Fixtures/be_users.csv');
+        $this->importCSVDataSet(__DIR__ . '/../Fixtures/crops.csv');
+        $this->setUpBackendUser(1);
+    }
+
+    public static function renderReturnsExpectedMarkupDataProvider(): array
+    {
+        /** Used files:
+         * ===========
+         *
+         * ImageViewHelperTest1.svg: with viewBox, no width, no height, 0x0 origin
+         * ImageViewHelperTest2.svg: with viewBox, no width, no height, shifted origin
+         * ImageViewHelperTest3.svg: with viewBox, with width and height
+         * ImageViewHelperTest4.svg: no viewBox, with height and width
+        **/
+
+        // width, height, [scalingFactorBasedOnWidth (60px/80%)], [scalingFactorBasedOnOffset (15px/10%)], [pixelBasedOnOffset]
+        $dimensionMap = [
+            'ImageViewHelperTest1.svg' => [
+                'input'                 => [1680, 1050],
+                'fixedCrop60px'         => [60, 38, 0.03571428572, 0.008928571429, 15, 9],
+                'heightAtMaxWidth60px'  => 62,
+                'relativeCrop80Percent' => [1344, 840, 0.8, 0.1, 168, 105],
+                'falUidCropped'         => 1,
+                'falUidUncropped'       => 6,
+                'falCropString'         => '77 581 231 238',
+                'falCropDim'            => [231, 238],
+            ],
+            'ImageViewHelperTest2.svg' => [
+                'input'                 => [283.5, 283.5],
+                'fixedCrop60px'         => [60, 60, 0.2116402117, 0.05291005291, 14, 14],
+                'heightAtMaxWidth60px'  => 15,
+                'relativeCrop80Percent' => [226, 226, 0.8, 0.1, 28, 28],
+                'falUidCropped'         => 2,
+                'falUidUncropped'       => 7,
+                'falCropString'         => '18 62 241 60',
+                'falCropDim'            => [241, 60],
+            ],
+            'ImageViewHelperTest3.svg' => [
+                'input'                 => [940.7, 724],
+                'fixedCrop60px'         => [60, 46, 0.06378228979, 0.01594557245, 15, 11],
+                'heightAtMaxWidth60px'  => 109,
+                'relativeCrop80Percent' => [753, 579, 0.8, 0.1, 94, 72],
+                'falUidCropped'         => 3,
+                'falUidUncropped'       => 8,
+                'falCropString'         => '235 303 176 320',
+                'falCropDim'            => [176, 320],
+            ],
+            'ImageViewHelperTest4.svg' => [
+                'input'                 => [1680, 1050],
+                'fixedCrop60px'         => [60, 38, 0.03571428572, 0.008928571429, 15, 9],
+                'heightAtMaxWidth60px'  => 69,
+                'relativeCrop80Percent' => [1344, 840, 0.8, 0.1, 168, 105],
+                'falUidCropped'         => 4,
+                'falUidUncropped'       => 9,
+                'falCropString'         => '59 345 114 131',
+                'falCropDim'            => [113, 131],
+            ],
+        ];
+
+        $expected = [];
+
+        $maximum = count($dimensionMap);
+
+        $storageDirOriginal = 'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers';
+        $storageDirTemp     = 'typo3temp/assets/_processed_/[0-9a-f]/[0-9a-f]';
+        $storageDirFal      = 'fileadmin/user_upload';
+        $storageDirFalTemp  = 'fileadmin/_processed_/[0-9a-f]/[0-9a-f]';
+
+        // To prevent excess copy and paste labor, this is done programmatically:
+        for ($i = 1; $i <= $maximum; $i++) {
+            $fn = 'ImageViewHelperTest' . $i . '.svg';
+            $fUid = $dimensionMap[$fn]['falUidCropped'];
+            $fUidUncropped = $dimensionMap[$fn]['falUidUncropped'];
+
+            $width  = round($dimensionMap[$fn]['input'][0]);
+            $height = round($dimensionMap[$fn]['input'][1]);
+
+            // Note: Uncropped SVGs are returned from their original location. No conversion/tampering is done.
+
+            //# SECTION 1: Referenced via EXT: ###
+            $expected[sprintf('no crop (%s)', $fn)] = [
+                sprintf(
+                    '<f:image src="EXT:fluid/Tests/Functional/Fixtures/ViewHelpers/%s" width="%d" height="%d" />',
+                    $fn,
+                    $width,
+                    $height,
+                ),
+                sprintf(
+                    '@^<img src="(%s/%s)" width="%d" height="%d" alt="" />$@',
+                    $storageDirOriginal,
+                    $fn,
+                    $width,
+                    $height,
+                ),
+                null,
+                false,
+            ];
+
+            $expected[sprintf('empty crop (%s)', $fn)] = [
+                sprintf(
+                    '<f:image src="EXT:fluid/Tests/Functional/Fixtures/ViewHelpers/%s" width="%d" height="%d" crop="null" />',
+                    $fn,
+                    $width,
+                    $height,
+                ),
+                sprintf(
+                    '@^<img src="(%s/%s)" width="%d" height="%d" alt="" />$@',
+                    $storageDirOriginal,
+                    $fn,
+                    $width,
+                    $height,
+                ),
+                null,
+                false,
+            ];
+
+            $expected[sprintf('crop as array - forced 60px (%s)', $fn)] = [
+                sprintf(
+                    '<f:image src="EXT:fluid/Tests/Functional/Fixtures/ViewHelpers/%1$s" width="%2$d" height="%3$d" crop="{\'default\':{\'cropArea\':{\'width\':%4$s,\'height\':%4$s,\'x\':%5$s,\'y\':%5$s},\'selectedRatio\':\'1:1\',\'focusArea\':null}}" />',
+                    $fn,
+                    $dimensionMap[$fn]['fixedCrop60px'][0], // width
+                    $dimensionMap[$fn]['fixedCrop60px'][1], // height
+                    $dimensionMap[$fn]['fixedCrop60px'][2], // crop-string width/height
+                    $dimensionMap[$fn]['fixedCrop60px'][3] // crop-string offset left/top
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_ImageViewHelperTest%d_.*\.svg)" width="%d" height="%d" alt="" />$@',
+                    $storageDirTemp,
+                    $i,
+                    $dimensionMap[$fn]['fixedCrop60px'][0],
+                    $dimensionMap[$fn]['fixedCrop60px'][1],
+                ),
+                $dimensionMap[$fn]['fixedCrop60px'][4] . ' ' . $dimensionMap[$fn]['fixedCrop60px'][5] . ' ' . $dimensionMap[$fn]['fixedCrop60px'][0] . ' ' . $dimensionMap[$fn]['fixedCrop60px'][1],
+            ];
+
+            $expected[sprintf('crop as array - no width/height (%s)', $fn)] = [
+                sprintf(
+                    '<f:image src="EXT:fluid/Tests/Functional/Fixtures/ViewHelpers/%1$s" crop="{\'default\':{\'cropArea\':{\'width\':%2$s,\'height\':%2$s,\'x\':%3$s,\'y\':%3$s},\'selectedRatio\':\'1:1\',\'focusArea\':null}}" />',
+                    $fn,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][2], // crop-string width/height
+                    $dimensionMap[$fn]['relativeCrop80Percent'][3] // crop-string offset left/top
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_ImageViewHelperTest%d_.*\.svg)" width="%d" height="%d" alt="" />$@',
+                    $storageDirTemp,
+                    $i,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][0],
+                    $dimensionMap[$fn]['relativeCrop80Percent'][1],
+                ),
+                $dimensionMap[$fn]['relativeCrop80Percent'][4] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][5] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][0] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][1],
+            ];
+
+            $expected[sprintf('force pixel-conversion, no crop (%s)', $fn)] = [
+                sprintf(
+                    '<f:image src="EXT:fluid/Tests/Functional/Fixtures/ViewHelpers/%s" width="%d" height="%d" fileExtension="png" />',
+                    $fn,
+                    $width,
+                    $height,
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_ImageViewHelperTest%d_.*\.png)" width="%d" height="%d" alt="" />$@',
+                    $storageDirTemp,
+                    $i,
+                    $width,
+                    $height,
+                ),
+                null,
+            ];
+
+            $expected[sprintf('force pixel-conversion, with crop (%s)', $fn)] = [
+                sprintf(
+                    '<f:image src="EXT:fluid/Tests/Functional/Fixtures/ViewHelpers/%1$s" fileExtension="png" width="%2$d" height="%3$d" crop="{\'default\':{\'cropArea\':{\'width\':%4$s,\'height\':%4$s,\'x\':%5$s,\'y\':%5$s},\'selectedRatio\':\'1:1\',\'focusArea\':null}}" />',
+                    $fn,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][0], // width
+                    $dimensionMap[$fn]['relativeCrop80Percent'][1], // height
+                    $dimensionMap[$fn]['relativeCrop80Percent'][2], // crop-string width/height
+                    $dimensionMap[$fn]['relativeCrop80Percent'][3] // crop-string offset left/top
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_ImageViewHelperTest%d_.*\.png)" width="%d" height="%d" alt="" />$@',
+                    $storageDirTemp,
+                    $i,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][0],
+                    $dimensionMap[$fn]['relativeCrop80Percent'][1],
+                ),
+                $dimensionMap[$fn]['relativeCrop80Percent'][4] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][5] . $dimensionMap[$fn]['relativeCrop80Percent'][0] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][1],
+            ];
+            //############################################################################
+
+            //# SECTION 2: Referenced via UID, cropped via sys_file_reference (with overrides) ###
+            // width/height is using the original dimensions, contained crop will be rendered within
+            $expected[sprintf('using sys_file_reference crop (UID %d)', $fUid)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" width="%2$d" height="%3$d" />',
+                    $fUid,
+                    $width,
+                    $height,
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.svg)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $width,
+                    $height,
+                ),
+                $dimensionMap[$fn]['falCropString'], // Stored in sys_file_reference
+                true,
+            ];
+
+            $expected[sprintf('using sys_file_reference crop, using maxWidth (60px, UID %d)', $fUid)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" maxWidth="60" />',
+                    $fUid,
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.svg)" width="60" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $dimensionMap[$fn]['heightAtMaxWidth60px']
+                ),
+                $dimensionMap[$fn]['falCropString'], // Stored in sys_file_reference
+                true,
+            ];
+
+            $expected[sprintf('empty crop (UID %d)', $fUid)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" width="%2$d" height="%3$d" crop="null" />',
+                    $fUid,
+                    $width,
+                    $height,
+                ),
+                sprintf(
+                    '@^<img src="(%s/%s)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFal,
+                    'FAL' . $fn,
+                    $width,
+                    $height,
+                ),
+                null,
+                false,
+            ];
+
+            $expected[sprintf('crop as array - forced 60px (UID %d)', $fUid)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" width="%2$d" height="%3$d" crop="{\'default\':{\'cropArea\':{\'width\':%4$s,\'height\':%4$s,\'x\':%5$s,\'y\':%5$s},\'selectedRatio\':\'1:1\',\'focusArea\':null}}" />',
+                    $fUid,
+                    $dimensionMap[$fn]['fixedCrop60px'][0], // width
+                    $dimensionMap[$fn]['fixedCrop60px'][1], // height
+                    $dimensionMap[$fn]['fixedCrop60px'][2], // crop-string width/height
+                    $dimensionMap[$fn]['fixedCrop60px'][3] // crop-string offset left/top
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.svg)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $dimensionMap[$fn]['fixedCrop60px'][0],
+                    $dimensionMap[$fn]['fixedCrop60px'][1],
+                ),
+                $dimensionMap[$fn]['fixedCrop60px'][4] . ' ' . $dimensionMap[$fn]['fixedCrop60px'][5] . ' ' . $dimensionMap[$fn]['fixedCrop60px'][0] . ' ' . $dimensionMap[$fn]['fixedCrop60px'][1],
+            ];
+
+            $expected[sprintf('crop as array - no width/height (UID %d)', $fUid)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" crop="{\'default\':{\'cropArea\':{\'width\':%2$s,\'height\':%2$s,\'x\':%3$s,\'y\':%3$s},\'selectedRatio\':\'1:1\',\'focusArea\':null}}" />',
+                    $fUid,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][2], // crop-string width/height
+                    $dimensionMap[$fn]['relativeCrop80Percent'][3] // crop-string offset left/top
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.svg)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][0],
+                    $dimensionMap[$fn]['relativeCrop80Percent'][1],
+                ),
+                $dimensionMap[$fn]['relativeCrop80Percent'][4] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][5] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][0] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][1],
+            ];
+
+            $expected[sprintf('force pixel-conversion, sys_file_reference crop (UID %d)', $fUid)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" width="%2$d" height="%3$d" fileExtension="png" />',
+                    $fUid,
+                    $width,
+                    $height,
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.png)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $width,
+                    $height,
+                ),
+                null,
+            ];
+
+            $expected[sprintf('force pixel-conversion, with crop (UID %d)', $fUid)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" fileExtension="png" width="%2$d" height="%3$d" crop="{\'default\':{\'cropArea\':{\'width\':%4$s,\'height\':%4$s,\'x\':%5$s,\'y\':%5$s},\'selectedRatio\':\'1:1\',\'focusArea\':null}}" />',
+                    $fUid,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][0], // width
+                    $dimensionMap[$fn]['relativeCrop80Percent'][1], // height
+                    $dimensionMap[$fn]['relativeCrop80Percent'][2], // crop-string width/height
+                    $dimensionMap[$fn]['relativeCrop80Percent'][3] // crop-string offset left/top
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.png)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][0],
+                    $dimensionMap[$fn]['relativeCrop80Percent'][1],
+                ),
+                $dimensionMap[$fn]['relativeCrop80Percent'][4] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][5] . $dimensionMap[$fn]['relativeCrop80Percent'][0] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][1],
+            ];
+            //############################################################################
+
+            //# SECTION 3: Referenced via UID, uncropped in sys_file_reference ###
+            $expected[sprintf('no crop (uncrop-UID %d)', $fUidUncropped)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" width="%2$d" height="%3$d" />',
+                    $fUidUncropped,
+                    $width,
+                    $height,
+                ),
+                sprintf(
+                    '@^<img src="(%s/%s)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFal,
+                    'FAL' . $fn,
+                    $width,
+                    $height,
+                ),
+                null,
+                false,
+            ];
+
+            $expected[sprintf('empty crop (uncrop-UID %d)', $fUidUncropped)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" width="%2$d" height="%3$d" crop="null" />',
+                    $fUidUncropped,
+                    $width,
+                    $height,
+                ),
+                sprintf(
+                    '@^<img src="(%s/%s)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFal,
+                    'FAL' . $fn,
+                    $width,
+                    $height,
+                ),
+                null,
+                false,
+            ];
+
+            $expected[sprintf('crop as array - forced 60px (uncrop-UID %d)', $fUidUncropped)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" width="%2$d" height="%3$d" crop="{\'default\':{\'cropArea\':{\'width\':%4$s,\'height\':%4$s,\'x\':%5$s,\'y\':%5$s},\'selectedRatio\':\'1:1\',\'focusArea\':null}}" />',
+                    $fUidUncropped,
+                    $dimensionMap[$fn]['fixedCrop60px'][0], // width
+                    $dimensionMap[$fn]['fixedCrop60px'][1], // height
+                    $dimensionMap[$fn]['fixedCrop60px'][2], // crop-string width/height
+                    $dimensionMap[$fn]['fixedCrop60px'][3] // crop-string offset left/top
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.svg)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $dimensionMap[$fn]['fixedCrop60px'][0],
+                    $dimensionMap[$fn]['fixedCrop60px'][1],
+                ),
+                $dimensionMap[$fn]['fixedCrop60px'][4] . ' ' . $dimensionMap[$fn]['fixedCrop60px'][5] . ' ' . $dimensionMap[$fn]['fixedCrop60px'][0] . ' ' . $dimensionMap[$fn]['fixedCrop60px'][1],
+            ];
+
+            $expected[sprintf('crop as array - no width/height (uncrop-UID %d)', $fUidUncropped)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" crop="{\'default\':{\'cropArea\':{\'width\':%2$s,\'height\':%2$s,\'x\':%3$s,\'y\':%3$s},\'selectedRatio\':\'1:1\',\'focusArea\':null}}" />',
+                    $fUidUncropped,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][2], // crop-string width/height
+                    $dimensionMap[$fn]['relativeCrop80Percent'][3] // crop-string offset left/top
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.svg)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][0],
+                    $dimensionMap[$fn]['relativeCrop80Percent'][1],
+                ),
+                $dimensionMap[$fn]['relativeCrop80Percent'][4] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][5] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][0] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][1],
+            ];
+
+            $expected[sprintf('force pixel-conversion, no crop (uncrop-UID %d)', $fUidUncropped)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" width="%2$d" height="%3$d" fileExtension="png" />',
+                    $fUidUncropped,
+                    $width,
+                    $height,
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.png)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $width,
+                    $height,
+                ),
+                null,
+            ];
+
+            $expected[sprintf('force pixel-conversion, with crop (uncrop-UID %d)', $fUidUncropped)] = [
+                sprintf(
+                    '<f:image src="%1$d" treatIdAsReference="true" fileExtension="png" width="%2$d" height="%3$d" crop="{\'default\':{\'cropArea\':{\'width\':%4$s,\'height\':%4$s,\'x\':%5$s,\'y\':%5$s},\'selectedRatio\':\'1:1\',\'focusArea\':null}}" />',
+                    $fUidUncropped,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][0], // width
+                    $dimensionMap[$fn]['relativeCrop80Percent'][1], // height
+                    $dimensionMap[$fn]['relativeCrop80Percent'][2], // crop-string width/height
+                    $dimensionMap[$fn]['relativeCrop80Percent'][3] // crop-string offset left/top
+                ),
+                sprintf(
+                    '@^<img src="(%s/csm_FALImageViewHelperTest%d_.*\.png)" width="%d" height="%d" alt="" />$@',
+                    $storageDirFalTemp,
+                    $i,
+                    $dimensionMap[$fn]['relativeCrop80Percent'][0],
+                    $dimensionMap[$fn]['relativeCrop80Percent'][1],
+                ),
+                $dimensionMap[$fn]['relativeCrop80Percent'][4] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][5] . $dimensionMap[$fn]['relativeCrop80Percent'][0] . ' ' . $dimensionMap[$fn]['relativeCrop80Percent'][1],
+            ];
+            //############################################################################
+        }
+
+        // Iterate the whole array, utilize and test f:uri.image with the same inputs.
+        // This is done if in the future the two viewHelpers may diverge from each other,
+        // to still perform all tests properly.
+        $uriImageCopy = $expected;
+        foreach ($uriImageCopy as $expectedKey => $imageViewHelperGreatExpectations) {
+            // Switch and bait execution string
+            $imageViewHelperGreatExpectations[0] = str_replace('<f:image', '<f:uri.image', $imageViewHelperGreatExpectations[0]);
+            // ... and expectation string
+            $imageViewHelperGreatExpectations[1] = '@^' . preg_replace('@^.+src="(.+)".+$@imsU', '\1', $imageViewHelperGreatExpectations[1]) . '$@';
+
+            // ... and append to the main data provider
+            $expected[$expectedKey . ' (f:uri.image)'] = $imageViewHelperGreatExpectations;
+        }
+
+        return $expected;
+    }
+
+    #[DataProvider('renderReturnsExpectedMarkupDataProvider')]
+    #[Test]
+    public function renderReturnsExpectedMarkup(string $template, string $expected, string|null $cropResult, bool $expectProcessedFile = true): void
+    {
+        $context = $this->get(RenderingContextFactory::class)->create();
+        $context->getTemplatePaths()->setTemplateSource($template);
+        $actual = (new TemplateView($context))->render();
+        self::assertMatchesRegularExpression($expected, $actual);
+
+        $dumpTables = [
+            'sys_file_processedfile' => 1,
+        ];
+
+        foreach ($dumpTables as $dumpTable => $expectedRecords) {
+            $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable($dumpTable);
+            $rows =
+                $queryBuilder
+                    ->select('*')
+                    ->from($dumpTable)
+                    ->executeQuery()
+                    ->fetchAllAssociative();
+
+            self::assertEquals(count($rows), $expectedRecords, sprintf('Expected post-conversion database records in %s do not match.', $dumpTable));
+
+            if ($dumpTable === 'sys_file_processedfile' && $expectProcessedFile) {
+                // Only SVGs count
+                if (str_ends_with($rows[0]['identifier'], '.svg')) {
+                    $this->verifySvg($rows[0], $cropResult);
+                }
+            }
+        }
+    }
+
+    protected function verifySvg(array $file, string|null $cropResult)
+    {
+        if ($file['storage'] == 1) {
+            $dir = Environment::getPublicPath() . '/fileadmin';
+        } else {
+            $dir = Environment::getPublicPath();
+        }
+
+        $svg = new \DOMDocument();
+        $svg->load($dir . $file['identifier']);
+
+        self::assertEquals($file['width'], $svg->documentElement->getAttribute('width'), 'SVG "width" mismatch.');
+        self::assertEquals($file['height'], $svg->documentElement->getAttribute('height'), 'SVG "height" mismatch.');
+        self::assertEquals($cropResult, $svg->documentElement->getAttribute('viewBox'), 'SVG "viewBox" (crop) mismatch.');
+        unlink($dir . $file['identifier']);
+    }
+
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/Rendering/Fixtures/SvgImageRenderingTest.typoscript b/typo3/sysext/frontend/Tests/Functional/Rendering/Fixtures/SvgImageRenderingTest.typoscript
new file mode 100644
index 0000000000000000000000000000000000000000..6654132e3eb8f5086735ac1bf489859b95185062
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/Rendering/Fixtures/SvgImageRenderingTest.typoscript
@@ -0,0 +1,175 @@
+page = PAGE
+page {
+  # SECTION1: Specific files, forced crop
+  20 = IMAGE
+  20 {
+    file = {$localImage1}
+    file.crop = 10,10,500,500
+  }
+
+  30 = IMAGE
+  30 {
+    file = {$localImage2}
+    file.crop = 10,10,500,500
+  }
+
+  40 = IMAGE
+  40 {
+    file = {$localImage3}
+    file.crop = 10,10,500,500
+  }
+
+  50 = IMAGE
+  50 {
+    file = {$localImage4}
+    file.crop = 10,10,500,500
+  }
+
+  # SECTION2: Specific files, forced crop, force pixel image
+  120 = IMAGE
+  120 {
+    file = {$localImage1}
+    file.crop = 10,10,500,500
+    file.ext = png
+  }
+
+  130 = IMAGE
+  130 {
+    file = {$localImage2}
+    file.crop = 10,10,500,500
+    file.ext = png
+  }
+
+  140 = IMAGE
+  140 {
+    file = {$localImage3}
+    file.crop = 10,10,500,500
+    file.ext = png
+  }
+
+  150 = IMAGE
+  150 {
+    file = {$localImage4}
+    file.crop = 10,10,500,500
+    file.ext = png
+  }
+
+
+  # SECTION3: FAL Items, sys_file_reference crop
+  220 = IMAGE
+  220 {
+    file = {$localImage1Uid}
+    file.treatIdAsReference = true
+  }
+
+  230 = IMAGE
+  230 {
+    file = {$localImage2Uid}
+    file.treatIdAsReference = true
+  }
+
+  240 = IMAGE
+  240 {
+    file = {$localImage3Uid}
+    file.treatIdAsReference = true
+  }
+
+  250 = IMAGE
+  250 {
+    file = {$localImage4Uid}
+    file.treatIdAsReference = true
+  }
+
+  # SECTION4: FAL Items, sys_file_reference crop, force pixel image
+  320 = IMAGE
+  320 {
+    file = {$localImage1Uid}
+    file.ext = png
+    file.treatIdAsReference = true
+  }
+
+  330 = IMAGE
+  330 {
+    file = {$localImage2Uid}
+    file.ext = png
+    file.treatIdAsReference = true
+
+  }
+
+  340 = IMAGE
+  340 {
+    file = {$localImage3Uid}
+    file.ext = png
+    file.treatIdAsReference = true
+  }
+
+  350 = IMAGE
+  350 {
+    file = {$localImage4Uid}
+    file.ext = png
+    file.treatIdAsReference = true
+  }
+
+  # SECTION5: FAL Items, override crop
+  420 = IMAGE
+  420 {
+    file = {$localImage1UidUncrop}
+    file.crop = 10,10,500,500
+    file.treatIdAsReference = true
+  }
+
+  430 = IMAGE
+  430 {
+    file = {$localImage2UidUncrop}
+    file.crop = 10,10,500,500
+    file.treatIdAsReference = true
+  }
+
+  440 = IMAGE
+  440 {
+    file = {$localImage3UidUncrop}
+    file.crop = 10,10,500,500
+    file.treatIdAsReference = true
+  }
+
+  450 = IMAGE
+  450 {
+    file = {$localImage4UidUncrop}
+    file.crop = 10,10,500,500
+    file.treatIdAsReference = true
+  }
+
+  # SECTION6: FAL Items, override crop, force pixel image
+  520 = IMAGE
+  520 {
+    file = {$localImage1UidUncrop}
+    file.crop = 10,10,500,500
+    file.ext = png
+    file.treatIdAsReference = true
+  }
+
+  530 = IMAGE
+  530 {
+    file = {$localImage2UidUncrop}
+    file.crop = 10,10,500,500
+    file.ext = png
+    file.treatIdAsReference = true
+  }
+
+  540 = IMAGE
+  540 {
+    file = {$localImage3UidUncrop}
+    file.crop = 10,10,500,500
+    file.ext = png
+    file.treatIdAsReference = true
+  }
+
+  550 = IMAGE
+  550 {
+    file = {$localImage4UidUncrop}
+    file.crop = 10,10,500,500
+    file.ext = png
+    file.treatIdAsReference = true
+  }
+
+}
diff --git a/typo3/sysext/frontend/Tests/Functional/Rendering/SvgImageRenderingTest.php b/typo3/sysext/frontend/Tests/Functional/Rendering/SvgImageRenderingTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..403f5c4af6c9c7253ff1d2ffe37aed26a4b366f2
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/Rendering/SvgImageRenderingTest.php
@@ -0,0 +1,182 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Frontend\Tests\Functional\Rendering;
+
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Test;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+final class SvgImageRenderingTest extends FunctionalTestCase
+{
+    use SiteBasedTestTrait;
+
+    /**
+     * @var string[]
+     */
+    private array $definedResources = [
+        'localImage1' => 'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest1.svg',
+        'localImage2' => 'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest2.svg',
+        'localImage3' => 'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest3.svg',
+        'localImage4' => 'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest4.svg',
+
+        'localImage1Uid' => '1',
+        'localImage2Uid' => '2',
+        'localImage3Uid' => '3',
+        'localImage4Uid' => '4',
+
+        'localImage1UidUncrop' => '6',
+        'localImage2UidUncrop' => '7',
+        'localImage3UidUncrop' => '8',
+        'localImage4UidUncrop' => '9',
+    ];
+
+    protected array $pathsToProvideInTestInstance = [
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest1.svg' => 'fileadmin/user_upload/FALImageViewHelperTest1.svg',
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest2.svg' => 'fileadmin/user_upload/FALImageViewHelperTest2.svg',
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest3.svg' => 'fileadmin/user_upload/FALImageViewHelperTest3.svg',
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest4.svg' => 'fileadmin/user_upload/FALImageViewHelperTest4.svg',
+        'typo3/sysext/fluid/Tests/Functional/Fixtures/ViewHelpers/ImageViewHelperTest5.svg' => 'fileadmin/user_upload/FALImageViewHelperTest5.svg',
+    ];
+
+    protected array $additionalFoldersToCreate = [
+        '/fileadmin/user_upload',
+    ];
+
+    protected array $coreExtensionsToLoad = ['rte_ckeditor'];
+
+    protected const LANGUAGE_PRESETS = [
+        'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8', 'iso' => 'en'],
+    ];
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->importCsvDataSet(__DIR__ . '/../../../../core/Tests/Functional/Fixtures/pages.csv');
+        $this->importCSVDataSet(__DIR__ . '/../../../../fluid/Tests/Functional/Fixtures/crops.csv');
+
+        $this->writeSiteConfiguration(
+            'test',
+            $this->buildSiteConfiguration(1, '/'),
+            [
+                $this->buildDefaultLanguageConfiguration('EN', '/en/'),
+            ],
+            $this->buildErrorHandlingConfiguration('Fluid', [404]),
+        );
+        $this->setUpFrontendRootPage(
+            1,
+            ['EXT:frontend/Tests/Functional/Rendering/Fixtures/SvgImageRenderingTest.typoscript']
+        );
+        $this->setTypoScriptConstantsToTemplateRecord(
+            1,
+            $this->compileTypoScriptConstants($this->definedResources)
+        );
+    }
+
+    public static function svgsAreRenderedWithTyposcriptDataProvider(): array
+    {
+        return [
+            'rendered svg assets contains' => [
+                [
+                    '@<img src="/typo3temp/assets/_processed_/[0-9a-f]/[0-9a-f]/csm_ImageViewHelperTest1_.*\.svg" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/typo3temp/assets/_processed_/[0-9a-f]/[0-9a-f]/csm_ImageViewHelperTest2_.*\.svg" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/typo3temp/assets/_processed_/[0-9a-f]/[0-9a-f]/csm_ImageViewHelperTest3_.*\.svg" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/typo3temp/assets/_processed_/[0-9a-f]/[0-9a-f]/csm_ImageViewHelperTest4_.*\.svg" width="500" height="500"\s+alt=""\s+/?>@U',
+
+                    '@<img src="/typo3temp/assets/_processed_/[0-9a-f]/[0-9a-f]/csm_ImageViewHelperTest1_.*\.png" width="500" height="500"\s+alt=""\s+/?>@U',
+                    // @todo should be 273x273 (or 274x274)
+                    '@<img src="/typo3temp/assets/_processed_/[0-9a-f]/[0-9a-f]/csm_ImageViewHelperTest2_.*\.png" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/typo3temp/assets/_processed_/[0-9a-f]/[0-9a-f]/csm_ImageViewHelperTest3_.*\.png" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/typo3temp/assets/_processed_/[0-9a-f]/[0-9a-f]/csm_ImageViewHelperTest4_.*\.png" width="500" height="500"\s+alt=""\s+/?>@U',
+
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest1_.*\.svg" width="231" height="238"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest2_.*\.svg" width="241" height="60"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest3_.*\.svg" width="176" height="320"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest4_.*\.svg" width="114" height="131"\s+alt=""\s+/?>@U',
+
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest1_.*\.png" width="231" height="238"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest2_.*\.png" width="241" height="60"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest3_.*\.png" width="176" height="320"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest4_.*\.png" width="114" height="131"\s+alt=""\s+/?>@U',
+
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest1_.*\.svg" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest2_.*\.svg" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest3_.*\.svg" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest4_.*\.svg" width="500" height="500"\s+alt=""\s+/?>@U',
+
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest1_.*\.png" width="500" height="500"\s+alt=""\s+/?>@U',
+                    // @todo should be 273x273 (or 274x274)
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest2_.*\.png" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest3_.*\.png" width="500" height="500"\s+alt=""\s+/?>@U',
+                    '@<img src="/fileadmin/_processed_/[0-9a-f]/[0-9a-f]/csm_FALImageViewHelperTest4_.*\.png" width="500" height="500"\s+alt=""\s+/?>@U',
+                ],
+            ],
+        ];
+    }
+
+    #[DataProvider('svgsAreRenderedWithTyposcriptDataProvider')]
+    #[Test]
+    public function svgsAreRenderedWithTyposcript(array $expectedAssets): void
+    {
+        $response = $this->executeFrontendSubRequest(
+            (new InternalRequest())->withQueryParameters([
+                'id' => 1,
+            ])
+        );
+        $content = (string)$response->getBody();
+
+        preg_match('@<body>(.+)</body>@imsU', $content, $bodyContent);
+        self::assertIsArray($bodyContent);
+
+        foreach ($expectedAssets as $expectedAsset) {
+            self::assertMatchesRegularExpression($expectedAsset, $bodyContent[1]);
+        }
+    }
+
+    /**
+     * Adds TypoScript constants snippet to the existing template record
+     */
+    protected function setTypoScriptConstantsToTemplateRecord(int $pageId, string $constants, bool $append = false): void
+    {
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_template');
+
+        $template = $connection->select(['uid', 'constants'], 'sys_template', ['pid' => $pageId, 'root' => 1])->fetchAssociative();
+        if (empty($template)) {
+            self::fail('Cannot find root template on page with id: "' . $pageId . '"');
+        }
+        $updateFields = [];
+        $updateFields['constants'] = ($append ? $template['constants'] . LF : '') . $constants;
+        $connection->update(
+            'sys_template',
+            $updateFields,
+            ['uid' => $template['uid']]
+        );
+    }
+
+    protected function compileTypoScriptConstants(array $constants): string
+    {
+        $lines = [];
+        foreach ($constants as $constantName => $constantValue) {
+            $lines[] = $constantName . ' = ' . $constantValue;
+        }
+        return implode(PHP_EOL, $lines);
+    }
+}