今日も適当ダイアリー

PHP や Javascript や Symfony、BEAR.Sunday などのWeb周りのことを中心に。それ以外のことも気まぐれに投稿します。

色空間メソッドと色のアニメーションを拡張するjQueryプラグインjQuery.iColor ver.1.0 beta

色空間メソッドと色のアニメーションを拡張するjQueryプラグイン jQuery.iColor をバージョンアップしました。

バグフィックスのみで、メソッドなども以前と同等になりますので、使い方については、下記エントリを参照してください。

選択範囲のテキストを操作するjQueryプラグインjQuery.selection

以下の内容は、古い内容です。
新しい記事で情報を確認してください。
http://blog.madapaja.net/entry/2012/05/09/070500


選択範囲のテキストを操作するためのjQueryプラグイン jQuery.selection を書いたので、アップしました。

IE6やFireFox3.5、Google Chrome 6など主要なブラウザでチェック済みです。


ファイルはこちら

http://jquery-selection.googlecode.com/svn/tags/(URLが間違っていたため訂正しました)
http://code.google.com/p/jquery-selection/downloads/list (圧縮ファイル)

簡単な説明

bold;">$.getSelection([mode = 'text']):HTMLドキュメント内の選択されたテキスト/HTMLを取得する
bold;">getSelection():テキストボックス/エリアの選択されたテキストを取得する
bold;">replaceSelection(text[, caret = 'keep']):テキストボックス/エリアの選択されたテキストを置換する
bold;">insertBeforeSelection(text[, caret = 'keep']):テキストボックス/エリアの選択されたテキストの前に文字列を挿入する
bold;">insertAfterSelection(text[, caret = 'keep']):テキストボックス/エリアの選択されたテキストの後に文字列を挿入する
bold;">getCaretPos():テキストボックス/エリアの選択範囲を取得する
bold;">setCaretPos(range):テキストボックス/エリアの選択範囲を設定する

デモも併せてご確認ください。

$.getSelection([mode = 'text'])

HTMLドキュメント内の選択されたテキスト/HTMLを取得する

mode

modeには、'text'もしくは'html'を指定します。
指定しなかった場合は、'text'となります。

返り値

String型で mode に指定した値によって、選択されたテキストか、HTMLテキストが返ります。

getSelection()

テキストボックス/エリアの選択されたテキストを取得する

返り値

String型で選択されたテキストが返ります。

replaceSelection(text[, caret = 'keep'])

テキストボックス/エリアの選択されたテキストを置換する

text

置換後の文字列を指定します。

caret

caretには'keep'か'start'、'end'のいずれかを指定します。
指定しなかった場合は、'keep'となります。

bold;">keep:選択範囲を保持します。(指定したtextが選択状態となります)
bold;">start:キャレットを選択範囲の最初に移動します
bold;">end:キャレットを選択範囲の後ろに移動します

insertBeforeSelection(text[, caret = 'keep'])

テキストボックス/エリアの選択されたテキストの前に文字列を挿入する

text

挿入する文字列を指定します。

caret

caretには'keep'か'start'、'end'のいずれかを指定します。
指定しなかった場合は、'keep'となります。

bold;">keep:選択範囲を保持します。
bold;">start:キャレットを選択範囲の最初に移動します
bold;">end:キャレットを選択範囲の後ろに移動します

insertAfterSelection(text[, caret = 'keep'])

テキストボックス/エリアの選択されたテキストの後に文字列を挿入する

text

挿入する文字列を指定します。

caret

caretには'keep'か'start'、'end'のいずれかを指定します。
指定しなかった場合は、'keep'となります。

bold;">keep:選択範囲を保持します。
bold;">start:キャレットを選択範囲の最初に移動します
bold;">end:キャレットを選択範囲の後ろに移動します

getCaretPos()

テキストボックス/エリアの選択範囲を取得する

返り値

Object型 {start: 選択開始位置, end: 選択終了位置} で選択範囲が返ります。

var pos = $('textarea').getCaretPos();
alert(''+ pos.start + ' から ' + pos.end + 'までが選択されています。');

setCaretPos(range)

テキストボックス/エリアの選択範囲を設定する

range

Object型 {start: 選択開始位置, end: 選択終了位置} で選択範囲を指定します。

var strLen = $('textarea').val().length; // 文字数を取得

// 全て選択します
$('textarea').setCaretPos({
  start: 0,     // 最初から
  end  : strLen // 最後まで
});
ファイルはこちら

http://jquery-selection.googlecode.com/svn/tags/(URLが間違っていたため訂正しました)
http://code.google.com/p/jquery-selection/downloads/list (圧縮ファイル)

色空間メソッドと色のアニメーションを拡張するjQueryプラグインiColorを書いた

[色空間] - 今日も適当ダイアリーなどで、色空間の変換についての記事を書きましたが、それらを簡単に使えるように、jQueryプラグインとして書き直しました。

まだ、アルファ版なのですが、取り急ぎ公開しておきます。

簡単な説明

このプラグインでは、jQueyrに下記3つが拡張されます。

  • animateの拡張(色のアニメーションが行えます)
  • getColorメソッドの追加(iColorオブジェクトを返します)
  • iColorオブジェクト($.iColor)の追加

animateを拡張

jQuery単体ではanimateにて色のアニメーションが行えないのですが、行えるように拡張を行いました。
また、柔軟な指定に対応しました。

対応したプロパティ名は下記の通りです。

  • color
  • backgroundColor
  • borderTopColor
  • borderRightColor
  • borderBottomColor
  • borderLeftColor
  • outlineColor

値は下記2通りの方法で指定出来ます。

  • CSS色指定 (※rgba、hsla形式も対応していますが、IEではalpha値は無視されます)
  • オブジェクト指定

サンプルを見た方がわかりやすいと思います。

$("#test1").animate({
  backgroundColor: 'yellow'
}, 2000);

$("#test2").animate({
  backgroundColor: 
  // オブジェクトとして、一部の値を渡すことも出来ます。
  {
    'h': '+=1080',
    's': '-=0.3',
    'mode': 'HSV'
  }
}, 3000);

$("#test3").animate({
  backgroundColor: 'rgba(255,255,0,0)',
  borderTopColor: 'lightblue',
  borderBottomColor: 'lightblue',
  borderLeftColor: 'lightblue',
  borderRightColor: 'lightblue'
}, 2000);

$("#test4").animate({
  backgroundColor: {
    'r': '-=100',
    'b': 200,
    'a': '+=0.5',
    'mode': 'RGB'
  }
}, 2000);

getColorメソッドの追加

iColor function getColor(attr)

attr にはCSSプロパティ名を指定します。

返り値はiColorオブジェクトとなります。

// iColor オブジェクトが返ります。
color = $('div#test1').getColor('background-color');

// こんな使い方もできます
$('#getColorTest2').text(
  $('div#test1').getColor('background-color') // div#test1の背景色を取得
    .change('h', '+=30') // hue (色相) を +30度 変える
    .getStr() // 色表現文字列を取得
);

iColorオブジェクトの追加

iColorオブジェクトは下記のいずれかにより生成されます。

// A) getColorメソッド
color = $('div#test1').getColor('background-color');

// B) iColorコンストラクタ
// B-1. 引数指定
color = jQuery.iColor(color1, color2, color3, [type = 'RGB']);
color = jQuery.iColor(color1 color2, color3, [alpha], [type = 'RGBA']);

// color1〜3は数値ですが、typeにより取り得る範囲が異なります。
//      R, G, B: 0 〜 255 または 0% 〜 100%
//      H: 0〜360 (角度なので 0 と 360 は同値)
//      S, V, L: 0 〜 1 または 0% 〜 100%
// alpha 値は 0〜1の数値です。
// type は色空間を表し次のいずれかを取ります。
//      'RGB', 'RGBA', 'HSV', 'HSVA', 'HLS', 'HLSA', 'HSL', 'HSLA'

// B-2. 配列指定
color = jQuery.iColor(colorArray, [type = 'RGB'||'RGBA']);
// colorArray は [color1, color2, color3]
// または [color1, color2, color3, alpha] となります。 

// B-3. オブジェクト指定
color = jQuery.iColor(colorArray, [type = 'RGB'||'RGBA']);
// 例: jQuery.iColor({r: 255, g: 255, b: 255}, 'RGB');
//      jQuery.iColor({r: 255, g: 255, b: 255, type: 'RGB'});

// B-4. CSS色表現指定
color = jQuery.iColor(colorStr);
// colorStr は、 blue や #rrggbb、rgb(255,255,0)、hsla(300,10%,50%,1) など

iColorオブジェクトのプロパティ

iColorオブジェクトは下記のプロパティを持ちます。

typeプロパティ

type プロパティは色空間を表し次のいずれかを取ります。
'RGB', 'RGBA', 'HSV', 'HSVA', 'HLS', 'HLSA', 'HSL', 'HSLA'

色要素プロパティ

typeプロパティに応じて、それぞれの色要素プロパティを持ちます。
たとえば、typeがRGBだった場合、r、g、bという3つの色要素プロパティを持つことになります。

iColorオブジェクトのメソッド

iColorオブジェクトは下記のメソッドを持ちます。

string function getStr([format = '#xxx'])

iColorオブジェクトのCSS色表現文字列を返します。

format は下記のいずれかを取ります

#xxx
#fffのような(可能であれば、短い)16進表現でテキストを返します。
#xxxxxx
#ffffffのような6桁の6進表現でテキストを返します。
rgb
rgb(255, 255, 255) または、rgba(255, 255, 255, 1)形式で返します。
rgb%
rgb(100%, 100%, 100%) または、rgba(100%, 100%, 100%, 1)形式で返します。
hsl
hsl(350, 0.5, 1) または、hsla(350, 0.5, 1, 1)形式で返します。
iColor function to(type)
iColor function toRGB()
iColor function toRGBA()
iColor function toHSV()
iColor function toHSVA()
iColor function toHSL()
iColor function toHSLA()
iColor function toHLS()
iColor function toHLSA()

iColorオブジェクトの色空間変換し、新しいiColorオブジェクトを返します。
toXXX 形式は、to('XXX')のショートカットであり、同一の挙動をします。

iColor function change(target, val, mode)

iColorオブジェクトの特定の色要素を変更し、新しいiColorオブジェクトを返します。

target には変更したい色要素名(1文字の文字列)を指定します。

val には、変更したい値(1 や '255' など)、もしくは変化させたい量('+=0.1' や '-=30' など)を指定出来ます。

詳細はデモで確認してください。

RGB色空間とHLS(HSL, HSI)色空間の相互変換 Javascript版

RGB色空間とHSV色空間の相互変換 Javascript版 - 今日も適当ダイアリーJavascript での RGBHSV の相互変換処理を書いたので、今度はRGB と HLS(HSL、HSIと呼ばれる事もある)の相互変換をメモ。

HLS色空間については HLS色空間 - Wikipedia などを参照してください。

RGB から HLS色空間へのコンバート

/**
 * RGB を HLS へ変換します
 *
 * @param   {Number}  r         red値   ※ 0〜255 の数値
 * @param   {Number}  g         green値 ※ 0〜255 の数値
 * @param   {Number}  b         blue値  ※ 0〜255 の数値
 * @return  {Object}  {h, l, s} ※ h は 0〜360の数値、l/s は 0〜1 の数値
 */
function RGBtoHLS(r, g, b) {
  var h, // 0..360
      l, s; // 0..1

  // 0..1 に変換
  r = r / 255;
  g = g / 255;
  b = b / 255;
  var max = Math.max(Math.max(r, g), b),
      min = Math.min(Math.min(r, g), b);

  // hue の計算
  if (max == min) {
    h = 0; // 本来は定義されないが、仮に0を代入
  } else if (max == r) {
    h = 60 * (g - b) / (max - min) + 0;
  } else if (max == g) {
    h = (60 * (b - r) / (max - min)) + 120;
  } else {
    h = (60 * (r - g) / (max - min)) + 240;
  }

  while (h < 0) {
    h += 360;
  }

  // Lightness の計算
  l = (max + min) / 2;

  // Saturation の計算
  if (max == min) {
    s = 0;
  } else {
    s = (l < 0.5)
      ? (max - min) / (max + min)
      : (max - min) / (2.0 - max - min);
  }

  return {'h': h, 'l': l, 's': s, 'type': 'HLS'};
}

HLS から RGB色空間へのコンバート

/**
 * HLS を RGB へ変換します
 *
 * @param   {Number}  h         hue値        ※ 0〜360の数値
 * @param   {Number}  l         lightness値  ※ 0〜1 の数値
 * @param   {Number}  s         saturation値 ※ 0〜1 の数値
 * @return  {Object}  {r, g, b} ※ r/g/b は 0〜255 の数値
 */
function HLStoRGB(h, l, s) {
  var r, g, b; // 0..255

  while (h < 0) {
    h += 360;
  }
  h = h % 360;

  // 特別な場合 saturation = 0
  if (s == 0) {
    // → RGB は V に等しい
    l = Math.round(l * 255);
    return {'r': l, 'g': l, 'b': l, 'type': 'RGB'};
  }

  var m2 = (l < 0.5) ? l * (1 + s) : l + s - l * s,
      m1 = l * 2 - m2,
      tmp;

  tmp = h + 120;
  if (tmp > 360) {
    tmp = tmp - 360
  }

  if (tmp < 60) {
    r = (m1 + (m2 - m1) * tmp / 60);
  } else if (tmp < 180) {
    r = m2;
  } else if (tmp < 240) {
    r = m1 + (m2 - m1) * (240 - tmp) / 60;
  } else {
    r = m1;
  }

  tmp = h;
  if (tmp < 60) {
    g = m1 + (m2 - m1) * tmp / 60;
  } else if (tmp < 180) {
    g = m2;
  } else if (tmp < 240) {
    g = m1 + (m2 - m1) * (240 - tmp) / 60;
  } else {
    g = m1;
  }

  tmp = h - 120;
  if (tmp < 0) {
    tmp = tmp + 360
  }
  if (tmp < 60) {
    b = m1 + (m2 - m1) * tmp / 60;
  } else if (tmp < 180) {
    b = m2;
  } else if (tmp < 240) {
    b = m1 + (m2 - m1) * (240 - tmp) / 60;
  } else {
    b = m1;
  }

  return {'r': Math.round(r * 255), 'g': Math.round(g * 255), 'b': Math.round(b * 255), 'type': 'RGB'};
}

ためしてみる

var r = 10, g = 155, b = 200, hls, rgb;
hls = RGBtoHLS(r, g, b);
rgb = HLStoRGB(hls.h, hls.l, hls.s);

alert('R: ' + r + ', G: ' + g + ', B: ' + b + '\n' +
  ' => H : ' + hls.h + ', L: ' + hls.l + ', S: ' + hls.s + '\n' +
  ' => R : ' + rgb.r + ', G: ' + rgb.g + ', B: ' + rgb.b);

RGB色空間とHSV色空間の相互変換 Javascript版

RGB色空間とHSV色空間の相互変換 PHP版 - 今日も適当ダイアリー の記事に、「Javascript RGB HSV変換」というキーワード検索で入ってくる方がいらっしゃるようなので、Javascript版も一応掲載しておきます。

HSV色空間については HSV色空間 - Wikipedia を参照してください。
上記Wikipediaに載っている変換式をそのままjavascriptにしただけなので、特に難しい事はしていません。


jQueryプラグインにすれば、ちょっとした時に使えるかも?と思いましたが、意外に面倒そうなので、書いたらあらためて、このブログで紹介したいと思います。

RGB から HSV色空間へのコンバート

/**
 * RGB配列 を HSV配列 へ変換します
 *
 * @param   {Number}  r         red値   ※ 0〜255 の数値
 * @param   {Number}  g         green値 ※ 0〜255 の数値
 * @param   {Number}  b         blue値  ※ 0〜255 の数値
 * @param   {Boolean} coneModel 円錐モデルにするか
 * @return  {Object}  {h, s, v} ※ h は 0〜360の数値、s/v は 0〜255 の数値
 */
function RGBtoHSV (r, g, b, coneModel) {
  var h, // 0..360
      s, v, // 0..255
      max = Math.max(Math.max(r, g), b),
      min = Math.min(Math.min(r, g), b);

  // hue の計算
  if (max == min) {
    h = 0; // 本来は定義されないが、仮に0を代入
  } else if (max == r) {
    h = 60 * (g - b) / (max - min) + 0;
  } else if (max == g) {
    h = (60 * (b - r) / (max - min)) + 120;
  } else {
    h = (60 * (r - g) / (max - min)) + 240;
  }

  while (h < 0) {
    h += 360;
  }

  // saturation の計算
  if (coneModel) {
    // 円錐モデルの場合
    s = max - min;
  } else {
    s = (max == 0)
      ? 0 // 本来は定義されないが、仮に0を代入
      : (max - min) / max * 255;
  }

  // value の計算
  v = max;

  return {'h': h, 's': s, 'v': v};
}

HSV から RGB色空間へのコンバート

/**
 * HSV配列 を RGB配列 へ変換します
 *
 * @param   {Number}  h         hue値        ※ 0〜360の数値
 * @param   {Number}  s         saturation値 ※ 0〜255 の数値
 * @param   {Number}  v         value値      ※ 0〜255 の数値
 * @return  {Object}  {r, g, b} ※ r/g/b は 0〜255 の数値
 */
function HSVtoRGB (h, s, v) {
  var r, g, b; // 0..255

  while (h < 0) {
    h += 360;
  }

  h = h % 360;

  // 特別な場合 saturation = 0
  if (s == 0) {
    // → RGB は V に等しい
    v = Math.round(v);
    return {'r': v, 'g': v, 'b': v};
  }

  s = s / 255;

  var i = Math.floor(h / 60) % 6,
      f = (h / 60) - i,
      p = v * (1 - s),
      q = v * (1 - f * s),
      t = v * (1 - (1 - f) * s)

  switch (i) {
    case 0 :
      r = v;  g = t;  b = p;  break;
    case 1 :
      r = q;  g = v;  b = p;  break;
    case 2 :
      r = p;  g = v;  b = t;  break;
    case 3 :
      r = p;  g = q;  b = v;  break;
    case 4 :
      r = t;  g = p;  b = v;  break;
    case 5 :
      r = v;  g = p;  b = q;  break;
  }

  return {'r': Math.round(r), 'g': Math.round(g), 'b': Math.round(b)};
}

ためしてみる

var r = 10, g = 155, b = 200, hsv, rgb;
hsv = RGBtoHSV(r, g, b);
rgb = HSVtoRGB(hsv.h, hsv.s, hsv.v);

alert('R: ' + r + ', G: ' + g + ', B: ' + b + '\n' +
  ' => H : ' + hsv.h + ', S: ' + hsv.s + ', V: ' + hsv.v + '\n' +
  ' => R : ' + rgb.r + ', G: ' + rgb.g + ', B: ' + rgb.b);

SlicehostからさくらのVPSへ乗り換えようか

趣味利用のサーバーとして、Rackspace Managed Cloud Services—More than just infrastructureを使っているんだけど、VPS(仮想専用サーバ)|さくらインターネット - 無料お試し実施中に乗り換えようか考えている。
とりあえず、昨日、申し込みをして、さくらのVPSをお試し中。

基本的なスペック(価格も含めて)は、さくらのVPSが上回っているんだけど、ちょっと僕が迷っているのは下記の3点だ。

Slicehostで出来て、さくらのVPSで出来ないこと

(自分が迷っている点のみ)

IPアドレス追加がない(1個だけ)
もう1つ(計2つ)欲しいんだよね。。
Slicehostの場合は、追加IPは1個につき $2.0で出来る。
ネームサーバがついてこない
自分で動かすのはいやだし。さくらのDNSサーバーでできるのかな?(でも、月額がVPSより高い。。。)
Slicehostなら標準でついてくる。
IPアドレスの逆引きができない
Slicehostなら標準でできる。

オプション対応とかでもいいので、してくれたら、はい変えます!って感じなのだけれど。

もうちょっと悩もうと思う。

結局、さくらに移転することになる気もするけど。

パスワード(ランダム)文字列を生成してみる

パスワード自動生成のための javascript を書いてみた。

要件

要件は、下記の通りとします。

  • 生成する文字列の文字数を指定できること
  • 生成する文字列に使用する文字リストを指定できること
  • 生成する文字列の文字が重複しないようにするか否かを指定できること

関数定義

関数の定義は以下の通り

パスワード文字列(ランダム文字列)を生成する

@param     {Number}   i 生成する文字列の文字数
@param     {String}   s 生成する文字列に使用する文字リスト
@param     {Boolean}  f 文字が重複しないようにするか否か
@returns   {String}   生成された文字列

素直にforループでまわしてみる

単純に、forループを使ってランダムな文字列を生成してみます。

なお、今回は、一回限り使えればよかったので無名関数で定義してます。(なので、使いきりです)

(function(i,s,f) {
    var p = '';

    for (; i > 0; i--) {
        // 文字リストからランダムに選択
        var j = Math.floor(Math.random() * s.length);

        // 結果文字列に追加
        p += s.charAt(j);

        // 文字を重複させない場合は今回使った文字をリストから削除
        if (f) {
            s = s.substr(0, j) + s.substr(j + 1);
        }
    }

    return p;
})(
    10, // 文字数
    'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789', // 文字リスト
    true); // 文字を重複させない場合はtrue

念のため言っておくと、実際に利用するには、下記のように記述します。

document.getElementById('ターゲットのID').value =
    /* ここに本体のコード */

やっている事は単純で、forループ内で文字リストからランダムな一文字を選択して、それを結果文字列に追加しているだけです。
文字を重複させない場合は、文字リストから今使った文字を削除していく感じですね。

実行例:http://www.weekendphp.com/misc/blog/hatena/ja9/2010-09-01.1.html

圧縮してみる

上記のコードをあーだこーだして、改行やスペースなどを取っ払って、圧縮(?)してみます。

(function(i,s,f){for(var m=Math,p='';i>0;i--){var j=m.floor(m.random()*s.length);p+=s.charAt(j);s=f?s.substr(0,j)+s.substr(j+1):s}return p})(10,'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789',true);

本体部分は140バイトになりました。
もっと短く出来ないかと、別の方法も考えてみます。

こんどは再帰関数にしてみる

再帰関数にすれば、もっと短くなるのでは?と思い、チャレンジしてみます。

今回は、勉強がてら無名関数を再帰してみます。
自分自身は、arguments.calleeで参照できます。

参考:arguments.callee - JavaScript | MDN

(function(i,s,f) {
    var j = Math.floor(Math.random() * s.length); // 文字リストからランダムに選択

    if (i <= 1) {
        return s.charAt(j);
    }

        // 文字を重複させない場合は今回使った文字をリストから削除
    if (f) {
        s = s.substr(0, j) + s.substr(j + 1);
    }

    // i > 1 なら再帰させる
    return s.charAt(j) +
            arguments.callee(i - 1, s, f); // 自分自身を呼び出す
})(
    10, // 文字数
    'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789', // 文字リスト
    true); // 文字を重複させない場合はtrue

やっている事自体は、forの時と大差ありません。

圧縮してみる

あーだこーだします。

(function(i,s,f){var m=Math,j=m.floor(m.random()*s.length);return s.charAt(j)+(i>1?arguments.callee(i-1,f?s.substr(0,j)+s.substr(j+1):s,f):'')})(10,'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789',true);

どうかな?
と思ったら、本体部分は144バイトでした。
forループより4バイト増えてしまいました。

そもそも、arguments.callee という変数/メソッド名が長いので、不利だったぽいです。

また、時間が時間があったら別の方法も考えてみようかな。

RGB色空間とHSV色空間の相互変換 PHP版

PHP配列とCSS色表現の相互変換 - 今日も適当ダイアリー に続いて、 relColors に使った関数を公開します。

色の明度や色相を計算するために、いったんRGBで表現された色情報を、HSV色空間に変換した上で計算を行いました。
HSV色空間については HSV色空間 - Wikipedia を参照してください。

RGB から HSV色空間へのコンバート

変換式は、上記したWikipediaに載っているので、それを素直にPHPで書きました。

<?php
/**
 * RGB配列 を HSV配列 へ変換します
 *
 * @param   array   $arr            array(r, g, b) ※ r/g/b は 0〜255 の数値
 * @param   bool    $coneModel      円錐モデルにするか
 * @return  array   array(h, s, v) ※h は 0〜360の数値、s/v は 0〜255 の数値
 */
function RGBtoHSV($arr, $coneModel = false)
{
    $h = 0; // 0..360
    $s = 0; // 0..255
    $v = 0; // 0..255

    $max = max($arr);
    $min = min($arr);

    // hue の計算
    if ($max == $min) {
        $h = 0; // 本来は定義されないが、仮に0を代入
    } else if ($max == $arr[0]) {
        // MAX = R
        // 60 * (G-B)/(MAX-MIN) + 0
        $h = 60 * ($arr[1] - $arr[2]) / ($max - $min) + 0;
    } else if ($max == $arr[1]) {
        // MAX = G
        // 60 * (B-R)/(MAX-MIN) + 120
        $h = (60 * ($arr[2] - $arr[0]) / ($max - $min)) + 120;
    } else {
        // MAX = B
        // 60 * (R-G)/(MAX-MIN) + 240
        $h = (60 * ($arr[0] - $arr[1]) / ($max - $min)) + 240;
    }

    while ($h < 0) {
        $h += 360;
    }

    // saturation の計算
    if ($coneModel) {
        // 円錐モデルの場合
        $s = $max - $min;
    } else {
        if ($max == 0) {
            // 本来は定義されないが、仮に0を代入
            $s = 0;
        } else {
            $s = ($max - $min) / $max * 255;
        }
    }

    // value の計算
    $v = $max;

    return array(
        $h, // H
        $s, // S
        $v, // V
    );
}

HSV から RGB色空間へのコンバート

<?php

/**
 * HSV配列 を RGB配列 へ変換します
 *
 * @param   array   $arr            array(h, s, v) ※h は 0〜360の数値、s/v は 0〜255 の数値
 * @return  array   array(r, g, b) ※ r/g/b は 0〜255 の数値
 */
function HSVtoRGB($arr)
{
    $r = 0; // 0..255
    $g = 0; // 0..255
    $b = 0; // 0..255

    while ($arr[0] < 0) {
      $arr[0] += 360;
    }

    $arr[0] = $arr[0] % 360;

    // 特別な場合
    if ($arr[1] == 0) {
        // S = 0.0
        // → RGB は V に等しい
        return array(
            round($arr[2]),
            round($arr[2]),
            round($arr[2]),
        );
    }

    $arr[1] = $arr[1] / 255;


    // Hi = floor(H/60) mod 6
    $i = floor($arr[0] / 60) % 6;
    // f = H/60 - Hi
    $f = ($arr[0] / 60) - $i;

    // p = V (1 - S)
    $p = $arr[2] * (1 - $arr[1]);
    // q = V (1 - fS)
    $q = $arr[2] * (1 - $f * $arr[1]);
    // t = V (1 - (1 - f) S)
    $t = $arr[2] * (1 - (1 - $f) * $arr[1]);

    switch ($i) {
        case 0 :
            // R = V, G = t, B = p
            $r = $arr[2];
            $g = $t;
            $b = $p;
            break;
        case 1 :
            // R = q, G = V, B = p
            $r = $q;
            $g = $arr[2];
            $b = $p;
            break;
        case 2 :
            // R = p, G = V, B = t
            $r = $p;
            $g = $arr[2];
            $b = $t;
            break;
        case 3 :
            // R = p, G = q, B = V
            $r = $p;
            $g = $q;
            $b = $arr[2];
            break;
        case 4 :
            // R = t, G = p, B = V
            $r = $t;
            $g = $p;
            $b = $arr[2];
            break;
        case 5 :
            // R = V, G = p, B = q
            $r = $arr[2];
            $g = $p;
            $b = $q;
            break;
    }

    return array(
        round($r), // r
        round($g), // g
        round($b), // b
    );
}

DB内のテーブル名一覧、テーブル情報などを取得するSQL

PHPで新人教育向けにSQL(ここでは PostgreSQL)勉強用プログラムを制作しているのだけど、その中で、DB内のテーブル名一覧や、テーブル情報、列情報などを表示させたい、と思った。

で、お題を追加や変更時になるべく最小限の変更で済むようにするには……とかそんな事を思ったら、やっぱりDBの事はDBに聞け、って結論に。

始めて pg_* 系のテーブルをあーだこーだして、たどり着いたSQLを(ツッコミどころがありそうで心配なのですが)備忘録代わりに記載しておきます。

テーブル一覧の取得

SELECT
    cls.oid,     -- pg_class.oid
    nsp.nspname, -- テーブルを持つスキーマ名 public とか
    usr.usename, -- 所有ユーザー名
    cls.relname  -- テーブル名
FROM
    pg_class cls
        INNER JOIN pg_namespace nsp
            ON (cls.relnamespace = nsp.oid)
        INNER JOIN pg_user usr
            ON (cls.relowner = usr.usesysid)
WHERE
    cls.relkind = 'r'
--     AND nsp.nspname = 'public' -- スキーマ名で絞込み
--     AND usr.usename = 'user1' -- 所有ユーザー名で絞込み
ORDER BY
    nsp.nspname,
    usr.usename,
    cls.relname

pg_class を基点に、ネームスペースとユーザー情報を取得します。
relkind は種別を表していますが、小文字の r が通常のテーブル、となります。

シーケンス一覧の取得

SELECT
    cls.oid,     -- pg_class.oid
    nsp.nspname, -- シーケンスを持つスキーマ名 public とか
    usr.usename, -- 所有ユーザー名
    cls.relname  -- テーブル名
FROM
    pg_class cls
        INNER JOIN pg_namespace nsp
            ON (cls.relnamespace = nsp.oid)
        INNER JOIN pg_user usr
            ON (cls.relowner = usr.usesysid)
WHERE
    cls.relkind = 'S'
--     nsp.nspname = 'public' -- スキーマ名で絞込み
--     usr.usename = 'user1' -- 所有ユーザー名で絞込み
ORDER BY
    nsp.nspname,
    usr.usename,
    cls.relname

やっている事は、テーブル一覧の取得と全く変わりません。
relkind が大文字の S がシーケンスを表します。

テーブル列一覧の取得

上記テーブル一覧取得の際にゲットした「pg_class.oid」を基に列の一覧を取得します。

SELECT
    colname as "列名",
    CASE
        WHEN size IS NULL
            THEN datatype
        -- サイズ情報が取得できた場合は付加
        ELSE datatype || ' (' || size || ')'
    END AS "データ型",
    CASE
        -- NOT NULL情報 と DEFAULT情報をマージ
        WHEN "notnull" IS NOT NULL AND "default" IS NOT NULL
            THEN "notnull" || ' ' || "default" -- 
        ELSE COALESCE("notnull", "default")
    END AS "属性"
FROM
    (
        SELECT
            attr.attnum AS idx, -- 列番号
            attr.attname AS colname, -- 列名
            CASE typ.typname -- 分かりやすい名前に変更
                WHEN 'int2'   THEN 'SMALLINT'
                WHEN 'int4'   THEN 'INT'
                WHEN 'int8'   THEN 'BIGINT'
                WHEN 'float4' THEN 'REAL'
                WHEN 'float8' THEN 'DOUBLE'
                WHEN 'bpchar' THEN 'CHAR'
                ELSE UPPER(typ.typname)
            END AS datatype, -- データ型
            -- attr.atttypmod が正の整数なら、
            -- サイズ付加情報あり?
            -- (よく調べて無いので自信なし)
            CASE WHEN attr.atttypmod > 0 THEN
                CASE typ.typname
                    WHEN 'numeric'
                        THEN (attr.atttypmod - 4) / 65536
                    WHEN 'decimal'
                        THEN (attr.atttypmod - 4) / 65536
                    WHEN 'date'
                        THEN COALESCE(attr.atttypmod - 4, 10)
                    WHEN 'time'
                        THEN COALESCE(attr.atttypmod - 4, 8)
                    WHEN 'timestamp'
                        THEN COALESCE(attr.atttypmod - 4, 19)
                    ELSE attr.atttypmod - 4
                END
            END AS size,
            -- attnotnull が true なら NOT NULL
            CASE attr.attnotnull
                WHEN TRUE THEN 'NOT NULL'
                ELSE NULL
            END AS notnull,
            -- atthasdef が true なら DEFAULT 値あり
            CASE attr.atthasdef
                WHEN TRUE THEN 'DEFAULT ' || adef.adsrc
                ELSE NULL
            END AS default
        FROM
            pg_class cls
                INNER JOIN pg_attribute attr
                    ON (cls.oid = attr.attrelid)
                INNER JOIN pg_type typ
                    ON (attr.atttypid = typ.oid)
                -- デフォルト値情報
                LEFT OUTER JOIN pg_attrdef adef
                    ON (cls.oid = adef.adrelid
                        AND attr.attnum = adef.adnum)
        WHERE
            cls.oid = ? -- pg_class.oid を入れよ
            -- 列番号が0以下の場合、システム列なので対象にしない
            AND attr.attnum >= 0
            -- attisdropped が TRUE なら、
            -- この列はすでに削除されていて無効状態
            AND attr.attisdropped IS NOT TRUE
            AND typ.typisdefined
    ) AS tbl
ORDER BY idx;

メジャーどころの型でしか試してないし、複合型などの事は一切考えずに書いているので、問題があるかもしれません。
特にサイズ情報の取得は自信がない(=適当)です。
必要があったら、また調べて書き直したいと思います。
というか、コメントで教えていただけると助かります。(自堕落ですみません)

テーブルインデックスの取得

「pg_class.oid」を基にテーブルインデックス情報を取得します。

SELECT
    cls.relname AS name, -- インデックス名
    (
        CASE
            -- プライマリキー
            WHEN idx.indisprimary
                THEN 'PRIMARY KEY, '
            -- ユニーク属性
            WHEN idx.indisunique
                THEN 'UNIQUE, '
        END
        || am.amname -- インデックスのアクセスメソッド名
        || ' ('
        -- 列名の取得(カンマ区切りで並べる)
        || ARRAY_TO_STRING(ARRAY(
            SELECT
                attr.attname
            FROM
                pg_class cls2
                    INNER JOIN pg_attribute attr
                        ON (cls2.oid = attr.attrelid)
            WHERE
                cls2.oid = idx.indrelid
                AND attr.attnum = ANY(idx.indkey)
        ), ', ')
        || ')'
    )AS value -- 情報
FROM
    pg_index idx
        INNER JOIN pg_class cls
            ON (idx.indexrelid = cls.oid)
        INNER JOIN pg_am am
            ON (cls.relam = am.oid)
WHERE
    idx.indrelid = ? -- pg_class.oid を入れよ
ORDER BY
    indexrelid;

テーブルチェック制約 / 外部キー制約の取得

「pg_class.oid」を基にチェック制約 / 外部キー制約を取得します。

SELECT
    con.conname AS name, -- 制約名
    pg_get_constraintdef(con.oid) AS value -- 情報
FROM
    pg_constraint con
WHERE
    con.conrelid = ? -- pg_class.oid を入れよ
    AND (con.contype = 'c' OR con.contype = 'f')
ORDER BY
    con.contype,
    conname;

テーブルトリガの取得

「pg_class.oid」を基にテーブルトリガ名、実行する関数情報を取得します。

SELECT
    trg.tgname, -- トリガ名
    prc.proname, -- 実行する関数名
    rtrim(replace(encode(trg.tgargs, 'escape')
        , E'\\000', ', '),', ') -- 引数
FROM
    pg_trigger trg
    INNER JOIN pg_proc prc ON (trg.tgfoid = prc.oid)
WHERE
    trg.tgrelid = 17812 -- pg_class.oid を入れよ
    AND trg.tgenabled
    AND trg.tgisconstraint IS FALSE

トリガが実行されるタイミングは取得していません。
(面倒そうだったので、機会があれば調べてみようと思います。)


間違い、過不足、他のいい方法などありましたら、コメントで教えてください。
よろしくお願いします。

PHP配列とCSS色表現の相互変換

前回(色をアルファブレンドする - 今日も適当ダイアリー)にて、 relColors に使った使い回せそうな関数の公開を始めたのですが、ユーティリティ関数を書くのをすっかり忘れていました。

ということで、色テキスト表現からPHP配列の相互変換の関数を提示しておきます。

たいした事してないですけどね。
PHPで16進数テキストを integer にしたい場合は hexdec を使うとか、integer から16進表現にする場合は sprintf を使うとか、そこあたりは、よく忘れますよね。
初めてこの関数を聞いたっていう方は、頭の片隅においておくと、何かの時に便利かもと思います。

<?php

/**
 * 色表現テキスト(#rrggbb 形式) を配列形式に変換します
 *
 * @param   string  $str        色表現テキスト(#rrggbb 形式)
 * @return  array   array(r, g, b)
 */
function getColor($str)
{
    // 先頭の # を除去 (#rrggbb → rrggbb)
    if (strpos($str, '#') === 0) {
        $str = substr($str, 1);
    }

    // 先頭の 0x を除去 (0xrrggbb → rrggbb)
    if (strpos($str, '0x') === 0) {
        $str = substr($str, 2);
    }

    // 6 桁にそろえる
    switch (strlen($str)) {
        case 6:
            // 6桁ならそのまま
            break;
        case 3:
            // 3桁なら6桁に (rgb → rrggbb)
            $str = $str[0] . $str[0] . $str[1] . $str[1] . $str[2] . $str[2];
            break;
        default:
            // それ以外はNG
            return false;
    }

    $matches = array();
    // rrggbb を色事にキャプチャ
    if (preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i', $str, $matches) != 1) {
        // rrggbb (それぞれは0~9 か a〜f) 以外ならNG
        return false;
    }

    return array(
        hexdec('0x' . $matches[1]), // (int) r
        hexdec('0x' . $matches[2]), // (int) g
        hexdec('0x' . $matches[3]), // (int) b
    );
}

/**
 * 配列形式 を 色表現テキスト(#rrggbb 形式) に変換します
 *
 * @param   array   $arr        array(r, g, b)
 * @param   bool    $compress   短い色表現(#rgb 形式) を許すか
 * @param   string  $prefix     プレフィックス (# 以外にしたい場合は指定)
 * @return  string  色表現テキスト(#rrggbb 形式)
 */
function getText($arr, $compress = true, $prefix = '#')
{
    // マイナス値が入っていたらNG
    if (min($arr) < 0) {
        return false;
    }

    // rrggbb 形式に変換 (integer 型に強制しておく)
    $tmp = sprintf('%02x%02x%02x', (int) $arr[0], (int) $arr[1], (int) $arr[2]);

    // 6桁にならない場合は失敗 (256以上が入っているとそうなる)
    if (strlen($tmp) !== 6) {
        return false;
    }

    // 短い色表現を許す場合で、
    // rr/gg/bb のそれぞれの上位と下位が同一であれば
    if ($compress
            && $tmp[0] == $tmp[1]
            && $tmp[2] == $tmp[3]
            && $tmp[4] == $tmp[5]) {
        // 3桁(rgb)にする
        $tmp = $tmp[0] . $tmp[2] . $tmp[4];
    }

    return $prefix . $tmp;
}

こんなもんです。