[FEATURE] Add responsive Image Rendering 52/22052/32
authorMartin Ficzel <ficzel@work.de>
Sat, 6 Jul 2013 17:06:03 +0000 (19:06 +0200)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Thu, 10 Oct 2013 13:30:25 +0000 (15:30 +0200)
The Image cObject can render a sourceCollection to support
different display-resolutions and screen sizes.

Resolves: #49723
Releases: 6.2
Change-Id: Ief02532f8f0a4e8e4b78ba15e7a9fd47c6cbd463
Reviewed-on: https://review.typo3.org/22052
Reviewed-by: Christian Kuhn
Tested-by: Christian Kuhn
Reviewed-by: Sebastian Fischer
Tested-by: Sebastian Fischer
Reviewed-by: Anja Leichsenring
Tested-by: Anja Leichsenring
typo3/sysext/css_styled_content/static/constants.txt
typo3/sysext/css_styled_content/static/setup.txt
typo3/sysext/frontend/Classes/ContentObject/ContentObjectOneSourceCollectionHookInterface.php [new file with mode: 0644]
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
typo3/sysext/frontend/Classes/Page/PageGenerator.php
typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php

index 64e35c0..4f7603b 100644 (file)
@@ -33,6 +33,12 @@ styles.content.imgtext {
   maxW = 600
     # cat=content/cImage/b2; type=int+; label= Max Image Width (Text): Same as above, but this is the maximum width when text is wrapped around an imageblock. Default is 50% of the normal Max Image Width.
   maxWInText =
+    # cat=content/cImage/b3; type=boolean; label= Responsive rendering for images
+  responsive =
+    # cat=content/cImage/b4; type=options[Default img-tag=default, img-tag with alternate sources as srcset-attribute=srcset, picture-tag with source-child-tags=picture, img-tag with alternate sources as data-attributes=data]; label= Rendering-type for responsive images.
+  layoutKey = default
+
+
     # cat=content/cImage/c1; type=int+; label= Click-enlarge Image Width: This specifies the width of the enlarged image when click-enlarge is enabled.
   linkWrap.width = 800m
   linkWrap.height = 600m
index 35e3dae..8e05232 100644 (file)
@@ -660,6 +660,47 @@ tt_content.image.20 {
        1 {
                file.import.current = 1
                file.width.field = imagewidth
+
+               layoutKey = {$styles.content.imgtext.layoutKey}
+               layout {
+                       default {
+                               element = <img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>
+                       }
+                       srcset {
+                               element = <img src="###SRC###" srcset="###SOURCECOLLECTION###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>
+                               source = |*|###SRC### ###SRCSETCANDIDATE###,|*|###SRC### ###SRCSETCANDIDATE###
+                       }
+                       picture {
+                               element = <picture>###SOURCECOLLECTION###<img src="###SRC###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###></picture>
+                               source = <source src="###SRC###" media="###MEDIAQUERY###"###SELFCLOSINGTAGSLASH###>
+                       }
+                       data {
+                               element = <img src="###SRC###" ###SOURCECOLLECTION### ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>
+                               source = data-###DATAKEY###="###SRC###"
+                       }
+               }
+               # Default sourceCollection as an example
+               # Please write your own sourceCollection (highly recommended)
+               sourceCollection {
+                       small {
+                               width = 200
+
+                               srcsetCandidate = 600w
+                               mediaQuery = (max-device-width: 600px)
+                               dataKey = small
+                       }
+                       smallRetina {
+                               if.directReturn = 0
+
+                               width = 200
+                               pixelDensity = 2
+
+                               srcsetCandidate = 600w 2x
+                               mediaQuery = (max-device-width: 600px) AND (min-resolution: 192dpi)
+                               dataKey = smallRetina
+                       }
+               }
+
                imageLinkWrap = 1
                imageLinkWrap {
                        bodyTag = <body style="margin:0; background:#fff;">
@@ -860,34 +901,134 @@ tt_content.image.20 {
                key.field = imageorient
                # above-center
                default = TEXT
-               default.value = <div class="csc-textpic csc-textpic-center csc-textpic-above###CLASSES###">###IMAGES######TEXT###</div>
+               default {
+                       value = <div class="csc-textpic csc-textpic-center csc-textpic-above###CLASSES###">###IMAGES######TEXT###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-center csc-textpic-above###CLASSES###">###IMAGES######TEXT###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
                # above-right
                1 = TEXT
-               1.value = <div class="csc-textpic csc-textpic-right csc-textpic-above###CLASSES###">###IMAGES######TEXT###</div>
+               1 {
+                       value = <div class="csc-textpic csc-textpic-right csc-textpic-above###CLASSES###">###IMAGES######TEXT###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-right csc-textpic-above###CLASSES###">###IMAGES######TEXT###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
                # above-left
                2 = TEXT
-               2.value = <div class="csc-textpic csc-textpic-left csc-textpic-above###CLASSES###">###IMAGES######TEXT###</div>
+               2 {
+                       value = <div class="csc-textpic csc-textpic-left csc-textpic-above###CLASSES###">###IMAGES######TEXT###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-left csc-textpic-above###CLASSES###">###IMAGES######TEXT###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
                # below-center
                8 = TEXT
-               8.value = <div class="csc-textpic csc-textpic-center csc-textpic-below###CLASSES###">###TEXT######IMAGES###</div>
+               8 {
+                       value = <div class="csc-textpic csc-textpic-center csc-textpic-below###CLASSES###">###TEXT######IMAGES###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-center csc-textpic-below###CLASSES###">###TEXT######IMAGES###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
                # below-right
                9 = TEXT
-               9.value = <div class="csc-textpic csc-textpic-right csc-textpic-below###CLASSES###">###TEXT######IMAGES###</div>
+               9 {
+                       value = <div class="csc-textpic csc-textpic-right csc-textpic-below###CLASSES###">###TEXT######IMAGES###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-right csc-textpic-below###CLASSES###">###TEXT######IMAGES###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
                # below-left
                10 = TEXT
-               10.value = <div class="csc-textpic csc-textpic-left csc-textpic-below###CLASSES###">###TEXT######IMAGES###</div>
+               10 {
+                       value = <div class="csc-textpic csc-textpic-left csc-textpic-below###CLASSES###">###TEXT######IMAGES###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-left csc-textpic-below###CLASSES###">###TEXT######IMAGES###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
                # intext-right
                17 = TEXT
-               17.value = <div class="csc-textpic csc-textpic-intext-right###CLASSES###">###IMAGES######TEXT###</div>
+               17 {
+                       value = <div class="csc-textpic csc-textpic-intext-right###CLASSES###">###IMAGES######TEXT###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-intext-right###CLASSES###">###IMAGES######TEXT###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
                # intext-left
                18 = TEXT
-               18.value = <div class="csc-textpic csc-textpic-intext-left###CLASSES###">###IMAGES######TEXT###</div>
+               18 {
+                       value = <div class="csc-textpic csc-textpic-intext-left###CLASSES###">###IMAGES######TEXT###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-intext-left###CLASSES###">###IMAGES######TEXT###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
                # intext-right-nowrap
                25 = TEXT
-               25.value = <div class="csc-textpic csc-textpic-intext-right-nowrap###CLASSES###">###IMAGES######TEXT###</div>
+               25 {
+                       value = <div class="csc-textpic csc-textpic-intext-right-nowrap###CLASSES###">###IMAGES######TEXT###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-intext-right-nowrap###CLASSES###">###IMAGES######TEXT###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
                # intext-left-nowrap
                26 = TEXT
-               26.value = <div class="csc-textpic csc-textpic-intext-left-nowrap###CLASSES###">###IMAGES######TEXT###</div>
+               26 {
+                       value = <div class="csc-textpic csc-textpic-intext-left-nowrap###CLASSES###">###IMAGES######TEXT###</div>
+                       override = <div class="csc-textpic csc-textpic-responsive csc-textpic-intext-left-nowrap###CLASSES###">###IMAGES######TEXT###</div>
+                       override {
+                               if {
+                                       value = default
+                                       equals = {$styles.content.imgtext.layoutKey}
+                                       negate = 1
+                               }
+                       }
+               }
        }
 
        rendering {
@@ -2107,6 +2248,8 @@ plugin.tx_cssstyledcontent._CSS_DEFAULT_STYLE (
        .csc-header-alignment-right { text-align: right; }
        .csc-header-alignment-left { text-align: left; }
 
+       div.csc-textpic-responsive, div.csc-textpic-responsive * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;  }
+
        /* Clear floats after csc-textpic and after csc-textpic-imagerow */
        div.csc-textpic, div.csc-textpic div.csc-textpic-imagerow, ul.csc-uploads li { overflow: hidden; }
 
diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectOneSourceCollectionHookInterface.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectOneSourceCollectionHookInterface.php
new file mode 100644 (file)
index 0000000..c256d50
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+namespace TYPO3\CMS\Frontend\ContentObject;
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2013 Ingo Schmitt <is@marketing-factory.de>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Interface for classes which hook into getSourceCollection for additional processing
+ */
+interface ContentObjectOneSourceCollectionHookInterface {
+
+       /**
+        * Renders One Source Collection
+        *
+        * @param array $sourceRenderConfiguration Array with TypoScript Properties for the imgResource
+        * @param array $sourceConfiguration
+        * @param string $oneSourceCollection already prerendered SourceCollection
+        * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $parentObject Parent content object
+        * @internal param array $configuration Array with the Source Configuration
+        * @return string HTML Content for oneSourceCollection
+        */
+       public function getOneSourceCollection(array $sourceRenderConfiguration, array $sourceConfiguration, $oneSourceCollection, \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer &$parentObject);
+
+}
\ No newline at end of file
index 3580212..fe46804 100644 (file)
@@ -1334,6 +1334,11 @@ class ContentObjectRenderer {
                        } else {
                                $source = $info[3];
                        }
+
+                       $layoutKey = $this->stdWrap($conf['layoutKey'], $conf['layoutKey.']);
+                       $imageTagTemplate = $this->getImageTagTemplate($layoutKey, $conf);
+                       $sourceCollection = $this->getImageSourceCollection($layoutKey, $conf, $file);
+
                        // This array is used to collect the image-refs on the page...
                        $GLOBALS['TSFE']->imagesOnPage[] = $source;
                        $altParam = $this->getAltParam($conf);
@@ -1342,7 +1347,20 @@ class ContentObjectRenderer {
                        } else {
                                $params = isset($conf['params.']) ? ' ' . $this->stdWrap($conf['params'], $conf['params.']) : '';
                        }
-                       $theValue = '<img src="' . htmlspecialchars($source) . '" width="' . $info[0] . '" height="' . $info[1] . '"' . $this->getBorderAttr(' border="' . intval($conf['border']) . '"') . $params . $altParam . (!empty($GLOBALS['TSFE']->xhtmlDoctype) ? ' /' : '') . '>';
+
+                       $imageTagValues = array(
+                               'width' =>  $info[0],
+                               'height' => $info[1],
+                               'src' => htmlspecialchars($source),
+                               'params' => $params,
+                               'altParams' => $altParam,
+                               'border' =>  $this->getBorderAttr(' border="' . intval($conf['border']) . '"'),
+                               'sourceCollection' => $sourceCollection,
+                               'selfClosingTagSlash' => (!empty($GLOBALS['TSFE']->xhtmlDoctype) ? ' /' : ''),
+                       );
+
+                       $theValue = $this->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', TRUE, TRUE);
+
                        $linkWrap = isset($conf['linkWrap.']) ? $this->stdWrap($conf['linkWrap'], $conf['linkWrap.']) : $conf['linkWrap'];
                        if ($linkWrap) {
                                $theValue = $this->linkWrap($theValue, $linkWrap);
@@ -1372,6 +1390,112 @@ class ContentObjectRenderer {
        }
 
        /**
+        * Returns the html-template for rendering the image-Tag if no template is defined via typoscript the
+        * default <img> tag template is returned
+        *
+        * @param string $layoutKey rendering key
+        * @param array $conf TypoScript configuration properties
+        * @return string
+        */
+       public function getImageTagTemplate($layoutKey, $conf) {
+               if ($layoutKey && isset($conf['layout.']) && isset($conf['layout.'][$layoutKey . '.'])) {
+                       $imageTagLayout = $this->stdWrap($conf['layout.'][$layoutKey . '.']['element'], $conf['layout.'][$layoutKey . '.']['element.']);
+               } else {
+                       $imageTagLayout = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
+               }
+               return $imageTagLayout;
+       }
+
+       /**
+        * Render alternate sources for the image tag. If no source collection is given an empty string is returned.
+        *
+        * @param string $layoutKey rendering key
+        * @param array $conf TypoScript configuration properties
+        * @param string $file
+        * @throws \UnexpectedValueException
+        * @return string
+        */
+       public function getImageSourceCollection($layoutKey, $conf, $file) {
+               $sourceCollection = '';
+               if ($layoutKey && $conf['sourceCollection.'] && ($conf['layout.'][$layoutKey . '.']['source'] || $conf['layout.'][$layoutKey . '.']['source.'])) {
+
+                       // find active sourceCollection
+                       $activeSourceCollections = array();
+                       foreach ($conf['sourceCollection.'] as $sourceCollectionKey => $sourceCollectionConfiguration) {
+                               if (substr($sourceCollectionKey, -1) == '.') {
+                                       if (
+                                               (isset($sourceCollectionConfiguration['if.']) && $this->checkIf($sourceCollectionConfiguration['if.']))
+                                               || !isset($sourceCollectionConfiguration['if.'])
+                                       ) {
+                                               $activeSourceCollections[] = $sourceCollectionConfiguration;
+                                       }
+                               }
+                       }
+
+                       // apply option split to configurations
+                       $srcLayoutOptionSplitted = $GLOBALS['TSFE']->tmpl->splitConfArray($conf['layout.'][$layoutKey . '.'], count($activeSourceCollections));
+
+                       // render sources
+                       foreach ($activeSourceCollections as $key => $sourceConfiguration) {
+                               $sourceLayout = $this->stdWrap($srcLayoutOptionSplitted[$key]['source'], $srcLayoutOptionSplitted[$key]['source.']);
+
+                               $sourceRenderConfiguration = array (
+                                       'file' => $file,
+                                       'file.' => $conf['file.']
+                               );
+
+                               if (isset($sourceConfiguration['pixelDensity'])) {
+                                       $pixelDensity = (int) $this->stdWrap($sourceConfiguration['pixelDensity'], $sourceConfiguration['pixelDensity.']);
+                               } else {
+                                       $pixelDensity = 1;
+                               }
+                               $dimensionKeys = array('width', 'height', 'maxW', 'minW', 'maxH', 'minH');
+                               foreach ($dimensionKeys as $dimensionKey) {
+                                       $dimension = $this->stdWrap($sourceConfiguration[$dimensionKey], $sourceConfiguration[$dimensionKey . '.']);
+                                       if (!$dimension && isset($conf['file.'][$dimensionKey])) {
+                                               $dimension = $this->stdWrap($conf['file.'][$dimensionKey], $conf['file.'][$dimensionKey . '.']);
+                                       }
+                                       if ($dimension) {
+                                               if (strstr($dimension, 'c') !== FALSE && ($dimensionKey === 'width' || $dimensionKey === 'height')) {
+                                                       $dimensionParts = explode('c', $dimension, 2);
+                                                       $dimension =  intval($dimensionParts[0] * $pixelDensity) . 'c';
+                                                       if ($dimensionParts[1]) $dimension .= $dimensionParts[1];
+                                               } else {
+                                                       $dimension = intval($dimension * $pixelDensity);
+                                               }
+                                               $sourceRenderConfiguration['file.'][$dimensionKey] = $dimension;
+                                       }
+                               }
+
+                               $sourceInfo = $this->getImgResource($sourceRenderConfiguration['file'], $sourceRenderConfiguration['file.']);
+                               $sourceConfiguration['width'] = $sourceInfo[0];
+                               $sourceConfiguration['height'] = $sourceInfo[1];
+                               $sourceConfiguration['src'] = htmlspecialchars($sourceInfo[3]);
+                               $sourceConfiguration['selfClosingTagSlash'] = (!empty($GLOBALS['TSFE']->xhtmlDoctype) ? ' /' : '');
+
+                               $oneSourceCollection = $this->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', TRUE, TRUE);
+
+                               if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'])) {
+                                       foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] as $classData) {
+                                               $hookObject = GeneralUtility::getUserObj($classData);
+                                               if (!$hookObject instanceof ContentObjectOneSourceCollectionHookInterface) {
+                                                       throw new \UnexpectedValueException(
+                                                               '$hookObject must implement interface TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectOneSourceCollectionHookInterface',
+                                                               1380007853
+                                                       );
+                                               }
+                                               /** @var $hookObject \TYPO3\CMS\Frontend\ContentObject\ContentObjectGetSingleHookInterface */
+                                               $oneSourceCollection = $hookObject->getOneSourceCollection((array) $sourceRenderConfiguration, (array) $sourceConfiguration, $oneSourceCollection, $this);
+                                       }
+                               }
+
+                               $sourceCollection .= $oneSourceCollection;
+                       }
+               }
+               return $sourceCollection;
+       }
+
+       /**
         * Wraps the input string in link-tags that opens the image in a new window.
         *
         * @param string $string String to wrap, probably an <img> tag
index 8e892d0..51fc940 100644 (file)
@@ -425,10 +425,19 @@ class PageGenerator {
                        foreach ($GLOBALS['TSFE']->tmpl->setup['plugin.'] as $key => $iCSScode) {
                                if (is_array($iCSScode)) {
                                        if ($iCSScode['_CSS_DEFAULT_STYLE'] && empty($GLOBALS['TSFE']->config['config']['removeDefaultCss'])) {
-                                               $temp_styleLines[] = '/* default styles for extension "' . substr($key, 0, -1) . '" */' . LF . $iCSScode['_CSS_DEFAULT_STYLE'];
+                                               if (isset($iCSScode['_CSS_DEFAULT_STYLE.'])) {
+                                                       $cssDefaultStyle = $GLOBALS['TSFE']->cObj->stdWrap($iCSScode['_CSS_DEFAULT_STYLE'], $iCSScode['_CSS_DEFAULT_STYLE.']);
+                                               } else {
+                                                       $cssDefaultStyle = $iCSScode['_CSS_DEFAULT_STYLE'];
+                                               }
+                                               $temp_styleLines[] = '/* default styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssDefaultStyle;
                                        }
                                        if ($iCSScode['_CSS_PAGE_STYLE'] && empty($GLOBALS['TSFE']->config['config']['removePageCss'])) {
-                                               $temp_styleLines[] = '/* specific page styles for extension "' . substr($key, 0, -1) . '" */' . LF . implode(LF, $iCSScode['_CSS_PAGE_STYLE']);
+                                               $cssPageStyle = implode(LF, $iCSScode['_CSS_PAGE_STYLE']);
+                                               if (isset($iCSScode['_CSS_PAGE_STYLE.'])) {
+                                                       $cssPageStyle = $GLOBALS['TSFE']->cObj->stdWrap($cssPageStyle, $iCSScode['_CSS_PAGE_STYLE.']);
+                                               }
+                                               $temp_styleLines[] = '/* specific page styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssPageStyle;
                                        }
                                }
                        }
index 599bf11..4fb0f12 100644 (file)
@@ -263,7 +263,7 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $_SERVER['QUERY_STRING'] = 'key1=value1';
                $getQueryArgumentsConfiguration = array();
                $overruleArguments = array(
-                       // Should be overriden
+                       // Should be overridden
                        'key1' => 'value1Overruled',
                        // Shouldn't be set: Parameter doesn't exist in source array and is not forced
                        'key2' => 'value2Overruled'
@@ -2008,4 +2008,402 @@ class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $aTagParams = $this->cObj->getATagParams(array('ATagParams' => ''));
                $this->assertEquals('', $aTagParams);
        }
+
+       /**
+        * @return array
+        */
+       public function getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFoundDataProvider() {
+               return array(
+                       array(NULL, NULL),
+                       array('', NULL),
+                       array('', array()),
+                       array('fooo', array('foo' => 'bar'))
+               );
+       }
+
+       /**
+        * Make sure that the rendering falls back to the classic <img style if nothing else is found
+        *
+        * @test
+        * @dataProvider getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFoundDataProvider
+        * @param string $key
+        * @param array $configuration
+        */
+       public function getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFound($key, $configuration) {
+               $defaultImgTagTemplate = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>';
+               $result = $this->cObj->getImageTagTemplate($key, $configuration);
+               $this->assertEquals($result, $defaultImgTagTemplate);
+       }
+
+       /**
+        * @return array
+        */
+       public function getImageTagTemplateReturnTemplateElementIdentifiedByKeyDataProvider() {
+               return array(
+                       array(
+                               'foo',
+                               array(
+                                       'layout.' => array(
+                                               'foo.' => array(
+                                                       'element' => '<img src="###SRC###" srcset="###SOURCES###" ###PARAMS### ###ALTPARAMS### ###FOOBAR######SELFCLOSINGTAGSLASH###>'
+                                               )
+                                       )
+                               ),
+                               '<img src="###SRC###" srcset="###SOURCES###" ###PARAMS### ###ALTPARAMS### ###FOOBAR######SELFCLOSINGTAGSLASH###>'
+                       )
+
+               );
+       }
+
+       /**
+        * Assure if a layoutKey and layout is given the selected layout is returned
+        *
+        * @test
+        * @dataProvider getImageTagTemplateReturnTemplateElementIdentifiedByKeyDataProvider
+        * @param string $key
+        * @param array $configuration
+        * @param string $expectation
+        */
+       public function getImageTagTemplateReturnTemplateElementIdentifiedByKey($key, $configuration, $expectation) {
+               $result = $this->cObj->getImageTagTemplate($key, $configuration);
+               $this->assertEquals($result, $expectation);
+       }
+
+       /**
+        * @return array
+        */
+       public function getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefinedDataProvider() {
+               return array(
+                       array(NULL, NULL, NULL),
+                       array('foo', NULL, NULL),
+                       array('foo', array('sourceCollection.' => 1), 'bar')
+               );
+       }
+
+       /**
+        * Make sure the source collection is empty if no valid configuration or source collection is defined
+        *
+        * @test
+        * @dataProvider getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefinedDataProvider
+        * @param string $layoutKey
+        * @param array $configuration
+        * @param string $file
+        */
+       public function getImageSourceCollectionReturnsEmptyStringIfNoSourcesAreDefined($layoutKey, $configuration, $file) {
+               $result = $this->cObj->getImageSourceCollection($layoutKey, $configuration, $file);
+               $this->assertSame($result, '');
+       }
+
+       /**
+        * Make sure the generation of subimages calls the generation of the subimages and uses the layout -> source template
+        *
+        * @test
+        */
+       public function getImageSourceCollectionRendersDefinedSources() {
+               /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer */
+               $cObj = $this->getMock(
+                       'TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer',
+                       array('stdWrap','getImgResource')
+               );
+               $cObj->start(array(), 'tt_content');
+
+               $layoutKey = 'test';
+
+               $configuration = array(
+                       'layoutKey' => 'test',
+                       'layout.' => array (
+                               'test.' => array(
+                                       'element' => '<img ###SRC### ###SRCCOLLECTION### ###SELFCLOSINGTAGSLASH###>',
+                                       'source' => '---###SRC###---'
+                               )
+                       ),
+                       'sourceCollection.' => array(
+                               '1.' => array(
+                                       'width' => '200'
+                               )
+                       )
+               );
+
+               $file = 'testImageName';
+
+               // Avoid calling of stdWrap
+               $cObj
+                       ->expects($this->any())
+                       ->method('stdWrap')
+                       ->will($this->returnArgument(0));
+
+               // Avoid calling of imgResource
+               $cObj
+                       ->expects($this->exactly(1))
+                       ->method('getImgResource')
+                       ->with($this->equalTo('testImageName'))
+                       ->will($this->returnValue(array(100, 100, NULL, 'bar')));
+
+               $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
+
+               $this->assertEquals('---bar---', $result);
+       }
+
+       /**
+        * Data provider for the getImageSourceCollectionRendersDefinedLayoutKeyDefault test
+        *
+        * @return array multi-dimensional array with the second level like this:
+        * @see getImageSourceCollectionRendersDefinedLayoutKeyDefault
+        */
+       public function getImageSourceCollectionRendersDefinedLayoutKeyDataDefaultProvider() {
+               /**
+                * @see css_styled_content/static/setup.txt
+                */
+               $sourceCollectionArray = array(
+                       'small.' => array(
+                               'width' => '200',
+                               'srcsetCandidate' => '600w',
+                               'mediaQuery' => '(max-device-width: 600px)',
+                               'dataKey' => 'small',
+                       ),
+                       'smallRetina.' => array(
+                               'if.directReturn' => 0,
+                               'width' => '200',
+                               'pixelDensity' => '2',
+                               'srcsetCandidate' => '600w 2x',
+                               'mediaQuery' => '(max-device-width: 600px) AND (min-resolution: 192dpi)',
+                               'dataKey' => 'smallRetina',
+                       )
+               );
+               return array(
+                       array(
+                               'default',
+                               array(
+                                       'layoutKey' => 'default',
+                                       'layout.' => array (
+                                               'default.' => array(
+                                                       'element' => '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>',
+                                                       'source' => ''
+                                               )
+                                       ),
+                                       'sourceCollection.' => $sourceCollectionArray
+                               )
+                       ),
+               );
+       }
+
+       /**
+        * Make sure the generation of subimages renders the expected HTML Code for the sourceset
+        *
+        * @test
+        * @dataProvider getImageSourceCollectionRendersDefinedLayoutKeyDataDefaultProvider
+        * @param string $layoutKey
+        * @param array $configuration
+        */
+       public function getImageSourceCollectionRendersDefinedLayoutKeyDefault($layoutKey , $configuration) {
+               /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer */
+               $cObj = $this->getMock(
+                       'TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer',
+                       array('stdWrap','getImgResource')
+               );
+               $cObj->start(array(), 'tt_content');
+
+               $file = 'testImageName';
+
+               // Avoid calling of stdWrap
+               $cObj
+                       ->expects($this->any())
+                       ->method('stdWrap')
+                       ->will($this->returnArgument(0));
+
+               $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
+
+               $this->assertEmpty($result);
+       }
+
+       /**
+        * Data provider for the getImageSourceCollectionRendersDefinedLayoutKeyData test
+        *
+        * @return array multi-dimensional array with the second level like this:
+        * @see getImageSourceCollectionRendersDefinedLayoutKeyData
+        */
+       public function getImageSourceCollectionRendersDefinedLayoutKeyDataDataProvider() {
+               /**
+                * @see css_styled_content/static/setup.txt
+                */
+               $sourceCollectionArray = array(
+                       'small.' => array(
+                               'width' => '200',
+                               'srcsetCandidate' => '600w',
+                               'mediaQuery' => '(max-device-width: 600px)',
+                               'dataKey' => 'small',
+                       ),
+                       'smallRetina.' => array(
+                               'if.directReturn' => 1,
+                               'width' => '200',
+                               'pixelDensity' => '2',
+                               'srcsetCandidate' => '600w 2x',
+                               'mediaQuery' => '(max-device-width: 600px) AND (min-resolution: 192dpi)',
+                               'dataKey' => 'smallRetina',
+                       )
+               );
+               return array(
+                       array(
+                               'srcset',
+                               array(
+                                       'layoutKey' => 'srcset',
+                                       'layout.' => array (
+                                               'srcset.' => array(
+                                                       'element' => '<img src="###SRC###" srcset="###SOURCECOLLECTION###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
+                                                       'source' => '|*|###SRC### ###SRCSETCANDIDATE###,|*|###SRC### ###SRCSETCANDIDATE###'
+                                               )
+                                       ),
+                                       'sourceCollection.' => $sourceCollectionArray
+                               ),
+                               'xhtml_strict',
+                               'bar-file.jpg 600w,bar-file.jpg 600w 2x',
+                       ),
+                       array(
+                               'picture',
+                               array(
+                                       'layoutKey' => 'picture',
+                                       'layout.' => array (
+                                               'picture.' => array(
+                                                       'element' => '<picture>###SOURCECOLLECTION###<img src="###SRC###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###></picture>',
+                                                       'source' => '<source src="###SRC###" media="###MEDIAQUERY###"###SELFCLOSINGTAGSLASH###>'
+                                               )
+                                       ),
+                                       'sourceCollection.' => $sourceCollectionArray,
+                               ),
+                               'xhtml_strict',
+                               '<source src="bar-file.jpg" media="(max-device-width: 600px)" /><source src="bar-file.jpg" media="(max-device-width: 600px) AND (min-resolution: 192dpi)" />',
+                       ),
+                       array(
+                               'picture',
+                               array(
+                                       'layoutKey' => 'picture',
+                                       'layout.' => array (
+                                               'picture.' => array(
+                                                       'element' => '<picture>###SOURCECOLLECTION###<img src="###SRC###" ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###></picture>',
+                                                       'source' => '<source src="###SRC###" media="###MEDIAQUERY###"###SELFCLOSINGTAGSLASH###>'
+                                               )
+                                       ),
+                                       'sourceCollection.' => $sourceCollectionArray,
+                               ),
+                               '',
+                               '<source src="bar-file.jpg" media="(max-device-width: 600px)"><source src="bar-file.jpg" media="(max-device-width: 600px) AND (min-resolution: 192dpi)">',
+                       ),
+                       array(
+                               'data',
+                               array(
+                                       'layoutKey' => 'data',
+                                       'layout.' => array (
+                                               'data.' => array(
+                                                       'element' => '<img src="###SRC###" ###SOURCECOLLECTION### ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
+                                                       'source' => 'data-###DATAKEY###="###SRC###"'
+                                               )
+                                       ),
+                                       'sourceCollection.' => $sourceCollectionArray
+                               ),
+                               'xhtml_strict',
+                               'data-small="bar-file.jpg"data-smallRetina="bar-file.jpg"',
+                       ),
+               );
+       }
+
+       /**
+        * Make sure the generation of subimages renders the expected HTML Code for the sourceset
+        *
+        * @test
+        * @dataProvider getImageSourceCollectionRendersDefinedLayoutKeyDataDataProvider
+        * @param string $layoutKey
+        * @param array $configuration
+        * @param string $xhtmlDoctype
+        * @param string $expectedHtml
+        */
+       public function getImageSourceCollectionRendersDefinedLayoutKeyData($layoutKey , $configuration, $xhtmlDoctype, $expectedHtml) {
+               /** @var $cObj \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer */
+               $cObj = $this->getMock(
+                       'TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectRenderer',
+                       array('stdWrap','getImgResource')
+               );
+               $cObj->start(array(), 'tt_content');
+
+               $file = 'testImageName';
+
+               $GLOBALS['TSFE']->xhtmlDoctype = $xhtmlDoctype;
+
+               // Avoid calling of stdWrap
+               $cObj
+                       ->expects($this->any())
+                       ->method('stdWrap')
+                       ->will($this->returnArgument(0));
+
+               // Avoid calling of imgResource
+               $cObj
+                       ->expects($this->exactly(2))
+                       ->method('getImgResource')
+                       ->with($this->equalTo('testImageName'))
+                       ->will($this->returnValue(array(100, 100, NULL, 'bar-file.jpg')));
+
+               $result = $cObj->getImageSourceCollection($layoutKey, $configuration, $file);
+
+               $this->assertEquals($expectedHtml, $result);
+       }
+
+       /**
+        * Make sure the hook in get sourceCollection is called
+        *
+        * @test
+        */
+       public function getImageSourceCollectionHookCalled() {
+               // Avoid calling stdwrap and getIMgResouce
+               $this->cObj->expects($this->any())
+                       ->method('stdWrap')
+                       ->will($this->returnArgument(0));
+
+               $this->cObj->expects($this->any())
+                       ->method('getImgResource')
+                       ->will($this->returnValue(array(100, 100, NULL, 'bar-file.jpg')));
+
+               $className = uniqid('tx_coretest_getImageSourceCollectionHookCalled');
+               $getImageSourceCollectionHookMock = $this->getMock('TYPO3\\CMS\\Frontend\\ContentObject\\ContentObjectOneSourceCollectionHookInterface', array('getOneSourceCollection'), array(), $className);
+               $GLOBALS['T3_VAR']['getUserObj'][$className] = $getImageSourceCollectionHookMock;
+               $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'][] = $className;
+
+               $getImageSourceCollectionHookMock
+                       ->expects($this->exactly(1))
+                       ->method('getOneSourceCollection')
+                       ->will($this->returnCallback(array($this, 'isGetOneSourceCollectionCalledCallback')));
+
+               $configuration = array(
+                       'layoutKey' => 'data',
+                       'layout.' => array (
+                               'data.' => array(
+                                       'element' => '<img src="###SRC###" ###SOURCECOLLECTION### ###PARAMS### ###ALTPARAMS######SELFCLOSINGTAGSLASH###>',
+                                       'source' => 'data-###DATAKEY###="###SRC###"'
+                               )
+                       ),
+                       'sourceCollection.' => array(
+                               'small.' => array(
+                                       'width' => '200',
+                                       'srcsetCandidate' => '600w',
+                                       'mediaQuery' => '(max-device-width: 600px)',
+                                       'dataKey' => 'small',
+                               ),
+                       ),
+               );
+
+               $result = $this->cObj->getImageSourceCollection('data', $configuration, uniqid('testImage-'));
+
+               $this->assertSame($result, 'isGetOneSourceCollectionCalledCallback');
+       }
+
+       /**
+        * Handles the arguments that have been sent to the getImgResource hook.
+        *
+        * @return      string
+        * @see getImageSourceCollectionHookCalled
+        */
+       public function isGetOneSourceCollectionCalledCallback() {
+               list($sourceRenderConfiguration, $sourceConfiguration, $oneSourceCollection, $parent) = func_get_args();
+               $this->assertTrue(is_array($sourceRenderConfiguration));
+               $this->assertTrue(is_array($sourceConfiguration));
+               return 'isGetOneSourceCollectionCalledCallback';
+       }
 }