サーチ…
備考
このトピックでは、さまざまなメディアの種類と、2Dインターフェイスでのキャンバスの使用方法について説明します。
メディアタイプには、一般的なフォーマット固有のカテゴリがあります。
メディアの種類
- アニメーション
- ビデオ
- イメージ
- HD画像
- ベクトル画像
- アニメーション画像
メディアフォーマット
- Jpg / Jpeg
- PNG
- Gif
- SVG
- M-JPEG
- Webm
- Webp
イメージ
ブラウザでサポートされている画像フォーマットは多種多様ですが、ブラウザはそれらをすべてサポートしていません。あなたが特定のイメージフォーマットを持っているなら、 Wikiブラウザとサポートされているイメージフォーマットを使用したいと思っています。
最高のサポートは3つの主要なフォーマット、 "jpeg"、 "png"、および "gif"であり、すべての主要なブラウザがサポートしています。
JPEG
JPEG画像は写真や写真のような画像に最適です。彼らは、チャート、図、テキストに自分自身を役立たない。 JPEG画像は透過性をサポートしていません。
Canvasはcanvas.toDataURL
とcanvas.toBlob
を介してJPEG画像を出力し、品質設定を提供します。 JPEGは透明度をサポートしていないので、透明なピクセルは最終的な出力JPGに対して黒とブレンドされます。結果として得られるイメージは、キャンバスの完全なコピーではありません。
PNG
PNG画像は最高品質の画像で、透過ピクセルのアルファチャンネルも含めることができます。イメージデータは圧縮されますが、JPGイメージのようなアーティファクトは生成されません。
ロスレス圧縮とアルファチャンネルサポートのため、PNGはゲーム、UIコンポーネント画像、チャート、図、テキストに使用されます。写真や写真のような画像に使用する場合、ファイルサイズはJPEGよりはるかに大きくなる可能性があります。 。
ブラウザのサポートは限られていますが、PNG形式でもアニメーションのサポートが提供されています。キャンバスで使用するアニメーションへのアクセスは、JavaScriptのAPIおよびライブラリを使用してのみ行うことができます
キャンバスは、 canvas.toDataURL
とcanvas.toBlob
を介してPNG画像をエンコードするために使用できますが、出力形式は圧縮された32Bit RGBAに制限されています。 PNGは、キャンバスの完全なピクセルのコピーを提供します。
GIF
GIFは短いアニメーションに使用されますが、高品質のグラフ、図、テキストなどの画像を提供するためにも使用できます。 GIFは非常に限られた色サポートを持っており、フレームあたり最大256色です。クリーバー画像処理では、特にアニメーション化されたときにgif画像が驚くほど良い結果を生み出すことができます。これはオンまたはオフに制限されていますが、Gifも透過性を提供します
PNGと同様に、GIFアニメーションはキャンバス上で直接使用することはできません。また、アクセスするにはJavascript APIまたはライブラリが必要です。キャンバスを介してGIFを保存することはできず、APIやライブラリが必要になります。
画像の読み込みと表示
イメージを読み込んでキャンバスに配置するには
var image = new Image(); // see note on creating an image
image.src = "imageURL";
image.onload = function(){
ctx.drawImage(this,0,0);
}
イメージの作成
画像を作成するにはいくつかの方法があります
-
new Image()
-
document.createElement("img")
-
<img src = 'imageUrl' id='myImage'>
HTML本文の一部として、document.getElementById('myImage')
取得しdocument.getElementById('myImage')
イメージはHTMLImageElement
Image.srcプロパティ
イメージsrc
は、任意の有効なイメージURLまたはエンコードされたdataURLを使用できます。イメージの形式とサポートの詳細については、このトピックの「備考」を参照してください。
-
image.src = "http://my.domain.com/images/myImage.jpg"
-
image.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="
*
* dataURLは、黒を含む1×1ピクセルのGIF画像です
読み込みとエラーに関する注意
srcプロパティが設定されると、イメージのロードが開始されます。読み込みはsyncriouseですが、関数またはコードが終了または返されるまで、 onload
イベントは呼び出されません。
ページからイメージを取得した場合(例えばdocument.getElementById("myImage")
)、そのsrc
が設定されている場合はロードされている場合とロードされていない場合があります。 HTMLImageElement.complete
を使用してイメージのステータスを確認することができます。完了するとtrue
になりtrue
。これは画像がロードされたことを意味するものではなく、
- ロードされた
- エラーがありました
- srcプロパティが設定されておらず、空の文字列
""
と等しい
イメージが信頼できないソースからのもので、さまざまな理由でアクセスできない場合は、エラーイベントが生成されます。これが起こると、イメージは壊れた状態になります。それをキャンバスに描画しようとすると、次のエラーがスローされます
Uncaught DOMException: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state.
image.onerror = myImgErrorHandler
イベントを指定すると、エラーを防ぐ適切な処置を取ることができます。
svg画像を描画する
ベクトルSVGイメージを描画するには、操作はラスタイメージと違いはありません。
まず、SVGイメージをHTMLImage要素にロードしてから、 drawImage()
メソッドを使用する必要があります。
var image = new Image();
image.onload = function(){
ctx.drawImage(this, 0,0);
}
image.src = "someFile.SVG";
SVG画像にはラスタ画像に比べていくつかの利点があります。画質を落とさず、キャンバス上に描いたスケールに関係なくです。しかし、ラスターイメージを描くよりも少し遅いかもしれません。
しかし、SVG画像にはラスター画像より多くの制限があります。
セキュリティ目的のために、HTMLImageElement(
<img>
)で参照されるSVGイメージから外部コンテンツをロードすることはできません
SVGImage(<image/>
)要素で参照される外部イメージ、xlink:href
属性(<use xlink:href="anImage.SVG#anElement"/>
)または<use xlink:href="anImage.SVG#anElement"/>
によってリンクされた外部フィルタまたは要素はありません(url()
)属性メソッドなど
また、メイン文書に追加されたスタイルシートは、HTMLImage要素で一度参照されたSVG文書には影響しません。
最後に、SVGイメージ内でスクリプトは実行されません。
回避策: HTMLImage要素を参照する前に、SVG自体の内部にすべての外部要素を追加する必要があります。 (イメージやフォントの場合、外部リソースのdataURIバージョンを追加する必要があります)。ルート要素(
<svg>
)は、幅と高さの属性を絶対値に設定する必要があります。
相対的な長さ(例えば、%
)を使用する場合、ブラウザーは相対的な長さを知ることができません。一部のブラウザ(点滅)は推測を試みますが、ほとんどの場合、イメージを無視して警告なしで何も描画しません。一部のブラウザでは、SVGイメージが描かれたときにキャンバスを汚すことがあります。
具体的には、インターネットエクスプローラ<いずれの場合でもEdge<foreignObject>
、<foreignObject>
がSVGイメージに存在する場合はSafari 9です。
キャンバスでのビデオの読み込みと再生。
キャンバスは、さまざまなソースからビデオを表示するために使用できます。この例では、ビデオをファイルリソースとしてロードして表示し、画面の再生/一時停止のトグルを簡単にクリックする方法を示します。
どのようにHTML5のキャンバスタグを使用してビデオを表示するかは、次のサンプルコードの動作を示しています。
ちょうどイメージ
ビデオはキャンバスに関する限り単なる画像です。あなたはどんなイメージのようにも描くことができます。違いは、ビデオが再生でき、サウンドがあることです。
キャンバスと基本設定を取得する
// It is assumed you know how to add a canvas and correctly size it.
var canvas = document.getElementById("myCanvas"); // get the canvas from the page
var ctx = canvas.getContext("2d");
var videoContainer; // object to hold video and associated info
ビデオの作成と読み込み
var video = document.createElement("video"); // create a video element
video.src = "urlOffVideo.webm";
// the video will now begin to load.
// As some additional info is needed we will place the video in a
// containing object for convenience
video.autoPlay = false; // ensure that the video does not auto play
video.loop = true; // set the video to loop.
videoContainer = { // we will add properties as needed
video : video,
ready : false,
};
画像要素とは異なり、動画はキャンバスに表示するために完全に読み込む必要はありません。ビデオはまた、ビデオのステータスを監視するために使用できる余分なイベントのホストを提供します。
この場合、動画の再生準備が整ったことがわかります。 oncanplay
は、十分なビデオがその一部を再生するために読み込まれていることを意味しますが、最後まで再生するには十分ではないかもしれません。
video.oncanplay = readyToPlayVideo; // set the event to the play function that
// can be found below
代わりに、十分なビデオがロードされたときにoncanplaythrough
し、最後まで再生できるoncanplaythrough
を使用することもできます。
video.oncanplaythrough = readyToPlayVideo; // set the event to the play function that
// can be found below
canPlayイベントの1つだけを使用することはできません。
can can event(イメージ・オンロードに相当)
function readyToPlayVideo(event){ // this is a referance to the video
// the video may not match the canvas size so find a scale to fit
videoContainer.scale = Math.min(
canvas.width / this.videoWidth,
canvas.height / this.videoHeight);
videoContainer.ready = true;
// the video can be played so hand it off to the display function
requestAnimationFrame(undateCanvas);
}
表示する
ビデオはキャンバスで再生されません。新しいフレームごとに描画する必要があります。正確なフレームレートを知ることは難しく、発生するタイミングは60fpsのようにビデオを表示することです。フレームレートが低い場合、wは同じフレームを2回レンダリングします。フレームレートが高い場合、余分なフレームを見ることができないので、無視するだけのものはありません。
ビデオ要素は単なる画像要素で、任意の画像のように描画することができます。拡大縮小、回転、パン、ミラーリング、フェード、クリップ、パーツ表示、グローバルコンポジットモードでの2回目の描画明るい、画面などのFXを追加する。
function updateCanvas(){
ctx.clearRect(0,0,canvas.width,canvas.height); // Though not always needed
// you may get bad pixels from
// previous videos so clear to be
// safe
// only draw if loaded and ready
if(videoContainer !== undefined && videoContainer.ready){
// find the top left of the video on the canvas
var scale = videoContainer.scale;
var vidH = videoContainer.video.videoHeight;
var vidW = videoContainer.video.videoWidth;
var top = canvas.height / 2 - (vidH /2 ) * scale;
var left = canvas.width / 2 - (vidW /2 ) * scale;
// now just draw the video the correct size
ctx.drawImage(videoContainer.video, left, top, vidW * scale, vidH * scale);
if(videoContainer.video.paused){ // if not playing show the paused screen
drawPayIcon();
}
}
// all done for display
// request the next frame in 1/60th of a second
requestAnimationFrame(updateCanvas);
}
基本的な再生一時停止制御
これで、ビデオがロードされ、再生コントロールが必要なだけ表示されました。画面上でクリックトグル再生とします。動画が再生されているときに、ユーザーが動画をクリックすると一時停止します。一時停止すると、クリックが再開されます。動画を暗くして再生アイコン(三角形)を描画する機能を追加します
function drawPayIcon(){
ctx.fillStyle = "black"; // darken display
ctx.globalAlpha = 0.5;
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = "#DDD"; // colour of play icon
ctx.globalAlpha = 0.75; // partly transparent
ctx.beginPath(); // create the path for the icon
var size = (canvas.height / 2) * 0.5; // the size of the icon
ctx.moveTo(canvas.width/2 + size/2, canvas.height / 2); // start at the pointy end
ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 + size);
ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 - size);
ctx.closePath();
ctx.fill();
ctx.globalAlpha = 1; // restore alpha
}
今、再生ポーズイベント
function playPauseClick(){
if(videoContainer !== undefined && videoContainer.ready){
if(videoContainer.video.paused){
videoContainer.video.play();
}else{
videoContainer.video.pause();
}
}
}
// register the event
canvas.addEventListener("click",playPauseClick);
概要
ビデオを再生することは、キャンバスを使用することは非常に簡単で、リアルタイムで効果を追加することも簡単です。しかし、フォーマットにはいくつかの制限があります。どのようにプレイしたり探すことができますか? MDN HTMLMediaElementは、ビデオオブジェクトへの完全な参照を取得する場所です。
イメージがキャンバスに描画されたら、 ctx.getImageData
を使用して、それに含まれるピクセルにアクセスできます。あるいは、 canvas.toDataURL
を使ってcanvas.toDataURL
をスナップしてダウンロードすることもできます。 (ビデオが信頼できるソースからのもので、キャンバスに汚れがない場合のみ)。
ビデオのサウンドが再生されている場合は、それも再生されます。
ハッピービデオ。
キャンバスのキャプチャとwebMのビデオとしての保存
キャンバスフレームからWebMビデオを作成し、キャンバスで再生したり、アップロードしたり、ダウンロードしたりできます。
キャプチャと再生キャンバスの例
name = "CanvasCapture"; // Placed into the Mux and Write Application Name fields of the WebM header
quality = 0.7; // good quality 1 Best < 0.7 ok to poor
fps = 30; // I have tried all sorts of frame rates and all seem to work
// Do some test to workout what your machine can handle as there
// is a lot of variation between machines.
var video = new Groover.Video(fps,quality,name)
function capture(){
if(video.timecode < 5000){ // 5 seconds
setTimeout(capture,video.frameDelay);
}else{
var videoElement = document.createElement("video");
videoElement.src = URL.createObjectURL(video.toBlob());
document.body.appendChild(videoElement);
video = undefined; // DeReference as it is memory hungry.
return;
}
// first frame sets the video size
video.addFrame(canvas); // Add current canvas frame
}
capture(); // start capture
拒絶されるだけの大きな努力を払うのではなく、これは受け入れられるかどうかを見るための素早い挿入物です。受け入れられる場合、詳細を記入します。また、より良いHDキャプチャレートのための追加のキャプチャオプションも含まれています(このバージョンからは削除され、良いマシンでHD 1080を50fpsでキャプチャできます)。
これはWammyからインスピレーションを受けていますが、方法論を進めていくうちに、エンコード時に必要なメモリを大幅に削減することができます。 30秒以上のデータを処理し、アルゴリズムを処理できます。
ノートフレームはWebPイメージにエンコードされます。 ChromeのみがwebPキャンバスのエンコーディングをサポートしています。他のブラウザ(FirefoxとEdge)では、 Libwebp Javascriptのようなサードパーティ製のWebPエンコーダを使用する必要があります。Javascript経由でWebPイメージをエンコードするのは遅いです。 (受け入れられた場合、生のWeb画像のサポートが追加されます)。
Whammyからインスピレーションを受けたwebMエンコーダ:リアルタイムJavascript WebM
var Groover = (function(){
// ensure webp is supported
function canEncode(){
var canvas = document.createElement("canvas");
canvas.width = 8;
canvas.height = 8;
return canvas.toDataURL("image/webp",0.1).indexOf("image/webp") > -1;
}
if(!canEncode()){
return undefined;
}
var webmData = null;
var clusterTimecode = 0;
var clusterCounter = 0;
var CLUSTER_MAX_DURATION = 30000;
var frameNumber = 0;
var width;
var height;
var frameDelay;
var quality;
var name;
const videoMimeType = "video/webm"; // the only one.
const frameMimeType = 'image/webp'; // can be no other
const S = String.fromCharCode;
const dataTypes = {
object : function(data){ return toBlob(data);},
number : function(data){ return stream.num(data);},
string : function(data){ return stream.str(data);},
array : function(data){ return data;},
double2Str : function(num){
var c = new Uint8Array((new Float64Array([num])).buffer);
return S(c[7]) + S(c[6]) + S(c[5]) + S(c[4]) + S(c[3]) + S(c[2]) + S(c[1]) + S(c[0]);
}
};
const stream = {
num : function(num){ // writes int
var parts = [];
while(num > 0){ parts.push(num & 0xff); num = num >> 8; }
return new Uint8Array(parts.reverse());
},
str : function(str){ // writes string
var i, len, arr;
len = str.length;
arr = new Uint8Array(len);
for(i = 0; i < len; i++){arr[i] = str.charCodeAt(i);}
return arr;
},
compInt : function(num){ // could not find full details so bit of a guess
if(num < 128){ // number is prefixed with a bit (1000 is on byte 0100 two, 0010 three and so on)
num += 0x80;
return new Uint8Array([num]);
}else
if(num < 0x4000){
num += 0x4000;
return new Uint8Array([num>>8, num])
}else
if(num < 0x200000){
num += 0x200000;
return new Uint8Array([num>>16, num>>8, num])
}else
if(num < 0x10000000){
num += 0x10000000;
return new Uint8Array([num>>24, num>>16, num>>8, num])
}
}
}
const ids = { // header names and values
videoData : 0x1a45dfa3,
Version : 0x4286,
ReadVersion : 0x42f7,
MaxIDLength : 0x42f2,
MaxSizeLength : 0x42f3,
DocType : 0x4282,
DocTypeVersion : 0x4287,
DocTypeReadVersion : 0x4285,
Segment : 0x18538067,
Info : 0x1549a966,
TimecodeScale : 0x2ad7b1,
MuxingApp : 0x4d80,
WritingApp : 0x5741,
Duration : 0x4489,
Tracks : 0x1654ae6b,
TrackEntry : 0xae,
TrackNumber : 0xd7,
TrackUID : 0x63c5,
FlagLacing : 0x9c,
Language : 0x22b59c,
CodecID : 0x86,
CodecName : 0x258688,
TrackType : 0x83,
Video : 0xe0,
PixelWidth : 0xb0,
PixelHeight : 0xba,
Cluster : 0x1f43b675,
Timecode : 0xe7,
Frame : 0xa3,
Keyframe : 0x9d012a,
FrameBlock : 0x81,
};
const keyframeD64Header = '\x9d\x01\x2a'; //VP8 keyframe header 0x9d012a
const videoDataPos = 1; // data pos of frame data header
const defaultDelay = dataTypes.double2Str(1000/25);
const header = [ // structure of webM header/chunks what ever they are called.
ids.videoData,[
ids.Version, 1,
ids.ReadVersion, 1,
ids.MaxIDLength, 4,
ids.MaxSizeLength, 8,
ids.DocType, 'webm',
ids.DocTypeVersion, 2,
ids.DocTypeReadVersion, 2
],
ids.Segment, [
ids.Info, [
ids.TimecodeScale, 1000000,
ids.MuxingApp, 'Groover',
ids.WritingApp, 'Groover',
ids.Duration, 0
],
ids.Tracks,[
ids.TrackEntry,[
ids.TrackNumber, 1,
ids.TrackUID, 1,
ids.FlagLacing, 0, // always o
ids.Language, 'und', // undefined I think this means
ids.CodecID, 'V_VP8', // These I think must not change
ids.CodecName, 'VP8', // These I think must not change
ids.TrackType, 1,
ids.Video, [
ids.PixelWidth, 0,
ids.PixelHeight, 0
]
]
]
]
];
function getHeader(){
header[3][2][3] = name;
header[3][2][5] = name;
header[3][2][7] = dataTypes.double2Str(frameDelay);
header[3][3][1][15][1] = width;
header[3][3][1][15][3] = height;
function create(dat){
var i,kv,data;
data = [];
for(i = 0; i < dat.length; i += 2){
kv = {i : dat[i]};
if(Array.isArray(dat[i + 1])){
kv.d = create(dat[i + 1]);
}else{
kv.d = dat[i + 1];
}
data.push(kv);
}
return data;
}
return create(header);
}
function addCluster(){
webmData[videoDataPos].d.push({ i: ids.Cluster,d: [{ i: ids.Timecode, d: Math.round(clusterTimecode)}]}); // Fixed bug with Round
clusterCounter = 0;
}
function addFrame(frame){
var VP8, kfS,riff;
riff = getWebPChunks(atob(frame.toDataURL(frameMimeType, quality).slice(23)));
VP8 = riff.RIFF[0].WEBP[0];
kfS = VP8.indexOf(keyframeD64Header) + 3;
frame = {
width: ((VP8.charCodeAt(kfS + 1) << 8) | VP8.charCodeAt(kfS)) & 0x3FFF,
height: ((VP8.charCodeAt(kfS + 3) << 8) | VP8.charCodeAt(kfS + 2)) & 0x3FFF,
data: VP8,
riff: riff
};
if(clusterCounter > CLUSTER_MAX_DURATION){
addCluster();
}
webmData[videoDataPos].d[webmData[videoDataPos].d.length-1].d.push({
i: ids.Frame,
d: S(ids.FrameBlock) + S( Math.round(clusterCounter) >> 8) + S( Math.round(clusterCounter) & 0xff) + S(128) + frame.data.slice(4),
});
clusterCounter += frameDelay;
clusterTimecode += frameDelay;
webmData[videoDataPos].d[0].d[3].d = dataTypes.double2Str(clusterTimecode);
}
function startEncoding(){
frameNumber = clusterCounter = clusterTimecode = 0;
webmData = getHeader();
addCluster();
}
function toBlob(vidData){
var data,i,vData, len;
vData = [];
for(i = 0; i < vidData.length; i++){
data = dataTypes[typeof vidData[i].d](vidData[i].d);
len = data.size || data.byteLength || data.length;
vData.push(stream.num(vidData[i].i));
vData.push(stream.compInt(len));
vData.push(data)
}
return new Blob(vData, {type: videoMimeType});
}
function getWebPChunks(str){
var offset, chunks, id, len, data;
offset = 0;
chunks = {};
while (offset < str.length) {
id = str.substr(offset, 4);
// value will have top bit on (bit 32) so not simply a bitwise operation
// Warning little endian (Will not work on big endian systems)
len = new Uint32Array(
new Uint8Array([
str.charCodeAt(offset + 7),
str.charCodeAt(offset + 6),
str.charCodeAt(offset + 5),
str.charCodeAt(offset + 4)
]).buffer)[0];
id = str.substr(offset, 4);
chunks[id] = chunks[id] === undefined ? [] : chunks[id];
if (id === 'RIFF' || id === 'LIST') {
chunks[id].push(getWebPChunks(str.substr(offset + 8, len)));
offset += 8 + len;
} else if (id === 'WEBP') {
chunks[id].push(str.substr(offset + 8));
break;
} else {
chunks[id].push(str.substr(offset + 4));
break;
}
}
return chunks;
}
function Encoder(fps, _quality = 0.8, _name = "Groover"){
this.fps = fps;
this.quality = quality = _quality;
this.frameDelay = frameDelay = 1000 / fps;
this.frame = 0;
this.width = width = null;
this.timecode = 0;
this.name = name = _name;
}
Encoder.prototype = {
addFrame : function(frame){
if('canvas' in frame){
frame = frame.canvas;
}
if(width === null){
this.width = width = frame.width,
this.height = height = frame.height
startEncoding();
}else
if(width !== frame.width || height !== frame.height){
throw RangeError("Frame size error. Frames must be the same size.");
}
addFrame(frame);
this.frame += 1;
this.timecode = clusterTimecode;
},
toBlob : function(){
return toBlob(webmData);
}
}
return {
Video: Encoder,
}
})()