[{"data":1,"prerenderedAt":115},["ShallowReactive",2],{"blog-extract-dominant-colors-from-a-logo":3},{"post":4,"related":58,"previous":97,"next":99},{"slug":5,"title":6,"description":7,"excerpt":8,"date":9,"updated":10,"status":11,"author":12,"authorTitle":10,"category":13,"categoryName":14,"categoryAccent":15,"tags":16,"image":20,"cover":21,"imageAlt":10,"featured":22,"readingMinutes":23,"wordCount":24,"headings":25,"html":55,"canonicalUrl":10,"sourceFile":56,"path":57},"extract-dominant-colors-from-a-logo","The Best Way to Extract Dominant Colors From a Logo","Median cut, octree and k-means were built to compress photographs. Point them at a logo and they return three whites and a gray. Here is what works instead.","Reach for a color extraction library and you will almost certainly get median cut, usually through ColorThief or one of its ports. Feed it a photograph and it does a genuinely good job. Feed it a logo…","2026-08-22T09:00:00.000Z","","published","Colorize","color-theory","Color Theory","#E879F9",[17,18,19],"brand colors","logos","color theory","/social/blog/extract-dominant-colors-from-a-logo.png","/social/blog/extract-dominant-colors-from-a-logo-cover.png",false,6,1291,[26,30,33,36,40,43,46,49,52],{"id":27,"text":28,"level":29},"what-median-cut-optimizes-for","What median cut optimizes for",2,{"id":31,"text":32,"level":29},"why-photos-and-logos-are-opposite-inputs","Why photos and logos are opposite inputs",{"id":34,"text":35,"level":29},"four-specific-failure-modes","Four specific failure modes",{"id":37,"text":38,"level":39},"the-background-wins","The background wins",3,{"id":41,"text":42,"level":39},"anti-aliasing-outvotes-the-real-color","Anti-aliasing outvotes the real color",{"id":44,"text":45,"level":39},"alpha-gets-thrown-away","Alpha gets thrown away",{"id":47,"text":48,"level":39},"resizing-invents-colors","Resizing invents colors",{"id":50,"text":51,"level":29},"what-works-better","What works better",{"id":53,"text":54,"level":29},"screenshots-are-the-other-case","Screenshots are the other case","\u003Cp>Reach for a color extraction library and you will almost certainly get median cut, usually through ColorThief or one of its ports. Feed it a photograph and it does a genuinely good job. Feed it a logo and it hands back something like this:\u003C/p>\n\u003Cdiv class=\"swatch-row\">\n  \u003Cspan class=\"swatch\" style=\"--swatch: #FFFFFF; --swatch-ink: #030712\">#FFFFFF\u003C/span>\n  \u003Cspan class=\"swatch\" style=\"--swatch: #FAFAFA; --swatch-ink: #030712\">#FAFAFA\u003C/span>\n  \u003Cspan class=\"swatch\" style=\"--swatch: #EEF1F5; --swatch-ink: #030712\">#EEF1F5\u003C/span>\n  \u003Cspan class=\"swatch\" style=\"--swatch: #A8BEDC; --swatch-ink: #030712\">#A8BEDC\u003C/span>\n\u003C/div>\n\u003Cp>For a logo that is a solid blue mark on a white background. The blue is missing, and in its place is a washed out tint that appears nowhere in the file. This is not a bug in the library. It is the algorithm doing exactly what it was designed to do, on an input it was never designed for.\u003C/p>\n\u003Ch2 id=\"what-median-cut-optimizes-for\">\u003Ca class=\"heading-anchor\" href=\"#what-median-cut-optimizes-for\" aria-label=\"Link to this section\">#\u003C/a>What median cut optimizes for\u003C/h2>\n\u003Cp>Median cut comes from Paul Heckbert's 1982 work on displaying continuous tone images on hardware limited to 256 colors. The problem it solves is: given millions of pixels, choose N colors that minimize the total error when every pixel is mapped to its nearest chosen color.\u003C/p>\n\u003Cp>The method is elegant. Put every pixel in one box in RGB space. Repeatedly take the box with the largest range along any axis, sort its pixels along that axis, and split it at the median. After N splits, average the pixels in each box. Octree quantization and k-means differ in mechanics but optimize the same objective.\u003C/p>\n\u003Cp>Read that objective again with a logo in mind. \u003Cstrong>Total error across all pixels.\u003C/strong> If 92% of your image is white background, then getting white exactly right is worth more to that objective than getting the brand color right at all. The algorithm is not confused. It is correctly answering a question you did not mean to ask.\u003C/p>\n\u003Cp>Splitting at the \u003Cem>median\u003C/em> is what makes it acute. The first split divides the pixel population in half. When most of the population is one flat color, the first several splits are all spent carving up that flat color into slightly different versions of itself.\u003C/p>\n\u003Ch2 id=\"why-photos-and-logos-are-opposite-inputs\">\u003Ca class=\"heading-anchor\" href=\"#why-photos-and-logos-are-opposite-inputs\" aria-label=\"Link to this section\">#\u003C/a>Why photos and logos are opposite inputs\u003C/h2>\n\u003Cdiv class=\"table-wrap\">\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth scope=\"col\">\u003C/th>\u003Cth scope=\"col\">Photograph\u003C/th>\u003Cth scope=\"col\">Logo\u003C/th>\u003C/tr>\u003C/thead>\u003Ctbody>\u003Ctr>\u003Ctd>Distinct colors\u003C/td>\u003Ctd>Hundreds of thousands\u003C/td>\u003Ctd>Often under ten, by intent\u003C/td>\u003C/tr>\u003Ctr>\u003Ctd>Distribution\u003C/td>\u003Ctd>Broad and continuous\u003C/td>\u003Ctd>A few huge spikes\u003C/td>\u003C/tr>\u003Ctr>\u003Ctd>Background\u003C/td>\u003Ctd>Part of the subject\u003C/td>\u003Ctd>Usually irrelevant\u003C/td>\u003C/tr>\u003Ctr>\u003Ctd>Transparency\u003C/td>\u003Ctd>None\u003C/td>\u003Ctd>Common and load bearing\u003C/td>\u003C/tr>\u003Ctr>\u003Ctd>Edges\u003C/td>\u003Ctd>Soft, everywhere\u003C/td>\u003Ctd>Hard, with anti-aliased ramps\u003C/td>\u003C/tr>\u003Ctr>\u003Ctd>What you want\u003C/td>\u003Ctd>A representative summary\u003C/td>\u003Ctd>The exact declared values\u003C/td>\u003C/tr>\u003C/tbody>\u003C/table>\u003C/div>\n\u003Cp>The last row is the one that matters most. For a photograph there is no correct answer sitting in the file waiting to be found; a palette is a reasonable summary and reasonable is the goal. For a logo there \u003Cem>is\u003C/em> a correct answer. Someone chose those colors, wrote them in a brand guide, and would recognize them instantly. Returning an average of them is returning a wrong answer, not an approximate one.\u003C/p>\n\u003Ch2 id=\"four-specific-failure-modes\">\u003Ca class=\"heading-anchor\" href=\"#four-specific-failure-modes\" aria-label=\"Link to this section\">#\u003C/a>Four specific failure modes\u003C/h2>\n\u003Ch3 id=\"the-background-wins\">\u003Ca class=\"heading-anchor\" href=\"#the-background-wins\" aria-label=\"Link to this section\">#\u003C/a>The background wins\u003C/h3>\n\u003Cp>Covered above, and it is the dominant failure. Any population weighted method spends its budget on whatever occupies the most pixels, and in a logo that is nearly always the background.\u003C/p>\n\u003Ch3 id=\"anti-aliasing-outvotes-the-real-color\">\u003Ca class=\"heading-anchor\" href=\"#anti-aliasing-outvotes-the-real-color\" aria-label=\"Link to this section\">#\u003C/a>Anti-aliasing outvotes the real color\u003C/h3>\n\u003Cp>Every diagonal and curve in a rasterized logo has a fringe of blended pixels between the mark and the background. On a small mark those blend pixels can outnumber the pure ones. Because they sit on a straight line in RGB space between the two real colors, they form a cluster that clustering algorithms happily discover and report as a color. That \u003Ccode>#A8BEDC\u003C/code> above is exactly this: the midpoint of the blue and the white, elected by the pixels that belong to neither.\u003C/p>\n\u003Ch3 id=\"alpha-gets-thrown-away\">\u003Ca class=\"heading-anchor\" href=\"#alpha-gets-thrown-away\" aria-label=\"Link to this section\">#\u003C/a>Alpha gets thrown away\u003C/h3>\n\u003Cp>Most quantization libraries take RGB and have nowhere to put an alpha channel. Decode a transparent PNG naively and the transparent region arrives as whatever the decoder left in those bytes, frequently black. You then get black returned as a dominant brand color for a logo with no black in it.\u003C/p>\n\u003Cp>The fix is not subtle but it does have to be explicit. Reject pixels below an alpha threshold before counting anything:\u003C/p>\n\u003Cdiv class=\"code-block\">\u003Cspan class=\"code-lang\" aria-hidden=\"true\">js\u003C/span>\u003Cpre>\u003Ccode class=\"language-js\">const { data, info } = await sharp(buffer)\n  .ensureAlpha()\n  .raw()\n  .toBuffer({ resolveWithObject: true })\n\nconst counts = new Map()\nlet opaque = 0\n\nfor (let i = 0; i &lt; data.length; i += 4) {\n  if (data[i + 3] &lt; 128) continue // transparent and semi-transparent\n  const key = `${data[i]},${data[i + 1]},${data[i + 2]}`\n  counts.set(key, (counts.get(key) || 0) + 1)\n  opaque += 1\n}\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Rejecting rather than compositing is the right call. Compositing transparent pixels onto white invents colors the brand never chose, and compositing onto black invents different ones.\u003C/p>\n\u003Ch3 id=\"resizing-invents-colors\">\u003Ca class=\"heading-anchor\" href=\"#resizing-invents-colors\" aria-label=\"Link to this section\">#\u003C/a>Resizing invents colors\u003C/h3>\n\u003Cp>Nearly every pipeline downscales before analysis, for speed. For a photograph, bilinear or Lanczos resampling is correct and harmless. For a logo it is a way of manufacturing more of the blend pixels described above, and Lanczos in particular adds ringing that produces values slightly outside the original range.\u003C/p>\n\u003Cp>If you downscale a logo, either use nearest neighbour, which preserves exact values at the cost of some aliasing, or downscale far less aggressively than you would a photo. A 256px logo analyzed at 256px is cheap.\u003C/p>\n\u003Ch2 id=\"what-works-better\">\u003Ca class=\"heading-anchor\" href=\"#what-works-better\" aria-label=\"Link to this section\">#\u003C/a>What works better\u003C/h2>\n\u003Cp>The structure that handles logos well inverts the assumption. Instead of clustering to find representative colors, count exact values and let the flat areas speak for themselves.\u003C/p>\n\u003Col>\u003Cli>\u003Cstrong>Decode to raw RGBA\u003C/strong> without heavy resampling, and reject pixels below an alpha threshold.\u003C/li>\u003Cli>\u003Cstrong>Count values with a small quantization step,\u003C/strong> not a large one. A step of four or eight collapses compression noise while keeping genuinely distinct colors apart. This is a histogram, not a clustering pass.\u003C/li>\u003Cli>\u003Cstrong>Filter achromatics\u003C/strong> conditionally. White, near-white, near-black and desaturated grays are backgrounds, text and edges far more often than they are brand colors. Only apply the filter when chromatic candidates remain, or you will return nothing for a monochrome brand.\u003C/li>\u003Cli>\u003Cstrong>Merge what is left perceptually.\u003C/strong> A histogram over quantized values still splits one real color across neighbouring buckets. Merging in a perceptually uniform space is what turns four near identical blues back into one, and the threshold has to sit above your bucket size. That interaction is the subject of \u003Ca href=\"/blog/accurate-brand-color-palettes\">The Complete Guide to Accurate Brand Color Palettes\u003C/a>.\u003C/li>\u003Cli>\u003Cstrong>Read the markup when the format has any.\u003C/strong> An SVG states its colors as numbers, including gradient stops that pixel counting cannot reach. See \u003Ca href=\"/blog/extract-brand-colors-from-svg-logos\">How to Extract Brand Colors From SVG Logos\u003C/a>.\u003C/li>\u003C/ol>\n\u003Caside class=\"callout\">\n  \u003Cstrong>Keep median cut in the toolbox.\u003C/strong> A flat histogram fails on the\n  input median cut was built for. If a logo is a photographic wordmark, a\n  gradient mesh or a heavily textured mark, no single value covers a meaningful\n  share of pixels and the histogram returns a long list of near-ties. That is the\n  moment to fall back to median cut, which handles exactly that distribution well.\n  The two methods are complementary, and the useful thing is knowing which input\n  you have.\n\u003C/aside>\n\u003Ch2 id=\"screenshots-are-the-other-case\">\u003Ca class=\"heading-anchor\" href=\"#screenshots-are-the-other-case\" aria-label=\"Link to this section\">#\u003C/a>Screenshots are the other case\u003C/h2>\n\u003Cp>Worth separating clearly, because the same word &quot;extraction&quot; covers both jobs.\u003C/p>\n\u003Cp>A full page screenshot is much closer to a photograph than to a logo. Thousands of colors, broad distribution, photography and gradients in the hero, no meaningful transparency. Median cut is the right tool, and a flat histogram over a screenshot returns the background color of the page repeated in five slightly different tones.\u003C/p>\n\u003Cp>The two also answer different questions. A logo palette answers &quot;what colors did this brand choose&quot;. A screenshot palette answers &quot;what does this page look like&quot;, which includes the stock photography and the third party embed. Both are useful. Reporting one as the other is how you end up telling somebody their brand color is the sky in their hero image.\u003C/p>\n\u003Cp>That separation is the thing worth taking away, more than any specific algorithm. The color in a logo, the colors declared in a stylesheet and the colors visible in a screenshot are three different measurements of three different things. Averaging them into one list destroys the only information that made them useful. You can see what keeping them apart looks like in the \u003Ca href=\"/brand-colors\">brand color directory\u003C/a>, or run a site through \u003Ca href=\"/\">the extractor\u003C/a> and compare the three groups it returns.\u003C/p>","content/blog/extract-dominant-colors-from-a-logo.md","/blog/extract-dominant-colors-from-a-logo",[59,74,85],{"slug":60,"title":61,"description":62,"excerpt":63,"date":64,"updated":10,"status":11,"author":12,"authorTitle":10,"category":65,"categoryName":66,"categoryAccent":67,"tags":68,"image":70,"cover":71,"imageAlt":10,"featured":22,"readingMinutes":72,"path":73},"extract-brand-colors-from-modern-css","How to Extract Brand Colors From Modern CSS","Regex over stylesheets used to be a fine way to collect a site's colors. CSS Color 4 and 5 ended that. Here is what to read instead of source text.","For about fifteen years you could collect every color a website used with three regular expressions. One for hex, one for rgb() and rgba(), one for hsl() and hsla(). Between them they matched…","2026-08-21T09:00:00.000Z","engineering","Engineering","#22D3EE",[69,17,19],"css","/social/blog/extract-brand-colors-from-modern-css.png","/social/blog/extract-brand-colors-from-modern-css-cover.png",5,"/blog/extract-brand-colors-from-modern-css",{"slug":75,"title":76,"description":77,"excerpt":78,"date":79,"updated":10,"status":11,"author":12,"authorTitle":10,"category":65,"categoryName":66,"categoryAccent":67,"tags":80,"image":82,"cover":83,"imageAlt":10,"featured":22,"readingMinutes":23,"path":84},"extract-brand-colors-from-svg-logos","How to Extract Brand Colors From SVG Logos","An SVG logo is text, so reading its colors should be trivial. Six things about real-world SVGs make it anything but, starting with currentColor.","An SVG is a text file with the colors written in it. Compared to sampling pixels out of a PNG, reading fill=\"#1F6FEB\" out of some markup should be the easy case. In practice SVG logos are the single…","2026-08-20T09:00:00.000Z",[17,81,18],"svg","/social/blog/extract-brand-colors-from-svg-logos.png","/social/blog/extract-brand-colors-from-svg-logos-cover.png","/blog/extract-brand-colors-from-svg-logos",{"slug":86,"title":87,"description":88,"excerpt":89,"date":90,"updated":10,"status":11,"author":12,"authorTitle":10,"category":13,"categoryName":14,"categoryAccent":15,"tags":91,"image":93,"cover":94,"imageAlt":10,"featured":22,"readingMinutes":95,"path":96},"accurate-brand-color-palettes","The Complete Guide to Accurate Brand Color Palettes","Near-duplicate shades are the most common flaw in an extracted palette. Here is how CIELAB and delta E collapse them into the colors a brand actually uses.","You point an extractor at a logo, ask for the brand palette, and get five colors back. Three of them are the same blue.","2026-08-19T09:00:00.000Z",[19,17,92],"palettes","/social/blog/accurate-brand-color-palettes.png","/social/blog/accurate-brand-color-palettes-cover.png",7,"/blog/accurate-brand-color-palettes",{"slug":60,"title":61,"description":62,"excerpt":63,"date":64,"updated":10,"status":11,"author":12,"authorTitle":10,"category":65,"categoryName":66,"categoryAccent":67,"tags":98,"image":70,"cover":71,"imageAlt":10,"featured":22,"readingMinutes":72,"path":73},[69,17,19],{"slug":100,"title":101,"description":102,"excerpt":103,"date":9,"updated":104,"status":11,"author":12,"authorTitle":10,"category":105,"categoryName":106,"categoryAccent":107,"tags":108,"image":111,"cover":112,"imageAlt":10,"featured":22,"readingMinutes":113,"path":114},"colorize-vs-site-palette","Colorize vs Site Palette: Which Extractor to Use","Both tools pull colors off a live site. What differs is where you run them, what counts as a palette, and what you can take away.","Site Palette and Colorize solve the same sentence (\"get me the colors this website uses\") from opposite ends. Site Palette is a browser extension: you sign in, install it, and it quantizes what is…","2026-08-23T09:00:00.000Z","comparisons","Comparisons","#34D399",[17,109,110],"workflow","comparison","/social/blog/colorize-vs-site-palette.png","/social/blog/colorize-vs-site-palette-cover.png",9,"/blog/colorize-vs-site-palette",1787496473239]