2015年8月29日土曜日

YV12To422 その2

YV12To422を更新しました。

https://github.com/chikuzen/YV12To422

YV12To422-1.0.1.zip

低負荷でそこそこ高速にはなってるので、とりあえずddcc.dllのyv12toyuy2や本体内蔵フィルタのConvertToYUY2の置き換えにはなると思いますが、なにぶん変換のパターンが多い(interlaced(2) x itype(3) x cplace(4) で24パターン + lshift + yuy2)ので確認漏れとかありそうです。

不具合を見つけたら報告おねがいします。


今回cubic補間の実装のため、avisynth+のresamplerのコードを読みましたが、ずいぶんわかりやすくなってますね。
avisynthのインラインASMコードはまったく分からなかったのと比べてすごい違いです。
やってること自体はすごく簡単だったのでちょっとびっくりしました。

追記:
20150831 ちょっと修正したのでバイナリを上げなおしました。

2015年8月22日土曜日

YV12To422

久々にavisynthプラグインを書きました。

https://github.com/chikuzen/YV12To422

YUVTo422-0.0.0.zip

前回のYV12ToYUY2の記事書くときにドキュメントやコードを読んでたらいつの間にやらVisualStudio起動してました。

なんつーか、triticalコードって読んでるうちに自分でやってみたくなるんですよね…。

avisynth+だと、どうもマルチスレッド化されたフィルタはうまく動かないらしいので、シングルスレッドでSIMD使うようにしました。

結果的に出力は同じで、より高速低負荷になってます。

まだまだ未実装の機能もありますが、興味がある方はどうぞ。

追記:
どうやらavisynth+でうまく動かなかったのはたまたまだったらしい。
今ではddcc.dllもちゃんと動いている。
一体なにが原因だったのだろう?

2015年8月19日水曜日

YV12ToYUY2

2chのソフトウェア板見てみたら、YV12->YUY2でちょっともりあがってたようなので久しぶりにエンコネタで書いてみる。

なんでもConvertToYUY2()を色差補間なしの可逆変換したい層がそこそこいるそうで、ddcc.dllのYV12ToYUY2をitype=0で使っているらしい。
で、今回ddcc.dllのコード見てみたのだけど、これってinterlaced=false,cplace=3の時以外は、ddcc.dll使わなくてもよさそう。

とりあえずitype=0の場合はavisynth2.6だとこのスクリプトで代用できる。
(itype=0の場合、cplaceは指定しても無視される)
#YV12ToYUY2_no_interp.avs

function YV12ToYUY2_ni(clip c, bool "interlaced")
{
    is_shit = default(interlaced, false)
    assert(c.IsYV12(), "input clip is not YV12.")
    return is_shit ? c.YV12ToYUY2_ni_i() : c.YV12ToYUY2_ni_p()
}

function YV12ToYUY2_ni_p(clip c)
{
    w = c.width() / 2
    h = c.height()
    u = c.UToY8().PointResize(w, h)
    v = c.VToY8().PointResize(w, h)
    return YToUV(u, v, c).ConvertToYUY2()
}

function YV12ToYUY2_ni_i(clip c)
{
    sep = c.SeparateFields()
    w = sep.width() / 2
    h = sep.height()
    u = sep.UToY8().PointResize(w, h)
    v = sep.VToY8().PointResize(w, h)
    return YToUV(u, v, sep).Weave().ConvertToYUY2()
}

ddcc.dllのyv12toyuy2はフレーム分割方式でマルチスレッド化はされてるけどCのみで書かれていてSIMDは使ってないので、threads=1だと上記のスクリプトのほうが速い。
もともと重い処理ではないのでシングルスレッドでもボトルネックにはならないと思うけど。

ちなみに上記の処理でYUY2にしたものをYV12に戻す場合はこうなる。
function YUY2ToYV12_ni(clip c, bool "interlaced")
{
    is_shit = default(interlaced, false)
    assert(c.IsYUY2(), "input clip is not YUY2.")
    assert(c.height() % 2 == 0, "height must be mod 2.")
    c = c.ConvertToYV16()
    return is_shit ? c.YUY2ToYV12_ni_i() : c.YUY2ToYV12_ni_p()
}

function YUY2ToYV12_ni_p(clip c)
{
    w = c.width() / 2
    h = c.height() / 2
    u = c.UToY8().PointResize(w, h)
    v = c.VToY8().PointResize(w, h)
    return YToUV(u, v, c)
}

function YUY2ToYV12_ni_i(clip c)
{
    sep = c.SeparateFields()
    w = sep.width() / 2
    h = sep.height() / 2
    u = sep.UToY8().PointResize(w, h)
    v = sep.VToY8().PointResize(w, h)
    return YToUV(u, v, sep).Weave()
}

出力に差があるかないか確認したい場合はこんな感じで
LoadPlugin("ddcc.dll")
LoadPlugin("masktools2.dll")
Import("YV12ToYUY2_no_interp.avs")

src = something_yv12_clip
v0 = src.YV12ToYUY2(interlaced=true, itype=0)
v1 = YV12ToYUY2_ni(interlaced=true)
v2 = v1.YUY2ToYV12(interlaced=true)

ShowDiffYUV(v0, v1)
#ShowDiffYUV(src, v2)

function ShowDiffYUV(clip c0, clip c1)
{
    assert(c0.IsYUV() && c1.IsYUV(), "not YUV clip was found")
    c0 = c0.IsYUY2() ? c0.ConvertToYV16() : c0
    c1 = c1.IsYUY2() ? c1.ConvertToYV16() : c1
    cond = c0.PixelType() == c1.PixelType() && c0.width() == c1.width() &&\
           c0.height() == c1.height()
    assert(cond, "It's impossible to compare.")

    return mt_makediff(c0, c1, chroma="process").mt_lut("x 128 == 128 255 ?", chroma="process")
}
もし出力に違いがあれば灰色以外のケバい色になるはずです。

追記:
20150820 default()の引数の順番間違えてたのを修正

2015年8月16日日曜日

canvasでvideoを描画2

ちょっと考えて前回のコードを一部変更してみた。

  vid.autoplay = true;

  vid.addEventListener("loadeddata", function(){  
      vid.pause();  
      draw();  
      button.value = "play";  
    }, false);  

  vid.autoplay = false;
  vid.loop = true;
  vid.addEventListener("loadeddata", function(){
      vid.play();
      setTimeout(function() {
          vid.pause();
          draw();
        }, 25);
      button.value = "play";
    }, false);

とりあえずloadeddataイベント発生後25ミリ秒だけ再生するようにしたところ、Edge/IEでもChromeと同じ挙動になった。
ただこれでもスマホ(Android/Zenfone5)のChromeだとダメ。

スマホのレイテンシ高すぎるんじゃ…

2015年8月15日土曜日

canvasでvideoを描画

html5のcanvasで動画を扱い方を調べつつ書いたもの。
ブラウザの違いでめんどいことは多いがなれればそれほどでもないかも。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>

<body>
<canvas id="cnvs" width="640" height="480"></canvas>
<input id="play-or-pause" type="button" value="loading">
<script>
(function() {
  var canvas = document.getElementById("cnvs");
  var ctx = canvas.getContext("2d");
  var button = document.getElementById("play-or-pause");
  var vid = document.createElement("video");
  vid.autoplay = true;
  vid.loop = true;
  if (vid.canPlayType("video/mp4").length === 0) {
    return false;    
  }
  vid.addEventListener("loadeddata", function(){
      vid.pause();
      draw();
      button.value = "play";
    }, false);
  vid.src = "susie.mp4";

  function draw() {
    for (var dy = -118; dy < canvas.height; dy += 144) {
      for (var dx = -160; dx < canvas.width; dx += 192) {
        ctx.drawImage(vid, dx, dy);
      }
    }
  }

  button.onclick = function() {
    var intervalId = null;
    if (button.value === "pause") {
      clearInterval(intervalId);
      vid.pause();
      button.value = "play";
    } else if (button.value === "play"){
      intervalId = setInterval(draw, 1000 / 30);
      vid.play();
      button.value = "pause";
    }
  };
})();
</script>
</body>
</html>



前回と同じくボタンで再生/一時停止。

手元の環境(Windows10,64bit)で確認したところ、Chrome/Firefoxではこのページを開いた場合、0フレームが表示された状態でpauseがかかるが、Edge/IE11では最初のフレームは表示されず真っ黒(Edge)か透明(IE11)である。
Edgeはwebkitと挙動が違えばバグらしいのでEdgeのはバグなんだろう(多分)。

追記:
スマホ(Android)のChromeで見てみるとEdgeと同じ挙動になった。
ってことはバグにはならない可能性のほうが高いのかな?
ここらへんはっきりさせて欲しい。

canvasで2Dアニメーション

html5のcanvasアニメーションを調べながら書いたコード。
とりあえず画像の表示と移動、回転を試したかっただけ。
手持ちの素材を適当に使ったらかなりくるった出来上がりになってしまったが気にしてはいけない。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
</head>

<body>
<canvas id="cnvs" width="640" height="480"></canvas>
<input id="play-or-pause" type="button" value="loading">
<script>
/*
  background: 背景のURL
  flyingObjects: 移動物のURLの配列
  num: 移動物の数
  fps: フレームレート
  xmax:1フレーム当たりの移動物のx軸方向の最大移動量(px)
  ymax:1フレーム当たりの移動物のy軸方向の最大移動量(px)
  tmax:1フレーム当たりの移動物の最大回転角(deg) 
*/
(function(background, flyingObjects, num, fps, xmax, ymax, tmax) {

  var canvas = document.getElementById("cnvs");
  var ctx = canvas.getContext("2d");
  var button = document.getElementById("play-or-pause");

  const PI2 = Math.PI * 2;

  var loader = (function() {
    var count = 0;
    return function() {
      if (++count > num) {
        button.value = "play";
        draw();  
      }
    }
  })();

  var bg = new Image();
  bg.src = background;
  bg.onload = loader;

  var fo = new Array(num);
  for (var i = 0; i < num; i++) {
    fo[i] = new Image();
    fo[i].params = {
      speedx: Math.random() * 2 * xmax - xmax,
      speedy: Math.random() * 2 * ymax - ymax,
      dstx: Math.random() * canvas.width,
      dsty: Math.random() * canvas.height,
      theta: ((Math.random() * 2 * tmax - tmax) * Math.PI / 180),
      angle: 0,
    };
    fo[i].src = flyingObjects[Math.random() * flyingObjects.length | 0];
    fo[i].onload = loader;
  }

  function draw() {
    ctx.drawImage(bg, 0, 0, canvas.width, canvas.height);
    for (var i = 0; i < num; i++) {
      var f = fo[i];
      var p = f.params;
      var cx = f.naturalWidth / 2;
      var cy = f.naturalHeight / 2;

      ctx.save();
      ctx.translate(p.dstx + cx, p.dsty + cy);
      ctx.rotate(p.angle);
      ctx.drawImage(f, -cx, -cy);
      ctx.restore();

      p.dstx += p.speedx;
      p.dsty += p.speedy;
      p.angle += p.theta;

      if (p.dstx > canvas.width) {
        p.dstx = -f.naturalWidth;
      }
      if (p.dstx < -f.naturalWidth) {
        p.dstx = canvas.width - 1;
      }
      if (p.dsty > canvas.height) {
        p.dsty = -f.naturalHeight;
      }
      if (p.dsty < -f.naturalHeight) {
        p.dsty = canvas.height - 1;
      }
      if (p.angle < -PI2) {
        p.angle += PI2;
      }
      if (p.angle > PI2) {
        p.angle -= PI2;
      }
    }
  };

  button.onclick = (function() {
    var intervalId = null;
    var interval = 1000 / fps | 0;
    return function() {
      if (button.value === "pause") {
        clearInterval(intervalId);
        button.value = "play";
      } else if (button.value === "play") {
        intervalId = setInterval(draw, interval);
        button.value = "pause";
      }
    };
  })();

})("images/soccer.jpg",
   ["images/saturn.png",
    "images/pig.png",
    "images/heart.png",
    "images/japan.gif"],
    50, 50, 3, 5, 30);
</script>
</body>
</html>




ボタンを押せば再生/一時停止します。

2015年8月7日金曜日

PHPでenum(のようなもの)

PHPでファイル送信機能付きのメールフォームを書いていた時のお話し。

入力フォームなんだから当然validationとかもしなければいけないわけで、いろいろやるクラスを別に作って結果を定数で返すようにした。
こういうときCだったらenumを使うのが多分普通なのですが、PHP5にはenumはないんですな。

enumよりももっとスマートな方法もあるのかもしれないけど、パッとは思いつかない。
それにJavaでもC#でも、switch文は頑なに拒否するPythonですらenumは標準モジュールで用意されてるんですから、やはりenumはいいものなのでしょう。

ググってみたら「enum実装しました!」とかいう人も当然いるわけですが、しかしenum程度でわざわざ外部ライブラリとか大げさな気もする。
で、自分なりの方法を模索するわけです。

とりあえずぱっと思いつくのはこれですな。
class RetCode {

    const VALID_POST = 0;
    const INVALID_POST = 1;
    const SHORTAGE_POST = 2;
    const INVALID_FILE_UPLOADED = 3;
      ・
      ・
      ・ 
}

自分で連番ふって定数定義…はい、こんなもんenumじゃありません。
自動で重複しない数値を振ってくれなければenumの替わりとは絶対認められないに決まってます。

で、PHPのドキュメント5分ほど眺めてこうすることにしました。

class RetCode {

    private static $ret = [
        "VALID_POST",
        "INVALID_POST",
        "SHORTAGE_POST",
        "INVALID_FILE_UPLOADED",
            ・
            ・
            ・
    ];

    public static function get_retcode() {
        return array_flip(self::$ret);
    }
}

keyなしで配列定義してarray_flip()してやれば、重複しない値をもつ配列になりますね。
定義されてないkeyを使おうとすればNotice Errorでるから、typo防止にもなるっちゃなりますし。

Cのenumとそれほど手間も変わらないんで、お手軽につかう分にはいいんじゃないかと思います。

2015年8月6日木曜日

JavaScriptで要素の高さを揃える

随分長いことほっぽってたブログですが、何か書きたくなってきたので再開。

といっても、最近は動画関連のことはほとんどやってないので、そっちのほうの需要(あるのか?)は満たせないと思いますが。

で、最近の筆者は普段はhtmlとかJavaScriptとかPHPとかを書いたり書かなかったりしています。
今回のお題はそちらの方面です。

本題

さて、今回のネタは次のような感じです。


こんな感じの表示を
こうしたい。

「要素 高さ 揃える」とかでググればjQuery使ったやり方がたくさんヒットするわけですが、IE8のサポートも残り半年を切ってる昨今では、別にアニメーションさせるわけでもないのにjQuery使うのってダサいらしいので今回はパス。

jQueryを使わずにこれをやるheightLine.jsなるライブラリもあるようですが、実際に中身を見てみたところどうもよく理解できない。書かれたのが8年前ではブラウザ環境とかもずいぶん違うだろうしなあ。

というわけで、自分で書いてみることにしました。


まず高さを揃えたい各要素(element)の処理前の高さは、element.offsetHeightの値でわかるので、各要素のoffsetHeightの最大値を求める。
var elems = document.getElementsByClassName(className); //とりあえずクラス名で対象要素を取得
var maxHeight = 0;
for (var i = 0; i < elems.length; i++) {
    maxHeight = Math.max(maxHeight, elems[i].offsetHeight);
}
これで揃える高さが求まりました。

で、offsetHeightは要素のpadding(上下)+border(上下)+heightなので、maxHeightからpaddingとborderの値を引いた値を要素のstyle.heightに設定してやればいいわけですが、ここで少し問題。

要素のpaddingやborderを<style>タグや外部CSSファイルで指定している場合は、element.style以下のプロパティは全部空("")なのですね。

かと言って高さを揃えたい各要素のborder+paddingは(上の画像のように)すべて同じとは限らないわけで、ハードコーディングするのも問題ありすぎです。

さてどーしようとちょっと考えたらこうなりました。
for (i = 0; i < elems.length; i++) {
    var elem = elems[i];
    elem.style.height = "0";
    var height = maxHeight - elem.offsetHeight;
    elem.style.height = height + "px";
}
offsetHeight==padding(上下)+border(上下)+height ですから、height==0のときoffsetHeight==padding(上下)+border(上下)になる。

これでstyle.heightに入れる値はmaxHeightとoffsetHeightの差をとればよいことになります。 なんかインチキ臭いけど、簡単だし実用上は問題ない(多分)。

実際に使うときはこんな感じになります。