29
\$\begingroup\$

It's winter, and the time of year has come for it to start getting cold (and for strange colorful headcloths to start appearing... soon). Let's write some code to make avatar pictures and other images frozen over, to fit the theme!

Input

The input to submissions to this challenge should be an image (the image to make frozen) and a number (the threshold, which will be explained later).

You may input the image in any way your language supports them (a file path or URL as an argument, taking it from the clipboard, dragging and dropping an image, etc.) and in any format listed here that expresses colors in RGB (you can support / require RGBA instead if you want, but this is not a requirement).

You can input the number in any way you would like as well (command line argument, STDIN, input dialog, etc.), with the exception of hardcoding it into your program (ex. n=10). If you use a file path / URL for the image, it must be input in this manner as well.

Output

The program must process the image according to the description below and then output it in any way you would like (to a file, showing it on screen, putting it on the clipboard, etc.).

Description

Submissions should process the image with the following three steps. n refers to the number that your program received as input along with the image.

  1. Apply a blur of radius n to the input image by replacing each pixel's R, G, and B values with the average R, G, and B values of all pixels within a Manhattan distance of n pixels, ignoring all out-of-bounds coordinates. (I.e. all pixels where the sum of the difference in X and the difference in Y is less than or equal to n.)

    (note: I used a Gaussian blur for the images above because there was a convenient built-in function for it, so your images might look a little different.)

  2. Set each pixel to a random pixel within a distance of n/2 pixels ("distance" is defined the same way as in the previous step).

    This should be done by looping through the image and setting each pixel to a random pixel in this range, so some pixels might disappear entirely and some might be duplicated.

    All changes must apply at the same time. In other words, use the old values of the pixels (after step 1 but before this step), not the new values after setting them to a random pixel.

  3. Multiply the "blue" RGB value of each pixel by 1.5, capping it at 255 (or whatever the maximum value for a band of pixels is) and rounding down.

Rules

  • You may use image libraries / image processing-related functions built into your language; however, you may not use any functions that perform one of the three major tasks mentioned in the description. For example, you can't use a blur function, but a getPixel function is fine.

  • This is , so the shortest code in bytes wins!

\$\endgroup\$
  • 1
    \$\begingroup\$ Step 1 has two points which need clarifying. Firstly, which metric? You say Manhattan (L-1) and describe L-infinity. Secondly, how should image boundaries be handled: no wrapping, reducing the denominator to average only over pixels inside the boundary? Step 2 has one point which needs clarifying: is the sampling from a copy of the image after step 1, or can changes from early in step 2 propagate? For step 3, capping at 255 is only appropriate in a 24-bit colour model, and the question doesn't anywhere require that. \$\endgroup\$ – Peter Taylor Dec 2 '14 at 22:54
  • \$\begingroup\$ @PeterTaylor I've tried to clarify all of those points, except for the first one. I don't really understand what you're saying; dx <= n && dy <= n is an accurate representation of Manhattan distance, is it not? \$\endgroup\$ – Doorknob Dec 2 '14 at 23:02
  • \$\begingroup\$ No, Manhattan distance is |dx| + |dy| <= n. \$\endgroup\$ – Peter Taylor Dec 2 '14 at 23:03
  • \$\begingroup\$ @PeterTaylor Alright, thanks, I've fixed that as well. \$\endgroup\$ – Doorknob Dec 2 '14 at 23:05
  • 1
    \$\begingroup\$ @stokastic I think "within a distance of n/2 pixels" is a perfectly valid statement without rounding/flooring n/2 at all (so effectively, "floored", I think). \$\endgroup\$ – Martin Ender Dec 3 '14 at 16:05
14
\$\begingroup\$

Python 2 - 326 339 358

Takes input from the user. File first, then n.

from PIL.Image import*;from random import*
a,N=input()
i=open(a)
d=list(i.getdata())
x,y=i.size
R=range(x*y)
m=lambda p,n,r:[p[i]for i in R if abs(n%x-i%x)+abs(n/y-i/y)<=r]
k=d[:]
for p in R:t=map(lambda x:sum(x)/len(x),zip(*m(k,p,N)));d[p]=t[0],t[1],min(255,t[2]*3/2)
i.putdata([choice(m(d,p,N/2))for p in R])
i.save('t.png')

This could probably be golfed much more :P Thanks to @SP3000 for golf ideas!

Sample input: (Windows)

"C:/Silly/Optimizer/Trix/Are/For/Kids.png",7

Edit: Bug fixed where blue was being propagated (Martin with n=20 is no longer a river ;_; )

Martin with n=2:

enter image description here

Martin with n=10:

enter image description here

Martin with n=20:

enter image description here

\$\endgroup\$
3
\$\begingroup\$

Python 2 - 617 Bytes

EDIT: golfed some, looks like FryAmTheEggMan has me beat though :)

from PIL import Image
import sys,random
j,i,n=sys.argv
n=int(n)
i=Image.open(i)
w,h=i.size
o=Image.new("RGB",(w,h))
D=list(i.getdata())
D=[D[i*w:i*w+w] for i in range(h)]
O=[]
d=n/2
z=range(-n,n+1)
M=lambda n:[[x,y] for x in z for y in z if abs(x)+abs(y)<=n]
m=M(n)
L=w*h
for i in range(L):
 y,x=i/w,i%w;c=r=g=b=0
 for q in m:
  try:C=D[y+q[1]][x+q[0]];r+=C[0];g+=C[1];b+=C[2];c+=1
  except:pass
 r/=c;g/=c;b/=c
 O.append((r,g,min(b*3/2,255)))
R=lambda:random.randint(-d,d)
for i in range(L):
 x,y=i%w,i/w;u=R();v=R()
 while not(0<x+u<w and 0<y+v<h):u=R();v=R()
 O[y*w+x]=O[(y+v)*w+(x+u)]
o.putdata(O)
o.save("b.png")
\$\endgroup\$
3
\$\begingroup\$

Java - 1009 bytes

eh, I thought I could do better than this...

import java.awt.*;import java.io.*;import java.util.*;import javax.imageio.*;class r{public static void main(String[]v)throws Exception{java.awt.image.BufferedImage i=ImageIO.read(new File("y.png"));int n=Byte.valueOf(v[0]),w=i.getWidth(),h=i.getHeight();for(int z=0;z<w*h;z++){int x=z/h,y=z%h,r=0,g=0,b=0,c=0,x2,y2,k;for(x2=x-n;x2<=x+n;x2++){for(y2=y-n;y2<=y+n;y2++){if(Math.abs(x2-x)+Math.abs(y2-y)<=n&&x2>=0&&x2<w&&y2>=0&&y2<h){k=i.getRGB(x2,y2); r+=(k>>16)&0xFF;g+=(k>>8)&0xFF;b+=k&0xFF;c++;}}}i.setRGB(x,y,new Color(r/c,g/c,b/c).getRGB());}int[]t=new int[w*h];for(int z=0;z<h*w;z++){int x=z/h,y=z%h,x2,y2;ArrayList<Integer>e=new ArrayList<>();for(x2=x-n;x2<=x+n;x2++){for(y2=y-n;y2<=y+n;y2++){if(Math.abs(x2-x)+Math.abs(y2-y)<=n/2&&x2>=0&&y2>=0&&x2<w&&y2<h)e.add(i.getRGB(x2,y2));}}int p=e.get((int)(Math.random()*e.size())),b=(int)((p&0xFF)*1.5);t[x*h+y]=new Color((p>>16)&0xFF,(p>>8)&0xFF,b>255?255:b).getRGB();}for(int d=0;d<w*h;d++){i.setRGB(d/h,d%h,t[d]);}ImageIO.write(i,"PNG",new File("n.png"));}}

import java.awt.*;
import java.io.*;
import java.util.*;
import javax.imageio.*;
class IceBlur{
    public static void main(String[]v)throws Exception{
        java.awt.image.BufferedImage i=ImageIO.read(new File("blah.png"));
        int n=Byte.valueOf(v[0]),w=i.getWidth(),h=i.getHeight();
        for(int z=0;z<w*h;z++){
            int x=z/h,y=z%h,r=0,g=0,b=0,c=0,x2,y2,k;
            for(x2=x-n;x2<=x+n;x2++){
                for(y2=y-n;y2<=y+n;y2++){
                    if(Math.abs(x2-x)+Math.abs(y2-y)<=n&&x2>=0&&x2<w&&y2>=0&&y2<h){
                        k=i.getRGB(x2,y2);
                        r+=(k>>16)&0xFF;
                        g+=(k>>8)&0xFF;
                        b+=k&0xFF;
                        c++;}}}i.setRGB(x,y,new Color(r/c,g/c,b/c).getRGB());}
        int[]t=new int[w*h];
        for(int z=0;z<h*w;z++){
            int x=z/h,y=z%h,x2,y2;
            ArrayList<Integer>e=new ArrayList<>();
            for(x2=x-n;x2<=x+n;x2++){
                for(y2=y-n;y2<=y+n;y2++){
                    if(Math.abs(x2-x)+Math.abs(y2-y)<=n/2&&x2>=0&&y2>=0&&x2<w&&y2<h)e.add(i.getRGB(x2, y2));}}
            int p=e.get((int)(Math.random()*e.size())),b=(int)((p&0xFF)*1.5);
            t[x*h+y]=new Color((p>>16)&0xFF,(p>>8)&0xFF,b>255?255:b).getRGB();}
        for(int d=0;d<w*h;d++){i.setRGB(d/h, d%h, t[d]);}
        ImageIO.write(i,"PNG",new File("blah2.png"));}}

Martin with n=5:

enter image description here

n=20:

enter image description here

Me with 10:

enter image description here

\$\endgroup\$
  • \$\begingroup\$ It's been a while since I java'd anything, but couldn't you do k&0xFF00? Furthermore, couldn't you use 255 in place of 0xFF? \$\endgroup\$ – FryAmTheEggman Dec 4 '14 at 21:44
3
\$\begingroup\$

C, 429 (391 + 38 for define flags)

i,R,G,B,q;char*c,t[99];main(r,a,b,k,z,p){scanf("%*[^ ]%d%*6s%d%[^N]%*[^R]R\n",&a,&b,t);int j=a*b,d[j],e[j];F(c=d;c<d+j;*c++=getchar());F(;i<j;e[i++]=X<<24|B/q<<16|G/q<<8|R/q,R=G=B=q=0)F(k=0;k<j;)p=d[k++],D<r&&(++q,R+=p&X,G+=p>>8&X,B+=p>>16&X);F(i=!printf("P7\nWIDTH %d\nHEIGHT %d%sNDHDR\n",a,b,t);i<j;d[i++]=e[k])F(;k=rand()%j,D>r/2;);F(c=d;q<j*4;i=(q%4-2?2:3)*c[q]/2,putchar(i>X?X:i),++q);}

Input format: pam file with no comments or extra whitespace in header, contents passed via STDIN.

n arguments are required (they can be anything).

Output format: pam file in STDOUT.

To compile:

gcc -DX=255 -DF=for "-DD=z=abs(k-i),z/b+z%a" -Wl,--stack,33554432 -funsigned-char icyavatars.c -o icyavatars

-Wl,--stack,33554432 increases the stack size; this may be changed or removed, depending on the size of the picture being processed (the program requires a stack size greater than twice the number of pixels times 4).

-funsigned-char has gcc use unsigned char instead of signed char for char. The C standards allows for either of these options, and this option is only needed here because gcc uses signed char by default.

To run (n=5):

./icyavatars random argument here fourth fifth < image.pam > output.pam

Note: If compiling on Windows, stdio.h, fcntl.h and io.h must be included, and the following code added to the start of main() in order for the program to read/write to STDIN/STDOUT as binary, not text, streams (this is irrelevant on Linux, but Windows uses \r\n instead of \n for text streams).

setmode(fileno(stdin), _O_BINARY);
setmode(fileno(stdout), _O_BINARY);

Commented version

int i,R,G,B,q;
char *c,t[99];
main(r,a,b,k,z,p){
    // read all of header
    // save a large chunk to t, save width to a, save height to b
    scanf("%*[^ ]%d%*6s%d%[^N]%*[^R]R\n", &a, &b, t);
    // create arrays for holding the pixels
    int j = a * b, d[j], e[j];
    // each pixel is 4 bytes, so we just read byte by byte to the int arrays
    for(c = d; c < d + j; ++c)
        *c=getchar();

    // calculating average rgb
    for(i = 0; i < j; ++i){
        // check every pixel; add r/g/b values to R/G/B if manhattan distance < r-1
        for(k = 0; k < j; ++k){
            // pixel being checked
            p = d[k];
            // manhattan distance
            z = abs(k - i)/b + abs(k - i)%a;
            if(z < r){
                // extract components and add
                ++q;
                R += p & 255;
                G += p >> 8 & 255;
                B += p >> 16 & 255;
            }
        }
        // set pixel in e (not d) to average RGB and 255 alpha
        e[i]= 255<<24 | B/q<<16 | G/q<<8 | R/q;
        // clear temporary variables
        R = G = B = q = 0;      
    }

    // print header
    printf("P7\nWIDTH %d\nHEIGHT %d%sNDHDR\n",a,b,t);
    // choose random pixels
    for(i = 0; i < j; ++i){
        // loop until randomly generated integer represents a pixel that is close enough
        do{
            k = rand() % j;
            // manhattan distance
            z = abs(k - i)/b + abs(k - i)%a;
        }while(z > r/2);
        // set d to the new pixel value
        d[i] = e[k];
    }
    // apply blue scaling and output
    for(c = d, q = 0; q < j * 4; ++q){
        // 3/2 if blue component, 1 otherwise
        i = (q % 4 - 2 ? 2 : 3)*c[q]/2;
        // cap components at 255
        putchar(i > 255 ? 255 : i);
    }
}

Martin with n=10:

Martin with n=10

Martin with n=20:

Martin with n=20

Martin with n=100:

Martin with n=100

\$\endgroup\$
1
\$\begingroup\$

R, 440 chars

f=function(n,p){a=png::readPNG(p);b=a;N=nrow(a);M=ncol(a);r=row(a[,,1]);c=col(a[,,1]);for(i in 1:N)for(j in 1:M)b[i,j,]=apply(a,3,function(x)mean(x[abs(r-i)+abs(c-j)<=n]));for(i in 1:N)for(j in 1:M){g=which(abs(r-i)+abs(c-j)<=n/2,arr.ind=T);o=sample(1:nrow(g),1);b[i,j,]=b[g[o,1],g[o,2],]};b[,,3]=b[,,3]*1.5;b[b>1]=1;png(w=M,h=N);par(mar=rep(0,4));plot(0,t="n",xli=c(1,M),yli=c(1,N),xaxs="i",yaxs="i",ax=F);rasterImage(b,1,1,M,N);dev.off()}

With line breaks for legibility:

f=function(n,p){
    a=png::readPNG(p) #use readPNG from package png
    b=a
    N=nrow(a)
    M=ncol(a)
    r=row(a[,,1])
    c=col(a[,,1])
    for(i in 1:N){ #braces can be deleted if all is contained in one line
        for(j in 1:M){
            b[i,j,]=apply(a,3,function(x)mean(x[abs(r-i)+abs(c-j)<=n]))
            }
        }
    for(i in 1:N){ #i'm sure this loop could be shortened
        for(j in 1:M){
            g=which(abs(r-i)+abs(c-j)<=n/2,arr.ind=T)
            o=sample(1:nrow(g),1)
            b[i,j,]=b[g[o,1],g[o,2],]
            }
        }
    b[,,3]=b[,,3]*1.5 #readPNG gives RGB values on a [0,1] range, so no need to round
    b[b>1]=1
    png(w=M,h=N)
    par(mar=rep(0,4))
    plot(0,t="n",xli=c(1,M),yli=c(1,N),xaxs="i",yaxs="i",ax=F)
    rasterImage(b,1,1,M,N)
    dev.off()
    }

Sample input: f(2,"avatar.png")

Results with n=2

My avatar with n=2

... with n=10

with n=10

... with n=20

with n=20

\$\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.