JPG、透明度、キャンバス、アニメーション

こんにちは友人!



HTML5アウトラインのアルファチャンネルを使用したアニメーションスプライトに関する小さなレッスンを提供します。



前文。



まず、何かを描きましょう

画像



なぜ丸くて黄色なのですか? Hitchhiker's Guide to the GalaxyのDouglas AdamsにはこのようなSlartibartfastがあります-地球の建設中に海岸線の賞を受賞した非常に感動的な叔父です。 したがって、念のため、黄色の星をアニメーション化します。



次に、256x256ピクセルのフレームサイズで、256フレームのシーケンスで星をアニメーション化します。

これらの256、これらの256、およびこれらの256は、単に例として挙げたものです。 ただし、アニメーションは短くも小さくもでき、長くも大きくもできます。それはすべて目標に依存します。 今日、256x256ピクセルはスプライトにとって非常に普通のサイズだと思います。大きすぎず小さすぎません。 もちろん、以前は草がより緑で、スプライトは16x16や32x32よりも控えめでしたが、成長を実験することになっています。

256フレームもそれほど多くはありませんが、それほど少なくありません。フレームレートに応じて、10〜20秒のシーケンスを取得できます。



これまでのところ、フレームの側面のサイズ、持続時間、深さを掛け合わせるまで、驚くことはありません。



256 x 256 x 256 x 4 = 64 MB



しかし! 圧縮しない64メガバイトは、スプライトにとっては少し脂っこいです。 256フレームすべてを16x16フレームマトリックスに誤って計算し、この魚をアルファチャネル付きのPNGに保存すると(すべてAdobe After Effectsで発生します)、18.6 MBになりますが、これは64ではありませんが、大量です。 このPNGを見ることに興味がある人のために、ここにリンクがあります。



絞って!



After EffectはPNG圧縮について不注意ですか? 結果のグリッドを適切なサイズにするために、OptiPNGおよびPNGOutユーティリティを使用して、レンダリングされたPNGを最適化しようとしました。 両方のユーティリティは、極端なものまでさまざまなオプションで起動されましたが、結果として、最良の場合、圧縮は1.5パーセントを魅了するように最適化されました。これは明らかに期待を満たしていません。 そのため、このアニメーションは品質を損なうことなくいくつかのファイル(同じPNG)に変換されますが、RGBとアルファは別々になり、Image Optimizerを使用していくつかのJPEGに変換されました。 JPGコンプレッサーの作業を容易にするため(正しい言葉ですか?)、RGBレイヤーを含むシーケンスの背景は、レンダリングの前にコスミックブラックではなくコスミックイエローで塗りつぶされます。



圧縮率に応じて、JPEGの異なる重みのペアを取得します。

圧縮 10% 15% 25% 50% 75% 85% 90%
RGB 139KB 198KB 250KB 425KB 691KB 992KB 1337KB
アルファ 363KB 459KB 524KB 689KB 856KB 1067KB 1273KB


これらのサイズは、あなたが望むものに近いです。 透明性を備えたファイルは、すべての場合において色を備えたファイルよりも重く、おそらく全体の点が対照的であることは、私にとってまったく驚きでした。 視覚的には、50%より強く圧縮されるものはすべて、レンガに似ており、コソムには似ていません。 友人、もしあなたの中によく圧縮されたJPEGをはっきりと区別できる人がいれば-あなたの経験を共有してください、私は喜んで記事を更新します。 それまでの間、最大75%圧縮された1組のファイルを使用してさらに実験を行います。合計1547KBは、PNGの18.6MBや非圧縮形式の64MBよりもはるかに優れています。



進む!



これで、これらの個別のJPEGがサーバーから提供され、クライアントではそれらを受け入れて、透明性のある1つの一般的な画像に接着します。



マークアップは最も空のマークアップになります。

マークアップ
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Canvas Example: Using canvas</title> <link rel="stylesheet" href="css/main.css" type="text/css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script> <script src="js/main.js" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function(){ loadRGBA("jpeg_16x16/render_16_16_rgb_75.jpg", "jpeg_16x16/render_16_16_aplha_75.jpg"); }); </script> </head> <body> <div id="star1" class="base64"></div> <div id="star2" class="base64"></div> </body> </html>
      
      







スタイルと同様に:

スタイル
 * { margin: 0; padding: 0; } html { background: #888 url(../backs/back64dark.png); font-size: 14px; font-family: 'Helvetica', Helvetica, sans-serif; } h1 { font-size: 1.5em; text-align: center; margin: 1em 0; color: #ddd; } #base64{ background: transparent; color: #bbb; width: 256px; height: 265px; margin: 0 auto; }
      
      







スクリプトブロックでは、ドキュメントの形成の最後に、イメージの読み込み関数を呼び出します。

非常に簡単です(js / main.jsファイルにあります):

関数loadRGBA()
 function loadRGBA(url_rgb, url_alpha){ var img_rgb = new Image(); var img_alpha = new Image(); var img_count = 0; var img_rgba = ''; img_rgb.src = url_rgb; img_alpha.src = url_alpha; img_rgb.onload = function(){ ++img_count; if(2 == img_count){ img_rgba = compileRGBA(img_rgb, img_alpha); } } img_alpha.onload = function(){ ++img_count; if(2 == img_count){ img_rgba = compileRGBA(img_rgb, img_alpha); } } }
      
      







簡単に言うと、2つのImageオブジェクトを宣言し、それらがロードされるのを待ちます(つまり、img_countの数が2の場合)。 私のスクリプトでは、この瞬間はどこかで行われました-ファイルのいずれかをロードした後、もう一方がすでにロードされているかどうかがチェックされ、ロードされている場合は、2つのイメージを1つに組み立てる機能が呼び出されます。 私はJavaScriptがあまり得意ではありません。何かがおかしいと感じていますが、私の気持ちにもかかわらず機能しています。 (この例では)常に2つのイメージが常に予想されるため、ロード関数はそのままにして、アセンブリ関数を記述します。



集めるために!



compileRGBA()
 function compileRGBA(raw_rgb, raw_alpha){ if (!raw_rgb.width || !raw_rgb.height || !raw_alpha.width || !raw_alpha.height){ return; } if (raw_rgb.width !== raw_alpha.width || raw_rgb.height !== raw_alpha.height){ alert(' RGB    ') return; } var canvas_rgb = document.createElement("canvas"); var canvas_alpha = document.createElement("canvas"); var canvas_frame = document.createElement("canvas"); if (!canvas_rgb || !canvas_rgb.getContext('2d') || !canvas_alpha || !canvas_alpha.getContext('2d') || !canvas_frame || !canvas_frame.getContext('2d')){ alert('----, ... ---, , '); return; } canvas_rgb.width = raw_rgb.width; canvas_rgb.height = raw_rgb.height; canvas_alpha.width = raw_alpha.width; canvas_alpha.height = raw_alpha.height; canvas_frame.width = 256; canvas_frame.height = 256; var context_rgb = canvas_rgb.getContext('2d'); var context_alpha = canvas_alpha.getContext('2d'); var context_frame = canvas_frame.getContext('2d'); context_rgb.drawImage(raw_rgb, 0, 0); context_alpha.drawImage(raw_alpha, 0, 0); var pix_rgb = context_rgb.getImageData(0, 0, raw_rgb.width, raw_rgb.height); var pix_alpha = context_alpha.getImageData(0, 0, raw_alpha.width, raw_alpha.height); for (var i = 0, n = pix_rgb.width * pix_rgb.height * 4; i < n; i += 4){ pix_rgb.data[i+3] = pix_alpha.data[i]; } context_rgb.putImageData(pix_rgb, 0, 0); var img_arr = []; var frames = []; for(var i=0; i<=15; i++){ for(var j=0; j<=15; j++){ frames[j*16 + i] = context_rgb.getImageData(i*256, j*256, 256, 256); } } var frame = 0; $("#base64").append(canvas_frame); var intFPS = setInterval(function(){ ++frame; if (frame > 255){ frame = 0; } context_frame.putImageData(frames[frame], 0, 0) }, 1000 / 16); }
      
      







ここでも、複雑なことは何もありません。最初の条件では次元の存在がチェックされ、2番目の条件ではそれらが対応します。 次に、色、透明度、アニメーションの3つのキャンバスを宣言します。 予想どおり、ブラウザーでCanvasメカニズムのサポートが利用可能かどうかを確認します(Mozilla FirefoxとGoogle Chromeでテストし、すべてが正常であり、IE8にかんしゃくがあり、そのままにしておきました。dasm32が示唆するように: sashagilは 「IE 10(これまではWin8のみ)」で通常表示されます」が、 Krovososの場合、 「SafariはiPadで動作せず、背景も表示されませんでした」 )、それらのコンテキストを生成します。 そして、魔法が始まります:

  var pix_rgb = context_rgb.getImageData(0, 0, raw_rgb.width, raw_rgb.height); var pix_alpha = context_alpha.getImageData(0, 0, raw_alpha.width, raw_alpha.height);
      
      





これらの2行を使用して、カラーファイルと透明度ファイルのコンテキストからRGBAレイヤーを描画します。これらはそれぞれ、64 MBの配列に展開されます。ただし、カラーファイルの場合、透明度コンポーネントはなく、グレーモードの透明度ファイルの場合、ビット層ですが、それはseljavaです-キャンバスは常に4バイトの深さです。 3色のそれぞれに1バイト、透明度に1バイト。 キャンバスに配置されると、モノクロ透明ファイルは32ビットずつ「詳細に展開」され、R = G = Bおよび透明= 255になります。



これらの配列のすべてのピクセルをループし、

 pix_rgb.data[i+3] = pix_alpha.data[i]
      
      





赤バイトの値を透明キャンバスから透明レイヤーにコピーします(データ[i]-赤、データ[i + 1]-緑、データ[i + 2]-青、データ[i + 3]-アルファ)



この時点から、それぞれが256x256ピクセルのサイズの256個の独立したフレームで構成される巨大な(4096x4096)フットクロスができました。 フレームは、上から下、左から右に16x16グリッドで配置されます。 このグリッドをキャンバスからドキュメントにBase64インスタンスとしてエクスポートし、適切なサイズのDIVのBackground-Imageプロパティにフィードして、タイマーの開始点をフレームサイズ分ずらすのが最も論理的です。 実験では、これはプロセッサ負荷の点でひどいオプションであり、私のDualCore 2.2GHzは5 FPSを超えることはできませんでした。 したがって、これらの16x16の太陽をフレームに切り取り、配列に入れます。

  var frames = []; for(var i=0; i<=15; i++){ for(var j=0; j<=15; j++){ frames[j*16 + i] = context_rgb.getImageData(i*256, j*256, 256, 256); } }
      
      





次に、タイマーを毎秒16回開始し(可能性が高く、遅くなる可能性があります)、 アニメーションが表示されるか、 ダウンロードしてローカルに表示されます。



注意! Chromeは、さの理由でファイルをローカルに「アップロード」しないため、FFは残ります。

UPD SHVV"... Chromeがローカルファイルを開くことができるように、 -disable -web-securityキーで起動する必要があります"



オンラインで視聴する場合、ダウンロード(1.5MB)は見過ごされていると言わなければなりませんが、接着は約5秒間フリーズしますが、その後フリーズし、アニメーションは動作します。 さらに、プロセッサには1%の負荷がかかるため、「交差する様子を見る」というテーマで、このような太陽のペアでさらに実験を行う理由が得られます。



スペース2.0



このためにCSSを変更

新しいスタイル
 .base64{ background: transparent; position: absolute; width: 256px; height: 265px; margin: 0; }
      
      







ブロックが飛ぶことができるように絶対配置を行います。



また、JavaScript。 まず、2つの星、それぞれ2つのDIVを作成し、2つの接着関数を呼び出します-

変更loadRGBA()
  img_rgb.onload = function(){ ++img_count; if(2 == img_count){ compileRGBA(img_rgb, img_alpha, "star1"); compileGGAA(img_rgb, img_alpha, "star2"); } } img_alpha.onload = function(){ ++img_count; if(2 == img_count){ compileRGBA(img_rgb, img_alpha, "star1"); compileGGAA(img_rgb, img_alpha, "star2"); } }
      
      







1つは以前と同じ(compileRGBA)で、2つ目は変更された(compileGGAA)それは、色の透明度チャネルで少し自由な方法でのみ1つ目と異なります:

  pix_rgb.data[i] = pix_rgb.data[i+1]; pix_rgb.data[i+2] = pix_rgb.data[i+3] = pix_alpha.data[i];
      
      





このループでわかるように、赤チャンネルの値は緑の値に置き換えられ、青と透明度は2番目のファイルの透明度からコピーされます。 星が青に変わります。 また、アニメーションタイマーでは、開始フレームの値が100シフトされます。 これは、シンクロナイズドスイミングを防ぐためです。



次に、これらの星を180度の位相差で楕円に沿って移動するためのタイマーを設定します。

タイマー
  var intRotate = setInterval(function(){ phase += .01; if (phase >= 6.28319) { phase = .0; } $("#star1.base64").css('top', (doc_h + 50*Math.sin(phase)) + 'px' ); $("#star2.base64").css('top', (doc_h + 50*Math.sin(phase + 3.14159)) + 'px' ); $("#star1.base64").css('left', (doc_w + 100*Math.cos(phase)) + 'px' ); $("#star2.base64").css('left', (doc_w + 100*Math.cos(phase + 3.14159)) + 'px' ); $("#star1.base64").css('z-index', (phase < 3.14159) ? '1001':'1000' ); $("#star2.base64").css('z-index', (phase < 3.14159) ? '1000':'1001' ); }, 1000 / 24);
      
      







第三に、中間構造を削除します。

ゴミを取り除く
  delete pix_rgb; delete pix_alpha; delete context_rgb; delete canvas_rgb; delete context_alpha; delete canvas_alpha;
      
      







これらの削除に意味があるかどうかを判断することは困難です。少なくともタスクマネージャーは、削除と削除の違いに気付かなかったのです。



このようにして、2つの交互に日食する星が得られます。



-みんなに会えますか?

「はい、もちろん。」 ダウンロードしますか?



まとめ



これで実験/レッスンは終了です。 感覚は2つあります。一方で、基本的にすべてが機能します。 一方、接着時のこの野生のブレーキは、勝利の喜びを台無しにします。 そして、これをどうするかはまだ明確ではありません。 何か提案があれば、喜んで聞いて調整します。



友達、ありがとう!



PS私はあなたのために微笑んで無慈悲になりました=)



All Articles