Color wheel

From Rosetta Code
Color wheel is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Task

Write a function to draw a HSV color wheel[1] completely with code.

This is strictly for learning purposes only. It's highly recommended that you use an image in an actual application to actually draw the color wheel   (as procedurally drawing is super slow). This does help you understand how color wheels work and this can easily be used to determine a color value based on a position within a circle.

AppleScript[edit]

 
choose color default color {0, 0, 0, 0}
 

GML[edit]

 
for (var i = 1; i <= 360; i++) {
for (var j = 0; j < 255; j++) {
 
var hue = 255*(i/360);
var saturation = j;
var value = 255;
 
var c = make_colour_hsv(hue,saturation,value);
 
//size of circle determined by how far from the center it is
//if you just draw them too small the circle won't be full.
//it will have patches inside it that didn't get filled in with color
var r = max(1,3*(j/255));
 
//Math for built-in GMS functions
//lengthdir_x(len,dir) = +cos(degtorad(direction))*length;
//lengthdir_y(len,dir) = -sin(degtorad(direction))*length;
draw_circle_colour(x+lengthdir_x(m_radius*(j/255),i),y+lengthdir_y(m_radius*(j/255),i),r,c,c,false);
}
}
 

Kotlin[edit]

We reuse the class in the Bitmap task for this and add a member function to draw the color wheel. To give a more 'wheel-like' image, a constant 'saturation' of 1.0 has been used rather than one which varies in line with distance from the center.

// Version 1.2.41
 
import java.awt.Color
import java.awt.Graphics
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
import kotlin.math.*
 
class BasicBitmapStorage(width: Int, height: Int) {
val image = BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR)
 
fun fill(c: Color) {
val g = image.graphics
g.color = c
g.fillRect(0, 0, image.width, image.height)
}
 
fun setPixel(x: Int, y: Int, c: Color) = image.setRGB(x, y, c.getRGB())
 
fun getPixel(x: Int, y: Int) = Color(image.getRGB(x, y))
 
fun colorWheel() {
val centerX = image.width / 2
val centerY = image.height / 2
val radius = minOf(centerX, centerY)
for (y in 0 until image.height) {
val dy = (y - centerY).toDouble()
for (x in 0 until image.width) {
val dx = (x - centerX).toDouble()
val dist = sqrt(dx * dx + dy * dy)
if (dist <= radius) {
val theta = atan2(dy, dx)
val hue = (theta + PI) / (2.0 * PI)
val rgb = Color.HSBtoRGB(hue.toFloat(), 1.0f, 1.0f)
setPixel(x, y, Color(rgb))
}
}
}
}
}
 
fun main(args: Array<String>) {
val bbs = BasicBitmapStorage(480, 480)
with (bbs) {
fill(Color.white)
colorWheel()
val cwFile = File("Color_wheel.png")
ImageIO.write(image, "png", cwFile)
}
}
 
Output:
Looks like mirror image of Smart BASIC entry 

Perl[edit]

Translation of: Sidef
use Imager;
use Math::Complex qw(cplx i pi);
 
my ($width, $height) = (300, 300);
my $center = cplx($width/2, $height/2);
 
my $img = Imager->new(xsize => $width,
ysize => $height);
 
foreach my $y (0 .. $height - 1) {
foreach my $x (0 .. $width - 1) {
 
my $vec = $center - $x - $y * i;
my $mag = 2 * abs($vec) / $width;
my $dir = (pi + atan2($vec->Re, $vec->Im)) / (2 * pi);
 
$img->setpixel(x => $x, y => $y,
color => {hsv => [360 * $dir, $mag, $mag < 1 ? 1 : 0]});
}
}
 
$img->write(file => 'color_wheel.png');

Perl 6[edit]

Works with: Rakudo version 2016.08
use Image::PNG::Portable;
 
my ($w, $h) = 300, 300;
 
my $out = Image::PNG::Portable.new: :width($w), :height($h);
 
my $center = $w/2 + $h/2*i;
 
color-wheel($out);
 
$out.write: 'Color-wheel-perl6.png';
 
sub color-wheel ( $png ) {
for ^$w -> $x {
for ^$h -> $y {
my $vector = $center - $x - $y*i;
my $magnitude = $vector.abs * 2 / $w;
my $direction = ( π + atan2( |$vector.reals ) ) / τ;
$png.set: $x, $y, |hsv2rgb( $direction, $magnitude, $magnitude < 1 );
}
}
}
 
sub hsv2rgb ( $h, $s, $v ){ # inputs normalized 0-1
my $c = $v * $s;
my $x = $c * (1 - abs( (($h*6) % 2) - 1 ) );
my $m = $v - $c;
my ($r, $g, $b) = do given $h {
when 0..^(1/6) { $c, $x, 0 }
when 1/6..^(1/3) { $x, $c, 0 }
when 1/3..^(1/2) { 0, $c, $x }
when 1/2..^(2/3) { 0, $x, $c }
when 2/3..^(5/6) { $x, 0, $c }
when 5/6..1 { $c, 0, $x }
}
( $r, $g, $b ) = map { (($_+$m) * 255).Int }, $r, $g, $b;
}

Until local image uploading is re-enabled, see Color-wheel-perl6.png

Python[edit]

from PIL import Image
import colorsys
import math
 
if __name__ == "__main__":
 
im = Image.new("RGB", (300,300))
radius = min(im.size)/2.0
centre = im.size[0]/2, im.size[1]/2
pix = im.load()
 
for x in range(im.width):
for y in range(im.height):
rx = x - centre[0]
ry = y - centre[1]
s = ((x - centre[0])**2.0 + (y - centre[1])**2.0)**0.5 / radius
if s <= 1.0:
h = ((math.atan2(ry, rx) / math.pi) + 1.0) / 2.0
rgb = colorsys.hsv_to_rgb(h, s, 1.0)
pix[x,y] = tuple([int(round(c*255.0)) for c in rgb])
 
im.show()

Run BASIC[edit]

' -----------------------------------
' color wheel
' -----------------------------------
global pi
pi = 22 / 7
steps = 1
 
graphic #g, 525, 525
 
 
for x =0 to 525 step steps
for y =0 to 525 step steps
angle = atan2(y - 250, x - 250) * 360 / 2 / pi ' full degrees....
sector = int(angle / 60) ' 60 degree sectors (0 to 5)
slope = (angle mod 60) /60 * 255 ' 1 degree sectors.
 
if sector = 0 then col$ = "255 "; str$( int( slope)); " 0"
if sector = 1 then col$ = str$(int(256 - slope)); " 255 0"
if sector = 2 then col$ = "0 255 "; str$( int( slope))
if sector = 3 then col$ = "0 "; str$( int( 256 -slope)); " 255"
if sector = 4 then col$ = str$(int(slope)); " 0 255"
if sector = 5 then col$ = "255 0 "; str$( int( 256 -slope))
 
red = val( word$( col$, 1))
grn = val( word$( col$, 2))
blu = val( word$( col$, 3))
p = ((x -270)^2 +(y -270)^2)^0.5 / 250
r = min(255,p * red)
g = min(255,p * grn)
b = min(255,p * blu)
if p > 1 then #g "color white" else #g color(r,g,b)
#g "set "; x; " "; y
next y
next x
render #g
end
 
function atan2(y,x)
if (x = 0) and (y <> 0) then
r$ = "Y"
if y > 0 then atan2 = pi /2
if y < 0 then atan2 = 3 * pi /2
end if
 
if y = 0 and (x <> 0) then
r$ = "Y"
if x > 0 then atan2 = 0
if x < 0 then atan2 = pi
end if
 
If r$ <> "Y" then
if x = 0 and y = 0 then
atan2 = 0
else
baseAngle = atn(abs(y) / abs(x))
if x > 0 then
if y > 0 then atan2 = baseAngle
If y < 0 then atan2 = 2 * pi - baseAngle
end if
if x < 0 then
If y > 0 then atan2 = pi - baseAngle
If y < 0 then atan2 = pi + baseAngle
end if
end if
end if
end function

Sidef[edit]

Translation of: Perl 6
require('Imager')
 
var (width, height) = (300, 300)
var center = Complex(width/2 , height/2)
 
var img = %s|Imager|.new(
xsize => width,
ysize => height,
)
 
for y, x in (^height ~X ^width) {
var vector = (center - x - y.i)
var magnitude = (vector.abs * 2 / width)
var direction = ((Num.pi + atan2(vector.real, vector.imag)) / Num.tau)
img.setpixel(
x => x,
y => y,
color => Hash(hsv => [360*direction, magnitude, magnitude < 1 ? 1 : 0])
)
}
 
img.write(file => 'color_wheel.png')

Smart BASIC[edit]

' Runs on iOS
GET SCREEN SIZE sw,sh
xmax=0.45*3/7*(sw+sh)
x0=sw/2!y0=sh/2
twopi=2*3.1415926
GRAPHICS
GRAPHICS CLEAR
DIM triX(1000), triY(1000)
triX(0)=x0 ! triY(0)=y0
steps=INT(1^2*360)+1
dAngle=twopi/steps
dAngle2=dAngle/2
REFRESH OFF
FOR i=0 TO steps-1
pal(i/steps+TintOffset)
ANGLE=i*dAngle
FILL COLOR pal.r,pal.g,pal.b
DRAW COLOR pal.r,pal.g,pal.b
x=x0+(xmax-radius)*COS(ANGLE)
y=y0-(xmax-radius)*SIN(ANGLE)
k=0
FOR j=-dAngle2 TO dAngle2 STEP 0.02
k+=1
triX(k)=x0+xmax*COS(ANGLE+j)
triY(k)=y0-xmax*SIN(ANGLE+j)
NEXT j
k+=1
triX(k)=x0+xmax*COS(ANGLE+dAngle2)
triY(k)=y0-xmax*SIN(ANGLE+dAngle2)
DRAW POLY triX,triY COUNT k+1
FILL POLY triX,triY COUNT k+1
NEXT i
REFRESH ON
END
 
DEF pal(tint)
tint=tint*360
h=(tint%360)/60 ! f=FRACT(h) ! z=1-f ! ic=FLOOR(h)+1
ON ic GOTO s1,s2,s3,s4,s5,s6
s1: r=1 ! g=f ! b=0 ! GOTO done
s2: r=z ! g=1 ! b=0 ! GOTO done
s3: r=0 ! g=1 ! b=f ! GOTO done
s4: r=0 ! g=z ! b=1 ! GOTO done
s5: r=f ! g=0 ! b=1 ! GOTO done
s6: r=1 ! g=0 ! b=z ! done:
END DEF

View the output on Dropbox https://www.dropbox.com/s/g3l5rbywo34bnp6/IMG_4600.PNG?dl=0

zkl[edit]

Uses Image Magick and the PPM class from http://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#zkl

var w=300,h=300,out=PPM(w,h);
colorWheel(out);
out.writeJPGFile("colorWheel.zkl.jpg");
 
fcn colorWheel(ppm){
zero,R:=ppm.w/2, zero;
foreach x,y in (w,h){
v,hue:=(x - zero).toFloat().toPolar(y - zero);
if(v<=R){ // only render in the circle
if((hue = hue.toDeg())<0) hue+=360; // (-pi..pi] to [0..2pi)
s:=v/R; // scale saturation zero at center to 1 at edge
ppm[x,y]=hsv2rgb(hue,1.0,s);
}
}
}
 
fcn hsv2rgb(hue,v,s){ // 0<=H<360, 0<=v(brightness)<=1, 0<=saturation<=1
// --> 24 bit RGB each R,G,B in [0..255]
to24bit:=fcn(r,g,b,m){
r,g,b=((r+m)*255).toInt(),((g+m)*255).toInt(),((b+m)*255).toInt();
r*0x10000 + g*0x100 + b
};
c:=v*s;
x:=c*(1.0 - (hue.toFloat()/60%2 - 1).abs());
m:=v - c;
if (0 <=hue< 60) return(to24bit(c, x, 0.0,m));
else if(60 <=hue<120) return(to24bit(x, c, 0.0,m));
else if(120<=hue<180) return(to24bit(0.0,c, x, m));
else if(180<=hue<240) return(to24bit(0.0,x, c, m));
else if(240<=hue<300) return(to24bit(x, 0.0,c, m));
else return(to24bit(c, 0.0,x, m));
}
Output:

See this image

References[edit]

  1. [1]