diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc
index df68065..2148206 100644
--- a/core/modules/image/image.admin.inc
+++ b/core/modules/image/image.admin.inc
@@ -438,6 +438,32 @@ function image_effect_scale_validate($element, &$form_state) {
 }
 
 /**
+ * Form element validation handler for a specified percent value.
+ */
+function image_effect_percent_validate($element, &$form_state) {
+  if ($element['#value'] !== '') {
+    if (!is_numeric($element['#value']) || $element['#value'] < 1 || $element['#value'] > 100) {
+      form_error($element, t('!name must be a number between 1 and 100.', array('!name' => $element['#title'])));
+    }
+  }
+}
+
+/**
+ * Form element validation handler to ensure the filter options are selected correctly.
+ */
+function image_effect_png_filter_validate($element, &$form_state) {
+  if (isset($element['#value'])) {
+    $count = count($element['#value']);
+    foreach ($element['#value'] as $value) {
+      // 'None' or 'All' can only be selected on its own, never with others
+      if (($value == 8 || $value ==248) && $count > 1) {
+        form_error($element, t("The options @none or @all can only be applied on its own", array('@none' => 'None', '@all' => 'All')));
+      }
+    }
+  }
+}
+
+/**
  * Form structure for the image resize form.
  *
  * Note that this is not a complete form, it only contains the portion of the
@@ -569,6 +595,80 @@ function image_rotate_form($data) {
 }
 
 /**
+ * Form structure for the image quality effect form.
+ *
+ * Note that this is not a complete form, it only contains the portion of the
+ * form for configuring the quality options. Therefore it does not not need to
+ * include metadata about the effect, nor a submit button.
+ *
+ * @param $data
+ *   The current configuration for this quality effect.
+ */
+function image_quality_form($data) {
+  // Obtain the option values from the serialized constant
+  $image_quality_options = _image_quality_png_settings();
+
+  $form['quality'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Quality'),
+    '#default_value' => isset($data['quality']) ? $data['quality'] : 75,
+    '#field_suffix' => t('%'),
+    '#required' => TRUE,
+    '#element_validate' => array('image_effect_percent_validate'),
+    '#size' => 10,
+    '#maxlength' => 3,
+    '#description' => t('Define the image quality for JPEG manipulations. Ranges from 1 to 100. Higher values mean better image quality but bigger files.'),
+  );
+  $form['png'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('PNG'),
+    '#description' => t('This setting will only apply for images in PNG format.'),
+    '#collapsible' => FALSE
+  );
+
+  $form['png']['filter'] = array(
+    '#type' => 'select',
+    '#title' => t('Filter'),
+    '#default_value' => isset($data['png']['filter']) ? $data['png']['filter'] : 8,
+    '#required' => TRUE,
+    '#options' => $image_quality_options['filter'],
+    '#multiple' => TRUE,
+    '#element_validate' => array('image_effect_png_filter_validate'),
+    '#description' => t("Select the filter(s) to be applied. More than one filter can be selected, but 'None' or 'All' can only be selected on its own."),
+    '#size' => 6
+  );
+
+  $form['png']['compression'] = array(
+    '#type' => 'select',
+    '#title' => t('Compression'),
+    '#default_value' => isset($data['png']['compression']) ? $data['png']['compression'] : 4,
+    '#required' => TRUE,
+    '#options' => $image_quality_options['compression'],
+    '#multiple' => FALSE,
+    '#description' => t('Define the image quality for PNG manipulations. Ranges from 0 - 9. Higher value means smaller file size but may incur slow processing.'),
+  );
+  return $form;
+}
+
+/**
+ * An internal function that returns configuration options for PNG filter and
+ * compression rate.
+ */
+function _image_quality_png_settings() {
+  return array(
+    'filter' => array(
+      8   => t('None'),
+      16  => t('Sub'),
+      32  => t('Up'),
+      64  => t('Average'),
+      128 => t('Perth'),
+      248 => t('All')
+    ),
+    'compression' => array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+  );
+}
+
+/**
  * Returns HTML for the page containing the list of image styles.
  *
  * @param $variables
@@ -834,3 +934,39 @@ 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']));
 }
+
+/**
+ * Returns HTML for a summary of an image quality effect.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - data: The current configuration for this quality effect.
+ *
+ * @ingroup themeable
+ */
+function theme_image_quality_summary($variables) {
+  $data = $variables['data'];
+
+  // JPEG quality
+  $summary = t('JPEG: !quality%; ', array('!quality' => $data['quality']));
+
+  // Get the key-value pairs for PNG quality, then display the labels
+  $image_quality_options = _image_quality_png_settings();
+
+  if (!empty($data['png']['compression'])) {
+    $summary .= t('PNG compression: ') . $image_quality_options['compression'][$data['png']['compression']] . '; ';
+  }
+
+  // Create a list of applied PNG filters
+  $filter_values = '';
+  foreach ($data['png']['filter'] as $filter) {
+    if (isset($image_quality_options['filter'][$filter])) {
+      $filter_values[] = $image_quality_options['filter'][$filter];
+    }
+  }
+  if (!empty($filter_values)) {
+    $summary .= format_plural(count($filter_values), 'Applied PNG filter: @filter', 'Applied PNG filters: @filter', $args = array('@filter' => implode(', ', $filter_values)));
+  }
+
+  return check_plain($summary);
+}
diff --git a/core/modules/image/image.effects.inc b/core/modules/image/image.effects.inc
index a56a429..a14bb3c 100644
--- a/core/modules/image/image.effects.inc
+++ b/core/modules/image/image.effects.inc
@@ -56,6 +56,14 @@ function image_image_effect_info() {
       'form callback' => 'image_rotate_form',
       'summary theme' => 'image_rotate_summary',
     ),
+    'image_quality' => array(
+      'label' => t('Quality'),
+      'help' => t('Decreasing the quality will make image file sizes smaller.'),
+      'effect callback' => 'image_quality_effect',
+      'dimensions passthrough' => TRUE,
+      'form callback' => 'image_quality_form',
+      'summary theme' => 'image_quality_summary',
+    ),
   );
 
   return $effects;
@@ -312,3 +320,28 @@ function image_rotate_dimensions(array &$dimensions, array $data) {
     $dimensions['width'] = $dimensions['height'] = NULL;
   }
 }
+
+/**
+ * Image effect callback; Change the quality for a target image.
+ *
+ * @param $image
+ *   An image object returned by image_load().
+ * @param $data
+ *   An associative array containing the effect configuration values:
+ *   - quality (JPEG): An integer representing the desired image quality in percent.
+ *   - compression (PNG): An integer representing the desired image quality in compression rate.
+ *   - filter (PNG): An array of integer values representing the desired PNG filters to be applied.
+ *
+ * @return
+ *   Always TRUE. The quality is set as a property on the $image, so the image
+ *   toolkit's save callback may apply it when the image is written to disk.
+ *
+ * @see image_save()
+ * @see image_gd_save()
+ */
+function image_quality_effect(&$image, $data) {
+  $image->quality = $data['quality'];
+  $image->filter = $data['png']['filter'];
+  $image->png_compression = $data['png']['compression'];
+  return TRUE;
+}
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 91a3d23..0f662b0 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -234,6 +234,9 @@ function image_theme() {
     'image_rotate_summary' => array(
       'variables' => array('data' => NULL),
     ),
+    'image_quality_summary' => array(
+      'variables' => array('data' => NULL),
+    ),
 
     // Theme functions in image.field.inc.
     'image_widget' => array(
diff --git a/core/modules/system/image.gd.inc b/core/modules/system/image.gd.inc
index 0d2f228..40e52d8 100644
--- a/core/modules/system/image.gd.inc
+++ b/core/modules/system/image.gd.inc
@@ -304,7 +304,13 @@ function image_gd_save(stdClass $image, $destination) {
       imagealphablending($image->resource, FALSE);
       imagesavealpha($image->resource, TRUE);
     }
-    $success = $function($image->resource, $destination);
+    // Set the png filter value. Convert the png filter settings into a bitmask.
+    // Defaults to 8 (NONE)
+    $png_filter = isset($image->filter) ? array_sum($image->filter) : 8;
+
+    // Set the compression rate. Defaults to 4
+    $png_compression = empty($image->png_compression) ? 4 : $image->png_compression;
+    $success = $function($image->resource, $destination, $png_compression, $png_filter);
   }
   // Move temporary local file to remote destination.
   if (isset($permanent_destination) && $success) {
diff --git a/core/modules/system/tests/modules/image_test/image_test.module b/core/modules/system/tests/modules/image_test/image_test.module
index fb6b720..610c2c6 100644
--- a/core/modules/system/tests/modules/image_test/image_test.module
+++ b/core/modules/system/tests/modules/image_test/image_test.module
@@ -36,6 +36,7 @@ function image_test_reset() {
     'rotate' => array(),
     'crop' => array(),
     'desaturate' => array(),
+    'quality' => array(),
   );
   state()->set('image_test.results', $results);
 }
