Sök…
Anmärkningar
Detta ämne är att täcka olika mediatyper och hur de kan användas med duken i 2D-gränssnitt.
Mediatyper har generiska och format specifika kategorier
Mediatyper
- animationer
- videoklipp
- Bilder
- HD-bilder
- Vektorbild
- Animerade bilder
Mediaformat
- Jpg / JPEG
- png
- Gif
- SVG
- M-JPEG
- WebM
- WebP
Bilder
Det finns en mängd olika bildformat som stöds av webbläsare, men ingen webbläsare stöder dem alla. Om du har vissa bildformat vill du använda Wiki Browsers och bildformat som stöds ger en bra översikt.
Det bästa stödet är för de tre huvudformaten, "jpeg", "png" och "gif" med alla större webbläsare som ger support.
JPEG
JPEG-bilder är bäst lämpade för foton och fotonliknande bilder. De lånar dem inte väl till diagram, diagram och text. JPEG-bilder stöder inte transparens.
Canvas kan mata ut JPEG-bilder via canvas.toDataURL
och canvas.toBlob
och ger en kvalitetsinställning. Eftersom JPEG inte stöder transparens kommer några transparenta pixlar att blandas med svart för den slutliga utgången JPG. Den resulterande bilden kommer inte att vara en perfekt kopia av duken.
PNG
PNG-bilden är bilder av högsta kvalitet och kan också inkludera en alfakanal för transparenta pixlar. Bilddata är komprimerade men producerar inte artefakter som JPG-bilder.
På grund av den förlustfria komprimeringen och alfakanalstödet används PNG: er för spel, ui-komponentbilder, diagram, diagram, text. När du använder dem till för foton och foton som bilder kan filstorleken vara mycket större än JPEG. .
PNG-formatet erbjuder också animationsstöd även om webbläsarsupport är begränsat, och åtkomst till animationen för användning på duken kan bara göras via Javascript API: er och bibliotek
Duken kan användas för att koda PNG-bilder via canvas.toDataURL
och canvas.toBlob
men utgångsformatet är begränsat till komprimerad 32Bit RGBA. PNG kommer att ge en pixel perfekt kopia av duken.
GIF
GIF används för korta animationer, men kan också användas för att tillhandahålla diagram, diagram och textliknande bilder av hög kvalitet. GIF har mycket begränsat färgstöd med högst 256 färger per ram. Med cleaver-bildbehandling kan gif-bilder ge förvånansvärt goda resultat, särskilt när de är animerade. Gif ger också öppenhet men detta är begränsat till på eller av
Som med PNG är GIF-animationer inte direkt tillgängliga för användning på duken och du behöver ett Javascript API eller bibliotek för att få åtkomst. GIF kan inte sparas via duken och kräver och API eller bibliotek för att göra det.
Laddar och visar en bild
För att ladda en bild och placera den på duken
var image = new Image(); // see note on creating an image
image.src = "imageURL";
image.onload = function(){
ctx.drawImage(this,0,0);
}
Skapa en bild
Det finns flera sätt att skapa en bild
-
new Image()
-
document.createElement("img")
-
<img src = 'imageUrl' id='myImage'>
Som en del av HTML-kroppen och hämtas meddocument.getElementById('myImage')
Bilden är en HTMLImageElement
Image.src-egendom
Image src
kan vara valfri bild-URL eller kodad dataURL. Se kommentarerna för detta ämne för mer information om bildformat och support.
-
image.src = "http://my.domain.com/images/myImage.jpg"
-
image.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="
*
* DataURL är en gif-bild på 1 x 1 pixel som innehåller svart
Kommentarer om lastning och fel
Bilden börjar laddas när dess src-egenskap är inställd. Laddningen är synkrios men onload
händelsen kommer inte att anropas förrän funktionen eller koden har lämnat / returnerats.
Om du får en bild från sidan (till exempel document.getElementById("myImage")
) och dess src
är inställd kan det ha kanske inte laddats. Du kan kontrollera bildens status med HTMLImageElement.complete
vilket är true
om det är komplett. Detta betyder inte att bilden har laddats, det betyder att den har heller
- lastad
- det var ett problem
- src-egenskapen har inte ställts in och är lika med den tomma strängen
""
Om bilden kommer från en opålitlig källa och kanske inte är tillgänglig av olika skäl kommer den att generera en felhändelse. När detta händer kommer bilden att vara i trasigt tillstånd. Om du sedan försöker dra den på duken kommer det att kasta följande fel
Uncaught DOMException: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state.
Genom att tillhandahålla image.onerror = myImgErrorHandler
händelsen kan du vidta lämpliga åtgärder för att förhindra fel.
Rita en svg-bild
För att rita en vektor-SVG-bild skiljer sig operationen inte från en rasterbild:
Du måste först ladda din SVG-bild i ett HTMLImage-element och sedan använda drawImage()
.
var image = new Image();
image.onload = function(){
ctx.drawImage(this, 0,0);
}
image.src = "someFile.SVG";
SVG-bilder har vissa fördelar jämfört med rasterbilder, eftersom du inte tappar kvalitet, oavsett vilken skala du ritar på din duk. Men se upp, det kan också vara lite långsammare än att rita en rasterbild.
SVG-bilder har dock fler begränsningar än rasterbilder.
Av säkerhetsskäl kan inget externt innehåll laddas från en SVG-bild som hänvisas till i en HTMLImageElement (
<img>
)
Inget externt formatmall, ingen extern bild hänvisad till SVGImage (<image/>
) -element, inget externt filter eller element länkat avxlink:href
(<use xlink:href="anImage.SVG#anElement"/>
) eller funcIRI (url()
) attributmetod etc.
Stilark som bifogas i huvuddokumentet kommer inte att ha någon effekt på SVG-dokumentet när det hänvisas till i ett HTMLImage-element.
Slutligen kommer inget skript att köras i SVG-bilden.
Lösning: Du måste lägga till alla externa element i själva SVG innan du hänvisar till HTMLImage-elementet. (för bilder eller teckensnitt måste du lägga till en dataURI-version av dina externa resurser).Rotelementet (
<svg>
) måste ha sina bredd- och höjdattribut inställda till ett absolut värde.
Om du skulle använda relativ längd (t.ex.%
) kan webbläsaren inte veta vad den är relativ. Vissa webbläsare (Blink) kommer att försöka göra gissningar, men de flesta ignorerar helt enkelt din bild och ritar inte något utan någon varning.Vissa webbläsare torkar duken när en SVG-bild har ritats på den.
I synnerhet Internet Explorer <Edge i alla fall och Safari 9 när ett<foreignObject>
finns i SVG-bilden.
Grundläggande laddning och uppspelning av en video på duken.
Duken kan användas för att visa video från olika källor. Det här exemplet visar hur du laddar en video som en filresurs, visar den och lägger till ett enkelt klick på skärmspelning / paus för att växla.
Denna självövervakade fråga med stackoverflow Hur visar jag en video med HTML5-duktagg visar följande exempelkod i aktion.
Bara en bild
En video är bara en bild när det gäller duken. Du kan rita det som vilken bild som helst. Skillnaden är att videon kan spela och har ljud.
Få duk och grundläggande installation
// 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
Skapa och ladda videon
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,
};
Till skillnad från bilderelement behöver videor inte laddas helt för att kunna visas på duken. Videor ger också en mängd extra händelser som kan användas för att övervaka status för videon.
I det här fallet vill vi veta när videon är redo att spelas upp. oncanplay
innebär att tillräckligt med videon har laddats för att spela en del av det, men det kanske inte finns tillräckligt för att spela till slutet.
video.oncanplay = readyToPlayVideo; // set the event to the play function that
// can be found below
Alternativt kan du använda oncanplaythrough
som kommer att eldas när tillräckligt med videon har laddats så att den kan spelas upp till slutet.
video.oncanplaythrough = readyToPlayVideo; // set the event to the play function that
// can be found below
Använd bara en av canPlay-händelserna, inte båda.
Händelsen kan spela (motsvarande bildavlastning)
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);
}
Visar
Videon spelar inte upp sig själv på duken. Du måste rita den för varje ny ram. Eftersom det är svårt att veta den exakta bildhastigheten och när de inträffar är den bästa tillvägagångssätten att visa videon som om den körs på 60 fps. Om bildfrekvensen är lägre, gör vi bara samma ram två gånger. Om bildfrekvensen är högre finns det inget som kan doneras för att se de extra ramarna så vi ignorerar dem bara.
Videoelementet är bara ett bildelement och kan rita som vilken bild som helst, du kan skala, rotera, panorera videon, spegla den, bleka den, klippa den och visa bara delar, rita den två gånger andra gången med ett globalt sammansatt läge för att lägga till FX som blixt, skärm osv.
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);
}
Grundläggande pauskontroll för uppspelning
Nu har vi laddat och visat videon allt vi behöver är uppspelningskontrollen. Vi kommer att göra det som ett klick för att växla uppspelningen på skärmen. När videon spelas upp och användaren klickar är videon pausad. När pausen pekar återupptar klickningen. Vi lägger till en funktion för att mörkna videon och rita en uppspelningsikon (triangel)
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
}
Nu spelar paushändelsen
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);
Sammanfattning
Att spela en video är mycket enkelt med duken, det är också lätt att lägga till effekt i realtid. Det finns dock vissa begränsningar för format, hur du kan spela och söka. MDN HTMLMediaElement är platsen för att få full hänvisning till videoobjektet.
När bilden har ritats på duken kan du använda ctx.getImageData
att komma åt pixlarna som den innehåller. Eller så kan du använda canvas.toDataURL
att ta en stillbild och ladda ner den. (Endast om videon kommer från en pålitlig källa och inte torkar duken).
Observera att om videon har ljud spelar den också ljudet.
Glad video.
Fånga duk och spara som webM-video
Skapa en WebM-video från dukramar och spela i duk, eller ladda upp eller ladda ner.
Exempel fånga och spela duk
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
I stället för att göra en enorm ansträngning bara för att bli avvisad, är detta en snabb insats för att se om det är acceptabelt. Kommer att ge fullständig information om det accepteras. Inkludera även ytterligare fångstalternativ för bättre HD-fångstfrekvens (tas bort från den här versionen, Kan fånga HD 1080 vid 50 fps på bra maskiner.)
Detta inspirerades av Wammy men är en fullständig omskrivning med kod när du går metod, vilket minskar minnet som krävs under inspelningen. Kan fånga mer än 30 sekunder bättre data, hantera algoritmer.
Obs- ramar är kodade i webP-bilder. Endast Chrome stöder webP-kanfaskodning. För andra webbläsare (Firefox och Edge) måste du använda en tredjeparts webbP-kodare som Libwebp Javascript Kodning av WebP-bilder via Javascript går långsamt. (kommer att inkludera tillägg av raw webp-bilder support om det accepteras).
WebM-kodaren inspirerad av Whammy: A Real Time 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,
}
})()