17
\$\begingroup\$

Load an image into this stack snippet and move your mouse over it. A black curve that follows the hue angle, starting at your cursor point, will be drawn:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><style>canvas{border:1px solid black;}</style>Load an image: <input type='file' onchange='load(this)'><br><br>Max length <input id='length' type='text' value='300'><br><br><div id='coords'></div><br><canvas id='c' width='100' height='100'>Your browser doesn't support the HTML5 canvas tag.</canvas><script>function load(t){if(t.files&&t.files[0]){var e=new FileReader;e.onload=setupImage,e.readAsDataURL(t.files[0])}}function setupImage(t){function e(t){t.attr("width",img.width),t.attr("height",img.height);var e=t[0].getContext("2d");return e.drawImage(img,0,0),e}img=$("<img>").attr("src",t.target.result)[0],ctx=e($("#c")),ctxRead=e($("<canvas>"))}function findPos(t){var e=0,a=0;if(t.offsetParent){do e+=t.offsetLeft,a+=t.offsetTop;while(t=t.offsetParent);return{x:e,y:a}}return void 0}$("#c").mousemove(function(t){function e(t,e){var a=ctxRead.getImageData(t,e,1,1).data,i=a[0]/255,r=a[1]/255,o=a[2]/255;return Math.atan2(Math.sqrt(3)*(r-o),2*i-r-o)}if("undefined"!=typeof img){var a=findPos(this),i=t.pageX-a.x,r=t.pageY-a.y;$("#coords").html("x = "+i.toString()+", y = "+r.toString());var o=parseInt($("#length").val());if(isNaN(o))return void alert("Bad max length!");for(var n=[i],f=[r],h=0;n[h]>=0&&n[h]<this.width&&f[h]>=0&&f[h]<this.height&&o>h;)n.push(n[h]+Math.cos(e(n[h],f[h]))),f.push(f[h]-Math.sin(e(n[h],f[h]))),h++;ctx.clearRect(0,0,this.width,this.height),ctx.drawImage(img,0,0);for(var h=0;h<n.length;h++)ctx.fillRect(Math.floor(n[h]),Math.floor(f[h]),1,1)}});</script>

I've only tested this snippet in Google Chrome.

For example, when the cursor is above red, the curve has a 0° slope, but when it's above yellow, it has a 60° slope. The curve continues on for the specified length, continuously changing its slope to match the hue.

Load up this image and when you pan the cursor across it, the line just around the cursor should do a complete counter-clockwise turn:

hue angles

This and this are other neat images to try. (You'll need to save them and then load them with the snippet. They can't be directly linked due to cross-origin constraints.)

Here is a non-minified version of the snippet:

function load(i) { //thanks http://jsfiddle.net/vacidesign/ja0tyj0f/
    if (i.files && i.files[0]) {
        var r = new FileReader()
        r.onload = setupImage
        r.readAsDataURL(i.files[0])
    }
}

function setupImage(e) {
    img = $('<img>').attr('src', e.target.result)[0]
    function setup(c) {
        c.attr('width', img.width)
        c.attr('height', img.height)
        var ctx = c[0].getContext('2d')
        ctx.drawImage(img, 0, 0)
        return ctx
    }
    ctx = setup($('#c'))
    ctxRead = setup($('<canvas>'))
}

function findPos(obj) {
    var curleft = 0, curtop = 0;
    if (obj.offsetParent) {
        do {
            curleft += obj.offsetLeft;
            curtop += obj.offsetTop;
        } while (obj = obj.offsetParent);
        return { x: curleft, y: curtop };
    }
    return undefined;
}

$('#c').mousemove(function(e) { //thanks http://stackoverflow.com/a/6736135/3823976
    if (typeof img === 'undefined') return
    var pos = findPos(this)
    var x = e.pageX - pos.x
    var y = e.pageY - pos.y
    $('#coords').html('x = ' + x.toString() + ', y = ' + y.toString())
    var maxLength = parseInt($('#length').val())
    if (isNaN(maxLength)) {
        alert('Bad max length!')
        return
    }
    
    function hue(x, y) {
        var rgb = ctxRead.getImageData(x, y, 1, 1).data
        var r = rgb[0] / 255, g = rgb[1] / 255, b = rgb[2] / 255
        return Math.atan2(Math.sqrt(3) * (g - b), 2 * r - g - b)
    }
    
    //gather points
    var xs = [x], ys = [y]
    var i = 0
    while (xs[i] >= 0 && xs[i] < this.width && ys[i] >= 0 && ys[i] < this.height && i < maxLength) {
        xs.push(xs[i] + Math.cos(hue(xs[i], ys[i])))
        ys.push(ys[i] - Math.sin(hue(xs[i], ys[i])))
        i++
    }   
    
    //draw stuff
    ctx.clearRect(0, 0, this.width, this.height)
    ctx.drawImage(img, 0, 0)
    for (var i = 0; i < xs.length; i++)
        ctx.fillRect(Math.floor(xs[i]), Math.floor(ys[i]), 1, 1) //not using strokes because they may be anti-aliased
})
canvas {
  border:1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Load an image: <input type='file' onchange='load(this)'>
<br><br>
Max length <input id='length' type='text' value='300'>
<br><br>
<div id='coords'></div>
<br>
<canvas id='c' width='100' height='100'>Your browser doesn't support the HTML5 canvas tag.</canvas>

Challenge

Write a program that does what the snippet is doing, just not interactively. Take an image and an (x, y) coordinate in the bounds of image, and a maximum curve length. Output the same image with the added black curve that follows the hue angles starting at (x, y) and ends when it reached the maximum length or hits an image boundary.

Specifically, start the curve at (x, y) and measure the hue angle there. Go one unit (one pixel's width) in that direction, noting that your new position is most likely not an integer coordinate. Mark another point on the curve and move again, using the hue from the closest pixel (using something like floor or round, I won't be checking this precisely). Continue on like this until the curve goes out of bounds or it exceeds the maximum length. To finish, plot all the curve points as single black pixels (again, use nearest pixels) overlayed on the image, and output this new image.

The "hue angle" is just the hue:

hue = atan2(sqrt(3) * (G - B), 2 * R - G - B)

Note that for grayscale values that don't technically have a hue, this returns 0, but that's fine.

(This formula uses atan2 which most built-in math libraries have. R, G, B are from 0 to 1, not 0 to 255.)

  • You can use any common lossless image file format as well as any image libraries.
  • Take input from stdin or the command line, or write a function with arguments for the image file name, x and y, and the max length.
  • The max length and x and y are always nonnegative integers. You can assume x and y are in range.
  • Save the output image with a name of your choice or simply display it.
  • Your implementation does not have to exactly match the snippet. A few pixels in slightly different places due to a slightly different rounding/calculation method is fine. (In chaotic cases this could lead to curves that end up majorly different, but as long as they look correct visually, it's fine.)

Scoring

The smallest submission in bytes wins.

\$\endgroup\$
  • 1
    \$\begingroup\$ The snippet is completely broken in Firefox. \$\endgroup\$ – Ypnypn Mar 15 '15 at 16:18
  • \$\begingroup\$ Snippet also doesn't work in Safari. (Cool challenge though, +1) \$\endgroup\$ – Alex A. Mar 17 '15 at 3:18
  • \$\begingroup\$ @Calvin's Hobbies May we chat? chat.stackexchange.com/rooms/22029/… \$\endgroup\$ – BrainSteel Mar 17 '15 at 16:54
2
\$\begingroup\$

MATLAB, 136

function t(g,x,y,l)
m=imread(g);imshow(m); h=2*pi*rgb2hsv(m);h=h(:,:,1);s=streamline(cos(h),-sin(h),x,y,[1,l]);set(s,'LineW',1,'Co','k');

Copied most of the setup and such from @sanchises, but I instead use streamline to compute and draw the path. It does antialias the line though, and uses bilinear interpolation, rather than nearest-neighbor as specified.

| improve this answer | |
\$\endgroup\$
5
\$\begingroup\$

C with SDL, 549 516 bytes

I'll start this party! For some reason, I felt like trying my hand at golf tonight. What you guys do is hard... If there's one thing I don't see any of on this site, it's SDL. I may have just found out why. This particular snippet complies with both SDL2 and SDL1.2, but is atrocious. Called like f("imagename.bmp", xcoord, ycoord, max_length);. It saves a file with the same name as given in the arguments. Output seems to be very similar to the OP's code snippet, but "fuzzier." I may try to fix this up a bit later.

#include"SDL.h"
f(char*C,x,y,m){SDL_Surface*P=SDL_LoadBMP(C);int p=P->pitch,i=P->format->BytesPerPixel,q=0;double X=x,Y=y,f=255,R,G,B,h;Uint8*Q,d=1,r,g,b,k;while(d){Q=P->pixels+y*p+i*x;SDL_GetRGB(i==4?*(Uint32*)Q:i==3?SDL_BYTEORDER==4321?*Q<<16|Q[1]<<8|Q[2]:*Q|Q[1]<<8|Q[2]<<16:i==2?*(Uint16*)Q:*Q,P->format,&r,&g,&b);R=r/f;G=g/f;B=b/f;h=atan2(sqrt(3)*(G-B),2*R-G-B);for(k=0;k<i;k++)Q[k]=0;X+=cos(h);Y-=sin(h);if((int)X-x|(int)Y-y)q++;x=X;y=Y;d=x<0|x>=P->w|y<0|y>=P->h|q>=m?0:1;}SDL_SaveBMP(P,C);SDL_FreeSurface(P);}

Here it is all unraveled:

#include"SDL.h"
f(char*C,x,y,m){
    SDL_Surface*P=SDL_LoadBMP(C);
    int p=P->pitch,i=P->format->BytesPerPixel,q=0;
    double X=x,Y=y,f=255,R,G,B,h;
    Uint8*Q,d=1,r,g,b,k;
    while(d){
        Q=P->pixels+y*p+i*x;
        SDL_GetRGB(i==4?*(Uint32*)Q:i==3?SDL_BYTEORDER==4321?*Q<<16|Q[1]<<8|Q[2]:*Q|Q[1]<<8|Q[2]<<16:i==2?*(Uint16*)Q:*Q,P->format,&r,&g,&b);
        R=r/f;
        G=g/f;
        B=b/f;
        h=atan2(sqrt(3)*(G-B),2*R-G-B);
        for(k=0;k<i;k++)Q[k]=0;
        X+=cos(h);
        Y-=sin(h);
        if((int)X-x|(int)Y-y)q++;
        x=X;y=Y;
        d=x<0|x>=P->w|y<0|y>=P->h|q>=m?0:1;
    }
    SDL_SaveBMP(P,C);
    SDL_FreeSurface(P);
}

I should note that care was taken to make it cross platform--in the interest of honesty, it didn't feel good to hardcode it for my machine, even if it cut off a substantial number of bytes. Still, I feel like a couple things are superfluous here, and I'll look at it again later.

EDIT-------

This IS Graphical Output, after all... I'll update with images that I find interesting periodically.

f("HueTest1.bmp", 270, 40, 200);

HueTest1.bmp

f("HueTest2.bmp", 50, 50, 200);

HueTest2.bmp

f("HueTest3.bmp", 400, 400, 300);

HueTest3.bmp

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

Python, 203 172

from scipy.misc import*
def f(i,x,y,l):
 a=imread(i);Y,X,_=a.shape;o=a.copy()
 while(X>x>0<y<Y<[]>l>0):r,g,b=a[y,x]/255.;o[y,x]=0;l-=1;x+=2*r-g-b;y-=3**.5*(g-b);imsave(i,o)

Sample output: enter image description here

Call with f("image.jpg", 400, 400, 300)

I've wasted a lot of characters for the import, if anyone has suggestions to improve it. May not work in 3.0

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ From a quick glance: sqrt(3) -> 3**.5? I can't think of anything for the imports though. \$\endgroup\$ – Sp3000 Mar 29 '15 at 9:08
  • \$\begingroup\$ Nice one! That will be useful in the future. \$\endgroup\$ – grovesNL Mar 29 '15 at 18:18
3
\$\begingroup\$

MATLAB, 186 172

The game is on! Call as t('YourImage.ext',123,456,100'), for an image of any type MATLAB supports, starting at (x,y)=(123,456) and maximum length 100. Initial positions cannot be exactly at the right and bottom edges (that would cost me two bytes), so to start on the edge, use something like x=799.99 to start at x=800. Indexing starts at 1, not 0.

Code:

function t(g,x,y,l)
m=imread(g);[Y,X,~]=size(m);h=2*pi*rgb2hsv(m);while(x>0&x<X&y>0&y<Y&l)
p=ceil(x);q=ceil(y);m(q,p,:)=0;a=h(q,p);x=x+cos(a);y=y-sin(a);l=l-1;end
imshow(m)

Revisions:

  • Changed from line from previous to next pixel to just putting a dot at a pixel, since the line will never be longer than a pixel! Still using line since that is the shortest code I know to produce a pixel.
  • Changed the color from black to blue, using Co to expand to Color (which MATLAB does automatically)
  • Changed order of calculating next position and drawing a point, since I realized thanks to @grovesNL that I was actually drawing out of bounds since I was checking bounds after changing the position.
  • Changed from drawing lines to setting rgb matrix to 0 and displaying after.

Black line candy

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Aren't x=0 or y=0 potentially valid positions? \$\endgroup\$ – grovesNL Mar 29 '15 at 4:31
  • \$\begingroup\$ Also, how is this 166 bytes? \$\endgroup\$ – grovesNL Mar 29 '15 at 5:05
  • \$\begingroup\$ @grovesNL Sorry, accidentally counted my test version without the function header. Spec did not require zero-based or one-based indexing, so I'm using MATLAB's one-based, so no, x=0 or y=0 is not valid in this program. \$\endgroup\$ – Sanchises Mar 29 '15 at 8:12
  • \$\begingroup\$ Ah, I forgot that MATLAB was one-based (it has been a while). But I guess that may make x=X and y=Y valid instead? \$\endgroup\$ – grovesNL Mar 29 '15 at 18:15
  • 1
    \$\begingroup\$ Hah! Beat you at your own game, my MATLAB solution is only 135 chars! \$\endgroup\$ – AJMansfield Mar 31 '15 at 19:58
1
\$\begingroup\$

Processing, 323 characters

void h(String f,float x,float y,float m){PImage i=loadImage(f);PImage i2=loadImage(f);while(m>0){color c=i.get((int)x,(int)y);float r=red(c)/255;float g=green(c)/255;float b=blue(c)/255;float h=atan2(sqrt(3)*(g-b),2*r-g-b);float dx=cos(h);float dy=-sin(h);m-=1;x+=dx;y+=dy;i2.set((int)x,(int)y,color(0));}i2.save("o.png");}

With whitespace:

void h(String f, float x, float y, float m) {
  PImage i = loadImage(f);
  PImage i2 = loadImage(f);

  while (m > 0) {

    color c = i.get((int)x, (int)y);
    float r = red(c)/255;
    float g = green(c)/255;
    float b = blue(c)/255;
    float h = atan2(sqrt(3) * (g - b), 2 * r - g - b);

    float dx = cos(h);
    float dy = -sin(h);

    m-= 1;
    x += dx;
    y += dy;

    i2.set((int)x, (int)y, color(0));
  }

  i2.save("o.png");
}

hue traced image

I'm sure I could shorten this further, but it works for now.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

JavaScript 414

function P(G,x,y,l){
I=new Image()
I.onload=function(){d=document,M=Math,C=d.createElement('canvas')
d.body.appendChild(C)
w=C.width=I.width,h=C.height=I.height,X=C.getContext('2d')
X.drawImage(I,0,0)
D=X.getImageData(0,0,w,h),d=D.data,m=255,i=0
for(;l--;i=(w*~~y+~~x)*4,r=d[i]/m,g=d[i+1]/m,b=d[i+2]/m,H=M.atan2(M.sqrt(3)*(g-b),2*r-g-b),d[i]=d[i+1]=d[i+2]=0,x+=M.cos(H),y-=M.sin(H))
X.putImageData(D,0,0)}
I.src=G}
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.