{-
Explore CIELab colors.
Copyright 2017 Ken Takusagawa
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-}
{-# LANGUAGE LambdaCase, ScopedTypeVariables #-}
module Main where {
import Data.Word(Word8);
import qualified Data.Colour.CIE.Illuminant;
import Data.Colour.CIE(Chromaticity,lightness,cieLABView,cieLAB);
import qualified Data.Colour.SRGB.Linear as Linear;
import Data.Colour.SRGB(sRGB,channelRed,channelGreen,channelBlue,Colour,sRGB24,RGB,toSRGB24,toSRGB);
import Data.Tuple.Curry(uncurryN);
import Control.Monad(join,msum,MonadPlus,mzero,guard);
import System.Environment(getArgs);
import Data.List(genericTake,genericIndex,genericLength,genericDrop,group,sortBy,intersperse,genericReplicate);
import Control.Exception(assert);
import Data.Maybe(fromJust);
import Codec.Picture(writePng,encodePng);
import Codec.Picture.Types(PixelRGBF(..),MutableImage,createMutableImage,Pixel,writePixel,PixelRGB8(..),Image,freezeImage,pixelMap);
import Control.Monad.ST(ST,runST);
import Control.Monad.Primitive(PrimState,PrimMonad);
import System.Random(randomRIO,randomIO,setStdGen,mkStdGen);
import Debug.Trace(trace);
import qualified Data.ByteString.Lazy as ByteString;
import qualified Data.Colour.Names as Color;
import Control.Arrow((***));
main :: IO();
main = getArgs >>= \case {
["band"] -> outppm_band rgbcolors;
-- uses the hardcoded lightness
["palette",n] -> outppm_band
$ map (get_color . nphi)
$ take (read n)
$ enumFrom 0;
["plot"] -> mapM_ putStrLn $ map flow rgbcolors; -- gets the shape of the butterfly for plotting in octave
["test-juicy"] -> bar;
["butterfly"] -> many_pixel; -- most colorful butterfly
-- the edge of the gamut, by angle on a given lightness plane
["lab",width,l] -> outppm_band
$ map (ab_find $ Lightness $ read l)
$ range01 $ read width;
-- perceptually uniform
["pband",l] -> outppm_band
$ all_edge $ Lightness $ read l;
-- at a fixed (hardcoded) lightness
["pbandf"] -> outppm_band $ all_colors;
["manylab",width,height] -> many_lab (read width) (read height);
["edgelength",l] -> print $ length $ all_edge $ Lightness $ read l;
["goline-scaled",n] -> mapM_ print $ scaled_pband (line_to_light $ read n) numColumns;
-- calculates the points on the perimeter, at perceptually equidistant fenceposts, in AB space
-- join (***) is an idiom for mapTuple;
["perim",n] -> mapM_ print $ map (join (***) (realToFrac::Double->Float)) $ all_ab $ line_to_light $ read n;
["slice",l] -> slice $ Lightness $ read l;
["lof",r,g,b] -> print $ lightness_of (read r)(read g)(read b); -- 0..1
["rslice",l] -> radial_slice $ Lightness $ read l;
["islice",n] -> radial_slice $ genericIndex lightness_index ((read n)::Integer);
["prainbow"] -> outppm_band all_rainbow;
["urainbow"] -> outppm_band uncorrected_rainbow;
["urainbown",n] -> outppm_band $ uncorrected_rainbow_of_width $ read n;
["read-goline"] -> all_read_goline;
["frame", w, h] -> out_rainbow_frame (read w) (read h);
["circle",shift] -> out_rainbow_circle $ read shift; -- 0..6
["read-perim"] -> all_read_perim;
["rainbow-shift",width,shift] -> rainbow_shift_plot (read width) (read shift);
["prainbow-disc", shift] -> out_perceptually_uniform_rainbow_circle $ read shift;
_ -> undefined;
};
-- this is to avoid warnings that trace is unused
dummy :: ();
dummy = trace "" ();
three_range :: [(Word8, Word8, Word8)];
three_range = let {
c :: Word8;
c = maxBound;
a :: [Word8];
a = enumFromTo 0 c;
b :: [Word8];
b = reverse a;
} in tail -- remove the circular duplicate
$ map head $ group -- remove duplicates
-- ends at 00c
$ do {x :: Word8 <- a;return (0,x,c);} -- 0cc
++ do {x :: Word8 <- b;return (0,c,x);} -- 0c0
++ do {x :: Word8 <- a;return (x,c,0);} -- cc0
++ do {x :: Word8 <- b;return (c,x,0);} -- c00
++ do {x :: Word8 <- a;return (c,0,x);} -- c0c
++ do {x :: Word8 <- b;return (x,0,c);} -- 00c
;
continuous3 :: Double -> (Double,Double,Double);
continuous3 x = let {
pieces :: [Double -> (Double, Double, Double)];
pieces =
[\t -> (0 ,t ,1)
,\t -> (0 ,1 ,1-t)
,\t -> (t ,1 ,0)
,\t -> (1 ,1-t,0)
,\t -> (1 ,0 ,t)
,\t -> (1-t,0 ,1)
];
npieces :: [(Double, Double -> (Double, Double, Double))];
npieces = zip (map fromInteger [(0::Integer)..]) pieces;
} in assert (0 <= x) $ if (6 < x) then continuous3 $ x-6 else
fromJust $ msum (map (eval_piece x) npieces);
eval_piece :: (MonadPlus m) => Double -> (Double,Double -> b) -> m b;
eval_piece x (z,f) = if x <= z+1 then
return $ f $ x - z else mzero;
type C = Colour Double;
tupleToColor :: (Word8,Word8,Word8) -> C;
tupleToColor = uncurryN sRGB24;
rgbcolors :: [C];
rgbcolors = map tupleToColor three_range;
white_point :: Chromaticity Double;
white_point=Data.Colour.CIE.Illuminant.d65;
-- the darkest color is solid blue
-- the lightest color is solid yellow
darkest :: Lightness;
darkest = Lightness $ minimum $ map (lightness white_point) rgbcolors;
-- see note below about out-of-gamut RGB colors
scale_color1 :: C -> C;
scale_color1 c = let {
(l,a,b) = cieLABView white_point c;
} in assert (darkest <= Lightness l) $ cieLAB_l darkest a b;
in01 :: Double -> Bool;
in01 x = 0<=x && x<=1;
scale_color_M :: forall m . (MonadPlus m) => C -> m C;
scale_color_M c1 = let {
c :: C;
c = scale_color1 c1;
} in do {
mapM_ (guard . in01) $ rgblist $ Linear.toRGB c;
return c;
};
-- a failure
step1fail :: [C];
step1fail = map scale_color1 rgbcolors;
circular_differences :: [a] -> [(a,a)];
circular_differences l = zip l (tail l ++ l);
color_difference :: C -> C -> Double;
color_difference x y = distance2 (list3 $ cieLABView white_point x) (list3 $ cieLABView white_point y);
list3 :: (a,a,a) -> [a];
list3 (x,y,z) = [x,y,z];
mapTuple3 :: (a -> b) -> (a,a,a) -> (b,b,b);
mapTuple3 f (x,y,z) = (f x, f y, f z);
distance2 :: (Num a) => [a] -> [a] -> a;
distance2 p q = sum $ map (\x -> x*x) $ zipWith (-) p q;
sortWith :: (Ord b) => (a -> b) -> [a] -> [a];
sortWith f = sortBy (\x y -> compare (f x) (f y));
-- fastest speed 0,72,196
-- slowest speed 7,0,255
speeds :: [C] -> [(Double,RGB Word8)];
speeds cs = do {
(x,y) <- circular_differences cs;
return (color_difference x y,toSRGB24 x);
};
fastest :: Double;
fastest = maximum $ map (uncurry color_difference) $circular_differences rgbcolors;
-- step1;
-- should be a small number
check_continuous :: Double;
check_continuous = let {
f :: Double -> (Word8,Word8,Word8) -> Double;
f n www = distance2 (map (* 255) $ list3 $ continuous3 $ get_t n)
(map fromIntegral $ list3 www);
get_t :: Double -> Double;
get_t n = 6*n/(fromIntegral $ length three_range);
} in maximum $ zipWith f [1..] three_range; -- start from 1 because tail in three_range
epsilon :: Double;
epsilon = 1e-11;
root_bisect :: (Double -> Double) -> Double -> Double -> Double;
root_bisect f left right =
let {mid = (left+right)/2;
z = f mid;
} in -- trace (show mid) $
if (abs (left - right)) C;
continuous3rgb t = let {
(x,y,z) = continuous3 t;
} in sRGB x y z;
desired_difference :: Double;
-- fastest is about 0.585; (for 1st try failure)
-- in the saturated rainbow, fastest is about 0.656;
-- desired_difference = fastest;
desired_difference = 0.1;
-- a failure
continuous_from_failure :: Double -> Double -> Double ;
continuous_from_failure x y = let {
f :: Double -> C;
f = scale_color1 . continuous3rgb }
in step_the_desired_distance (f x) (f y);
tstep :: Double;
tstep = 1/255; -- magic, = length three_range / 6
root_find_from :: (Double -> Double) -> Double -> Double;
root_find_from f left = let { right = left + tstep*0.1 } in
if (f right>0) then root_bisect f left right
else root_find_from f right;
-- next_t :: Double -> Double;
-- next_t = next_x continuous_from;
next_x :: (Double -> Double -> Double) -> Double -> Double;
next_x distance t = root_find_from (distance t) t;
-- all_t :: [Double];
-- all_t = takeWhile (<6) $ iterate next_t 0;
-- distance from the last color to the first color, offset fastest
-- edge_effect_error :: Double;
-- edge_effect_error = (continuous_from 0 $ last all_t)/desired_difference;
all_colors :: [C];
-- This fails terribly due to weirdness of Lab colors.
-- Taking a color, bringing down its lightness, results in
-- out-of-gamut RGB colors all the time, at the lightness of blue (darkest).
-- I do not fully understand why;
-- all_colors = map (scale_color . continuous3rgb) all_t;
all_colors = all_edge
$ line_to_light 546; -- discovered to have the longest perimeter
-- (Lightness 50) ;
-- $ Lightness darkest ;
unlist3 :: [a] -> (a,a,a);
unlist3 [x,y,z] = (x,y,z);
unlist3 _ = undefined;
quantize :: C -> C;
quantize = (uncurryN sRGB24). unlist3. rgblist. toSRGB24;
rgblist :: RGB a -> [a];
rgblist c = [channelRed c,channelGreen c,channelBlue c];
outcolor :: C -> String;
outcolor = concat . intersperse " " . map show . rgblist . toSRGB24;
phi :: Double;
phi = 0.5*(1+sqrt(5));
frac :: Double -> Double;
frac x = x - fromInteger( floor x);
nphi :: Integer -> Double;
nphi n = frac $ (fromIntegral n)*phi;
get_color_index :: Double -> Integer;
get_color_index x = if x==1 then pred $ genericLength all_colors
else (floor $ x*(fromIntegral $ length all_colors));
get_color :: Double -> C;
get_color x = genericIndex all_colors $ get_color_index x;
flow :: C -> String;
flow c = let {
(_,a,b) = cieLABView white_point c;
} in (show a)++" "++(show b);
foo :: forall s . ST s (Image PixelRGBF);
foo = do {
pic :: MutableImage s PixelRGBF <- createMutableImage 100 100 $ PixelRGBF 0 0 0;
writePixel pic 50 50 $ PixelRGBF 0 0.8 0;
freezeImage pic;
};
bar2 :: IO();
bar2 = writePng "/mit/bitbucket/www/ss1.png" $ pixelMap quantize_pixel $ runST foo;
-- origin is top left
bar :: IO();
bar = do {
pic :: MutableImage (PrimState IO) PixelRGBF <- createMutableImage 100 100 $ PixelRGBF 0 0 0;
writePixel pic 20 60 $ PixelRGBF 0 0.8 0;
freezeImage pic >>= writePng "/mit/bitbucket/www/ss1.png" . pixelMap quantize_pixel;
};
addpoint :: IO ((Double,Double),PixelRGBF);
addpoint = do {
c :: C <- randomRIO (0,6) >>= return . continuous3rgb;
let {(_,a,b) = cieLABView white_point c};
t :: Double <- randomIO >>= return . sqrt; --sqrt pushes toward edge
let {v :: [Double];v = map (realToFrac .(*t)) [a,b]};
return ((v!!0,v!!1),fpixel c);
};
image_width :: Integer;
image_width = 4800;
image_height :: Integer;
image_height = image_width;
-- usually 1000000
num_rays :: Integer;
num_rays = 20000;
offset :: Double;
offset = 128;
scale_location :: (Double,Double) -> (Double,Double);
scale_location (x,y) = ((x+offset)/(2*offset)*fromIntegral image_width
,(1-(y+offset)/(2*offset))*fromIntegral image_height
);
one_add_pixel :: MutableImage (PrimState IO) PixelRGBF -> IO();
one_add_pixel pic = do {
(p,c) <- addpoint;
let {(x,y) = scale_location p;};
writePixel pic (round x) (round y) c;
};
many_pixel :: IO ();
many_pixel = do {
setStdGen $ mkStdGen 1;
_ :: Double <- randomIO; -- avoid the first sample;
pic :: MutableImage (PrimState IO) PixelRGBF <- standard_image $ PixelRGBF 0 0 0;
-- replicateM_ 10000000 $ one_add_pixel pic;
genericReplicateM_ num_rays $ one_rainbow_ray pic;
outpic pic;
-- freezeImage pic >>= writePng "/mit/kenta/0/ss2.png" . pixelMap quantize_pixel;
};
fpixel :: C -> PixelRGBF;
fpixel = uncurryN PixelRGBF . unlist3 . map realToFrac . rgblist . toSRGB;
-- supposedly realToFrac should be optimized via RULES to use GHC.Float.double2float
quantize_pixel :: PixelRGBF -> PixelRGB8;
quantize_pixel (PixelRGBF x y z) = let {
q :: Float -> Word8;
q t
| t>1 = 0
| t==1 = 255
| t<0 = 255
| True = floor $ t*256;
}
in uncurryN PixelRGB8 $ mapTuple3 q (x,y,z);
draw_line :: (Pixel px,PrimMonad m) => MutableImage (PrimState m) px -> (Double,Double) -> (Double,Double) -> px -> m();
draw_line pic a b c = mapM_
(\(p,q) -> writePixel pic (fromIntegral p) (fromIntegral q) c) $ line_tuples a b;
line_tuples :: (Double,Double) -> (Double,Double) -> [(Integer,Integer)];
line_tuples (ax,ay) (bx,by) = let {
x = bx-ax;
y = by-ay;
maxt = max (abs x)(abs y);
} in do {
t :: Integer <- enumFromTo 0 $ round maxt;
let { d :: Double = (fromIntegral t)/maxt};
return (round $ ax+d*x,round $ ay+d*y);
};
{-
in if x < y
then map Tuple.swap $ line_tuples (swap a) (swap b)
else do {
t::Integer <- enumFromTo 0 $ round x;
-- this aint bresenham
return (t, round $ y*(fromIntegral t)/x);
};
-}
cieLAB_l :: Lightness -> Double -> Double -> C;
cieLAB_l (Lightness l) = cieLAB white_point l;
ab_find2 :: Lightness -> Double -> (Double,Double);
ab_find2 l theta = let {
a = cos (2*pi*theta);
b = sin (2*pi*theta);
mkcolor :: Double -> C;
mkcolor t = cieLAB_l l (t*a) (t*b);
mktuple :: Double -> (Double,Double);
mktuple t = (t*a,t*b);
f :: Double -> Double;
-- doing it backward because we want the last point just inside, according to "right" in root_bisect
f t = if and $ map in01 $ rgblist $ toSRGB $ mkcolor t then 1 else -1;
far = 200
} in assert (f far<0)
$ assert (0 < f 0)
$ mktuple $ root_bisect f far 0;
ab_find :: Lightness -> Double -> C;
ab_find l theta = (uncurry $ cieLAB_l l) $ ab_find2 l theta;
range01 :: Integer -> [Double];
range01 width =
map (\(i::Double) -> i/(fromIntegral width))
$ genericTake width
$ enumFrom 0;
edge_from :: Lightness -> Double -> Double -> Double;
edge_from l x y = step_the_desired_distance (ab_find l x)(ab_find l y);
newtype Lightness = Lightness Double deriving (Show, Ord, Eq);
all_edge :: Lightness -> [C];
all_edge l = map (uncurry $ cieLAB_l l) $ all_ab l;
-- map (ab_find l) $ takeWhile (<1) $ iterate (next_x $ edge_from l ) 0;
all_ab :: Lightness -> [(Double,Double)];
all_ab l = map (ab_find2 l) $ takeWhile (<1) $ iterate (next_x $ edge_from l ) 0;
outppm_band :: [C] -> IO();
outppm_band l = mapM_ putStrLn $ ["P3"
,show $ length l
,"1","255"]
++ map outcolor l;
red_lightness :: Lightness;
red_lightness = Lightness $ lightness white_point $ sRGB 1 0 0;
many_lab :: Integer -> Integer -> IO();
many_lab width height = do {
putStrLn "P3";
print width;
print height;
putStrLn "255";
sequence_ $ do {
l :: Lightness <- range01 height >>= return . Lightness . (*100);
return$ mapM_ putStrLn $ map outcolor $ map (ab_find l) $ range01 width;
}
};
length_band :: Lightness -> Integer;
length_band (Lightness 0) = 0;
length_band l = genericLength $ all_edge l;
scaled_pband :: Lightness -> Integer -> [C];
scaled_pband (Lightness 0) n = genericReplicate n Color.black;
scaled_pband l n = let {
band :: [C];
band = all_edge l;
index :: Double -> Integer;
index x = floor $ x*(fromIntegral $ length band);
} in trace (show l ++ " " ++ (show $ length band)) $ do {
t <- range01 n;
return $ genericIndex band $ index t;
};
numLines :: Integer;
numLines=1024; -- 0 and 1024 not used
line_to_light :: Integer -> Lightness;
line_to_light n = Lightness $ (fromInteger n)/(fromInteger numLines)*100;
numColumns :: Integer;
numColumns = 1024;
lightness_of :: Double -> Double -> Double -> Lightness;
lightness_of r g b = Lightness $ lightness white_point $ sRGB r g b;
lightness_plot :: Lightness -> Double -> Double -> C;
lightness_plot l a b = let {
c :: C;
c = cieLAB_l l a b;
} in if and $ map in01 $ rgblist $ toSRGB c then c else Color.black;
slice :: Lightness -> IO ();
slice l = do {
let {size :: Integer; size= image_width};
let {grow :: Double -> Double;
grow x = (realToFrac offset)*(x*2-1);
};
putStrLn "P3";
print size;print size;
putStrLn "255";
sequence_ $ do {
b <- range01 size >>= return . grow . (1-);
a <- range01 size >>= return . grow;
return $ putStrLn $ outcolor $ lightness_plot l a b;
};
};
one_radial_ray :: MutableImage (PrimState IO) PixelRGBF -> Lightness -> IO();
one_radial_ray pic l = do {
-- nphi might be better than randomIO
c :: C <- randomIO >>= return . ab_find l;
let {(_,a,b) = cieLABView white_point c;
q = scale_location (a,b);
};
if (sqrt(a*a+b*b)>200) then
error $ "one_radial_ray " ++ show (l,a,b);
else
return ();
draw_line pic (scale_location (0,0)) q $ fpixel c;
};
outpic :: MutableImage (PrimState IO) PixelRGBF -> IO();
outpic pic = freezeImage pic >>= ByteString.putStr . encodePng . pixelMap quantize_pixel;
radial_slice :: Lightness -> IO();
radial_slice l = do {
setStdGen $ mkStdGen 1;
_ :: Double <- randomIO; -- avoid the first sample;
let {bg :: Float = if l > Lightness 50 then 0 else 1};
pic :: MutableImage (PrimState IO) PixelRGBF <- standard_image $ PixelRGBF bg bg bg;
-- 10000 takes about 17 seconds compiled
-- 100000 is probably fine
-- 1000000 is overkill
genericReplicateM_ num_rays $ one_radial_ray pic l;
outpic pic;
};
-- not in Control.Monad!
genericReplicateM_ :: (Monad m, Integral i) => i -> m a -> m ();
genericReplicateM_ n x = sequence_ (genericReplicate n x);
one_rainbow_ray :: MutableImage (PrimState IO) PixelRGBF -> IO();
one_rainbow_ray pic = do {
c :: C <- randomRIO (0,6) >>= return . continuous3rgb;
let {(_,a,b) = cieLABView white_point c;
q = scale_location (a,b);
};
draw_line pic (scale_location (0,0)) q $ fpixel c;
};
rainbow_distance :: Double -> Double -> Double;
rainbow_distance p q = step_the_desired_distance (continuous3rainbow p)(continuous3rainbow q);
step_the_desired_distance :: C -> C -> Double;
step_the_desired_distance p q = color_difference p q - desired_difference;
-- perceptually uniform rainbow
all_rainbow :: [C];
all_rainbow = map continuous3rainbow $ takeWhile (<6) $ iterate (next_x rainbow_distance) 0;
rainbow_in_proper_order :: (a,a,a) -> (a,a,a);
rainbow_in_proper_order (x,y,z) = (z,y,x);
continuous3rainbow :: Double -> C;
continuous3rainbow t = uncurryN sRGB $ rainbow_in_proper_order $ continuous3 t;
uncorrected_rainbow :: [C];
uncorrected_rainbow = map tupleToColor $ map rainbow_in_proper_order three_range;
read_goline :: Integer -> IO [C];
read_goline n = readFile ("goline."++ show n) >>= return . map read .lines;
read_and_emit :: Integer -> IO ();
read_and_emit n = read_goline n >>= mapM_ (putStrLn .outcolor);
-- emits the giant collection of perceptually scaled colors by lightness
all_read_goline :: IO ();
all_read_goline = do {
putStrLn "P3\n1024 1023\n255";
mapM_ read_and_emit [1.. (pred numLines)];
};
to_float_color :: C -> Colour Float;
to_float_color = uncurryN Linear.rgb . unlist3 . map realToFrac . rgblist . Linear.toRGB;
colors_by_lightness :: [C];
colors_by_lightness = [sRGB 0 0 0, sRGB 0 0 1, sRGB 1 0 0, sRGB 1 0 1, sRGB 0 1 0, sRGB 0 1 1, sRGB 1 1 0, sRGB 1 1 1];
and_midpoints :: (Num a, Fractional a) => [a] -> [a];
and_midpoints [] = [];
and_midpoints [x] = [x];
and_midpoints (x: yrest@(y:_)) = x:((x+y)/2):(and_midpoints yrest);
lightness_index :: [Lightness];
lightness_index = map Lightness $ and_midpoints $ map (lightness white_point) colors_by_lightness;
distance_from_edge :: (Ord a, Num a) => a -> a -> a;
distance_from_edge t y = assert (0<=t) $ assert (t a -> a -> (a,a) -> a;
frame_distance x y (x1, y1) = min (distance_from_edge x x1)
(distance_from_edge y y1);
distance_color_scaling :: Double;
distance_color_scaling = 30;
distance_to_color :: Integer -> C;
distance_to_color i = let {z = fromIntegral i / distance_color_scaling } in
assert (z>=0) $ if z <6 then continuous3rainbow $ colorshift $ z else Color.black;
colorshift :: Double -> Double;
colorshift = (6*) . frac . (+ (1/6)) . (/6);
out_rainbow_frame :: Integer -> Integer -> IO ();
out_rainbow_frame width height = do {
putStrLn "P3";
print width;
print height;
putStrLn "255";
sequence_ $ do {
j :: Integer <- genericTake height $ enumFrom 0;
i :: Integer <- genericTake width $ enumFrom 0;
return $ putStrLn $ outcolor $ distance_to_color $ frame_distance i j (width,height);
};
};
standard_image :: (Pixel px, PrimMonad m) => px -> m (MutableImage (PrimState m) px);
standard_image bg = createMutableImage (fromIntegral image_width) (fromIntegral image_height) bg;
out_rainbow_circle :: Double -> IO();
out_rainbow_circle shift = do {
pic :: MutableImage (PrimState IO) PixelRGBF <- standard_image $ PixelRGBF 0 0 0;
sequence_ $ do {
t <- range01 num_rays;
let {
cx = fromIntegral image_width /2;
cy = fromIntegral image_height /2;
r = 0.8* min cx cy;
th = 2*pi*t;
};
return $ draw_line pic (cx,cy) (cx+r*sin th, cy+r*cos th) $ fpixel $ continuous3rainbow $ shift + t*6;
};
outpic pic;
};
tolab :: Lightness -> (Double, Double) -> C;
tolab l (a,b) = cieLAB_l l a b;
read_perim :: Integer -> IO [C];
read_perim n = readFile ("perim."++show n) >>= return . map (tolab (line_to_light n) . read) . lines;
smooth_index :: Integer -> Double -> Integer;
smooth_index n x = floor $ x * fromIntegral n;
c_rescale :: Integer -> [a] -> [a];
c_rescale desired l = do {
t :: Double <- range01 desired;
return $ genericIndex l $ smooth_index (genericLength l) t;
};
all_read_perim :: IO();
all_read_perim = do {
putStrLn "P3";
print numColumns; print numLines; putStrLn "255";
genericReplicateM_ numColumns (putStrLn $ outcolor Color.black);
mapM_ (\n -> read_perim n >>= mapM_ (putStrLn . outcolor) . c_rescale numColumns) [1 .. pred numLines];
};
uncorrected_rainbow_of_width :: Integer -> [C];
uncorrected_rainbow_of_width n = map (continuous3rainbow . (*6)) $ range01 n;
circular_shift :: Integer -> [a] -> [a];
circular_shift n l = genericDrop n l ++ genericTake n l;
rainbow_shift_diff :: Integer -> [Double];
rainbow_shift_diff n = map sqrt $ zipWith color_difference all_rainbow (circular_shift n all_rainbow);
range_plot :: Integer -> [Double] -> IO();
range_plot width l = do {
putStrLn "P1";
print width;
print $ length l;
mapM_ (\x ->
let {
n1 :: Integer;
n1 = round $ fromIntegral width * x;
n2 = width - n1;
} in do {
genericReplicateM_ n1 $ putStrLn "1";
genericReplicateM_ n2 $ putStrLn "0";
}) l;
};
rainbow_shift_plot :: Integer -> Integer -> IO();
rainbow_shift_plot width n = let {
xs = rainbow_shift_diff n;
} in range_plot width $ map (/ (maximum xs)) xs;
out_generic_circle :: (Double -> C) -> IO();
out_generic_circle f = do {
pic :: MutableImage (PrimState IO) PixelRGBF <- standard_image $ PixelRGBF 0 0 0;
sequence_ $ do {
t <- range01 num_rays;
let {
cx = fromIntegral image_width /2;
cy = fromIntegral image_height /2;
r = 0.8* min cx cy;
th = 2*pi*t;
};
return $ draw_line pic (cx,cy) (cx+r*sin th, cy+r*cos th) $ fpixel $ f t;
};
outpic pic;
};
perceptually_uniform_rainbow :: Double -> C;
perceptually_uniform_rainbow t = index_by_float all_rainbow t;
index_by_float :: [a] -> Double -> a;
index_by_float band x = genericIndex band $ (floor $ (frac x)*(fromIntegral $ length band)::Integer);
out_perceptually_uniform_rainbow_circle :: Double -> IO ();
out_perceptually_uniform_rainbow_circle shift = out_generic_circle (\t -> perceptually_uniform_rainbow $ t-shift);
} -- end of program