Recherche…
Remarques
Cette rubrique couvre les différents types de support et leur utilisation avec le canevas dans une interface 2D.
Les types de média ont des catégories génériques et spécifiques au format
Types de médias
- Des animations
- Vidéos
- Images
- Images HD
- Image vectorielle
- Images animées
Formats de média
- Jpg / Jpeg
- Png
- Gif
- SVG
- M-JPEG
- Webm
- Webp
Images
Il existe une grande variété de formats d’images supportés par les navigateurs, mais aucun navigateur ne les supporte tous. Si vous souhaitez utiliser des navigateurs Wiki et des formats d’images pris en charge avec des formats d’ images particuliers, vous avez une bonne vue d’ensemble.
Le meilleur support est pour les 3 principaux formats, "jpeg", "png" et "gif" avec tous les principaux navigateurs fournissant un support.
JPEG
Les images JPEG conviennent mieux aux photos et aux images de type photo. Ils ne se prêtent pas bien aux graphiques, aux diagrammes et au texte. Les images JPEG ne prennent pas en charge la transparence.
Canvas peut générer des images JPEG via canvas.toDataURL
et canvas.toBlob
et fournit un paramètre de qualité. Le format JPEG ne prenant pas en charge la transparence, tous les pixels transparents seront mélangés au noir pour la sortie finale JPG. L'image résultante ne sera pas une copie parfaite de la toile.
PNG
PNG Image est la plus haute qualité d'image et peut également inclure un canal alpha pour les pixels transparents. Les données d'image sont compressées mais ne produisent pas d'artefacts tels que des images JPG.
Grâce à la compression sans perte et à la prise en charge du canal alpha, les fichiers PNG sont utilisés pour les jeux, les images de composants d'interface utilisateur, les graphiques, les diagrammes et le texte. Lorsque vous les utilisez pour des photos et des photos comme des images, la taille de leur fichier peut être beaucoup plus grande que celle du format JPEG. .
Le format PNG prend également en charge l'animation, même si la prise en charge du navigateur est limitée, et l'accès à l'animation pour une utilisation sur le canevas ne peut être effectué que via des API et des bibliothèques Javascript.
Le canevas peut être utilisé pour encoder des images PNG via canvas.toDataURL
et canvas.toBlob
même si le format de sortie est limité au format RGBA 32 bits compressé. Le PNG fournira une copie parfaite de la toile.
GIF
Les GIF sont utilisés pour de courtes animations, mais peuvent également être utilisés pour fournir des graphiques, des diagrammes et des textes de haute qualité, tels que des images. Les GIF ont un support couleur très limité avec un maximum de 256 couleurs par image. Avec le traitement des images cleaver, les images gif peuvent produire des résultats étonnamment bons, surtout lorsqu'elles sont animées. Les gifs offrent également de la transparence, bien que cela soit limité à l'activation ou à la désactivation
Comme avec PNG, les animations GIF ne sont pas directement accessibles pour une utilisation sur le canevas et vous aurez besoin d'une API ou d'une bibliothèque Javascript pour y accéder. GIF ne peut pas être enregistré via le canevas et nécessitera une API ou une bibliothèque pour le faire.
Chargement et affichage d'une image
Charger une image et la placer sur la toile
var image = new Image(); // see note on creating an image
image.src = "imageURL";
image.onload = function(){
ctx.drawImage(this,0,0);
}
Créer une image
Il y a plusieurs façons de créer une image
-
new Image()
-
document.createElement("img")
-
<img src = 'imageUrl' id='myImage'>
Dans le cadre du corps HTML et récupéré avecdocument.getElementById('myImage')
L'image est un HTMLImageElement
Propriété Image.src
L'image src
peut être une URL d'image valide ou une URL de données encodée. Consultez les remarques de cette rubrique pour plus d'informations sur les formats d'image et la prise en charge.
-
image.src = "http://my.domain.com/images/myImage.jpg"
-
image.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="
*
* Le dataURL est une image gif de 1 par 1 pixel contenant du noir
Remarques sur le chargement et les erreurs
L'image commence à se charger lorsque sa propriété src est définie. Le chargement est syncriouse mais l'événement onload
ne sera pas appelé tant que la fonction ou le code n'est pas sorti / retourné.
Si vous obtenez une image à partir de la page (par exemple document.getElementById("myImage")
) et que son src
est défini, il peut ou non être chargé. Vous pouvez vérifier le statut de l'image avec HTMLImageElement.complete
qui sera true
si complet. Cela ne signifie pas que l'image a été chargée, cela signifie qu'elle a soit
- chargé
- Il y avait une erreur
- La propriété src n'a pas été définie et est égale à la chaîne vide
""
Si l'image provient d'une source non fiable et peut ne pas être accessible pour diverses raisons, cela générera un événement d'erreur. Lorsque cela se produit, l'image sera dans un état cassé. Si vous essayez ensuite de le dessiner sur le canevas, l'erreur suivante apparaîtra.
Uncaught DOMException: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state.
En fournissant l' image.onerror = myImgErrorHandler
, vous pouvez prendre les mesures appropriées pour éviter les erreurs.
Dessiner une image svg
Pour dessiner une image SVG vectorielle, l'opération n'est pas différente d'une image raster:
Vous devez d'abord charger votre image SVG dans un élément HTMLImage, puis utiliser la méthode drawImage()
.
var image = new Image();
image.onload = function(){
ctx.drawImage(this, 0,0);
}
image.src = "someFile.SVG";
Les images SVG présentent certains avantages par rapport aux images matricielles, car vous ne perdrez pas de qualité, quelle que soit l'échelle que vous allez dessiner sur votre toile. Mais attention, cela peut aussi être un peu plus lent que dessiner une image raster.
Cependant, les images SVG comportent plus de restrictions que les images matricielles.
Pour des raisons de sécurité, aucun contenu externe ne peut être chargé à partir d'une image SVG référencée dans un objet HTMLImageElement (
<img>
)
Aucune feuille de style externe, aucune image externe référencée dans les éléments SVGImage (<image/>
), aucun filtre ou élément externe lié par l'xlink:href
(<use xlink:href="anImage.SVG#anElement"/>
) ou le funcIRI (url()
) méthode d'attribut etc.
De même, les feuilles de style ajoutées au document principal n'auront aucun effet sur le document SVG, une fois référencé dans un élément HTMLImage.
Enfin, aucun script ne sera exécuté dans l’image SVG.
Solution de contournement: vous devez ajouter tous les éléments externes à l'intérieur du fichier SVG avant de référencer l'élément HTMLImage. (pour les images ou les polices, vous devez ajouter une version de dataURI de vos ressources externes).L'élément root (
<svg>
) doit avoir ses attributs width et height définis sur une valeur absolue.
Si vous utilisiez une longueur relative (par exemple%
), le navigateur ne pourra pas savoir à quoi il est relatif. Certains navigateurs (Blink) essaieront de deviner, mais la plupart ignoreront simplement votre image et ne dessineront rien, sans avertissement.Certains navigateurs vont corrompre le canevas lorsqu'une image SVG y est dessinée.
Plus précisément, Internet-Explorer <Edge dans tous les cas et Safari 9 lorsqu'un<foreignObject>
est présent dans l'image SVG.
Chargement de base et lecture d'une vidéo sur la toile.
Le canevas peut être utilisé pour afficher des vidéos provenant de diverses sources. Cet exemple montre comment charger une vidéo en tant que ressource de fichier, l'afficher et ajouter un simple clic sur la lecture d'écran / pause.
Cette question auto-répondue stackoverflow Comment afficher une vidéo à l'aide de la balise canvas HTML5 montre l'exemple de code suivant en action.
Juste une image
Une vidéo n'est qu'une image en ce qui concerne la toile. Vous pouvez le dessiner comme n'importe quelle image. La différence étant la vidéo peut jouer et a du son.
Obtenir la toile et la configuration de base
// 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
Créer et charger la vidéo
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,
};
Contrairement aux images, les vidéos ne doivent pas être entièrement chargées pour être affichées sur le canevas. Les vidéos fournissent également une foule d'événements supplémentaires pouvant être utilisés pour surveiller l'état de la vidéo.
Dans ce cas, nous souhaitons savoir quand la vidéo est prête à jouer. oncanplay
signifie que suffisamment de vidéo a été chargée pour en lire une partie, mais il n'y en a peut-être pas assez pour jouer jusqu'à la fin.
video.oncanplay = readyToPlayVideo; // set the event to the play function that
// can be found below
Sinon, vous pouvez utiliser une oncanplaythrough
qui se déclenche lorsque la vidéo est suffisamment chargée pour pouvoir être jouée jusqu'à la fin.
video.oncanplaythrough = readyToPlayVideo; // set the event to the play function that
// can be found below
N'utilisez qu'un des événements canPlay pas les deux.
L'événement peut jouer (équivalent à une image en charge)
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);
}
Afficher
La vidéo ne se jouera pas sur la toile. Vous devez le dessiner pour chaque nouvelle image. Comme il est difficile de connaître la fréquence d'images exacte et quand elle se produit, la meilleure approche consiste à afficher la vidéo comme si elle fonctionnait à 60 images par seconde. Si le taux de trame est inférieur, alors il suffit de rendre le même cadre deux fois. Si le taux de trame est plus élevé, il n'y a rien à voir pour voir les trames supplémentaires, alors nous les ignorons.
L'élément vidéo n'est qu'un élément d'image et peut être dessiné comme n'importe quelle image, vous pouvez le mettre à l'échelle, le faire pivoter, le faire pivoter, le découper et afficher uniquement les parties, le dessiner deux fois avec un mode composite global pour ajouter des effets comme l'éclaircir, l'écran, etc.
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);
}
Contrôle de la pause de lecture de base
Maintenant, nous avons la vidéo chargée et affichée, tout ce dont nous avons besoin est le contrôle de lecture. Nous allons le faire comme un clic pour jouer à l'écran. Lorsque la vidéo est en cours de lecture et que l'utilisateur clique, la vidéo est suspendue. En pause, le clic reprend. Nous allons ajouter une fonction pour assombrir la vidéo et dessiner une icône de lecture (triangle)
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
}
Maintenant l'événement de pause de lecture
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);
Résumé
Jouer une vidéo est très facile en utilisant le canevas, l'ajout d'effet en temps réel est également facile. Il existe toutefois des limitations sur les formats, la façon dont vous pouvez jouer et chercher. MDN HTMLMediaElement est l'endroit où obtenir la référence complète à l'objet vidéo.
Une fois l'image dessinée sur le canevas, vous pouvez utiliser ctx.getImageData
pour accéder aux pixels qu'elle contient. Ou vous pouvez utiliser canvas.toDataURL
pour canvas.toDataURL
un alambic et le télécharger. (Uniquement si la vidéo provient d'une source fiable et n'altère pas le canevas).
Notez que si la vidéo a du son, la lecture jouera également le son.
Joyeuses vidéos.
Capture de toile et enregistrer en tant que vidéo WebM
Création d'une vidéo WebM à partir d'images de canevas et de lecture dans un canevas, de téléchargement ou de téléchargement.
Exemple de capture et de lecture de toile
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
Plutôt que de mettre un effort énorme pour être rejeté, il s’agit d’une insertion rapide pour voir si elle est acceptable. Donnera des détails complets si accepté. Également inclure des options de capture supplémentaires pour de meilleurs taux de capture HD (supprimés de cette version, peut capturer HD 1080 à 50 images par seconde sur de bonnes machines.)
Ceci a été inspiré par Wammy mais est une réécriture complète avec encodage au fur et à mesure de votre méthodologie, réduisant considérablement la mémoire nécessaire lors de la capture. Peut capturer plus de 30 secondes de données de meilleure qualité, en manipulant des algorithmes.
Les cadres de notes sont encodés en images WebP. Seul Chrome prend en charge l'encodage webp. Pour les autres navigateurs (Firefox et Edge), vous devrez utiliser un encodeur WebP tiers tel que Libwebp Javascript Encoding WebP via Javascript est lent. (inclura l'ajout du support d'images brutes webp si accepté).
L'encodeur webM inspiré de Whammy: un WebM Javascript en temps réel
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,
}
})()