20
\$\begingroup\$

Over on http://meta.stackoverflow.com, we have a few memes of our own. One of them is Freehand Red Circles.

See this post:

So, the challenge is,

can you draw freehand red circles... with code?

Additional restrictions:

  • You will take an image as input, and you must output the image with freehand red circle added.
  • Must be predictable, i.e. same image input must result in same output. You can use randomness, but results have to be consistent for same input.
  • Output must be exactly the same image as input, except with circle (no other alterations).
  • The freehand red circle must look freehand (no perfect circles!), be red (obviously), and look generally like a circle (no random squiggly lines).

This is a , so the answer with the most upvotes at the beginning of March 2014 will win. There is no specific goal, other than "freehand red circles," so be as creative as possible so you get the most upvotes! (To be as unbiased as possible, I will upvote any answer that follows the rules.)

\$\endgroup\$
8
  • 11
    \$\begingroup\$ I think this needs a bit more clarification. Are we to a) draw a nothing but a circle on a plain white canvas, b) take an image containing text, and draw a circle around the text block, or c) take a text, and create an image of the text with a circle around it? \$\endgroup\$
    – primo
    Jan 29, 2014 at 14:19
  • 3
    \$\begingroup\$ +1 to @primo. Also, there's other things to be considered: If all we need to do is draw a circle, is it the same circle every time or does the program need to be capable of drawing different circles - and are those circles to just be randomly different, or somehow specified by user input? Does the program need to be capable of handling user input at all, to determine the size or shape of the circle? Does it matter what format the image output is in, or could someone just come up with some clever ASCII art? \$\endgroup\$
    – Iszi
    Jan 29, 2014 at 15:53
  • 2
    \$\begingroup\$ I think the answer is "this is a popularity contest, so impress your code-golf buddies" \$\endgroup\$
    – McKay
    Jan 29, 2014 at 16:39
  • \$\begingroup\$ I don't know what is unclear about this question. @Iszi - The key word is freehand. Open up Paint or GIMP and draw some freehand circles, do they all look the same? From the description and the link, it looks like you have to draw circles around something, which would imply X & Y and size. What does it matter what file format you use? Just run it through a converter if you want PNG, JPEG or whatever. \$\endgroup\$
    – user15259
    Jan 29, 2014 at 17:13
  • \$\begingroup\$ I believe with McKay. If you want a lot of upvotes, draw random freehand circles. Otherwise, hardcode your circle in. \$\endgroup\$
    – user10766
    Jan 29, 2014 at 19:33

3 Answers 3

27
\$\begingroup\$

C + GD library

Instead of just drawing circles any old where, I thought it would be fun to find something red in the picture and draw a circle around that.

Here are some examples of the results obtained with a few photos from Wikimedia Commons:

red things with circles appearing around them

And here's the code. It's a bit messy, but not too difficult to follow, I hope:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <gd.h>

#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))

/* Used for image segmentation */
int floodfill(int *tmp, int i, int w, int id) {
  int np=1;
  tmp[i]=id;
  if (tmp[i-w-1]<0) np+=floodfill(tmp,i-w-1,w,id);
  if (tmp[i-w]<0) np+=floodfill(tmp,i-w,w,id);
  if (tmp[i-w+1]<0) np+=floodfill(tmp,i-w+1,w,id);
  if (tmp[i-1]<0) np+=floodfill(tmp,i-1,w,id);
  if (tmp[i+1]<0) np+=floodfill(tmp,i+1,w,id);
  if (tmp[i+w-1]<0) np+=floodfill(tmp,i+w-1,w,id);
  if (tmp[i+w]<0) np+=floodfill(tmp,i+w,w,id);
  if (tmp[i+w+1]<0) np+=floodfill(tmp,i+w+1,w,id);
  return np;
}

int main(int argv, char *argc[]) {
  FILE          *infile,*outfile;
  gdImagePtr    img;
  int           *t, *tmp;
  int           w,h,x,y,r,g,b;
  int           c,redness,rgb;
  int           i,np,max,min,thresh;
  int           xt,yt,n;
  int           areaID,size,maxID;
  double        xmin,ymin,xmax,ymax,rad,r0,th;
  gdPoint       v[33];

  
  /* Check command line and open source JPEG file */
  if (argv!=3) return printf("Usage: %s <in.jpg> <out.jpg>\n",argc[0]);
  if (!(infile=fopen(argc[1],"r"))) return printf("Can't open <%s>\n",argc[1]);
  if (!(img=gdImageCreateFromJpeg(infile))) return printf("Bad JPEG: <%s>\n",argc[1]);
  fclose(infile);
  
  /* Extract red pixels and auto-threshold */
  w=img->sx;
  h=img->sy;
  np=w*h;
  t=tmp=calloc(np,sizeof(int));
  for (max=0,min=255,y=1;y<h-1;y++) {
    for (x=1;x<w-1;x++) {
      rgb=gdImageGetTrueColorPixel(img,x,y);
      r = (rgb&0xff0000)>>16;
      g = (rgb&0xff00)>>8;
      b = rgb&0xff;
      redness = max(0,r-(max(g,b)+abs(g-b)));
      if (redness>max) max=redness;
      if (redness<min) min=redness;
      *t++ = redness;
    }
    t += 2;
  }
  thresh = (max+min)/2;
  for (t=tmp,i=0;i<np;i++,t++) *t=((*t>thresh)?-1:0);
  
  /* Label each area detected */
  areaID=1;
  maxID=0;
  max=-1;
  for (t=tmp,i=0;i<np;i++,t++) {
    if (*t<0) {
      size=floodfill(tmp,i,w,areaID);
      if (size>max) {
        max = size;
        maxID = areaID;
      }
      areaID++;
    }
  }
  
  /* Calculate centre coordinates and area */
  if (max>0) {
    xt=yt=n=xmax=ymax=0;
    xmin=w; ymin=h;
    for (t=tmp,y=0;y<h;y++) {
      for (x=0;x<w;x++) {
        if (*t++==maxID) {
          xt+=x;
          yt+=y;
          n++;
          if (x<xmin) xmin=x;
          if (y<ymin) ymin=y;
          if (x>xmax) xmax=x;
          if (y>ymax) ymax=y;
        }
      }
    }
    x = xt/(2*n) + (xmax+xmin)/4;
    y = yt/(2*n) + (ymax+ymin)/4;

    r0 = max(20,min(min(w,h),max(xmax-xmin,ymax-ymin))/2);
  }
  /* Default circle if nothing found */
  else {
    x=w/2; y=h/2; r0=min(w,h)/3;
  }
  
  /* Draw a red circle */
  for (th=4.0,i=0;i<33;i++) {
    rad = r0 * (1.2 + (" ,<MYZVSB>@EJIOSWZfgb^bbfgeZTOI@2"[i]-87)/160.0);
    v[i].x = x + rad * sin(th);
    v[i].y = y + rad * cos(th);
    th += 0.22;
  }
  gdImageSetThickness(img,7);
  c = gdImageColorAllocate(img,255,0,0);
  gdImageOpenPolygon(img,v,33,c);
  
  /* Output results to file */
  printf("Saving...\n");
  if (!(outfile=fopen(argc[2],"w"))) {
    return printf("Can't open <%s> for writing\n",argc[2]);
  }
  gdImageJpeg(img,outfile,85);
  fclose(outfile);
  gdImageDestroy(img);
  printf("Finished\n");
  return 0;
}

Note: Markdown messed up my link in the comments, so I'll just point out that the code uses segmentation to identify all the areas of red in the picture, and then draws a circle around the largest of these. For example, this image:

red bucket and spade on a beach

produces the following output:

the red bucket has a circle around it, because it is larger than the spade

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Nice job! ;) Goes more with the theme of drawing them to emphasize something. But I'm curious about what it would do if there were two red objects...? (+1) \$\endgroup\$
    – Doorknob
    Feb 4, 2014 at 21:59
  • 2
    \$\begingroup\$ It converts all the red areas into different segments and chooses the largest. So for example in this photo of a red bucket and spade, the bucket wins. Here's the result \$\endgroup\$
    – r3mainer
    Feb 4, 2014 at 22:14
13
\$\begingroup\$

C — about 750 720 bytes if squeezed*

I think I came up with something that looks freehand-y enough.

  • starts at a random angle
  • draws a full circle plus or minus a bit
  • uses a thick squiggly line (maybe too squiggly!)
  • is customizable by changing a MAGIC number

Compile:

gcc -o freehand freehand.c -lm

Run:

./freehand [X center in % W] [Y center in % H] [radius in % diagonal] < [PPM file input] > [PPM file output]

Example:

./freehand 28.2 74.5 3.5 < screenshot.ppm > freehand.ppm

Before:

Before

After:

After

Code:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#define MAGIC      42
#define UNIFORM(x) ((x) * (double)rand() / (double)RAND_MAX)

typedef struct {unsigned char r, g, b;} RGB;

int main(int argc, char **argv)
{
    int W, H, i, f, g, x, y;
    double X, Y, R, a, r;
    RGB *p;

    srand(MAGIC);

    if (argc != 4 || scanf("P6 %d %d 255\n", &W, &H) != 2)
        return 1;

    p = malloc(sizeof(RGB) * W * H);

    fread(p, sizeof(RGB), W * H, stdin);

    X = W * atof(argv[1]) / 100.0;
    Y = H * atof(argv[2]) / 100.0;
    R = hypot(W, H) * atof(argv[3]) / 100.0;

    for (a = UNIFORM(M_PI), i = 2.0 * M_PI * R + UNIFORM(R / 4.0), r = R; i > 0; i--, a += 1.0 / R)
    {
        r += UNIFORM(2.0) - 1.0;
        f = sin(a) * r + X;
        g = cos(a) * r + Y;

        for (x = f - 2; x <= f + 2; x++)
        {
            for (y = g - 2; y <= g + 2; y++)
            {
                if (x >= 0 && x < W && y >= 0 && y < H)
                {
                    RGB *s = p + y * W + x;
                    s->r = 255;
                    s->g = 0;
                    s->b = 0;
                }
            }
        }
    }

    printf("P6 %d %d 255\n", W, H);
    fwrite(p, sizeof(RGB), W * H, stdout);

    free(p);

    return 0;
}

* and using U for UNIFORM and M for MAGIC

\$\endgroup\$
0
10
\$\begingroup\$

Mathematica

ClearAll[f]
f[image_,rad_, xPos_:.5,yPos_:.5,color_:Darker[Red,0.3],thick_:.01,axes_:False]:=
 Module[{i=ImageDimensions[image],rr,y=SeedRandom[2]},
 rr:=RandomReal[{-.1,.1}];
 Show[image,Graphics[{color,JoinForm["Round"],CapForm["Round"],Thickness[thick],
 Line[t=Table[{rad i[[2]] (Cos[z]+rr)+i[[1]]xPos,rad i[[2]] (Sin[z]+rr)+i[[2]] yPos},
 {z,0, 2 Pi+2Pi/12,Pi/12}]]}],Axes-> axes]]

f takes the following parameters:

  • image: the image that will be marked up with a circle
  • rad: the radius of the circle, in fraction of image width
  • xPos: the position of the center of the circle along x, from 0 to 1 (default=.5)
  • yPos: the position of the center of the circle along y, from 0 to 1 (default=.5)
  • color: ink color (default= dark red)
  • thickness: stroke thickness (default = .01)
  • axes: whether to display axes (default = False)

Examples

text = Import["text.png"]
f[text,.13,.58,.23]

pic1

A different radius, location, blue color, thicker stroke, displaying axes.

f[text,.22,.7,.5,Blue,.015,True]

pic2

\$\endgroup\$
5
  • \$\begingroup\$ Wow, very nice! Is this random, though? (It must produce same output for same input.) \$\endgroup\$
    – Doorknob
    Feb 1, 2014 at 0:11
  • \$\begingroup\$ I used randomness for the deviations from a true circle. I thought that was ok. If not, I can hardwire the shape. \$\endgroup\$
    – DavidC
    Feb 1, 2014 at 2:17
  • \$\begingroup\$ "Must be predictable, i.e. same image input must result in same output. You can use randomness, but results have to be consistent for same input." There must be some way to get a seeded RNG in Mathematica, right? \$\endgroup\$
    – Doorknob
    Feb 1, 2014 at 2:18
  • \$\begingroup\$ Yes, SeedRandom seems to do the trick. \$\endgroup\$
    – DavidC
    Feb 1, 2014 at 2:48
  • \$\begingroup\$ Alright, great! +1 \$\endgroup\$
    – Doorknob
    Feb 1, 2014 at 2:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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