Rainbowlify An Image

23

5

This challenge is about gradually shifting hues in an image to make pretty pictures like this:

big Starry Night (original)

Challenge

Write a program or function that takes in two non-negative integers and an image in any common image file format of your choosing (you can take a path to the image or the raw image data).

We'll call the first integer the cycles and the second integer the offset.

We'll also define floating-point step as 360 times cycles divided by the area of the image, or step = 360 * cycles / (image width * image height).

For each pixel P in the image, moving one row at a time, left-to-right, top-to-bottom (i.e. in reading order if the pixels were letters), do the following:

  1. Increase the hue of P by offset degrees (looping around from 360 to 0 if necessary).

  2. Then increase offset by step.

Save, display, or output raw the resulting image in any common image file format.

This procedure incrementally increases the hue of all the pixels in the image, making cycles full loops around the hue rainbow, starting by initially offsetting the hue by offset.

When cycles is 1 and offset is 0, as in the Starry Night image above, the top and bottom rows of pixels have practically no hue shift but in between there's a full color cycle.

Details

  • Cycles can be any non-negative integer but you may assume that offset is from 0 to 359 inclusive.

  • When cycles is 0, every pixel in the image will have its hue shifted by exactly offset since step must be 0 too. (In this case if offset is 0 then the image doesn't change at all.)

  • You may assume cycles and offset are input as floats if desired (i.e. 1.0 instead of 1). (I realize they needn't be integers at all, it just makes the challenge simpler.)

  • "Hue" refers to the RGB color space version, common in HSL/HSV color models.

Examples

Original:

river

Cycles = 1, offset = 0:

river output 1

Cycles = 1, offset = 180:

river output 2

Original:

spheres

Cycles = 2, offset = 60:

spheres output

Original:

sunset
(Thanks ArtOfCode.)

Cycles = 1, offset = 120:

sunset output

Original:

doorknob
(Thanks Doorknob.)

Cycles = 1, offset = 0:

doorknob output 1

Cycles = 4, offset = 0:

doorknob output 2

Cycles = 200, offset = 0:

doorknob output 3

Cycles = 30000, offset = 0:

doorknob output 4

(These images may not be pixel perfect due to imgur compressing them.)

Scoring

The shortest code in bytes wins. Tiebreaker is higher voted answer.

Answers that post their own cool looking test images will get more brownie points from me.

Calvin's Hobbies

Posted 2016-05-03T00:44:07.297

Reputation: 84 000

6This looks like Doorknob is smoking some pot. – Denker – 2016-05-03T00:52:43.417

I presume an array of integers as return value would be included in "or output raw"? – Marv – 2016-05-03T01:12:46.517

2

@Marv No. I mean the raw bytes of the image (in your chosen common format, say ppm) can be piped direct to stdout.

– Calvin's Hobbies – 2016-05-03T01:14:58.610

2Do outputs have to be identical to your examples? I'm getting slightly different images. – James – 2016-05-03T02:37:18.137

1@DrGreenEggsandHamDJ If you can't visually tell a difference then it's probably fine. Pixel perfection is not required (imgur may have lossily compressed my images anyway). – Calvin's Hobbies – 2016-05-03T03:29:37.107

Answers

8

Pyth, 86 bytes, full program

=N.tE7=Z*6*.n0cEl.n'zMmhtS[0255ss*VG.>+Lc-1.tH1 3[.tH1Kc.tH0@3 2_K)d)3.wmmgk~-NZd'z

Pyth doesn't have built-in color space conversions - this is the real deal.

Takes input in the following format on stdin:

input_filename.png
offset
cycles

The output image is written to o.png.


This works by rotating the color cube around its diagonal, and then clamping any values outside of the range.

If a is the angle to rotate by, and r, g, b is the input color, we calculate the new color r', g', b' by:

o = cos(a), i = sin(a) / sqrt(3)
n = (1 - o) / 3
m = [n + o, n - i, n + i]
clamp(c) = max(0, min(255, c))
r' = clamp(r*m[0] + g*m[1] + b*m[2])
g' = clamp(r*m[2] + g*m[0] + b*m[1])
b' = clamp(r*m[1] + g*m[2] + b*m[0])

orlp

Posted 2016-05-03T00:44:07.297

Reputation: 37 067

6

Java (Full program), 491 488 bytes (Thanks @Geobits)

import java.awt.*;import java.io.*;import static javax.imageio.ImageIO.*;class Q{public static void main(String[]v)throws Exception{File f=new File(v[2]);java.awt.image.BufferedImage b=read(f);for(int i=0,j,h=b.getHeight(),w=b.getWidth();i<h;i++)for(j=0;j<w;){Color c=new Color(b.getRGB(j,i));float[]a=new float[3];c.RGBtoHSB(c.getRed(),c.getGreen(),c.getBlue(),a);b.setRGB(j++,i,c.HSBtoRGB((a[0]+Float.valueOf(v[1])/360+(i*w+j)*Float.valueOf(v[0])/w/h)%1,a[1],a[2]));}write(b,"png",f);}}

Ungolfed

import java.awt.*;
import java.io.*;

import static javax.imageio.ImageIO.*;

class A79200 {
    public static void main(String[] v) throws Exception {
        File file = new File(v[2]);
        java.awt.image.BufferedImage image = read(file);
        for (int i = 0, j, height = image.getHeight(), width = image.getWidth(); i < height; i++)
            for (j = 0; j < width; ) {
                Color color = new Color(image.getRGB(j, i));
                float[] arr = new float[3];
                color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), arr);
                image.setRGB(j++, i, color.HSBtoRGB((arr[0] + Float.valueOf(v[1]) / 360 + (i * width + j) * Float.valueOf(v[0]) / width / height) % 1, arr[1], arr[2]));
            }
        write(image, "png", file);
    }
}

Explanation

  • Usage: Pretty straightforward. Compile with java -c Q.java. Run with java Q <cycles> <offset> <imagepath>. Will override the existing image, so be careful.

  • I was gonna make a method only solution at first but I didn't quite know how to handle imports on those, so I figured I'd go full , this is probably not going to win anyways :^)

Results:

Image 1: 1 cycle, 0 offset

1

Image 1: 1 cycle, 180 offset

2

Image 2: 2 cycles, 60 offset

3

Image 3: 1 cycle, 120 offset

4

Image 4: 1 cycle, 0 offset

5

Image 4: 4 cycles, 0 offset

6

Image 4: 200 cycles, 0 offset

7

Bonus: The Starry Night, 1 cycle, 0 offset

enter image description here

Marv

Posted 2016-05-03T00:44:07.297

Reputation: 839

1For future reference, you can do imports for method-only answers just the same as you normally would. Just put them outside the method body and count the bytes. You can also fully-qualify classes instead of importing them if you'll only be needing it once, to save a few bytes in some cases. – Geobits – 2016-05-03T13:02:07.857

Also, is there a reason you're importing java.io.File instead of java.io.* ? – Geobits – 2016-05-03T13:02:42.203

Thanks, good to know. And second, nope, theres no reason. Good point. – Marv – 2016-05-03T16:56:23.770

Why importstatic, not just import? – Solomon Ucko – 2016-05-03T20:41:31.337

1So that I'm able to call ImageIO::read and ImageIO::write without having to prepend ImageIO.: This adds 9 bytes (static .*) but saves 16 (ImageIO. twice). – Marv – 2016-05-03T20:51:22.150

6

Python, 379 bytes

from PIL.Image import*
from colorsys import*
def f(H,c,I):
 i=open(I);x,y=i.size;S=1.*c/(x*y);r,g,b=i.split();R=[];G=[];B=[]
 for x,y,z in zip(r.getdata(),g.getdata(),b.getdata()):
  e=255.;h,s,v=rgb_to_hsv(x/e,y/e,z/e);t=hsv_to_rgb(h+H,s,v);H=H+S%1.;x,y,z=[int(x*e)for x in t];R.append(x);G.append(y);B.append(z)
 p=Image.putdata;p(r,R);p(g,G);p(b,B);return merge('RGB',(r,g,b))

This takes a path to a .jpg as input. It will not work with png, although you can change r,g,b=i.split(); to r,g,b=i.split()[:3]; to load a png image.

Here are some images:

Original:

enter image description here

Offset: 0, Cycles: 4

enter image description here

Original:

enter image description here

Offset 0, 1 cycle:

enter image description here

Original:

enter image description here

Offset 0, 2.5 cycles:

enter image description here

James

Posted 2016-05-03T00:44:07.297

Reputation: 54 537