JavaScript Diary

任意色間グラデーション [ 2001/12/11 ]

2色間グラデーション、3色間グラデーションは見かけるものの任意色間のグラデーションはないように思われる。
かなり前から僕自身も考えていたのですが、需要がないとか、ダルそうだったので作成しなかったのですが、取り組んでみたところ、それ程難しいことでもなかったので紹介しておきます。

さて、2色間グラデーションは簡単でプログラムは以下になります。

// 引数pを2桁の16進数文字列に変換
function toHex( p ){
    p = Math.round( p );
    if( p <= 0x00 ){ // 0以下
        return '00' ;
    }else if( p < 0x10 ){ // 16より小さい場合
        return "0"+p.toString(16);
    }else if( p <= 0xff ){ // 255以下
        return p.toString(16);
    }else{ // 255より大きい
        return 'ff' ;
    }
}

// 16進数6桁のカラー文字列を引数(c)にとり、
// r, g, b をプロパティに持つオブジェクトを返す。
// 0 <= r, g, b <= 255
function toRGB( c ){
    // 文字列を数に変換("#"は除く)
    var p = parseInt( '0x'+( c.charAt(0) == "#" ? c.substring(1) : c ) );
    var r = ( p & 0xff0000 ) >>> 0x10 ; // 論理積からビットシフトし、R要素を取り出す
    var g = ( p & 0x00ff00 ) >>> 0x08 ; // 論理積からビットシフトし、G要素を取り出す
    var b = ( p & 0x0000ff ) >>> 0x00 ; // 論理積からビットシフトし、B要素を取り出す
    return { r:r, g:g, b:b };
}

// 16進数6桁のカラー文字列を引数(p0,p1)にとり、
// 長さnの2色間グラデーション配列を生成する。
function gradation2( p0, p1, n ){
    var c0 = toRGB( p0 ); // オブジェクトを生成
    var c1 = toRGB( p1 ); // オブジェクトを生成
    var re = new Array( n ); // 返り値
    var dr = ( c1.r-c0.r )/( n-1 ); // R方向オフセット量
    var dg = ( c1.g-c0.g )/( n-1 ); // G方向オフセット量
    var db = ( c1.b-c0.b )/( n-1 ); // B方向オフセット量
    for(var i=0;i<n;i++){
        // 16進数6桁の文字列にして格納
        re[i] = toHex( c0.r )+''+toHex( c0.g )+''+toHex( c0.b );
        // オフセット
        c0.r += dr ;
        c0.g += dg ;
        c0.b += db ;
    }
    return re ;
}

流れはプログラムを参考に。次に3色ですが、これは上記関数(gradation2)を再利用します。

// 16進数6桁のカラー文字列を引数(p0,p1,p2)にとり、
// 長さnの3色間グラデーション配列を生成する。
function gradation3( p0, p1, p2, n ){
    // 3色間の場合 [ p0-p1 ], [ p1-p2 ] のグラデーション配列を
    // それぞれ求め、それを連結する
    var m = Math.floor( n/2 ); // [ p0-p1 ], [ p1-p2 ] の配列の長さ
    var re0 = gradation2( p0, p1, m+1 ); // [ p0-p1 ]間グラデーション配列
    var re1 = gradation2( p1, p2, m+(n&1) ); // [ p1-p2 ]間グラデーション配列
    return re0.concat( re1.slice(1) ); // 重複分を除き、連結して返す
}

問題の任意色ですが、先ずはプログラムを見て下さい。同様に gradation2 を使います。

// 引数の例
// gradationX( "ff0000", "00ff00", "0000ff", "ffff00", "ff00ff", "00ffff", 128 )
// この場合、赤 → 緑 → 青 → 黄 → 桃 → 水 で長さ128の配列を返す
function gradationX(){
    var a = arguments ; // Argumentオブジェクト
    var s = a.length-1 ; // 色数
    var n = a[ a.length-1 ]; // 返す配列の長さ
    if( s == 2 ){
        return gradation2( a[0], a[1], n );
    }else{
        var rex = new Array( s-1 ); // 各色間のグラデーション配列(二次元配列)
        var m = Math.floor( n/rex.length ); // 各rex要素の長さ
        var dn = n-m*rex.length ; // 上記 m の式は丸められているため、その誤差を求めておく
        if( dn-- == 0 ){ // 誤差がないとき
            for(var i=0;i<rex.length;i++) rex[i] = gradation2( a[i], a[i+1], m+Number(i!=rex.length-1) );
        }else{
            for(var i=0;i<rex.length;i++) rex[i] = gradation2( a[i], a[i+1], m+1+Number(i<dn) );
        }
        var re = rex[0];
        for(var i=0;i<rex.length-1;i++) re = re.concat( rex[i+1].slice(1) );
        return re ;
    }
}

この関数の使用例はこちら
難しいのは指定した長さ(n)の配列を返すことであって、基本的なアルゴリズムは3色の場合と変わらないです。

さて、これらの関数をいちいち定義しておくのは面倒なので、一つの関数にまとめました。
これ一つで何色でもOKです。

// string[] gradation( c0, c1, [ c2, [ c3, ... ], n )
function gradation(){
    var a = arguments ;
    var s = a.length-1 ;
    var n = a[ a.length-1 ];
    switch( s ){
        case 2 : {
            var c0 = toRGB( a[0] );
            var c1 = toRGB( a[1] );
            var re = new Array( n );
            var dc = new Array( 3 );
            for(var i=0;i<3;i++) dc[i] = ( c1[i]-c0[i] )/( n-1 );
            for(var i=0;i<n;i++){
                re[i] = toHex( c0[0] )+''+toHex( c0[1] )+''+toHex( c0[2] );
                for(var j=0;j<3;j++) c0[j] += dc[j];
            }
            return re ;
        }
        case 3 : {
            var m = Math.floor( n/2 );
            var re0 = a.callee( a[0], a[1], m+1 );
            var re1 = a.callee( a[1], a[2], m+(n&1) );
            return re0.concat( re1.slice(1) );
        }
        default : {
            var t = s-1 ;
            var rex = new Array( t );
            var m = Math.floor( n/t );
            var dn = n-m*t ;
            if( dn-- == 0 ){
                for(var i=0;i<t;i++) rex[i] = a.callee( a[i], a[i+1], m+Number(i!=t-1) );
            }else{
                for(var i=0;i<t;i++) rex[i] = a.callee( a[i], a[i+1], m+1+Number(i<dn) );
            }
            var re = rex[0];
            for(var i=0;i<t-1;i++) re = re.concat( rex[i+1].slice(1) );
            return re ;
        }
    }
    function toHex( p ){
        p = Math.round( p );
        return p <= 0x00 ? '00' : ( p < 0x10 ? "0"+p.toString(16) : ( p <= 0xff ? p.toString(16) : 'ff' ) );
    }
    function toRGB( c ){
        var p = parseInt( '0x'+( c.charAt(0) == "#" ? c.substring(1) : c ) );
        var r = ( p & 0xff0000 ) >>> 0x10 ;
        var g = ( p & 0x00ff00 ) >>> 0x08 ;
        var b = ( p & 0x0000ff ) >>> 0x00 ;
        return new Array( r, g, b );
    }
}

これを使ったカラーテーブル

もう随分昔になりますが、これと関係して Mathematics > Gradation のページを作成していたんですが、時間がなくてほにゃらら。