Prosty WebCam – czyli userMedia w Chrome

Witam ;)

Bawiąc się Chrome’em wpadłem na pomysł żeby napisać tutaj kilka linijek kodu które pokażą że hiper łatwo można napisać prosty WebCam używający podłączonej do komputera kamerki.

Zacznijmy od tego że zabierając się do tego zrobiłem sobie plik html i plik js. Uruchomiłem go (bez serwera) i niestety okazało się że nie mogę w takich warunkach używać mediów.

Więc w swoim xammpie dodałem kolejnego vhosta i wrzuciłem tam pliki. Było znacznie lepiej ale.. Nadal źle – okazało się że w najnowszej wersji Google Chrome’a nie można używać mediów bez httpsa. Nieco poirytowany tym faktem szukałem rozwiązania, pierwszym było uruchamianie przeglądarki ze specjalnym przełącznikiem – ale gdy dodałem ten przełącznik to Chrome wyświetlał zacny komunikat „nie rozpoznana opcja blabla”. Jak zawsze – wiatr w oczy i coś nie powiem w co – ale nie poddałem się. Wygenerowałem certyfikat self-signed (sądziłem że na Windows’ie będą problemy, ale nie było), podpiąłem do vhosta i.. Udało się!

No a skoro już nakreśliłem jakie problemy mnie spotkały to napiszmy kod.

Całość magii leży w funkcji

1
getUserMedia

– pozwala ona na dostęp do mediów zgodnie z przekazanymi jako pierwszy argument – ograniczeniami. W tym przykładzie chcę uzyskać dostęp do kamerki więc kod będzie wyglądał tak:

1
2
3
4
5
6
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia

var media = navigator.getUserMedia({
    video:true,
    audio:false
}, successMedia, errorMedia);

pierwsza linijka pozwala na wybór dostępnej implementacji mediów (konkretnej dla konkretnej przeglądarki) a kolejna to już właściwe wywołanie funkcji. Drugi argument to funkcja zwrotna dla sukcesu przy dostępnie do mediów i trzeci, analogicznie – dla błędu. Jeśli odmówimy dostępu to wykona się właśnie funkcja errorMedia.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function(){
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia

    var successMedia = function(stream){
        var videoDestination = document.getElementsByTagName("video")[0];
        videoDestination.src = window.URL.createObjectURL(stream);
    }

    var errorMedia = function(error){
        alert(error)
    }

    var media = navigator.getUserMedia({
        video:true,
        audio:false
    }, successMedia, errorMedia);
})();

Rozszerzyliśmy nasz kod. Podpięliśmy w funkcji successMedia, strumień do elementu video. Dodaliśmy go również do kodu HTML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<html>
<head>
    <meta charset="utf-8">

<style type="text/css">
    .video{
        width: 100%;
        height: 600px;
    }
    .left{
        float:left;
        width: 65%;
    }
</style>
</head>
<body>
    <div>
        <div class="left">
            <video class="video" autoplay>
            </video>
        </div>
        <div>
        </div>
    </div>
</body>
    <script type="text/javascript" src="media.js"></script>
</html>

I to praktycznie mógłby być koniec – bo już uruchamiając taki plik – możemy zobaczyć to co widzi nasza kamerka ;)

Ale.. Rozbudujmy to nieco.

Co to za WebCam bez możliwości nadawania efektów do zdjęć?

Skorzystamy z filtrów oferowanych przez -webkit-filter.
Dodajemy więc klasę:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var EffectsManipulator = function(target){
    this.target = target;
    this.effectsClasses = {
        "none":"",
        "blur":"blur(2px)",
        "grayscale":"grayscale(100%)",
        "sepia":"sepia(100%)",
        "brightness":"brightness(30%)",
        "contrast":"contrast(1000%)",
        "invert":"invert(100%)",
        "saturate":"saturate(700%)"

    };
    this.currentEffect = "none";
};

EffectsManipulator.prototype.setEffect = function(effectName) {
    if(this.effectsClasses.hasOwnProperty(effectName)){
        this.target.style['-webkit-filter']=this.effectsClasses[effectName];
        this.currentEffect = effectName;
    } else {
        this.setEffect("none");
    }
};

i podpinamy ją do targetu:

1
2
3
4
var effectsManipulator = new EffectsManipulator(document.getElementsByTagName("video")[0]);
document.getElementById("effect-changer").onchange = function(el){
    effectsManipulator.setEffect(el.srcElement.selectedOptions[0].value);
};

Jak widać po kodzie – plik HTML też zmodyfikowaliśmy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<html>
<head>
    <meta charset="utf-8">

<style type="text/css">
    .video{
        width: 100%;
        height: 600px;
    }
    .left{
        float:left;
        width: 65%;
    }

</style>
</head>
<body>
    <div>
        <div class="left">
            <video class="video" autoplay>
            </video>
        </div>
        <div>
            Wybierz efekt:
            <select id="effect-changer">
                <option value="none">Brak</option>
                <option value="sepia">Sepia</option>
                <option value="grayscale">Odcień szarości</option>
                <option value="contrast">Kontrast</option>
                <option value="saturate">Nasycenie</option>
                <option value="invert">Negatyw</option>
                <option value="blur">Rozmycie</option>
                <option value="brightness">Przyciemnienie</option>
            </select>
        </div>
    </div>
</body>
    <script type="text/javascript" src="media.js"></script>
</html>

I już działa – możemy, wybierając z listy – nakładać efekt na to co widzi nasza kamerka.
Jaką jeszcze funkcję mają WebCam’y? Ano robienie sobie zdjęć ;)

Zaimplementujmy to w ten sposób:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
EffectsManipulator.prototype.takePhoto = function(){
    var canvas = document.createElement('canvas');
    canvas.width = 860;
    canvas.height = 640;
    var ctx = canvas.getContext('2d');
    ctx.drawImage(this.target, 0, 0, canvas.width, canvas.height);

    var dataURI = canvas.toDataURL('image/jpeg');
    var img = document.createElement("img");
    img.src = dataURI;
    img.style['-webkit-filter'] = this.effectsClasses[this.currentEffect];
    var newWindow = window.open("", "Image","scrollbars=0, toolbar=0, width="+img.width+", height="+img.height);
    newWindow.document.write(img.outerHTML);
}

Tworzymy sobie w pamięci obiekt Canvas, rysujemy na nim zawartość elementu video. Wyrysowaną zawartość pobieramy z canvas’a i przypisujemy do nowo stworzonego elementu img. Na ten element nakładamy ten sam filtr co jest nałożony na element video i otwieramy nowe okno z dołączonym elementem img.
Funkcja fajna, ale dodajmy jeszcze przycisk do HTMLa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<html>
<head>
    <meta charset="utf-8">

<style type="text/css">
    .video{
        width: 100%;
        height: 600px;
    }
    .left{
        float:left;
        width: 65%;
    }
    .take-photo{
        margin: auto;

    }
</style>
</head>
<body>
    <div>
        <div class="left">
            <video class="video" autoplay>
            </video>
        </div>
        <div>
            Wybierz efekt:
            <select id="effect-changer">
                <option value="none">Brak</option>
                <option value="sepia">Sepia</option>
                <option value="grayscale">Odcień szarości</option>
                <option value="contrast">Kontrast</option>
                <option value="saturate">Nasycenie</option>
                <option value="invert">Negatyw</option>
                <option value="blur">Rozmycie</option>
                <option value="brightness">Przyciemnienie</option>
            </select>
            <p>
                <button class="take-photo">Zrób zdjęcie!</button>
            </p>
        </div>
    </div>
</body>
    <script type="text/javascript" src="media.js"></script>
</html>

i np. taki kawałek kodu do JSa:

1
2
3
document.getElementsByClassName("take-photo")[0].onclick = function(){
    effectsManipulator.takePhoto();
};

DEMO można zobaczyć na stronie: https://media.mmazur.eu.org – i tak, certyfikat nadal nie prawidłowy, bo chodź podpisany to nie pasuje do mojej domeny (jest podpisany dla mojego dostawcy usług hostingowych) co powoduje że będziecie mieli komunikat – nie ma się co bać – zaakceptujcie.

PS. Nigdzie Waszych zdjęć nie zapisuję :D
PS2. Użycie -webkit-filter wymaga aby demo było uruchamiane w Chrome :P kamerka zadziała np. w FireFoxie ale już filtry nie będą się nakładały.

Matt.

Dzięki za wizytę,
Mateusz Mazurek
Podziel się na:
    Facebook email PDF Wykop Twitter

Dodaj komentarz

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subskrybuj  
Powiadom o