# Rainbowlify An Image

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

(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:

Cycles = 1, offset = 0:

Cycles = 1, offset = 180:

Original:

Cycles = 2, offset = 60:

Original:

(Thanks ArtOfCode.)

Cycles = 1, offset = 120:

Original:

(Thanks Doorknob.)

Cycles = 1, offset = 0:

Cycles = 4, offset = 0:

Cycles = 200, offset = 0:

Cycles = 30000, offset = 0:

(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.

• This looks like Doorknob is smoking some pot. Commented May 3, 2016 at 0:52
• I presume an array of integers as return value would be included in "or output raw"?
– Marv
Commented May 3, 2016 at 1:12
• @Marv No. I mean the raw bytes of the image (in your chosen common format, say ppm) can be piped direct to stdout. Commented May 3, 2016 at 1:14
• Do outputs have to be identical to your examples? I'm getting slightly different images. Commented May 3, 2016 at 2:37
• @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). Commented May 3, 2016 at 3:29

# 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])


# 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:

Offset: 0, Cycles: 4

Original:

Offset 0, 1 cycle:

Original:

Offset 0, 2.5 cycles:

# 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]);
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

Image 1: 1 cycle, 180 offset

Image 2: 2 cycles, 60 offset

Image 3: 1 cycle, 120 offset

Image 4: 1 cycle, 0 offset

Image 4: 4 cycles, 0 offset

Image 4: 200 cycles, 0 offset

Bonus: The Starry Night, 1 cycle, 0 offset

• For 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. Commented May 3, 2016 at 13:02
• Also, is there a reason you're importing java.io.File instead of java.io.* ? Commented May 3, 2016 at 13:02
• Thanks, good to know. And second, nope, theres no reason. Good point.
– Marv
Commented May 3, 2016 at 16:56
• Why import **static**, not just import? Commented May 3, 2016 at 20:41
• So 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
Commented May 3, 2016 at 20:51