diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e1cc194 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +# WordPress Coding Standards +# https://make.wordpress.org/core/handbook/coding-standards/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = tab + +[{.jshintrc,*.json,*.yml}] +indent_style = space +indent_size = 2 + +[{*.txt,wp-config-sample.php}] +end_of_line = crlf diff --git a/class-respimg.php b/class-respimg.php new file mode 100644 index 0000000..120fc52 --- /dev/null +++ b/class-respimg.php @@ -0,0 +1,245 @@ + + * + * @package wp-respimg + * @version 0.0.1 + */ + + if (class_exists('Imagick')) { + class RespimgImagick extends Imagick { } + } else { + class RespimgImagick { } + } + + + /** + * An Imagick extension to provide better (higher quality, lower file size) image resizes. + * + * This class extends Imagick () based on + * research into optimal image resizing techniques (). + * + * Using these methods with their default settings should provide image resizing that is + * visually indistinguishable from Photoshop’s “Save for Web…”, but at lower file sizes. + * + * @author David Newton + * @copyright 2015 David Newton + * @license https://raw.githubusercontent.com/nwtn/php-respimg/master/LICENSE MIT + * @version 1.0.0 + */ + + class Respimg extends RespimgImagick { + + /** + * Resizes the image using smart defaults for high quality and low file size. + * + * This function is basically equivalent to: + * + * $optim == true: `mogrify -path OUTPUT_PATH -filter Triangle -define filter:support=2.0 -thumbnail OUTPUT_WIDTH -unsharp 0.25x0.08+8.3+0.045 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB INPUT_PATH` + * + * $optim == false: `mogrify -path OUTPUT_PATH -filter Triangle -define filter:support=2.0 -thumbnail OUTPUT_WIDTH -unsharp 0.25x0.25+8+0.065 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB -strip INPUT_PATH` + * + * @access public + * + * @param integer $columns The number of columns in the output image. 0 = maintain aspect ratio based on $rows. + * @param integer $rows The number of rows in the output image. 0 = maintain aspect ratio based on $columns. + * @param bool $optim Whether you intend to perform optimization on the resulting image. Note that setting this to `true` doesn’t actually perform any optimization. + */ + + public function smartResize($columns, $rows, $optim = false) { + + $this->setOption('filter:support', '2.0'); + $this->thumbnailImage($columns, $rows, false, false, Imagick::FILTER_TRIANGLE); + if ($optim) { + $this->unsharpMaskImage(0.25, 0.08, 8.3, 0.045); + } else { + $this->unsharpMaskImage(0.25, 0.25, 8, 0.065); + } + $this->posterizeImage(136, false); + $this->setImageCompressionQuality(82); + $this->setOption('jpeg:fancy-upsampling', 'off'); + $this->setOption('png:compression-filter', '5'); + $this->setOption('png:compression-level', '9'); + $this->setOption('png:compression-strategy', '1'); + $this->setOption('png:exclude-chunk', 'all'); + $this->setInterlaceScheme(Imagick::INTERLACE_NO); + $this->setColorspace(Imagick::COLORSPACE_SRGB); + if (!$optim) { + $this->stripImage(); + } + + } + + + /** + * Changes the size of an image to the given dimensions and removes any associated profiles. + * + * `thumbnailImage` changes the size of an image to the given dimensions and + * removes any associated profiles. The goal is to produce small low cost + * thumbnail images suited for display on the Web. + * + * With the original Imagick thumbnailImage implementation, there is no way to choose a + * resampling filter. This class recreates Imagick’s C implementation and adds this + * additional feature. + * + * Note: has been filed for this issue. + * + * @access public + * + * @param integer $columns The number of columns in the output image. 0 = maintain aspect ratio based on $rows. + * @param integer $rows The number of rows in the output image. 0 = maintain aspect ratio based on $columns. + * @param bool $bestfit Treat $columns and $rows as a bounding box in which to fit the image. + * @param bool $fill Fill in the bounding box with the background colour. + * @param integer $filter The resampling filter to use. Refer to the list of filter constants at . + * + * @return bool Indicates whether the operation was performed successfully. + */ + + public function thumbnailImage($columns, $rows, $bestfit = false, $fill = false, $filter = Imagick::FILTER_TRIANGLE) { + + // sample factor; defined in original ImageMagick thumbnailImage function + // the scale to which the image should be resized using the `sample` function + $SampleFactor = 5; + + // filter whitelist + $filters = array( + Imagick::FILTER_POINT, + Imagick::FILTER_BOX, + Imagick::FILTER_TRIANGLE, + Imagick::FILTER_HERMITE, + Imagick::FILTER_HANNING, + Imagick::FILTER_HAMMING, + Imagick::FILTER_BLACKMAN, + Imagick::FILTER_GAUSSIAN, + Imagick::FILTER_QUADRATIC, + Imagick::FILTER_CUBIC, + Imagick::FILTER_CATROM, + Imagick::FILTER_MITCHELL, + Imagick::FILTER_LANCZOS, + Imagick::FILTER_BESSEL, + Imagick::FILTER_SINC + ); + + // Parse parameters given to function + $columns = (double) ($columns); + $rows = (double) ($rows); + $bestfit = (bool) $bestfit; + $fill = (bool) $fill; + + // We can’t resize to (0,0) + if ($rows < 1 && $columns < 1) { + return false; + } + + // Set a default filter if an acceptable one wasn’t passed + if (!in_array($filter, $filters)) { + $filter = Imagick::FILTER_TRIANGLE; + } + + // figure out the output width and height + $width = (double) $this->getImageWidth(); + $height = (double) $this->getImageHeight(); + $new_width = $columns; + $new_height = $rows; + + $x_factor = $columns / $width; + $y_factor = $rows / $height; + if ($rows < 1) { + $new_height = round($x_factor * $height); + } elseif ($columns < 1) { + $new_width = round($y_factor * $width); + } + + // if bestfit is true, the new_width/new_height of the image will be different than + // the columns/rows parameters; those will define a bounding box in which the image will be fit + if ($bestfit && $x_factor > $y_factor) { + $x_factor = $y_factor; + $new_width = round($y_factor * $width); + } elseif ($bestfit && $y_factor > $x_factor) { + $y_factor = $x_factor; + $new_height = round($x_factor * $height); + } + if ($new_width < 1) { + $new_width = 1; + } + if ($new_height < 1) { + $new_height = 1; + } + + // if we’re resizing the image to more than about 1/3 it’s original size + // then just use the resize function + if (($x_factor * $y_factor) > 0.1) { + $this->resizeImage($new_width, $new_height, $filter, 1); + + // if we’d be using sample to scale to smaller than 128x128, just use resize + } elseif ((($SampleFactor * $new_width) < 128) || (($SampleFactor * $new_height) < 128)) { + $this->resizeImage($new_width, $new_height, $filter, 1); + + // otherwise, use sample first, then resize + } else { + $this->sampleImage($SampleFactor * $new_width, $SampleFactor * $new_height); + $this->resizeImage($new_width, $new_height, $filter, 1); + } + + // if the alpha channel is not defined, make it opaque + if ($this->getImageAlphaChannel() == Imagick::ALPHACHANNEL_UNDEFINED) { + $this->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE); + } + + // set the image’s bit depth to 8 bits + $this->setImageDepth(8); + + // turn off interlacing + $this->setInterlaceScheme(Imagick::INTERLACE_NO); + + // Strip all profiles except color profiles. + foreach ($this->getImageProfiles('*', true) as $key => $value) { + if ($key != 'icc' && $key != 'icm') { + $this->removeImageProfile($key); + } + } + + if (method_exists($this, 'deleteImageProperty')) { + $this->deleteImageProperty('comment'); + $this->deleteImageProperty('Thumb::URI'); + $this->deleteImageProperty('Thumb::MTime'); + $this->deleteImageProperty('Thumb::Size'); + $this->deleteImageProperty('Thumb::Mimetype'); + $this->deleteImageProperty('software'); + $this->deleteImageProperty('Thumb::Image::Width'); + $this->deleteImageProperty('Thumb::Image::Height'); + $this->deleteImageProperty('Thumb::Document::Pages'); + } else { + $this->setImageProperty('comment', ''); + $this->setImageProperty('Thumb::URI', ''); + $this->setImageProperty('Thumb::MTime', ''); + $this->setImageProperty('Thumb::Size', ''); + $this->setImageProperty('Thumb::Mimetype', ''); + $this->setImageProperty('software', ''); + $this->setImageProperty('Thumb::Image::Width', ''); + $this->setImageProperty('Thumb::Image::Height', ''); + $this->setImageProperty('Thumb::Document::Pages', ''); + } + + // In case user wants to fill use extent for it rather than creating a new canvas + // …fill out the bounding box + if ($bestfit && $fill && ($new_width != $columns || $new_height != $rows)) { + $extent_x = 0; + $extent_y = 0; + + if ($columns > $new_width) { + $extent_x = ($columns - $new_width) / 2; + } + if ($rows > $new_height) { + $extent_y = ($rows - $new_height) / 2; + } + + $this->extentImage($columns, $rows, 0 - $extent_x, $extent_y); + } + + return true; + + } + + } diff --git a/class-wp-image-editor-respimg.php b/class-wp-image-editor-respimg.php new file mode 100644 index 0000000..5683984 --- /dev/null +++ b/class-wp-image-editor-respimg.php @@ -0,0 +1,218 @@ +file into new Respimg Object. + * + * @access protected + * + * @return boolean|WP_Error True if loaded; WP_Error on failure. + */ + public function load() { + if ( $this->image instanceof Respimg ) + return true; + + if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) + return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file ); + + /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */ + // Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits + @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) ); + + try { + $this->image = new Respimg( $this->file ); + + if( ! $this->image->valid() ) + return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file); + + // Select the first frame to handle animated images properly + if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) ) + $this->image->setIteratorIndex(0); + + $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() ); + } + catch ( Exception $e ) { + return new WP_Error( 'invalid_image', $e->getMessage(), $this->file ); + } + + $updated_size = $this->update_size(); + if ( is_wp_error( $updated_size ) ) { + return $updated_size; + } + + return $this->set_quality(); + } + + /** + * Resizes current image. + * + * At minimum, either a height or width must be provided. + * If one of the two is set to null, the resize will + * maintain aspect ratio according to the provided dimension. + * + * @access public + * + * @param int|null $max_w Image width. + * @param int|null $max_h Image height. + * @param boolean $crop + * @return boolean|WP_Error + */ + public function resize( $max_w, $max_h, $crop = false ) { + if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) + return true; + + $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); + if ( ! $dims ) + return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') ); + list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; + + if ( $crop ) { + return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h ); + } + + try { + if ($this->mime_type === 'image/gif') { + $this->image->scaleImage( $dst_w, $dst_h ); + } else { + $this->image->smartResize( $dst_w, $dst_h, false ); + } + } + catch ( Exception $e ) { + return new WP_Error( 'image_resize_error', $e->getMessage() ); + } + + return $this->update_size( $dst_w, $dst_h ); + } + + /** + * Resize multiple images from a single source. + * + * @access public + * + * @param array $sizes { + * An array of image size arrays. Default sizes are 'small', 'medium', 'large'. + * + * Either a height or width must be provided. + * If one of the two is set to null, the resize will + * maintain aspect ratio according to the provided dimension. + * + * @type array $size { + * @type int ['width'] Optional. Image width. + * @type int ['height'] Optional. Image height. + * @type bool $crop Optional. Whether to crop the image. Default false. + * } + * } + * @return array An array of resized images' metadata by size. + */ + public function multi_resize( $sizes ) { + $metadata = array(); + $orig_size = $this->size; + $orig_image = clone $this->image; + + foreach ( $sizes as $size => $size_data ) { + if ( ! $this->image ) + $this->image = clone $orig_image; + + if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) { + continue; + } + + if ( ! isset( $size_data['width'] ) ) { + $size_data['width'] = null; + } + if ( ! isset( $size_data['height'] ) ) { + $size_data['height'] = null; + } + + if ( ! isset( $size_data['crop'] ) ) { + $size_data['crop'] = false; + } + + $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); + $duplicate = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) ); + + if ( ! is_wp_error( $resize_result ) && ! $duplicate ) { + $resized = $this->_save( $this->image ); + + $this->image->clear(); + $this->image->destroy(); + $this->image = null; + + if ( ! is_wp_error( $resized ) && $resized ) { + unset( $resized['path'] ); + $metadata[$size] = $resized; + } + } + + $this->size = $orig_size; + } + + $this->image = $orig_image; + + return $metadata; + } + + /** + * Crops Image. + * + * @access public + * + * @param int $src_x The start x position to crop from. + * @param int $src_y The start y position to crop from. + * @param int $src_w The width to crop. + * @param int $src_h The height to crop. + * @param int $dst_w Optional. The destination width. + * @param int $dst_h Optional. The destination height. + * @param boolean $src_abs Optional. If the source crop points are absolute. + * @return boolean|WP_Error + */ + public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) { + if ( $src_abs ) { + $src_w -= $src_x; + $src_h -= $src_y; + } + + try { + $this->image->cropImage( $src_w, $src_h, $src_x, $src_y ); + $this->image->setImagePage( $src_w, $src_h, 0, 0); + + if ( $dst_w || $dst_h ) { + // If destination width/height isn't specified, use same as + // width/height from source. + if ( ! $dst_w ) + $dst_w = $src_w; + if ( ! $dst_h ) + $dst_h = $src_h; + + if ($this->mime_type === 'image/gif') { + $this->image->scaleImage( $dst_w, $dst_h ); + } else { + $this->image->smartResize( $dst_w, $dst_h, false ); + } + return $this->update_size(); + } + } + catch ( Exception $e ) { + return new WP_Error( 'image_crop_error', $e->getMessage() ); + } + return $this->update_size(); + } + + } diff --git a/js/picturefill.min.js b/js/picturefill.min.js index f05ff18..26b1d36 100755 --- a/js/picturefill.min.js +++ b/js/picturefill.min.js @@ -1,4 +1,4 @@ -/*! Picturefill - v2.3.0 - 2015-03-23 +/*! Picturefill - v2.3.1 - 2015-04-09 * http://scottjehl.github.io/picturefill * Copyright (c) 2015 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT */ -window.matchMedia||(window.matchMedia=function(){"use strict";var a=window.styleMedia||window.media;if(!a){var b=document.createElement("style"),c=document.getElementsByTagName("script")[0],d=null;b.type="text/css",b.id="matchmediajs-test",c.parentNode.insertBefore(b,c),d="getComputedStyle"in window&&window.getComputedStyle(b,null)||b.currentStyle,a={matchMedium:function(a){var c="@media "+a+"{ #matchmediajs-test { width: 1px; } }";return b.styleSheet?b.styleSheet.cssText=c:b.textContent=c,"1px"===d.width}}}return function(b){return{matches:a.matchMedium(b||"all"),media:b||"all"}}}()),function(a,b,c){"use strict";function d(b){"object"==typeof module&&"object"==typeof module.exports?module.exports=b:"function"==typeof define&&define.amd&&define("picturefill",function(){return b}),"object"==typeof a&&(a.picturefill=b)}function e(a){var b,c,d,e,f,i=a||{};b=i.elements||g.getAllElements();for(var j=0,k=b.length;k>j;j++)if(c=b[j],d=c.parentNode,e=void 0,f=void 0,"IMG"===c.nodeName.toUpperCase()&&(c[g.ns]||(c[g.ns]={}),i.reevaluate||!c[g.ns].evaluated)){if(d&&"PICTURE"===d.nodeName.toUpperCase()){if(g.removeVideoShim(d),e=g.getMatch(c,d),e===!1)continue}else e=void 0;(d&&"PICTURE"===d.nodeName.toUpperCase()||!g.sizesSupported&&c.srcset&&h.test(c.srcset))&&g.dodgeSrcset(c),e?(f=g.processSourceSet(e),g.applyBestCandidate(f,c)):(f=g.processSourceSet(c),(void 0===c.srcset||c[g.ns].srcset)&&g.applyBestCandidate(f,c)),c[g.ns].evaluated=!0}}function f(){function c(){var b;a._picturefillWorking||(a._picturefillWorking=!0,a.clearTimeout(b),b=a.setTimeout(function(){e({reevaluate:!0}),a._picturefillWorking=!1},60))}g.initTypeDetects(),e();var d=setInterval(function(){return e(),/^loaded|^i|^c/.test(b.readyState)?void clearInterval(d):void 0},250);a.addEventListener?a.addEventListener("resize",c,!1):a.attachEvent&&a.attachEvent("onresize",c)}if(a.HTMLPictureElement)return void d(function(){});b.createElement("picture");var g=a.picturefill||{},h=/\s+\+?\d+(e\d+)?w/;g.ns="picturefill",function(){g.srcsetSupported="srcset"in c,g.sizesSupported="sizes"in c}(),g.trim=function(a){return a.trim?a.trim():a.replace(/^\s+|\s+$/g,"")},g.makeUrl=function(){var a=b.createElement("a");return function(b){return a.href=b,a.href}}(),g.restrictsMixedContent=function(){return"https:"===a.location.protocol},g.matchesMedia=function(b){return a.matchMedia&&a.matchMedia(b).matches},g.getDpr=function(){return a.devicePixelRatio||1},g.getWidthFromLength=function(a){var c;if(!a||a.indexOf("%")>-1!=!1||!(parseFloat(a)>0||a.indexOf("calc(")>-1))return!1;a=a.replace("vw","%"),g.lengthEl||(g.lengthEl=b.createElement("div"),g.lengthEl.style.cssText="border:0;display:block;font-size:1em;left:0;margin:0;padding:0;position:absolute;visibility:hidden",g.lengthEl.className="helper-from-picturefill-js"),g.lengthEl.style.width="0px";try{g.lengthEl.style.width=a}catch(d){}return b.body.appendChild(g.lengthEl),c=g.lengthEl.offsetWidth,0>=c&&(c=!1),b.body.removeChild(g.lengthEl),c},g.detectTypeSupport=function(b,c){var d=new a.Image;return d.onerror=function(){g.types[b]=!1,e()},d.onload=function(){g.types[b]=1===d.width,e()},d.src=c,"pending"},g.types=g.types||{},g.initTypeDetects=function(){g.types["image/jpeg"]=!0,g.types["image/gif"]=!0,g.types["image/png"]=!0,g.types["image/svg+xml"]=b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1"),g.types["image/webp"]=g.detectTypeSupport("image/webp","data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=")},g.verifyTypeSupport=function(a){var b=a.getAttribute("type");if(null===b||""===b)return!0;var c=g.types[b];return"string"==typeof c&&"pending"!==c?(g.types[b]=g.detectTypeSupport(b,c),"pending"):"function"==typeof c?(c(),"pending"):c},g.parseSize=function(a){var b=/(\([^)]+\))?\s*(.+)/g.exec(a);return{media:b&&b[1],length:b&&b[2]}},g.findWidthFromSourceSize=function(c){for(var d,e=g.trim(c).split(/\s*,\s*/),f=0,h=e.length;h>f;f++){var i=e[f],j=g.parseSize(i),k=j.length,l=j.media;if(k&&(!l||g.matchesMedia(l))&&(d=g.getWidthFromLength(k)))break}return d||Math.max(a.innerWidth||0,b.documentElement.clientWidth)},g.parseSrcset=function(a){for(var b=[];""!==a;){a=a.replace(/^\s+/g,"");var c,d=a.search(/\s/g),e=null;if(-1!==d){c=a.slice(0,d);var f=c.slice(-1);if((","===f||""===c)&&(c=c.replace(/,+$/,""),e=""),a=a.slice(d+1),null===e){var g=a.indexOf(",");-1!==g?(e=a.slice(0,g),a=a.slice(g+1)):(e=a,a="")}}else c=a,a="";(c||e)&&b.push({url:c,descriptor:e})}return b},g.parseDescriptor=function(a,b){var c,d=b||"100vw",e=a&&a.replace(/(^\s+|\s+$)/g,""),f=g.findWidthFromSourceSize(d);if(e)for(var h=e.split(" "),i=h.length-1;i>=0;i--){var j=h[i],k=j&&j.slice(j.length-1);if("h"!==k&&"w"!==k||g.sizesSupported){if("x"===k){var l=j&&parseFloat(j,10);c=l&&!isNaN(l)?l:1}}else c=parseFloat(parseInt(j,10)/f)}return c||1},g.getCandidatesFromSourceSet=function(a,b){for(var c=g.parseSrcset(a),d=[],e=0,f=c.length;f>e;e++){var h=c[e];d.push({url:h.url,resolution:g.parseDescriptor(h.descriptor,b)})}return d},g.dodgeSrcset=function(a){a.srcset&&(a[g.ns].srcset=a.srcset,a.srcset="",a.setAttribute("data-pfsrcset",a[g.ns].srcset))},g.processSourceSet=function(a){var b=a.getAttribute("srcset"),c=a.getAttribute("sizes"),d=[];return"IMG"===a.nodeName.toUpperCase()&&a[g.ns]&&a[g.ns].srcset&&(b=a[g.ns].srcset),b&&(d=g.getCandidatesFromSourceSet(b,c)),d},g.backfaceVisibilityFix=function(a){var b=a.style||{},c="webkitBackfaceVisibility"in b,d=b.zoom;c&&(b.zoom=".999",c=a.offsetWidth,b.zoom=d)},g.setIntrinsicSize=function(){var c={},d=function(a,b,c){b&&a.setAttribute("width",parseInt(b/c,10))};return function(e,f){var h;e[g.ns]&&!a.pfStopIntrinsicSize&&(void 0===e[g.ns].dims&&(e[g.ns].dims=e.getAttribute("width")||e.getAttribute("height")),e[g.ns].dims||(f.url in c?d(e,c[f.url],f.resolution):(h=b.createElement("img"),h.onload=function(){if(c[f.url]=h.width,!c[f.url])try{b.body.appendChild(h),c[f.url]=h.width||h.offsetWidth,b.body.removeChild(h)}catch(a){}e.src===f.url&&d(e,c[f.url],f.resolution),e=null,h.onload=null,h=null},h.src=f.url)))}}(),g.applyBestCandidate=function(a,b){var c,d,e;a.sort(g.ascendingSort),d=a.length,e=a[d-1];for(var f=0;d>f;f++)if(c=a[f],c.resolution>=g.getDpr()){e=c;break}e&&(e.url=g.makeUrl(e.url),b.src!==e.url&&(g.restrictsMixedContent()&&"http:"===e.url.substr(0,"http:".length).toLowerCase()?void 0!==window.console&&console.warn("Blocked mixed content image "+e.url):(b.src=e.url,b.currentSrc=b.src,g.backfaceVisibilityFix(b))),g.setIntrinsicSize(b,e))},g.ascendingSort=function(a,b){return a.resolution-b.resolution},g.removeVideoShim=function(a){var b=a.getElementsByTagName("video");if(b.length){for(var c=b[0],d=c.getElementsByTagName("source");d.length;)a.insertBefore(d[0],c);c.parentNode.removeChild(c)}},g.getAllElements=function(){for(var a=[],c=b.getElementsByTagName("img"),d=0,e=c.length;e>d;d++){var f=c[d];("PICTURE"===f.parentNode.nodeName.toUpperCase()||null!==f.getAttribute("srcset")||f[g.ns]&&null!==f[g.ns].srcset)&&a.push(f)}return a},g.getMatch=function(a,b){for(var c,d=b.childNodes,e=0,f=d.length;f>e;e++){var h=d[e];if(1===h.nodeType){if(h===a)return c;if("SOURCE"===h.nodeName.toUpperCase()){null!==h.getAttribute("src")&&void 0!==typeof console&&console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`.");var i=h.getAttribute("media");if(h.getAttribute("srcset")&&(!i||g.matchesMedia(i))){var j=g.verifyTypeSupport(h);if(j===!0){c=h;break}if("pending"===j)return!1}}}}return c},f(),e._=g,d(e)}(window,window.document,new window.Image); \ No newline at end of file +window.matchMedia||(window.matchMedia=function(){"use strict";var a=window.styleMedia||window.media;if(!a){var b=document.createElement("style"),c=document.getElementsByTagName("script")[0],d=null;b.type="text/css",b.id="matchmediajs-test",c.parentNode.insertBefore(b,c),d="getComputedStyle"in window&&window.getComputedStyle(b,null)||b.currentStyle,a={matchMedium:function(a){var c="@media "+a+"{ #matchmediajs-test { width: 1px; } }";return b.styleSheet?b.styleSheet.cssText=c:b.textContent=c,"1px"===d.width}}}return function(b){return{matches:a.matchMedium(b||"all"),media:b||"all"}}}()),function(a,b,c){"use strict";function d(b){"object"==typeof module&&"object"==typeof module.exports?module.exports=b:"function"==typeof define&&define.amd&&define("picturefill",function(){return b}),"object"==typeof a&&(a.picturefill=b)}function e(a){var b,c,d,e,f,i=a||{};b=i.elements||g.getAllElements();for(var j=0,k=b.length;k>j;j++)if(c=b[j],d=c.parentNode,e=void 0,f=void 0,"IMG"===c.nodeName.toUpperCase()&&(c[g.ns]||(c[g.ns]={}),i.reevaluate||!c[g.ns].evaluated)){if(d&&"PICTURE"===d.nodeName.toUpperCase()){if(g.removeVideoShim(d),e=g.getMatch(c,d),e===!1)continue}else e=void 0;(d&&"PICTURE"===d.nodeName.toUpperCase()||!g.sizesSupported&&c.srcset&&h.test(c.srcset))&&g.dodgeSrcset(c),e?(f=g.processSourceSet(e),g.applyBestCandidate(f,c)):(f=g.processSourceSet(c),(void 0===c.srcset||c[g.ns].srcset)&&g.applyBestCandidate(f,c)),c[g.ns].evaluated=!0}}function f(){function c(){clearTimeout(d),d=setTimeout(h,60)}g.initTypeDetects(),e();var d,f=setInterval(function(){return e(),/^loaded|^i|^c/.test(b.readyState)?void clearInterval(f):void 0},250),h=function(){e({reevaluate:!0})};a.addEventListener?a.addEventListener("resize",c,!1):a.attachEvent&&a.attachEvent("onresize",c)}if(a.HTMLPictureElement)return void d(function(){});b.createElement("picture");var g=a.picturefill||{},h=/\s+\+?\d+(e\d+)?w/;g.ns="picturefill",function(){g.srcsetSupported="srcset"in c,g.sizesSupported="sizes"in c,g.curSrcSupported="currentSrc"in c}(),g.trim=function(a){return a.trim?a.trim():a.replace(/^\s+|\s+$/g,"")},g.makeUrl=function(){var a=b.createElement("a");return function(b){return a.href=b,a.href}}(),g.restrictsMixedContent=function(){return"https:"===a.location.protocol},g.matchesMedia=function(b){return a.matchMedia&&a.matchMedia(b).matches},g.getDpr=function(){return a.devicePixelRatio||1},g.getWidthFromLength=function(a){var c;if(!a||a.indexOf("%")>-1!=!1||!(parseFloat(a)>0||a.indexOf("calc(")>-1))return!1;a=a.replace("vw","%"),g.lengthEl||(g.lengthEl=b.createElement("div"),g.lengthEl.style.cssText="border:0;display:block;font-size:1em;left:0;margin:0;padding:0;position:absolute;visibility:hidden",g.lengthEl.className="helper-from-picturefill-js"),g.lengthEl.style.width="0px";try{g.lengthEl.style.width=a}catch(d){}return b.body.appendChild(g.lengthEl),c=g.lengthEl.offsetWidth,0>=c&&(c=!1),b.body.removeChild(g.lengthEl),c},g.detectTypeSupport=function(b,c){var d=new a.Image;return d.onerror=function(){g.types[b]=!1,e()},d.onload=function(){g.types[b]=1===d.width,e()},d.src=c,"pending"},g.types=g.types||{},g.initTypeDetects=function(){g.types["image/jpeg"]=!0,g.types["image/gif"]=!0,g.types["image/png"]=!0,g.types["image/svg+xml"]=b.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1"),g.types["image/webp"]=g.detectTypeSupport("image/webp","data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=")},g.verifyTypeSupport=function(a){var b=a.getAttribute("type");if(null===b||""===b)return!0;var c=g.types[b];return"string"==typeof c&&"pending"!==c?(g.types[b]=g.detectTypeSupport(b,c),"pending"):"function"==typeof c?(c(),"pending"):c},g.parseSize=function(a){var b=/(\([^)]+\))?\s*(.+)/g.exec(a);return{media:b&&b[1],length:b&&b[2]}},g.findWidthFromSourceSize=function(c){for(var d,e=g.trim(c).split(/\s*,\s*/),f=0,h=e.length;h>f;f++){var i=e[f],j=g.parseSize(i),k=j.length,l=j.media;if(k&&(!l||g.matchesMedia(l))&&(d=g.getWidthFromLength(k)))break}return d||Math.max(a.innerWidth||0,b.documentElement.clientWidth)},g.parseSrcset=function(a){for(var b=[];""!==a;){a=a.replace(/^\s+/g,"");var c,d=a.search(/\s/g),e=null;if(-1!==d){c=a.slice(0,d);var f=c.slice(-1);if((","===f||""===c)&&(c=c.replace(/,+$/,""),e=""),a=a.slice(d+1),null===e){var g=a.indexOf(",");-1!==g?(e=a.slice(0,g),a=a.slice(g+1)):(e=a,a="")}}else c=a,a="";(c||e)&&b.push({url:c,descriptor:e})}return b},g.parseDescriptor=function(a,b){var c,d=b||"100vw",e=a&&a.replace(/(^\s+|\s+$)/g,""),f=g.findWidthFromSourceSize(d);if(e)for(var h=e.split(" "),i=h.length-1;i>=0;i--){var j=h[i],k=j&&j.slice(j.length-1);if("h"!==k&&"w"!==k||g.sizesSupported){if("x"===k){var l=j&&parseFloat(j,10);c=l&&!isNaN(l)?l:1}}else c=parseFloat(parseInt(j,10)/f)}return c||1},g.getCandidatesFromSourceSet=function(a,b){for(var c=g.parseSrcset(a),d=[],e=0,f=c.length;f>e;e++){var h=c[e];d.push({url:h.url,resolution:g.parseDescriptor(h.descriptor,b)})}return d},g.dodgeSrcset=function(a){a.srcset&&(a[g.ns].srcset=a.srcset,a.srcset="",a.setAttribute("data-pfsrcset",a[g.ns].srcset))},g.processSourceSet=function(a){var b=a.getAttribute("srcset"),c=a.getAttribute("sizes"),d=[];return"IMG"===a.nodeName.toUpperCase()&&a[g.ns]&&a[g.ns].srcset&&(b=a[g.ns].srcset),b&&(d=g.getCandidatesFromSourceSet(b,c)),d},g.backfaceVisibilityFix=function(a){var b=a.style||{},c="webkitBackfaceVisibility"in b,d=b.zoom;c&&(b.zoom=".999",c=a.offsetWidth,b.zoom=d)},g.setIntrinsicSize=function(){var c={},d=function(a,b,c){b&&a.setAttribute("width",parseInt(b/c,10))};return function(e,f){var h;e[g.ns]&&!a.pfStopIntrinsicSize&&(void 0===e[g.ns].dims&&(e[g.ns].dims=e.getAttribute("width")||e.getAttribute("height")),e[g.ns].dims||(f.url in c?d(e,c[f.url],f.resolution):(h=b.createElement("img"),h.onload=function(){if(c[f.url]=h.width,!c[f.url])try{b.body.appendChild(h),c[f.url]=h.width||h.offsetWidth,b.body.removeChild(h)}catch(a){}e.src===f.url&&d(e,c[f.url],f.resolution),e=null,h.onload=null,h=null},h.src=f.url)))}}(),g.applyBestCandidate=function(a,b){var c,d,e;a.sort(g.ascendingSort),d=a.length,e=a[d-1];for(var f=0;d>f;f++)if(c=a[f],c.resolution>=g.getDpr()){e=c;break}e&&(e.url=g.makeUrl(e.url),b.src!==e.url&&(g.restrictsMixedContent()&&"http:"===e.url.substr(0,"http:".length).toLowerCase()?void 0!==window.console&&console.warn("Blocked mixed content image "+e.url):(b.src=e.url,g.curSrcSupported||(b.currentSrc=b.src),g.backfaceVisibilityFix(b))),g.setIntrinsicSize(b,e))},g.ascendingSort=function(a,b){return a.resolution-b.resolution},g.removeVideoShim=function(a){var b=a.getElementsByTagName("video");if(b.length){for(var c=b[0],d=c.getElementsByTagName("source");d.length;)a.insertBefore(d[0],c);c.parentNode.removeChild(c)}},g.getAllElements=function(){for(var a=[],c=b.getElementsByTagName("img"),d=0,e=c.length;e>d;d++){var f=c[d];("PICTURE"===f.parentNode.nodeName.toUpperCase()||null!==f.getAttribute("srcset")||f[g.ns]&&null!==f[g.ns].srcset)&&a.push(f)}return a},g.getMatch=function(a,b){for(var c,d=b.childNodes,e=0,f=d.length;f>e;e++){var h=d[e];if(1===h.nodeType){if(h===a)return c;if("SOURCE"===h.nodeName.toUpperCase()){null!==h.getAttribute("src")&&void 0!==typeof console&&console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`.");var i=h.getAttribute("media");if(h.getAttribute("srcset")&&(!i||g.matchesMedia(i))){var j=g.verifyTypeSupport(h);if(j===!0){c=h;break}if("pending"===j)return!1}}}}return c},f(),e._=g,d(e)}(window,window.document,new window.Image); diff --git a/js/wp-tevko-responsive-images.js b/js/wp-tevko-responsive-images.js index de9ff1f..77ccc7a 100644 --- a/js/wp-tevko-responsive-images.js +++ b/js/wp-tevko-responsive-images.js @@ -2,48 +2,52 @@ (function() { - /** - * Recalculate srcset attribute after an image-update event - */ - if ( wp.media ) { - wp.media.events.on( 'editor:image-update', function( args ) { - // arguments[0] = { Editor, image, metadata } - var image = args.image, - metadata = args.metadata, - srcsetGroup = [], - srcset = '', - sizes = ''; - - // if the image url has changed, recalculate srcset attributes - if ( metadata && metadata.url !== metadata.originalUrl ) { - // we need to get the postdata for the image because - // the sizes array isn't passed into the editor - var imagePostData = new wp.media.model.PostImage( metadata ), - crops = imagePostData.attachment.attributes.sizes; - - // grab all the sizes that match our target ratio and add them to our srcset array - _.each(crops, function(size){ - var softHeight = Math.round( size.width * metadata.height / metadata.width ); - - // If the height is within 1 integer of the expected height, let it pass. - if ( size.height >= softHeight - 1 && size.height <= softHeight + 1 ) { - srcsetGroup.push(size.url + ' ' + size.width + 'w'); - } - }); - - // convert the srcsetGroup array to our srcset value - srcset = srcsetGroup.join(', '); - sizes = '(max-width: ' + metadata.width + 'px) 100vw, ' + metadata.width + 'px'; - - // update the srcset attribute of our image - image.setAttribute( 'srcset', srcset ); - - // update the sizes attribute of our image - image.setAttribute( 'data-sizes', sizes ); - } - - }); - } + /** + * Recalculate srcset attribute after an image-update event + */ + if ( wp.media ) { + wp.media.events.on( 'editor:image-update', function( args ) { + // arguments[0] = { Editor, image, metadata } + var image = args.image, + metadata = args.metadata; + + // If the image url has changed, recalculate srcset attributes. + if ( metadata && metadata.url !== metadata.originalUrl ) { + // Update the srcset attribute. + updateSrcset( image, metadata ); + // Update the sizes attribute. + updateSizes( image, metadata ); + } + + }); + } + + /** + * Update the srcet attribute on an image in the editor + */ + var updateSrcset = function( image, metadata ) { + + var data = { + action: 'tevkori_ajax_srcset', + postID: metadata.attachment_id, + size: metadata.size + }; + + jQuery.post( ajaxurl, data, function( response ) { + image.setAttribute( 'srcset', response ); + }); + }; + + /** + * Update the data-sizes attribute on an image in the editor + */ + var updateSizes = function( image, metadata ) { + + var sizes = '(max-width: ' + metadata.width + 'px) 100vw, ' + metadata.width + 'px'; + + // Update the sizes attribute of our image. + image.setAttribute( 'data-sizes', sizes ); + }; })(); diff --git a/readme.md b/readme.md index a7b0214..2b160bb 100644 --- a/readme.md +++ b/readme.md @@ -21,6 +21,19 @@ No configuration is needed! Just install the plugin and enjoy automatic responsi This plugin includes several functions that can be used by theme and plugin developers in templates. +###Advanced Image Compression + +**This is an experimental feature, if used, please provide us with feedback!** + +This feature turns on advanced compression, which will deliver higher quality images at a smaller file size. To enable, place the following code in your `functions.php` file - +``` +function custom_theme_setup() { + add_theme_support( 'advanced-image-compression' ); +} +add_action( 'after_setup_theme', 'custom_theme_setup' ); +``` +--- + ####tevkori_get_sizes( $id, $size, $args ) Returns a valid source size value for use in a 'sizes' attribute. The parameters include the ID of the image, the default size of the image, and an array or string containing of size information. The ID parameter is required. [Link](https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images/blob/master/wp-tevko-responsive-images.php#L28) @@ -84,17 +97,16 @@ Returns an array of image source candidates for use in a 'srcset' attribute. The ***Usage Example*** ``` - $sources = tevkori_get_srcset_array( 11, 'medium' ); -$srcset = array(); -foreach( $srcset as $source ) { - if ( false === strpos(' 900w', $source) { - $srcset[] = $source; - } +// Optionally remove a specific source from the srcset list. +foreach( $sources as $key => $source ) { + if ( strpos( $source, '300w' ) ) { + unset( $s[$key] ); + } } - + ``` --- @@ -123,11 +135,22 @@ We use a hook because if you attempt to dequeue a script before it's enqueued, w ##Version -2.2.1 +2.3.0 ##Changelog -- JS patch for wordpress +- Improved performance of `get_srcset_array` +- Added advanced image compression option (available by adding hook to functions.php) +- Duplicate entires now filtered out from srcset array +- Upgrade Picturefill to 2.3.1 +- Refactoring plugin JS, including a switch to ajax for updating the srcset value when the image is changed in the editor +- Now using wp_get_attachment_image_attributes filter for post thumbnails +- Readme and other general code typo fixes +- Gallery images will now contain a srcset attribute + +**2.2.1** + +- JS patch for WordPress **2.2.0** diff --git a/readme.txt b/readme.txt index 571e118..d390954 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: https://app.etapestry.com/hosted/BoweryResidentsCommittee/OnlineDon Tags: Responsive, Images, Responsive Images, SRCSET, Picturefill Requires at least: 4.1 Tested up to: 4.1 -Stable tag: 2.2.1 +Stable tag: 2.3.0 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.txt @@ -22,8 +22,19 @@ This plugin works by including all available image sizes for each image upload. 1. Upload `plugin-name.php` to the `/wp-content/plugins/` directory 2. Activate the plugin through the 'Plugins' menu in WordPress +3. If you'd like to enable the advanced image compression feature, Please see the instructions at https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images/tree/dev#advanced-image-compression == Changelog == += 2.3.0 +* Improved performance of get_srcset_array +* Added advanced image compression option (available by adding hook to functions.php) +* Duplicate entires now filtered out from srcset array +* Upgrade Picturefill to 2.3.1 +* Refactoring plugin JS, including a switch to ajax for updating the srcset value when the image is changed in the editor +* Now using wp_get_attachment_image_attributes filter for post thumbnails +* Readme and other general code typo fixes +* Gallery images will now contain a srcset attribute + = 2.2.1 = * Patch fixing missing javascript error diff --git a/wp-tevko-responsive-images.php b/wp-tevko-responsive-images.php index abd0d59..96e1f88 100644 --- a/wp-tevko-responsive-images.php +++ b/wp-tevko-responsive-images.php @@ -1,5 +1,6 @@ array( array( - 'size_value' => '100vw', - 'mq_value' => $img_width, - 'mq_name' => 'max-width' + 'size_value' => '100vw', + 'mq_value' => $img_width, + 'mq_name' => 'max-width' ), array( - 'size_value' => $img_width + 'size_value' => $img_width ), ) ); $args = wp_parse_args( $args, $defaults ); - // If sizes is passed as a string, just use the string + // If sizes is passed as a string, just use the string. if ( is_string( $args['sizes'] ) ) { $size_list = $args['sizes']; - // Otherwise, breakdown the array and build a sizes string + // Otherwise, breakdown the array and build a sizes string. } elseif ( is_array( $args['sizes'] ) ) { $size_list = ''; - foreach( $args['sizes'] as $size ) { + foreach ( $args['sizes'] as $size ) { + // Use 100vw as the size value unless something else is specified. $size_value = ( $size['size_value'] ) ? $size['size_value'] : '100vw'; // If a media length is specified, build the media query. - if ( ! empty($size['mq_value']) ) { + if ( ! empty( $size['mq_value'] ) ) { $media_length = $size['mq_value']; // Use max-width as the media condition unless min-width is specified. - $media_condition = ( ! empty($size['mq_name']) ) ? $size['mq_name'] : 'max-width'; + $media_condition = ( ! empty( $size['mq_name'] ) ) ? $size['mq_name'] : 'max-width'; // If a media_length was set, create the media query. $media_query = '(' . $media_condition . ": " . $media_length . ') '; } else { - // If not meda length was set, $media_query is blank + + // If not meda length was set, $media_query is blank. $media_query = ''; } @@ -98,7 +105,7 @@ function tevkori_get_sizes( $id, $size = 'thumbnail', $args = null ) { } // Remove the trailing comma and space from the end of the string. - $size_list = substr($size_list, 0, -2); + $size_list = substr( $size_list, 0, -2 ); } // If $size_list is defined set the string, otherwise set false. @@ -108,19 +115,19 @@ function tevkori_get_sizes( $id, $size = 'thumbnail', $args = null ) { } /** -* Return a source size list for an image from an array of values. -* -* @since 2.2.0 -* -* @param int $id Image attacment ID. -* @param string $size Optional. Name of image size. Default value: 'thumbnail'. -* @param array $args { -* Optional. Arguments to retrieve posts. -* -* @type array|string $sizes An array or string containing of size information. -* } -* @return string|bool A valid source size list as a 'sizes' attribute or false. -*/ + * Return a source size list for an image from an array of values. + * + * @since 2.2.0 + * + * @param int $id Image attachment ID. + * @param string $size Optional. Name of image size. Default value: 'thumbnail'. + * @param array $args { + * Optional. Arguments to retrieve posts. + * + * @type array|string $sizes An array or string containing of size information. + * } + * @return string|bool A valid source size list as a 'sizes' attribute or false. + */ function tevkori_get_sizes_string( $id, $size = 'thumbnail', $args = null ) { $sizes = tevkori_get_sizes( $id, $size, $args ); $sizes_string = $sizes ? 'sizes="' . $sizes . '"' : false; @@ -131,165 +138,194 @@ function tevkori_get_sizes_string( $id, $size = 'thumbnail', $args = null ) { /** * Get an array of image sources candidates for use in a 'srcset' attribute. * - * @param int $id Image attacment ID. - * @param string $size Optional. Name of image size. Default value: 'thumbnail'. - * @return array|bool An array of of srcset values or false. + * @param int $id Image attachment ID. + * @param string $size Optional. Name of image size. Default value: 'thumbnail'. + * @return array|bool An array of of srcset values or false. */ function tevkori_get_srcset_array( $id, $size = 'thumbnail' ) { $arr = array(); - // See which image is being returned and bail if none is found - if ( ! $image = wp_get_attachment_image_src( $id, $size ) ) { + // See which image is being returned and bail if none is found. + if ( ! $img = wp_get_attachment_image_src( $id, $size ) ) { return false; }; - // break image data into url, width, and height - list( $img_url, $img_width, $img_height ) = $image; + // Break image data into url, width, and height. + list( $img_url, $img_width, $img_height ) = $img; - // image meta - $image_meta = wp_get_attachment_metadata( $id ); + // Get the image meta data. + $img_meta = wp_get_attachment_metadata( $id ); - // default sizes - $default_sizes = $image_meta['sizes']; + // Build an array with image sizes. + $img_sizes = $img_meta['sizes']; - // add full size to the default_sizes array - $default_sizes['full'] = array( - 'width' => $image_meta['width'], - 'height' => $image_meta['height'], - 'file' => $image_meta['file'] + // Add full size to the img_sizes array. + $img_sizes['full'] = array( + 'width' => $img_meta['width'], + 'height' => $img_meta['height'], + 'file' => substr( $img_meta['file'], strrpos( $img_meta['file'], '/' ) + 1 ) ); - // Remove any hard-crops - foreach ( $default_sizes as $key => $image_size ) { + // Get the image base url. + $img_base_url = substr( $img_url, 0, strrpos( $img_url, '/' ) + 1 ); + + // Calculate the image aspect ratio. + $img_ratio = $img_height / $img_width; + + // Only use sizes with same aspect ratio. + foreach ( $img_sizes as $img_size => $img ) { - // calculate the height we would expect if this is a soft crop given the size width - $soft_height = (int) round( $image_size['width'] * $img_height / $img_width ); + // Calculate the height we would expect if the image size has the same aspect ratio. + $expected_height = (int) round( $img['width'] * $img_ratio ); - // If image height varies more than 1px over the expected, throw it out. - if ( $image_size['height'] <= $soft_height - 2 || $image_size['height'] >= $soft_height + 2 ) { - unset( $default_sizes[$key] ); + // If image height doesn't varies more than 2px over the expected, use it. + if ( $img['height'] >= $expected_height - 2 && $img['height'] <= $expected_height + 2 ) { + $arr[] = $img_base_url . $img['file'] . ' ' . $img['width'] .'w'; } } - // No sizes? Checkout early - if( ! $default_sizes ) - return false; + if ( empty( $arr ) ) { + return false; + } - // Loop through each size we know should exist - foreach( $default_sizes as $key => $size ) { + return $arr; +} - // Reference the size directly by it's pixel dimension - $image_src = wp_get_attachment_image_src( $id, $key ); - $arr[] = $image_src[0] . ' ' . $size['width'] .'w'; +/** + * Get the value for the 'srcset' attribute. + * + * @since 2.3.0 + * + * @param int $id Image attachment ID. + * @param string $size Optional. Name of image size. Default value: 'thumbnail'. + * @return string|bool A 'srcset' value string or false. + */ +function tevkori_get_srcset( $id, $size = 'thumbnail' ) { + $srcset_array = tevkori_get_srcset_array( $id, $size ); + + if ( empty( $srcset_array ) ) { + return false; } - return $arr; + return implode( ', ', $srcset_array ); } /** * Create a 'srcset' attribute. * - * @param int $id Image attacment ID. - * @param string $size Optional. Name of image size. Default value: 'thumbnail'. - * @return string|bool A full 'srcset' string or false. + * @since 2.1.0 + * + * @param int $id Image attachment ID. + * @param string $size Optional. Name of image size. Default value: 'thumbnail'. + * @return string|bool A full 'srcset' string or false. */ function tevkori_get_srcset_string( $id, $size = 'thumbnail' ) { - $srcset_array = tevkori_get_srcset_array( $id, $size ); - if ( empty( $srcset_array ) ) { + $srcset_value = tevkori_get_srcset( $id, $size ); + + if ( empty( $srcset_value ) ) { return false; } - return 'srcset="' . implode( ', ', $srcset_array ) . '"'; + + return 'srcset="' . $srcset_value . '"'; } /** * Create a 'srcset' attribute. * * @deprecated 2.1.0 - * @deprecated Use tevkori_get_srcset_string + * @deprecated Use tevkori_get_srcset_string instead. * @see tevkori_get_srcset_string * - * @param int $id Image attacment ID. - * @return string|bool A full 'srcset' string or false. + * @param int $id Image attachment ID. + * @return string|bool A full 'srcset' string or false. */ function tevkori_get_src_sizes( $id, $size = 'thumbnail' ) { return tevkori_get_srcset_string( $id, $size ); } /** - * Filter for extending image tag to include srcset attribute + * Filter for extending image tag to include srcset attribute. * - * @see 'images_send_to_editor' + * @see images_send_to_editor * @return string HTML for image. */ function tevkori_extend_image_tag( $html, $id, $caption, $title, $align, $url, $size, $alt ) { add_filter( 'editor_max_image_size', 'tevkori_editor_image_size' ); $sizes = tevkori_get_sizes( $id, $size ); + // Build the data-sizes attribute if sizes were returned. if ( $sizes ) { $sizes = 'data-sizes="' . $sizes . '"'; } + // Build the srcset attribute. $srcset = tevkori_get_srcset_string( $id, $size ); + remove_filter( 'editor_max_image_size', 'tevkori_editor_image_size' ); + $html = preg_replace( '/(src\s*=\s*"(.+?)")/', '$1 ' . $sizes . ' ' . $srcset, $html ); + return $html; } add_filter( 'image_send_to_editor', 'tevkori_extend_image_tag', 0, 8 ); /** - * Filter to add srcset attributes to post_thumbnails + * Filter to add srcset and sizes attributes to post thumbnails and gallery images. * - * @see 'post_thumbnail_html' - * @return string HTML for image. + * @see wp_get_attachment_image_attributes + * @return array Attributes for image. */ -function tevkori_filter_post_thumbnail_html( $html, $post_id, $post_thumbnail_id, $size, $attr ) { - // if the HTML is empty, short circuit - if ( '' === $html ) { - return; +function tevkori_filter_attachment_image_attributes( $attr, $attachment, $size ) { + $attachment_id = $attachment->ID; + + if ( ! isset( $attr['sizes'] ) ) { + $sizes = tevkori_get_sizes( $attachment_id, $size ); + + // Build the sizes attribute if sizes were returned. + if ( $sizes ) { + $attr['sizes'] = $sizes; + } } - $sizes = tevkori_get_sizes( $post_thumbnail_id, $size ); - // Build the data-sizes attribute if sizes were returned. - if ( $sizes ) { - $sizes = 'sizes="' . $sizes . '"'; + if ( ! isset( $attr['srcset'] ) ) { + $srcset = tevkori_get_srcset( $attachment_id, $size ); + $attr['srcset'] = $srcset; } - $srcset = tevkori_get_srcset_string( $post_thumbnail_id, $size ); - $html = preg_replace( '/(src\s*=\s*"(.+?)")/', '$1 ' . $sizes . ' ' . $srcset, $html ); - return $html; + return $attr; } -add_filter( 'post_thumbnail_html', 'tevkori_filter_post_thumbnail_html', 0, 5); +add_filter( 'wp_get_attachment_image_attributes', 'tevkori_filter_attachment_image_attributes', 0, 3 ); /** * Disable the editor size constraint applied for images in TinyMCE. * - * @param array $max_image_size An array with the width as the first element, and the height as the second element. + * @param array $max_image_size An array with the width as the first element, and the height as the second element. * @return array A width & height array so large it shouldn't constrain reasonable images. */ -function tevkori_editor_image_size( $max_image_size ){ +function tevkori_editor_image_size( $max_image_size ) { return array( 99999, 99999 ); } /** - * Load admin scripts + * Load admin scripts. * * @param string $hook Admin page file name. */ function tevkori_load_admin_scripts( $hook ) { - if ($hook == 'post.php' || $hook == 'post-new.php') { - wp_enqueue_script( 'wp-tevko-responsive-images', plugin_dir_url( __FILE__ ) . 'js/wp-tevko-responsive-images.js', array('wp-backbone'), '2.0.0', true ); + if ( $hook == 'post.php' || $hook == 'post-new.php' ) { + wp_enqueue_script( 'wp-tevko-responsive-images', plugin_dir_url( __FILE__ ) . 'js/wp-tevko-responsive-images.js', array( 'wp-backbone' ), '2.0.0', true ); } } add_action( 'admin_enqueue_scripts', 'tevkori_load_admin_scripts' ); /** - * Filter for the_content to replace data-size attributes with size attributes + * Filter for the_content to replace data-size attributes with size attributes. * * @since 2.2.0 * - * @param string $content The raw post content to be filtered. + * @param string $content The raw post content to be filtered. */ function tevkori_filter_content_sizes( $content ) { $images = '/(