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.

JPEG på wikipedia

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.

PNG på wikipedia

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.

GIF på wikipedia

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 med document.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 av xlink: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,
    }
})()


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow