[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    [\"env\", {\n      \"modules\": false\n    }],\n    \"react\"\n  ],\n  \"env\": {\n    \"commonjs\": {\n      \"presets\": [\n        [\"env\", {\n          \"modules\": \"commonjs\"\n        }]\n      ]\n    }\n  }\n}"
  },
  {
    "path": ".flowconfig",
    "content": "[ignore]\n\n[include]\n\n[libs]\n\n[lints]\n\n[options]\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nlib\nes\n.DS_Store\nyarn-error.log\npackage-lock.json\nlerna-debug.log"
  },
  {
    "path": ".prettierignore",
    "content": "src/color-thief.js"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - \"8\"\n  - \"6\"\n  - \"4\"\ndist: trusty # needs Ubuntu Trusty\nsudo: false  # no need for virtualization.\naddons:\n  chrome: stable # have Travis install chrome stable.\nscript:\n  - yarn test"
  },
  {
    "path": ".yarnclean",
    "content": "# test directories\n__tests__\ntest\ntests\npowered-test\n\n# asset directories\ndocs\ndoc\nwebsite\nimages\nassets\n\n# examples\nexample\nexamples\n\n# code coverage directories\ncoverage\n.nyc_output\n\n# build scripts\nMakefile\nGulpfile.js\nGruntfile.js\n\n# configs\n.tern-project\n.gitattributes\n.editorconfig\n.*ignore\n.eslintrc\n.jshintrc\n.flowconfig\n.documentup.json\n.yarn-metadata.json\n.*.yml\n*.yml\n\n# misc\n*.gz\n*.md\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Formidable Labs\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "[![Build Status](https://travis-ci.com/FormidableLabs/react-image-palette.svg?token=ycKCGETrX5nV3P6ePUdx&branch=master)](https://travis-ci.com/FormidableLabs/react-image-palette)\n\n<h1 align=\"center\">image-palette</h1>\n\n<h4 align=\"center\">\n  Dynamically generate accessible color palettes from images\n</h4>\n\n![image-palette demo](./screenshot.jpg)\n\nImplement adaptive UIs dynamically from any image in right in the browser. Every\npalette is parsed from the most dominant and vibrant colors in the source image,\nand guaranteed to meet the\n[WCAG contrast standard](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html)\nfor accessible color pairings.\n\n### Packages\n\nThis repository is setup as a monorepo with the following packages:\n\n* [`image-palette-core`](https://github.com/FormidableLabs/image-palette/tree/master/packages/image-palette-core) - core logic for parsing palettes from images\n* [`react-image-palette`](https://github.com/FormidableLabs/image-palette/tree/master/packages/react-image-palette) - A React adapter for `image-palette-core`\n* [`preact-image-palette`](https://github.com/FormidableLabs/image-palette/tree/master/packages/preact-image-palette) - A Preact adapter for `image-palette-core`\n\n\n## Maintenance Status\n\n**Archived:** This project is no longer maintained by Formidable. We are no longer responding to issues or pull requests unless they relate to security concerns. We encourage interested developers to fork this project and make it their own!\n"
  },
  {
    "path": "demo/.gitignore",
    "content": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# testing\n/coverage\n\n# production\n/build\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "demo/package.json",
    "content": "{\n  \"name\": \"demo\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"dependencies\": {\n    \"react\": \"^16.0.0\",\n    \"react-dom\": \"^16.0.0\",\n    \"react-image-palette\": \"^0.1.3\",\n    \"react-scripts\": \"1.0.13\",\n    \"react-spinkit\": \"^3.0.0\"\n  },\n  \"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test --env=jsdom\",\n    \"eject\": \"react-scripts eject\",\n    \"deploy\": \"yarn build && surge ./build -d react-image-palette-demo.surge.sh\"\n  }\n}\n"
  },
  {
    "path": "demo/public/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta name=\"theme-color\" content=\"#000000\">\n    <!--\n      manifest.json provides metadata used when your web app is added to the\n      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/\n    -->\n    <link rel=\"manifest\" href=\"%PUBLIC_URL%/manifest.json\">\n    <link rel=\"shortcut icon\" href=\"%PUBLIC_URL%/favicon.ico\">\n    <link href=\"https://fonts.googleapis.com/css?family=Nunito:400,600\" rel=\"stylesheet\">\n    \n    <!--\n      Notice the use of %PUBLIC_URL% in the tags above.\n      It will be replaced with the URL of the `public` folder during the build.\n      Only files inside the `public` folder can be referenced from the HTML.\n\n      Unlike \"/favicon.ico\" or \"favicon.ico\", \"%PUBLIC_URL%/favicon.ico\" will\n      work correctly both with client-side routing and a non-root public URL.\n      Learn how to configure a non-root public URL by running `npm run build`.\n    -->\n    <title>React App</title>\n  </head>\n  <body>\n    <noscript>\n      You need to enable JavaScript to run this app.\n    </noscript>\n    <div id=\"root\"></div>\n    <!--\n      This HTML file is a template.\n      If you open it directly in the browser, you will see an empty page.\n\n      You can add webfonts, meta tags, or analytics to this file.\n      The build step will place the bundled scripts into the <body> tag.\n\n      To begin the development, run `npm start` or `yarn start`.\n      To create a production bundle, use `npm run build` or `yarn build`.\n    -->\n  </body>\n</html>\n"
  },
  {
    "path": "demo/public/manifest.json",
    "content": "{\n  \"short_name\": \"React App\",\n  \"name\": \"Create React App Sample\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"start_url\": \"./index.html\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#000000\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "demo/src/App.js",
    "content": "import React, { Component } from \"react\";\nimport ImagePaletteProvider from \"react-image-palette\";\nimport Spinner from \"react-spinkit\";\nimport \"./main.css\";\n\nconst getAPIURL = artist =>\n  `http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=${encodeURIComponent(\n    artist.toLowerCase()\n  )}&api_key=ff4b76a7c5f5029f2196a4ae65468679&format=json`;\n\nclass App extends Component {\n  state = { albums: [], artist: \"Com Truise\", loading: true };\n\n  componentWillMount() {\n    this.mounted = true;\n    this.updateSearch();\n  }\n\n  updateArist = event => this.setState({ artist: event.target.value });\n\n  updateSearch = () => {\n    const { artist } = this.state;\n    this.setState({ albums: [] });\n    fetch(getAPIURL(this.state.artist))\n      .then(res => res.json())\n      .then(json =>\n        this.setState({\n          albums: json.topalbums ? json.topalbums.album : [],\n          loading: false\n        })\n      );\n  };\n\n  getLargestImageUrl(images) {\n    const image = images && (images[3] || images[2] || images[1] || images[0]);\n    return image ? image[\"#text\"] : null;\n  }\n\n  handleKeyDown = event => {\n    if (event.key === \"Enter\") {\n      this.updateSearch();\n    }\n  };\n\n  render() {\n    const { albums, loading } = this.state;\n    const renderedAlbums = albums.map(album => {\n      const image = this.getLargestImageUrl(album.image);\n      if (!image) {\n        return null;\n      }\n      return (\n        <ImagePaletteProvider crossOrigin image={image} key={image}>\n          {({ backgroundColor, color, alternativeColor }) => (\n            <div className=\"album\" style={{ backgroundColor, color }}>\n              <h1>{album.name}</h1>\n              <a href={album.url} style={{ color: alternativeColor }}>\n                Learn More\n              </a>\n              <br />\n              <img src={image} />\n            </div>\n          )}\n        </ImagePaletteProvider>\n      );\n    });\n    return (\n      <div className=\"container\">\n        <a\n          className=\"github-ribbon\"\n          href=\"https://github.com/FormidableLabs/react-image-palette/\"\n        >\n          <img\n            style={{ position: \"absolute\", top: 0, right: 0, border: 0 }}\n            src=\"https://camo.githubusercontent.com/38ef81f8aca64bb9a64448d0d70f1308ef5341ab/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6461726b626c75655f3132313632312e706e67\"\n            alt=\"Fork me on GitHub\"\n            data-canonical-src=\"https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png\"\n          />\n        </a>\n        <h1 className=\"title\">\n          <a href=\"https://github.com/FormidableLabs/react-image-palette\">\n            <pre>react-image-palette</pre>\n          </a>\n        </h1>\n        <p>\n          Search for an artist to see generated color palettes for all the album\n          art in their discog.\n        </p>\n        <br />\n        <div className=\"search--container\">\n          <input\n            spellCheck={false}\n            className=\"search--input\"\n            value={this.state.artist}\n            onChange={this.updateArist}\n            onKeyDown={this.handleKeyDown}\n          />\n          <button className=\"search--button\" onClick={this.updateSearch}>\n            Search\n          </button>\n        </div>\n        <div className=\"album--container\">\n          {loading && (\n            <div className=\"spinner--container\">\n              <Spinner name=\"ball-spin-fade-loader\" />\n            </div>\n          )}\n          {!loading && renderedAlbums}\n        </div>\n      </div>\n    );\n  }\n}\n\nexport default App;\n"
  },
  {
    "path": "demo/src/index.js",
    "content": "import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport App from \"./App\";\n\nReactDOM.render(<App />, document.getElementById(\"root\"));\n"
  },
  {
    "path": "demo/src/main.css",
    "content": ".container {\n  font-family: 'Nunito', sans-serif;\n  text-align: center;\n  padding: 5px;\n}\n\n.title {\n  font-size: 20px;\n  margin-bottom: 0px;\n}\n\n.search--container {\n  display: flex;\n  justify-content: center;\n  margin-bottom: 20px;\n  max-width: 362px;\n  margin: 10px auto;\n}\n\n.search--input {\n  box-sizing: border-box;\n  height: 40px;\n  font-size: 16px;\n  padding: 10px;\n  border: 1px solid #efefef;\n  outline: none;\n  width: 100%;\n}\n\n.search--button {\n  background-color: #37474f;\n  color: #ffffff;\n  font-size: 14px;\n  border: none;\n  width: 20%;\n}\n\n.album--container {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: center;\n}\n\n.album {\n  box-sizing: border-box;\n  width: 100%;\n  max-width: 400px;\n  padding: 30px;\n  margin: 10px 0px;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n}\n\n.album img {\n  width: 280px;\n}\n\n.spinner--container {\n  display: flex;\n  align-items: center;\n  height: 200px;\n}\n\n.github-ribbon {\n  display: none;\n}\n\n\n\n@media screen and (min-width: 350px) {\n  .title {\n    font-size: 25px;\n  }\n}\n\n@media screen and (min-width: 768px) {\n  .title {\n    font-size: 35px;\n  }\n  .album {\n    width: 400px;\n    margin: 20px;\n  }\n  .album img {\n    width: 300px;\n  }\n  .github-ribbon {\n    display: block;\n  }\n}\n\n@media screen and (min-width: 1024px) {\n  .title {\n    font-size: 40px;\n  }\n}"
  },
  {
    "path": "karma.conf.js",
    "content": "module.exports = config => {\n  config.set({\n    // client: {\n    //   mocha: {\n    //     timeout: 6000\n    //   }\n    // },\n    frameworks: ['mocha', 'chai'],\n    files: ['./packages/**/__tests__/**/*.js'],\n    preprocessors: {\n      './packages/**/__tests__/**/*.js': ['webpack']\n    },\n    webpack: {\n      module: {\n        loaders: [\n          {\n            test: /\\.png$/,\n            loader: 'url-loader'\n          }\n        ]\n      }\n    },\n    webpackMiddleware: {\n      quiet: true\n    },\n    reporters: ['mocha'],\n    port: 9876,\n    colors: true,\n    logLevel: config.LOG_INFO,\n    browsers: ['ChromeHeadless'],\n    autoWatch: false,\n    concurrency: Infinity\n  });\n};\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"lerna\": \"2.1.2\",\n  \"packages\": [\n    \"packages/*\"\n  ],\n  \"version\": \"independent\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n   \"name\": \"image-palette\",\n   \"private\": true,\n   \"scripts\": {\n     \"test\": \"lerna run test\",\n     \"build\": \"lerna run build\"\n   },\n   \"devDependencies\": {\n      \"babel-cli\": \"^6.26.0\",\n      \"babel-preset-env\": \"^1.6.0\",\n      \"babel-preset-react\": \"^6.24.1\",\n      \"chai\": \"^4.1.2\",\n      \"flow-bin\": \"^0.55.0\",\n      \"karma\": \"^1.7.1\",\n      \"karma-chai\": \"^0.1.0\",\n      \"karma-chrome-launcher\": \"^2.2.0\",\n      \"karma-mocha\": \"^1.3.0\",\n      \"karma-mocha-reporter\": \"^2.2.4\",\n      \"karma-webpack\": \"^2.0.4\",\n      \"lerna\": \"^2.1.2\",\n      \"mocha\": \"^3.5.3\",\n      \"prettier\": \"^1.7.2\",\n      \"rimraf\": \"^2.6.2\",\n      \"url-loader\": \"^0.6.2\",\n      \"webpack\": \"^3.8.1\"\n   }\n}\n"
  },
  {
    "path": "packages/image-palette-core/.npmignore",
    "content": "/*\n!/lib\n!/es\n!/src\n!LICENSE.txt\n!README.md\n!package.json"
  },
  {
    "path": "packages/image-palette-core/README.md",
    "content": "# image-palette-core\n\nThe core logic for parsing a palette from image data. You can use this if you want an imperative\nAPI for generating a palette. If you want to use it with React you can use [`react-image-palette`](https://github.com/FormidableLabs/image-palette/tree/master/packages/react-image-palette)\n\n### Install\n\n```\nnpm install --save image-palette-core\n```\n\n## Usage\n\nThe main export of the package is a `getImagePalette` function which takes an image and returns an accessible color palette representing the most dominant colors in the image.\n\n```js\nimport getImagePalette from 'image-palette-core'\n\nconst img = new Image();\nimg.src = 'foo.jpg';\nimg.onload = function() {\n  // The image *must* be loaded before calling `getImagePalette`\n  const palette = getImagePalette(img);\n}\n```\n\n> ⚠️ Keep in mind that the image will be loaded into a canvas and parsed as data, so you should only use images from trusted origins. \n\n### The Palette\n\nThe parsed palette will have the following shape:\n\n```\ntype Palette = {\n  backgroundColor: String,\n  color: String,\n  alternativeColor: String\n}\n```\n\n* `backgroundColor` will be the most dominant color in the image.\n* `color` will be the color that looks the best overlayed over `backgroundColor`. \n* `alternativeColor` will be the second best color. If there are only two colors parsed, it will default to `color`.\n\nBoth `alternativeColor` and `color` are guaranteed to meet the minimum contrast ratio requirements when overlayed with `backgroundColor`, but overlaying `color` on `alternativeColor` (or vice-versa) is a bad idea as they will often have very similar contrast levels.\n"
  },
  {
    "path": "packages/image-palette-core/__tests__/test.js",
    "content": "import getImagePalette from \"../lib\";\n// Album cover for Com Truise - Fairlight\nimport testImage from \"./fairlight.png\";\n\ndescribe(\"image-palette-core\", () => {\n  describe(\"provider\", () => {\n    it(\"should parse a palette from an image\", done => {\n      const img = new Image();\n      img.src = testImage;\n      img.onload = () => {\n        const palette = getImagePalette(img);\n        expect(palette.backgroundColor).to.equal(\"rgb(60, 16, 32)\");\n        expect(palette.color).to.equal(\"#EF4E2E\");\n        expect(palette.alternativeColor).to.equal(\"#D17872\");\n        done();\n      };\n    });\n  });\n});\n"
  },
  {
    "path": "packages/image-palette-core/package.json",
    "content": "{\n  \"name\": \"image-palette-core\",\n  \"version\": \"0.2.2\",\n  \"description\": \"Create ARIA-compliant color themes based on any image.\",\n  \"main\": \"lib/index.js\",\n  \"module\": \"es/index.js\",\n  \"jsnext:main\": \"es/index.js\",\n  \"author\": \"Brandon Dail\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"build\": \"npm run clean && npm run build:es && npm run build:lib\",\n    \"build:es\": \"BABEL_ENV=es babel src --out-dir es\",\n    \"build:lib\": \"BABEL_ENV=commonjs babel src --out-dir lib\",\n    \"clean\": \"rimraf es lib\",\n    \"preversion\": \"npm run test\",\n    \"test:only\": \"karma start --single-run --browsers ChromeHeadless ../../karma.conf.js\",\n    \"test\": \"npm run build && npm run test:only\"\n  },\n  \"dependencies\": {\n    \"color\": \"^2.0.0\",\n    \"lodash.isequal\": \"^4.5.0\",\n    \"lodash.sortby\": \"^4.7.0\",\n    \"lodash.uniq\": \"^4.5.0\",\n    \"lodash.uniqby\": \"^4.7.0\"\n  }\n}\n"
  },
  {
    "path": "packages/image-palette-core/src/color-thief.js",
    "content": "/*\n * Color Thief v2.0\n * by Lokesh Dhakar - http://www.lokeshdhakar.com\n *\n * License\n * -------\n * Creative Commons Attribution 2.5 License:\n * http://creativecommons.org/licenses/by/2.5/\n *\n * Thanks\n * ------\n * Nick Rabinowitz - For creating quantize.js.\n * John Schulz - For clean up and optimization. @JFSIII\n * Nathan Spady - For adding drag and drop support to the demo page.\n *\n */\n\n\n/*\n  CanvasImage Class\n  Class that wraps the html image element and canvas.\n  It also simplifies some of the canvas context manipulation\n  with a set of helper functions.\n*/\n\nvar iAmOnNode = false;\nvar Canvas;\nvar Image;\nvar fs;\nif ( !!process && process.execPath ) {\n    iAmOnNode = true;\n}\n// if (iAmOnNode) {\n//   Canvas = require('canvas');\n//   Image = Canvas.Image;\n//   fs = require('fs');\n// }\n\nvar vboxColorMap = {};\n\nvar CanvasImage = function (image) {\n    // in node we use strings as path to an image\n    // whereas in the browser we use an image element\n    if (iAmOnNode) {\n      this.canvas = new Canvas()\n      var img = new Image;\n\n      if(image instanceof Buffer) {\n        img.src = image\n      }else{\n        img.src = fs.readFileSync(image);\n      }\n\n    } else {\n      this.canvas = document.createElement('canvas');\n      document.body.appendChild(this.canvas);\n      var img = image;\n    }\n    \n    this.context = this.canvas.getContext('2d');\n\n    this.width  = this.canvas.width  = img.width;\n    this.height = this.canvas.height = img.height;\n\n    this.context.drawImage(img, 0, 0, this.width, this.height);\n};\n\nCanvasImage.prototype.clear = function () {\n    this.context.clearRect(0, 0, this.width, this.height);\n};\n\nCanvasImage.prototype.update = function (imageData) {\n    this.context.putImageData(imageData, 0, 0);\n};\n\nCanvasImage.prototype.getPixelCount = function () {\n    return this.width * this.height;\n};\n\nCanvasImage.prototype.getImageData = function () {\n    return this.context.getImageData(0, 0, this.width, this.height);\n};\n\nCanvasImage.prototype.removeCanvas = function () {\n  if (this.canvas.parentNode) {\n    this.canvas.parentNode.removeChild(this.canvas);\n  }\n};\n\n\nvar ColorThief = function () {};\n\n/*\n * getColor(sourceImage[, quality])\n * returns {r: num, g: num, b: num}\n *\n * Use the median cut algorithm provided by quantize.js to cluster similar\n * colors and return the base color from the largest cluster.\n *\n * Quality is an optional argument. It needs to be an integer. 0 is the highest quality settings.\n * 10 is the default. There is a trade-off between quality and speed. The bigger the number, the\n * faster a color will be returned but the greater the likelihood that it will not be the visually\n * most dominant color.\n *\n * */\nColorThief.prototype.getColor = function(sourceImage, quality, allowWhite) {\n    // control if second parameter is allowWhite\n    if (quality === true || quality === false) {\n      allowWhite = quality;\n      quality = undefined;\n    }\n  \n    var palette       = this.getPalette(sourceImage, 5, quality, allowWhite);\n    var dominantColor = palette[0];\n    return dominantColor;\n};\n\n\n/*\n * getPalette(sourceImage[, colorCount, quality])\n * returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...]\n *\n * Use the median cut algorithm provided by quantize.js to cluster similar colors.\n *\n * colorCount determines the size of the palette; the number of colors returned. If not set, it\n * defaults to 10.\n *\n * BUGGY: Function does not always return the requested amount of colors. It can be +/- 2.\n *\n * quality is an optional argument. It needs to be an integer. 0 is the highest quality settings.\n * 10 is the default. There is a trade-off between quality and speed. The bigger the number, the\n * faster the palette generation but the greater the likelihood that colors will be missed.\n *\n *\n */\nColorThief.prototype.getPalette = function(sourceImage, colorCount, quality, allowWhite) {\n\n    if (typeof colorCount === 'undefined') {\n        colorCount = 10;\n    };\n    if (typeof quality === 'undefined') {\n        quality = 10;\n    };\n\n    // Create custom CanvasImage object\n    var image      = new CanvasImage(sourceImage);\n    var imageData  = image.getImageData();\n    var pixels     = imageData.data;\n    var pixelCount = image.getPixelCount();\n    var palette    = this.getPaletteFromPixels(pixels, pixelCount, colorCount, quality, allowWhite);\n\n    // Clean up\n    image.removeCanvas();\n\n    return palette;\n};\n\n/*\n * getPaletteFromPixels(pixels, pixelCount, colorCount, quality)\n * returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...]\n *\n * Low-level function that takes pixels and computes color palette.\n * Used by getPalette() and getColor()\n *\n */\nColorThief.prototype.getPaletteFromPixels = function(pixels, pixelCount, colorCount, quality) {\n    var allowWhite = true;\n    // Store the RGB values in an array format suitable for quantize function\n    var pixelArray = [];\n    for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {\n        offset = i * 4;\n        r = pixels[offset + 0];\n        g = pixels[offset + 1];\n        b = pixels[offset + 2];\n        a = pixels[offset + 3];\n        // If pixel is mostly opaque and not white\n        if (a >= 125) {\n            if (!(r > 255 && g > 255 && b > 255)) {\n                pixelArray.push([r, g, b]);\n            }\n        }\n    }\n\n    // Send array to quantize function which clusters values\n    // using median cut algorithm\n    var cmap    = MMCQ.quantize(pixelArray, colorCount);\n    var palette = cmap.palette();\n\n    return palette;\n}\n\n/*!\n * quantize.js Copyright 2008 Nick Rabinowitz.\n * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php\n */\n\n// fill out a couple protovis dependencies\n/*!\n * Block below copied from Protovis: http://mbostock.github.com/protovis/\n * Copyright 2010 Stanford Visualization Group\n * Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php\n */\nif (!pv) {\n    var pv = {\n        map: function(array, f) {\n          var o = {};\n          return f\n              ? array.map(function(d, i) { o.index = i; return f.call(o, d); })\n              : array.slice();\n        },\n        naturalOrder: function(a, b) {\n            return (a < b) ? -1 : ((a > b) ? 1 : 0);\n        },\n        sum: function(array, f) {\n          var o = {};\n          return array.reduce(f\n              ? function(p, d, i) { o.index = i; return p + f.call(o, d); }\n              : function(p, d) { return p + d; }, 0);\n        },\n        max: function(array, f) {\n          return Math.max.apply(null, f ? pv.map(array, f) : array);\n        }\n    };\n}\n\n\n\n/**\n * Basic Javascript port of the MMCQ (modified median cut quantization)\n * algorithm from the Leptonica library (http://www.leptonica.com/).\n * Returns a color map you can use to map original pixels to the reduced\n * palette. Still a work in progress.\n *\n * @author Nick Rabinowitz\n * @example\n\n// array of pixels as [R,G,B] arrays\nvar myPixels = [[190,197,190], [202,204,200], [207,214,210], [211,214,211], [205,207,207]\n                // etc\n                ];\nvar maxColors = 4;\n\nvar cmap = MMCQ.quantize(myPixels, maxColors);\nvar newPalette = cmap.palette();\nvar newPixels = myPixels.map(function(p) {\n    return cmap.map(p);\n});\n\n */\nvar MMCQ = (function() {\n    // private constants\n    var sigbits = 5,\n        rshift = 8 - sigbits,\n        maxIterations = 1000,\n        fractByPopulations = 0.75;\n\n    // get reduced-space color index for a pixel\n    function getColorIndex(r, g, b) {\n        return (r << (2 * sigbits)) + (g << sigbits) + b;\n    }\n\n    // Simple priority queue\n    function PQueue(comparator) {\n        var contents = [],\n            sorted = false;\n\n        function sort() {\n            contents.sort(comparator);\n            sorted = true;\n        }\n\n        return {\n            push: function(o) {\n                contents.push(o);\n                sorted = false;\n            },\n            peek: function(index) {\n                if (!sorted) sort();\n                if (index===undefined) index = contents.length - 1;\n                return contents[index];\n            },\n            pop: function() {\n                if (!sorted) sort();\n                return contents.pop();\n            },\n            size: function() {\n                return contents.length;\n            },\n            map: function(f) {\n                return contents.map(f);\n            },\n            debug: function() {\n                if (!sorted) sort();\n                return contents;\n            }\n        };\n    }\n\n    // 3d color space box\n    function VBox(r1, r2, g1, g2, b1, b2, histo) {\n        var vbox = this;\n        vbox.r1 = r1;\n        vbox.r2 = r2;\n        vbox.g1 = g1;\n        vbox.g2 = g2;\n        vbox.b1 = b1;\n        vbox.b2 = b2;\n        vbox.histo = histo;\n    }\n    VBox.prototype = {\n        volume: function(force) {\n            var vbox = this;\n            if (!vbox._volume || force) {\n                vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1));\n            }\n            return vbox._volume;\n        },\n        count: function(force) {\n            var vbox = this,\n                histo = vbox.histo;\n            if (!vbox._count_set || force) {\n                var npix = 0,\n                    i, j, k;\n                for (i = vbox.r1; i <= vbox.r2; i++) {\n                    for (j = vbox.g1; j <= vbox.g2; j++) {\n                        for (k = vbox.b1; k <= vbox.b2; k++) {\n                             var index = getColorIndex(i,j,k);\n                             npix += (histo[index] || 0);\n                        }\n                    }\n                }\n                vbox._count = npix;\n                vbox._count_set = true;\n            }\n            return vbox._count;\n        },\n        copy: function() {\n            var vbox = this;\n            return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo);\n        },\n        avg: function(force) {\n            var vbox = this,\n                histo = vbox.histo;\n            if (!vbox._avg || force) {\n                var ntot = 0,\n                    mult = 1 << (8 - sigbits),\n                    rsum = 0,\n                    gsum = 0,\n                    bsum = 0,\n                    hval,\n                    i, j, k, histoindex;\n                for (i = vbox.r1; i <= vbox.r2; i++) {\n                    for (j = vbox.g1; j <= vbox.g2; j++) {\n                        for (k = vbox.b1; k <= vbox.b2; k++) {\n                             histoindex = getColorIndex(i,j,k);\n                             hval = histo[histoindex] || 0;\n                             ntot += hval;\n                             rsum += (hval * (i + 0.5) * mult);\n                             gsum += (hval * (j + 0.5) * mult);\n                             bsum += (hval * (k + 0.5) * mult);\n                        }\n                    }\n                }\n                if (ntot) {\n                    vbox._avg = [~~(rsum/ntot), ~~(gsum/ntot), ~~(bsum/ntot)];\n                } else {\n                    vbox._avg = [\n                        ~~(mult * (vbox.r1 + vbox.r2 + 1) / 2),\n                        ~~(mult * (vbox.g1 + vbox.g2 + 1) / 2),\n                        ~~(mult * (vbox.b1 + vbox.b2 + 1) / 2)\n                    ];\n                }\n            }\n            return vbox._avg;\n        },\n        contains: function(pixel) {\n            var vbox = this,\n                rval = pixel[0] >> rshift;\n                gval = pixel[1] >> rshift;\n                bval = pixel[2] >> rshift;\n            return (rval >= vbox.r1 && rval <= vbox.r2 &&\n                    gval >= vbox.g1 && gval <= vbox.g2 &&\n                    bval >= vbox.b1 && bval <= vbox.b2);\n        }\n    };\n\n    // Color map\n    function CMap() {\n        this.vboxes = new PQueue(function(a,b) {\n            return pv.naturalOrder(\n                a.vbox.count()*a.vbox.volume(),\n                b.vbox.count()*b.vbox.volume()\n            )\n        });;\n      \n    }\n    CMap.prototype = {\n        push: function(vbox) {\n            this.vboxes.push({\n                vbox: vbox,\n                rgb: vbox.avg(),\n                count: vbox.count()\n            });\n        },\n        palette: function() {\n            return this.vboxes.map(function(vb) { return { rgb: vb.rgb, count: vb.count } });\n        },\n        size: function() {\n            return this.vboxes.size();\n        },\n        map: function(color) {\n            var vboxes = this.vboxes;\n            for (var i=0; i<vboxes.size(); i++) {\n                if (vboxes.peek(i).vbox.contains(color)) {\n                    return vboxes.peek(i).color;\n                }\n            }\n            return this.nearest(color);\n        },\n        nearest: function(color) {\n            var vboxes = this.vboxes,\n                d1, d2, pColor;\n            for (var i=0; i<vboxes.size(); i++) {\n                d2 = Math.sqrt(\n                    Math.pow(color[0] - vboxes.peek(i).color[0], 2) +\n                    Math.pow(color[1] - vboxes.peek(i).color[1], 2) +\n                    Math.pow(color[2] - vboxes.peek(i).color[2], 2)\n                );\n                if (d2 < d1 || d1 === undefined) {\n                    d1 = d2;\n                    pColor = vboxes.peek(i).color;\n                }\n            }\n            return pColor;\n        },\n        forcebw: function() {\n            // XXX: won't  work yet\n            var vboxes = this.vboxes;\n            vboxes.sort(function(a,b) { return pv.naturalOrder(pv.sum(a.color), pv.sum(b.color) )});\n\n            // force darkest color to black if everything < 5\n            var lowest = vboxes[0].color;\n            if (lowest[0] < 5 && lowest[1] < 5 && lowest[2] < 5)\n                vboxes[0].color = [0,0,0];\n\n            // force lightest color to white if everything > 251\n            var idx = vboxes.length-1,\n                highest = vboxes[idx].color;\n            if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251)\n                vboxes[idx].color = [255,255,255];\n        }\n    };\n\n    // histo (1-d array, giving the number of pixels in\n    // each quantized region of color space), or null on error\n    function getHisto(pixels) {\n        var histosize = 1 << (3 * sigbits),\n            histo = new Array(histosize),\n            index, rval, gval, bval;\n        pixels.forEach(function(pixel) {\n            rval = pixel[0] >> rshift;\n            gval = pixel[1] >> rshift;\n            bval = pixel[2] >> rshift;\n            index = getColorIndex(rval, gval, bval);\n            histo[index] = (histo[index] || 0) + 1;\n        });\n      \n        return histo;\n    }\n\n    function vboxFromPixels(pixels, histo) {\n        var rmin=1000000, rmax=0,\n            gmin=1000000, gmax=0,\n            bmin=1000000, bmax=0,\n            rval, gval, bval;\n        // find min/max\n        pixels.forEach(function(pixel) {\n            rval = pixel[0] >> rshift;\n            gval = pixel[1] >> rshift;\n            bval = pixel[2] >> rshift;\n            if (rval < rmin) rmin = rval;\n            else if (rval > rmax) rmax = rval;\n            if (gval < gmin) gmin = gval;\n            else if (gval > gmax) gmax = gval;\n            if (bval < bmin) bmin = bval;\n            else if (bval > bmax)  bmax = bval;\n        });\n        return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);\n    }\n\n    function medianCutApply(histo, vbox) {\n        if (!vbox.count()) return;\n\n        var rw = vbox.r2 - vbox.r1 + 1,\n            gw = vbox.g2 - vbox.g1 + 1,\n            bw = vbox.b2 - vbox.b1 + 1,\n            maxw = pv.max([rw, gw, bw]);\n        // only one pixel, no split\n        if (vbox.count() == 1) {\n            return [vbox.copy()]\n        }\n        /* Find the partial sum arrays along the selected axis. */\n        var total = 0,\n            partialsum = [],\n            lookaheadsum = [],\n            i, j, k, sum, index;\n        if (maxw == rw) {\n            for (i = vbox.r1; i <= vbox.r2; i++) {\n                sum = 0;\n                for (j = vbox.g1; j <= vbox.g2; j++) {\n                    for (k = vbox.b1; k <= vbox.b2; k++) {\n                        index = getColorIndex(i,j,k);\n                        sum += (histo[index] || 0);\n                    }\n                }\n                total += sum;\n                partialsum[i] = total;\n            }\n        }\n        else if (maxw == gw) {\n            for (i = vbox.g1; i <= vbox.g2; i++) {\n                sum = 0;\n                for (j = vbox.r1; j <= vbox.r2; j++) {\n                    for (k = vbox.b1; k <= vbox.b2; k++) {\n                        index = getColorIndex(j,i,k);\n                        sum += (histo[index] || 0);\n                    }\n                }\n                total += sum;\n                partialsum[i] = total;\n            }\n        }\n        else {  /* maxw == bw */\n            for (i = vbox.b1; i <= vbox.b2; i++) {\n                sum = 0;\n                for (j = vbox.r1; j <= vbox.r2; j++) {\n                    for (k = vbox.g1; k <= vbox.g2; k++) {\n                        index = getColorIndex(j,k,i);\n                        sum += (histo[index] || 0);\n                    }\n                }\n                total += sum;\n                partialsum[i] = total;\n            }\n        }\n        partialsum.forEach(function(d,i) {\n            lookaheadsum[i] = total-d\n        });\n        function doCut(color) {\n            var dim1 = color + '1',\n                dim2 = color + '2',\n                left, right, vbox1, vbox2, d2, count2=0;\n            for (i = vbox[dim1]; i <= vbox[dim2]; i++) {\n                if (partialsum[i] > total / 2) {\n                    vbox1 = vbox.copy();\n                    vbox2 = vbox.copy();\n                    left = i - vbox[dim1];\n                    right = vbox[dim2] - i;\n                    if (left <= right)\n                        d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2));\n                    else d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2));\n                    // avoid 0-count boxes\n                    while (!partialsum[d2]) d2++;\n                    count2 = lookaheadsum[d2];\n                    while (!count2 && partialsum[d2-1]) count2 = lookaheadsum[--d2];\n                    // set dimensions\n                    vbox1[dim2] = d2;\n                    vbox2[dim1] = vbox1[dim2] + 1;\n                    return [vbox1, vbox2];\n                }\n            }\n\n        }\n        // determine the cut planes\n        return maxw == rw ? doCut('r') :\n            maxw == gw ? doCut('g') :\n            doCut('b');\n    }\n\n    function quantize(pixels, maxcolors) {\n        // short-circuit\n        if (!pixels.length || maxcolors < 2 || maxcolors > 256) {\n            return new CMap();\n        }\n\n        // XXX: check color content and convert to grayscale if insufficient\n\n        var histo = getHisto(pixels),\n            histosize = 1 << (3 * sigbits);\n        // check that we aren't below maxcolors already\n        var nColors = 0;\n        histo.forEach(function() { nColors++ });\n      \n        if (nColors <= maxcolors) {\n            // XXX: generate the new colors from the histo and return\n        }\n\n        // get the beginning vbox from the colors\n        var vbox = vboxFromPixels(pixels, histo),\n            pq = new PQueue(function(a,b) { return pv.naturalOrder(a.count(), b.count()) });\n        pq.push(vbox);\n\n        // inner function to do the iteration\n        function iter(lh, target) {\n            var ncolors = 1,\n                niters = 0,\n                vbox;\n            while (niters < maxIterations) {\n                vbox = lh.pop();\n                if (!vbox.count())  { /* just put it back */\n                    lh.push(vbox);\n                    niters++;\n                    continue;\n                }\n                // do the cut\n                var vboxes = medianCutApply(histo, vbox),\n                    vbox1 = vboxes[0],\n                    vbox2 = vboxes[1];\n\n                if (!vbox1) {\n                    return;\n                }\n                lh.push(vbox1);\n                if (vbox2) {  /* vbox2 can be null */\n                    lh.push(vbox2);\n                    ncolors++;\n                }\n                if (ncolors >= target) return;\n                if (niters++ > maxIterations) {\n                    return;\n                }\n            }\n        }\n\n        // first set of colors, sorted by population\n        iter(pq, fractByPopulations * maxcolors);\n\n        // Re-sort by the product of pixel occupancy times the size in color space.\n        var pq2 = new PQueue(function(a,b) {\n            return pv.naturalOrder(a.count()*a.volume(), b.count()*b.volume())\n        });\n        while (pq.size()) {\n            pq2.push(pq.pop());\n        }\n\n        // next set - generate the median cuts using the (npix * vol) sorting.\n        iter(pq2, maxcolors - pq2.size());\n\n        // calculate the actual colors\n        var cmap = new CMap();\n        while (pq2.size()) {\n            cmap.push(pq2.pop());\n        }\n\n        return cmap;\n    }\n\n    return {\n        quantize: quantize\n    }\n})();\n\n\nmodule.exports = ColorThief;"
  },
  {
    "path": "packages/image-palette-core/src/index.js",
    "content": "// @flow\nimport uniqBy from \"lodash.uniqby\";\nimport isEqual from \"lodash.isequal\";\nimport sortBy from \"lodash.sortby\";\nimport ColorThief from \"./color-thief\";\nimport Color from \"color\";\n\ntype ColorDescriptor = {\n  color: Color,\n  score: number,\n  contrast: number\n};\n\ntype ColorPairings = Array<ColorDescriptor>;\n\ntype ColorPairingMap = {\n  [key: string]: ColorPairings\n};\n\nconst THRESHOLD_CONTRAST_RATIO = 1.0;\n// This is the minimum required for \"AA\" certification\nconst MINIMUM_CONTRAST_RATIO = 4.5;\nconst IDEAL_CONTRAST_RATIO = 7.5;\n// Track the total number of pixels, reset everytime\n// a palette is parsed.\nlet totalPixelCount = 0;\n// Track pixel count by RGB values, reset everytime\n// a palette is parsed.\nlet RGBToPixelCountMap = {};\n\n/**\n * The \"range\" is a metric used to determine how\n * vibrant a color is. It checks the delta between\n * the highest and lowest channel values, giving us an indiciation\n * of how dominant one range might be.\n * \n * @example\n * For the RGB value [250, 30, 10] we can see\n * that the red channel dominates, meaning it will be\n * primarily red.\n */\nfunction getRGBRange(color: Color) {\n  var rgb = sortBy(color.rgb().array()).reverse();\n  var [max, med, min] = rgb;\n  return (max - min) / 10;\n}\n\n/**\n * Returns a value between 0 and 1 representing how\n * many pixels in the original image are represented\n * by this color, 0 meaning none and 1 meaning all.\n * @param {*} color \n * @returns {number}\n */\nfunction getPixelDominance(color: Color) {\n  const pixelCount: number = RGBToPixelCountMap[color];\n  return pixelCount / totalPixelCount;\n}\n\n/**\n * Calculates the total score for each color\n * in an array of pairs which are matches for some\n * dominant color. Score represents viability, so higher is better.\n * @param {*} pairs \n * @returns {number}\n */\nfunction calculateTotalPairScore(pairs) {\n  return pairs.reduce((score, color) => score + color.score, 0);\n}\n\n/**\n * Return a new array of pairs, sorted by score.\n * @param {*} pairs \n */\nfunction sortPairsByScore(pairs: ColorPairings) {\n  pairs.sort((a, b) => {\n    if (a.score === b.score) {\n      return 0;\n    }\n    return a.score > b.score ? -1 : 1;\n  });\n}\n\n/**\n * Dominance is ranked using a system that weighs\n * each dominant color by three factors:\n *  - The true dominance of the color in the original image.\n *    This is determined by tracking the total number of pixels\n *    in the image, and the total pixels found for an image,\n *    and dividing the color pixels by the total.\n * \n *  - The number of valid color pairs. If a color has no matching\n *    pairs it is given a score of zero, since we can't build\n *    a color palette with a single color.\n * \n *  - The total score of each matching color. Each color is scored\n *    based on the contrast with the dominant color, the dominance\n *    in the original image, and its \"range\", which is the difference\n *    between the highest channel value and lowest, which sort of measures\n *    vibrance.\n * @param {*} WCAGCompliantColorPairs \n */\nfunction getMostDominantPrimaryColor(WCAGCompliantColorPairs: ColorPairingMap) {\n  var highestDominanceScore = 0;\n  var mostDominantColor = \"\";\n  for (var dominantColor in WCAGCompliantColorPairs) {\n    var pairs = WCAGCompliantColorPairs[dominantColor];\n    var dominance = getPixelDominance(dominantColor);\n    var totalPairScore = calculateTotalPairScore(pairs);\n    var score = pairs.length ? (pairs.length + totalPairScore) * dominance : 0;\n    if (score > highestDominanceScore) {\n      highestDominanceScore = score;\n      mostDominantColor = dominantColor;\n    }\n  }\n  sortPairsByScore(WCAGCompliantColorPairs[mostDominantColor]);\n  return mostDominantColor;\n}\n\n/**\n * Gets the palette for an image from color-thief and maps\n * the colors to Color instances. It also re-initializes\n * and updates the RGBToPixelCountMap so we get a fresh\n * dataset for pixel dominance\n * @param {string} image \n * @param {ColorThief} colorThief \n */\nfunction getColorPalette(image, colorThief: ColorThief): Array<Color> {\n  totalPixelCount = 0;\n  RGBToPixelCountMap = {};\n  return colorThief.getPalette(image).map(color => {\n    var colorWrapper = new Color(color.rgb);\n    RGBToPixelCountMap[colorWrapper] = color.count;\n    totalPixelCount += color.count;\n    return colorWrapper;\n  });\n}\n\n/**\n * The main export that takes an image URI and optional instance\n * of color-thief and generates the palettes. Responsible for\n * building the WCAGCompliantColorPairs map and generating\n * accessible color pairings.\n * \n * Returns an object with a backgroundColor, color, and alternativeColor.\n * If there's only one potential color pairing, alternativeColor will\n * just be color.\n * @param {*} image \n * @param {*} colorThief \n */\nexport default function getImagePalette(image: string, colorThief: ColorThief) {\n  colorThief = colorThief || new ColorThief();\n  var palettes = getColorPalette(image, colorThief);\n  var highestMatchCount = 0;\n  var WCAGCompliantColorPairs: ColorPairingMap = {};\n  palettes.forEach((dominantColor, index) => {\n    var pairs = (WCAGCompliantColorPairs[dominantColor] = []);\n    palettes.forEach(color => {\n      var contrast = dominantColor.contrast(color);\n      if (contrast > THRESHOLD_CONTRAST_RATIO) {\n        /**\n         * The score is determined based three things:\n         * \n         *  contrast:\n         *     how well contrasted the color is with the dominant color.\n         *  dominance:\n         *    the level of dominance of the dominant color\n         *    which is based on the index of the color in\n         *    the palette array.\n         *   range/vibrance:\n         *    we want some vibrant colors\n         */\n        var range = getRGBRange(color);\n        /**0\n         * If the contrast isn't high enough, lighten/darken\n         * the color so that we get a more accessible\n         * version of the color.\n         */\n        if (contrast < MINIMUM_CONTRAST_RATIO) {\n          var delta = (MINIMUM_CONTRAST_RATIO - contrast) / 20;\n          var lighten = dominantColor.dark();\n          while (contrast < MINIMUM_CONTRAST_RATIO) {\n            var newColor = lighten ? color.lighten(delta) : color.darken(delta);\n            // If the new color is the same as the old one, we're not getting any\n            // lighter or darker so we need to stop.\n            if (newColor.hex() === color.hex()) {\n              break;\n            }\n            var newContrast = dominantColor.contrast(newColor);\n            // If the new contrast is lower than the old contrast\n            // then we need to start moving the other direction in the spectrum\n            if (newContrast < contrast) {\n              lighten = !lighten;\n            }\n            color = newColor;\n            contrast = newContrast;\n          }\n        }\n        var score = contrast + range;\n        pairs.push({ color, score, contrast });\n      }\n    });\n  });\n  var backgroundColor = getMostDominantPrimaryColor(WCAGCompliantColorPairs);\n  var [color, alternativeColor, accentColor] = WCAGCompliantColorPairs[\n    backgroundColor\n  ];\n  if (!alternativeColor) {\n    alternativeColor = color;\n  }\n  if (!accentColor) {\n    accentColor = alternativeColor;\n  }\n  return {\n    backgroundColor,\n    color: color.color.hex(),\n    alternativeColor: alternativeColor.color.hex()\n  };\n}\n"
  },
  {
    "path": "packages/preact-image-palette/.babelrc",
    "content": "{\n  \"presets\": [\n    [\"env\", {\n      \"modules\": false\n    }]\n  ],\n  \"env\": {\n    \"commonjs\": {\n      \"presets\": [\n        [\"env\", {\n          \"modules\": \"commonjs\"\n        }]\n      ]\n    }\n  }\n}"
  },
  {
    "path": "packages/preact-image-palette/.npmignore",
    "content": "/*\n!/lib\n!/es\n!/src\n!LICENSE.txt\n!README.md\n!package.json"
  },
  {
    "path": "packages/preact-image-palette/README.md",
    "content": "# preact-image-palette\n\n\nA Preact adpater for [`image-palette-core`](https://github.com/FormidableLabs/image-palette/tree/master/packages/image-palette-core)\n\n\n### Install\n\n```\nnpm install --save preact-image-palette\n```\n\n## Usage\n\nThe main export of the package is the `ImagePalette` component, which uses a [render callback](https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce) to provide the color palette after the image is parsed.\n\n```jsx\nimport ImagePalette from 'preact-image-palette'\n\nconst SomeComponent = ({ image }) => (\n  <ImagePalette image={image}>\n    {({ backgroundColor, color, alternativeColor }) => (\n      <div style={{ backgroundColor, color }}>\n        This div has been themed based on\n        <span style={{ color: alternativeColor }}>{image}</span>\n      </div>\n    )}\n  </ImagePalette>\n)\n```\n\n\n## API\n\n### Palette\n\nSee the [`image-palette-core` documentation](https://github.com/FormidableLabs/tree/master/packages/image-palette-core#the-palette)\n\n### Props\n\nProperty  \t| \tType\t\t|\t  Description\n:-----------------------|:--------------|:--------------------------------\n`image` |   `String!` |  The URL for the image to parse.\n`crossOrigin` | `Boolean` | Sets the `crossOrigin` property on the `Image` instance that loads the source image <sup>1</sup>\n`render` | `Palette => ReactElement` | If you prefer to use a `render` prop over a function child, go for it! `react-image-palette` supports both.\n`defaults` | `Palette` | A default palette to render if a palette cannot be parsed. This would typically occur when the source image fails to load\n\n> <sup>1</sup> ⚠️ Keep in mind that the image will be loaded into a canvas and parsed as data, so you should only use images from trusted origins. \n"
  },
  {
    "path": "packages/preact-image-palette/__tests__/test.js",
    "content": "import {h, render, Component} from 'preact'\nimport PreactImagePalette from \"../lib\";\n// Album cover for Com Truise - Fairlight\nimport testImage from \"./fairlight.png\";\n\nclass TestComponent extends Component {\n  render() {\n    const { render } = this.props;\n    return h(ReactImagePalette, { image: testImage }, render);\n  }\n}\n\nlet container;\n\nconst renderWithExpect = (done, palette) => {\n  expect(palette.backgroundColor).to.equal(\"rgb(60, 16, 32)\");\n  expect(palette.color).to.equal(\"#EF4E2E\");\n  expect(palette.alternativeColor).to.equal(\"#D17872\");\n  done();\n  // Return null so React doesn't throw an error\n  // about an empty return\n  return null;\n};\n\ndescribe(\"preact-image-palette\", () => {\n  beforeEach(() => {\n    container = document.createElement(\"div\");\n  });\n  describe(\"provider\", () => {\n    it(\"should parse a palette from an image\", done => {\n      render(\n        h(PreactImagePalette, {\n          image: testImage,\n          render: renderWithExpect.bind(this, done)\n        }),\n        container\n      );\n    });\n    it(\"should render defaults if the image fails to load\", done => {\n      const defaults = {\n        backgroundColor: \"red\",\n        color: \"white\",\n        alternativeColor: \"blue\"\n      };\n      render(\n        h(PreactImagePalette, {\n          image: \"unknown-image.gif\",\n          defaults,\n          render: palette => {\n            expect(palette).to.equal(defaults);\n            done();\n            return null;\n          }\n        }),\n        container\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/preact-image-palette/package.json",
    "content": "{\n  \"name\": \"preact-image-palette\",\n  \"version\": \"0.1.1\",\n  \"description\": \"Create ARIA-compliant color themes based on any image.\",\n  \"main\": \"lib/index.js\",\n  \"module\": \"es/index.js\",\n  \"jsnext:main\": \"es/index.js\",\n  \"author\": \"Brandon Dail\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"build\": \"npm run clean && npm run build:es && npm run build:lib\",\n    \"build:es\": \"BABEL_ENV=es babel src --out-dir es\",\n    \"build:lib\": \"BABEL_ENV=commonjs babel src --out-dir lib\",\n    \"clean\": \"rimraf es lib\",\n    \"preversion\": \"npm run test\",\n    \"test:only\": \"karma start --single-run --browsers ChromeHeadless ../../karma.conf.js\",\n    \"test\": \"npm run build && npm run test:only\"\n  },\n  \"dependencies\": {\n    \"image-palette-core\": \"^0.2.2\"\n  },\n  \"devDependencies\": {\n    \"preact\": \"^8.0.0\"\n  },\n  \"peerDependencies\": {\n    \"preact\": \"^8.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/preact-image-palette/src/index.js",
    "content": "import ImagePaletteProvider from \"./provider\";\nexport default ImagePaletteProvider;\n"
  },
  {
    "path": "packages/preact-image-palette/src/provider.js",
    "content": "import {h, Component} from \"preact\";\nimport getImagePalette from \"image-palette-core\";\n\nexport default class ImagePaletteProvider extends Component {\n  constructor(...args) {\n    super(...args);\n    this.state = { colors: null };\n    this.onImageload = this.onImageload.bind(this);\n    this.onImageError = this.onImageError.bind(this);\n  }\n\n  componentDidMount() {\n    const image = (this.image = new Image());\n    image.crossOrigin = this.props.crossOrigin;\n    image.src = this.props.image;\n    image.onload = this.onImageload;\n    image.onerror = this.onImageError;\n  }\n\n  componentWillUnmount() {\n    this.image.onload = null;\n    this.image.onerror = null;\n  }\n\n  onImageload() {\n    var image = this.image;\n    var colors = getImagePalette(this.image);\n    this.setState({ colors });\n  }\n\n  onImageError() {\n    if (this.props.defaults) {\n      this.setState({ colors: this.props.defaults });\n    }\n  }\n\n  render() {\n    const { colors } = this.state;\n    const { children, render } = this.props;\n    const callback = render || children;\n    if (!callback) {\n      throw new Error(\n        \"ImagePaletteProvider expects a render callback either as a child or via the `render` prop\"\n      );\n    }\n    return colors ? callback(colors) : null;\n  }\n}\n"
  },
  {
    "path": "packages/react-image-palette/.npmignore",
    "content": "/*\n!/lib\n!/es\n!/src\n!LICENSE.txt\n!README.md\n!package.json"
  },
  {
    "path": "packages/react-image-palette/README.md",
    "content": "# react-image-palette\n\n\nA React adpater for [`image-palette-core`](https://github.com/FormidableLabs/image-palette/tree/master/packages/image-palette-core)\n\n\n### Install\n\n```\nnpm install --save react-image-palette\n```\n\n## Usage\n\nThe main export of the package is the `ImagePalette` component, which uses a [render callback](https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce) to provide the color palette after the image is parsed.\n\n```jsx\nimport ImagePalette from 'react-image-palette'\n\nconst SomeComponent = ({ image }) => (\n  <ImagePalette image={image}>\n    {({ backgroundColor, color, alternativeColor }) => (\n      <div style={{ backgroundColor, color }}>\n        This div has been themed based on\n        <span style={{ color: alternativeColor }}>{image}</span>\n      </div>\n    )}\n  </ImagePalette>\n)\n```\n\n\n## API\n\n### Palette\n\nSee the [`image-palette-core` documentation](https://github.com/FormidableLabs/image-palette/tree/master/packages/image-palette-core#the-palette)\n\n### Props\n\nProperty  \t| \tType\t\t|\t  Description\n:-----------------------|:--------------|:--------------------------------\n`image` |   `String!` |  The URL for the image to parse.\n`crossOrigin` | `Boolean` | Sets the `crossOrigin` property on the `Image` instance that loads the source image <sup>1</sup>\n`render` | `Palette => ReactElement` | If you prefer to use a `render` prop over a function child, go for it! `react-image-palette` supports both.\n`defaults` | `Palette` | A default palette to render if a palette cannot be parsed. This would typically occur when the source image fails to load\n\n> <sup>1</sup> ⚠️ Keep in mind that the image will be loaded into a canvas and parsed as data, so you should only use images from trusted origins. \n"
  },
  {
    "path": "packages/react-image-palette/__tests__/test.js",
    "content": "import React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport ReactImagePalette from \"../lib\";\n// Album cover for Com Truise - Fairlight\nimport testImage from \"./fairlight.png\";\n\nclass TestComponent extends React.Component {\n  render() {\n    const { render } = this.props;\n    return React.createElement(ReactImagePalette, { image: testImage }, render);\n  }\n}\n\nlet container;\n\nconst renderWithExpect = (done, palette) => {\n  expect(palette.backgroundColor).to.equal(\"rgb(60, 16, 32)\");\n  expect(palette.color).to.equal(\"#EF4E2E\");\n  expect(palette.alternativeColor).to.equal(\"#D17872\");\n  done();\n  // Return null so React doesn't throw an error\n  // about an empty return\n  return null;\n};\n\ndescribe(\"react-image-palette\", () => {\n  beforeEach(() => {\n    container = document.createElement(\"div\");\n  });\n  describe(\"provider\", () => {\n    it(\"should parse a palette from an image\", done => {\n      ReactDOM.render(\n        React.createElement(ReactImagePalette, {\n          image: testImage,\n          render: renderWithExpect.bind(this, done)\n        }),\n        container\n      );\n    });\n    it(\"should render defaults if the image fails to load\", done => {\n      const defaults = {\n        backgroundColor: \"red\",\n        color: \"white\",\n        alternativeColor: \"blue\"\n      };\n      ReactDOM.render(\n        React.createElement(ReactImagePalette, {\n          image: \"unknown-image.gif\",\n          defaults,\n          render: palette => {\n            expect(palette).to.equal(defaults);\n            done();\n            return null;\n          }\n        }),\n        container\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "packages/react-image-palette/package.json",
    "content": "{\n  \"name\": \"react-image-palette\",\n  \"version\": \"0.2.4\",\n  \"description\": \"Create ARIA-compliant color themes based on any image.\",\n  \"main\": \"lib/index.js\",\n  \"module\": \"es/index.js\",\n  \"jsnext:main\": \"es/index.js\",\n  \"author\": \"Brandon Dail\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"build\": \"npm run clean && npm run build:es && npm run build:lib\",\n    \"build:es\": \"BABEL_ENV=es babel src --out-dir es\",\n    \"build:lib\": \"BABEL_ENV=commonjs babel src --out-dir lib\",\n    \"clean\": \"rimraf es lib\",\n    \"preversion\": \"npm run test\",\n    \"test:only\": \"karma start --single-run --browsers ChromeHeadless ../../karma.conf.js\",\n    \"test\": \"npm run build && npm run test:only\"\n  },\n  \"dependencies\": {\n    \"image-palette-core\": \"^0.2.2\"\n  },\n  \"devDependencies\": {\n    \"react-dom\": \"^16.1.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^15.0.0-0 || ^16.0.0-0\"\n  }\n}\n"
  },
  {
    "path": "packages/react-image-palette/src/index.js",
    "content": "import ImagePaletteProvider from \"./provider\";\nexport default ImagePaletteProvider;\n"
  },
  {
    "path": "packages/react-image-palette/src/provider.js",
    "content": "import React from \"react\";\nimport getImagePalette from \"image-palette-core\";\n\nexport default class ImagePaletteProvider extends React.Component {\n  constructor(...args) {\n    super(...args);\n    this.state = { colors: null };\n    this.onImageload = this.onImageload.bind(this);\n    this.onImageError = this.onImageError.bind(this);\n  }\n\n  componentDidMount() {\n    const image = (this.image = new Image());\n    image.crossOrigin = this.props.crossOrigin;\n    image.src = this.props.image;\n    image.onload = this.onImageload;\n    image.onerror = this.onImageError;\n  }\n\n  componentWillUnmount() {\n    this.image.onload = null;\n    this.image.onerror = null;\n  }\n\n  onImageload() {\n    var image = this.image;\n    var colors = getImagePalette(this.image);\n    this.setState({ colors });\n  }\n\n  onImageError() {\n    if (this.props.defaults) {\n      this.setState({ colors: this.props.defaults });\n    }\n  }\n\n  render() {\n    const { colors } = this.state;\n    const { children, render } = this.props;\n    const callback = render || children;\n    if (!callback) {\n      throw new Error(\n        \"ImagePaletteProvider expects a render callback either as a child or via the `render` prop\"\n      );\n    }\n    return colors ? callback(colors) : null;\n  }\n}\n"
  }
]