5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ExtendScriptで演算子のオーバーロード

Posted at

演算子のオーバーロード

会社で演算子のオーバーロードの話になったのですが、ExtendScriptでは演算子のオーバーロードがサポートされていたので、試してみました。

アニメならではの時間表記

アニメではカット等の時間表記が‟3+18”のように秒と、コマ数で表記されるのが一般的です。
1秒あたりのコマ数は24コマとなっているので、この場合コマ数でいうと90コマになります。
また、コマ数同士の計算もよく行われます。

例えば‟3+18”のカットと‟1+12”のカットの時間の合計は‟5+6”です、アニメ撮影マンは24進数がわかるよう訓練されているので簡単に暗算できますが、これをAfterEffectsのスクリプトでやろうとすると少し面倒です。

コマ数にしてしまえば普通に加算、減算ができるので以下のような関数を書けばいいでしょう。

function frame2koma(frame, fps){
    var sec = Math.floor(frame / fps);
    var koma = frame % fps;
    return sec + '+' + koma;
    }

function koma2frame(koma, fps){
    var split_koma = koma.split('+');
    var sec = Number(split_koma[0]);
    var koma = Number(split_koma[1]);
    return sec * fps + koma;
    }

function add(operandA, operandB, fps){
    return frame2koma(koma2frame(operandA, fps) + koma2frame(operandB, fps), fps);
    }

var a = '3+18';
var b = '1+12';

var c = add(a, b, 24);

$.writeln(c); //'5+6'

これでも問題なく機能していますが、もっと直感的にa + bのように出来ないでしょうか。
そこでESTKでサポートされている演算子オーバーロードを使用してみます。

ExtendScriptの演算子オーバーロードサポート

javaScript Tools Guide CC.pdfによると

// define the constructor method
function MyClass (initialValue) {
this.value = initialValue;
}
// define the addition operator
MyClass.prototype [“+”] = function (operand) {
return this.value + operand;
}
This allows you to perform the "+" operation with any object of this class:
var obj = new MyClass (5);
Result: [object Object] obj + 10;
Result: 15

とのことです。

なるほど、prototypeに生やせばOKらしいですね。ということはクラスを作ってしまえばOKなのでゴリっとクラスを作って、そこで演算子をオーバーロードしていきます。

KomaTime.jsxinc
var KomaTime = (function(){
    
    function KomaTime(initValue, fps){
        var frame = initValue;
        if (String(initValue).match(/\+/)){
            frame = koma2frame (initValue, fps);
            }
        this.frame = Number(frame);
        this.fps = fps;
        this.koma = frame2koma (frame, fps);
        }
    
    function frame2koma(frame, fps){
        var sign = (frame < 0) ? '-' : '';
        var sec = Math.floor(Math.abs(frame) / fps);
        var koma = Math.abs(frame) % fps;
        return sign + sec + '+' + koma;
        }
    
    function koma2frame(koma, fps){
        var split_koma = koma.split('+');
        var sec = Number(split_koma[0]);
        var koma = Number(split_koma[1]);
        if(sec < 0) return sec * fps - koma;
        return sec * fps + koma;
        }
    
    function correctOperand(operandValue, fps){
        var value = (operandValue instanceof KomaTime) ? String(operandValue) : operandValue;
        if (String(value).match(/\+/)){
            return koma2frame (value, fps);
            }
        return Number(value);
        }
    
    var p = KomaTime.prototype;
    
    p.toString = function(){
        return this.koma;
        }
    
    p ["+"] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        var new_frame = this.frame + corrected_Operand;
        return new KomaTime(new_frame, this.fps);
        };
    
    p ["-"] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        var new_frame = this.frame - corrected_Operand;
        return new KomaTime(new_frame, this.fps);
        };
    
    p ["<"] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        if (this.frame < corrected_Operand) return true;
        return false;
        };
    
    p [">"] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        if (this.frame > corrected_Operand) return true;
        return false;
        };
    
    p ["<="] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        if (this.frame <= corrected_Operand) return true;
        return false;
        };
    
    p [">="] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        if (this.frame >= corrected_Operand) return true;
        return false;
        };
    
    p ["=="] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        if (this.frame == corrected_Operand) return true;
        return false;
        };
    
    p ["!="] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        if (this.frame != corrected_Operand) return true;
        return false;
        };
    
    p ["==="] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        if (this.frame === corrected_Operand) return true;
        return false;
        };
    
    p ["!=="] = function(operand){
        var corrected_Operand = correctOperand(operand, this.fps);
        if (this.frame !== corrected_Operand) return true;
        return false;
        };
    
    return KomaTime;
    
    }());

ExtendScriptにはassertがないので、簡単な比較でチェックしてみます。

test.jsx
(function (){
    #include 'KomaTime.jsxinc'
    
    function assert(actual, expected) {
        $.writeln(actual === expected, '\tactual : ', String(actual), '\texpected : ', String(expected));
        }

    var kt1 = new KomaTime('3+18', 24);
    var kt2 = new KomaTime('1+12', 24);

    assert (kt1, '3+18');
    assert (kt2, '1+12');
    assert (kt1 + kt2, '5+6');
    assert (kt2 - kt1, '-2+6');
    assert ('-2+6' + kt1, kt2);
    assert (kt1 > 0, true);
    assert (kt1 < 0, false);
    assert (kt1 - kt2 > 0, true);
    assert (kt1 < 91, true);
    assert (kt1 <= 90, true);
    assert (kt1 >= 90, true);
    assert (kt1 == 90, true);
    assert (kt1 === 90, true);
    assert (kt2 != kt1, true);

}());
JavaScriptコンソール
true	actual : 3+18	expected : 3+18
true	actual : 1+12	expected : 1+12
true	actual : 5+6	expected : 5+6
true	actual : -2+6	expected : -2+6
true	actual : 1+12	expected : 1+12
true	actual : true	expected : true
true	actual : false	expected : false
true	actual : true	expected : true
true	actual : true	expected : true
true	actual : true	expected : true
true	actual : true	expected : true
true	actual : true	expected : true
true	actual : true	expected : true
true	actual : true	expected : true
結果 : undefined

クラス自体のコードは長くなりましたが、これで加算だけではなく、減算、比較もできるようになったので、色々な計算が楽になりました。

オーバーロードできる演算子は以下のようです。

Unary +, ~
Binary +, -,
*, /, %, ^
<, <=, ==
<<, >>, >>>
&, |, ===

今回は乗算、除算、インクリメント、デクリメントはどう扱った方がいいのか考えて見送っているので、運用に合わせて機能を追加してみるのもいいと思います。

まとめ

意外に便利。

その他

ExtendScriptでクラスを書くときは、今は大体こんな感じで書いています。使用する際には即時関数化されたスコープ内で#includeすると、グローバル汚染もしないので#includeはコードの一番上に書かないようにしています。
ExtendScriptはES3準拠のAdobe独自言語ですが、標準でファイルアクセスができたり、簡単なSocketが実装されていたり、地味に面白い機能がいろいろあるのですが、ES3準拠のままアップデートがなく結構大変。とはいえAdobe製品の自動化には欠かせない機能です。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?