From 5fe4fcaf425e3143f59ab0037d2ebe68b329d5fc Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Sun, 22 Sep 2013 20:10:53 +0300
Subject: [PATCH] Issue #1898420 by claudiu.cristea, thedavidmeister, Albert
 Volkman, shanethehat, DamienMcKenna, c4rl: Image.module - Convert theme_
 functions to Twig.

---
 core/modules/image/image.admin.inc                 | 234 +++++++--------------
 core/modules/image/image.field.inc                 |  56 ++---
 core/modules/image/image.module                    |  61 +++---
 .../image/lib/Drupal/image/ImageEffectBase.php     |   5 +
 .../image/Plugin/ImageEffect/CropImageEffect.php   |  45 ++--
 .../image/Plugin/ImageEffect/ResizeImageEffect.php |   6 +-
 .../image/Plugin/ImageEffect/RotateImageEffect.php |   5 +-
 .../image/Plugin/ImageEffect/ScaleImageEffect.php  |   5 +-
 .../lib/Drupal/image/Tests/ImageDimensionsTest.php |  56 ++---
 .../Drupal/image/Tests/ImageFieldDisplayTest.php   |  13 +-
 .../modules/image/templates/image-anchor.html.twig |  14 ++
 .../image/templates/image-crop-summary.html.twig   |  22 ++
 .../image/templates/image-formatter.html.twig      |  24 +++
 .../image/templates/image-resize-summary.html.twig |  18 ++
 .../image/templates/image-rotate-summary.html.twig |  29 +++
 .../image/templates/image-scale-summary.html.twig  |  39 ++++
 .../image/templates/image-style-preview.html.twig  |  51 +++++
 core/modules/image/templates/image-style.html.twig |  29 +++
 .../modules/image/templates/image-widget.html.twig |  25 +++
 19 files changed, 467 insertions(+), 270 deletions(-)
 create mode 100644 core/modules/image/templates/image-anchor.html.twig
 create mode 100644 core/modules/image/templates/image-crop-summary.html.twig
 create mode 100644 core/modules/image/templates/image-formatter.html.twig
 create mode 100644 core/modules/image/templates/image-resize-summary.html.twig
 create mode 100644 core/modules/image/templates/image-rotate-summary.html.twig
 create mode 100644 core/modules/image/templates/image-scale-summary.html.twig
 create mode 100644 core/modules/image/templates/image-style-preview.html.twig
 create mode 100644 core/modules/image/templates/image-style.html.twig
 create mode 100644 core/modules/image/templates/image-widget.html.twig

diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc
index b8f9c96..09c1b67 100644
--- a/core/modules/image/image.admin.inc
+++ b/core/modules/image/image.admin.inc
@@ -5,6 +5,8 @@
  * Administration pages for image settings.
  */
 
+use Drupal\Component\Utility\String;
+
 /**
  * Returns HTML for a listing of the effects within a specific image style.
  *
@@ -64,113 +66,92 @@ function theme_image_style_effects($variables) {
 }
 
 /**
- * Returns HTML for a preview of an image style.
+ * Prepares variables for image style preview templates.
  *
- * @param $variables
+ * Default template: image-style-preview.html.twig.
+ *
+ * @param array $variables
  *   An associative array containing:
  *   - style: \Drupal\image\ImageStyleInterface image style being previewed.
- *
- * @ingroup themeable
  */
-function theme_image_style_preview($variables) {
+function template_preprocess_image_style_preview(&$variables) {
   $style = $variables['style'];
-
-  $sample_image = \Drupal::config('image.settings')->get('preview_image');
-  $sample_width = 160;
-  $sample_height = 160;
-
-  // Set up original file information.
-  $original_path = $sample_image;
   $image_factory = \Drupal::service('image.factory');
-  $original_image = $image_factory->get($original_path);
-  $original_image = array(
-    'width' => $original_image->getWidth(),
-    'height' => $original_image->getHeight(),
-  );
-  if ($original_image['width'] > $original_image['height']) {
-    $original_width = min($original_image['width'], $sample_width);
-    $original_height = round($original_width / $original_image['width'] * $original_image['height']);
-  }
-  else {
-    $original_height = min($original_image['height'], $sample_height);
-    $original_width = round($original_height / $original_image['height'] * $original_image['width']);
-  }
-  $original_image['style'] = 'width: ' . $original_width . 'px; height: ' . $original_height . 'px;';
 
-  // Set up preview file information.
-  $preview_file = $style->buildUri($original_path);
-  if (!file_exists($preview_file)) {
-    $style->createDerivative($original_path, $preview_file);
-  }
-  $preview_image = $image_factory->get($preview_file);
-  $preview_image = array(
-    'width' => $preview_image->getWidth(),
-    'height' => $preview_image->getHeight(),
+  // Sample image path, width and height.
+  $sample = array(
+    'path' => \Drupal::config('image.settings')->get('preview_image'),
+    'width' => 160,
+    'height' => 160,
   );
-  if ($preview_image['width'] > $preview_image['height']) {
-    $preview_width = min($preview_image['width'], $sample_width);
-    $preview_height = round($preview_width / $preview_image['width'] * $preview_image['height']);
-  }
-  else {
-    $preview_height = min($preview_image['height'], $sample_height);
-    $preview_width = round($preview_height / $preview_image['height'] * $preview_image['width']);
-  }
-  $preview_image['style'] = 'width: ' . $preview_width . 'px; height: ' . $preview_height . 'px;';
-
-  // In the previews, timestamps are added to prevent caching of images.
-  $output = '<div class="image-style-preview preview clearfix">';
 
-  // Build the preview of the original image.
-  $original_url = file_create_url($original_path);
-  $image = array(
-    '#theme' => 'image',
-    '#uri' => $original_path,
-    '#alt' => t('Sample original image'),
-    '#title' => '',
-    '#attributes' => $original_image,
+  // Init the set of original and preview images data.
+  $variables += array(
+    'style_id' => String::checkPlain($style->id()),
+    'style_name' => String::checkPlain($style->label()),
+    'original' => array(),
+    'derivative' => array(),
   );
-  $output .= '<div class="preview-image-wrapper">';
-  $output .= t('original') . ' (' . l(t('view actual size'), $original_url) . ')';
-  $output .= '<div class="preview-image original-image" style="' . $original_image['style'] . '">';
-  $output .= '<a href="' . $original_url . '">' . drupal_render($image) . '</a>';
-  $output .= '<div class="height" style="height: ' . $original_height . 'px"><span>' . $original_image['height'] . 'px</span></div>';
-  $output .= '<div class="width" style="width: ' . $original_width . 'px"><span>' . $original_image['width'] . 'px</span></div>';
-  $output .= '</div>'; // End preview-image.
-  $output .= '</div>'; // End preview-image-wrapper.
 
-  // Build the preview of the image style.
-  $preview_url = file_create_url($preview_file) . '?cache_bypass=' . REQUEST_TIME;
-  $image = array(
-    '#theme' => 'image',
-    '#uri' => $preview_url,
-    '#alt' => t('Sample modified image'),
-    '#title' => '',
-    '#attributes' => $preview_image,
+  $images = array(
+    'original' => array(
+      'label' => t('Sample original image'),
+      'path' => $sample['path'],
+    ),
+    'derivative' => array(
+      'label' => t('Sample modified image'),
+      'path' => $style->buildUri($sample['path']),
+    ),
   );
-  $output .= '<div class="preview-image-wrapper">';
-  $output .= check_plain($style->label()) . ' (' . l(t('view actual size'), file_create_url($preview_file) . '?' . time()) . ')';
-  $output .= '<div class="preview-image modified-image" style="' . $preview_image['style'] . '">';
-  $output .= '<a href="' . file_create_url($preview_file) . '?' . time() . '">' . drupal_render($image) . '</a>';
-  $output .= '<div class="height" style="height: ' . $preview_height . 'px"><span>' . $preview_image['height'] . 'px</span></div>';
-  $output .= '<div class="width" style="width: ' . $preview_width . 'px"><span>' . $preview_image['width'] . 'px</span></div>';
-  $output .= '</div>'; // End preview-image.
-  $output .= '</div>'; // End preview-image-wrapper.
 
-  $output .= '</div>'; // End image-style-preview.
+  // Create derivative if necessary.
+  if (!file_exists($images['derivative']['path'])) {
+    $style->createDerivative($sample['path'], $images['derivative']['path']);
+  }
 
-  return $output;
+  foreach ($images as $type => $image_info) {
+    $image = $image_factory->get($image_info['path']);
+    if ($image->getWidth() > $image->getHeight()) {
+      $width = min($image->getWidth(), $sample['width']);
+      $height = round($width / $image->getWidth() * $image->getHeight());
+    }
+    else {
+      $height = min($image->getHeight(), $sample['height']);
+      $width = round($height / $image->getHeight() * $image->getWidth());
+    }
+    $variables[$type]['url'] = file_create_url($image_info['path']);
+    $variables[$type]['width'] = $image->getWidth();
+    $variables[$type]['height'] = $image->getHeight();
+    $variables[$type]['attributes'] = array(
+      'width' => $width,
+      'height' => $height,
+      'style' => String::format('width: @width; height: @height;',
+        array(
+          '@width' => $width . 'px',
+          '@height' => $height . 'px',
+        )
+      ),
+      'alt' => $image_info['label'],
+    );
+    $variables[$type]['rendered'] = array(
+      '#theme' => 'image',
+      '#uri' => $image_info['path'],
+      '#alt' => $image_info['label'],
+      '#attributes' => $variables[$type]['attributes'],
+    );
+  }
 }
 
 /**
- * Returns HTML for a 3x3 grid of checkboxes for image anchors.
+ * Prepares variables for image anchor templates.
  *
- * @param $variables
- *   An associative array containing:
- *   - element: A render element containing radio buttons.
+ * Default template: image-anchor.html.twig.
  *
- * @ingroup themeable
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the image.
  */
-function theme_image_anchor($variables) {
+function template_preprocess_image_anchor(&$variables) {
   $element = $variables['element'];
 
   $rows = array();
@@ -178,86 +159,21 @@ function theme_image_anchor($variables) {
   foreach (element_children($element) as $n => $key) {
     $element[$key]['#attributes']['title'] = $element[$key]['#title'];
     unset($element[$key]['#title']);
-    $row[] = drupal_render($element[$key]);
+    $row[] = array(
+      'data' => $element[$key],
+    );
     if ($n % 3 == 3 - 1) {
       $rows[] = $row;
       $row = array();
     }
   }
 
-  $table = array(
+  $variables['table'] = array(
     '#theme' => 'table',
     '#header' => array(),
     '#rows' => $rows,
-    '#attributes' => array('class' => array('image-anchor')),
+    '#attributes' => array(
+      'class' => array('image-anchor'),
+    ),
   );
-  return drupal_render($table);
-}
-
-/**
- * Returns HTML for a summary of an image resize effect.
- *
- * @param $variables
- *   An associative array containing:
- *   - data: The current configuration for this resize effect.
- *
- * @ingroup themeable
- */
-function theme_image_resize_summary($variables) {
-  $data = $variables['data'];
-
-  if ($data['width'] && $data['height']) {
-    return check_plain($data['width']) . 'x' . check_plain($data['height']);
-  }
-  else {
-    return ($data['width']) ? t('width @width', array('@width' => $data['width'])) : t('height @height', array('@height' => $data['height']));
-  }
-}
-
-/**
- * Returns HTML for a summary of an image scale effect.
- *
- * @param $variables
- *   An associative array containing:
- *   - data: The current configuration for this scale effect.
- *
- * @ingroup themeable
- */
-function theme_image_scale_summary($variables) {
-  $image_resize_summary = array(
-    '#theme' => 'image_resize_summary',
-    '#data' => $variables['data'],
-  );
-  return drupal_render($image_resize_summary) . ' ' . ($variables['data']['upscale'] ? '(' . t('upscaling allowed') . ')' : '');
-}
-
-/**
- * Returns HTML for a summary of an image crop effect.
- *
- * @param $variables
- *   An associative array containing:
- *   - data: The current configuration for this crop effect.
- *
- * @ingroup themeable
- */
-function theme_image_crop_summary($variables) {
-  $image_resize_summary = array(
-    '#theme' => 'image_resize_summary',
-    '#data' => $variables['data'],
-  );
-  return drupal_render($image_resize_summary);
-}
-
-/**
- * Returns HTML for a summary of an image rotate effect.
- *
- * @param $variables
- *   An associative array containing:
- *   - data: The current configuration for this rotate effect.
- *
- * @ingroup themeable
- */
-function theme_image_rotate_summary($variables) {
-  $data = $variables['data'];
-  return ($data['random']) ? t('random between -@degrees&deg and @degrees&deg', array('@degrees' => str_replace('-', '', $data['degrees']))) : t('@degrees&deg', array('@degrees' => $data['degrees']));
 }
diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc
index 1111b8a..aa5e155 100644
--- a/core/modules/image/image.field.inc
+++ b/core/modules/image/image.field.inc
@@ -124,90 +124,76 @@ function _image_field_required_fields_validate($element, &$form_state) {
 }
 
 /**
- * Returns HTML for an image field widget.
+ * Prepares variables for image widget templates.
+ *
+ * Default template: image-widget.html.twig.
  *
  * @param array $variables
  *   An associative array containing:
  *   - element: A render element representing the image field widget.
- *
- * @ingroup themeable
  */
-function theme_image_widget($variables) {
+function template_preprocess_image_widget(&$variables) {
   $element = $variables['element'];
-  $output = '';
-  $output .= '<div class="image-widget form-managed-file clearfix">';
+  $element['#attributes']['class'] = array('image-widget', 'form-managed-file', 'clearfix');
 
   if (isset($element['preview'])) {
-    $output .= '<div class="image-preview">';
-    $output .= drupal_render($element['preview']);
-    $output .= '</div>';
+    $variables['preview'] = $element['preview'];
   }
 
-  $output .= '<div class="image-widget-data">';
   if (!empty($element['fids']['#value'])) {
     $file = reset($element['#files']);
     $element['file_' . $file->id()]['filename']['#suffix'] = ' <span class="file-size">(' . format_size($file->getSize()) . ')</span> ';
   }
-  $output .= drupal_render_children($element);
-  $output .= '</div>';
-  $output .= '</div>';
 
-  return $output;
+  $variables['data'] = $element;
+  $variables['attributes'] = $element['#attributes'];
 }
 
 /**
- * Returns HTML for an image field formatter.
+ * Prepares variables for image formatter templates.
+ *
+ * Default template: image-formatter.html.twig.
  *
  * @param array $variables
  *   An associative array containing:
  *   - item: An array of image data.
  *   - image_style: An optional image style.
  *   - path: An optional array containing the link 'path' and link 'options'.
- *
- * @ingroup themeable
  */
-function theme_image_formatter($variables) {
+function template_preprocess_image_formatter(&$variables) {
   $item = $variables['item'];
-  $image = array();
+  $variables['image'] = array();
 
   // Do not output an empty 'title' attribute.
   if (isset($item['title']) && drupal_strlen($item['title']) != 0) {
-    $image['#title'] = $item['title'];
+    $variables['image']['#title'] = $item['title'];
   }
 
   if (isset($item['entity']) && empty($item['uri'])) {
-    $image['#uri'] = $item['entity']->getFileUri();
+    $variables['image']['#uri'] = $item['entity']->getFileUri();
   }
   else {
-    $image['#uri'] = $item['uri'];
+    $variables['image']['#uri'] = $item['uri'];
   }
 
   foreach (array('width', 'height', 'alt', 'attributes') as $key) {
     if (isset($item[$key]) || array_key_exists($key, $item)) {
-      $image["#$key"] = $item[$key];
+      $variables['image']["#$key"] = $item[$key];
     }
   }
 
   if ($variables['image_style']) {
-    $image['#theme'] = 'image_style';
-    $image['#style_name'] = $variables['image_style'];
+    $variables['image']['#theme'] = 'image_style';
+    $variables['image']['#style_name'] = $variables['image_style'];
   }
   else {
-    $image['#theme'] = 'image';
+    $variables['image']['#theme'] = 'image';
   }
 
   // The link path and link options are both optional, but for the options to be
   // processed, the link path must at least be an empty string.
   if (isset($variables['path']['path'])) {
-    $path = $variables['path']['path'];
     $options = isset($variables['path']['options']) ? $variables['path']['options'] : array();
-    // When displaying an image inside a link, the html option must be TRUE.
-    $options['html'] = TRUE;
-    $output = l($image, $path, $options);
+    $variables['url'] = url($variables['path']['path'], $options);
   }
-  else {
-    $output = drupal_render($image);
-  }
-
-  return $output;
 }
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 910612b..da921f1 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -154,6 +154,7 @@ function image_theme() {
         'title' => NULL,
         'attributes' => array(),
       ),
+      'template' => 'image-style',
     ),
 
     // Theme functions in image.admin.inc.
@@ -164,36 +165,40 @@ function image_theme() {
     'image_style_preview' => array(
       'variables' => array('style' => NULL),
       'file' => 'image.admin.inc',
+      'template' => 'image-style-preview',
     ),
     'image_anchor' => array(
       'render element' => 'element',
       'file' => 'image.admin.inc',
+      'template' => 'image-anchor',
     ),
     'image_resize_summary' => array(
-      'variables' => array('data' => NULL),
-      'file' => 'image.admin.inc',
+      'variables' => array('data' => NULL, 'effect' => array()),
+      'template' => 'image-resize-summary',
     ),
     'image_scale_summary' => array(
-      'variables' => array('data' => NULL),
-      'file' => 'image.admin.inc',
+      'variables' => array('data' => NULL, 'effect' => array()),
+      'template' => 'image-scale-summary',
     ),
     'image_crop_summary' => array(
-      'variables' => array('data' => NULL),
-      'file' => 'image.admin.inc',
+      'variables' => array('data' => NULL, 'effect' => array()),
+      'template' => 'image-crop-summary',
     ),
     'image_rotate_summary' => array(
-      'variables' => array('data' => NULL),
-      'file' => 'image.admin.inc',
+      'variables' => array('data' => NULL, 'effect' => array()),
+      'template' => 'image-rotate-summary',
     ),
 
     // Theme functions in image.field.inc.
     'image_widget' => array(
       'render element' => 'element',
       'file' => 'image.field.inc',
+      'template' => 'image-widget',
     ),
     'image_formatter' => array(
       'variables' => array('item' => NULL, 'path' => NULL, 'image_style' => NULL),
       'file' => 'image.field.inc',
+      'template' => 'image-formatter',
     ),
   );
 }
@@ -327,18 +332,17 @@ function image_style_options($include_empty = TRUE) {
 }
 
 /**
- * Returns HTML for an image using a specific image style.
+ * Prepares variables for image style templates.
+ *
+ * Default template: image-style.html.twig.
  *
- * @param $variables
+ * @param array $variables
  *   An associative array containing:
- *   - style_name: The name of the style to be used to alter the original image.
- *   - uri: The path of the image file relative to the Drupal files directory.
- *     This function does not work with images outside the files directory nor
- *     with remotely hosted images. This should be in a format such as
- *     'images/image.jpg', or using a stream wrapper such as
- *     'public://images/image.jpg'.
- *   - width: The width of the source image (if known).
- *   - height: The height of the source image (if known).
+ *   - width: The width of the image.
+ *   - height: The height of the image.
+ *   - style_name: The name of the image style to be applied.
+ *   - attributes: Additional attributes to apply to the image.
+ *   - uri: URI of the source image before styling.
  *   - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0
  *     always require an alt attribute. The HTML 5 draft allows the alt
  *     attribute to be omitted in some cases. Therefore, this variable defaults
@@ -352,12 +356,8 @@ function image_style_options($include_empty = TRUE) {
  *   - title: The title text is displayed when the image is hovered in some
  *     popular browsers.
  *   - attributes: Associative array of attributes to be placed in the img tag.
- *
- * @ingroup themeable
  */
-function theme_image_style($variables) {
-  // @todo Image style loading will be moved outside theme in
-  //   https://drupal.org/node/2029649
+function template_preprocess_image_style(&$variables) {
   $style = entity_load('image_style', $variables['style_name']);
 
   // Determine the dimensions of the styled image.
@@ -365,28 +365,21 @@ function theme_image_style($variables) {
     'width' => $variables['width'],
     'height' => $variables['height'],
   );
-
   $style->transformDimensions($dimensions);
 
   // Add in the image style name as an HTML class.
   $variables['attributes']['class'][] = 'image-style-' . drupal_html_class($variables['style_name']);
 
-  $image = array(
+  $variables['image'] = array(
     '#theme' => 'image',
     '#width' => $dimensions['width'],
     '#height' => $dimensions['height'],
     '#attributes' => $variables['attributes'],
     '#uri' => $style->buildUrl($variables['uri']),
+    '#alt' => isset($variables['alt']) ? $variables['alt'] : NULL,
+    '#title' => isset($variables['title']) ? $variables['title'] : NULL,
+    '#style' => $style,
   );
-
-  if (isset($variables['alt']) || array_key_exists('alt', $variables)) {
-    $image['#alt'] = $variables['alt'];
-  }
-  if (isset($variables['title']) || array_key_exists('title', $variables)) {
-    $image['#title'] = $variables['title'];
-  }
-
-  return drupal_render($image);
 }
 
 /**
diff --git a/core/modules/image/lib/Drupal/image/ImageEffectBase.php b/core/modules/image/lib/Drupal/image/ImageEffectBase.php
index e915206..76e85ce 100644
--- a/core/modules/image/lib/Drupal/image/ImageEffectBase.php
+++ b/core/modules/image/lib/Drupal/image/ImageEffectBase.php
@@ -50,6 +50,11 @@ public function transformDimensions(array &$dimensions) {
   public function getSummary() {
     return array(
       '#markup' => '',
+      '#effect' => array(
+        'id' => $this->pluginDefinition['id'],
+        'label' => $this->label(),
+        'description' => $this->pluginDefinition['description'],
+      ),
     );
   }
 
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
index 39f58f3..3c846e1 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
@@ -40,10 +40,14 @@ public function applyEffect(ImageInterface $image) {
    * {@inheritdoc}
    */
   public function getSummary() {
-    return array(
+    $summary = array(
       '#theme' => 'image_crop_summary',
       '#data' => $this->configuration,
     );
+    $summary += parent::getSummary();
+    $summary['#data']['anchor_label'] = static::getAnchorOptions($this->configuration['anchor']);
+
+    return $summary;
   }
 
   /**
@@ -63,17 +67,7 @@ public function getForm() {
     $form['anchor'] = array(
       '#type' => 'radios',
       '#title' => t('Anchor'),
-      '#options' => array(
-        'left-top' => t('Top') . ' ' . t('Left'),
-        'center-top' => t('Top') . ' ' . t('Center'),
-        'right-top' => t('Top') . ' ' . t('Right'),
-        'left-center' => t('Center') . ' ' . t('Left'),
-        'center-center' => t('Center'),
-        'right-center' => t('Center') . ' ' . t('Right'),
-        'left-bottom' => t('Bottom') . ' ' . t('Left'),
-        'center-bottom' => t('Bottom') . ' ' . t('Center'),
-        'right-bottom' => t('Bottom') . ' ' . t('Right'),
-      ),
+      '#options' => static::getAnchorOptions(),
       '#theme' => 'image_anchor',
       '#default_value' => $this->configuration['anchor'],
       '#description' => t('The part of the image that will be retained during the crop.'),
@@ -81,4 +75,31 @@ public function getForm() {
     return $form;
   }
 
+  /**
+   * Builds a list of anchor options.
+   *
+   * @var string $anchor
+   *   (optional) If provided, only the label of this anchor will be returned.
+   *
+   * @return array|string
+   *   A list of anchor options or an anchor label if $anchor has been provided.
+   */
+  static protected function getAnchorOptions($anchor = NULL) {
+    $anchors = array(
+      'left-top' => t('Top') . ' ' . t('Left'),
+      'center-top' => t('Top') . ' ' . t('Center'),
+      'right-top' => t('Top') . ' ' . t('Right'),
+      'left-center' => t('Center') . ' ' . t('Left'),
+      'center-center' => t('Center'),
+      'right-center' => t('Center') . ' ' . t('Right'),
+      'left-bottom' => t('Bottom') . ' ' . t('Left'),
+      'center-bottom' => t('Bottom') . ' ' . t('Center'),
+      'right-bottom' => t('Bottom') . ' ' . t('Right'),
+    );
+    if (empty($anchor)) {
+      return $anchors;
+    }
+    return isset($anchors[$anchor]) ? $anchors[$anchor] : NULL;
+  }
+
 }
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php
index 27d5538..3053461 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php
@@ -48,10 +48,14 @@ public function transformDimensions(array &$dimensions) {
    * {@inheritdoc}
    */
   public function getSummary() {
-    return array(
+    $summary = parent::getSummary();
+    unset($summary['#markup']);
+    $summary += array(
       '#theme' => 'image_resize_summary',
       '#data' => $this->configuration,
     );
+
+    return $summary;
   }
 
   /**
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
index 23c0560..0b61780 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
@@ -76,10 +76,13 @@ public function transformDimensions(array &$dimensions) {
    * {@inheritdoc}
    */
   public function getSummary() {
-    return array(
+    $summary = array(
       '#theme' => 'image_rotate_summary',
       '#data' => $this->configuration,
     );
+    $summary += parent::getSummary();
+
+    return $summary;
   }
 
   /**
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php
index 87b0dac..92e7f45 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php
@@ -47,10 +47,13 @@ public function transformDimensions(array &$dimensions) {
    * {@inheritdoc}
    */
   public function getSummary() {
-    return array(
+    $summary = array(
       '#theme' => 'image_scale_summary',
       '#data' => $this->configuration,
     );
+    $summary += parent::getSummary();
+
+    return $summary;
   }
 
   /**
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
index c136fa6..f609590 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
@@ -34,7 +34,7 @@ public static function getInfo() {
   /**
    * Test styled image dimensions cumulatively.
    */
-  function testImageDimensions() {
+  public function testImageDimensions() {
     $image_factory = $this->container->get('image.factory');
     // Create a working copy of the file.
     $files = $this->drupalGetTestFiles('image');
@@ -48,15 +48,16 @@ function testImageDimensions() {
     $url = $style->buildUrl($original_uri);
 
     $variables = array(
-      'style_name' => 'test',
-      'uri' => $original_uri,
-      'width' => 40,
-      'height' => 20,
+      '#theme' => 'image_style',
+      '#style_name' => 'test',
+      '#uri' => $original_uri,
+      '#width' => 40,
+      '#height' => 20,
     );
     // Verify that the original image matches the hard-coded values.
     $image_file = $image_factory->get($original_uri);
-    $this->assertEqual($image_file->getWidth(), $variables['width']);
-    $this->assertEqual($image_file->getHeight(), $variables['height']);
+    $this->assertEqual($image_file->getWidth(), $variables['#width']);
+    $this->assertEqual($image_file->getHeight(), $variables['#height']);
 
     // Scale an image that is wider than it is high.
     $effect = array(
@@ -70,8 +71,7 @@ function testImageDimensions() {
     );
 
     $style->saveImageEffect($effect);
-    $img_tag = theme_image_style($variables);
-    $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="120" height="60" alt="" />');
+    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" width="120" height="60" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
@@ -91,8 +91,7 @@ function testImageDimensions() {
     );
 
     $style->saveImageEffect($effect);
-    $img_tag = theme_image_style($variables);
-    $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="60" height="120" alt="" />');
+    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" width="60" height="120" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
@@ -113,8 +112,7 @@ function testImageDimensions() {
     );
 
     $style->saveImageEffect($effect);
-    $img_tag = theme_image_style($variables);
-    $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="45" height="90" alt="" />');
+    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" width="45" height="90" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
@@ -135,8 +133,7 @@ function testImageDimensions() {
     );
 
     $style->saveImageEffect($effect);
-    $img_tag = theme_image_style($variables);
-    $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="45" height="90" alt="" />');
+    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" width="45" height="90" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
@@ -153,8 +150,7 @@ function testImageDimensions() {
     );
 
     $style->saveImageEffect($effect);
-    $img_tag = theme_image_style($variables);
-    $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="45" height="90" alt="" />');
+    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" width="45" height="90" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
@@ -174,8 +170,7 @@ function testImageDimensions() {
     );
 
     $style->saveImageEffect($effect);
-    $img_tag = theme_image_style($variables);
-    $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" alt="" />');
+    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
@@ -194,8 +189,7 @@ function testImageDimensions() {
     );
 
     $style->saveImageEffect($effect);
-    $img_tag = theme_image_style($variables);
-    $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="30" height="30" alt="" />');
+    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" width="30" height="30" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
@@ -215,8 +209,7 @@ function testImageDimensions() {
     );
 
     $effect_id = $style->saveImageEffect($effect);
-    $img_tag = theme_image_style($variables);
-    $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" alt="" />');
+    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
@@ -234,7 +227,20 @@ function testImageDimensions() {
     );
 
     $style->saveImageEffect($effect);
-    $img_tag = theme_image_style($variables);
-    $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" alt="" />');
+    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" alt="" />');
   }
+
+  /**
+   * Render an image style element.
+   *
+   * drupal_render() alters the passed $variables array by adding a new key
+   * '#printed' => TRUE. This prevents next call to re-render the element. We
+   * wrap drupal_render() in a helper protected method and pass each time a
+   * fresh array so that $variables won't get altered and the element is
+   * re-rendered each time.
+   */
+  protected function getImageTag($variables) {
+    return drupal_render($variables);
+  }
+
 }
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php
index 5c27462..157ff93 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php
@@ -105,15 +105,24 @@ function _testImageFieldFormatters($scheme) {
     $display_options['settings']['image_link'] = 'content';
     $display->setComponent($field_name, $display_options)
       ->save();
+
     $image = array(
       '#theme' => 'image',
       '#uri' => $image_uri,
       '#width' => 40,
       '#height' => 20,
     );
-    $default_output = l($image, 'node/' . $nid, array('html' => TRUE, 'attributes' => array('class' => 'active')));
     $this->drupalGet('node/' . $nid);
-    $this->assertRaw($default_output, 'Image linked to content formatter displaying correctly on full node view.');
+    $elements = $this->xpath(
+      '//a[@href=:path]/img[@src=:url and @alt="" and @width=:width and @height=:height]',
+      array(
+        ':path' => url('node/' . $nid),
+        ':url' => file_create_url($image['#uri']),
+        ':width' => $image['#width'],
+        ':height' => $image['#height'],
+      )
+    );
+    $this->assertEqual(count($elements), 1, 'Image linked to content formatter displaying correctly on full node view.');
 
     // Test the image style 'thumbnail' formatter.
     $display_options['settings']['image_link'] = '';
diff --git a/core/modules/image/templates/image-anchor.html.twig b/core/modules/image/templates/image-anchor.html.twig
new file mode 100644
index 0000000..eb670a4
--- /dev/null
+++ b/core/modules/image/templates/image-anchor.html.twig
@@ -0,0 +1,14 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a 3x3 grid of checkboxes for image anchors.
+ *
+ * Available variables:
+ * - table: HTML for the table of image anchors.
+ *
+ * @see template_preprocess_image_anchor()
+ *
+ * @ingroup themeable
+ */
+#}
+{{ table }}
diff --git a/core/modules/image/templates/image-crop-summary.html.twig b/core/modules/image/templates/image-crop-summary.html.twig
new file mode 100644
index 0000000..39585b3
--- /dev/null
+++ b/core/modules/image/templates/image-crop-summary.html.twig
@@ -0,0 +1,22 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a summary of an image crop effect.
+ *
+ * Available variables:
+ * - data: The current configuration for this resize effect, including:
+ *   - width: The width of the resized image.
+ *   - height: The height of the resized image.
+ *   - anchor: The part of the image that will be retained after cropping.
+ *   - anchor_label: The translated label of the crop anchor.
+ * - effect: The effect information, including:
+ *   - id: The effect identifier.
+ *   - label: The effect name.
+ *   - description: The effect description.
+ *
+ * @ingroup themeable
+ */
+#}
+{% spaceless %}
+  {{ data.width }}x{{ data.height }}
+{% endspaceless %}
diff --git a/core/modules/image/templates/image-formatter.html.twig b/core/modules/image/templates/image-formatter.html.twig
new file mode 100644
index 0000000..3ed72b4
--- /dev/null
+++ b/core/modules/image/templates/image-formatter.html.twig
@@ -0,0 +1,24 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a formatted image field.
+ *
+ * Available variables:
+ * - image: A collection of image data.
+ * - image_style: An optional image style.
+ * - path: An optional array containing the link 'path' and link 'options'.
+ * - url: An optional URL the image can be linked to.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_image_formatter()
+ *
+ * @ingroup themeable
+ */
+#}
+{% spaceless %}
+  {% if url %}
+    <a href="{{ url }}">{{ image }}</a>
+  {% else %}
+    {{ image }}
+  {% endif %}
+{% endspaceless %}
diff --git a/core/modules/image/templates/image-resize-summary.html.twig b/core/modules/image/templates/image-resize-summary.html.twig
new file mode 100644
index 0000000..cc502c5
--- /dev/null
+++ b/core/modules/image/templates/image-resize-summary.html.twig
@@ -0,0 +1,18 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a summary of an image resize effect.
+ *
+ * Available variables:
+ * - data: The current configuration for this resize effect, including:
+ *   - width: The width of the resized image.
+ *   - height: The height of the resized image.
+ * - effect: The effect information, including:
+ *   - id: The effect identifier.
+ *   - label: The effect name.
+ *   - description: The effect description.
+ *
+ * @ingroup themeable
+ */
+#}
+{{ data.width }}x{{ data.height }}
diff --git a/core/modules/image/templates/image-rotate-summary.html.twig b/core/modules/image/templates/image-rotate-summary.html.twig
new file mode 100644
index 0000000..913728a
--- /dev/null
+++ b/core/modules/image/templates/image-rotate-summary.html.twig
@@ -0,0 +1,29 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a summary of an image rotate effect.
+ *
+ * Available variables:
+ * - data: The current configuration for this resize effect, including:
+ *   - degrees: Degrees to rotate the image, positive values will rotate the
+ *     image clockwise, negative values counter-clockwise.
+ *   - bgcolor: The hex background color of the new areas created as consequence
+ *     of rotation.
+ *   - random: If the rotation angle is randomized.
+ * - effect: The effect information, including:
+ *   - id: The effect identifier.
+ *   - label: The effect name.
+ *   - description: The effect description.
+ *
+ * @ingroup themeable
+ */
+#}
+{% spaceless %}
+  {% if data.random %}
+    {% trans %}
+      random between -{{ data.degrees|abs }}&deg; and {{ data.degrees|abs }}&deg;
+    {% endtrans %}
+  {% else %}
+    {{ data.degrees }}&deg;
+  {% endif %}
+{% endspaceless %}
diff --git a/core/modules/image/templates/image-scale-summary.html.twig b/core/modules/image/templates/image-scale-summary.html.twig
new file mode 100644
index 0000000..cd3cfbb
--- /dev/null
+++ b/core/modules/image/templates/image-scale-summary.html.twig
@@ -0,0 +1,39 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a summary of an image scale effect.
+ *
+ * Available variables:
+ * - data: The current configuration for this resize effect, including:
+ *   - width: The width of the resized image.
+ *   - height: The height of the resized image.
+ *   - upscale: If images larger than their original size can scale.
+ * - effect: The effect information, including:
+ *   - id: The effect identifier.
+ *   - label: The effect name.
+ *   - description: The effect description.
+ *
+ * @ingroup themeable
+ */
+#}
+{% spaceless %}
+  {% if data.width and data.height %}
+    {{ data.width }}x{{ data.height }}
+  {% else %}
+    {% if data.width %}
+      {% trans %}
+        width {{ data.width }}
+      {% endtrans %}
+    {% elseif data.height %}
+      {% trans %}
+        height {{ data.height }}
+      {% endtrans %}
+    {% endif %}
+  {% endif %}
+
+  {% if data.upscale %}
+    {% trans %}
+      (upscaling allowed)
+    {% endtrans %}
+  {% endif %}
+{% endspaceless %}
diff --git a/core/modules/image/templates/image-style-preview.html.twig b/core/modules/image/templates/image-style-preview.html.twig
new file mode 100644
index 0000000..1f52a67
--- /dev/null
+++ b/core/modules/image/templates/image-style-preview.html.twig
@@ -0,0 +1,51 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a preview of an image style.
+ *
+ * Available variables:
+ * - style_id: The ID of the image style.
+ * - style_name: The name of the image style.
+ * - original: An associative array containing:
+ *   - url: The URL of the original image.
+ *   - width: The width in pixels of the original image.
+ *   - height: The height in pixels of the original image.
+ *   - attributes: HTML sample attributes for the original style.
+ *   - rendered: The rendered original image.
+ * - derivative: An associative array containing:
+ *   - url: The URL of the derivative image.
+ *   - width: The width in pixels of the derivative image.
+ *   - height: The height in pixels of the derivative image.
+ *   - attributes: HTML sample attributes for the derivative style.
+ *   - rendered:  The rendered derivative image.
+ *
+ * @see template_preprocess_image_style_preview()
+ *
+ * @ingroup themeable
+ */
+#}
+<div class="image-style-preview preview clearfix">
+  {# Preview of the original image. #}
+  <div class="preview-image-wrapper">
+      {{ 'original'|t }} (<a href="{{ original.url }}">{{ 'view actual size'|t }}</a>)
+      <div class="preview-image original-image" style="{{ original.attributes.style }}">
+        <a href="{{ original.url }}">
+          {{ original.rendered }}
+        </a>
+      <div class="height" style="height: {{ original.attributes.height }}px"><span>{{ original.height }}px</span></div>
+      <div class="width" style="width: {{ original.attributes.width }}px"><span>{{ original.width }}px</span></div>
+    </div>
+  </div>{# End preview-image-wrapper. #}
+
+  {# Derivative of the image style. #}
+  <div class="preview-image-wrapper">
+    {{ style_name }} (<a href="{{ derivative.url }}">{{ 'view actual size'|t }}</a>)
+    <div class="preview-image derivative-image" style="{{ derivative.attributes.style }}">
+      <a href="{{ derivative.url }}">
+        {{ derivative.rendered }}
+      </a>
+      <div class="height" style="height: {{ derivative.attributes.height }}px"><span>{{ derivative.height }}px</span></div>
+      <div class="width" style="width: {{ derivative.attributes.width }}px"><span>{{ derivative.width }}px</span></div>
+    </div>
+  </div>{# End preview-image-wrapper. #}
+</div>
diff --git a/core/modules/image/templates/image-style.html.twig b/core/modules/image/templates/image-style.html.twig
new file mode 100644
index 0000000..6b52839
--- /dev/null
+++ b/core/modules/image/templates/image-style.html.twig
@@ -0,0 +1,29 @@
+{#
+/**
+ * @file
+ * Default theme implementation for an image using a specific image style.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the image, including the following:
+ *   - src: Full URL or relative path to the image file.
+ *   - class: One or more classes to be applied to the image.
+ *   - width: The width of the image (if known).
+ *   - height: The height of the image (if known).
+ *   - title: The title of the image.
+ *   - alt: The alternate text for the image. HTML 4 and XHTML 1.0 always
+ *     require an alt attribute. The HTML 5 draft allows the alt attribute to be
+ *     omitted in some cases. Therefore, this variable defaults to an empty
+ *     string, but can be set to NULL for the attribute to be omitted. Usually,
+ *     neither omission nor an empty string satisfies accessibility
+ *     requirements, so it is strongly encouraged for code calling
+ *     theme('image') to pass a meaningful value for this variable.
+ *
+ * @see template_preprocess_image_style()
+ * @see http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
+ * @see http://www.w3.org/TR/xhtml1/dtds.html
+ * @see http://dev.w3.org/html5/spec/Overview.html#alt
+ *
+ * @ingroup themeable
+ */
+#}
+{% spaceless %}{{ image }}{% endspaceless %}
diff --git a/core/modules/image/templates/image-widget.html.twig b/core/modules/image/templates/image-widget.html.twig
new file mode 100644
index 0000000..43faffc
--- /dev/null
+++ b/core/modules/image/templates/image-widget.html.twig
@@ -0,0 +1,25 @@
+{#
+/**
+ * @file
+ * Default theme implementation for an image field widget.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the containing element.
+ * - preview: A rendered preview image.
+ * - data: Render elements of image data.
+ *
+ * @see template_preprocess_image_widget()
+ *
+ * @ingroup themeable
+ */
+#}
+<div{{ attributes }}>
+  {% if preview is defined %}
+    <div class="image-preview">
+      {{ preview }}
+    </div>
+  {% endif %}
+  <div class="image-widget-data">
+    {{ data }}
+  </div>
+</div>
-- 
1.8.3.1

