diff --git a/core/modules/picture/picture.module b/core/modules/picture/picture.module
index 9dc9a6f..cdcc653 100644
--- a/core/modules/picture/picture.module
+++ b/core/modules/picture/picture.module
@@ -163,6 +163,7 @@ function picture_theme() {
         'attributes' => array(),
         'breakpoints' => array(),
       ),
+      'template' => 'picture',
     ),
     'picture_formatter' => array(
       'variables' => array(
@@ -171,20 +172,24 @@ function picture_theme() {
         'image_style' => NULL,
         'breakpoints' => array(),
       ),
+      'template' => 'picture-formatter',
     ),
     'picture_source' => array(
       'variables' => array(
         'src' => NULL,
         'srcset' => NULL,
-        'dimension' => NULL,
+        'dimensions' => array(),
         'media' => NULL,
       ),
+      'template' => 'picture-source',
     ),
   );
 }
 
 /**
- * Returns HTML for a picture field formatter.
+ * Prepares variables for picture formatter templates.
+ *
+ * Default template: picture-formatter.html.twig.
  *
  * @param array $variables
  *   An associative array containing:
@@ -192,53 +197,59 @@ function picture_theme() {
  *   - 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_picture_formatter($variables) {
+function template_preprocess_picture_formatter(&$variables) {
   if (!isset($variables['breakpoints']) || empty($variables['breakpoints'])) {
-    return theme('image_formatter', $variables);
-  }
-
-  $item = $variables['item'];
-
-  // Do not output an empty 'title' attribute.
-  if (isset($item['title']) && drupal_strlen($item['title']) == 0) {
-    unset($item['title']);
+    $variables['image'] = array(
+      '#theme' => 'image_formatter',
+      '#item' => $variables['item'],
+      '#image_style' => $variables['image_style'],
+      '#path' => $variables['path'],
+    );
   }
+  else {
+    $item = $variables['item'];
 
-  $item['style_name'] = $variables['image_style'];
-  $item['breakpoints'] = $variables['breakpoints'];
+    // Do not output an empty 'title' attribute.
+    if (isset($item['title']) && drupal_strlen($item['title']) == 0) {
+      unset($item['title']);
+    }
 
-  if (!isset($item['path']) && isset($variables['uri'])) {
-    $item['path'] = $variables['uri'];
-  }
-  $output = theme('picture', $item);
+    $variables['image'] = array(
+      '#theme' => 'picture',
+      '#style_name' => $variables['image_style'],
+      '#path' => $item['uri'],
+      '#width' => $item['width'],
+      '#height' => $item['height'],
+      '#breakpoints' => $variables['breakpoints'],
+      '#alt' => isset($item['alt']) ? $item['alt'] : NULL,
+      '#title' => isset($item['title']) ? $item['title'] : NULL,
+      '#attributes' => isset($item['attributes']) ? $item['attributes'] : NULL,
+    );
 
-  if (isset($variables['path']['path'])) {
-    $path = $variables['path']['path'];
-    $options = isset($variables['path']['options']) ? $variables['path']['options'] : array();
-    $options['html'] = TRUE;
-    $output = l($output, $path, $options);
+    if (isset($variables['path']['path'])) {
+      $variables['path']['attributes'] = isset($variables['path']['options']['attributes']) ? new Attribute($variables['path']['options']['attributes']) : new Attribute();
+      $options = isset($variables['path']['options']) ? $variables['path']['options'] : array();
+      $variables['path']['path'] = url($variables['path']['path'], $options);
+    }
   }
-  return $output;
 }
 
 /**
- * Returns HTML for a picture.
+ * Prepares variables for picture templates.
+ *
+ * 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).
  *   - alt: The alternative text for text-based browsers.
  *   - breakpoints: An array containing breakpoints.
- *
- * @ingroup themeable
  */
-function theme_picture($variables) {
+function template_preprocess_picture(&$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/
@@ -251,13 +262,13 @@ function theme_picture($variables) {
     unset($variables['height']);
   }
 
-  $sources = array();
-  $output = array();
+  $variables['sources'] = array();
 
   // Fallback image, output as source with media query.
-  $sources[] = array(
-    'src' => image_style_url($variables['style_name'], $variables['uri']),
-    'dimensions' => picture_get_image_dimensions($variables),
+  $variables['sources'][] = array(
+    '#theme' => 'picture_source',
+    '#src' => image_style_url($variables['style_name'], $variables['path']),
+    '#dimensions' => picture_get_image_dimensions($variables),
   );
 
   // All breakpoints and multipliers.
@@ -274,50 +285,54 @@ function theme_picture($variables) {
 
       // Only one image, use src.
       if (count($new_sources) == 1) {
-        $sources[] = array(
-          'src' => image_style_url($new_sources[0]['style_name'], $new_sources[0]['uri']),
-          'dimensions' => picture_get_image_dimensions($new_sources[0]),
-          'media' => $breakpoint->mediaQuery,
+        $variables['sources'][] = array(
+          '#theme' => 'picture_source',
+          '#src' => image_style_url($new_sources[0]['style_name'], $new_sources[0]['path']),
+          '#dimensions' => picture_get_image_dimensions($new_sources[0]),
+          '#media' => $breakpoint->mediaQuery,
         );
       }
       else {
-        // Mutliple images, use srcset.
+        // Multiple images, use srcset.
         $srcset = array();
         foreach ($new_sources as $new_source) {
-          $srcset[] = image_style_url($new_source['style_name'], $new_source['uri']) . ' ' . $new_source['#multiplier'];
+          $srcset[] = image_style_url($new_source['style_name'], $new_source['path']) . ' ' . $new_source['#multiplier'];
         }
-        $sources[] = array(
-          'srcset' => implode(', ', $srcset),
-          'dimensions' => picture_get_image_dimensions($new_sources[0]),
-          'media' => $breakpoint->mediaQuery,
+        $variables['sources'][] = array(
+          '#theme' => 'picture_source',
+          '#srcset' => implode(', ', $srcset),
+          '#dimensions' => picture_get_image_dimensions($new_sources[0]),
+          '#media' => $breakpoint->mediaQuery,
         );
       }
     }
   }
 
-  if (!empty($sources)) {
-    $attributes = array();
-    foreach (array('alt', 'title') as $key) {
-      if (isset($variables[$key])) {
-        $attributes[$key] = $variables[$key];
-      }
-    }
-    $output[] = '<picture' . new Attribute($attributes) . '>';
-
-    // Add source tags to the output.
-    foreach ($sources as $source) {
-      $output[] = theme('picture_source', $source);
+  // Prepare picture tag attributes.
+  $attributes = array();
+  foreach (array('alt', 'title') as $key) {
+    if (isset($variables[$key])) {
+      $attributes[$key] = $variables[$key];
     }
-
-    // Output the fallback image.
-    $output[] = '<noscript>' . theme('image_style', $variables) . '</noscript>';
-    $output[] = '</picture>';
-    return implode("\n", $output);
   }
+  $variables['attributes'] = new Attribute($attributes);
+
+  // Prepare noscript fallback image.
+  $variables['fallback'] = array(
+    '#theme' => 'image_style',
+    '#uri' => $variables['path'],
+    '#width' => $variables['width'],
+    '#height' => $variables['height'],
+    '#style_name' => $variables['style_name'],
+    '#alt' => isset($variables['alt']) ? $variables['alt'] : NULL,
+    '#title' => isset($variables['title']) ? $variables['title'] : NULL,
+  );
 }
 
 /**
- * Returns HTML for a source tag.
+ * Prepares variables for source tag templates.
+ *
+ * Default template: picture-source.html.twig.
  *
  * @param type $variables
  *   An associative array containing:
@@ -326,27 +341,24 @@ function theme_picture($variables) {
  *     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
+ *   - dimensions: An array containing the width and height of the image (if
+ *     known).
  */
-function theme_picture_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']) . ' />';
+function template_preprocess_picture_source(&$variables) {
+  $attributes = array();
+  foreach (array('media', 'src', 'srcset') as $attribute) {
+    if (!empty($variables[$attribute])) {
+      $attributes[$attribute] = $variables[$attribute];
+      unset($variables[$attribute]);
     }
   }
-  else {
-    $output[] = '<!-- <source src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
-    $output[] = '<source src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . '/>';
-  }
-  return implode("\n", $output);
+  $attributes += $variables['dimensions'];
+  unset($variables['dimensions']);
+
+  $variables['attributes'] = new Attribute($attributes);
+  // Create a duplicate attributes variable to allow printing the attributes
+  // twice.
+  $variables['attributes_copy'] = new Attribute($attributes);
 }
 
 /**
diff --git a/core/modules/picture/templates/picture-formatter.html.twig b/core/modules/picture/templates/picture-formatter.html.twig
new file mode 100644
index 0000000..bf7810d
--- /dev/null
+++ b/core/modules/picture/templates/picture-formatter.html.twig
@@ -0,0 +1,23 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a picture field formatter.
+ *
+ * Available variables:
+ * - image: Picture element, if picture has breakpoint sources, otherwise img
+ *   element.
+ * - path: (optional) Contains information about the link.
+ *   - path.path: The URL the image links to.
+ *   - path.attributes: HTML attributes for the link tag.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_picture_formatter()
+ *
+ * @ingroup themeable
+ */
+#}
+{% if path %}
+  <a href="{{ path.path }}"{{ path.attributes }}>{{ image }}</a>
+{% else %}
+  {{ image }}
+{% endif %}
diff --git a/core/modules/picture/templates/picture-source.html.twig b/core/modules/picture/templates/picture-source.html.twig
new file mode 100644
index 0000000..970d240
--- /dev/null
+++ b/core/modules/picture/templates/picture-source.html.twig
@@ -0,0 +1,38 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a picture source tag.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the source tag, including:
+ *   - media: The media query to use.
+ *   - width: The width of the image (if known).
+ *   - height: The width of the image (if known).
+ *   One of the following two variables will be available:
+ *   - src: Either the path of the image file (relative to base_path()) or a
+ *     full URL.
+ *   - srcset: A group of image sources/resolutions used to display different
+ *     image resolutions at different breakpoints.
+ * - attributes_copy: A copy of the HTML attributes in the 'attributes' variable
+ *   to allow for printing the attributes a second time for the commented source
+ *   tags. Drupal's Attribute object keeps track of which attributes have been
+ *   printed and will only print an attribute once.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_picture_source()
+ *
+ * @ingroup themeable
+ */
+#}
+{% if attributes.media %}
+  {% if attributes.src %}
+  <!-- <source{{ attributes_copy }} /> -->
+    <source{{ attributes }} />
+  {% elseif attributes.srcset %}
+    <!-- <source{{ attributes_copy }} /> -->
+    <source{{ attributes }} />
+  {% endif %}
+{% else %}
+  <!-- <source{{ attributes_copy }} /> -->
+  <source{{ attributes }} />
+{% endif %}
diff --git a/core/modules/picture/templates/picture.html.twig b/core/modules/picture/templates/picture.html.twig
new file mode 100644
index 0000000..c8fff2f
--- /dev/null
+++ b/core/modules/picture/templates/picture.html.twig
@@ -0,0 +1,24 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a picture tag.
+ *
+ * Available variables:
+ * - sources: Source elements.
+ * - attributes: HTML attributes for the picture tag.
+ * - fallback: Fallback image tag for non-JS browsers.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_picture()
+ *
+ * @ingroup themeable
+ */
+#}
+{% if sources %}
+  <picture{{ attributes }}>
+    {% for source in sources %}
+      {{ source }}
+    {% endfor %}
+    <noscript>{{ fallback }}</noscript>
+  </picture>
+{% endif %}
