﻿

Colour cycling

Cycling colours with different methods gives results that satisfy certain criteria.

For cyclic animations of images of fractal noise, or images that have been distorted by fractal noise (see Fractal noise animations), I needed an ImageMagick process that would take a noise image and a percentage. At slightly different percentages it would create slightly different noise images, but 100% would give the same result as 0% (making it cyclic).

The problem

For convenience, this page considers just one channel, so the examples are grayscale. The same methods can be applied to three channels independently.

We have a grayscale image with values that are distributed evenly from black to white, and the mean is 50%. All values are equally likely. We want to add a value X% (0 <= X <= 100) to every pixel, to create a new image. How can we do this? There is an obvious method: "-evaluate AddModulus" does the job. This is quick and easy. If we add a small value, such as 1%, most pixels will change a small amount. Unfortunately, some pixels will change a large amount; pixels that were slightly beneath white will become slightly above black. This may be undesirable.

So we would like a number of criteria to be satisfied.

• Adding zero percentage will not change any pixels.
• Adding a non-zero percentage will change all the pixels.
• Adding a small percentage will change all the pixels only slightly. (No sudden changes.)
• Aside from any sudden changes, all pixels change by the same amount.
• If the input mean is 50%, then so is the output.
• The change is reversible by some method. For a given output, we might want to derive the input that made it. Ideally, adding X% then (100-X%) won't change any pixels. If different input values can create the same output value, the process is not reversible.

Other criteria may be desirable, for example that the standard deviation should be constant.

I don't think any method satisfies all criteria. In particular, I don't think an animated technique can have no sudden changes while also being reversible.

(For some purposes, strict reversibility is not important. Provided we can find some input that gives the desired output, we don't care if other inputs can also give that same output.)

The methods

Create a simple image that will be animated by colour-cycling, using each method.

 ```del ccy_*.miff set SRC=ccy_src.png %IMG7%magick ^ -seed 1234 ^ -size 200x200 ^ ( xc: +noise Random ^ -channel R -separate +channel ) ^ gradient: ^ -append +repage ^ %SRC%```

For each method, we can create an animation. A loop is run with %%i as the loop counter. Environment variable LZ is set to this value with six digits and leading zeros. (I always use six digits for frame filenames, although six is overkill here.) magick creates each frame file. When the loop is finished, another magick reads all the frame files and creates an animated GIF, which is shown.

The top half of the animation shows what the method does to a noise image. The bottom half shows what the method does to a gradient image, which helps to explain the process.

Each method also calls the script ccy_stats.bat, which calculates some statistics for that method.

• zeroAE is the number of pixels that change between the source and frame zero. If this is "0", the "no change at zero" criteria is satisfied.
• zeroRMSE is the RMSE between the source and frame zero. Methods such as sinarc2 have a high zeroAE, but the difference is invisible.
• ch20 is the number of pixels that change between frames 0 and 20. If this number is 80000 (=pixels in the source, 200x400), "non-zero changes all" is satisfied for frame 20, and may be satisfied for all frames.
• pae1820 is the peak absolute error between frames 18 and 20. The number in parenthesis has a range 0.0 to 1.0. If it is close to 0.0 (eg under 0.05) then "no sudden changes" may be satisfied. (Method "amneg" has a sudden change, but not between frames 18 and 20, so it falsely appears to pass this text.)
• mae1820 is the mean absolute error. This will be less than pae1820. If it is close to zero, "pixels change equally" may be satisfied.
• mean20 is the mean value of frame 20. If this is close to 0.5, "mean 50%" may be satisfied.

The statistics could be collected for every pair of adjacent frames.

Commands here use Windows cmd arithmetic, which assumes all numbers are integers. For real use, we should use floating-point arithmetic.

We will generate frames from 0 to 99, but skip even-numbered frames to save time and space.

`set FOR_ARGS=0,2,99`
 ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! %IMG7%magick ^ %SRC% ^ -evaluate AddModulus %%i%% ^ ccy_am_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_am_*.miff ^ ccy_am.gif call %PICTBAT%ccy_stats am``` ```0 zeroAE=0 (0) zeroRMSE=80000 ch20=64241 (0.980255) pae1820=2575.29 (0.0392964) mae1820= mean20=0.49982``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A PC=2*%%i %IMG7%magick ^ %SRC% ^ -evaluate AddModulus !PC!%% ^ ccy_am2_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_am2_*.miff ^ ccy_am2.gif call %PICTBAT%ccy_stats am2``` ```0 zeroAE=0 (0) zeroRMSE=80000 ch20=62930 (0.960251) pae1820=5116.73 (0.0780762) mae1820= mean20=0.498832``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! %IMG7%magick ^ %SRC% ^ -evaluate AddModulus %%i%% ^ -solarize 50%% ^ -evaluate Multiply 2 ^ ccy_amsol_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_amsol_*.miff ^ ccy_amsol.gif call %PICTBAT%ccy_stats amsol``` ```79800 zeroAE=26738.8 (0.408008) zeroRMSE=79999 ch20=2639.96 (0.0402832) pae1820=2568.86 (0.0391982) mae1820= mean20=0.499046``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A PC=%%i set /A NEGPC=100-!PC! %IMG7%magick ^ %SRC% ^ ^( -clone 0 -evaluate AddModulus !PC!%% ^) ^ ^( -clone 1 -negate ^) ^ ^( -clone 0 -threshold !NEGPC!%% ^) ^ -delete 0 ^ -composite ^ ccy_amneg_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_amneg_*.miff ^ ccy_amneg.gif call %PICTBAT%ccy_stats amneg``` ```0 zeroAE=0 (0) zeroRMSE=80000 ch20=1327.98 (0.0202637) pae1820=1297.5 (0.0197986) mae1820= mean20=0.659339``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A PC=%%i set /A NEGPC=100-!PC! %IMG7%magick ^ %SRC% ^ ^( -clone 0 -evaluate AddModulus !PC!%% ^) ^ ^( -clone 1 -negate ^) ^ ^( -clone 0 -threshold !NEGPC!%% ^) ^ -delete 0 ^ -composite ^ -auto-level ^ -auto-gamma ^ ccy_amnegaa_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_amnegaa_*.miff ^ ccy_amnegaa.gif call %PICTBAT%ccy_stats amnegaa``` ```79167 zeroAE=29.3111 (0.000447259) zeroRMSE=79763 ch20=2559.96 (0.0390625) pae1820=957.202 (0.014606) mae1820= mean20=0.524423``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A PC=2*%%i set /A NEGPC=100-!PC! if /I !NEGPC! LEQ 0 ( set /A NEGPC=-!NEGPC! set /A PC=200-!PC! ) %IMG7%magick ^ %SRC% ^ ^( -clone 0 -evaluate AddModulus !PC!%% ^) ^ ^( -clone 1 -negate ^) ^ ^( -clone 0 -threshold !NEGPC!%% ^) ^ -delete 0 ^ -composite ^ ccy_amneg2_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_amneg2_*.miff ^ ccy_amneg2.gif call %PICTBAT%ccy_stats amneg2``` ```0 zeroAE=0 (0) zeroRMSE=80000 ch20=2639.96 (0.0402832) pae1820=2567.78 (0.0391817) mae1820= mean20=0.739364``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A PC=2*%%i set /A NEGPC=100-!PC! if /I !NEGPC! LEQ 0 ( set /A NEGPC=-!NEGPC! set /A PC=200-!PC! ) %IMG7%magick ^ %SRC% ^ ^( -clone 0 -evaluate AddModulus !PC!%% ^) ^ ^( -clone 1 -negate ^) ^ ^( -clone 0 -threshold !NEGPC!%% ^) ^ -delete 0 ^ -composite ^ -auto-level ^ -auto-gamma ^ ccy_amneg2aa_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_amneg2aa_*.miff ^ ccy_amneg2aa.gif call %PICTBAT%ccy_stats amneg2aa``` ```79167 zeroAE=29.3111 (0.000447259) zeroRMSE=79782 ch20=5839.91 (0.0891113) pae1820=3832.15 (0.0584748) mae1820= mean20=0.516788``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A PC=2*%%i set /A NEGPC=100-!PC! if /I !NEGPC! LEQ 0 ( set /A NEGPC=!NEGPC!+100 set sNEG=-negate ) else ( set sNEG= ) %IMG7%magick ^ %SRC% ^ ^( -clone 0 -evaluate AddModulus !PC!%% ^ !sNEG! ^) ^ ^( -clone 1 -negate ^) ^ ^( -clone 0 -threshold !NEGPC!%% ^) ^ -delete 0 ^ -composite ^ ccy_amneg3_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_amneg3_*.miff ^ ccy_amneg3.gif call %PICTBAT%ccy_stats amneg3``` ```0 zeroAE=0 (0) zeroRMSE=80000 ch20=2639.96 (0.0402832) pae1820=2567.78 (0.0391817) mae1820= mean20=0.739364``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A PC=200-2*%%i set /A NEGPC=100-!PC! if /I !NEGPC! LSS 0 ( set /A NEGPC=!NEGPC!+100 set sNEG=-negate ) else ( set sNEG= ) %IMG7%magick ^ %SRC% ^ ^( -clone 0 -evaluate AddModulus !PC!%% ^ !sNEG! ^) ^ ^( -clone 1 -negate ^) ^ ^( -clone 0 -threshold !NEGPC!%% ^) ^ -delete 0 ^ -composite ^ ccy_amneg3n_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_amneg3n_*.miff ^ ccy_amneg3n.gif call %PICTBAT%ccy_stats amneg3n``` ```200 zeroAE=3276.75 (0.05) zeroRMSE=80000 ch20=2639.96 (0.0402832) pae1820=2569.81 (0.0392128) mae1820= mean20=0.260846``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A PC=2*%%i set /A NEGPC=100-!PC! if /I !NEGPC! LEQ 0 ( set /A NEGPC=!NEGPC!+100 set sNEG=-negate ) else ( set sNEG= ) %IMG7%magick ^ %SRC% ^ ^( -clone 0 -evaluate AddModulus !PC!%% ^ !sNEG! ^) ^ ^( -clone 1 -negate ^) ^ ^( -clone 0 -threshold !NEGPC!%% ^) ^ -delete 0 ^ -composite ^ -auto-level ^ -auto-gamma ^ ccy_amneg3aa_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_amneg3aa_*.miff ^ ccy_amneg3aa.gif call %PICTBAT%ccy_stats amneg3aa``` ```79167 zeroAE=29.3111 (0.000447259) zeroRMSE=79782 ch20=5839.91 (0.0891113) pae1820=3832.15 (0.0584748) mae1820= mean20=0.516788``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A DEG=%%i*36/10-90 %IMG7%magick ^ %SRC% ^ -function sinusoid 1,!DEG!,0.5,0.5 ^ ccy_sin_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_sin_*.miff ^ ccy_sin.gif call %PICTBAT%ccy_stats sin``` ```79799 zeroAE=29880.3 (0.455943) zeroRMSE=79999 ch20=4591.93 (0.0700684) pae1820=2917.94 (0.0445249) mae1820= mean20=0.498773``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A DEG=%%i*36/10-90 %IMG7%magick ^ %SRC% ^ -function sinusoid 0.5,!DEG!,0.5,0.5 ^ ccy_sin2_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_sin2_*.miff ^ ccy_sin2.gif call %PICTBAT%ccy_stats sin2``` ```79598 zeroAE=4942.03 (0.0754106) zeroRMSE=79999 ch20=4591.93 (0.0700684) pae1820=2911.31 (0.0444238) mae1820= mean20=0.801687``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A DEG=%%i*36/10-90 %IMG7%magick ^ %SRC% ^ -function sinusoid 0.5,!DEG!,0.5,0.5 ^ -auto-level ^ -auto-gamma ^ ccy_sin2aa_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_sin2aa_*.miff ^ ccy_sin2aa.gif call %PICTBAT%ccy_stats sin2aa``` ```79599 zeroAE=4936.99 (0.0753336) zeroRMSE=79748 ch20=9759.85 (0.148926) pae1820=4411.45 (0.0673144) mae1820= mean20=0.572583``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A DEG=%%i*36/10-90 %IMG7%magick ^ %SRC% ^ -function sinusoid 1,!DEG!,0.5,0.5 ^ -function arcsin 1 ^ ccy_sinarc_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_sinarc_*.miff ^ ccy_sinarc.gif call %PICTBAT%ccy_stats sinarc``` ```79800 zeroAE=26738.8 (0.408008) zeroRMSE=79999 ch20=2943.96 (0.0449219) pae1820=2848.1 (0.0434592) mae1820= mean20=0.499059``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A DEG=%%i*36/10-90 %IMG7%magick ^ %SRC% ^ -function sinusoid 0.5,!DEG!,0.5,0.5 ^ -function arcsin 1 ^ ccy_sinarc2_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_sinarc2_*.miff ^ ccy_sinarc2.gif call %PICTBAT%ccy_stats sinarc2``` ```69494 zeroAE=6.98167 (0.000106533) zeroRMSE=79999 ch20=2943.96 (0.0449219) pae1820=2846.61 (0.0434365) mae1820= mean20=0.739352``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A DEG=270-%%i*36/10 %IMG7%magick ^ %SRC% ^ -function sinusoid 0.5,!DEG!,0.5,0.5 ^ -function arcsin 1 ^ ccy_sinarc2n_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_sinarc2n_*.miff ^ ccy_sinarc2n.gif call %PICTBAT%ccy_stats sinarc2n``` ```69494 zeroAE=6.98167 (0.000106533) zeroRMSE=80000 ch20=2928.5 (0.044686) pae1820=2849.02 (0.0434732) mae1820= mean20=0.260854``` Combine sinarc2 and sinarc2n, masked over using least-significant bit. ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A DEG1=%%i*36/10-90 set /A DEG2=270-%%i*36/10 %IMG7%magick ^ %SRC% ^ ^( -clone 0 ^ -function sinusoid 0.5,!DEG1!,0.5,0.5 ^ -function arcsin 1 ^ ^) ^ ^( -clone 0 ^ -function sinusoid 0.5,!DEG2!,0.5,0.5 ^ -function arcsin 1 ^ ^) ^ ^( -clone 0 ^ -evaluate And 1 -fill White +opaque Black ^ ^) ^ -delete 0 ^ -composite ^ ccy_sinarc2o_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_sinarc2o_*.miff ^ ccy_sinarc2o.gif call %PICTBAT%ccy_stats sinarc2o``` ```69494 zeroAE=6.98167 (0.000106533) zeroRMSE=79998 ch20=2943.96 (0.0449219) pae1820=2857.07 (0.043596) mae1820= mean20=0.50119``` The changes in the noisy half are smooth, with no overall change in brightness. The gradient part has only 200 values, so the least-significant bit has a beat frequency. ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! set /A DEG=%%i*36/10-90 %IMG7%magick ^ %SRC% ^ -function sinusoid 0.5,!DEG!,0.5,0.5 ^ -function arcsin 1 ^ -auto-level ^ -auto-gamma ^ ccy_sinarc2aa_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_sinarc2aa_*.miff ^ ccy_sinarc2aa.gif call %PICTBAT%ccy_stats sinarc2aa``` ```79167 zeroAE=29.3111 (0.000447259) zeroRMSE=79786 ch20=6495.9 (0.0991211) pae1820=4221.02 (0.0644086) mae1820= mean20=0.516784``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! if %%i LSS 50 ( set /A PC=2*%%i ) else ( set /A PC=200-2*%%i ) set /A PC2=100-!PC! %IMG7%magick ^ %SRC% ^ +level !PC!%%,!PC2!%% ^ ccy_lev1_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_lev1_*.miff ^ ccy_lev1.gif call %PICTBAT%ccy_stats lev1``` ```0 zeroAE=0 (0) zeroRMSE=79998 ch20=2623.96 (0.0400391) pae1820=1315.05 (0.0200664) mae1820= mean20=0.499889``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! if %%i LSS 50 ( set /A PCL=%%i set /A PCH=%%i*3 set /A PCH2=100-%%i ) else ( set /A PCL=100-%%i set /A PCH=300-%%i*3 set /A PCH2=%%i ) set /A PCL2=100-!PCL!*3 %IMG7%magick ^ %SRC% ^ ^( -clone 0 ^ +level !PCL!%%,!PCL2!%% ^ ^) ^ ^( -clone 0 ^ +level !PCH!%%,!PCH2!%% ^ ^) ^ ^( -clone 0 ^ -threshold 50%% ^ ^) ^ -delete 0 ^ -composite ^ ccy_lev2_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_lev2_*.miff ^ ccy_lev2.gif call %PICTBAT%ccy_stats lev2``` ```0 zeroAE=0 (0) zeroRMSE=79999 ch20=1311.98 (0.0200195) pae1820=656.346 (0.0100152) mae1820= mean20=0.500259``` ```for /L %%i in (%FOR_ARGS%) do ( set LZ=000000%%i set LZ=!LZ:~-6! if %%i LSS 50 ( set /A PCL=%%i*2 set /A PCL2=100-%%i*4 set /A PCH=%%i*4 set /A PCH2=100-%%i*2 ) else ( set /A PCL=200-%%i*2 set /A PCL2=%%i*4-300 set /A PCH=400-%%i*4 set /A PCH2=%%i*2-100 ) %IMG7%magick ^ %SRC% ^ ^( -clone 0 ^ +level !PCL!%%,!PCL2!%% ^ ^) ^ ^( -clone 0 ^ +level !PCH!%%,!PCH2!%% ^ ^) ^ ^( -clone 0 ^ -threshold 50%% ^ ^) ^ -delete 0 ^ -composite ^ -auto-level ^ ccy_lev3_!LZ!.miff ) %IMG7%magick ^ -loop 0 ^ ccy_lev3_*.miff ^ ccy_lev3.gif call %PICTBAT%ccy_stats lev3``` ```0 zeroAE=0 (0) zeroRMSE=79999 ch20=9119.86 (0.13916) pae1820=4566.01 (0.0696728) mae1820= mean20=0.501216```

Some animations have a single frame with black in the bottom row when it should be white. This arises because "-threshold N%" turns only values above N% to white, so "-threshold 0%" turns everything white, except black which remains black. A fix is to subtract a small value from N. This is messy is a BAT script, so isn't fixed the simple demonstrations above. However, it is fixed in colCyclStr.bat.

Results

How well do the methods satisfy the criteria?

The null method is not shown above, but is provided in the script colCyclStr.bat; it returns an empty string so there is no colour change.

Method Criteria
No change
at zero
Non-zero
changes all
No sudden
changes
Pixels change
equally
Mean 50%
stays 50%
Reversible
null pass fail pass (no changes at all) pass
am pass pass fail pass pass pass
am2 pass pass fail pass pass pass
amsol fail pass pass pass pass fail
amneg pass pass fail pass fail fail
amnegaa pass pass fail pass pass fail
amneg2 pass pass pass pass fail fail
amneg2aa pass pass pass pass pass fail
amneg3 pass pass pass pass fail fail
amneg3n pass pass pass pass fail fail
amneg3aa pass pass pass pass pass fail
sin fail pass pass fail pass fail
sin2 fail pass pass pass fail fail
sin2aa fail pass pass pass slight fail fail
sinarc fail pass pass pass pass fail
sinarc2 pass pass pass pass fail fail
sinarc2n pass pass pass pass fail fail
sinarc2o pass pass pass pass pass fail
sinarc2aa pass pass pass pass pass fail
lev1 pass pass pass fail pass pass
lev2 pass pass pass fail pass pass
lev2 pass pass pass fail pass pass

Discussion

Where a method fails the "mean 50% stays 50%" test and the image cycles through dark and light, I provide an "aa" version of the same method. This adds "-auto-level -auto-gamma" to the method, which tends to keep the mean at 50% but sometimes gives a visual appearance of accelerating and decelerating the change of colour.

The "n" suffix denotes a reversal of the method without the "n". I have provided just two of these: amneg3n and sinarc2n.

Each method has strengths and weaknesses. Leaving aside the "Reversible" criteria (because I don't think it is important), methods amneg3aa, sinarc2aa and sinarc2o pass all the tests. However, the "aa" versions suffer the acceleration/deceleration appearance, and sinarc2o relies on the least significant bit being noisy. When the LSB is not noisy, the result has artefacts.

Method sinarc2o uses the least significant bit of the input image as a mask to choose one of two transformations (sinarc2 and sinarc2n) that both have "no change at zero" but have opposite mean-shifts. The resulting mean is kept constant, without the problems introduced by -auto-level -auto-gamma.

amsol and sinarc are both useful but fail the "no change at zero" test.

The methods that have "sudden changes" may have little practical use.

The goal is for uniformally distributed noise to be handled well, and to remain uniform. IM's +noise Random creates such noise. Other noise types are not uniform and cycling changes the distribution, causing visible pulsing at each cycle. See script nseCycl.bat (results not shown here). For non-uniform noise, method sinarc2o may be the most acceptable, but a better solution is to use +noise Random, apply a chosen method, then apply a clut that changes the distribution from uniform to Gaussian or whatever is desired (perhaps using the method shown in Process modules: matching histograms).

Practical use

If one of the methods is to be used, the appropriate code can be copied. If a choice of methods is to be provided, the script colCyclStr.bat can be called with a method name and percentage (which need not be an integer). It returns appropriate IM code in the variable ccsImStr, and this value can be used within a magick command.

For example:

```call %PICTBAT%colCyclStr amneg 25.6

echo %ccsImStr% ```
`( -clone 0 -evaluate AddModulus 25.6%% )   ( -clone 1 -negate )   ( -clone 0 -threshold 74.4%% )   -delete 0 -compose Over -composite `

To get a list of method names available from colCyclStr, call the script colCyclList.bat:

`call %PICTBAT%colCyclList `
```null
am
am2
amsol
amneg
amnegaa
amneg2
amneg2aa
amneg3
amneg3n
amneg3aa
sin
sin2
sin2aa
sinarc
sinarc2
sinarc2n
sinarc2o
sinarc2aa
lev1
lev2
lev3```

Colour cycle tests

The script colCyclTst.bat uses the above scripts to make a GIF animation from each method of a given input image. The input image can be colour or grayscale. To keep the GIFs small, I use the built-in "rose:".

```call %PICTBAT%colCyclTst rose:
call procH1 cct_gifs```

The script also builds HTML code for a web page that shows each GIF: Colour cycle tests.

Grayscale versus percent display

Each animation above shows a 2D (two-dimensional) image, transformed by a method with a different percentage to each frame. Instead, we can make a 1D image, transform it into other 1D images, and concatenate (append) them in the other dimension.

 Make a 1D gradient. ```set SRC1D=ccy_1d_src.png %IMG7%magick ^ -size 1x256 gradient: ^ %SRC1D%``` Show the sinarc2aa method. ```echo off for /L %%i in (0,1,255) do ( set LZ=000000%%i set LZ=!LZ:~-6! for /F "usebackq" %%L in (`%IMG7%magick identify ^ -format "PC=%%[fx:100*%%i/256]" ^ xc:`) do set %%L call %PICTBAT%colCyclStr sinarc2aa !PC! %IMG7%magick ^ %SRC1D% ^ !ccsImStr! ^ ccy_1dsa_!LZ!.miff ) echo on %IMG7%magick ^ ccy_1dsa_*.miff ^ +append +repage ^ -pointsize 30 -gravity South ^ label:sinarc2aa ^ -append +repage ^ ccy_1dsa.png```

In the output image, the y-axis represents the input lightness and the x-axis represents the percentage. Each row shows the changes a pixel will undergo; for example, a pixel that is initially 25% will follow the changes shown in the row that is 25% of the way up an image. Each column shows the output values a grayscale gradient has for a given percentage.

We can put this is a loop, to make an output for each method.

```set HTM=ccy_1dsa.htm
del %HTM%

call %PICTBAT%colCyclList >ccyList.lis

echo off

for /F %%M in (ccyList.lis) do (
set METH=%%M
echo method=!METH!

for /L %%i in (0,1,255) do (

set LZ=000000%%i
set LZ=!LZ:~-6!

for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "PC=%%[fx:100*%%i/256]" ^
xc:`) do set %%L

call %PICTBAT%colCyclStr !METH! !PC!

%IMG7%magick ^
%SRC1D% ^
!ccsImStr! ^
ccy_1dsa_!LZ!.miff
)

%IMG7%magick ^
ccy_1dsa_*.miff ^
+append +repage ^
-pointsize 30 -gravity South ^
label:!METH! ^
-append +repage ^
ccy_1dsa_!METH!.png

echo ^<img src="ccy_1dsa_!METH!.png" /^> >>%HTM%
)
echo on```

Each result, except amneg and amnegaa, can tile horizontally.

Concatenating methods

The output of one method can be used as the input to the same or a different method. Some methods double frequencies, in that a black-white gradient is converted to a double black-white-black gradient. When two doublers are concatenated, we get a quadrupling of frequencies: black-white-black-white-black.

With concatenation, the two (or more) calls to colCyclStr.bat may have different percentages, perhaps changing at different speeds.

Future

A method could use cluts. For 0 to 50%, interpolate between identity clut (which is a gradient) and given clut. Then interpolate back to identity. We could have two cluts: interpolate from identity to first, then first to second, finally second to identity. Probably wise if caller also supplies the zero-frame clut.

The mask technique used in sinarc2o could be useful with other methods. Instead of the least significant bit, a different bit (or perhaps bit parity, or a noise image) might be used.

What method is like amneg2 but instead of the light band moving down then up, the dark band moves up then down?

A method could use a rolling clut, exactly the same size as there are frames in a cycle. Or, instead of rolling, use SRT with "-virtual-pixel tile".

Cleanup

```:end

rem del ccy_*.miff```

Scripts

For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.

ccy_stats.bat

```@rem
@rem Updated:
@rem   22-August-2022 Upgraded for IM v7.
@rem

set METH=%1

set LIS=ccy_stats_%METH%.lis

%IMG7%magick compare -metric AE -format "\nzeroAE=" ccy_%METH%_000000.miff %SRC% NULL: >%LIS% 2>&1
%IMG7%magick compare -metric RMSE -format "\nzeroRMSE=" ccy_%METH%_000000.miff %SRC% NULL: >>%LIS% 2>&1
%IMG7%magick compare -metric AE -format "\nch20=" ccy_%METH%_000000.miff ccy_%METH%_000020.miff  NULL: >>%LIS% 2>&1
%IMG7%magick compare -metric PAE -format "\npae1820=" ccy_%METH%_000018.miff ccy_%METH%_000020.miff  NULL: >>%LIS% 2>&1
%IMG7%magick compare -metric MAE -format "\nmae1820=" ccy_%METH%_000018.miff ccy_%METH%_000020.miff  NULL: >>%LIS% 2>&1
%IMG7%magick ccy_%METH%_000020.miff -format "\nmean20=%%[fx:mean]" info: >>%LIS%

type %LIS%```

colCyclStr.bat

```rem Given %1 is a color cycling method and %2 is a percentage of cycle,
rem returns ccsImStr, a string to implement it.
@rem
@rem Updated:
@rem   22-August-2022 Upgraded for IM v7.
@rem

if "%1%"=="" exit /B 1
if "%2%"=="" exit /B 1

goto %1

:null
set ccsImStr=
@exit /B 0

:am
@exit /B 0

:am2
for /F "usebackq" %%L in (`%IMG7%magick identify -format "ccsPC=%%[fx:2*(%2)]" xc:`) do set %%L
@exit /B 0

:amsol
set ccsImStr=-evaluate AddModulus %2%%%% -solarize 50%% -evaluate Multiply 2
@exit /B 0

:amneg
set ccsPC=%2
for /F "usebackq" %%L in (`%IMG7%magick identify -format "ccsNEGPC=%%[fx:100-(%2)]" xc:`) do set %%L
set ccsImStr=^( -clone 0 -evaluate AddModulus !ccsPC!%%%% ^) ^
^^^( -clone 1 -negate ^^^) ^
^^^( -clone 0 -threshold !ccsNEGPC!%%%% ^^^) ^
-delete 0 -compose Over -composite
@exit /B 0

:amnegaa
set ccsPC=%2
for /F "usebackq" %%L in (`%IMG7%magick identify -format "ccsNEGPC=%%[fx:100-(%2)]" xc:`) do set %%L
set ccsImStr=^( -clone 0 -evaluate AddModulus !ccsPC!%%%% ^) ^
^^^( -clone 1 -negate ^^^) ^
^^^( -clone 0 -threshold !ccsNEGPC!%%%% ^^^) ^
-delete 0 -compose Over -composite ^
-auto-level -auto-gamma
@exit /B 0

:amneg2
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsPC=%%[fx:%2<50?2*(%2):200-2*(%2)]\nccsNEGPC=%%[fx:(%2<50?100-2*(%2):2*(%2)-100)-0.00001]" ^
xc:`) do set %%L
set ccsImStr=^( -clone 0 -evaluate AddModulus !ccsPC!%%%% ^) ^
^^^( -clone 1 -negate ^^^) ^
^^^( -clone 0 -threshold !ccsNEGPC!%%%% ^^^) ^
-delete 0 -compose Over -composite
@exit /B 0

:amneg2aa
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsPC=%%[fx:%2<50?2*(%2):200-2*(%2)]\nccsNEGPC=%%[fx:(%2<50?100-2*(%2):2*(%2)-100)-0.00001]" ^
xc:`) do set %%L
set ccsImStr=^( -clone 0 -evaluate AddModulus !ccsPC!%%%% ^) ^
^^^( -clone 1 -negate ^^^) ^
^^^( -clone 0 -threshold !ccsNEGPC!%%%% ^^^) ^
-delete 0 -compose Over -composite ^
-auto-level -auto-gamma
@exit /B 0

:amneg3
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsPC=%%[fx:2*(%2)]\nccsNEGPC=%%[fx:%2<50?100-2*(%2):200-2*(%2)]\nccsIsNEG=%%[fx:%2<50?0:1]" ^
xc:`) do set %%L
if !ccsIsNEG!==1 (set ccsNEG=-negate) else set ccsNEG=
set ccsImStr=^( -clone 0 -evaluate AddModulus !ccsPC!%%%% !ccsNEG! ^) ^
^^^( -clone 1 -negate ^^^) ^
^^^( -clone 0 -threshold !ccsNEGPC!%%%% ^^^) ^
-delete 0 -compose Over -composite
@exit /B 0

:amneg3n
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsPC=%%[fx:200-2*(%2)]\nccsNEGPC=%%[fx:(%2>=50?2*(%2)-100:2*(%2))-0.00001]\nccsIsNEG=%%[fx:%2>=50?0:1]" ^
xc:`) do set %%L
if !ccsIsNEG!==1 (set ccsNEG=-negate) else set ccsNEG=
set ccsImStr=^( -clone 0 -evaluate AddModulus !ccsPC!%%%% !ccsNEG! ^) ^
^^^( -clone 1 -negate ^^^) ^
^^^( -clone 0 -threshold !ccsNEGPC!%%%% ^^^) ^
-delete 0 -compose Over -composite
@exit /B 0

:amneg3aa
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsPC=%%[fx:2*(%2)]\nccsNEGPC=%%[fx:%2<50?100-2*(%2):200-2*(%2)]\nccsIsNEG=%%[fx:%2<50?0:1]" ^
xc:`) do set %%L
if !ccsIsNEG!==1 (set ccsNEG=-negate) else set ccsNEG=
set ccsImStr=^( -clone 0 -evaluate AddModulus !ccsPC!%%%% !ccsNEG! ^) ^
^^^( -clone 1 -negate ^^^) ^
^^^( -clone 0 -threshold !ccsNEGPC!%%%% ^^^) ^
-delete 0 -compose Over -composite ^
-auto-level -auto-gamma
@exit /B 0

:sin
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsDEG=%%[fx:(%2)*3.6-90]" ^
xc:`) do set %%L
set ccsImStr=-function sinusoid 1,!ccsDEG!,0.5,0.5
@exit /B 0

:sin2
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsDEG=%%[fx:(%2)*3.6-90]" ^
xc:`) do set %%L
set ccsImStr=-function sinusoid 0.5,!ccsDEG!,0.5,0.5
@exit /B 0

:sin2aa
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsDEG=%%[fx:(%2)*3.6-90]" ^
xc:`) do set %%L
set ccsImStr=-function sinusoid 0.5,!ccsDEG!,0.5,0.5 -auto-level -auto-gamma
@exit /B 0

:sinarc
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsDEG=%%[fx:(%2)*3.6-90]" ^
xc:`) do set %%L
set ccsImStr=-function sinusoid 1,!ccsDEG!,0.5,0.5 -function arcsin 1
@exit /B 0

:sinarc2
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsDEG=%%[fx:(%2)*3.6-90]" ^
xc:`) do set %%L
set ccsImStr=-function sinusoid 0.5,!ccsDEG!,0.5,0.5 -function arcsin 1
@exit /B 0

:sinarc2n
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsDEG=%%[fx:270-(%2)*3.6]" ^
xc:`) do set %%L
set ccsImStr=-function sinusoid 0.5,!ccsDEG!,0.5,0.5 -function arcsin 1
@exit /B 0

:sinarc2o
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsDEG1=%%[fx:%2*3.6-90]\nccsDEG2=%%[fx:270-(%2)*3.6]" ^
xc:`) do set %%L
set ccsImStr=^
^^^( -clone 0 ^
-function sinusoid 0.5,!ccsDEG1!,0.5,0.5 ^
-function arcsin 1 ^
^^^) ^
^^^( -clone 0 ^
-function sinusoid 0.5,!ccsDEG2!,0.5,0.5 ^
-function arcsin 1 ^
^^^) ^
^^^( -clone 0 ^
-evaluate And 1 -fill White +opaque Black ^
^^^) ^
-delete 0 ^
-compose Over -composite
@exit /B 0

:sinarc2aa
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsDEG=%%[fx:(%2)*3.6-90]" ^
xc:`) do set %%L
set ccsImStr=-function sinusoid 0.5,!ccsDEG!,0.5,0.5 -function arcsin 1 -auto-level -auto-gamma
@exit /B 0

:lev1
for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "ccsPC=%%[fx:%2<50?2*(%2):200-2*(%2)]\nccsPC2=%%[fx:%2<50?100-2*(%2):2*(%2)-100]\n" ^
xc:`) do set %%L
set ccsImStr=+level !ccsPC!%%,!ccsPC2!%%
@exit /B 0

:lev2
set ccsFORM=^
ccsPCL=%%[fx:%2^<50?(%2):100-(%2)]\n^
ccsPCH=%%[fx:%2^<50?3*(%2):300-3*(%2)]\n^
ccsPCH2=%%[fx:%2^<50?100-(%2):(%2)]\n^
ccsPCL2=%%[fx:%2^<50?100-3*(%2):3*(%2)-200]

for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "!ccsFORM!" ^
xc:`) do set %%L
set ccsImStr=^( -clone 0 ^
+level !ccsPCL!%%%%,!ccsPCL2!%%%% ^) ^
^^^( -clone 0 +level !ccsPCH!%%%%,!ccsPCH2!%%%% ^^^) ^
^^^( -clone 0 -threshold 50%%%% ^^^) ^
-delete 0 -compose Over -composite
@exit /B 0

:lev3
set ccsFORM=^
ccsPCL=%%[fx:%2^<50?2*(%2):200-2*(%2)]\n^
ccsPCL2=%%[fx:%2^<50?100-4*(%2):4*(%2)-300]\n^
ccsPCH=%%[fx:%2^<50?4*(%2):400-4*(%2)]\n^
ccsPCH2=%%[fx:%2^<50?100-2*(%2):2*(%2)-100]

for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "!ccsFORM!" ^
xc:`) do set %%L
set ccsImStr=^( -clone 0 ^
+level !ccsPCL!%%%%,!ccsPCL2!%%%% ^) ^
^^^( -clone 0 +level !ccsPCH!%%%%,!ccsPCH2!%%%% ^^^) ^
^^^( -clone 0 -threshold 50%%%% ^^^) ^
-delete 0 ^
-compose Over -composite -auto-level
@exit /B 0```

colCyclList.bat

I don't provide the source or binary of chStrs.exe, which here simply strips any colons (":") from the piped stream.

```@rem  Write a list of available colour cycle methods to stdout

@findstr "^:" %PICTBAT%colCyclStr.bat |chStrs /p0 /i- /o- /f":"```

colCyclTst.bat

```rem Given image %1,
rem for each colour cycling method, makes test GIF named cct_!METH!.gif
rem Also creates an HTM file, name %2.
rem %3 optional prefix for GIF files. [cct]
@rem
@rem Updated:
@rem   25-September-2022 for IM v7.
@rem

@if "%1"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal

@call echoOffSave

call %PICTBAT%setInOut %1 pd

set HTM=%2
if "%HTM%"=="" set HTM=cct_gifs.h1

set GIF_PREF=%3
if "%GIF_PREF%"=="" set GIF_PREF=cct

set TMPDIR=%TEMP%\

del %TMPDIR%cct_*.miff 2>nul

set METH_LIST=%TEMP%\cctList.lis
del %HTM% 2>nul

set TXT=%TMPDIR%cct_text.png

for /F "usebackq" %%L in (`%IMG7%magick identify ^
-format "WW=%%w\nHH=%%h\nWT=%%[fx:max(w*0.9,50)]\nHT=%%[fx:max(h*0.9,20)]" ^
%INFILE%`) do set %%L

echo ^<h1^>Color cycle tests^</h1^> >%HTM%
echo See ^<a href="colcycl.htm#cct"^>Colour cycling: tests^</a^>.^</p^> >>%HTM%

set FOR_ARGS=0,2,99

call %PICTBAT%colCyclList >%METH_LIST%

for /F %%M in (%METH_LIST%) do (
set METH=%%M
echo method=!METH!

%IMG7%magick ^
-size %WT%x%HT% label:!METH! ^
-trim +repage ^
-bordercolor White -border 3 ^
-size %WW%x%HH% xc: ^
+swap ^
-gravity South -append +repage ^
%TXT%

for /L %%i in (%FOR_ARGS%) do (

set LZ=000000%%i
set LZ=!LZ:~-6!

call %PICTBAT%colCyclStr !METH! %%i

%IMG7%magick ^
%INFILE% ^
!ccsImStr! ^
%TMPDIR%cct_!LZ!.miff
)

%IMG7%magick ^
-loop 0 ^
%TXT% ^
null: ^
%TMPDIR%cct_*.miff ^
-gravity North ^
-layers composite ^
+repage ^
-layers optimize ^
%GIF_PREF%_!METH!.gif

del %TMPDIR%cct_*.miff

echo ^<img src="%GIF_PREF%_!METH!.gif" /^> >>%HTM%
)

setlocal DisableDelayedExpansion
echo ^<!--Page-version v1.0 23-Sep-2015--^> >>%HTM%
endlocal

echo Created cct_*.gif and %HTM%

call echoRestore

@endlocal & set pdOUTFILE=%HTM%```

nseCycl.bat

This creates a single web page showing all colour cycling methods for all +noise types.

```@rem
@rem Updated:
@rem   22-August-2022 Upgraded for IM v7.
@rem

set OUT_HTM=nc_htm.htm

del %OUT_HTM%

for /F "usebackq" %%N in (`%IMG7%magick -list noise`) do (
echo %%N

%IMG7%magick ^
-size 200x200 xc:gray^(50%%^) +noise %%N ^
nc_%%N.png

set HTM=htm_%%N

call %PICTBAT%colCyclTst nc_%%N.png !HTM! nc_%%N

echo ^<h2^>%%N^</h2^> >>%OUT_HTM%

type !HTM! >>%OUT_HTM%

)```

All images on this page were created by the commands shown, using:

`%IMG7%magick -version`
```Version: ImageMagick 6.9.1--6 Q16 x64 2015-06-20 http://www.imagemagick.org
Features: Cipher DPC Modules OpenMP
Delegates (built-in): bzlib cairo freetype jng jp2 jpeg lcms lqr openexr pangocairo png ps rsvg tiff webp xml zlib
```

To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF to JPG.

Source file for this web page is colcycl.h1. To re-create this web page, run "procH1 colcycl".

This page, including the images, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.

Anyone is permitted to re-publish this page, but only for non-commercial use.