まくろぐ

プログラムからレインボーカラー(虹色)のグラデーションを作成する

最終更新:

とあるコーディングにおいて、色を滑らかに変化させる必要があったのでメモメモ。

RGB ではなく HSV で考える

何らかのプログラミング言語から虹色のグラデーションを作る必要がある場合、RGB の色空間で色調整を行うのは大変ですが、HSV の色空間で考えると簡単に表現することができます。

/p/4cbbh6o/hsv.png
図: HSV 色空間

HSV はそれぞれ下記のような情報を表しており、

  • 色相 (Hue): 0~360
  • 彩度 (Saturation・Chroma): 0~1
  • 明度 (Value・Brightness): 0~1

このうち、色相 (Hue) の値を 0~360 の間で変化させてやることでレインボーカラーを表現することができます。

プログラムのサンプル

/p/4cbbh6o/rainbow-bar.png
図: 虹色グラデーションの描画

例えば、Android では、android.graphics.Color.HSVToColor() という関数を使用すると、HSV 色空間における値を、描画に使用するカラーデータに変換することができます。

下記の ColorGenerator クラスの nextColor() メソッドを連続して呼び出すと、徐々に変わっていく色をひとつずつ取り出すことができます。 やっていることは、メソッドの呼び出しごとに、色相 (Hue) の値を少しずつ変化させているだけです。 色相 (Hue) が、何度の nextColor() 呼び出しで一周するかは、コンストラクタの steps パラメータで指定できるようにしています。

ColorGenerator.kt
class ColorGenerator(val steps: Int, initialHue: Float = 0.0F) {
    private val hueStep: Float = 360F / steps
    private var currentHue = initialHue

    fun nextColor(): Int {
        val hsv = floatArrayOf(currentHue, 1.0F, 1.0F)
        val color: Int = Color.HSVToColor(255, hsv)
        currentHue = (currentHue + hueStep) % 360F
        return color
    }
}

次のコードは、このクラスを使って、虹色のグラデーション(細い矩形の連続)を描画するコードの抜粋です。 ここでは、色を 30 段階に分けて描画しています。

虹色の矩形を描画する(抜粋)
val colorGen = ColorGenerator(30)

override fun onDraw(canvas: Canvas){
    var left = 0F
    val paint = Paint()
    for (i in 0 until colorGen.steps) {
        paint.color = colorGen.nextColor()
        c.drawRect(left, 0F, left + 10, 100F, paint)
        left += 10
    }
}

TypeScript: 名前空間を定義する (namespace)

最終更新:

namespace ではなくモジュールの仕組みを使うべし

TypeScript では、namespace キーワードを使って名前空間を定義することができますが、通常はより柔軟性の高い モジュールの仕組み を使うようにしてください(といっても .ts ファイルを分けるだけですが)。

namespace を使うと、同じファイル内で階層化された名前空間を作ることができますが、あくまでその階層構造はグローバルに共有されています。 一方、モジュールの仕組みを使うと、ファイル単位で名前空間のコンテキストを分けることができます。 大きなプロジェクトであっても、適切な単位でモジュール(ファイル)を分割している限り、名前の衝突は本質的には発生しません。

とはいえ、これは namespace の記事なので、ここからは namespace の使い方の説明をします。

namespace による名前空間の定義

namespace による名前空間の定義は簡単で、namespace Xxx { ... } というブロックで囲むだけです。 次の例では、FirstSecond という名前空間を作成し、それぞれに同じ名前の Person というクラスを定義しています。

namespace First {
  export class Person {
    greet() { console.log('First.Person'); }
  }
}

namespace Second {
  export class Person {
    greet() { console.log('Second.Person'); }
  }
}

const p1 = new First.Person();
const p2 = new Second.Person();

p1.greet();  //=> First.Person
p2.greet();  //=> Second.Person

namespace の中で定義したクラスなどは、デフォルトではその外の名前空間からは見えないようになっています。 外からアクセスしたい場合は、上記のようにクラス定義の前に export を付けて公開しておく必要があります。

名前空間は、ドットで区切って入れ子で定義することもできます。

namespace First.Second.Third {
  export class Person {
    greet() { console.log('Hello'); }
  }
}

const p = new First.Second.Third.Person();

上記は、下記のように定義するのと同等です。

namespace First {
  export namespace Second {
    export namespace Third {
      export class Person {
        greet() { console.log('Hello'); }
      }
    }
  }
}

内部の namespace は、export で公開しなければ外からアクセスできないため、上記のように export の連続になります。

import を使って、名前空間内のメンバーに別名(エイリアス)をつけてアクセスすることもできます。

namespace First {
  export class Person {
    greet() { console.log('First.Person'); }
  }
}

import Person = First.Person;
const p = new Person();
p.greet();

同じ階層にあるクラスと名前空間名が同じ名前の場合は、クラス定義が先にある限り TypeScript トランスパイラがうまくマージしてくれます。

class Foo {
  greet() {
    console.log('greet1');
  }
}

namespace Foo {
  export function greet() {
    console.log('greet2');
  }
}

new Foo().greet();
Foo.greet();

もちろん、このような名前の重複は発生しないように定義すべきですし、そもそも namespace は使わずに モジュールの仕組み だけで名前空間の管理をすれば、このような混乱が発生することはないでしょう。

TypeScript: モジュールを作成する/読み込む (export/import)

最終更新:

モジュールとは

TypeScript で大きなプログラムを作成するときは、モジュールの仕組みを使って複数のファイルに分割していきます。 ファイルを分割することでコードを管理しやすくなるだけでなく、名前空間のコンテキストが分けられることになるので、名前の衝突の問題も解決することができます。

モジュールを作るのは簡単で、export キーワードを含む .ts ファイルを作ればそれがモジュールになります。 export キーワードでは、クラスやインタフェースをまるごと公開することもできるし、関数や変数の単位で公開することもできます。

export の使い方のポイントは、次のように、クラスや変数を定義するときにプレフィックスとして付加するというところです。 基本的には、定義済みのオブジェクトを後から export するという使い方はしません。

  • export class …
  • export interface …
  • export const …
  • export let …

ここからは、具体的な export の使い方を見ていきます。

クラスやインタフェースの定義を export する

下記の lib/mylib.ts ファイルでは、MyInterface インタフェースと、MyClass クラスを公開しています。 それぞれの定義の前に export キーワードを付けるだけでよいので簡単ですね。

lib/mylib.ts
export interface MyInterface {
  name: string;
}

export class MyClass implements MyInterface {
  constructor(public name: string) {}
}

次の index.ts ファイルからは、上記のクラスモジュールを import して読み込んでいます。

index.ts(個別に import)
import { MyInterface, MyClass } from './lib/mylib';

const obj: MyInterface = new MyClass('Maku');
console.log(obj.name);  //=> Maku

from の後ろに指定するファイルパスには、拡張子の .ts を記述しないことに注意してください(実際に読み込むファイルは .js なので、.ts の指定は意味的にも間違っています)。 上記の例では、インタフェースやクラスの名前を直接指定して、個別の変数に読み込んでいますが、次のようにワイルドカードを使って export されているものを 1 つの変数にすべて読み込むこともできます。

index.js(まとめて import)
import * as mylib from './lib/mylib';

const obj: mylib.MyInterface = new mylib.MyClass('Maku');
console.log(obj.name);  //=> Maku

ここでは、mylib という変数のプロパティ経由で公開されているインタフェースにアクセスできるようにしています。 ワイルドカードを使用する場合、この変数の指定は必須であることに注意してください(それにより、不注意による名前の衝突を防げるようになっています)。

個別の変数に読み込む場合でも、as キーワードを使って別名を付けることができます。 使用頻度は高くないかもしれませんが、名前の衝突が起こってしまう場合や、長いインタフェース名を短い名前で参照したいときなどに使えるテクニックです。

index.js(別名を付ける)
import { MyInterface as MI, MyClass as MC } from './lib/mylib';

const obj: MI = new MC('Maku');
console.log(obj.name);  //=> Maku

単一の変数や定数、関数などを export する

constlet を使って変数を定義するときに、export キーワードを付けておくと、その変数を単独で公開できます。

次の config.ts モジュールは、config 変数(Config クラスのインスタンス)を公開しています。

config.ts
class Config {
  debug: boolean = true;
  animSpeed: number = 10;
}

export const config = new Config();

参照側では次のようにして config 変数を扱うことができます。

index.ts
import { config } from './config';

console.log(config.debug);  //=> true

ちなみに、このような設定情報であれば、次のように各プロパティをクラス変数で定義してしまって、クラスごと export した方がシンプルでよいかもしれません。

config.ts
export class Config {
  static DEBUG: boolean = true;
}
index.ts
import { Config } from './config';

console.log(Config.DEBUG);  //=> true

定数や関数を単独で公開することもできます。

mylib.ts
export const MAX_CHANNELS = 100;
export function greet() {
  console.log('Hello');
}
index.ts
import { MAX_CHANNELS, greet } from './config';

console.log(MAX_CHANNELS);  //=> 100
greet();  //=> Hello

次のように、export するときに別名を付けることもできます。

mylib.ts
function greet() {
  console.log('Hello');
}

export { greet as myGreet }

再 export (Re-export)

次のように export を使用すると、別のモジュールから export されているインタフェースを、あたかも自分が export しているかのように見せることができます。

export { Foo, Bar } from './submodule';

次のように、別名を付けて再 export したり、すべてのインタフェースを再 export することもできます。

export { Foo as MyFoo } from './submodule';
export * from './submodule';

このようにワイルドカードを使用する場合は、import の場合と違い、as による別名を付けなくてよいことに注意してください。

この再 export の仕組みを使用すれば、複数のサブモジュールで構成されたライブラリを、 1 つの大きなモジュールとして見せることができます。

デフォルト export

(この仕組みはオススメしません)

export default を指定すると、モジュールの中の 1 つのメンバーをデフォルト export に設定することができ、シンプルな記述で import できるようになります。

次の例では、Book クラスの定義をデフォルト export しています。

book.ts
export default class Book {
  constructor(public title: string) {}
}

このクラスは下記のようにシンプルに import できます(変数名を {} で囲んだりする必要がありません)。

index.ts
import Book from './book';

const book = new Book('Title');

デフォルト export された Book クラスは、次のように別の名前の変数にも代入できてしまいます(それでも実体は Book クラスです)。

index.ts
import Hoge from './book';

const book = new Hoge('Title');

このように名前の変更ができてしまうので、デフォルト export を使ったコードは分かりにくくなってしまう可能性があります。 そのため、デフォルト export の仕組みはあまりオススメできません

export default は、次のように、単一のインスタンス(ここでは Config オブジェクト)を公開するのにも使用できます。

config.ts
class Config {
  debug: boolean = true;
  animSpeed: number = 10;
}

export default new Config();

次のように import すれば、モジュール側で生成された Config オブジェクトを直接参照できます。

index.ts
import config from './config';

console.log(config.debug);  //=> true

とはいえ、通常の export の使い方でも同様のことを実現できるため、やはり export default の使用はオススメできません。

オブジェクト export (Object export)

(この仕組みもオススメしません)

単一のオブジェクトだけを公開したい場合は、上で説明した export default の仕組みを使用する方法以外にも、

export = オブジェクト;

という構文を使用する方法があります。 CommonJS の exports & requre のようなものだというと伝わる人には伝わるかもしれません。

次の config.ts モジュールでは、唯一 Config クラスのインスタンスだけを export しています。

class Config {
  debug: boolean = true;
  animSpeed: number = 10;
}

export = new Config();

このモジュールを読み込む側では、import/from ではなく、import/require を使って読み込む必要があります。

import config = require('./config');

console.log(config.debug);  //=> true

この方法でモジュールを読み込む場合、公開されているオブジェクトの名前と合わせた変数に代入する必要はありません(上記の例では意図的に config と合わせているだけです)。

import hogehoge = require('./config');

このように使用側で名前を勝手に変更できてしまう点は、export default との共通点であり、オススメできない理由でもあります。

まくろぐ
サイトマップまくへのメッセージ