Make a sprite-sheet from all the Stack Exchange sites' favicons

2

1

Your goal in this code golf is to compile all the Stack Exchange sites' favicons.


Getting the Domains

The domains you will be using will be:

  • *.stackexchange.com
  • stackexchange.com
  • stackoverflow.com
  • superuser.com
  • serverfault.com
  • askubuntu.com
  • mathoverflow.net
  • stackapps.com
  • stackauth.com

Everything but stackexchange.com and stackauth.com, as of now, can be gathered from /sites.

All the meta counterparts will also be included. (just prepend meta. to the beginning of any of the domains to get the meta site)

The list above shows the order of the sites. Sites without their own domain (*.stackexchange.com, i.e. codegolf.stackexchange.com) are first in the stylesheet, and are sorted alphabetically. After you've sorted the subdomain sites, the rest of the domains are in the order listed above.

Then, you can append /favicon.ico to each one of the domains you dynamically compiled and get the 32 by 32 pixel image that results from it.

Here's another list for domains that redirect to *.stackexchange.com sites:

  • askdifferent.comapple.stackexchange.com
  • arqade.comgaming.stackexchange.com
  • seasonedadvice.comcooking.stackexchange.com

Output the Images

As soon as you have all of the favicons from the sites and their meta counterparts, your code combines them. Optionally, convert them into .PNG files (you gathered .ICOs, and trying to get favicon.png doesn't work) After that, you can start combining the images into one rectangle.

There will be 10 columns, each 32 pixels in width, making a total of 320 pixels. The number of rows will match those needed to fit all the favicons, and they are 32 pixels in height.

Your code should output an image with all of the favicons you gathered as a transparent .PNG file, which is already , like seen below:


Scoring

This is and so the least bytes wins!

Leaderboard

Here is a Stack Snippet to generate both a regular leaderboard and an overview of winners by language.

To make sure that your answer shows up, please start your answer with a headline, using the following Markdown template:

# Language Name, N bytes

where N is the size of your submission. If you improve your score, you can keep old scores in the headline, by striking them through. For instance:

# Ruby, <s>104</s> <s>101</s> 96 bytes

var QUESTION_ID=86753;function answersUrl(e){return"http://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),e.has_more?getAnswers():process()}})}function shouldHaveHeading(e){var a=!1,r=e.body_markdown.split("\n");try{a|=/^#/.test(e.body_markdown),a|=["-","="].indexOf(r[1][0])>-1,a&=LANGUAGE_REG.test(e.body_markdown)}catch(n){}return a}function shouldHaveScore(e){var a=!1;try{a|=SIZE_REG.test(e.body_markdown.split("\n")[0])}catch(r){}return a}function getAuthorName(e){return e.owner.display_name}function process(){answers=answers.filter(shouldHaveScore).filter(shouldHaveHeading),answers.sort(function(e,a){var r=+(e.body_markdown.split("\n")[0].match(SIZE_REG)||[1/0])[0],n=+(a.body_markdown.split("\n")[0].match(SIZE_REG)||[1/0])[0];return r-n});var e={},a=1,r=null,n=1;answers.forEach(function(s){var t=s.body_markdown.split("\n")[0],o=jQuery("#answer-template").html(),l=(t.match(NUMBER_REG)[0],(t.match(SIZE_REG)||[0])[0]),c=t.match(LANGUAGE_REG)[1],i=getAuthorName(s);l!=r&&(n=a),r=l,++a,o=o.replace("{{PLACE}}",n+".").replace("{{NAME}}",i).replace("{{LANGUAGE}}",c).replace("{{SIZE}}",l).replace("{{LINK}}",s.share_link),o=jQuery(o),jQuery("#answers").append(o),e[c]=e[c]||{lang:c,user:i,size:l,link:s.share_link}});var s=[];for(var t in e)e.hasOwnProperty(t)&&s.push(e[t]);s.sort(function(e,a){return e.lang>a.lang?1:e.lang<a.lang?-1:0});for(var o=0;o<s.length;++o){var l=jQuery("#language-template").html(),t=s[o];l=l.replace("{{LANGUAGE}}",t.lang).replace("{{NAME}}",t.user).replace("{{SIZE}}",t.size).replace("{{LINK}}",t.link),l=jQuery(l),jQuery("#languages").append(l)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",answers=[],page=1;getAnswers();var SIZE_REG=/\d+(?=[^\d&]*(?:&lt;(?:s&gt;[^&]*&lt;\/s&gt;|[^&]+&gt;)[^\d&]*)*$)/,NUMBER_REG=/\d+/,LANGUAGE_REG=/^#*\s*([^,]+)/;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:290px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"><div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table></div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table></div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody></table><table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody></table>

haykam

Posted 2016-07-27T21:57:08.180

Reputation: 784

You might want to consider providing a list of URLs to use and what order they should be in as it is a bit ambiguous at the moment. Also, what does "unlimited rows/height" mean? Do you intend every submission to output the same image or can users choose the dimensions of the rectangle? – GamrCorps – 2016-07-27T22:03:02.867

@GamrCorps It means if more sites are added to the current lineup and they end up requiring a new line, then it can be added. – haykam – 2016-07-27T22:04:22.983

Ok. Two more questions though: are URL shorteners allowed and do you require submissions to be full programs, functions, or either? I think this question could have benefited time in the sandbox.

– GamrCorps – 2016-07-27T22:06:15.180

@GamrCorps Also, You can find a list of the Stack Exchange subdomains on Wolfram Alpha.

– haykam – 2016-07-27T22:06:30.077

@GamrCorps URL shorteners are not allowed, and they have to be either – haykam – 2016-07-27T22:07:22.700

4The list of Stack Exchange sites is not fixed; subdomains may be added or removed at all times. The challenge spec should include the list that is required for this task. – Dennis – 2016-07-27T22:09:50.990

@Dennis It has to be dynamically found. – haykam – 2016-07-27T22:13:46.047

2OK, so what about SE sites that aren't a subdomain of SE.com or SO.com? Should they be excluded? – Dennis – 2016-07-27T22:20:07.063

@Dennis Can you give me examples? – haykam – 2016-07-28T00:31:10.033

superuser.com, serverfault.com, askubuntu.com, askdifferent.com, mathoverflow.net, stackapps.com, seasonedadvice.com, stackauth.com – Dennis – 2016-07-28T02:21:47.583

1

What about parse the sites from http://stackexchange.com/sites ?

– Giacomo Garabello – 2016-07-28T15:17:11.610

1@GiacomoGarabello That is an option. – haykam – 2016-07-28T16:18:49.937

@Dennis, adding those into the main post. – haykam – 2016-07-28T16:19:07.840

Sites without their own domain The order of the images in the sprite-sheet will be that, except the separate sites not including Stack Overflow will be in alphabetical order. This entire part needs some major clarification. I mean Sites without their own domain... what? The order of the images in the sprite-sheet will be that what is "that"? And what are the "separate sites" mentioned here? – R. Kap – 2016-07-29T00:59:35.723

Not to say that this is a bad question. This can still be a pretty good question in my opinion. It just needs quite a bit of clarification. – R. Kap – 2016-07-29T01:03:06.380

@R.Kap, I've explained it a bit more. Somehow I never finished writing that sentence in my edit. – haykam – 2016-07-29T01:22:52.247

1

@Dennis, Seasoned Advice is actually at cooking.stackexchange.com and seasonedadvice.com is just a redirect, much like arqade.com redirects to gaming.stackexchange.com.

– haykam – 2016-07-29T01:24:49.277

I feel like the sorting order needs to be less arbitrary. Instead of how you specified, can it just be *.stackexchange.com alphabetically by subdomain, then the remaining sites alphabetically by domain? Also, where are the meta sites factored in? – Patrick Roberts – 2016-07-29T02:03:21.407

1Actually, www.stackexchange.com/favicon.ico returns a 16 x 16 image. – R. Kap – 2016-07-29T19:54:57.700

Also, www.seasonedadvice.com/favicon.ico returns a 404 not found error.

– R. Kap – 2016-07-29T20:13:15.530

@R.Kap you're right about stackexchange, but you forgot that seasonedadvice redirects: http://cdn.sstatic.net/Sites/cooking/img/favicon.ico. It's in my sprite-sheet.

– Patrick Roberts – 2016-07-29T20:14:26.293

@PatrickRoberts Yeah, but then again, OP did say to just append /favicon.ico to the end of each domain to get the favicon, but when I do that to www.seasonedadvice.com, I get the 404 error. – R. Kap – 2016-07-29T20:16:33.347

@R.Kap see this comment. He knows.

– Patrick Roberts – 2016-07-29T20:17:19.010

@PatrickRoberts Well then, in that case, why is www.seasonedadvice.com in the list? And if that has to be there, why isn't www.arqade.com also in the list? – R. Kap – 2016-07-29T20:19:15.270

Let us continue this discussion in chat.

– Patrick Roberts – 2016-07-29T20:22:34.080

www.stackauth.com also returns a 16 x 16 favicon. – R. Kap – 2016-07-29T20:33:34.517

@R.Kap, I've posted a question on Meta Stack Exchange.

– haykam – 2016-07-29T21:30:20.867

Answers

9

JavaScript (ES6), 691 687 680 663 646 657 bytes

fetch(new Request('//crossorigin.me/http://stackexchange.com/sites')).then(r=>r.text(t=(d=document).body.appendChild(c=d.createElement('canvas')).getContext('2d')).then(a=>(d=new DOMParser(c.width=320).parseFromString(a,'text/html'),s=[...d.querySelectorAll('.grid-view-container>a')].map(e=>!~(h=e.href[p='slice'](7))[o='indexOf']('met')?h:h[p](5)),c.height=-~(s.length/5)*32,s.sort((a,b)=>(e=a[o](k='.s'),g=b[o](k),~e&&~g?a[l='localeCompare'](b):!~e&&!~g?a[p](e)[l](b[p](g)):g-e)).concat(s.filter(u=>!~u[o]('ka')).map(u=>'meta.'+u))).map((u,i,x,y)=>(u=`http://${u}favicon.ico`,x=i%10,y=(i-x)/10,i=new Image,i.src=u,i.onload=_=>t.drawImage(i,x*32,y*32)))))

Oof. Hopefully I can trim down the byte count.

Basically:

  • CORS request to /sites, parse DOM and scrape the site list from there using a query selector.
  • Sort first by *.stackexchange.com sites alphabetically, then by independent domain sites alphabetically.
  • Append the meta sites, filtering out meta.stackexchange.com and stackapps.com since they're both listed on /sites and are without their own meta site.
  • Asynchronously load each favicon.ico without CORS since we don't care about tainting the canvas.
  • Use each site's index i from the sorted array to place the loaded image in the correct position on the sprite-sheet.

You can right-click on the image and select "Save Image as", so I don't think I need to write more just to explicitly prompt the image for download, since it's already enough work just displaying it.

Revisions

  1. Inlining global declarations saved 2 bytes. Submission is now one loooong line.
  2. Removing () from new Image() saved another 2 bytes.
  3. Changing Math.ceil(...) to -~(...) saved 7 bytes. Doesn't apply now, but in the event that the amount of sites is a multiple of 10, it's still okay because "There are 10 columns, which is 320 pixels in width, and unlimited rows/height" regardless of the amount of sites.
  4. Saved 17 bytes by moving a global declaration and refactoring an unneeded ternary operator in a .map() function.
  5. Saved another 17 bytes by storing long repeated method names to variables.
  6. Cost 11 bytes to factor in stackexchange.com and its meta... gross.

Patrick Roberts

Posted 2016-07-27T21:57:08.180

Reputation: 2 475

Nothing seems to shows up when I run your code snippet and save the image... – R. Kap – 2016-07-29T18:46:18.870

@R.Kap does the sprite-sheet populate with all the favicons when viewing the program in progress? I'm running on Chrome 52 and it works perfectly. Make sure your browser supports the Request API and ES6. Check for errors in your console when running the program. If there are any, it would be helpful for me if you could share those. – Patrick Roberts – 2016-07-29T18:47:07.363

Well, I am running the snippet in Firefox and don't really know if it supports the Request API. However, I know it does support ES6 since a lot of other ES6 snippets on PPCG do work. There are no errors in the console, so t's most likely that Firefox may not support the Request API. – R. Kap – 2016-07-29T18:52:19.400

http://codegolf.stackexchange.com/a/86853/42091 Since you're in Firefox and there are no errors, my guess is that it's blocking "non-secure requests from HTTPS to HTTP". Just click that link and make sure your URL is HTTP instead of HTTPS when running the script. – Patrick Roberts – 2016-07-29T18:54:15.083

@R.Kap the first half is non-meta, second half is meta. – Patrick Roberts – 2016-07-29T18:58:04.727

Yeah, I finally figured it out. Really good job! It looks awesome in Chrome. +1 – R. Kap – 2016-07-29T18:58:25.873

Where is the www.stackexchange.com favicon? I don't see that in the output. – R. Kap – 2016-07-29T19:56:32.530

@R.Kap thanks for pointing that out... I think it's dumb that /sites includes its meta site, but not itself... I'll factor that in. – Patrick Roberts – 2016-07-29T20:00:30.830

Also, do you have the www.stackauth.com favicon in there? – R. Kap – 2016-07-29T20:07:59.577

@R.Kap no, and that's okay. stackauth.com is not in /sites and according to the author of this challenge, parsing /sites for the list of favicons is acceptable.

– Patrick Roberts – 2016-07-29T20:10:59.057

@PatrickRoberts You can poll /sites, but you still need to include www.stackexchange.com and stackauth.com.

– haykam – 2016-07-29T21:25:34.847

@Peanut I did include stackexchange but I don't understand why stackauth needs to be included when it's not listed in sites. You're changing your challenge's specification by making that a requirement. – Patrick Roberts – 2016-07-29T22:34:00.530

1@Peanut I don't appreciate you editing my answer without my permission. I'm reverting it. Don't do that again. – Patrick Roberts – 2016-07-30T23:37:03.333

You can shorten this by replacing http://stackexchange.com/sites with http://s.tk/sites. – R. Kap – 2016-07-31T07:02:55.933

@R.Kap URL shorteners are not allowed

– Patrick Roberts – 2016-07-31T15:47:14.780

@PatrickRoberts Oh dang. I did not see that comment. – R. Kap – 2016-07-31T17:34:53.357

Looks like crossorigin.me is broken now.

– haykam – 2017-03-01T03:09:45.813

@haykam Still a valid answer because it worked at the time of the question. I'll update the demo or remove it later depending on if I find an alternative. – Patrick Roberts – 2017-03-01T03:23:16.607

3

Python 3.5, 591 549 541 557 570 bytes:

(+16 bytes (541 -> 557) to add the ability for the final image to dynamically expand in height based on the number of favicons gathered.)

(+13 bytes (557 -> 570) since URL shorteners are apparently not allowed.)

from urllib.request import*;from PIL import Image as I;from io import*;import re
H='http://';U=urlopen;u=y=0;V=sorted(re.findall('<h2><a href="%s((?!meta).+?.stackexchange.com)">'%H,U(H+'stackexchange.com/sites?view=list').read().decode()[7167:]))+['%s.com'%i for i in'stackexchange stackoverflow superuser serverfault askubuntu mathoverflow stackapps stackauth'.split()];i=I.new("RGBA",(320,-~(len(V)*2//10)*32))
for l in V+['meta.'+i for i in V]:
 try:
  i.paste(I.open(BytesIO(U(H+l+'/favicon.ico').read())),(u,y));u+=32
  if u>319:u=0;y+=32
 except:0
i.save('O.png')

Quite long, but hey, I beat Javascript! :D This imports the modules for the following uses:

  • urllib to fetch website and image data.
  • io module for the BytesIO function to decode the fetched image data for PIL.
  • re for the use of regular expressions to parse the website data.
  • PIL to create the final image from the fetched image data.

Creates and saves a 320 x 1024 PNG image named O.png in the current working directory, with all the favicons in the order specified. I will try to golf this down more over time as much as I can, but for now, I could not be happier. :)

Final Output Image:

Final

R. Kap

Posted 2016-07-27T21:57:08.180

Reputation: 4 730

+1 because it doesn't have duplicated Stack Overflow favicons, unlike the JavaScript version – haykam – 2016-07-30T00:59:24.373

Got an error: SyntaxError: Unexpected keyword 'case'. Expected a closing '}' at the end of a block statement. – haykam – 2016-07-30T03:03:14.753

@Peanut It works perfectly fine for me with Python 3.5 on Mac. – R. Kap – 2016-07-30T03:05:24.823