AviUtlの色変換プラグインを作ってみました。名前は rgb2yuy2(BT.601) で、SSE2必須です。

 いつもの置き場所

 「補間なしYUY2アップサンプリング」と「UVダウンサンプリング(のYUY2平均)」を統合したようなもので、RGB → YUY2 を重視したつもりです。
 Aviutl標準の BT.601 と同じように使えますが、Aviutl標準はYUY2出力時に小数点以下切捨ての傾向があるようなので、それに対して四捨五入としてみました。
 あと、頑張ってSSE2化(prefetch*やmovnt*も使ってる)してますが、標準より遅いです……。


 以下、薀蓄です。
 rgb2yuy2(BT.601)では、RGBからAviUtlの内部変数YC48への計算式はこんな感じにしています。
 この +2048 が薀蓄の結論です。

y = ( 19666*R + 38595*G + 7532*B + 2048) >> 12;
cb = (-11105*R - 21792*G + 32897*B + 2048) >> 12;
cr = ( 32897*R - 27525*G - 5372*B + 2048) >> 12;


 2の累乗の除算を右シフトに置き換えるのはよくあることで、今やというか大昔からコンパイラーがやってくれます。実はこのシフト命令への置き換えには細かい注意点があるのですが、意外と忘れがちです。特にMMXやSSE2を使うにあたっては、整数の除算命令がなく、自前で右シフトに置き換える必要があり、その際に細かいミスを犯している可能性があります。

 とりあえず思い浮かんだ注意点は3つ。
 (1) 右シフトには算術右シフトと論理右シフトがある
 (2) -1 をいくら算術右シフトしても -1 のまま
 (3) -5 / 2 は -2 だが、-5 >> 1 は -3

 算術右シフトは空いたビットを符号ビットで埋めますが、論理右シフトはゼロで埋めていきます。C言語で普通にシフトすると、符号付整数(signed)のシフトは算術シフト、符号なし整数(unsigned)のシフトなら論理シフトとなる場合が多いです(※処理系よって違うことあり)。

 (2)と(3)は小数点以下の丸め方が違うために起こります。除算の丸め方向は処理系依存ではありますが、gcc4.4.4(x86)では切り捨てですので、以下除算は切捨てするものとして書きます。
 この解決方法ですが、コンパイラーは次のように置き換えて問題を解決しているようです。

※ a / 4096 を右シフトで計算する場合(aはint型)

a が正数 → a >> 12 とそのまま計算

a が負数 → (a + 4095) >> 12 として計算


 注目は被除数が負数のときで、(除数 - 1)を加算してからシフトします。
 条件分岐があると速度が落ちてしまいそうですが、コンパイラーはちゃんとcmov命令を使うなどして機械語レベルで分岐をなくしています。
 SSE2ではpcmp*系の命令でビットマスクを作成して分岐なしにできますが、今回はそこまで正確にやらず、小数点以下の丸めを四捨五入にすることで近似しました。

 まず四捨五入について説明します。一見、除数/2 を加算してから除算すれば良いように思えますが、これも正負でやり方が変わります。

※ a / 4096 を四捨五入丸めで計算する場合(aはint型)

a が正数 → (a + 2048) / 4096 と計算

b が負数 → (a - 2048) / 4096 と計算


 被除数の正負によって加算する値の正負が変わります。(本題とずれる細かい注意点になりますが、+2048や-2048するときにオーバーフローやアンダーフローする場合もあるのでこれも注意)

 で、この四捨五入と右シフトを組み合わせます。

※ a / 4096 を四捨五入丸めで右シフトで計算する場合(aはint型)

a が正数 → (a + 2048) >> 12 と計算

b が負数 → (a - 2048 + 4095) >> 12 すなわち (a + 2047) >> 12 と計算


 計算式が正と負でだいたい同じになりました。
 2048 と 2047の違いなら画像処理においては誤差として扱うことができます。誤差があるといっても何も考えずにシフトするより精度は高いはずです。


 以上のことから一番最初にあげた色変換の計算で +2048 しているわけです。「なんで +2048 したんだろう」と後で忘れるかもしれないので書いてみました。
 ちなみに切り上げ・切捨てをランダムにすると多分ディザリングっぽくなると思う。