# Image Optimization 2017/08 by YB --- # Content - Why - Images - Responsive Image - Image Optimization - Frontend - Backend - References --- # Why ---- ### Responsive Web Design ![](https://developers.google.com/web/fundamentals/design-and-ui/responsive/img/art-direction.png) - from https://developers.google.com/web/fundamentals/design-and-ui/responsive/images --- # Images - Vector Image - Raster Image ---- ### Vector Image ![](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/images/vector-zoom.png) - from https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization ---- #### SVG files ```xml <?xml version="1.0" encoding="utf-8"?> <!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <svg version="1.2" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 612 792" xml:space="preserve"> <g id="XMLID_1_"> <g> <circle fill="red" stroke="black" stroke-width="2" stroke-miterlimit="10" cx="50" cy="50" r="40"/> </g> </g> </svg> ``` - XML-based image format - should be minified to reduce their size - should be compressed with GZIP ---- ![](https://i.imgur.com/amYm5Y2.png) - from http://caniuse.com/ ---- ### Raster images ![](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/images/raster-zoom.png) - from https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization ---- | Format | Transparency | Animation | Browser | | ------ | ------------ | --------- | ------- | | GIF | Yes | Yes | All | | PNG | Yes | No | All | | JPEG | No | No | All | | JPEG XR | Yes | Yes | IE | | WebP | Yes | Yes | Chrome, Opera, Android | - 與可比較的 JPEG 圖片相比,WebP 平均可以使檔案大小縮減 30%。 ---- ![](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/images/format-tree.png) - from https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization ---- ### Checklist - 優先選用向量格式 - 縮減及壓縮 SVG 資源 - 選擇最佳點陣圖片格式 - 試驗點陣格式的最佳品質設定 - 刪除不必要的圖片中繼資料 - 提供可縮放的圖 - 自動化、自動化、自動化 - from [Google Web Fundamentals](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization) --- # Responsive Image ---- ## Viewport ```htmlmixed= <meta name="viewport" content="width=device-width, initial-scale=1.0"> ``` ---- | without viewport | viewport | |:----------------:|:--------:| | ![](https://developers.google.com/web/fundamentals/design-and-ui/responsive/imgs/no-vp.png) | ![](https://developers.google.com/web/fundamentals/design-and-ui/responsive/imgs/vp.png) | - from https://developers.google.com/web/fundamentals/design-and-ui/responsive/ ---- CSS pixels vs. Device pixels ![](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/images/css-vs-device-pixels.png) ---- ```htmlmixed= <meta name="viewport" content="width=device-width, initial-scale=1.0"> ``` - width=device-width,讓 Device Pixels 配合螢幕寬度。 - initial-scale=1,讓 CSS Pixels 和 Device Pixels 之間建立 1:1 的關係。 ---- ## Device Pixel Ratios (DPR) ``` DPR = Physical Pixels / Logical Pixels ``` ``` DPR = Device Pixels / CSS Pixels ``` ---- ## Use Cases - Device-pixel-ratio-based selection - Image formats - Art direction - The Fluid - And Variable-Sized-Image Use Cases ---- ### Device-pixel-ratio-based selection ![](http://usecases.responsiveimages.org/images/dpr_selection.jpg) - from http://usecases.responsiveimages.org/ ---- ```htmlmixed= <img src="image_2x.jpg" srcset="image_2x.jpg 2x, image_1x.jpg 1x" alt="a cool image" > ``` ```css= img { background-image: image-set( url(image_1x.png) 1x, url(image_2x.png) 2x ); } ``` ---- ### Image formats ```htmlmixed= <picture> <source srcset="large.webp" type="image/webp"> <img srcset="large.jpg" alt="a cool image"> </picture> ``` ---- ### Art direction ![](https://developers.google.com/web/fundamentals/design-and-ui/responsive/img/art-direction.png) - from https://developers.google.com/web/fundamentals/design-and-ui/responsive/images ---- ```htmlmixed= <picture> <source media="(min-width: 800px)" srcset="head.jpg, head-2x.jpg 2x"> <source media="(min-width: 450px)" srcset="head-small.jpg, head-small-2x.jpg 2x"> <img src="head-fb.jpg" srcset="head-fb-2x.jpg 2x" alt="a head carved out of wood"> </picture> ``` - [Google Sample](https://googlesamples.github.io/web-fundamentals/fundamentals/design-and-ui/media/media.html) ---- ```css= img { background-image: image-set( url(head-fb.jpg) 1x, url(head-fb-2x.jpg) 2x ); } @media (min-width: 450px) { img { background-image: image-set( url(head-small.jpg) 1x, url(head-small-2x.jpg) 2x ); } } ``` ---- ### The Fluid - And Variable-Sized-Image Use Cases ```htmlmixed= <img src="image_1000.jpg" // for fallback sizes="100vw" srcset="image_1000.jpg 1000w, image_600.jpg 600w" alt="a cool image" > ``` - depending on viewport size and DPR - [Example By Google](https://googlesamples.github.io/web-fundamentals/fundamentals/design-and-ui/media/sizes.html) ---- if (viewport === '320px' && (dpr === 1)) ```htmlmixed= // image need: 320 px * 1 dpr = 320px; // image_600: 600 / 320 = 1.875; // image_1000: 1000 / 320 = 3.125; <img src="image_1000.jpg" // for fallback sizes="100vw" srcset="image_1000.jpg 1000w, image_600.jpg 600w" alt="a cool image" > // show image_600.jpg ``` ---- if (viewport === '320px' && (dpr === 2)) ```htmlmixed= // image need: 320 px * 2 dpr = 640px; // image_600: 600 / 640 = 0.9375; // image_1000: 1000 / 640 = 1.5625; <img src="image_1000.jpg" // for fallback sizes="100vw" srcset="image_1000.jpg 1000w, image_600.jpg 600w" alt="a cool image" > // show image_1000.jpg ``` ---- if (viewport === '600px' && (dpr === 2)) ```htmlmixed= // image need: 600 px * 2 dpr * (50vw / 100vw) = 600px; // image_600: 600 / 600 = 1; // image_1000: 1000 / 600 = 1.667; <img src="image_1000.jpg" // for fallback sizes="50vw" srcset="image_1000.jpg 1000w, image_600.jpg 600w" alt="a cool image" > // show image_600.jpg ``` ---- ```htmlmixed= <img src="400.png" sizes="(min-width: 600px) 25vw, (min-width: 500px) 50vw, 100vw" srcset="100.png 100w, 200.png 200w, 400.png 400w, 800.png 800w, 1600.png 1600w, 2000.png 2000w" alt="an example image"> ``` ---- ## Can I Use? ---- ### Srcset and sizes ![](https://i.imgur.com/BO2fcKx.png) - from http://caniuse.com/ ---- ### Picture ![](https://i.imgur.com/nradlZ8.png) - from http://caniuse.com/ ---- ### [Picturefill](https://scottjehl.github.io/picturefill/) - A Responsive Image Polyfill ---- ### image-set ![](https://i.imgur.com/iRbq6RZ.png) - from http://caniuse.com/ ---- ### PostCSS-cssnext - Use tomorrow’s CSS syntax, today. ```css= /* Input example */ .foo { background-image: image-set( url(img/test.png) 1x, url(img/test-2x.png) 2x ); } ``` ```css= /* Output example */ .foo { background-image: url(img/test.png); } @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .foo { background-image: url(img/test-2x.png); } } ``` --- # Image Optimization - Image Compression - Lossless - lossy - Serve Responsive Images - Versioning ---- - Frontend - Backend --- ## Frontend ---- ### Automate - Gulp - Webpack ---- #### Gulp ![](http://persistentsoftware.com/imgs/app/homepage/assetpipeline.png) - from http://persistentsoftware.com/ ---- #### Webpack ![](https://webpack.github.io/assets/what-is-webpack.png) - from https://webpack.github.io/ ---- ### Image Compression ---- #### Tools | Tool | Description | | ---- | ----------- | | gifsicle | 建立及最佳化 GIF 圖片 | | jpegtran | 最佳化 JPEG 圖片 | | optipng | 無失真 PNG 最佳化 | | pngquant | 失真 PNG 最佳化 | | svgo | SVG 最佳化 | ---- #### imagemin ![](https://avatars3.githubusercontent.com/u/7868808?v=4&s=200) - https://github.com/imagemin/imagemin ---- #### [gulp-imagemin for gulp](https://github.com/sindresorhus/gulp-imagemin) ```javascript= var gulp = require('gulp'); var imagemin = require('gulp-imagemin'); gulp.task('images', function() { return gulp.src('src/images/**/*.+(jpe?g|png|svg|gif)') .pipe(imagemin()) .pipe(gulp.dest('dist/images')); }); ``` ---- #### [mage-webpack-loader for webpack](https://github.com/tcoopman/image-webpack-loader) ```javascript= module.exports = { // ... module: { rules: [ { test: /\.(gif|png|jpe?g|svg)$/i, loaders: [ 'file-loader', 'image-webpack-loader', ], }, ], }, }; ``` ---- ### Responsive Images ---- #### [gulp-responsive for gulp](https://github.com/mahnunchik/gulp-responsive) - base on sharp ```javascript= var gulp = require('gulp'); var responsive = require('gulp-responsive'); gulp.task('image:resize', function () { return gulp.src('src/images/**/*.{png,jpg}') .pipe(responsive({ '*': [{ width: 480, rename: { suffix: '-480px' }, }, { width: 1140, rename: { suffix: '-1140px' }, }, { // Compress, strip metadata, and rename original image rename: { suffix: '-original' }, }], })) .pipe(gulp.dest('dist')); }); ``` ---- ```htmlmixed= <img src="myImage-480px.jpg" srcSet="myImage-480px.jpg 480w, myImage-1140px.jpg 1140w" > ``` ---- ```css= .myImage { background: url('myImage-480px.jpg'); } @media (min-width: 480px) { .myImage { background: url('myImage-1140px.jpg'); } } ``` ---- #### [responsive-loader for webpack](https://github.com/herrstucki/responsive-loader) - jimp (entirely in JavaScript) - sharp (need libvips) ```javascript= module.exports = { // ... module: { rules: [ { test: /\.(jpe?g|png)$/i, loader: 'responsive-loader', options: { // If you want to enable sharp support: // adapter: require('responsive-loader/sharp') } } ] }, } ``` ---- ```javascript= const responsiveImage = require('myImage.jpg?sizes[]=480,sizes[]=1140'); // responsiveImage.images => [{height: 240, path: 'myImage-480.jpg', width: 480}, {height: 570, path: 'myImage-1140.jpg', width: 1140}] // responsiveImage.srcSet => 'myImage-480.jpg 480w,myImage-1140.jpg 1140w' // responsiveImage.src => 'myImage-480.jpg' ReactDOM.render( <img srcSet={responsiveImage.srcSet} src={responsiveImage.src} />, el ); ``` ---- ```css= .myImage { background: url('myImage.jpg?size=480'); } @media (min-width: 480px) { .myImage { background: url('myImage.jpg?size=1140'); } } ``` ---- ### Versioning ---- #### [gulp-rev for gulp](https://github.com/sindresorhus/gulp-rev) ```javascript= const gulp = require('gulp'); const rev = require('gulp-rev'); gulp.task('default', function() { return gulp.src('src/images/**/*.{png,jpg}') .pipe(rev()) .pipe(gulp.dest('dist')) }); ``` ---- #### [file-loader for webpack](https://github.com/webpack-contrib/file-loader) ```javascript= module.exports = { // ... module: { rules: [ { test: /\.(jpe?g|png)$/i, loader: 'file-loader', options: { name: '[hash].[ext]', } } ] } } ``` ---- ### Linter - [Linter Tool](https://github.com/ausi/RespImageLint) - [Common Mistakes and Best Practises](https://ausi.github.io/respimagelint/docs.html) --- ## Backend ---- ![](https://i.imgur.com/mIdJvsA.png) - from [CodeTengu Weekly 碼天狗週刊](https://weekly.codetengu.com/issues/93#tzangms) ---- - [Intervention Image](http://image.intervention.io/getting_started/introduction) - image processing libraries - GD library - Imagick - Basic Usage - Resizing - Adjusting - Applying Effects - Drawing - Retrieving Information - Append Watermark ---- ### Image Compression and Responsive ```php= // Laravel Route::get('images/original/{fileName}', 'ImageController@showOriginal'); Route::get('images/{type}/{size}/{fileName}', 'ImageController@show'); // type: resize or fit // size: 100x100 or 100x or x100 ``` ---- ```php= // Laravel public function show($type, $size, $fileName, Request $request) { // parse inputs $rect = explode('x', $size); $width = $rect[0] ?: null; $height = $rect[1] ?: null; // resize or fit img $imgSrc = Storage::get('images/' . $fileName); switch($type) { case 'resize': $img = Image::cache(function($image) use ($imgSrc, $width, $height) { $image->make($imgSrc) ->resize($width, $height, function ($constraint) { $constraint->aspectRatio(); }); }, 43200, true); break; case 'fit': $img = Image::cache(function($image) use ($imgSrc, $width, $height) { $image->make($imgSrc) ->fit($width, $height, function ($constraint) { $constraint->upsize(); }); }, 43200, true); break; } // response return $img->response(); } ``` ---- ### HTTP Cache ---- ![](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/images/http-cache-decision-tree.png) - from https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#publicprivate ---- - 設定快取圖片時間為一年 ```php= <VirtualHost *:80> // ... <LocationMatch "^.*$(?<!\.ico|\.pdf|\.jpg|\.jpeg|\.png|\.bmp|\.gif|\.css|\.js|\.svg)"> Header set Cache-Control "max-age=31536000, public" </LocationMatch> </VirtualHost> ``` ---- ### Versioning - 在圖片變更時,變更圖片的網址,強制使用者下載新的圖片 ```php= // Laravel use Illuminate\Http\Request; function store(Request $request) { $image = $request->file('photo'); $hashName = $image->hashName(); Storage::put('images/' . $hashName, $image); } ``` ---- ### Guetzli ---- - Guetzli: A New Open-Source JPEG Encoder - Guetzli = 一種瑞士餅乾 - Google Open Source in 2017 / 3 / 16 - 同樣品質下,可以省下29 ~ 45 % 的空間 - 需要大量的 CPU 運算 - [google / guetzli](https://github.com/google/guetzli) - [Guetzli: Perceptually Guided JPEG Encoder](https://arxiv.org/abs/1703.04421) ---- - 適合排程處理壓縮 - 12 張 1920×1280 圖片 - Intel Core i5 3.1 GHz - 95% 的 quality 壓縮花了 38 分 40 秒 - from [Guetzli 開放原始碼 JPEG 圖片壓縮編碼器](https://blog.gtwang.org/web-development/guetzli-jpeg-image-encoder/) --- # Conclusion - Responsive Image Design - Image formats - SVG - JPG - PNG - Basic Usage - Image Compress - Image Resize - Versioning - Frontend Workflow Automation - Backend Image API - HTTP Cache --- # Refereces - [Google Web Fundamentals: Image Optimization](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization) - [Google Web Fundamentals: Images](https://developers.google.com/web/fundamentals/design-and-ui/responsive/images) - [Use Cases and Requirements for Standardizing Responsive Images](http://usecases.responsiveimages.org/) - [Intervention Image](http://image.intervention.io/) - [Guetzli 開放原始碼 JPEG 圖片壓縮編碼器](https://blog.gtwang.org/web-development/guetzli-jpeg-image-encoder/) - [Google Web Fundamentals: Http Cache](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching) - [循序漸進理解 HTTP Cache 機制](http://blog.techbridge.cc/2017/06/17/cache-introduction/) - [Can I Use](http://caniuse.com/)