├── .gitignore ├── README.md ├── package.json ├── src ├── Image.js ├── ImageMap.js ├── ImagingHeap.js └── cmd.js └── test ├── ImageTest.js ├── ericportis.html ├── images ├── fam-320.jpg ├── fam-480.jpg ├── fam-960.jpg ├── fam.jpg ├── sunset-1280.jpg ├── sunset-320.jpg ├── sunset-3264.jpg ├── sunset-640.jpg └── sunset.jpg ├── img-2.html ├── img.html ├── picture-2.html ├── picture.html ├── srcset-2.html └── srcset.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: This project is archived and the repository is no longer maintained. 2 | 3 | # imaging-heap 4 | 5 | There’s beauty in the breakdown of bitmap image data. A command line tool to measure the efficiency of your responsive image markup across viewport sizes and device pixel ratios. 6 | 7 | Works out-of-the-box with `img` (of course), `img[srcset]`, `img[srcset][sizes]`, `picture`, `picture [srcset]`, `picture [srcset][sizes]`. Ignores `.svg` files. No support for background images (yet?). 8 | 9 | ## Installation 10 | 11 | ```sh 12 | npm install --global imaging-heap 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```sh 18 | imagingheap https://filamentgroup.com/ 19 | ``` 20 | 21 | ```sh 22 | imagingheap http://example.com/ [options] 23 | 24 | Options: 25 | --version Show version number [boolean] 26 | --min Minimum viewport width [default: 320] 27 | --max Maximum viewport width [default: 1280] 28 | --by Increment viewport by [default: 80] 29 | --dpr List of Device Pixel Ratios [string] [default: "1,2,3"] 30 | --minimagewidth Ignore images smaller than image width [default: 5] 31 | --csv Output CSV [boolean] [default: false] 32 | --help Show help [boolean] 33 | ``` 34 | 35 | ### Debug Output 36 | 37 | ```sh 38 | DEBUG=ImagingHeap* imagingheap https://filamentgroup.com/ 39 | ``` 40 | 41 | ## Sample output 42 | 43 | ```sh 44 | ╔══════════╤══════════╤═══════╤════════════╤════════╤════════════╤════════╤════════════╗ 45 | ║ │ Image │ @1x │ @1x │ @2x │ @2x │ @3x │ @3x ║ 46 | ║ │ Width in │ Image │ Percentage │ Image │ Percentage │ Image │ Percentage ║ 47 | ║ Viewport │ Layout │ Width │ Match │ Width │ Match │ Width │ Match ║ 48 | ╟──────────┼──────────┼───────┼────────────┼────────┼────────────┼────────┼────────────╢ 49 | ║ 320px │ 161px │ 301px │ 187.0% │ 301px │ 93.5% │ 601px │ 125.2% ║ 50 | ║ 480px │ 241px │ 301px │ 124.9% │ 601px │ 125.2% │ 601px │ 83.5% ║ 51 | ║ 640px │ 321px │ 601px │ 187.2% │ 601px │ 93.6% │ 901px │ 93.9% ║ 52 | ║ 800px │ 401px │ 601px │ 149.9% │ 901px │ 112.6% │ 1201px │ 100.1% ║ 53 | ║ 960px │ 480px │ 600px │ 125.0% │ 900px │ 93.8% │ 1200px │ 83.3% ║ 54 | ║ 1120px │ 560px │ 600px │ 107.1% │ 1200px │ 107.1% │ 1200px │ 71.4% ║ 55 | ║ 1280px │ 640px │ 900px │ 140.6% │ 1200px │ 93.8% │ 1200px │ 62.5% ║ 56 | ╚══════════╧══════════╧═══════╧════════════╧════════╧════════════╧════════╧════════════╝ 57 | ``` 58 | 59 | ## Related Projects 60 | 61 | * Hugely inspired by the [NCC Image Checker Chrome Extension](https://github.com/nccgroup/image-checker) (which is a great visual tool). The main difference here is that imaging-heap will collate data across viewport sizes and device pixel ratios. 62 | * [RespImageLint](https://ausi.github.io/respimagelint/) a Linter Bookmarklet for Responsive Images 63 | 64 | ## Important Links 65 | 66 | * _A big big thank you_ for pre-release feedback from noted responsive images expert [Eric Portis](https://ericportis.com/) ([@eeeps](https://github.com/eeeps/)). 67 | * Idea originally documented at [@zachleat/idea-book/3](https://github.com/zachleat/idea-book/issues/3) 68 | * _“There’s beauty in the breakdown of bitmap image data.”_—[@jefflembeck](https://github.com/jefflembeck) 69 | * [Responsive Images Community Group](https://responsiveimages.org/) 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imaging-heap", 3 | "version": "1.0.0", 4 | "description": "A command line tool to measure responsive image efficiency across viewport sizes and device pixel ratios.", 5 | "main": "src/ImagingHeap.js", 6 | "engines": { 7 | "node": ">=8.0.0" 8 | }, 9 | "scripts": { 10 | "test": "ava" 11 | }, 12 | "bin": { 13 | "imagingheap": "./src/cmd.js" 14 | }, 15 | "keywords": [ 16 | "responsive-images", 17 | "img" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/filamentgroup/image-report.git" 22 | }, 23 | "author": { 24 | "name": "Zach Leatherman", 25 | "email": "zach@filamentgroup.com", 26 | "url": "https://filamentgroup.com/" 27 | }, 28 | "license": "MIT", 29 | "ava": { 30 | "files": [ 31 | "test/*.js" 32 | ], 33 | "source": [ 34 | "src/**/*.js" 35 | ] 36 | }, 37 | "devDependencies": { 38 | "ava": "^0.25.0" 39 | }, 40 | "dependencies": { 41 | "chalk": "^2.3.2", 42 | "debug": "^3.1.0", 43 | "puppeteer": "^1.2.0", 44 | "table": "^4.0.3", 45 | "yargs": "^11.0.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Image.js: -------------------------------------------------------------------------------- 1 | class Image { 2 | constructor() { 3 | this.sizes = {}; 4 | this.html = ""; 5 | } 6 | 7 | setHTML( html ) { 8 | this.html = html; 9 | } 10 | 11 | addStatsObject( stats ) { 12 | this.sizes[ parseInt( stats.viewportWidth, 10 ) ] = stats; 13 | } 14 | 15 | addStats( viewportWidth, dimensions, fileDimensions, src ) { 16 | let obj = { 17 | src: src, 18 | viewportWidth: viewportWidth, 19 | 20 | width: Array.isArray(dimensions) ? dimensions[0] : dimensions.width, 21 | fileWidth: Array.isArray(fileDimensions) ? fileDimensions[0] : fileDimensions.width 22 | }; 23 | 24 | this.addStatsObject( obj ); 25 | } 26 | 27 | getSizes() { 28 | return this.sizes; 29 | } 30 | 31 | getViewportSizes() { 32 | return Object.keys(this.sizes).map(size => parseInt(size, 10)); 33 | } 34 | 35 | getStatsAtViewportWidth(viewportWidth) { 36 | return this.sizes[parseInt(viewportWidth, 10)]; 37 | } 38 | 39 | // images are assumed to have the same aspect ratio 40 | getEfficiencyAtViewportWidth(viewportWidth) { 41 | let stats = this.getStatsAtViewportWidth(viewportWidth); 42 | return this.getEfficiencyFromStats(stats); 43 | } 44 | 45 | getEfficiencyFromStats(stats) { 46 | return stats.fileWidth / stats.width; 47 | } 48 | 49 | getStats() { 50 | return this.getViewportSizes().map(vw => { 51 | let stats = this.getStatsAtViewportWidth(vw); 52 | let eff = this.getEfficiencyFromStats(stats); 53 | 54 | return { 55 | html: stats.html, 56 | src: stats.src, 57 | viewportWidth: vw, 58 | fileWidth: stats.fileWidth, 59 | width: stats.width, 60 | efficiency: eff 61 | }; 62 | }); 63 | } 64 | } 65 | 66 | module.exports = Image; -------------------------------------------------------------------------------- /src/ImageMap.js: -------------------------------------------------------------------------------- 1 | const Image = require("./Image"); 2 | const { table } = require("table"); 3 | const chalk = require("chalk"); 4 | 5 | class ImageMap { 6 | constructor() { 7 | this.map = {}; 8 | this.showCurrentSrc = false; 9 | this.showPercentages = true; 10 | } 11 | 12 | setMinimumImageWidth(width) { 13 | this.minImageWidth = width; 14 | } 15 | 16 | setShowCurrentSrc(showCurrentSrc) { 17 | this.showCurrentSrc = !!showCurrentSrc; 18 | } 19 | 20 | setShowPercentages(showPercentages) { 21 | this.showPercentages = !!showPercentages; 22 | } 23 | 24 | addImage(identifier, dpr, stats) { 25 | if( !this.map[identifier] ) { 26 | this.map[identifier] = {}; 27 | } 28 | 29 | let img = this.map[identifier][dpr]; 30 | 31 | if(!img) { 32 | img = new Image(); 33 | this.map[identifier][dpr] = img; 34 | } 35 | 36 | img.addStatsObject(stats); 37 | } 38 | 39 | getMap() { 40 | return this.map; 41 | } 42 | 43 | getNumberOfImages() { 44 | return Object.keys(this.map).length; 45 | } 46 | 47 | truncateUrl(url) { 48 | // let urlMaxLength = 20; 49 | // return (url.length > urlMaxLength ? "…" : "") + url.substr(-1 * urlMaxLength); 50 | return url.split("/").pop(); 51 | } 52 | 53 | _getTableHeadersForIdentifier(identifier) { 54 | let tableHeaders = [ 55 | ["", "Image"], 56 | ["", "Width in"], 57 | ["Viewport", "Layout"] 58 | ]; 59 | let map = this.map[identifier]; 60 | 61 | for( let dpr in map ) { 62 | tableHeaders[0].push(`@${dpr}x`); 63 | tableHeaders[1].push(`Image`); 64 | tableHeaders[2].push(`Width`); 65 | tableHeaders[0].push(`@${dpr}x`); 66 | if( this.showPercentages ) { 67 | tableHeaders[1].push(`Percentage`); 68 | tableHeaders[2].push(`Match`); 69 | } else { 70 | tableHeaders[1].push(`Ratio`); 71 | tableHeaders[2].push(``); 72 | } 73 | 74 | if(this.showCurrentSrc) { 75 | tableHeaders[0].push(``); 76 | tableHeaders[1].push(`@${dpr}x`); 77 | tableHeaders[2].push(`currentSrc`); 78 | } 79 | } 80 | 81 | return tableHeaders; 82 | } 83 | 84 | _convertTableHeadersToString(headers) { 85 | let ret = []; 86 | let firstRow = true; 87 | for( let row of headers ) { 88 | for( let colKey = 0, k = row.length; colKey < k; colKey++ ) { 89 | if( firstRow ) { 90 | ret.push([]); 91 | } 92 | 93 | if( row[colKey] ) { 94 | ret[colKey].push(row[colKey]); 95 | } 96 | } 97 | 98 | firstRow = false; 99 | } 100 | return ret.map(header => header.join(" ")); 101 | } 102 | 103 | _getOutputObj() { 104 | let output = {}; 105 | for( let identifier in this.map ) { 106 | let map = this.map[identifier]; 107 | 108 | let tableRows = {}; 109 | let htmlOutput = ""; 110 | let includeInOutput = false; 111 | 112 | for( let dpr in map ) { 113 | let stats = map[dpr].getStats(); 114 | for( let vwStats of stats ) { 115 | if( !htmlOutput ) { 116 | htmlOutput = `${vwStats.html}`; 117 | } 118 | let dprNum = parseInt(dpr); 119 | let widthRatio = vwStats.efficiency; 120 | 121 | let percentage = (widthRatio * 100 / dprNum).toFixed(1); 122 | let str = `${widthRatio.toFixed(2)}x`; 123 | 124 | if( this.showPercentages ) { 125 | str = `${percentage}%`; 126 | } 127 | 128 | let efficiencyOutput; 129 | if( widthRatio < 1 || percentage < 75 ) { 130 | efficiencyOutput = chalk.red(str); 131 | } else if( percentage < 92 || percentage > 150 ) { 132 | efficiencyOutput = chalk.yellow(str); 133 | } else { 134 | efficiencyOutput = str; 135 | } 136 | 137 | let vw = `${vwStats.viewportWidth}px`; 138 | if(!tableRows[vw]) { 139 | if(vwStats.width && (!this.minImageWidth || vwStats.width > this.minImageWidth)) { 140 | includeInOutput = true; 141 | } 142 | tableRows[vw] = [`${vwStats.width}px`]; 143 | } 144 | tableRows[vw].push(`${vwStats.fileWidth}px`); 145 | tableRows[vw].push(efficiencyOutput); 146 | 147 | if( this.showCurrentSrc ) { 148 | tableRows[vw].push(vwStats.src); 149 | } 150 | } 151 | } 152 | 153 | if( includeInOutput ) { 154 | let tableContent = []; 155 | for(let row in tableRows) { 156 | tableContent.push([].concat(row, tableRows[row])); 157 | } 158 | 159 | output[htmlOutput] = { 160 | headers: this._getTableHeadersForIdentifier(identifier, this.showCurrentSrc), 161 | content: tableContent 162 | }; 163 | } 164 | } 165 | 166 | return output; 167 | } 168 | 169 | getCsvOutput() { 170 | this.setShowCurrentSrc(true); 171 | 172 | let DELIMITER = ","; 173 | let obj = this._getOutputObj(); 174 | let output = []; 175 | for( let html in obj ) { 176 | output.push(this._convertTableHeadersToString(obj[html].headers)); 177 | for( let row of obj[html].content) { 178 | output.push(row.join(DELIMITER)); 179 | } 180 | output.push("# ---"); // DELIMIT CSV FILES 181 | } 182 | 183 | return output.join("\n"); 184 | } 185 | 186 | _getOutput() { 187 | let obj = this._getOutputObj(); 188 | let output = []; 189 | 190 | for( let html in obj ) { 191 | output.push(html); 192 | 193 | let rows = [].concat(obj[html].headers, obj[html].content); 194 | output.push(table(rows, { 195 | drawHorizontalLine: (index, size) => { 196 | return index === 0 || index === 3 || index === size; 197 | } 198 | }) + 199 | `${chalk.underline("Legend")}: @1x ${chalk.red("<100%")} ${chalk.yellow(">150%")} Above @1x ${chalk.red("<75%")} ${chalk.yellow("75%–92%, >150%")}\n`); 200 | } 201 | 202 | let size = this.getNumberOfImages(); 203 | output.push(size + " bitmap image" + (size !== 1 ? "s" : "") + " found."); 204 | 205 | return output.join("\n"); 206 | } 207 | 208 | getOutput(useCsv) { 209 | if(useCsv) { 210 | chalk.enabled = false; 211 | 212 | return this.getCsvOutput(); 213 | } 214 | 215 | return this._getOutput(); 216 | } 217 | } 218 | 219 | module.exports = ImageMap; -------------------------------------------------------------------------------- /src/ImagingHeap.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require("puppeteer"); 2 | const ImageMap = require("./ImageMap"); 3 | const debug = require("debug")("ImagingHeap"); 4 | 5 | class ImagingHeap { 6 | constructor(options) { 7 | this.options = Object.assign({}, this.defaultOptions, options || {}); 8 | 9 | this.dprArray = (this.options.dpr || "").split(",").map(dpr => parseInt(dpr, 10)); 10 | 11 | debug("Options: %o", this.options); 12 | 13 | this.map = {}; 14 | } 15 | 16 | static get defaultOptions() { 17 | return { 18 | minViewportWidth: 320, 19 | maxViewportWidth: 1280, 20 | increment: 80, 21 | useCsv: false, 22 | dpr: "1,2,3", 23 | minImageWidth: 5 24 | }; 25 | } 26 | 27 | getDprArraySize() { 28 | return this.dprArray.length; 29 | } 30 | 31 | async start() { 32 | this.browser = await puppeteer.launch(); 33 | } 34 | 35 | async getPage(url, dpr) { 36 | let page = await this.browser.newPage(); 37 | 38 | // page.setCacheEnabled(false); 39 | let defaultPageOptions = { 40 | width: this.options.minViewportWidth, 41 | height: 768, 42 | deviceScaleFactor: dpr 43 | }; 44 | 45 | debug("Setting default page options %o", defaultPageOptions); 46 | await page.setViewport(defaultPageOptions); 47 | 48 | await page.goto(url, { 49 | waitUntil: ["load", "networkidle0"] 50 | }); 51 | 52 | page.on("console", function(msg) { 53 | debug("(browser console): %o", msg.text()); 54 | }); 55 | 56 | return page; 57 | } 58 | 59 | async iterate(url, callback) { 60 | this.map[url] = new ImageMap(); 61 | this.map[url].setMinimumImageWidth(this.options.minImageWidth); 62 | 63 | for( let j = 0, k = this.dprArray.length; j < k; j++ ) { 64 | let dpr = this.dprArray[j]; 65 | await this.iterateViewports(url, dpr, callback); 66 | } 67 | } 68 | 69 | async iterateViewports(url, dpr, callback) { 70 | let page = await this.getPage(url, dpr); 71 | for(let width = this.options.minViewportWidth, end = this.options.maxViewportWidth; width <= end; width += this.options.increment) { 72 | let viewportOptions = { 73 | width: width, 74 | height: 768, 75 | deviceScaleFactor: dpr 76 | }; 77 | debug("Setting viewport options: %o", viewportOptions); 78 | await page.setViewport(viewportOptions); 79 | 80 | // await page.reload({ 81 | // waitUntil: ["load", "networkidle0"] 82 | // }); 83 | 84 | let imagesStats = await this.getImagesStats(page); 85 | for( let statsJson of imagesStats ) { 86 | let stats = JSON.parse(statsJson); 87 | this.map[url].addImage(stats.id, dpr, stats); 88 | } 89 | 90 | callback(); 91 | } 92 | } 93 | 94 | async getImagesStats(page) { 95 | return page.evaluate(function() { 96 | function findNaturalWidth(src, stats, resolve) { 97 | var naturalImg = document.createElement("img"); 98 | naturalImg.src = src; 99 | naturalImg.onload = function() { 100 | resolve(JSON.stringify(Object.assign(stats, { 101 | fileWidth: naturalImg.naturalWidth, 102 | src: src, 103 | }))); 104 | 105 | this.parentNode.removeChild(this); 106 | }; 107 | 108 | naturalImg.onerror = function() { 109 | resolve(JSON.stringify(Object.assign(stats, { 110 | fileWidth: undefined, 111 | src: src, 112 | }))); 113 | }; 114 | 115 | document.body.appendChild(naturalImg); 116 | } 117 | 118 | let viewportWidth = document.documentElement.clientWidth; 119 | console.log(`New viewportWidth: ${viewportWidth}`); 120 | 121 | let imgNodes = document.querySelectorAll("img"); 122 | let imgArray = Array.from(imgNodes); 123 | 124 | let promises = Promise.all( 125 | imgArray.filter(function(img) { 126 | let src = img.currentSrc; 127 | if( !src ) { 128 | return true; 129 | } 130 | let split = (new URL(src)).pathname.split("."); 131 | if( !split.length ) { 132 | return true; 133 | } 134 | 135 | return split.pop().toLowerCase() !== "svg"; 136 | }).map(function(img) { 137 | let key = "data-image-report-index"; 138 | let id = img.getAttribute(key); 139 | if(!id) { 140 | id = img.getAttribute("src"); 141 | // console.log("Creating new img id", id); 142 | img.setAttribute(key, id); 143 | } else { 144 | // console.log("Re-use existing img id", id); 145 | } 146 | 147 | let picture = img.closest("picture"); 148 | img.removeAttribute(key); 149 | let html = (picture || img).outerHTML.replace(/\t/g, ""); 150 | img.setAttribute(key, id); 151 | 152 | 153 | let stats = { 154 | id: id, 155 | viewportWidth: viewportWidth, 156 | html: html 157 | }; 158 | 159 | return new Promise(function(resolve, reject) { 160 | function done() { 161 | stats.width = img.clientWidth; 162 | console.log(`currentSrc: ${img.currentSrc}`); 163 | console.log(`width: ${img.clientWidth}`); 164 | 165 | findNaturalWidth(img.currentSrc, stats, resolve); 166 | } 167 | 168 | if( !img.currentSrc ) { 169 | img.addEventListener("load", done, { 170 | once: true 171 | }); 172 | } else { 173 | done(); 174 | } 175 | }); 176 | }) 177 | ); 178 | 179 | return promises; 180 | }); 181 | } 182 | 183 | getResults() { 184 | let output = []; 185 | for( let url in this.map ) { 186 | let str = this.map[url].getOutput(this.options.useCsv); 187 | output.push(str); 188 | } 189 | 190 | return output.join( "\n" ); 191 | } 192 | 193 | async finish() { 194 | if( !this.browser ) { 195 | throw new Error("this.browser doesn’t exist, did you run .start()?"); 196 | } 197 | await this.browser.close(); 198 | } 199 | } 200 | 201 | module.exports = ImagingHeap; -------------------------------------------------------------------------------- /src/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const ImagingHeap = require("./ImagingHeap"); 3 | let defaults = ImagingHeap.defaultOptions; 4 | 5 | const argv = require("yargs") 6 | .usage("imagingheap http://example.com/ [options]") 7 | .demandCommand(1) 8 | .options({ 9 | min: { 10 | describe: "Minimum viewport width", 11 | default: defaults.minViewportWidth 12 | }, 13 | max: { 14 | describe: "Maximum viewport width", 15 | default: defaults.maxViewportWidth 16 | }, 17 | by: { 18 | describe: "Increment viewport by", 19 | default: defaults.increment 20 | }, 21 | dpr: { 22 | describe: "List of Device Pixel Ratios", 23 | default: defaults.dpr, 24 | type: "string" 25 | }, 26 | minimagewidth: { // tracking pixels 27 | describe: "Ignore images smaller than image width", 28 | default: defaults.minImageWidth 29 | }, 30 | csv: { 31 | describe: "Output CSV", 32 | default: defaults.useCsv, 33 | type: "boolean" 34 | } 35 | }) 36 | .help() 37 | .argv; 38 | 39 | const ProgressBar = require("progress"); 40 | 41 | (async function() { 42 | try { 43 | let report = new ImagingHeap({ 44 | minViewportWidth: argv.min, 45 | maxViewportWidth: argv.max, 46 | increment: argv.by, 47 | useCsv: argv.csv, 48 | dpr: argv.dpr, 49 | minImageWidth: argv.minimagewidth 50 | }); 51 | 52 | let bar; 53 | if (!process.env.DEBUG) { 54 | bar = new ProgressBar(":bar :current/:total", { 55 | incomplete: ".", 56 | clear: true, 57 | callback: async function() { 58 | await report.finish(); 59 | console.log(report.getResults()); 60 | }, 61 | total: ((argv.max - argv.min) / argv.by + 1) * report.getDprArraySize() 62 | }); 63 | } 64 | 65 | await report.start(); 66 | 67 | await report.iterate(argv._.pop(), function() { 68 | if (bar) { 69 | bar.tick(); 70 | } 71 | }); 72 | 73 | if (!bar) { 74 | await report.finish(); 75 | console.log(report.getResults()); 76 | } 77 | } catch (e) { 78 | console.log("Error!", e); 79 | } 80 | })(); 81 | -------------------------------------------------------------------------------- /test/ImageTest.js: -------------------------------------------------------------------------------- 1 | import test from "ava"; 2 | import Image from "../src/Image"; 3 | 4 | test("getStatsAtViewportWidth", t => { 5 | let img = new Image(); 6 | t.deepEqual(img.getViewportSizes(), []); 7 | img.addStats( 320, [200,100], [100,50]); 8 | t.deepEqual(img.getViewportSizes(), [320]); 9 | t.truthy(img.getStatsAtViewportWidth(320)); 10 | t.falsy(img.getStatsAtViewportWidth(340)); 11 | 12 | img.addStats( 340, [200,100], [100,50]); 13 | t.deepEqual(img.getViewportSizes(), [320, 340]); 14 | t.truthy(img.getStatsAtViewportWidth(340)); 15 | }); 16 | 17 | test("getEfficiencyAtViewportWidth", t => { 18 | let img = new Image(); 19 | img.addStats( 320, [100,50], [200,100]); 20 | t.is(img.getEfficiencyAtViewportWidth(320), 2); 21 | 22 | img.addStats( 340, [200,100], [100,50]); 23 | t.is(img.getEfficiencyAtViewportWidth(340), 0.5); 24 | }); 25 | 26 | test("getEfficiency", t => { 27 | let img = new Image(); 28 | img.addStats( 320, [100,50], [200,100]); 29 | let efficiency = img.getStats(); 30 | t.is(efficiency.length, 1); 31 | 32 | t.is(efficiency[0].efficiency, 2); 33 | 34 | img.addStats( 340, [100,50], [200,100]); 35 | efficiency = img.getStats(); 36 | 37 | t.is(efficiency.length, 2); 38 | t.is(efficiency[0].efficiency, 2); 39 | t.is(efficiency[1].efficiency, 2); 40 | }); -------------------------------------------------------------------------------- /test/ericportis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/images/fam-320.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/imaging-heap/97c9a6d327b238ee00f431a292da97f080253274/test/images/fam-320.jpg -------------------------------------------------------------------------------- /test/images/fam-480.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/imaging-heap/97c9a6d327b238ee00f431a292da97f080253274/test/images/fam-480.jpg -------------------------------------------------------------------------------- /test/images/fam-960.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/imaging-heap/97c9a6d327b238ee00f431a292da97f080253274/test/images/fam-960.jpg -------------------------------------------------------------------------------- /test/images/fam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/imaging-heap/97c9a6d327b238ee00f431a292da97f080253274/test/images/fam.jpg -------------------------------------------------------------------------------- /test/images/sunset-1280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/imaging-heap/97c9a6d327b238ee00f431a292da97f080253274/test/images/sunset-1280.jpg -------------------------------------------------------------------------------- /test/images/sunset-320.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/imaging-heap/97c9a6d327b238ee00f431a292da97f080253274/test/images/sunset-320.jpg -------------------------------------------------------------------------------- /test/images/sunset-3264.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/imaging-heap/97c9a6d327b238ee00f431a292da97f080253274/test/images/sunset-3264.jpg -------------------------------------------------------------------------------- /test/images/sunset-640.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/imaging-heap/97c9a6d327b238ee00f431a292da97f080253274/test/images/sunset-640.jpg -------------------------------------------------------------------------------- /test/images/sunset.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/filamentgroup/imaging-heap/97c9a6d327b238ee00f431a292da97f080253274/test/images/sunset.jpg -------------------------------------------------------------------------------- /test/img-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/img.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/picture-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/picture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/srcset-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/srcset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------