Lazy Click and Drag

7

1

Your task is to write a program or function which, given two nonzero integers x and y, returns a truthy value if the image at coordinates x east and y north of xkcd's click and drag comic is mostly white and a falsey value otherwise.

Specifications

  • The image should be retrieved from imgs.xkcd.com/clickdrag/[y]n[x]e.png, where [y] and [x] are replaced with the absolute values of the input y and x values.
  • If x is negative, e should be changed to a w. If y is negative, n should be changed to a s.
  • If a 404 error is returned, assume the image is mostly white (output truthy) if y is positive and mostly black otherwise. This is because (as far as I can tell) xkcd stores neither completely white regions of the sky nor completely black regions of the ground, leading to less data in their servers and these 404s you must handle. This error handling is not intended to deal with the images not being available.
  • "white" is defined as rgb #FFFFFF. You may assume that the images are only white(#FFFFFF) and black(#000000), without gray or other colors. "Mostly white" is defined as at least 2097153 pixels of the image are white (because the png the program should retrieve from img.xkcd.com is always 2048x2048 pixels).
  • The program may have any behavior when dealing with pixels that are not exactly #FFFFFF nor #000000 but must produce the same output as expected for the test cases.

Example

The image at x=2, y=9 should be retrieved from imgs.xkcd.com/clickdrag/9n2e.png and appears as follows (formatted as quote to show border):

clickdrag/9n2e

It is mostly white, so the program should return a truthy value.

Input / Output

Input should be two integers or a list of two integers representing x and y.

Output should be a truthy or falsy value corresponding to if the image is mostly white.

Input and output should be through any standard I/O method.

Test Cases

Truthy (as (x,y))

2,9
7,-17
9, 5
3, 2
-5, 1
1, 3

Falsy (as (x,y))

3, 1
3, -1
7, -16
2, 1
5, 1
17, -1

fireflame241

Posted 2017-06-10T00:40:41.590

Reputation: 7 021

@JonathanAllan Yes. – fireflame241 – 2017-06-10T01:43:38.413

@JonathanAllan I switched the X and Y for that. I will look over any other errors I may have made. – fireflame241 – 2017-06-10T01:46:27.720

How should we handle the greys? Can we assume that any colour that isn't exactly #fff is black or, alternatively, that any colour that isn't #000 is white? – Shaggy – 2017-06-10T14:40:22.137

Those exist? Interesting. – CalculatorFeline – 2017-06-10T15:51:59.773

1@Shaggy You may have any behavior regarding pixels which are not white nor black. Updating in the question. – fireflame241 – 2017-06-10T15:58:13.090

2Boo-urns to input validation! (handling 404s) – Shaggy – 2017-06-10T16:50:17.467

To elaborate on the above: there are 2 situations where a 404 might arise. Either x and/or y are "out of bounds" and therefore the image doesn't exist; that would seem, to me, to be input validation, which, from what I've seen, generally doesn't go down well in code golf. Or the images have been deleted, which we shouldn't have to account for. My apologies for not catching this in Sandbox - I was probably pressed for time - but, given that there are only 3 answers so far, I don't think it's too late to change it, leaving a comment on each answer to notify them. – Shaggy – 2017-06-10T20:36:59.433

@Shaggy Xkcd's servers use 404, as far as I can tell, to hold regions of the sky and ground which are all white or all black respectively (Less space than having a fully white image for empty regions of the sky and fully black images for full regions of the ground. See this full map of the comic for reference) These parts are not out of bounds: Even 2n1e gives a 404 error because it is empty sky, not out of bounds. I understand with your second situation - not having any image to look up should not be accounted for.

– fireflame241 – 2017-06-10T20:49:22.390

Ah, OK, I didn't know that. In that case, I suppose, I can see the need for error handling. I would, though, suggest expanding on that point in the spec to include this information. – Shaggy – 2017-06-10T20:54:12.053

One more point, again with apologies for not catching it in Sandbox: if an input is negative, is it's sign included in the URL. I'm guessing not but probably best to clarify. – Shaggy – 2017-06-10T21:14:31.323

Answers

3

MATLAB, 144 bytes

function r=f(x,y);n=@num2str;r=1;try;r=nnz(imread(['http://imgs.xkcd.com/clickdrag/',n(y),'n'+5*(y<0),n(x),'e'+18*(x<0),'.png'],'png'))>2^21;end

This function takes two arguments x,y. imread reads the image at the given path. In this case we get a black and white image where each pixel (= entry in a matrix) contains the number 0 or 255. To count the white (255) pixels, nnz is used (which conts the number of nonzero entries). To account for possible eerrors, we wrap this call in try/end and predefine the return value to 1 (nonzero numbers are truthy in MATLAB). The change of e.g. n and s depending on the sign of y is done via character arithmetic.

flawr

Posted 2017-06-10T00:40:41.590

Reputation: 40 560

2

bash, 172 164 158 bytes

t=tr\ \\-0-9
curl imgs.xkcd.com/clickdrag/${2#-}$($t sn<<<${2::1})${1#-}$($t we<<<${1::1}).png|convert - a.pgm
echo $[$??$2>0:`grep -ao ÿ a.pgm|wc -l`>2**21]

This won't work on TIO due to the requirement for internet access.

There's an \xFF byte in the code, which is likely to be mangled due to encodings, so here's a script you can use to run all the test cases locally:

#!/bin/bash

xxd -r >clickdrag.sh <<'end'
00000000: 743d 7472 5c20 5c5c 2d30 2d39 0a63 7572  t=tr\ \\-0-9.cur
00000010: 6c20 696d 6773 2e78 6b63 642e 636f 6d2f  l imgs.xkcd.com/
00000020: 636c 6963 6b64 7261 672f 247b 3223 2d7d  clickdrag/${2#-}
00000030: 2428 2474 2073 6e3c 3c3c 247b 323a 3a31  $($t sn<<<${2::1
00000040: 7d29 247b 3123 2d7d 2428 2474 2077 653c  })${1#-}$($t we<
00000050: 3c3c 247b 313a 3a31 7d29 2e70 6e67 7c63  <<${1::1}).png|c
00000060: 6f6e 7665 7274 202d 2061 2e70 676d 0a65  onvert - a.pgm.e
00000070: 6368 6f20 245b 243f 3f24 323e 303a 6067  cho $[$??$2>0:`g
00000080: 7265 7020 2d61 6f20 ff20 612e 7067 6d7c  rep -ao . a.pgm|
00000090: 7763 202d 6c60 3e32 2a2a 3231 5d         wc -l`>2**21]
end

for x in '2 9' '7 -17' '9 5' '3 2' '-5 1' '1 3' '3 1' '3 -1' '7 -16' '2 1' '5 1' '17 -1'
do
    bash clickdrag.sh $x 2>/dev/null
done

rm clickdrag.sh

Explanation

First, an explanation of how the URL is built:

imgs.xkcd.com/clickdrag/${2#-}$($t sn<<<${2::1})${1#-}$($t we<<<${1::1}).png

Here, ${2#-} removes a leading negative sign from the second command line argument, and ${1#-} does the same for the first. The n/s and e/w are generated, respectively, with the snippets (after expanding $t)

tr '\-0-9' sn<<<${2::1}
tr '\-0-9' we<<<${1::1}

The ${_::1}s take the first character of the appropriate command line argument (which will be - if it is negative and a digit otherwise). The tr command then turns - into s/w and digits into n/e.

The URL is then passed to curl, which retrieves the image:

curl URL|convert - a.pgm

The convert command from ImageMagick turns the difficult PNG format into the much easier PGM, which uses one byte per pixel. It writes to the file a.pgm.

If the URL 404s, the convert command will fail to find an image and exit with a nonzero status code. Thus, we can effectively use $? (exit code of the last command) as an indicator of whether the URL returned a 404. This comprises the remainder of the code:

echo $[$??

If the exit code was nonzero (link was a 404), we simply check whether the y coordinate is positive:

$2>0

Otherwise, we count the number of \xFF bytes (white) in the image and test to see whether it's greater than 221:

:`grep -ao ÿ a.pgm|wc -l`>2**21]

Doorknob

Posted 2017-06-10T00:40:41.590

Reputation: 68 138

1

Javascript (ES6), 317 bytes

f=(x,y)=>{l=console.log;c=document.createElement`canvas`.getContext`2d`;i=new Image;i.onload=_=>{c.drawImage(i,0,0,1,1);l(!!c.getImageData(0,0,1,1).data[0])};i.onerror=e=>l(y>=0);i.crossOrigin='anonymous';i.src=`https://crossorigin.me/https://imgs.xkcd.com/clickdrag/${(y<0?y*-1+'s':y+'n')+(x<0?x*-1+'w':x+'e')}.png`}

This works by shrinking the image to an 1*1 pixel, and checking whether the red channel of this pixel is 0 or not (if white it would be 255).

Note that it has a somehow big margin of error for 50 white/50 black cases : on my computer, if there are less than between 50% - 1024px and 50%, it will report as gray, and hence white. But, as long as there is no proof that there is an image in the list with less than 1024 px of difference between black and white, I guess it's still valid.

Ungolfed :

f=(x,y)=>{
  l=console.log; // output in the console since the function is async
  c=document.createElement`canvas`.getContext`2d`;
  i=new Image;
  i.onload=_=>{
    c.drawImage(i,0,0,1,1); // draw resized at 1px*1px
    // is the first red value of the pixel '0' ?
    l(!!c.getImageData(0,0,1,1).data[0])
    };
   // handle 404
  i.onerror= e=> l(y>=0);
  // 56 bytes only for cross-origins...
  i.crossOrigin="anonymous";
  i.src= `https://crossorigin.me/https://imgs.xkcd.com/clickdrag/${
    // there could probably be some out-golfing to do here
    (y<0?y*-1+'s':y+'n') + (x<0?x*-1+'w':x+'e')
    }.png`;
}

Here is a 319 bytes Promise based live version, for better logging of the outputs, since all this is async :

f=(x,y)=>new Promise(l=>{c=document.createElement`canvas`.getContext`2d`;i=new Image;i.onload=_=>{c.drawImage(i,0,0,1,1);l(!!c.getImageData(0,0,1,1).data[0])};i.onerror=e=>l(y>=0);i.crossOrigin='anonymous';i.src=`https://crossorigin.me/https://imgs.xkcd.com/clickdrag/${(y<0?y*-1+'s':y+'n')+(x<0?x*-1+'w':x+'e')}.png`})


var pos = [
// truthy
  {x:2,y:9},
  {x:7,y:-17},
  {x:9,y:5},
  {x:3,y:2},
  {x:-5,y:1},
  {x:1,y:3},
// falsy  
  {x:3,y:1},
  {x:3,y:-1},
  {x:7,y:-16},
  {x:2,y:1},
  {x:5,y:1},
  {x:17,y:-1}
  ];
function test(i){
  let p = pos[i];
  let prom = f(p.x, p.y)
  prom.then(r=>{ 
    console.log(p.x, p.y, ': ', r);
    if(i < pos.length) 
      test(++i);
    });
  }
test(0);

And if ever someone find an image with less than 1024px of difference between whites and blacks, here is a 378 bytes version which handles it :

f=(x,y)=>{l=console.log;o=document.createElement`canvas`;w=o.width=o.height=2048;i=new Image;i.onload=_=>{c=o.getContext`2d`;c.drawImage(i,0,0);l(c.getImageData(0,0,w,w).data.reduce((a,v,i)=>(i%4&&v?++a:a),0)/4>w*w/2)};i.onerror=e=>l(y>=0);i.crossOrigin='anonymous';i.src=`https://crossorigin.me/https://imgs.xkcd.com/clickdrag/${(y<0?y*-1+'s':y+'n')+(x<0?x*-1+'w':x+'e')}.png`}

Kaiido

Posted 2017-06-10T00:40:41.590

Reputation: 181

Nice idea, shrinking the image to a single pixel (although I can't test how accurate it is on my phone). Few things: You don't need to handle errors and your function doesn't need to log the result, simply output it. – Shaggy – 2017-06-10T16:27:47.930

Oh, wait, in this case we do need to handle errors. Boo-urns to input validation! – Shaggy – 2017-06-10T16:45:39.427

@Shaggy, I have to admit I didn't really tested for exactly 2097152blacks vs 2097152 whites, and I guess that results may vary across devices and implementations, but as long as nobody shows me a perfectly 50/50 images in xkcd where it fails, I guess it holds, right ? And for the console.log, it's because the function is async... I could have returned a promise, but at the cost of 2 bytes (added it anyway though). – Kaiido – 2017-06-11T02:02:53.210

So after testing, there is a somewhat large gray area around 50/50 where the pixel will be set to 128,128,128, and hence my code will see it white, but once again, I'm waiting for someone to find an image in the list which would fall into this gray area. – Kaiido – 2017-06-11T02:11:47.440