diff --git a/core/modules/responsive_image/lib/Drupal/responsive_image/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php b/core/modules/responsive_image/lib/Drupal/responsive_image/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php
index 8f935c0..bfd3540 100644
--- a/core/modules/responsive_image/lib/Drupal/responsive_image/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php
+++ b/core/modules/responsive_image/lib/Drupal/responsive_image/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php
@@ -114,18 +114,9 @@ public function settingsSummary() {
    */
   public function viewElements(FieldItemListInterface $items) {
     $elements = array();
-    // Check if the formatter involves a link.
-    if ($this->getSetting('image_link') == 'content') {
-      $uri = $items->getEntity()->urlInfo();
-      // @todo Remove when theme_responsive_image_formatter() has support for route name.
-      $uri['path'] = $items->getEntity()->getSystemPath();
-    }
-    elseif ($this->getSetting('image_link') == 'file') {
-      $link_file = TRUE;
-    }
-
     $breakpoint_styles = array();
-    $fallback_image_style = '';
+    $image_styles = array_keys(image_style_options(FALSE));
+    $fallback_image_style = reset($image_styles);
 
     $responsive_image_mapping = entity_load('responsive_image_mapping', $this->getSetting('responsive_image_mapping'));
     if ($responsive_image_mapping) {
@@ -163,22 +154,52 @@ public function viewElements(FieldItemListInterface $items) {
     }
 
     foreach ($items as $delta => $item) {
-      if (isset($link_file)) {
-        $uri = array(
-          'path' => file_create_url($item->entity->getFileUri()),
-          'options' => array(),
-        );
+      $item = $item->getValue(TRUE);
+      if (!isset($item['uri']) && isset($item['entity'])) {
+        $item['uri'] = $item['entity']->getFileUri();
+      }
+      if (isset($item['title']) && drupal_strlen($item['title']) == 0) {
+        unset($item['title']);
       }
-      $elements[$delta] = array(
-        '#theme' => 'responsive_image_formatter',
+      $picture = array(
+        '#theme' => 'responsive_image',
+        '#style_name' => $fallback_image_style,
+        '#uri' => $item['uri'],
+        '#width' => $item['width'],
+        '#height' => $item['height'],
+        '#breakpoints' => $breakpoint_styles,
+        '#alt' => isset($item['alt']) || array_key_exists('alt', $item) ? $item['alt'] : NULL,
+        '#title' => isset($item['title']) ? $item['title'] : NULL,
+        '#attributes' => isset($item['attributes']) ? $item['attributes'] : NULL,
         '#attached' => array('library' => array(
           'core/picturefill',
         )),
-        '#item' => $item,
-        '#image_style' => $fallback_image_style,
-        '#breakpoints' => $breakpoint_styles,
-        '#path' => isset($uri) ? $uri : '',
       );
+
+      // Check if the formatter involves a link.
+      if ($this->getSetting('image_link') == 'content') {
+        $uri = $items->getEntity()->urlInfo();
+      }
+      elseif ($this->getSetting('image_link') == 'file') {
+        $uri = array(
+          'path' => file_create_url($item['uri']),
+          'options' => array(),
+        );
+      }
+       if (isset($uri)) {
+        $elements[$delta] = array(
+          '#type' => 'link',
+          '#href' => $uri['path'],
+          '#title' => $picture,
+          '#options' => array(
+            'html' => TRUE,
+            'attributes' => isset($uri['attributes']) ? $uri['attributes'] : array(),
+          ),
+        );
+      }
+      else {
+        $elements[$delta] = $picture;
+      }
     }
 
     return $elements;
diff --git a/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageFieldDisplayTest.php
index c643774..f31768e 100644
--- a/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageFieldDisplayTest.php
+++ b/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageFieldDisplayTest.php
@@ -199,14 +199,13 @@ public function _testResponsiveImageFieldFormatters($scheme) {
     );
     $default_output = '<noscript>' . drupal_render($image_style) . '</noscript>';
     $this->drupalGet('node/' . $nid);
-    $this->assertRaw($default_output, 'Image style thumbnail formatter displaying correctly on full node view.');
+    $this->assertRaw($default_output, 'Fallback image style (large) printed correctly on full node view.');
 
     if ($scheme == 'private') {
       // Log out and try to access the file.
       $this->drupalLogout();
       $this->drupalGet($large_style->buildUrl($image_uri));
-      $this->assertResponse('403', 'Access denied to image style thumbnail as anonymous user.');
+      $this->assertResponse('403', 'Access denied to image style (large) as anonymous user.');
     }
   }
-
 }
diff --git a/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageThemeFunctionsTest.php b/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageThemeFunctionsTest.php
new file mode 100644
index 0000000..7b17827
--- /dev/null
+++ b/core/modules/responsive_image/lib/Drupal/responsive_image/Tests/ResponsiveImageThemeFunctionsTest.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\responsive_image\Tests\ResponsiveImageThemeFunctionsTest.
+ */
+
+namespace Drupal\responsive_image\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests responsive image theme functions.
+ */
+class ResponsiveImageThemeFunctionsTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('responsive_image');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Responsive Image theme functions',
+      'description' => 'Tests the responsive_image theme functions.',
+      'group' => 'Responsive Image',
+    );
+  }
+
+  /**
+   * Tests usage of the template_preprocess_responsive_image() function.
+   */
+  function testResponsiveImageTheme() {
+    // Create an image.
+    $files = $this->drupalGetTestFiles('image');
+    $file = reset($files);
+    $original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME);
+
+    // Create a style.
+    $style = entity_create('image_style', array('name' => 'test', 'label' => 'Test'));
+    $style->save();
+    $url = $style->buildUrl($original_uri);
+
+    // Test using template_preprocess_responsive_image() with a NULL value for the alt option.
+    $path = $this->randomName();
+    $element = array(
+      '#theme' => 'responsive_image',
+      '#uri' => $original_uri,
+      '#alt' => NULL,
+      '#style_name' => 'test',
+      '#breakpoints' => 'test',
+    );
+    $rendered_element = render($element);
+    $this->assertTrue(strpos($rendered_element, '<picture>') !== FALSE, 'picture element in template_preprocess_responsive_image() renders correctly.');
+
+    // Test using template_preprocess_responsive_image() without passing a value for the alt option.
+    unset($element['#alt']);
+    $rendered_element = render($element);
+    $expected_result = '<img class="image-style-test" src="' . $url . '" alt="" />';
+    $this->assertTrue(strpos($rendered_element, $expected_result) !== FALSE, 'img element in template_preprocess_responsive_image() correctly renders the alt option.');
+  }
+
+}
diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module
index 5113afc..98c9096 100644
--- a/core/modules/responsive_image/responsive_image.module
+++ b/core/modules/responsive_image/responsive_image.module
@@ -117,96 +117,30 @@ function responsive_image_theme() {
         'attributes' => array(),
         'breakpoints' => array(),
       ),
+      'template' => 'responsive-image',
     ),
-    'responsive_image_formatter' => array(
-      'variables' => array(
-        'item' => NULL,
-        'path' => NULL,
-        'image_style' => NULL,
-        'breakpoints' => array(),
-      ),
-    ),
-    'responsive_image_source' => array(
-      'variables' => array(
-        'src' => NULL,
-        'srcset' => NULL,
-        'dimensions' => NULL,
-        'media' => NULL,
-      ),
-    ),
-  );
-}
-
-/**
- * Returns HTML for a responsive image field formatter.
- *
- * @param array $variables
- *   An associative array containing:
- *   - item: An ImageItem object.
- *   - image_style: An optional image style.
- *   - path: An optional array containing the link 'path' and link 'options'.
- *   - breakpoints: An array containing breakpoints.
- *
- * @ingroup themeable
- */
-function theme_responsive_image_formatter($variables) {
-  $item = $variables['item'];
-  if (!isset($variables['breakpoints']) || empty($variables['breakpoints'])) {
-    $image_formatter = array(
-      '#theme' => 'image_formatter',
-      '#item' => $item,
-      '#image_style' => $variables['image_style'],
-      '#path' => $variables['path'],
-    );
-    return drupal_render($image_formatter);
-  }
-
-  $responsive_image = array(
-    '#theme' => 'responsive_image',
-    '#width' => $item->width,
-    '#height' => $item->height,
-    '#style_name' => $variables['image_style'],
-    '#breakpoints' => $variables['breakpoints'],
   );
-  if (isset($item->uri)) {
-    $responsive_image['#uri'] = $item->uri;
-  }
-  elseif ($entity = $item->entity) {
-    $responsive_image['#uri'] = $entity->getFileUri();
-    $responsive_image['#entity'] = $entity;
-  }
-  $responsive_image['#alt'] = $item->alt;
-  if (drupal_strlen($item->title) != 0) {
-    $responsive_image['#title'] = $item->title;
-  }
-  // @todo Add support for route names.
-  if (isset($variables['path']['path'])) {
-    $path = $variables['path']['path'];
-    $options = isset($variables['path']['options']) ? $variables['path']['options'] : array();
-    $options['html'] = TRUE;
-    return l($responsive_image, $path, $options);
-  }
-
-  return drupal_render($responsive_image);
 }
 
 /**
- * Returns HTML for a responsive image.
+ * Prepares variables for picture templates.
+ * Returns HTML for a picture.
+ * Default template: picture.html.twig.
  *
  * @param $variables
  *   An associative array containing:
- *   - uri: Either the path of the image file (relative to base_path()) or a
+ *   - path: Either the path of the image file (relative to base_path()) or a
  *     full URL.
  *   - width: The width of the image (if known).
  *   - height: The height of the image (if known).
+ *   - breakpoints: An array containing breakpoints.
  *   - alt: The alternative text for text-based browsers.
  *   - title: The title text is displayed when the image is hovered in some
  *     popular browsers.
- *   - breakpoints: An array containing breakpoints.
- *
- * @ingroup themeable
+ *   - attributes: Attributes for the picture element.
+ *   - style_name: The name of the style to be used to alter the original image.
  */
-function theme_responsive_image($variables) {
+function template_preprocess_responsive_image(&$variables) {
   // Make sure that width and height are proper values
   // If they exists we'll output them
   // @see http://www.w3.org/community/respimg/2012/06/18/florians-compromise/
@@ -219,118 +153,91 @@ function theme_responsive_image($variables) {
     unset($variables['height']);
   }
 
-  $sources = array();
-  $output = array();
+  $variables['sources'] = array();
 
-  // Fallback image, output as source with media query.
-  $sources[] = array(
-    'src' => entity_load('image_style', $variables['style_name'])->buildUrl($variables['uri']),
-    'dimensions' => responsive_image_get_image_dimensions($variables),
-  );
+  // Prepare picture tag attributes.
+  $attributes = array();
+  if (isset($variables['title'])) {
+    $attributes['title'] = $variables['title'];
+  }
+  $variables['attributes'] = new Attribute($attributes);
 
-  // All breakpoints and multipliers.
-  foreach ($variables['breakpoints'] as $breakpoint_name => $multipliers) {
-    $breakpoint = breakpoint_load($breakpoint_name);
-    if ($breakpoint) {
-      $new_sources = array();
-      foreach ($multipliers as $multiplier => $image_style) {
-        $new_source = $variables;
-        $new_source['style_name'] = $image_style;
-        $new_source['#multiplier'] = $multiplier;
-        $new_sources[] = $new_source;
-      }
+  if (!empty($variables['breakpoints'])) {
+    // Fallback image, output as source with media query.
+    $src_attributes = responsive_image_get_image_dimensions($variables);
+    $src_attributes['src'] = entity_load('image_style', $variables['style_name'])->buildUrl($variables['uri']);
+    $variables['sources'][] = array(
+      'attributes' => new Attribute($src_attributes),
+    );
 
-      // Only one image, use src.
-      if (count($new_sources) == 1) {
-        $sources[] = array(
-          'src' => entity_load('image_style', $new_sources[0]['style_name'])->buildUrl($new_sources[0]['uri']),
-          'dimensions' => responsive_image_get_image_dimensions($new_sources[0]),
-          'media' => $breakpoint->mediaQuery,
-        );
-      }
-      else {
-        // Multiple images, use srcset.
-        $srcset = array();
-        foreach ($new_sources as $new_source) {
-          $srcset[] = entity_load('image_style', $new_source['style_name'])->buildUrl($new_source['uri']) . ' ' . $new_source['#multiplier'];
+    // All breakpoints and multipliers.
+    foreach ($variables['breakpoints'] as $breakpoint_name => $multipliers) {
+      $breakpoint = breakpoint_load($breakpoint_name);
+      if ($breakpoint) {
+        $new_sources = array();
+        foreach ($multipliers as $multiplier => $image_style) {
+          $new_source = $variables;
+          $new_source['style_name'] = $image_style;
+          $new_source['#multiplier'] = $multiplier;
+          $new_sources[] = $new_source;
         }
-        $sources[] = array(
-          'srcset' => implode(', ', $srcset),
-          'dimensions' => responsive_image_get_image_dimensions($new_sources[0]),
-          'media' => $breakpoint->mediaQuery,
-        );
-      }
-    }
-  }
 
-  if (!empty($sources)) {
-    $output[] = '<picture>';
+        // Only one image, use src. @todo
+        if (count($new_sources) == 1) {
+          // Setup source attributes.
+          $src_attributes = array(
+            'media' => $breakpoint->mediaQuery,
+            'src' => entity_load('image_style', $new_sources[0]['style_name'])->buildUrl($new_sources[0]['uri']),
+          );
+        }
+        else {
+          // Multiple images, use srcset.
+          $srcset = array();
+          foreach ($new_sources as $new_source) {
+            $srcset[] = entity_load('image_style', $new_source['style_name'])->buildUrl($new_source['uri']) . ' ' . $new_source['#multiplier'];
+          }
 
-    // Add source tags to the output.
-    foreach ($sources as $source) {
-      $responsive_image_source = array(
-        '#theme' => 'responsive_image_source',
-        '#src' => $source['src'],
-        '#dimensions' => $source['dimensions'],
-      );
-      if (isset($source['media'])) {
-        $responsive_image_source['#media'] = $source['media'];
-      }
-      if (isset($source['srcset'])) {
-        $responsive_image_source['#srcset'] = $source['srcset'];
+          // Setup source attributes.
+          $src_attributes = array(
+            'media' => $breakpoint->mediaQuery,
+            'srcset' => implode(', ', $srcset),
+          );
+
+          // Add src if we have one.
+          if (!empty($variables['src'])) {
+            $src_attributes['src'] = $variables['src'];
+            unset($variables['src']);
+          }
+        }
+        $src_attributes += responsive_image_get_image_dimensions($new_sources[0]);
+        $variables['sources'][] = array(
+          'attributes' => new Attribute($src_attributes),
+        );
       }
-      $output[] = drupal_render($responsive_image_source);
     }
-
-    // Output the fallback image.
-    $image_style = array(
+    // Prepare noscript fallback image.
+    $variables['fallback'] = array(
       '#theme' => 'image_style',
-      '#style_name' => $variables['style_name'],
+      '#uri' => $variables['uri'],
       '#width' => $variables['width'],
       '#height' => $variables['height'],
+      '#style_name' => $variables['style_name'],
+      '#alt' => isset($variables['alt']) || array_key_exists('alt', $variables) ? $variables['alt'] : NULL,
+      '#title' => isset($variables['title']) ? $variables['title'] : NULL,
     );
-    foreach (array('uri', 'alt', 'title', 'attributes') as $key) {
-      if (isset($variables[$key])) {
-        $image_style["#$key"] = $variables[$key];
-      }
-    }
-    $output[] = '  <noscript>' . drupal_render($image_style) . '</noscript>';
-    $output[] = '</picture>';
-    return implode("\n", $output);
-  }
-}
-
-/**
- * Returns HTML for a source tag.
- *
- * @param type $variables
- *   An associative array containing:
- *   - media: The media query to use.
- *   - srcset: The srcset containing the the path of the image file or a full
- *     URL and optionally multipliers.
- *   - src: Either the path of the image file (relative to base_path()) or a
- *     full URL.
- *   - dimensions: The width and height of the image (if known).
- *
- * @ingroup themeable
- */
-function theme_responsive_image_source($variables) {
-  $output = array();
-  if (isset($variables['media']) && !empty($variables['media'])) {
-    if (!isset($variables['srcset'])) {
-      $output[] = '<!-- <source media="' . $variables['media'] . '" src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
-      $output[] = '<source media="' . $variables['media'] . '" src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . '/>';
-    }
-    elseif (!isset($variables['src'])) {
-      $output[] = '<!-- <source media="' . $variables['media'] . '" srcset="' . $variables['srcset'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
-      $output[] = '<source media="' . $variables['media'] . '" srcset="' . $variables['srcset'] . '" ' . new Attribute($variables['dimensions']) . ' />';
-    }
   }
   else {
-    $output[] = '<!-- <source src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
-    $output[] = '<source src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . '/>';
+    // No matching picture mapping or style name.
+    $variables['fallback'] = array(
+      '#theme' => 'image',
+      '#uri' => $variables['uri'],
+      '#width' => $variables['width'],
+      '#height' => $variables['height'],
+      '#alt' => isset($variables['alt']) || array_key_exists('alt', $variables) ? $variables['alt'] : NULL,
+      '#title' => isset($variables['title']) ? $variables['title'] : NULL,
+    );
   }
-  return implode("\n", $output);
+
 }
 
 /**
@@ -353,7 +260,9 @@ function responsive_image_get_image_dimensions($variables) {
     'height' => $variables['height'],
   );
 
-  entity_load('image_style', $variables['style_name'])->transformDimensions($dimensions);
+  if ($image_style = entity_load('image_style', $variables['style_name'])) {
+    $image_style->transformDimensions($dimensions);
+  }
 
   return $dimensions;
 }
diff --git a/core/modules/responsive_image/templates/responsive-image.html.twig b/core/modules/responsive_image/templates/responsive-image.html.twig
new file mode 100644
index 0000000..89b5f24
--- /dev/null
+++ b/core/modules/responsive_image/templates/responsive-image.html.twig
@@ -0,0 +1,26 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a picture tag.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the picture tag.
+ * - sources: A set of source data.
+ *   - source.attributes: HTML attributes for each source tag.
+ * - fallback: Fallback image tag for non-JS browsers.
+ *
+ * @see template_preprocess_responsive_image()
+ *
+ * @ingroup themeable
+ */
+#}
+{% if sources %}
+  <picture{{ attributes }}>
+    {% for source in sources %}
+      <source{{ source.attributes }} />
+    {% endfor %}
+    <noscript>{{ fallback }}</noscript>
+  </picture>
+{% else %}
+  {{- fallback -}}
+{% endif %}
