diff --git a/config/schema/drimage.schema.yml b/config/schema/drimage.schema.yml index 0e523b2..fcd9688 100644 --- a/config/schema/drimage.schema.yml +++ b/config/schema/drimage.schema.yml @@ -17,6 +17,9 @@ drimage.settings: lazy_offset: type: integer label: 'Lazyloader offeset' + enable_modern_image_format: + type: text + label: 'Enable modern image format' fallback_style: type: text label: 'Fallback Image Style' diff --git a/css/drimage.css b/css/drimage.css index aefb56b..0a6e3e8 100644 --- a/css/drimage.css +++ b/css/drimage.css @@ -15,13 +15,17 @@ .drimage.is-background-image { background-repeat: no-repeat; } - -.drimage.is-background-image > img, -.drimage.is-background-image > a > img { +.drimage.is-background-image > picture, +.drimage.is-background-image > a > picture { clip: rect(1px, 1px, 1px, 1px); overflow: hidden; height: 1px; width: 1px; word-wrap: normal; margin-top: -1px; + display: block; +} + +.drimage.is-background-image { + background-image: var(--drimage-img); } diff --git a/drimage.install b/drimage.install index 0a9724a..d2c547b 100644 --- a/drimage.install +++ b/drimage.install @@ -19,6 +19,7 @@ function drimage_install() { // Assuming a HD resolution width 2x multiplier as the max. ->set('downscale', 3840) ->set('multiplier', 1) + ->set('enable_modern_image_format', '') ->set('lazy_offset', 100) ->save(); } @@ -31,3 +32,12 @@ function drimage_update_8006(&$sandbox) { ->set('proxy_cache_maximum_age', 0) ->save(); } + +/** + * Set false as default value for enable_webp_support. + */ +function drimage_update_8007(&$sandbox) { + \Drupal::configFactory()->getEditable('drimage.settings') + ->set('enable_modern_image_format', '') + ->save(); +} diff --git a/drimage.module b/drimage.module index e91bccf..3084fd5 100644 --- a/drimage.module +++ b/drimage.module @@ -27,6 +27,7 @@ function drimage_theme() { 'item' => NULL, 'item_attributes' => NULL, 'image_style' => NULL, + 'modern_image_format' => NULL, 'url' => NULL, 'alt' => NULL, 'width' => NULL, diff --git a/drimage.routing.yml b/drimage.routing.yml index f23fa6e..7ef4dc4 100644 --- a/drimage.routing.yml +++ b/drimage.routing.yml @@ -1,5 +1,5 @@ drimage.image: - path: '/drimage/{width}/{height}/{fid}/{iwc_id}' + path: '/drimage/{width_height}/{fid}/{iwc_id}/{filename}' defaults: _controller: '\Drupal\drimage\Controller\DrImageController::image' requirements: diff --git a/js/drimage.js b/js/drimage.js index c9c4b1a..d566794 100644 --- a/js/drimage.js +++ b/js/drimage.js @@ -166,24 +166,30 @@ el.classList.add('is-loading'); el.setAttribute('data-w', size[0]); el.setAttribute('data-h', size[1]); - var downloadingImage = new Image(); - downloadingImage.onload = function () { - var img = el.querySelectorAll('img'); - if (img.length > 0) { - img[0].src = this.src; - } - if (data.image_handling === 'background') { - el.style.backgroundImage = 'url("' + this.src + '")'; - } - }; - // Extra parameter with a "-" value as default to indicate we do // not want to use image_widget_crop integration by default. var iwc = '-'; if (data.image_handling === 'iwc') { iwc = data.iwc.image_style; } - downloadingImage.src = data.subdir + '/drimage/' + size[0] + '/' + size[1] + '/' + data.fid + '/' + iwc + data.original_source; + var imgUrl = data.subdir + '/drimage/' + size[0] + 'x' + size[1] + '/' + data.fid + '/' + iwc + '/' + data.filename; + + if (data.image_handling === 'background') { + if (data.modern_image_format !== null) { + el.style.backgroundImage = 'url("' + imgUrl + '.' + data.modern_image_format + '")'; + el.style.setProperty('--drimage-img', 'url("' + imgUrl + '")'); + } else { + el.style.backgroundImage = 'url("' + imgUrl + '")'; + } + } else { + if (data.modern_image_format !== null) { + var source = el.querySelector('source'); + source.setAttribute('srcset', imgUrl + '.' + data.modern_image_format); + } + var img = el.querySelector('img'); + img.src = imgUrl; + } + setTimeout(function() { el.classList.remove('is-loading'); }, 1000); @@ -196,8 +202,9 @@ Drupal.behaviors.drimage = { attach: function (context) { - Drupal.drimage.init(context); var timer; + clearTimeout(timer); + timer = setTimeout(Drupal.drimage.init, 1, context); addEventListener('resize', function () { clearTimeout(timer); timer = setTimeout(Drupal.drimage.init, 100); diff --git a/js/drimage.min.js b/js/drimage.min.js index 827ba51..5d4c45d 100644 --- a/js/drimage.min.js +++ b/js/drimage.min.js @@ -1 +1 @@ -!function e(t,i,r){function a(o,d){if(!i[o]){if(!t[o]){var l="function"==typeof require&&require;if(!d&&l)return l(o,!0);if(n)return n(o,!0);var l=new Error("Cannot find module '"+o+"'");throw l.code="MODULE_NOT_FOUND",l}var l=i[o]={exports:{}};t[o][0].call(l.exports,function(e){return a(t[o][1][e]||e)},l,l.exports,e,t,i,r)}return i[o].exports}for(var n="function"==typeof require&&require,o=0;o=r[0]/d&&(u=l)),r=(r=t.drimage.resize(r,u,0))[0]>i.downscale?t.drimage.resize(r,i.downscale,0):r},t.drimage.init=function(i){var r=(i=void 0===i?e:i).querySelectorAll(".drimage");if(00){var n=parseInt(r[0].getAttribute("width")),d=parseInt(r[0].getAttribute("height"))/n*i[0];d1){var g=Math.floor(s/a.threshold)*a.threshold+a.upscale;g>=i[0]/o&&(l=g)}return(i=t.drimage.resize(i,l,0))[0]>a.downscale&&(i=t.drimage.resize(i,a.downscale,0)),i},t.drimage.init=function(a){void 0===a&&(a=e);var i=a.querySelectorAll(".drimage");if(i.length>0)for(var r=0;r0){var o=parseInt(d[0].getAttribute("width"))/n.aspect_ratio.width*n.aspect_ratio.height;d[0].setAttribute("height",o)}}t.drimage.renderEl(i[r]),"background"===n.image_handling&&(i[r].classList.contains("is-background-image")||(i[r].style.backgroundAttachment=n.background.attachment,i[r].style.backgroundPosition=n.background.position,i[r].style.backgroundSize=n.background.size,i[r].classList.add("is-background-image")))}},t.drimage.renderEl=function(a){if(null===t.drimage.findDelayParent(a)){var i=a.getBoundingClientRect(),r=t.drimage.fetchData(a);if((i.top+r.lazy_offset>=0&&i.top-r.lazy_offset<=(window.innerHeight||e.documentElement.clientHeight)||i.bottom+r.lazy_offset>=0&&i.bottom-r.lazy_offset<=(window.innerHeight||e.documentElement.clientHeight))&&!1===isNaN(r.fid)&&r.fid%1==0&&Number(r.fid)>0){var n=t.drimage.size(a),d=Number(a.getAttribute("data-w")),o=Number(a.getAttribute("data-h"));if((n[0]!==d||n[1]!==o)&&n[0]>0){a.classList.add("is-loading"),a.setAttribute("data-w",n[0]),a.setAttribute("data-h",n[1]);var s="-";"iwc"===r.image_handling&&(s=r.iwc.image_style);var l=r.subdir+"/drimage/"+n[0]+"x"+n[1]+"/"+r.fid+"/"+s+"/"+r.filename;if("background"===r.image_handling)null!==r.modern_image_format?(a.style.backgroundImage='url("'+l+"."+r.modern_image_format+'")',a.style.setProperty("--drimage-img",'url("'+l+'")')):a.style.backgroundImage='url("'+l+'")';else{if(null!==r.modern_image_format)a.querySelector("source").setAttribute("srcset",l+"."+r.modern_image_format);a.querySelector("img").src=l}setTimeout(function(){a.classList.remove("is-loading")},1e3)}}}},t.behaviors.drimage={attach:function(a){var i;clearTimeout(i),i=setTimeout(t.drimage.init,1,a),addEventListener("resize",function(){clearTimeout(i),i=setTimeout(t.drimage.init,100)}),addEventListener("scroll",function(){t.drimage.init(e)})}}}(document,Drupal); \ No newline at end of file diff --git a/src/Controller/DrImageController.php b/src/Controller/DrImageController.php index 2e82a35..1acc35b 100644 --- a/src/Controller/DrImageController.php +++ b/src/Controller/DrImageController.php @@ -3,9 +3,11 @@ namespace Drupal\drimage\Controller; use Drupal\Core\Entity\EntityStorageException; +use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\file\Entity\File; use Drupal\image\Controller\ImageStyleDownloadController; use Drupal\image\Entity\ImageStyle; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -273,10 +275,8 @@ class DrImageController extends ImageStyleDownloadController { * * @param \Symfony\Component\HttpFoundation\Request $request * The request object. - * @param int $width - * The requested width in pixels that came from the JS. - * @param int $height - * The requested height in pixels that came from the JS. + * @param string $width_height + * The requested width and height in pixels seperated by x that came from the JS. * @param int $fid * The file id to render. * @param string|NULL $iwc_id @@ -290,8 +290,9 @@ class DrImageController extends ImageStyleDownloadController { * @throws \Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException * Thrown when the file is still being generated. */ - public function image(Request $request, $width, $height, $fid, $iwc_id = NULL) { + public function image(Request $request, $width_height, $fid, $iwc_id = NULL, $filename = NULL) { // Bail out if the image is not valid. + list($width, $height) = explode('x', $width_height); $file = File::load($fid); $image = $this->imageFactory->get($file->getFileUri()); if (!$image->isValid()) { @@ -339,6 +340,11 @@ class DrImageController extends ImageStyleDownloadController { // Variable translation to make the original imageStyle deliver method work. $image_uri = explode('://', $file->getFileUri()); $scheme = $image_uri[0]; + $filename_path_info = pathinfo($filename); + $modern_image_format = NULL; + if (isset($filename_path_info['extension']) && $filename_path_info['extension'] === 'webp' || $filename_path_info['extension'] === 'avif') { + $modern_image_format = $filename_path_info['extension']; + } $request->query->set('file', $image_uri[1]); // Use the fallback image style if something went wrong. @@ -361,6 +367,29 @@ class DrImageController extends ImageStyleDownloadController { //usleep(1000000); $response = $this->deliver($request, $scheme, $image_style); + if ($modern_image_format !== NULL) { + $modern_image_uri = $image_uri . '.' . $modern_image_format; + $modern_image_derivative_uri = $image_style->buildUri($modern_image_uri); + // If parent:deliver() returns BinaryFileResponse, we'll replace + // the BinaryFileResponse with one containing the .webp image + // so long as it exists. + if ($response instanceof BinaryFileResponse) { + if (file_exists($modern_image_derivative_uri)) { + $image = $this->imageFactory->get($modern_image_derivative_uri); + $uri = $image->getSource(); + $headers = [ + 'Content-Type' => 'image/' . $modern_image_format, + 'Content-Length' => $image->getFileSize(), + ]; + $response = new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private'); + } + else { + // If the derivative does not exist, return a failed reponse. + $response = new Response($this->t('Error generating image.'), 500); + } + } + } + $drimage_config = $this->config('drimage.settings'); $proxy_cache_maximum_age = $drimage_config->get('proxy_cache_maximum_age'); if (!empty($proxy_cache_maximum_age)) { diff --git a/src/Form/DrimageSettingsForm.php b/src/Form/DrimageSettingsForm.php index df4b566..f4476c0 100644 --- a/src/Form/DrimageSettingsForm.php +++ b/src/Form/DrimageSettingsForm.php @@ -138,6 +138,21 @@ class DrimageSettingsForm extends ConfigFormBase { '#description' => $this->t('Will produce higher quality images on screens that have more physical pixels then logical pixels.'), ]; + $module_handler = \Drupal::service('module_handler'); + + $form['enable_modern_image_format'] = [ + '#type' => 'select', + '#title' => $this->t('Enable modern image format'), + '#default_value' => $this->config('drimage.settings')->get('enable_modern_image_format'), + '#description' => $this->t('This options is only selectable if imageapi_optimize_webp module is installed. After enabling create a pipeline, add WebP or AVIF Deriver and set this pipeline as "Sitewide default pipeline". After that flush the image cache. (drush if)'), + '#disabled' => !$module_handler->moduleExists('imageapi_optimize_webp'), + '#options' => [ + '' => $this->t('None'), + 'webp' => 'WebP', + 'avif' => 'AVIF', + ] + ]; + $form['lazy_offset'] = [ '#type' => 'number', '#title' => $this->t('Lazyloader offeset'), @@ -202,6 +217,7 @@ class DrimageSettingsForm extends ConfigFormBase { ->set('upscale', $form_state->getValue('upscale')) ->set('downscale', $form_state->getValue('downscale')) ->set('multiplier', $form_state->getValue('multiplier')) + ->set('enable_modern_image_format', $form_state->getValue('enable_modern_image_format')) ->set('lazy_offset', $form_state->getValue('lazy_offset')) ->set('fallback_style', $form_state->getValue('fallback_style')) ->set('proxy_cache_maximum_age', $form_state->getValue('proxy_cache_maximum_age')) diff --git a/src/Plugin/Field/FieldFormatter/DrImageFormatter.php b/src/Plugin/Field/FieldFormatter/DrImageFormatter.php index 8a46d71..411b249 100644 --- a/src/Plugin/Field/FieldFormatter/DrImageFormatter.php +++ b/src/Plugin/Field/FieldFormatter/DrImageFormatter.php @@ -44,20 +44,20 @@ class DrImageFormatter extends ImageFormatter { */ public static function defaultSettings() { return [ - 'image_handling' => 'scale', - 'aspect_ratio' => [ - 'width' => 1, - 'height' => 1, - ], - 'background' => [ - 'attachment' => 'scroll', - 'position' => 'center center', - 'size' => 'cover', - ], - 'iwc' => [ - 'image_style' => NULL, - ], - ] + parent::defaultSettings(); + 'image_handling' => 'scale', + 'aspect_ratio' => [ + 'width' => 1, + 'height' => 1, + ], + 'background' => [ + 'attachment' => 'scroll', + 'position' => 'center center', + 'size' => 'cover', + ], + 'iwc' => [ + 'image_style' => NULL, + ], + ] + parent::defaultSettings(); } /** @@ -237,11 +237,12 @@ class DrImageFormatter extends ImageFormatter { $config = \Drupal::configFactory()->get('drimage.settings'); + $url = Url::fromUri('internal:/')->toString(); if (substr($url, -1) === '/') { $url = substr($url, 0, -1); } - + $modern_image_format = !empty($config->get('enable_modern_image_format')) ? $config->get('enable_modern_image_format') : NULL; foreach ($elements as $delta => $element) { $elements[$delta]['#item_attributes'] = new Attribute(); $elements[$delta]['#item_attributes']['class'] = ['drimage']; @@ -256,6 +257,7 @@ class DrImageFormatter extends ImageFormatter { 'upscale' => $config->get('upscale'), 'downscale' => $config->get('downscale'), 'multiplier' => $config->get('multiplier'), + 'modern_image_format' => $modern_image_format, 'lazy_offset' => $config->get('lazy_offset'), 'subdir' => $url, ]; @@ -264,6 +266,8 @@ class DrImageFormatter extends ImageFormatter { // implementing lightbox-style plugins that show the original image. $elements[$delta]['#width'] = $element['#item']->getValue()['width']; $elements[$delta]['#height'] = $element['#item']->getValue()['height']; + $elements[$delta]['#modern_image_format'] = $modern_image_format; + $elements[$delta]['#alt'] = $element['#item']->getValue()['alt']; $elements[$delta]['#data']['original_width'] = $element['#item']->getValue()['width']; $elements[$delta]['#data']['original_height'] = $element['#item']->getValue()['height']; @@ -301,6 +305,11 @@ class DrImageFormatter extends ImageFormatter { break; } + // Media embed preview workaround + if (\Drupal::routeMatch()->getRouteName() == 'media.filter.preview') { + $elements[$delta]['#data']['media_preview'] = TRUE; + } + // Unset the fallback image. unset($elements[$delta]['#image']); } diff --git a/src/Plugin/Field/FieldFormatter/DrImageUriFormatter.php b/src/Plugin/Field/FieldFormatter/DrImageUriFormatter.php index 1ffa403..ba2f63d 100644 --- a/src/Plugin/Field/FieldFormatter/DrImageUriFormatter.php +++ b/src/Plugin/Field/FieldFormatter/DrImageUriFormatter.php @@ -47,6 +47,7 @@ class DrImageUriFormatter extends DrImageFormatter { 'upscale' => $config->get('upscale'), 'downscale' => $config->get('downscale'), 'multiplier' => $config->get('multiplier'), + 'enable_webp_support' => $config->get('enable_webp_support'), 'lazy_offset' => $config->get('lazy_offset'), 'subdir' => $url, ]; diff --git a/templates/drimage-formatter.html.twig b/templates/drimage-formatter.html.twig index f8fefc0..37b8f61 100644 --- a/templates/drimage-formatter.html.twig +++ b/templates/drimage-formatter.html.twig @@ -21,12 +21,22 @@
{% if url %} {% spaceless %} - - {{ alt }} - + + + {% if modern_image_format is not empty %} + + {% endif %} + {{ alt }} + + {% endspaceless %} {% else %} - {{ alt }} + + {% if modern_image_format is not empty %} + + {% endif %} + {{ alt }} + {% endif %}