From e7ec4abaea793808a2248595b2d6f11c1f2bb585 Mon Sep 17 00:00:00 2001
From: Frans Saris <franssaris@gmail.com>
Date: Sat, 7 Mar 2015 20:05:44 +0100
Subject: [PATCH] [FEATURE] Add TCA type image_manipulation

TCA type image_manipulation brings a image manipulation wizard
to the core.

This first version brings image cropping with the possibility to
set a certain aspect ratio for the cropped area. The
sys_file_reference.crop property is extended and can now also hold
a json string to describe the image manipulation.

The LocalCropScaleMaskHelper that is used by the core
to create adjusted images is also adjusted to handle the new format.

Overriding TCA by TSConfig will be done in a followup.

Resolves: #65585
Releases: master
Change-Id: I58dee33e0f884ba2907259e8b03254c43f4c9186
Reviewed-on: http://review.typo3.org/37622
Reviewed-by: Benjamin Kott <info@bk2k.info>
Tested-by: Benjamin Kott <info@bk2k.info>
Reviewed-by: Andreas Fernandez <andreas.fernandez@aspedia.de>
Tested-by: Andreas Fernandez <andreas.fernandez@aspedia.de>
Reviewed-by: Mathias Schreiber <mathias.schreiber@wmdb.de>
Tested-by: Mathias Schreiber <mathias.schreiber@wmdb.de>
Reviewed-by: Frans Saris <franssaris@gmail.com>
Tested-by: Frans Saris <franssaris@gmail.com>
---
 Build/Gruntfile.js                            |   2 +
 Build/bower.json                              |   2 +
 .../Form/Element/ImageManipulationElement.php | 242 ++++++++++++
 .../backend/Classes/Form/FormEngine.php       |   1 +
 .../Form/Wizard/ImageManipulationWizard.php   | 109 ++++++
 .../Wizards/ImageManipulationWizard.html      | 102 ++++++
 .../Public/JavaScript/ImageManipulation.js    | 271 ++++++++++++++
 .../Resources/Public/JavaScript/Modal.js      |  32 +-
 .../core/Classes/DataHandling/DataHandler.php |   3 +-
 .../sysext/core/Classes/Page/PageRenderer.php |   2 +
 .../Processing/LocalCropScaleMaskHelper.php   |  11 +-
 .../Configuration/DefaultConfiguration.php    |   4 +
 .../Configuration/TCA/sys_file_reference.php  |   5 +-
 ...ure-65585-AddTCATypeImage_manipulation.rst |  37 ++
 .../Public/JavaScript/Contrib/cropper.min.js  |  10 +
 .../Contrib/imagesloaded.pkgd.min.js          |   7 +
 typo3/sysext/core/ext_tables.sql              |   2 +-
 typo3/sysext/lang/locallang_tca.xlf           |   2 +-
 typo3/sysext/lang/locallang_wizards.xlf       |  72 ++++
 .../Styles/TYPO3/_element_cropper.less        |  19 +
 .../Private/Styles/TYPO3/_element_modal.less  |  71 +++-
 .../Private/Styles/TYPO3/_element_table.less  |  28 +-
 .../Resources/Private/Styles/t3skin.less      |   5 +
 .../Resources/Public/Css/visual/t3skin.css    | 343 ++++++++++++++++++
 .../Public/Images/cropper-background.png      | Bin 0 -> 158 bytes
 25 files changed, 1370 insertions(+), 12 deletions(-)
 create mode 100644 typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
 create mode 100644 typo3/sysext/backend/Classes/Form/Wizard/ImageManipulationWizard.php
 create mode 100644 typo3/sysext/backend/Resources/Private/Templates/Wizards/ImageManipulationWizard.html
 create mode 100644 typo3/sysext/backend/Resources/Public/JavaScript/ImageManipulation.js
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-65585-AddTCATypeImage_manipulation.rst
 create mode 100644 typo3/sysext/core/Resources/Public/JavaScript/Contrib/cropper.min.js
 create mode 100644 typo3/sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min.js
 create mode 100644 typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_cropper.less
 create mode 100644 typo3/sysext/t3skin/Resources/Public/Images/cropper-background.png

diff --git a/Build/Gruntfile.js b/Build/Gruntfile.js
index 7b830f6ba611..e6337c08c5ef 100644
--- a/Build/Gruntfile.js
+++ b/Build/Gruntfile.js
@@ -49,6 +49,8 @@ module.exports = function(grunt) {
 					'<%= paths.core %>Public/JavaScript/Contrib/jquery.dataTables.js': '/datatables/media/js/jquery.dataTables.min.js',
 					'<%= paths.core %>Public/JavaScript/Contrib/require.js': '/requirejs/require.js',
 					'<%= paths.core %>Public/JavaScript/Contrib/moment.js': '/moment/moment.js',
+					'<%= paths.core %>Public/JavaScript/Contrib/cropper.min.js': '/cropper/dist/cropper.min.js',
+					'<%= paths.core %>Public/JavaScript/Contrib/imagesloaded.pkgd.min.js': '/imagesloaded/imagesloaded.pkgd.min.js',
 					'<%= paths.core %>Public/JavaScript/Contrib/bootstrap-datetimepicker.js': '/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js',
 					'<%= paths.core %>Public/JavaScript/Contrib/autosize.js': '/autosize/dest/autosize.min.js',
 					'<%= paths.core %>Public/JavaScript/Contrib/placeholders.jquery.min.js': '/Placeholders.js/dist/placeholders.jquery.min.js',
diff --git a/Build/bower.json b/Build/bower.json
index 4cccb5123bd1..b1a966c13572 100644
--- a/Build/bower.json
+++ b/Build/bower.json
@@ -35,6 +35,8 @@
     "nprogress": "0.1.6",
     "datatables": "1.10.5",
     "autosize": "2.0.0",
+    "cropper": "0.9.1",
+    "imagesloaded": "3.1.8",
     "Placeholders.js": "4.0.1"
   }
 }
diff --git a/typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php b/typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
new file mode 100644
index 000000000000..1d2455dc6f0f
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
@@ -0,0 +1,242 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Element;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Form\FormEngine;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Extbase\Utility\ArrayUtility;
+use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
+
+/**
+ * Generation of crop image TCEform elements
+ */
+class ImageManipulationElement extends AbstractFormElement {
+
+	/**
+	 * Default element configuration
+	 *
+	 * @var array
+	 */
+	protected $defaultConfig = array(
+		'file_field' => 'uid_local',
+		'enableZoom' => FALSE,
+		'allowedExtensions' => NULL, // default: $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
+		'ratios' => array(
+			'1.7777777777777777' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.16_9',
+			'1.3333333333333333' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.4_3',
+			'1' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.1_1',
+			'NaN' => 'LLL:EXT:lang/locallang_wizards.xlf:imwizard.ratio.free',
+		)
+	);
+
+	/**
+	 * Handler for unknown types.
+	 *
+	 * @param string $table The table name of the record
+	 * @param string $field The field name which this element is supposed to edit
+	 * @param array $row The record data array where the value(s) for the field can be found
+	 * @param array $additionalInformation An array with additional configuration options.
+	 * @return string The HTML code for the TCEform field
+	 */
+	public function render($table, $field, $row, &$additionalInformation) {
+		// If ratios are set do not add default options
+		if (isset($additionalInformation['fieldConf']['config']['ratios'])) {
+			unset($this->defaultConfig['ratios']);
+		}
+		$config = ArrayUtility::arrayMergeRecursiveOverrule($this->defaultConfig, $additionalInformation['fieldConf']['config']);
+
+		// By default we allow all image extensions that can be handled by the GFX functionality
+		if ($config['allowedExtensions'] === NULL) {
+			$config['allowedExtensions'] = $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
+		}
+
+		if ($this->isGlobalReadonly() || $config['readOnly']) {
+			$formEngineDummy = new FormEngine();
+			$noneElement = GeneralUtility::makeInstance(NoneElement::class, $formEngineDummy);
+			$elementConfiguration = array(
+				'fieldConf' => array(
+					'config' => $config,
+				),
+				'itemFormElValue' => $additionalInformation['itemFormElValue'],
+			);
+			return $noneElement->render('', '', '', $elementConfiguration);
+		}
+
+		$file = $this->getFile($row, $config['file_field']);
+		if (!$file) {
+			return '';
+		}
+
+		$content = '';
+		$preview = '';
+		if (GeneralUtility::inList(mb_strtolower($config['allowedExtensions']), mb_strtolower($file->getExtension()))) {
+
+			// Get preview
+			$preview = $this->getPreview($file, $additionalInformation['itemFormElValue']);
+
+			// Check if ratio labels hold translation strings
+			$languageService = $this->getLanguageService();
+			foreach ((array)$config['ratios'] as $ratio => $label) {
+				$config['ratios'][$ratio] = $languageService->sL($label, TRUE);
+			}
+
+			$formFieldId = str_replace('.', '', uniqid('formengine-image-manipulation-', TRUE));
+			$wizardData = array(
+				'file' => $file->getUid(),
+				'zoom' => $config['enableZoom'] ? '1' : '0',
+				'ratios' => json_encode($config['ratios']),
+			);
+			$wizardData['token'] = GeneralUtility::hmac(implode('|', $wizardData), 'ImageManipulationWizard');
+
+			$buttonAttributes = array(
+				'data-url' => BackendUtility::getAjaxUrl('ImageManipulationWizard::getHtmlForImageManipulationWizard', $wizardData),
+				'data-severity' => 'notice',
+				'data-image-name' => $file->getNameWithoutExtension(),
+				'data-image-uid' => $file->getUid(),
+				'data-file-field' => $config['file_field'],
+				'data-field' => $formFieldId,
+			);
+
+			$button = '<button class="btn btn-default t3js-image-manipulation-trigger"';
+			foreach ($buttonAttributes as $key => $value) {
+				$button .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
+			}
+			$button .= '><span class="t3-icon fa fa-crop"></span>';
+			$button .= $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.open-editor', TRUE);
+			$button .= '</button>';
+
+			$inputField = '<input type="hidden" '
+				. 'id="' . $formFieldId . '" '
+				. 'name="' . $additionalInformation['itemFormElName'] . '" '
+				. 'value="' . htmlspecialchars($additionalInformation['itemFormElValue']) . '" />';
+
+			$content .= $inputField . $button;
+
+			$content .= $this->getImageManipulationInfoTable($additionalInformation['itemFormElValue']);
+
+			/** @var $pageRenderer \TYPO3\CMS\Core\Page\PageRenderer */
+			$pageRenderer = $GLOBALS['SOBE']->doc->getPageRenderer();
+			$pageRenderer->loadRequireJsModule(
+				'TYPO3/CMS/Backend/ImageManipulation',
+				'function(ImageManipulation){ImageManipulation.initializeTrigger()}' // Initialize after load
+			);
+		}
+
+		$content .= '<p class="text-muted"><em>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.supported-types-message', TRUE) . '<br />';
+		$content .= mb_strtoupper(implode(', ', GeneralUtility::trimExplode(',', $config['allowedExtensions'])));
+		$content .= '</em></p>';
+
+		$item = '<div class="media">';
+		$item .= $preview;
+		$item .= '<div class="media-body">' . $content . '</div>';
+		$item .= '</div>';
+
+		return $item;
+	}
+
+	/**
+	 * Get file object
+	 *
+	 * @param array $row
+	 * @param string $fieldName
+	 * @return NULL|\TYPO3\CMS\Core\Resource\File
+	 */
+	protected function getFile(array $row, $fieldName) {
+		$file = NULL;
+		$fileUid = !empty($row[$fieldName]) ? $row[$fieldName] : NULL;
+
+		if (strpos($fileUid, 'sys_file_') === 0) {
+			$fileUid = substr($fileUid, 9);
+		}
+		if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
+			try {
+				$file = ResourceFactory::getInstance()->getFileObject($fileUid);
+			} catch (FileDoesNotExistException $e) {
+			}
+		}
+		return $file;
+	}
+
+	/**
+	 * Get preview image if cropping is set
+	 *
+	 * @param File $file
+	 * @param string $crop
+	 * @return string
+	 */
+	public function getPreview(File $file, $crop) {
+		$preview = '';
+		if ($crop) {
+			$imageSetup = array('width' => '150m', 'height' => '200m', 'crop' => $crop);
+			$processedImage = $file->process(\TYPO3\CMS\Core\Resource\ProcessedFile::CONTEXT_IMAGECROPSCALEMASK, $imageSetup);
+			// Only use a thumbnail if the processing process was successful by checking if image width is set
+			if ($processedImage->getProperty('width')) {
+				$imageUrl = $processedImage->getPublicUrl(TRUE);
+				$preview = '<img src="' . $imageUrl . '" ' .
+					'class="media-object thumbnail" ' .
+					'width="' . $processedImage->getProperty('width') . '" ' .
+					'height="' . $processedImage->getProperty('height') . '" >';
+			}
+		}
+		return '<div class="media-left t3js-image-manipulation-preview' . ($preview ? '' : ' hide'). '">' . $preview . '</div>';
+	}
+
+	/**
+	 * Get image manipulation info table
+	 *
+	 * @param string $rawImageManipulationValue
+	 * @return string
+	 */
+	protected function getImageManipulationInfoTable($rawImageManipulationValue) {
+		$content = '';
+		$imageManipulation = NULL;
+		$x = $y = $width = $height = 0;
+
+		// Determine cropping values
+		if ($rawImageManipulationValue) {
+			$imageManipulation = json_decode($rawImageManipulationValue);
+			if (is_object($imageManipulation)) {
+				$x = (int)$imageManipulation->x;
+				$y = (int)$imageManipulation->y;
+				$width = (int)$imageManipulation->width;
+				$height = (int)$imageManipulation->height;
+			} else {
+				$imageManipulation = NULL;
+			}
+		}
+		$languageService = $this->getLanguageService();
+
+		$content .= '<div class="table-fit-block table-spacer-wrap">';
+		$content .= '<table class="table table-no-borders t3js-image-manipulation-info'. ($imageManipulation === NULL ? ' hide' : '') . '">';
+		$content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-x', TRUE) . '</td>';
+		$content .= '<td class="t3js-image-manipulation-info-crop-x">' . $x . 'px</td></tr>';
+		$content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-y', TRUE) . '</td>';
+		$content .= '<td class="t3js-image-manipulation-info-crop-y">' . $y . 'px</td></tr>';
+		$content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-width', TRUE) . '</td>';
+		$content .= '<td class="t3js-image-manipulation-info-crop-width">' . $width . 'px</td></tr>';
+		$content .= '<tr><td>' . $languageService->sL('LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-height', TRUE) . '</td>';
+		$content .= '<td class="t3js-image-manipulation-info-crop-height">' . $height . 'px</td></tr>';
+		$content .= '</table>';
+		$content .= '</div>';
+
+		return $content;
+	}
+}
diff --git a/typo3/sysext/backend/Classes/Form/FormEngine.php b/typo3/sysext/backend/Classes/Form/FormEngine.php
index 68ccf5828a6b..892f1294cccc 100644
--- a/typo3/sysext/backend/Classes/Form/FormEngine.php
+++ b/typo3/sysext/backend/Classes/Form/FormEngine.php
@@ -1039,6 +1039,7 @@ class FormEngine {
 				'none' => 'NoneElement',
 				'user' => 'UserElement',
 				'flex' => 'FlexElement',
+				'image_manipulation' => 'ImageManipulationElement',
 				'unknown' => 'UnknownElement',
 			);
 			if (!isset($typeClassNameMapping[$type])) {
diff --git a/typo3/sysext/backend/Classes/Form/Wizard/ImageManipulationWizard.php b/typo3/sysext/backend/Classes/Form/Wizard/ImageManipulationWizard.php
new file mode 100644
index 000000000000..3847fd7d1f84
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/Wizard/ImageManipulationWizard.php
@@ -0,0 +1,109 @@
+<?php
+namespace TYPO3\CMS\Backend\Form\Wizard;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\HttpUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+/**
+ * Wizard for rendering image manipulation view
+ */
+class ImageManipulationWizard {
+
+	/**
+	 * @var string
+	 */
+	protected $templatePath = 'EXT:backend/Resources/Private/Templates/';
+
+	/**
+	 * Returns the html for the AJAX API
+	 *
+	 * @param array $params
+	 * @param \TYPO3\CMS\Core\Http\AjaxRequestHandler $ajaxRequestHandler
+	 * @return void
+	 */
+	public function getHtmlForImageManipulationWizard($params, $ajaxRequestHandler) {
+		if (!$this->checkHmacToken()) {
+			HttpUtility::setResponseCodeAndExit(HttpUtility::HTTP_STATUS_403);
+		}
+
+		$fileUid = GeneralUtility::_GET('file');
+		$image = NULL;
+		if (MathUtility::canBeInterpretedAsInteger($fileUid)) {
+			try {
+				$image = ResourceFactory::getInstance()->getFileObject($fileUid);
+			} catch (FileDoesNotExistException $e) {}
+		}
+
+		$view = $this->getFluidTemplateObject($this->templatePath . 'Wizards/ImageManipulationWizard.html');
+		$view->assign('image', $image);
+		$view->assign('zoom', (bool)GeneralUtility::_GET('zoom'));
+		$view->assign('ratios', $this->getRatiosArray());
+		$content = $view->render();
+
+		$ajaxRequestHandler->addContent('content', $content);
+		$ajaxRequestHandler->setContentFormat('html');
+	}
+
+	/**
+	 * Check if hmac token is correct
+	 *
+	 * @return bool
+	 */
+	protected function checkHmacToken() {
+		$parameters = array();
+		if (GeneralUtility::_GET('file')) {
+			$parameters['file'] = GeneralUtility::_GET('file');
+		}
+		$parameters['zoom'] = GeneralUtility::_GET('zoom') ? '1' : '0';
+		$parameters['ratios'] = GeneralUtility::_GET('ratios') ?: '';
+
+		$token = GeneralUtility::hmac(implode('|', $parameters), 'ImageManipulationWizard');
+		return $token === GeneralUtility::_GET('token');
+	}
+
+	/**
+	 * Get available ratios
+	 *
+	 * @return array
+	 */
+	protected function getRatiosArray() {
+		$ratios = json_decode(GeneralUtility::_GET('ratios'));
+		// Json transforms a array with sting keys to a array,
+		// we need to transform this to an array for the fluid ForViewHelper
+		if (is_object($ratios)) {
+			$ratios = get_object_vars($ratios);
+		}
+		return $ratios;
+	}
+
+	/**
+	 * Returns a new standalone view, shorthand function
+	 *
+	 * @param string $templatePathAndFileName optional the path to set the template path and filename
+	 * @return StandaloneView
+	 */
+	protected function getFluidTemplateObject($templatePathAndFileName = NULL) {
+		$view = GeneralUtility::makeInstance(StandaloneView::class);
+		if ($templatePathAndFileName) {
+			$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
+		}
+		return $view;
+	}
+}
diff --git a/typo3/sysext/backend/Resources/Private/Templates/Wizards/ImageManipulationWizard.html b/typo3/sysext/backend/Resources/Private/Templates/Wizards/ImageManipulationWizard.html
new file mode 100644
index 000000000000..2d3d92f218ed
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Templates/Wizards/ImageManipulationWizard.html
@@ -0,0 +1,102 @@
+
+<f:if condition="{image.properties.width}">
+	<f:then>
+		<div class="modal-panel">
+
+			<div class="modal-panel-sidebar modal-panel-sidebar-right">
+				<div class="modal-header">
+					<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
+					<h4 class="modal-title"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.image-manipulation" /></h4>
+				</div>
+				<div class="modal-body">
+					<form class="form">
+						<div class="form-group">
+							<label><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.image-title" />:</label>
+							<p>{f:if(condition:image.properties.title, then:image.properties.title, else:image.name)}</p>
+						</div>
+						<div class="form-group">
+							<label><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.original-dimensions" />:</label>
+							<p>{image.properties.width} × {image.properties.height}</p>
+						</div>
+
+						<f:if condition="{ratios}">
+						<div class="form-group">
+							<label for="ratio"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.aspect-ratio" /></label>
+							<div class="btn-group btn-group-justified t3js-ratio-buttons" data-toggle="buttons">
+								<f:for each="{ratios}" as="ratio" key="key" iteration="iteration">
+									<label class="btn btn-default" data-method="setAspectRatio" data-option="{key}" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.set-aspect-ratio')}"><input class="sr-only" id="aspestRatio{iteration.cycle}" name="aspestRatio" value="{key}" type="radio"> <span>{ratio}</span></label>
+								</f:for>
+							</div>
+						</div>
+						</f:if>
+
+						<f:if condition="{zoom}">
+						<div class="form-group t3js-setting-zoom">
+							<label for="zoom"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.zoom" /></label>
+							<div class="btn-group">
+								<button class="btn btn-default" data-method="zoom" data-option="0.1" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.zoom-in')}"><i class="fa fa-search-plus"></i></button>
+								<button class="btn btn-default" data-method="zoom" data-option="-0.1" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.zoom-out')}"><i class="fa fa-search-minus"></i></button>
+							</div>
+						</div>
+						</f:if>
+
+						<div class="form-group">
+							<label><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.selection" /></label>
+							<div class="table-fit-block">
+								<table class="table table-no-borders table-transparent t3js-image-manipulation-info">
+									<tr>
+										<td>
+											<f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-x"/>
+										</td>
+										<td class="t3js-image-manipulation-info-crop-x"></td>
+									</tr>
+									<tr>
+										<td>
+											<f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-y"/>
+										</td>
+										<td class="t3js-image-manipulation-info-crop-y"></td>
+									</tr>
+									<tr>
+										<td>
+											<f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-width"/>
+										</td>
+										<td class="t3js-image-manipulation-info-crop-width"></td>
+									</tr>
+									<tr>
+										<td>
+											<f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.crop-height"/>
+										</td>
+										<td class="t3js-image-manipulation-info-crop-height"></td>
+									</tr>
+								</table>
+							</div>
+							<div class="form-group">
+								<button class="btn btn-default" data-method="reset" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.reset')}"><i class="fa fa-refresh"></i> {f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.reset')}</button>
+							</div>
+						</div>
+
+					</form>
+				</div>
+				<div class="modal-footer">
+					<button class="btn btn-default" data-method="dismiss" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.cancel')}"><i class="fa fa-remove"></i> {f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.cancel')}</button>
+					<button class="btn btn-default" data-method="save" title="{f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.accept')}"><i class="fa fa-check"></i> {f:translate(key:'LLL:EXT:lang/locallang_wizards.xlf:imwizard.accept')}</button>
+				</div>
+			</div>
+
+			<div class="modal-panel-body">
+				<div class="t3js-cropper-image-container">
+					<img src="{f:uri.image(image:image, maxWidth:'1600', maxHeight: '1024')}"
+						 naturalWidth="{image.properties.width}" naturalHeight="{image.properties.height}" />
+				</div>
+			</div>
+
+		</div>
+
+	</f:then>
+	<f:else>
+		<div class="alert alert-danger">
+			<h4 class="alert-title"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.no-image-found" /></h4>
+			<p class="alert-message"><f:translate key="LLL:EXT:lang/locallang_wizards.xlf:imwizard.no-image-found-message" /></p>
+		</div>
+	</f:else>
+</f:if>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ImageManipulation.js b/typo3/sysext/backend/Resources/Public/JavaScript/ImageManipulation.js
new file mode 100644
index 000000000000..15392fdcf4c0
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/ImageManipulation.js
@@ -0,0 +1,271 @@
+/*
+ * 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!
+ */
+
+/**
+ * contains all logic for the image crop GUI
+ */
+define('TYPO3/CMS/Backend/ImageManipulation', ['jquery', 'TYPO3/CMS/Backend/Modal'], function ($) {
+
+	var ImageManipulation = {
+		margin: 20,
+		currentModal: null,
+		cropperSelector: '.t3js-cropper-image-container > img',
+		$trigger: null
+	};
+
+
+	/**
+	 * Initialize triggers
+	 */
+	ImageManipulation.initializeTrigger = function() {
+		var $triggers = $('.t3js-image-manipulation-trigger');
+		// Remove existing bind function
+		$triggers.off('click', ImageManipulation.buttonClick);
+		// Bind new function
+		$triggers.on('click', ImageManipulation.buttonClick);
+	};
+
+	/**
+	 * Functions that should be bind to the trigger button
+	 *
+	 * @param e click event
+	 */
+	ImageManipulation.buttonClick = function(e) {
+		e.preventDefault();
+		// Prevent double trigger
+		if (ImageManipulation.$trigger !== $(this)) {
+			ImageManipulation.$trigger = $(this);
+			ImageManipulation.show();
+		}
+	};
+
+	/**
+	 * Open modal with image to crop
+	 */
+	ImageManipulation.show = function() {
+		ImageManipulation.currentModal = top.TYPO3.Modal.loadUrl(
+			ImageManipulation.$trigger.data('image-name'),
+			TYPO3.Severity.notice,
+			[],
+			ImageManipulation.$trigger.data('url'),
+			ImageManipulation.initializeCropperModal,
+			'.modal-content'
+		);
+		ImageManipulation.currentModal.addClass('modal-dark');
+	};
+
+	/**
+	 * Initialize the cropper modal
+	 */
+	ImageManipulation.initializeCropperModal = function() {
+		top.require(['cropper', 'imagesloaded'], function(cropperJs, imagesLoaded) {
+			var $image = ImageManipulation.getCropper();
+
+			// wait until image is loaded
+			imagesLoaded($image, function() {
+				var $modal = ImageManipulation.currentModal.find('.modal-dialog');
+				var $modalContent = $modal.find('.modal-content');
+				var $modalPanelSidebar = $modal.find('.modal-panel-sidebar');
+				var $modalPanelBody = $modal.find('.modal-panel-body');
+				// Let modal auto-fill width
+				$modal.css({width:'auto', marginLeft: ImageManipulation.margin, marginRight: ImageManipulation.margin})
+					  .addClass('modal-image-manipulation modal-resize');
+
+				$modalContent.addClass('cropper-bg');
+
+				// Determine available height
+				var height = top.TYPO3.jQuery(window).height()
+						- (ImageManipulation.margin * 2);
+				$image.css({maxHeight: height});
+
+				// Wait a few microseconds before calculating available width (DOM isn't always updated direct)
+				setTimeout(function() {
+					$modalPanelBody.css({width: $modalContent.innerWidth() - $modalPanelSidebar.outerWidth() - (ImageManipulation.margin * 2)});
+
+					setTimeout(function() {
+						// Shrink modal when possible (the set left/right margin + width auto above makes it fill 100%)
+						var minWidth = Math.max(500, $image.outerWidth() + $modalPanelSidebar.outerWidth() + (ImageManipulation.margin * 2));
+						var width = $modal.width() > minWidth ? minWidth : $modal.width();
+						$modal.width(width);
+						$modalPanelBody.width(width - $modalPanelSidebar.outerWidth() - (ImageManipulation.margin * 2));
+
+						var modalBodyMinHeight = $modalContent.height() -
+							($modalPanelSidebar.find('.modal-header').outerHeight() + $modalPanelSidebar.find('.modal-body-footer').outerHeight());
+						$modalPanelSidebar.find('.modal-body').css('min-height', modalBodyMinHeight);
+
+						// Center modal horizontal
+						$modal.css({marginLeft: 'auto', marginRight: 'auto'});
+
+						// Center modal vertical
+						top.TYPO3.Modal.center();
+
+						// Wait a few microseconds to let the modal resize
+						setTimeout(ImageManipulation.initializeCropper, 100);
+					}, 100);
+
+				}, 100);
+			});
+
+		});
+	};
+
+	/**
+	 * Initialize cropper
+	 */
+	ImageManipulation.initializeCropper = function() {
+		var $image = ImageManipulation.getCropper(), cropData;
+
+		// Give img-container same dimensions as the image
+		ImageManipulation.currentModal.find('.t3js-cropper-image-container').
+		css({width: $image.width(), height: $image.height()});
+
+		var $trigger = ImageManipulation.$trigger;
+		var jsonString = $trigger.parent().find('#' + $trigger.data('field')).val();
+		if (jsonString.length) {
+			cropData = $.parseJSON(jsonString);
+		}
+
+		// Calculate current resize factor
+		var factor = $image.width() / $image.prop('naturalWidth');
+		var $infoX = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-x');
+		var $infoY = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-y');
+		var $infoWidth = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-width');
+		var $infoHeight = ImageManipulation.currentModal.find('.t3js-image-manipulation-info-crop-height');
+
+		$image.cropper({
+			autoCropArea: 0.5,
+			strict: false,
+			zoomable: ImageManipulation.currentModal.find('.t3js-setting-zoom').length > 0,
+			built: function() {
+				if (cropData) {
+					var cropBox = {};
+					cropBox.left = cropData.x * factor;
+					cropBox.top = cropData.y * factor;
+					cropBox.width = cropData.width * factor;
+					cropBox.height = cropData.height * factor;
+					$image.cropper('setCropBoxData', cropBox);
+				}
+			},
+			crop: function (data) {
+				$infoX.text(Math.round(data.x) + 'px');
+				$infoY.text(Math.round(data.y) + 'px');
+				$infoWidth.text(Math.round(data.height) + 'px');
+				$infoHeight.text(Math.round(data.width) + 'px');
+			}
+		});
+
+		// Destroy cropper when modal is closed
+		ImageManipulation.currentModal.on('hidden.bs.modal', function() {
+			$image.cropper('destroy');
+		});
+
+		ImageManipulation.initializeCroppingActions();
+	};
+
+	/**
+	 * Get image to be cropped
+	 *
+	 * @returns jQuery object
+	 */
+	ImageManipulation.getCropper = function() {
+		return ImageManipulation.currentModal.find(ImageManipulation.cropperSelector);
+	};
+
+	/**
+	 * Bind buttons from cropper tool panel
+	 */
+	ImageManipulation.initializeCroppingActions = function() {
+		ImageManipulation.currentModal.find('[data-method]').click(function(e) {
+			e.preventDefault();
+			var method = $(this).data('method');
+			var options = $(this).data('option') || {};
+			if (typeof ImageManipulation[method] === 'function') {
+				ImageManipulation[method](options);
+			}
+		});
+	};
+
+	/**
+	 * Change the aspect ratio of the crop box
+	 *
+	 * @param aspectRatio Number
+	 */
+	ImageManipulation.setAspectRatio = function(aspectRatio) {
+		var $cropper = ImageManipulation.getCropper();
+		$cropper.cropper('setAspectRatio', aspectRatio);
+	};
+
+	/**
+	 * Set zoom ratio
+	 *
+	 * Zoom in: requires a positive number (ratio > 0)
+	 * Zoom out: requires a negative number (ratio < 0)
+	 *
+	 * @param ratio Number
+	 */
+	ImageManipulation.zoom = function(ratio) {
+		var $cropper = ImageManipulation.getCropper();
+		$cropper.cropper('zoom', ratio);
+	};
+
+	/**
+	 * Save crop values in form and close modal
+	 */
+	ImageManipulation.save = function() {
+		var $image = ImageManipulation.getCropper();
+		var $trigger = ImageManipulation.$trigger;
+		var formFieldId = $trigger.data('field');
+		var $formField = $trigger.parent().find('#' + formFieldId);
+		var $formGroup = $formField.closest('.form-group');
+		var cropData = $image.cropper('getData');
+		var newValue = '';
+		$formGroup.addClass('has-change');
+		if (cropData.width > 0 && cropData.height > 0) {
+			newValue = JSON.stringify(cropData);
+			$formGroup.find('.t3js-image-manipulation-info').removeClass('hide');
+			$formGroup.find('.t3js-image-manipulation-info-crop-x').text(Math.round(cropData.x) + 'px');
+			$formGroup.find('.t3js-image-manipulation-info-crop-y').text(Math.round(cropData.y) + 'px');
+			$formGroup.find('.t3js-image-manipulation-info-crop-width').text(Math.round(cropData.width) + 'px');
+			$formGroup.find('.t3js-image-manipulation-info-crop-height').text(Math.round(cropData.height) + 'px');
+
+		} else {
+			$formGroup.find('.t3js-image-manipulation-info').addClass('hide');
+			$formGroup.find('.t3js-image-manipulation-preview').addClass('hide');
+		}
+		$formField.val(newValue);
+		ImageManipulation.dismiss();
+	};
+
+	/**
+	 * Reset crop selection
+	 */
+	ImageManipulation.reset = function() {
+		var $image = ImageManipulation.getCropper();
+		$image.cropper('clear');
+	};
+
+	/**
+	 * Close the current open modal
+	 */
+	ImageManipulation.dismiss = function() {
+		if (ImageManipulation.currentModal) {
+			ImageManipulation.currentModal.modal('hide').remove();
+			ImageManipulation.currentModal = null;
+		}
+	};
+
+	return function() {
+		TYPO3.ImageManipulation = ImageManipulation;
+		return ImageManipulation;
+	}();
+});
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
index 2ba2f2a95742..cfdbae8060e3 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
@@ -111,6 +111,29 @@ define('TYPO3/CMS/Backend/Modal', ['jquery', 'TYPO3/CMS/Backend/Notification', '
 		return $modal;
 	};
 
+	/**
+	 * load URL with AJAX, append the content to the modal-body
+	 * and trigger the callback
+	 *
+	 * @param {string} title
+	 * @param {int} severity
+	 * @param {array} buttons
+	 * @param {string} url
+	 * @param {string} target
+	 * @param {function} callback
+	 */
+	Modal.loadUrl = function(title, severity, buttons, url, callback, target) {
+		$.get(url, function(response) {
+			Modal.currentModal.find(target ? target : '.modal-body').empty().append(response);
+			if (callback) {
+				callback();
+			}
+			Modal.currentModal.trigger('modal-loaded');
+		}, 'html');
+		return Modal.show(title, '<p class="loadmessage"><i class="fa fa-spinner fa-spin fa-5x "></i></p>', severity, buttons);
+	};
+
+
 	/**
 	 * Shows a dialog
 	 * Events:
@@ -233,6 +256,7 @@ define('TYPO3/CMS/Backend/Modal', ['jquery', 'TYPO3/CMS/Backend/Notification', '
 		$(document).on('click', '.t3js-modal-trigger', function(evt) {
 			evt.preventDefault();
 			var $element = $(this);
+			var url = $element.data('url') || null;
 			var title = $element.data('title') || 'Alert';
 			var content = $element.data('content') || 'Are you sure?';
 			var severity = (typeof top.TYPO3.Severity[$element.data('severity')] !== 'undefined') ? top.TYPO3.Severity[$element.data('severity')] : top.TYPO3.Severity.info;
@@ -252,7 +276,13 @@ define('TYPO3/CMS/Backend/Modal', ['jquery', 'TYPO3/CMS/Backend/Notification', '
 					}
 				}
 			];
-			Modal.confirm(title, content, severity, buttons);
+			if (url !== null) {
+				var separator = (url.indexOf('?') > -1) ? '&' : '?';
+				var params = $.param({data: $element.data()});
+				Modal.loadUrl(title, severity, buttons, url + separator + params);
+			} else {
+				Modal.show(title, content, severity, buttons);
+			}
 		});
 	};
 
diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
index b3b4f36ed237..a305d2c2fa9e 100644
--- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php
+++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
@@ -1598,7 +1598,7 @@ class DataHandler {
 				$res = $this->checkValue_text($res, $value, $tcaFieldConf, $PP, $field);
 				break;
 			case 'passthrough':
-
+			case 'image_manipulation':
 			case 'user':
 				$res['value'] = $value;
 				break;
@@ -1612,7 +1612,6 @@ class DataHandler {
 				$res = $this->checkValue_radio($res, $value, $tcaFieldConf, $PP);
 				break;
 			case 'group':
-
 			case 'select':
 				$res = $this->checkValue_group_select($res, $value, $tcaFieldConf, $PP, $uploadedFiles, $field);
 				break;
diff --git a/typo3/sysext/core/Classes/Page/PageRenderer.php b/typo3/sysext/core/Classes/Page/PageRenderer.php
index 692b5bbf35c1..865c0b3bde66 100644
--- a/typo3/sysext/core/Classes/Page/PageRenderer.php
+++ b/typo3/sysext/core/Classes/Page/PageRenderer.php
@@ -1594,6 +1594,8 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface {
 				'datatables' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/jquery.dataTables',
 				'nprogress' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/nprogress',
 				'moment' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/moment',
+				'cropper' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/cropper.min',
+				'imagesloaded' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min',
 				'bootstrap' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/bootstrap/bootstrap',
 				'twbs/bootstrap-datetimepicker' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/bootstrap-datetimepicker',
 				'autosize' => $this->backPath . 'sysext/core/Resources/Public/JavaScript/Contrib/autosize',
diff --git a/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php b/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php
index 11e687bc3715..91675a538587 100644
--- a/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php
+++ b/typo3/sysext/core/Classes/Resource/Processing/LocalCropScaleMaskHelper.php
@@ -76,9 +76,18 @@ class LocalCropScaleMaskHelper {
 
 		$croppedImage = NULL;
 		if (!empty($configuration['crop'])) {
+
+			// check if it is a json object
+			$cropData = json_decode($configuration['crop']);
+			if ($cropData) {
+				$crop = implode(',', array((int)$cropData->x, (int)$cropData->y, (int)$cropData->width, (int)$cropData->height));
+			} else {
+				$crop = $configuration['crop'];
+			}
+
 			$im = $gifBuilder->imageCreateFromFile($originalFileName);
 			$croppedImage = Utility\GeneralUtility::tempnam('crop_', '.' . $sourceFile->getExtension());
-			$gifBuilder->crop($im, ['crop' => $configuration['crop']]);
+			$gifBuilder->crop($im, ['crop' => $crop]);
 			if ($gifBuilder->ImageWrite($im, $croppedImage)) {
 				$originalFileName = $croppedImage;
 			}
diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php
index 37b5fbdaf14a..0cfc56ff2321 100644
--- a/typo3/sysext/core/Configuration/DefaultConfiguration.php
+++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php
@@ -735,6 +735,10 @@ return array(
 			'UserSettings::process' => array(
 				'callbackMethod' => \TYPO3\CMS\Backend\Controller\UserSettingsController::class . '->processAjaxRequest',
 				'csrfTokenCheck' => TRUE
+			),
+			'ImageManipulationWizard::getHtmlForImageManipulationWizard' => array(
+				'callbackMethod' => \TYPO3\CMS\Backend\Form\Wizard\ImageManipulationWizard::class . '->getHtmlForImageManipulationWizard',
+				'csrfTokenCheck' => TRUE
 			)
 		),
 		'toolbarItems' => array(), // Array: Registered toolbar items classes
diff --git a/typo3/sysext/core/Configuration/TCA/sys_file_reference.php b/typo3/sysext/core/Configuration/TCA/sys_file_reference.php
index 9499f0c9eac7..cde302826507 100644
--- a/typo3/sysext/core/Configuration/TCA/sys_file_reference.php
+++ b/typo3/sysext/core/Configuration/TCA/sys_file_reference.php
@@ -221,10 +221,7 @@ return array(
 			'exclude' => 1,
 			'label' => 'LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.crop',
 			'config' => array(
-				'type' => 'input',
-				'size' => '10',
-				'max' => '30',
-				'placeholder' => 'x,y,w,h',
+				'type' => 'image_manipulation'
 			)
 		)
 	),
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-65585-AddTCATypeImage_manipulation.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-65585-AddTCATypeImage_manipulation.rst
new file mode 100644
index 000000000000..02ea06140b41
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-65585-AddTCATypeImage_manipulation.rst
@@ -0,0 +1,37 @@
+=================================================
+Feature - #65585: Add TCA type image_manipulation
+=================================================
+
+Description
+===========
+
+TCA type `image_manipulation` brings a image manipulation wizard to the core.
+
+This first version brings image cropping with the possibility to
+set a certain aspect ratio for the cropped area. The
+sys_file_reference.crop property is extended and can now also hold
+a json string to describe the image manipulation.
+
+The `LocalCropScaleMaskHelper` that is used by the core
+to create adjusted images is also adjusted to handle the new format.
+
+
+Impact
+======
+
+There is an new TCA type column type `image_manipulation` it supports the following config:
+
+- file_field: string, default `uid_local`
+- enableZoom: bool, default `FALSE`
+- allowedExtensions: string, default `$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']`
+- ratios: array, default
+    '1.7777777777777777' => '16:9',
+    '1.3333333333333333' => '4:3',
+    '1' => '1:1',
+    'NaN' => 'Free',
+
+When `ratios` is set in TCA the defaults are neglected.
+
+
+Property `sys_file_reference.crop` can now hold a string representing a json object. `LocalCropScaleMaskHelper` checks
+if the it can parse the string as json. If it can it assumes it holds the properties: `x`, `y`, `width` and `height`.
\ No newline at end of file
diff --git a/typo3/sysext/core/Resources/Public/JavaScript/Contrib/cropper.min.js b/typo3/sysext/core/Resources/Public/JavaScript/Contrib/cropper.min.js
new file mode 100644
index 000000000000..cb9502475ef6
--- /dev/null
+++ b/typo3/sysext/core/Resources/Public/JavaScript/Contrib/cropper.min.js
@@ -0,0 +1,10 @@
+/*!
+ * Cropper v0.9.1
+ * https://github.com/fengyuanchen/cropper
+ *
+ * Copyright (c) 2014-2015 Fengyuan Chen and contributors
+ * Released under the MIT license
+ *
+ * Date: 2015-03-21T04:58:27.265Z
+ */
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports?require("jquery"):jQuery)}(function(a){"use strict";function b(a){return"number"==typeof a}function c(a){return"undefined"==typeof a}function d(a,c){var d=[];return b(c)&&d.push(c),d.slice.apply(a,d)}function e(a,b){var c=d(arguments,2);return function(){return a.apply(b,c.concat(d(arguments)))}}function f(a){var b=a.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);return b&&(b[1]!==n.protocol||b[2]!==n.hostname||b[3]!==n.port)}function g(a){var b="timestamp="+(new Date).getTime();return a+(-1===a.indexOf("?")?"?":"&")+b}function h(a){return a?"rotate("+a+"deg)":"none"}function i(a,b){var c,d,e=P(a.degree)%180,f=(e>90?180-e:e)*Math.PI/180,g=Q(f),h=R(f),i=a.width,j=a.height,k=a.aspectRatio;return b?(c=i/(h+g/k),d=c/k):(c=i*h+j*g,d=i*g+j*h),{width:c,height:d}}function j(b,c){var d=a("<canvas>")[0],e=d.getContext("2d"),f=c.naturalWidth,g=c.naturalHeight,h=c.rotate,j=i({width:f,height:g,degree:h});return h?(d.width=j.width,d.height=j.height,e.save(),e.translate(j.width/2,j.height/2),e.rotate(h*Math.PI/180),e.drawImage(b,-f/2,-g/2,f,g),e.restore()):(d.width=f,d.height=g,e.drawImage(b,0,0,f,g)),d}function k(b,c){this.$element=a(b),this.options=a.extend({},k.DEFAULTS,a.isPlainObject(c)&&c),this.ready=!1,this.built=!1,this.rotated=!1,this.cropped=!1,this.disabled=!1,this.load()}var l=a(window),m=a(document),n=window.location,o=".cropper",p=/^(e|n|w|s|ne|nw|sw|se|all|crop|move|zoom)$/,q="cropper-modal",r="cropper-hide",s="cropper-hidden",t="cropper-invisible",u="cropper-move",v="cropper-crop",w="cropper-disabled",x="cropper-bg",y="mousedown touchstart",z="mousemove touchmove",A="mouseup mouseleave touchend touchleave touchcancel",B="wheel mousewheel DOMMouseScroll",C="dblclick",D="resize"+o,E="build"+o,F="built"+o,G="dragstart"+o,H="dragmove"+o,I="dragend"+o,J="zoomin"+o,K="zoomout"+o,L=a.isFunction(a("<canvas>")[0].getContext),M=Math.sqrt,N=Math.min,O=Math.max,P=Math.abs,Q=Math.sin,R=Math.cos,S=parseFloat,T={};T.load=function(b){var c,d,e,h=this.options,i=this.$element;b||(i.is("img")?b=i.prop("src"):i.is("canvas")&&L&&(b=i[0].toDataURL())),b&&(d=a.Event(E),i.one(E,h.build).trigger(d),d.isDefaultPrevented()||(h.checkImageOrigin&&f(b)&&(c=" crossOrigin",i.prop("crossOrigin")||(b=g(b))),this.$clone=e=a("<img>"),e.one("load",a.proxy(function(){var a=e.prop("naturalWidth")||e.width(),c=e.prop("naturalHeight")||e.height();this.image={naturalWidth:a,naturalHeight:c,aspectRatio:a/c,rotate:0},this.url=b,this.ready=!0,this.build()},this)).attr({src:b,crossOrigin:c}),e.addClass(r).insertAfter(i)))},T.build=function(){var b,c,d=this.$element,e=this.$clone,f=this.options;this.ready&&(this.built&&this.unbuild(),this.$cropper=b=a(k.TEMPLATE),d.addClass(s),e.removeClass(r),this.$container=d.parent().append(b),this.$canvas=b.find(".cropper-canvas").append(e),this.$dragBox=b.find(".cropper-drag-box"),this.$cropBox=c=b.find(".cropper-crop-box"),this.$viewBox=b.find(".cropper-view-box"),this.addListeners(),this.initPreview(),f.aspectRatio=S(f.aspectRatio)||0/0,f.autoCrop?(this.cropped=!0,f.modal&&this.$dragBox.addClass(q)):c.addClass(s),f.background&&b.addClass(x),f.highlight||c.find(".cropper-face").addClass(t),f.guides||c.find(".cropper-dashed").addClass(s),f.movable||c.find(".cropper-face").data("drag","move"),f.resizable||c.find(".cropper-line, .cropper-point").addClass(s),this.setDragMode(f.dragCrop?"crop":"move"),this.built=!0,this.render(),d.one(F,f.built).trigger(F))},T.unbuild=function(){this.built&&(this.built=!1,this.removeListeners(),this.$preview.empty(),this.$preview=null,this.$viewBox=null,this.$cropBox=null,this.$dragBox=null,this.$canvas=null,this.$container=null,this.$cropper.remove(),this.$cropper=null)},a.extend(T,{render:function(){this.initContainer(),this.initCanvas(),this.initCropBox()},initContainer:function(){var a=this.$element,b=this.$container,c=this.$cropper,d=this.options;c.addClass(s),a.removeClass(s),c.css(this.container={width:O(b.width(),S(d.minContainerWidth)||200),height:O(b.height(),S(d.minContainerHeight)||100)}),a.addClass(s),c.removeClass(s)},initCanvas:function(){var b=this.options,c=this.container,d=c.width,e=c.height,f=this.image,g=f.aspectRatio,h={aspectRatio:g,width:d,height:e,left:0,top:0,minLeft:-d,minTop:-e,maxLeft:d,maxTop:e,minWidth:0,minHeight:0,maxWidth:1/0,maxHeight:1/0};e*g>d?b.strict?h.width=e*g:h.height=d/g:b.strict?h.height=d/g:h.width=e*g,h.oldLeft=h.left=(d-h.width)/2,h.oldTop=h.top=(e-h.height)/2,this.canvas=h,this.limitCanvas(),this.initialImage=a.extend({},f),this.initialCanvas=a.extend({},this.canvas),this.renderCanvas()},limitCanvas:function(){var a=this.options,b=this.container,c=b.width,d=b.height,e=this.canvas,f=e.aspectRatio,g=c,h=d;d*f>c?a.strict?g=d*f:h=c/f:a.strict?h=c/f:g=d*f,a.strict?(e.minWidth=g,e.minHeight=h,e.maxLeft=0,e.maxTop=0,e.minLeft=c-g,e.minTop=d-h):(e.minLeft=-g,e.minTop=-h)},renderCanvas:function(b){var c,d,e,f=this.options,g=this.container,j=this.canvas,k=this.image;this.rotated&&(this.rotated=!1,e=i({width:k.width,height:k.height,degree:k.rotate}),c=e.width/e.height,c!==j.aspectRatio&&(j.left-=(e.width-j.width)/2,j.top-=(e.height-j.height)/2,j.width=e.width,j.height=e.height,j.aspectRatio=c,this.limitCanvas())),(j.width>j.maxWidth||j.width<j.minWidth)&&(j.left=j.oldLeft),(j.height>j.maxHeight||j.height<j.minHeight)&&(j.top=j.oldTop),j.width=N(O(j.width,j.minWidth),j.maxWidth),j.height=N(O(j.height,j.minHeight),j.maxHeight),f.strict?(j.minLeft=g.width-j.width,j.minTop=g.height-j.height):(j.minLeft=-j.width,j.minTop=-j.height),j.oldLeft=j.left=N(O(j.left,j.minLeft),j.maxLeft),j.oldTop=j.top=N(O(j.top,j.minTop),j.maxTop),this.$canvas.css({width:j.width,height:j.height,left:j.left,top:j.top}),k.rotate?(d=i({width:j.width,height:j.height,degree:k.rotate,aspectRatio:k.aspectRatio},!0),a.extend(k,{width:d.width,height:d.height,left:(j.width-d.width)/2,top:(j.height-d.height)/2})):a.extend(k,{width:j.width,height:j.height,left:0,top:0}),this.$clone.css({width:k.width,height:k.height,marginLeft:k.left,marginTop:k.top,transform:h(k.rotate)}),b&&(this.preview(),f.crop&&f.crop.call(this.$element,this.getData()))},initCropBox:function(){var b=this.options,c=this.container,d=this.canvas,e=b.strict,f=b.aspectRatio,g=S(b.minCropBoxWidth)||0,h=S(b.minCropBoxHeight)||0,i=S(b.autoCropArea)||.8,j=c.width,k=c.height,l={width:e?j:d.width,height:e?k:d.height,minWidth:g,minHeight:h,maxWidth:j,maxHeight:k};f&&(k*f>j?(l.height=l.width/f,l.maxHeight=j/f):(l.width=l.height*f,l.maxWidth=k*f),e||(l.height*d.aspectRatio>l.width?(l.height=d.height,l.width=l.height*f):(l.width=d.width,l.height=l.width/f)),g?l.minHeight=l.minWidth/f:h&&(l.minWidth=l.minHeight*f)),l.minWidth=N(l.maxWidth,l.minWidth),l.minHeight=N(l.maxHeight,l.minHeight),l.width=O(l.minWidth,l.width*i),l.height=O(l.minHeight,l.height*i),l.oldLeft=l.left=(j-l.width)/2,l.oldTop=l.top=(k-l.height)/2,this.initialCropBox=a.extend({},l),this.cropBox=l,this.cropped&&this.renderCropBox()},renderCropBox:function(){var a=this.options,b=this.container,c=b.width,d=b.height,e=this.$cropBox,f=this.cropBox;(f.width>f.maxWidth||f.width<f.minWidth)&&(f.left=f.oldLeft),(f.height>f.maxHeight||f.height<f.minHeight)&&(f.top=f.oldTop),f.width=N(O(f.width,f.minWidth),f.maxWidth),f.height=N(O(f.height,f.minHeight),f.maxHeight),f.oldLeft=f.left=N(O(f.left,0),c-f.width),f.oldTop=f.top=N(O(f.top,0),d-f.height),a.movable&&e.find(".cropper-face").data("drag",f.width===c&&f.height===d?"move":"all"),e.css({width:f.width,height:f.height,left:f.left,top:f.top}),this.disabled||(this.preview(),a.crop&&a.crop.call(this.$element,this.getData()))}}),T.initPreview=function(){var b=this.url;this.$preview=a(this.options.preview),this.$viewBox.html('<img src="'+b+'">'),this.$preview.each(function(){var c=a(this);c.data({width:c.width(),height:c.height()}).html('<img src="'+b+'" style="display:block;width:100%;min-width:0!important;min-height:0!important;max-width:none!important;max-height:none!important;image-orientation: 0deg!important">')})},T.preview=function(){var b=this.image,c=this.canvas,d=this.cropBox,e=b.width,f=b.height,g=d.left-c.left-b.left,i=d.top-c.top-b.top,j=b.rotate;this.cropped&&!this.disabled&&(this.$viewBox.find("img").css({width:e,height:f,marginLeft:-g,marginTop:-i,transform:h(j)}),this.$preview.each(function(){var b=a(this),c=b.data(),k=c.width/d.width,l=c.width,m=d.height*k;m>c.height&&(k=c.height/d.height,l=d.width*k,m=c.height),b.width(l).height(m).find("img").css({width:e*k,height:f*k,marginLeft:-g*k,marginTop:-i*k,transform:h(j)})}))},T.addListeners=function(){var b=this.options;this.$element.on(G,b.dragstart).on(H,b.dragmove).on(I,b.dragend).on(J,b.zoomin).on(K,b.zoomout),this.$cropper.on(y,a.proxy(this.dragstart,this)).on(C,a.proxy(this.dblclick,this)),b.zoomable&&b.mouseWheelZoom&&this.$cropper.on(B,a.proxy(this.wheel,this)),m.on(z,this._dragmove=e(this.dragmove,this)).on(A,this._dragend=e(this.dragend,this)),b.responsive&&l.on(D,this._resize=e(this.resize,this))},T.removeListeners=function(){var a=this.options;this.$element.off(G,a.dragstart).off(H,a.dragmove).off(I,a.dragend).off(J,a.zoomin).off(K,a.zoomout),this.$cropper.off(y,this.dragstart).off(C,this.dblclick),a.zoomable&&a.mouseWheelZoom&&this.$cropper.off(B,this.wheel),m.off(z,this._dragmove).off(A,this._dragend),a.responsive&&l.off(D,this._resize)},a.extend(T,{resize:function(){var b,c,d,e=this.$container,f=this.container;this.disabled||(d=e.width()/f.width,(1!==d||e.height()!==f.height)&&(b=this.getCanvasData(),c=this.getCropBoxData(),this.render(),this.setCanvasData(a.each(b,function(a,c){b[a]=c*d})),this.setCropBoxData(a.each(c,function(a,b){c[a]=b*d}))))},dblclick:function(){this.disabled||this.setDragMode(this.$dragBox.hasClass(v)?"move":"crop")},wheel:function(a){var b=a.originalEvent,c=1;this.disabled||(a.preventDefault(),b.deltaY?c=b.deltaY>0?1:-1:b.wheelDelta?c=-b.wheelDelta/120:b.detail&&(c=b.detail>0?1:-1),this.zoom(.1*c))},dragstart:function(b){var c,d,e,f=this.options,g=b.originalEvent,h=g&&g.touches,i=b;if(!this.disabled){if(h){if(e=h.length,e>1){if(!f.zoomable||!f.touchDragZoom||2!==e)return;i=h[1],this.startX2=i.pageX,this.startY2=i.pageY,c="zoom"}i=h[0]}if(c=c||a(i.target).data("drag"),p.test(c)){if(b.preventDefault(),d=a.Event(G,{originalEvent:g,dragType:c}),this.$element.trigger(d),d.isDefaultPrevented())return;this.dragType=c,this.cropping=!1,this.startX=i.pageX,this.startY=i.pageY,"crop"===c&&(this.cropping=!0,this.$dragBox.addClass(q))}}},dragmove:function(b){var c,d,e=this.options,f=b.originalEvent,g=f&&f.touches,h=b,i=this.dragType;if(!this.disabled){if(g){if(d=g.length,d>1){if(!e.zoomable||!e.touchDragZoom||2!==d)return;h=g[1],this.endX2=h.pageX,this.endY2=h.pageY}h=g[0]}if(i){if(b.preventDefault(),c=a.Event(H,{originalEvent:f,dragType:i}),this.$element.trigger(c),c.isDefaultPrevented())return;this.endX=h.pageX,this.endY=h.pageY,this.change()}}},dragend:function(b){var c,d=this.dragType;if(!this.disabled&&d){if(b.preventDefault(),c=a.Event(I,{originalEvent:b.originalEvent,dragType:d}),this.$element.trigger(c),c.isDefaultPrevented())return;this.cropping&&(this.cropping=!1,this.$dragBox.toggleClass(q,this.cropped&&this.options.modal)),this.dragType=""}}}),a.extend(T,{reset:function(){this.disabled||(this.image=a.extend({},this.initialImage),this.canvas=a.extend({},this.initialCanvas),this.renderCanvas(),this.cropped&&(this.cropBox=a.extend({},this.initialCropBox),this.renderCropBox()))},clear:function(){this.cropped&&!this.disabled&&(a.extend(this.cropBox,{left:0,top:0,width:0,height:0}),this.renderCropBox(),this.cropped=!1,this.$dragBox.removeClass(q),this.$cropBox.addClass(s))},destroy:function(){var a=this.$element;this.ready||this.$clone.off("load").remove(),this.unbuild(),a.removeClass(s).removeData("cropper")},replace:function(a){this.ready&&!this.disabled&&a&&this.load(a)},enable:function(){this.built&&(this.disabled=!1,this.$cropper.removeClass(w))},disable:function(){this.built&&(this.disabled=!0,this.$cropper.addClass(w))},move:function(a,c){var d=this.canvas;this.built&&!this.disabled&&b(a)&&b(c)&&(d.left+=a,d.top+=c,this.renderCanvas(!0))},zoom:function(b){var c,d,e,f=this.canvas;if(b=S(b),b&&this.built&&!this.disabled&&this.options.zoomable){if(c=a.Event(b>0?J:K),this.$element.trigger(c),c.isDefaultPrevented())return;b=-1>=b?1/(1-b):1>=b?1+b:b,d=f.width*b,e=f.height*b,f.left-=(d-f.width)/2,f.top-=(e-f.height)/2,f.width=d,f.height=e,this.renderCanvas(!0),this.setDragMode("move")}},rotate:function(a){var b=this.image;a=S(a),a&&this.built&&!this.disabled&&this.options.rotatable&&(b.rotate=(b.rotate+a)%360,this.rotated=!0,this.renderCanvas(!0))},getData:function(){var b,c,d=this.cropBox,e=this.canvas,f=this.image,g=f.rotate;return this.built&&this.cropped?(c={x:d.left-e.left,y:d.top-e.top,width:d.width,height:d.height},b=f.width/f.naturalWidth,a.each(c,function(a,d){d/=b,c[a]=d})):c={x:0,y:0,width:0,height:0},c.rotate=g,c},getContainerData:function(){return this.built?this.container:{}},getImageData:function(){return this.ready?this.image:{}},getCanvasData:function(){var a,b=this.canvas;return this.built&&(a={left:b.left,top:b.top,width:b.width,height:b.height}),a||{}},setCanvasData:function(c){var d=this.canvas,e=d.aspectRatio;this.built&&!this.disabled&&a.isPlainObject(c)&&(b(c.left)&&(d.left=c.left),b(c.top)&&(d.top=c.top),b(c.width)?(d.width=c.width,d.height=c.width/e):b(c.height)&&(d.height=c.height,d.width=c.height*e),this.renderCanvas(!0))},getCropBoxData:function(){var a,b=this.cropBox;return this.built&&this.cropped&&(a={left:b.left,top:b.top,width:b.width,height:b.height}),a||{}},setCropBoxData:function(c){var d=this.cropBox,e=this.options.aspectRatio;this.built&&this.cropped&&!this.disabled&&a.isPlainObject(c)&&(b(c.left)&&(d.left=c.left),b(c.top)&&(d.top=c.top),e?b(c.width)?(d.width=c.width,d.height=d.width/e):b(c.height)&&(d.height=c.height,d.width=d.height*e):(b(c.width)&&(d.width=c.width),b(c.height)&&(d.height=c.height)),this.renderCropBox())},getCroppedCanvas:function(b){var c,d,e,f,g,h,i,k,l,m,n;if(this.built&&this.cropped&&L)return a.isPlainObject(b)||(b={}),n=this.getData(),c=n.width,d=n.height,k=c/d,a.isPlainObject(b)&&(g=b.width,h=b.height,g?(h=g/k,i=g/c):h&&(g=h*k,i=h/d)),e=g||c,f=h||d,l=a("<canvas>")[0],l.width=e,l.height=f,m=l.getContext("2d"),b.fillColor&&(m.fillStyle=b.fillColor,m.fillRect(0,0,e,f)),m.drawImage.apply(m,function(){var a,b,e,f,g,h,k=j(this.$clone[0],this.image),l=k.width,m=k.height,o=[k],p=n.x,q=n.y;return-c>=p||p>l?p=a=e=g=0:0>=p?(e=-p,p=0,a=g=N(l,c+p)):l>=p&&(e=0,a=g=N(c,l-p)),0>=a||-d>=q||q>m?q=b=f=h=0:0>=q?(f=-q,q=0,b=h=N(m,d+q)):m>=q&&(f=0,b=h=N(d,m-q)),o.push(p,q,a,b),i&&(e*=i,f*=i,g*=i,h*=i),g>0&&h>0&&o.push(e,f,g,h),o}.call(this)),l},setAspectRatio:function(a){var b=this.options;this.disabled||c(a)||(b.aspectRatio=S(a)||0/0,this.built&&this.initCropBox())},setDragMode:function(a){var b=this.$dragBox,c=!1,d=!1;if(this.ready&&!this.disabled){switch(a){case"crop":this.options.dragCrop?(c=!0,b.data("drag",a)):d=!0;break;case"move":d=!0,b.data("drag",a);break;default:b.removeData("drag")}b.toggleClass(v,c).toggleClass(u,d)}}}),T.change=function(){var a,b=this.dragType,c=this.canvas,d=this.container,e=d.width,f=d.height,g=this.cropBox,h=g.width,i=g.height,j=g.left,k=g.top,l=j+h,m=k+i,n=!0,o=this.options.aspectRatio,p={x:this.endX-this.startX,y:this.endY-this.startY};switch(o&&(p.X=p.y*o,p.Y=p.x/o),b){case"all":j+=p.x,k+=p.y;break;case"e":if(p.x>=0&&(l>=e||o&&(0>=k||m>=f))){n=!1;break}h+=p.x,o&&(i=h/o,k-=p.Y/2),0>h&&(b="w",h=0);break;case"n":if(p.y<=0&&(0>=k||o&&(0>=j||l>=e))){n=!1;break}i-=p.y,k+=p.y,o&&(h=i*o,j+=p.X/2),0>i&&(b="s",i=0);break;case"w":if(p.x<=0&&(0>=j||o&&(0>=k||m>=f))){n=!1;break}h-=p.x,j+=p.x,o&&(i=h/o,k+=p.Y/2),0>h&&(b="e",h=0);break;case"s":if(p.y>=0&&(m>=f||o&&(0>=j||l>=e))){n=!1;break}i+=p.y,o&&(h=i*o,j-=p.X/2),0>i&&(b="n",i=0);break;case"ne":if(o){if(p.y<=0&&(0>=k||l>=e)){n=!1;break}i-=p.y,k+=p.y,h=i*o}else p.x>=0?e>l?h+=p.x:p.y<=0&&0>=k&&(n=!1):h+=p.x,p.y<=0?k>0&&(i-=p.y,k+=p.y):(i-=p.y,k+=p.y);0>h&&0>i?(b="sw",i=0,h=0):0>h?(b="nw",h=0):0>i&&(b="se",i=0);break;case"nw":if(o){if(p.y<=0&&(0>=k||0>=j)){n=!1;break}i-=p.y,k+=p.y,h=i*o,j+=p.X}else p.x<=0?j>0?(h-=p.x,j+=p.x):p.y<=0&&0>=k&&(n=!1):(h-=p.x,j+=p.x),p.y<=0?k>0&&(i-=p.y,k+=p.y):(i-=p.y,k+=p.y);0>h&&0>i?(b="se",i=0,h=0):0>h?(b="ne",h=0):0>i&&(b="sw",i=0);break;case"sw":if(o){if(p.x<=0&&(0>=j||m>=f)){n=!1;break}h-=p.x,j+=p.x,i=h/o}else p.x<=0?j>0?(h-=p.x,j+=p.x):p.y>=0&&m>=f&&(n=!1):(h-=p.x,j+=p.x),p.y>=0?f>m&&(i+=p.y):i+=p.y;0>h&&0>i?(b="ne",i=0,h=0):0>h?(b="se",h=0):0>i&&(b="nw",i=0);break;case"se":if(o){if(p.x>=0&&(l>=e||m>=f)){n=!1;break}h+=p.x,i=h/o}else p.x>=0?e>l?h+=p.x:p.y>=0&&m>=f&&(n=!1):h+=p.x,p.y>=0?f>m&&(i+=p.y):i+=p.y;0>h&&0>i?(b="nw",i=0,h=0):0>h?(b="sw",h=0):0>i&&(b="ne",i=0);break;case"move":c.left+=p.x,c.top+=p.y,this.renderCanvas(!0),n=!1;break;case"zoom":this.zoom(function(a,b,c,d){var e=M(a*a+b*b),f=M(c*c+d*d);return(f-e)/e}(P(this.startX-this.startX2),P(this.startY-this.startY2),P(this.endX-this.endX2),P(this.endY-this.endY2))),this.startX2=this.endX2,this.startY2=this.endY2,n=!1;break;case"crop":p.x&&p.y&&(a=this.$cropper.offset(),j=this.startX-a.left,k=this.startY-a.top,h=g.minWidth,i=g.minHeight,p.x>0?p.y>0?b="se":(b="ne",k-=i):p.y>0?(b="sw",j-=h):(b="nw",j-=h,k-=i),this.cropped||(this.cropped=!0,this.$cropBox.removeClass(s)))}n&&(g.width=h,g.height=i,g.left=j,g.top=k,this.dragType=b,this.renderCropBox()),this.startX=this.endX,this.startY=this.endY},a.extend(k.prototype,T),k.DEFAULTS={aspectRatio:0/0,autoCropArea:.8,crop:null,preview:"",strict:!0,responsive:!0,checkImageOrigin:!0,modal:!0,guides:!0,highlight:!0,background:!0,autoCrop:!0,dragCrop:!0,movable:!0,resizable:!0,rotatable:!0,zoomable:!0,touchDragZoom:!0,mouseWheelZoom:!0,minCropBoxWidth:0,minCropBoxHeight:0,minContainerWidth:200,minContainerHeight:100,build:null,built:null,dragstart:null,dragmove:null,dragend:null,zoomin:null,zoomout:null},k.setDefaults=function(b){a.extend(k.DEFAULTS,b)},k.TEMPLATE=function(a,b){return b=b.split(","),a.replace(/\d+/g,function(a){return b[a]})}('<0 6="5-container"><0 6="5-canvas"></0><0 6="5-2-9" 3-2="move"></0><0 6="5-crop-9"><1 6="5-view-9"></1><1 6="5-8 8-h"></1><1 6="5-8 8-v"></1><1 6="5-face" 3-2="all"></1><1 6="5-7 7-e" 3-2="e"></1><1 6="5-7 7-n" 3-2="n"></1><1 6="5-7 7-w" 3-2="w"></1><1 6="5-7 7-s" 3-2="s"></1><1 6="5-4 4-e" 3-2="e"></1><1 6="5-4 4-n" 3-2="n"></1><1 6="5-4 4-w" 3-2="w"></1><1 6="5-4 4-s" 3-2="s"></1><1 6="5-4 4-ne" 3-2="ne"></1><1 6="5-4 4-nw" 3-2="nw"></1><1 6="5-4 4-sw" 3-2="sw"></1><1 6="5-4 4-se" 3-2="se"></1></0></0>',"div,span,drag,data,point,cropper,class,line,dashed,box"),k.other=a.fn.cropper,a.fn.cropper=function(b){var e,f=d(arguments,1);return this.each(function(){var c,d=a(this),g=d.data("cropper");g||d.data("cropper",g=new k(this,b)),"string"==typeof b&&a.isFunction(c=g[b])&&(e=c.apply(g,f))}),c(e)?this:e},a.fn.cropper.Constructor=k,a.fn.cropper.setDefaults=k.setDefaults,a.fn.cropper.noConflict=function(){return a.fn.cropper=k.other,this}});
\ No newline at end of file
diff --git a/typo3/sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min.js b/typo3/sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min.js
new file mode 100644
index 000000000000..d66f658937d8
--- /dev/null
+++ b/typo3/sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min.js
@@ -0,0 +1,7 @@
+/*!
+ * imagesLoaded PACKAGED v3.1.8
+ * JavaScript is all like "You images are done yet or what?"
+ * MIT License
+ */
+
+(function(){function e(){}function t(e,t){for(var n=e.length;n--;)if(e[n].listener===t)return n;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var i=e.prototype,r=this,o=r.EventEmitter;i.getListeners=function(e){var t,n,i=this._getEvents();if("object"==typeof e){t={};for(n in i)i.hasOwnProperty(n)&&e.test(n)&&(t[n]=i[n])}else t=i[e]||(i[e]=[]);return t},i.flattenListeners=function(e){var t,n=[];for(t=0;e.length>t;t+=1)n.push(e[t].listener);return n},i.getListenersAsObject=function(e){var t,n=this.getListeners(e);return n instanceof Array&&(t={},t[e]=n),t||n},i.addListener=function(e,n){var i,r=this.getListenersAsObject(e),o="object"==typeof n;for(i in r)r.hasOwnProperty(i)&&-1===t(r[i],n)&&r[i].push(o?n:{listener:n,once:!1});return this},i.on=n("addListener"),i.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},i.once=n("addOnceListener"),i.defineEvent=function(e){return this.getListeners(e),this},i.defineEvents=function(e){for(var t=0;e.length>t;t+=1)this.defineEvent(e[t]);return this},i.removeListener=function(e,n){var i,r,o=this.getListenersAsObject(e);for(r in o)o.hasOwnProperty(r)&&(i=t(o[r],n),-1!==i&&o[r].splice(i,1));return this},i.off=n("removeListener"),i.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},i.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},i.manipulateListeners=function(e,t,n){var i,r,o=e?this.removeListener:this.addListener,s=e?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(i=n.length;i--;)o.call(this,t,n[i]);else for(i in t)t.hasOwnProperty(i)&&(r=t[i])&&("function"==typeof r?o.call(this,i,r):s.call(this,i,r));return this},i.removeEvent=function(e){var t,n=typeof e,i=this._getEvents();if("string"===n)delete i[e];else if("object"===n)for(t in i)i.hasOwnProperty(t)&&e.test(t)&&delete i[t];else delete this._events;return this},i.removeAllListeners=n("removeEvent"),i.emitEvent=function(e,t){var n,i,r,o,s=this.getListenersAsObject(e);for(r in s)if(s.hasOwnProperty(r))for(i=s[r].length;i--;)n=s[r][i],n.once===!0&&this.removeListener(e,n.listener),o=n.listener.apply(this,t||[]),o===this._getOnceReturnValue()&&this.removeListener(e,n.listener);return this},i.trigger=n("emitEvent"),i.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},i.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},i._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},i._getEvents=function(){return this._events||(this._events={})},e.noConflict=function(){return r.EventEmitter=o,e},"function"==typeof define&&define.amd?define("eventEmitter/EventEmitter",[],function(){return e}):"object"==typeof module&&module.exports?module.exports=e:this.EventEmitter=e}).call(this),function(e){function t(t){var n=e.event;return n.target=n.target||n.srcElement||t,n}var n=document.documentElement,i=function(){};n.addEventListener?i=function(e,t,n){e.addEventListener(t,n,!1)}:n.attachEvent&&(i=function(e,n,i){e[n+i]=i.handleEvent?function(){var n=t(e);i.handleEvent.call(i,n)}:function(){var n=t(e);i.call(e,n)},e.attachEvent("on"+n,e[n+i])});var r=function(){};n.removeEventListener?r=function(e,t,n){e.removeEventListener(t,n,!1)}:n.detachEvent&&(r=function(e,t,n){e.detachEvent("on"+t,e[t+n]);try{delete e[t+n]}catch(i){e[t+n]=void 0}});var o={bind:i,unbind:r};"function"==typeof define&&define.amd?define("eventie/eventie",o):e.eventie=o}(this),function(e,t){"function"==typeof define&&define.amd?define(["eventEmitter/EventEmitter","eventie/eventie"],function(n,i){return t(e,n,i)}):"object"==typeof exports?module.exports=t(e,require("wolfy87-eventemitter"),require("eventie")):e.imagesLoaded=t(e,e.EventEmitter,e.eventie)}(window,function(e,t,n){function i(e,t){for(var n in t)e[n]=t[n];return e}function r(e){return"[object Array]"===d.call(e)}function o(e){var t=[];if(r(e))t=e;else if("number"==typeof e.length)for(var n=0,i=e.length;i>n;n++)t.push(e[n]);else t.push(e);return t}function s(e,t,n){if(!(this instanceof s))return new s(e,t);"string"==typeof e&&(e=document.querySelectorAll(e)),this.elements=o(e),this.options=i({},this.options),"function"==typeof t?n=t:i(this.options,t),n&&this.on("always",n),this.getImages(),a&&(this.jqDeferred=new a.Deferred);var r=this;setTimeout(function(){r.check()})}function f(e){this.img=e}function c(e){this.src=e,v[e]=this}var a=e.jQuery,u=e.console,h=u!==void 0,d=Object.prototype.toString;s.prototype=new t,s.prototype.options={},s.prototype.getImages=function(){this.images=[];for(var e=0,t=this.elements.length;t>e;e++){var n=this.elements[e];"IMG"===n.nodeName&&this.addImage(n);var i=n.nodeType;if(i&&(1===i||9===i||11===i))for(var r=n.querySelectorAll("img"),o=0,s=r.length;s>o;o++){var f=r[o];this.addImage(f)}}},s.prototype.addImage=function(e){var t=new f(e);this.images.push(t)},s.prototype.check=function(){function e(e,r){return t.options.debug&&h&&u.log("confirm",e,r),t.progress(e),n++,n===i&&t.complete(),!0}var t=this,n=0,i=this.images.length;if(this.hasAnyBroken=!1,!i)return this.complete(),void 0;for(var r=0;i>r;r++){var o=this.images[r];o.on("confirm",e),o.check()}},s.prototype.progress=function(e){this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded;var t=this;setTimeout(function(){t.emit("progress",t,e),t.jqDeferred&&t.jqDeferred.notify&&t.jqDeferred.notify(t,e)})},s.prototype.complete=function(){var e=this.hasAnyBroken?"fail":"done";this.isComplete=!0;var t=this;setTimeout(function(){if(t.emit(e,t),t.emit("always",t),t.jqDeferred){var n=t.hasAnyBroken?"reject":"resolve";t.jqDeferred[n](t)}})},a&&(a.fn.imagesLoaded=function(e,t){var n=new s(this,e,t);return n.jqDeferred.promise(a(this))}),f.prototype=new t,f.prototype.check=function(){var e=v[this.img.src]||new c(this.img.src);if(e.isConfirmed)return this.confirm(e.isLoaded,"cached was confirmed"),void 0;if(this.img.complete&&void 0!==this.img.naturalWidth)return this.confirm(0!==this.img.naturalWidth,"naturalWidth"),void 0;var t=this;e.on("confirm",function(e,n){return t.confirm(e.isLoaded,n),!0}),e.check()},f.prototype.confirm=function(e,t){this.isLoaded=e,this.emit("confirm",this,t)};var v={};return c.prototype=new t,c.prototype.check=function(){if(!this.isChecked){var e=new Image;n.bind(e,"load",this),n.bind(e,"error",this),e.src=this.src,this.isChecked=!0}},c.prototype.handleEvent=function(e){var t="on"+e.type;this[t]&&this[t](e)},c.prototype.onload=function(e){this.confirm(!0,"onload"),this.unbindProxyEvents(e)},c.prototype.onerror=function(e){this.confirm(!1,"onerror"),this.unbindProxyEvents(e)},c.prototype.confirm=function(e,t){this.isConfirmed=!0,this.isLoaded=e,this.emit("confirm",this,t)},c.prototype.unbindProxyEvents=function(e){n.unbind(e.target,"load",this),n.unbind(e.target,"error",this)},s});
\ No newline at end of file
diff --git a/typo3/sysext/core/ext_tables.sql b/typo3/sysext/core/ext_tables.sql
index 7e714dddabca..a1e6c76959ea 100644
--- a/typo3/sysext/core/ext_tables.sql
+++ b/typo3/sysext/core/ext_tables.sql
@@ -412,7 +412,7 @@ CREATE TABLE sys_file_reference (
 	alternative tinytext,
 	link varchar(1024) DEFAULT '' NOT NULL,
 	downloadname tinytext,
-	crop varchar(64) DEFAULT '' NOT NULL,
+	crop varchar(4000) DEFAULT '' NOT NULL,
 
 	PRIMARY KEY (uid),
 	KEY parent (pid,deleted),
diff --git a/typo3/sysext/lang/locallang_tca.xlf b/typo3/sysext/lang/locallang_tca.xlf
index 0bcd5a81baef..abd0510fec7f 100644
--- a/typo3/sysext/lang/locallang_tca.xlf
+++ b/typo3/sysext/lang/locallang_tca.xlf
@@ -544,7 +544,7 @@
 				<source>Link</source>
 			</trans-unit>
 			<trans-unit id="sys_file_reference.crop">
-				<source>Crop image</source>
+				<source>Image manipulation</source>
 			</trans-unit>
 			<trans-unit id="sys_file_collection">
 				<source>File collection</source>
diff --git a/typo3/sysext/lang/locallang_wizards.xlf b/typo3/sysext/lang/locallang_wizards.xlf
index e77917f9e93e..02d04ae9af72 100644
--- a/typo3/sysext/lang/locallang_wizards.xlf
+++ b/typo3/sysext/lang/locallang_wizards.xlf
@@ -258,6 +258,78 @@
 			<trans-unit id="grid_columnHelp">
 				<source>The column position defines in which area the content is rendered in the frontend.</source>
 			</trans-unit>
+			<trans-unit id="imwizard.open-editor">
+				<source>Open Editor</source>
+			</trans-unit>
+			<trans-unit id="imwizard.supported-types-message">
+				<source>Image manipulation is only available for supported types:</source>
+			</trans-unit>
+			<trans-unit id="imwizard.no-image-found">
+				<source>No image found</source>
+			</trans-unit>
+			<trans-unit id="imwizard.no-image-found-message">
+				<source>No image found. Could also be that image is present but that the original dimensions are unknow.</source>
+			</trans-unit>
+			<trans-unit id="imwizard.image-manipulation">
+				<source>Image manipulation</source>
+			</trans-unit>
+			<trans-unit id="imwizard.image-title">
+				<source>Title</source>
+			</trans-unit>
+			<trans-unit id="imwizard.original-dimensions">
+				<source>Original dimensions</source>
+			</trans-unit>
+			<trans-unit id="imwizard.reset">
+				<source>Reset</source>
+			</trans-unit>
+			<trans-unit id="imwizard.accept">
+				<source>Accept</source>
+			</trans-unit>
+			<trans-unit id="imwizard.cancel">
+				<source>Cancel</source>
+			</trans-unit>
+			<trans-unit id="imwizard.aspect-ratio">
+				<source>Aspect ratio</source>
+			</trans-unit>
+			<trans-unit id="imwizard.set-aspect-ratio">
+				<source>Set Aspect Ratio</source>
+			</trans-unit>
+			<trans-unit id="imwizard.ratio.16_9">
+				<source>16:9</source>
+			</trans-unit>
+			<trans-unit id="imwizard.ratio.4_3">
+				<source>4:3</source>
+			</trans-unit>
+			<trans-unit id="imwizard.ratio.1_1">
+				<source>1:1</source>
+			</trans-unit>
+			<trans-unit id="imwizard.ratio.free">
+				<source>Free</source>
+			</trans-unit>
+			<trans-unit id="imwizard.zoom">
+				<source>Zoom</source>
+			</trans-unit>
+			<trans-unit id="imwizard.zoom-in">
+				<source>Zoom in</source>
+			</trans-unit>
+			<trans-unit id="imwizard.zoom-out">
+				<source>Zoom out</source>
+			</trans-unit>
+			<trans-unit id="imwizard.selection">
+				<source>Selected Size</source>
+			</trans-unit>
+			<trans-unit id="imwizard.crop-x">
+				<source>x:</source>
+			</trans-unit>
+			<trans-unit id="imwizard.crop-y">
+				<source>y:</source>
+			</trans-unit>
+			<trans-unit id="imwizard.crop-width">
+				<source>width:</source>
+			</trans-unit>
+			<trans-unit id="imwizard.crop-height">
+				<source>height:</source>
+			</trans-unit>
 		</body>
 	</file>
 </xliff>
diff --git a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_cropper.less b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_cropper.less
new file mode 100644
index 000000000000..708bb1802cf4
--- /dev/null
+++ b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_cropper.less
@@ -0,0 +1,19 @@
+.cropper-line {
+	background-color: #FFFFFF;
+}
+.cropper-point {
+	background-color: #FFFFFF;
+}
+.cropper-point.point-se:before {
+	background-color: #FFFFFF;
+}
+.cropper-view-box {
+	outline: 1px dashed #6699ff;
+	outline-color: rgb(255, 255, 255, 0.75);
+}
+.cropper-bg {
+	background-image: data-uri("../../Images/cropper-background.png");
+}
+.cropper-image-container {
+
+}
\ No newline at end of file
diff --git a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_modal.less b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_modal.less
index 59a1ba7e8fab..ca81489627f5 100644
--- a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_modal.less
+++ b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_modal.less
@@ -49,7 +49,6 @@
 		color: @modal-warning-color;
 		border-bottom-color: @modal-warning-border;
 	}
-
 	&.t3-modal-danger .modal-header {
 		background-color: @modal-danger-bg;
 		color: @modal-danger-color;
@@ -84,4 +83,74 @@
 	.transition(margin-top .1s ease-in);
 	border: none;
 	overflow: hidden;
+
+	.loadmessage {
+		text-align: center;
+		color: @gray-darker;
+	}
+}
+
+.modal-resize {
+	&.fade .modal-dialog {
+		.transition-property(~"height, width");
+		.transition-duration(.35s);
+		.transition-timing-function(ease);
+	}
+}
+
+.modal-image-manipulation {
+	.modal-body {
+		.col-lg-12 {
+			padding-right: 450px;
+			.panel {
+				margin: 0;
+				width: 400px;
+				position: absolute;
+				top: 0px;
+				right: 15px;
+			}
+		}
+	}
+}
+
+.modal.modal-dark {
+
+	color: #FFF;
+	.modal-content {
+		background-color: #212424;
+	}
+	.modal-header {
+		color: #FFF;
+		background-color: #484848;
+		border-bottom-color: #000000;
+	}
+	.modal-body, .modal-footer {
+		background-color: #212424;
+		color: #FFF;
+	}
+	.modal-footer {
+		border-top: none;
+	}
+}
+
+// Modal as panel
+.modal-panel {
+	.modal-panel-body {
+		float: left;
+		width: 400px;
+	}
+	.modal-panel-sidebar-right {
+		width: 300px;
+		float: right;
+		border-left: 1px solid #000000;
+	}
 }
+
+.modal-image-manipulation {
+	.modal-panel-body {
+		padding: 20px;
+		img {
+			max-width: 100%;
+		}
+	}
+}
\ No newline at end of file
diff --git a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less
index ef43277241b5..34a827bcfb10 100644
--- a/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less
+++ b/typo3/sysext/t3skin/Resources/Private/Styles/TYPO3/_element_table.less
@@ -192,7 +192,20 @@ table {
 		}
 	}
 }
-
+.table-no-borders {
+	border: none;
+	> thead,
+	> tbody,
+	> tfoot {
+		> tr {
+			> th,
+			> td {
+				border: none;
+				padding: 2px;
+			}
+		}
+	}
+}
 //
 // Fits the table in the viewport and makes overflow possible
 //
@@ -259,3 +272,16 @@ table {
 		width: auto;
 	}
 }
+.table-fit-block {
+	max-width: 100%;
+	width: auto;
+	display: block;
+	margin: 0;
+	> .table {
+		width: auto;
+	}
+}
+.table-spacer-wrap {
+	margin-top: 10px;
+	margin-bottom: 10px;
+}
diff --git a/typo3/sysext/t3skin/Resources/Private/Styles/t3skin.less b/typo3/sysext/t3skin/Resources/Private/Styles/t3skin.less
index a740e7b18da7..42415af047e7 100644
--- a/typo3/sysext/t3skin/Resources/Private/Styles/t3skin.less
+++ b/typo3/sysext/t3skin/Resources/Private/Styles/t3skin.less
@@ -69,6 +69,10 @@
 // Components from bootstrap plugins
 @import "@{library-directory-prefix}/eonasdan-bootstrap-datetimepicker/src/less/bootstrap-datetimepicker.less";
 
+// Image Manipulation Wizard base styles
+@import "@{library-directory-prefix}/cropper/src/less/cropper.less";
+
+
 /*!
  *  Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome
  *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
@@ -103,6 +107,7 @@
 
 @import "TYPO3/_element_animation.less";
 @import "TYPO3/_element_clipboard.less";
+@import "TYPO3/_element_cropper.less";
 @import "TYPO3/_element_csh.less";
 @import "TYPO3/_element_csm.less";
 @import "TYPO3/_element_docheader.less";
diff --git a/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css b/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css
index c9019bbdd7f8..b9bdf937b896 100644
--- a/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css
+++ b/typo3/sysext/t3skin/Resources/Public/Css/visual/t3skin.css
@@ -5608,6 +5608,252 @@ button.close {
     width: 283px;
   }
 }
+/*!
+ * Cropper v@VERSION
+ * https://github.com/fengyuanchen/cropper
+ *
+ * Copyright (c) 2014-@YEAR Fengyuan Chen and contributors
+ * Released under the MIT license
+ *
+ * Date: @DATE
+ */
+.cropper-container {
+  position: relative;
+  overflow: hidden;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  -webkit-tap-highlight-color: transparent;
+  -webkit-touch-callout: none;
+}
+.cropper-container img {
+  display: block;
+  image-orientation: 0deg !important;
+  width: 100%;
+  height: 100%;
+  min-width: 0 !important;
+  min-height: 0 !important;
+  max-width: none !important;
+  max-height: none !important;
+}
+.cropper-canvas,
+.cropper-drag-box,
+.cropper-crop-box,
+.cropper-modal {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+}
+.cropper-drag-box {
+  background-color: #fff;
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+.cropper-modal {
+  background-color: #000;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+.cropper-view-box {
+  display: block;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  outline: 1px solid #6699ff;
+  outline-color: rgba(102, 153, 255, 0.75);
+}
+.cropper-dashed {
+  position: absolute;
+  display: block;
+  border: 0 dashed #fff;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+.cropper-dashed.dashed-h {
+  top: 33.33333333%;
+  left: 0;
+  width: 100%;
+  height: 33.33333333%;
+  border-top-width: 1px;
+  border-bottom-width: 1px;
+}
+.cropper-dashed.dashed-v {
+  top: 0;
+  left: 33.33333333%;
+  width: 33.33333333%;
+  height: 100%;
+  border-right-width: 1px;
+  border-left-width: 1px;
+}
+.cropper-face,
+.cropper-line,
+.cropper-point {
+  position: absolute;
+  display: block;
+  width: 100%;
+  height: 100%;
+  opacity: 0.1;
+  filter: alpha(opacity=10);
+}
+.cropper-face {
+  top: 0;
+  left: 0;
+  cursor: move;
+  background-color: #fff;
+}
+.cropper-line {
+  background-color: #6699ff;
+}
+.cropper-line.line-e {
+  top: 0;
+  right: -3px;
+  width: 5px;
+  cursor: e-resize;
+}
+.cropper-line.line-n {
+  top: -3px;
+  left: 0;
+  height: 5px;
+  cursor: n-resize;
+}
+.cropper-line.line-w {
+  top: 0;
+  left: -3px;
+  width: 5px;
+  cursor: w-resize;
+}
+.cropper-line.line-s {
+  bottom: -3px;
+  left: 0;
+  height: 5px;
+  cursor: s-resize;
+}
+.cropper-point {
+  width: 5px;
+  height: 5px;
+  background-color: #6699ff;
+  opacity: 0.75;
+  filter: alpha(opacity=75);
+}
+.cropper-point.point-e {
+  top: 50%;
+  right: -3px;
+  margin-top: -3px;
+  cursor: e-resize;
+}
+.cropper-point.point-n {
+  top: -3px;
+  left: 50%;
+  margin-left: -3px;
+  cursor: n-resize;
+}
+.cropper-point.point-w {
+  top: 50%;
+  left: -3px;
+  margin-top: -3px;
+  cursor: w-resize;
+}
+.cropper-point.point-s {
+  bottom: -3px;
+  left: 50%;
+  margin-left: -3px;
+  cursor: s-resize;
+}
+.cropper-point.point-ne {
+  top: -3px;
+  right: -3px;
+  cursor: ne-resize;
+}
+.cropper-point.point-nw {
+  top: -3px;
+  left: -3px;
+  cursor: nw-resize;
+}
+.cropper-point.point-sw {
+  bottom: -3px;
+  left: -3px;
+  cursor: sw-resize;
+}
+.cropper-point.point-se {
+  right: -3px;
+  bottom: -3px;
+  width: 20px;
+  height: 20px;
+  cursor: se-resize;
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+.cropper-point.point-se:before {
+  position: absolute;
+  right: -50%;
+  bottom: -50%;
+  display: block;
+  width: 200%;
+  height: 200%;
+  content: " ";
+  background-color: #6699ff;
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+@media (min-width: 768px) {
+  .cropper-point.point-se {
+    width: 15px;
+    height: 15px;
+  }
+}
+@media (min-width: 992px) {
+  .cropper-point.point-se {
+    width: 10px;
+    height: 10px;
+  }
+}
+@media (min-width: 1200px) {
+  .cropper-point.point-se {
+    width: 5px;
+    height: 5px;
+    opacity: 0.75;
+    filter: alpha(opacity=75);
+  }
+}
+.cropper-bg {
+  background-image: url("../img/bg.png");
+}
+.cropper-invisible {
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+.cropper-hide {
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: -1;
+  width: auto!important;
+  height: auto!important;
+  min-width: 0!important;
+  min-height: 0!important;
+  max-width: none!important;
+  max-height: none!important;
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+.cropper-hidden {
+  display: none !important;
+}
+.cropper-move {
+  cursor: move;
+}
+.cropper-crop {
+  cursor: crosshair;
+}
+.cropper-disabled .cropper-canvas,
+.cropper-disabled .cropper-face,
+.cropper-disabled .cropper-line,
+.cropper-disabled .cropper-point {
+  cursor: not-allowed;
+}
 /*!
  *  Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome
  *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
@@ -7794,6 +8040,22 @@ table#typo3-clipboard tr.bgColor5 td a {
 table#typo3-clipboard tr.bgColor5 td img {
   vertical-align: middle;
 }
+.cropper-line {
+  background-color: #FFFFFF;
+}
+.cropper-point {
+  background-color: #FFFFFF;
+}
+.cropper-point.point-se:before {
+  background-color: #FFFFFF;
+}
+.cropper-view-box {
+  outline: 1px dashed #6699ff;
+  outline-color: #ffffff;
+}
+.cropper-bg {
+  background-image: url("../../Images/cropper-background.png");
+}
 .t3-help-link {
   display: inline!important;
 }
@@ -8459,6 +8721,62 @@ table#typo3-history-item img {
   border: none;
   overflow: hidden;
 }
+.modal-content .loadmessage {
+  text-align: center;
+  color: #1e1e1e;
+}
+.modal-resize.fade .modal-dialog {
+  -webkit-transition-property: height, width;
+  transition-property: height, width;
+  -webkit-transition-duration: 0.35s;
+  transition-duration: 0.35s;
+  -webkit-transition-timing-function: ease;
+  transition-timing-function: ease;
+}
+.modal-image-manipulation .modal-body .col-lg-12 {
+  padding-right: 450px;
+}
+.modal-image-manipulation .modal-body .col-lg-12 .panel {
+  margin: 0;
+  width: 400px;
+  position: absolute;
+  top: 0px;
+  right: 15px;
+}
+.modal.modal-dark {
+  color: #FFF;
+}
+.modal.modal-dark .modal-content {
+  background-color: #212424;
+}
+.modal.modal-dark .modal-header {
+  color: #FFF;
+  background-color: #484848;
+  border-bottom-color: #000000;
+}
+.modal.modal-dark .modal-body,
+.modal.modal-dark .modal-footer {
+  background-color: #212424;
+  color: #FFF;
+}
+.modal.modal-dark .modal-footer {
+  border-top: none;
+}
+.modal-panel .modal-panel-body {
+  float: left;
+  width: 400px;
+}
+.modal-panel .modal-panel-sidebar-right {
+  width: 300px;
+  float: right;
+  border-left: 1px solid #000000;
+}
+.modal-image-manipulation .modal-panel-body {
+  padding: 20px;
+}
+.modal-image-manipulation .modal-panel-body img {
+  max-width: 100%;
+}
 #typo3-pagetree {
   height: 100%;
 }
@@ -9167,6 +9485,18 @@ fieldset[disabled] .table .btn-default.active {
 .table-vertical-bottom > tfoot > tr > td {
   vertical-align: bottom;
 }
+.table-no-borders {
+  border: none;
+}
+.table-no-borders > thead > tr > th,
+.table-no-borders > tbody > tr > th,
+.table-no-borders > tfoot > tr > th,
+.table-no-borders > thead > tr > td,
+.table-no-borders > tbody > tr > td,
+.table-no-borders > tfoot > tr > td {
+  border: none;
+  padding: 2px;
+}
 .table-fit {
   width: 100%;
   margin-bottom: 1.5em;
@@ -9216,6 +9546,19 @@ fieldset[disabled] .table .btn-default.active {
 .table-fit-inline-block > .table {
   width: auto;
 }
+.table-fit-block {
+  max-width: 100%;
+  width: auto;
+  display: block;
+  margin: 0;
+}
+.table-fit-block > .table {
+  width: auto;
+}
+.table-spacer-wrap {
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
 .tooltip-inner {
   padding: 5px 10px;
 }
diff --git a/typo3/sysext/t3skin/Resources/Public/Images/cropper-background.png b/typo3/sysext/t3skin/Resources/Public/Images/cropper-background.png
new file mode 100644
index 0000000000000000000000000000000000000000..8eddf72bad35efba2cfeabdf79fafbbbe730fdfe
GIT binary patch
literal 158
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b
z3=G`DAk4@xYmNj^kiEpy*OmP~3m+c?qkGi}Q=pKxr;B5V#p&dbz`&p98`w8&+Vp60
xT0+tSfgULX37!N+k@NfO|I6_nd6>_@@U56-&hkH3jey!1JYD@<);T3K0RYG3En5Hp

literal 0
HcmV?d00001

-- 
GitLab