Manipulera Pixlar Med Hjälp Av Canvas

0
10

Moderna webbläsare har stöd för uppspelning av video via <video> – elementet. De flesta webbläsare har också tillgång till webbkameror via MediaDevices.getUserMedia() API. Men även med dessa två saker tillsammans, vi kan inte riktigt komma åt och manipulera dessa pixlar direkt.

Lyckligtvis webbläsare har en Canvas-API som tillåter oss att rita grafik med hjälp av JavaScript. Vi kan faktiskt dra bilder till <canvas> från själva videon, vilket ger oss möjlighet att manipulera och leka med dessa pixlar.

Allt du lär dig mer här om hur man kan manipulera pixlar kommer att ge dig en grund för att arbeta med bilder och video av något slag eller någon källa, inte bara på duk.

Lägga till en bild på duk

Innan vi börjar spela med video, låt oss titta på att lägga till en bild på duk.

<img id=”SourceImage” src=”image.jpg”>
<div class=”video-container”>
<canvas id=”Duk” class=”video”></canvas>
</div>

Vi skapade en bild element som representerar den bild som kommer att dras på duk. Alternativt kan vi använda Bilden objektet i JavaScript.

var duken;
var sammanhang.

function init() {
var image = dokument.getElementById(‘SourceImage’);
duk = dokument.getElementById(‘Canvas”);
context = canvas.getContext(‘2d’);

drawImage(bild)
// Eller
// var bild = new Image();
// bild.onload = function () {
// drawImage(bild)
// }
// bild.src = ‘image.jpg’;
}

funktion drawImage(bilden) {
// Set duken samma bredd och höjd på bilden
duk.bredd = image.bredd;
duk.höjd = image.höjd;

sammanhanget.drawImage(bild, 0, 0);
}

fönster.addEventListener(‘load’, init);

Koden ovan drar hela bilden på duken.

Se Pennan Måla upp bilden på duk av Welling Guzman (@wellingguzman) på CodePen.

Nu kan vi börja spela med dessa pixlar!

Uppdatering av bilddata

Bilddata på canvas tillåter oss att manipulera och ändra pixlar.

Data egendom är en ImageData objekt med tre egenskaper — bredd, höjd och data/ alla som representerar de saker baserad på den ursprungliga bilden. Alla dessa egenskaper är skrivskyddad. Vi bryr oss om är data, n-dimensionell vektor representeras av en Uint8ClampedArray objekt, som innehåller de uppgifter som för varje pixel i en RGBA-format.

Även om data egendom är skrivskyddad, det betyder inte att vi inte kan ändra sitt värde. Det betyder att vi inte kan tilldela en annan array till denna egendom.

// Get canvas bild data
var imageData = sammanhanget.getImageData(0, 0, canvas.bredd, canvas.höjd);

bild.data = new Uint8ClampedArray(); // FEL
bild.data[1] = 0; // RÄTTA

Vilka värden har Uint8ClampedArray objektet representerar, kanske du frågar. Här är beskrivningen från MDN:

Den Uint8ClampedArray skrivit matrisen representerar en rad 8-bitars unsigned heltal fastklämd 0-255; om du har angett ett värde som inte ligger i intervallet [0,255], 0 eller 255 kommer att ställas in istället, om du anger en icke-heltal, närmaste heltal kommer att ställas in. Innehållet är initieras till 0. När den väl är etablerad, kan du referera element i matrisen med hjälp av objektets metoder, eller med hjälp av standard-array-index syntax (det är, med fäste notation)

Kort sagt, detta array som lagrar värden från 0 till 255 i varje position, vilket gör detta till den perfekta lösningen för den RGBA-format, som att varje del representeras av 0 till 255 värden.

RGBA färger

Färger kan representeras av RGBA-format, som är en kombination av Röd, Grön och Blå. Den representerar En alpha-värde som är oklara färg.

Varje position i matrisen representerar en färg (pixel) kanal värde.

  • 1: a position är den Röda värde
  • 2: a position är den Gröna värde
  • 3: e plats är den Blå värde
  • 4: e position är Alfa-värde
  • 5: e position är nästa pixel Red värde
  • 6: e plats är nästa pixel Grön värde
  • 7: e plats är nästa pixel Blå värde
  • 8: e plats är nästa pixel Alfa-värde
  • Och så vidare…

Om du har en 2×2 bild, då har vi en 16 position array (2×2 pixlar x 4 värde).

2×2 bilden zoomas upp nära

Matrisen kommer att vara representerade så som visas nedan:

// RÖD GRÖN BLÅ VIT
[ 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255]
Ändra pixel data

En av de snabbaste saker vi kan göra är att ställa in alla pixlar till vitt genom att ändra alla RGBA värden till 255.

// Använd en knapp för att utlösa den “effekt”
var knapp = dokument.getElementById (“Knapp”);

– knappen.addEventListener(‘klicka’, text);

funktion changeToWhite(data) {
for (var i = 0; i < data.längd; i++) {
data[i] = 255;
}
}

funktion onClick() {
var imageData = sammanhanget.getImageData(0, 0, canvas.bredd, canvas.höjd);

changeToWhite(imageData.data);

// Uppdatering duken med nya data
sammanhanget.putImageData(imageData, 0, 0);
}

Uppgifterna kommer att skickas som referens, vilket innebär att alla ändringar vi gör i det, det kommer att ändra det värdet på argumentet passerade.

Invertera färger

En fin effekt som inte kräver mycket beräkningar är att invertera färgerna i en bild.

Invertera en färg värdet kan göras med XOR-operatorn (^) eller denna formel 255 – värde (värdet måste vara mellan 0 och 255).

funktion invertColors(data) {
for (var i = 0; i < data.längd; i+= 4) {
data[i] = data[i] ^ 255; // Invertera Röd
data[i+1] = data[i+1] ^ 255; // Invertera Grön
data[i+2] = data[i+2] ^ 255; // Invertera Blå
}
}

funktion onClick() {
var imageData = sammanhanget.getImageData(0, 0, canvas.bredd, canvas.höjd);

invertColors(imageData.data);

// Uppdatering duken med nya data
sammanhanget.putImageData(imageData, 0, 0);
}

Vi uppräkning slinga med 4 istället för 1 som vi gjorde innan, så vi kan från pixel till pixel som varje fyller 4 element i matrisen.

Alpha-värdet har inte effekt på invertera färger, så vi hoppar över det.

Ljusstyrka och kontrast

Justera ljusstyrkan i en bild kan göras med hjälp av nästa formel: newValue = currentValue + 255 * (ljusstyrka / 100).

  • ljusstyrkan måste vara mellan -100 och 100
  • currentValue är den nuvarande ljuset värde av antingen Rött, Grönt eller Blått.
  • newValue är resultatet av den aktuella färgen light plus ljusstyrka

Justera kontrasten i en bild kan göras med den här formeln:

faktor = (259 * (kontrast + 255)) / (255 * (259 – kontrast))
färg = GetPixelColor(x, y)
newRed = Avkorta(faktor * (Röda(färg) – 128) + 128)
newGreen = Avkorta(faktor * (Gröna(färg) – 128) + 128)
newBlue = Avkorta(faktor * (Blå(färg) – 128) + 128)

De viktigaste beräkning är att få kontrast faktor som kommer att tillämpas för varje färg värde. Trunkera är en funktion som gör att värdet stanna mellan 0 och 255.

Låt oss skriva dessa funktioner i JavaScript:

funktion applyBrightness(data, ljusstyrka) {
for (var i = 0; i < data.längd; i+= 4) {
data[i] += 255 * (ljusstyrka / 100);
data[i+1] += 255 * (ljusstyrka / 100);
data[i+2] += 255 * (ljusstyrka / 100);
}
}

funktion truncateColor(value) {
if (value < 0) {
värde = 0;
} else if (value > 255) {
värde = 255;
}

return värde;
}

funktion applyContrast(data), contrast (kontrast) {
var faktor = (259.0 * (kontrast + 255.0)) / (255.0 * (259.0 – kontrast));

for (var i = 0; i < data.längd; i+= 4) {
data[i] = truncateColor(faktor * (data[i] – 128.0) + 128.0);
data[i+1] = truncateColor(faktor * (data[i+1] – 128.0) + 128.0);
data[i+2] = truncateColor(faktor * (data[i+2] – 128.0) + 128.0);
}
}

I detta fall behöver du inte truncateColor funktion som Uint8ClampedArray kommer att trunkera dessa värden, utan att för den skull översätta algoritmen vi lade till att.

En sak att tänka på är att, om du använder en ljusstyrka eller kontrast, det finns ingen väg tillbaka till ett tidigare tillstånd som bilden data skrivs över. Den ursprungliga bilden data måste lagras separat för referenser om vi vill återställa till det ursprungliga tillståndet. Att hålla bilden variabel tillgängligt för andra funktioner som kommer att vara till hjälp som du kan använda som bild i stället för att rita om den i canvas med den ursprungliga bilden.

var image = dokument.getElementById(‘SourceImage’);

funktion redrawImage() {
sammanhanget.drawImage(bild, 0, 0);
}

Använda video

För att få det att fungera med video, vi kommer att ta våra första bild script och HTML-koden och göra några små förändringar.

HTML –

Ändra Bildens element med en video element genom att ersätta den här raden:

<img id=”SourceImage” src=”image.jpg”>

…med detta:

<video id=”SourceVideo” src=”video.mp4″></video>
JavaScript

Byt ut den här raden:

var image = dokument.getElementById(‘SourceImage’);

…med detta:

var video = dokument.getElementById(‘SourceVideo’);

Att börja jobba med video, har vi att vänta tills videon kan spelas upp.

video.addEventListener(‘canplay’, function () {
// Set duken samma bredd och höjd video
duk.bredd = video.videoWidth;
duk.höjd = video.videoHeight;

// Spela video
video.spela();

// börja rita ramar
drawFrame(video);
});

Händelsen canplay startar när tillräckligt med data finns som media kan spelas upp, åtminstone för ett par ramar.

Vi kan inte se någon video som visas på duken, eftersom vi bara visa den första bildrutan. Vi måste köra drawFrame varje n millisekunder för att hålla upp med bildrutor priser.

Inne drawFrame vi kallar drawFrame igen varje 10ms.

funktion drawFrame(video) {
sammanhanget.drawImage(video, 0, 0);

setTimeout(function () {
drawFrame(video);
}, 10);
}

Efter att vi kör drawFrame, vi skapar en loop verkställande drawFrame varje 10ms — tillräckligt med tid för att hålla video i synk i canvas.

Lägga till effekten i en video

Vi kan använda samma funktion som vi skapat innan för att invertera färger:

funktion invertColors(data) {
for (var i = 0; i < data.längd; i+= 4) {
data[i] = data[i] ^ 255; // Invertera Röd
data[i+1] = data[i+1] ^ 255; // Invertera Grön
data[i+2] = data[i+2] ^ 255; // Invertera Blå
}
}

Och lägga in den i drawFrame funktion:

funktion drawFrame(video) {
sammanhanget.drawImage(video, 0, 0);

var imageData = sammanhanget.getImageData(0, 0, canvas.bredd, canvas.höjd);
invertColors(imageData.data);
sammanhanget.putImageData(imageData, 0, 0);

setTimeout(function () {
drawFrame(video);
}, 10);
}

Vi kan lägga till en knapp och växla effekter:

funktion drawFrame(video) {
sammanhanget.drawImage(video, 0, 0);

om (applyEffect) {
var imageData = sammanhanget.getImageData(0, 0, canvas.bredd, canvas.höjd);
invertColors(imageData.data);
sammanhanget.putImageData(imageData, 0, 0);
}

setTimeout(function () {
drawFrame(video);
}, 10);
}

Med hjälp av kamera

Vi kommer att hålla samma kod vi använder för video med bara annorlunda är att vi kommer att ändra videoströmmen från en fil till kameran ström med hjälp av MediaDevices.getUserMedia

MediaDevices.getUserMedia är det nya API nedsättande föregående API MediaDevices.getUserMedia(). Det är fortfarande webbläsare stöd för den gamla versionen och vissa webbläsare har inte stöd för den nya versionen, och vi måste ta till polyfill att kontrollera att webbläsaren har stöd för en av dem

För det första, ta bort attributet src från tv-inslag:

<video id=”SourceVideo”><kod></pre>

<pre rel=”JavaScript”><code class=”språket-javascript”>// Ange källa video till kamera ström
funktion initCamera(ström) {
video.src = fönster.URL: en.createObjectURL(ström);
}

om (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({video: sant, ljud: false})
.då(initCamera)
.catch(konsol.fel)
);
}

Live Demo

Effekter

Allt vi har täckt hittills är den grund vi behöver för att skapa olika effekter på en video eller bild. Det finns en massa olika effekter som vi kan använda genom att omvandla varje färg separat.

Gråskala

Konvertera en färg till en gråskala kan göras på olika sätt med hjälp av olika formler/tekniker, för att undvika att få alltför djupt i ämnet jag kommer att visa dig fem av formler baserade på GIMP desaturate verktyg och Luma:

Grå = 0.21 R + 0.72 G + 0.07 B // Luminositet
Grått = (R + G + B) ÷ 3 // Genomsnittlig Ljusstyrka
Grå = 0.299 R + 0.587 G + 0.114 B // rec601 standard
Grå = 0.2126 R + 0.7152 G + 0.0722 B // ITU-R-BT.709 standard
Grå = 0.2627 R + 0.6780 G + 0.0593-B // ITU-R-BT.2100 standard

Vad vi vill hitta med hjälp av dessa formler är ljusstyrkan intensitet för varje pixel färg. Värdet varierar mellan 0 (svart) till 255 (vit). Dessa värden kommer att skapa en gråskala (svart och vit) effekt.

Detta innebär att den ljusaste färgen kommer att vara närmast till 255 och den mörkaste färgen som ligger närmast till 0.

Live Demo

Duplex

Skillnaden mellan tvåfärgat effekt och gråskala effekt är de två färger som används. På gråskala du har en övertoning från svart till vitt, även om tvåfärgat du kan ha en gradient från en färg till en annan färg, blå till rosa som ett exempel.

Med hjälp av den intensitet värde av gråskala, vi kan ersätta detta från lutning värden.

Vi måste skapa en gradient från ColorA att ColorB.

funktion createGradient(colorA, colorB) {
// Värderingar av gradient från colorA att colorB
var gradient = [];
// den maximala färg värde är 255
var maxvärde = 255;
// Konvertera hex färg värden för RGB-objekt
var från = getRGBColor(colorA);
var till = getRGBColor(colorB);

// Skapar 256 färger från En Färg till Färg B
for (var i = 0; i <= maxValue; i++) {
// IntensityB kommer att gå från 0 till 255
// IntensityA kommer att gå från 255 till 0
// IntensityA kommer att minska i intensitet samtidigt som instensityB kommer att öka
// Vad detta betyder är att ColorA kommer att börja solid och långsamt förvandlas till ColorB
// Om man ser på det på andra sätt insyn i En färg kommer att öka och insyn i färg B kommer att minska
var intensityB = jag;
var intensityA = maxValue – intensityB;

// Den formeln nedan kombinerar de två färg baserat på deras intensitet
// (IntensityA * ColorA + IntensityB * ColorB) / maxValue
gradient[i] = {
r: (intensityA*från.r + intensityB*om du vill.r) / maxValue,
g: (intensityA*från.g + intensityB*om du vill.g) / maxValue,
b: (intensityA*från.b + intensityB*om du vill.b) / maxValue
};
}

tillbaka lutning;
}

// Hjälpfunktioner för att konvertera 6digit hex-värden för att en RGB-objekt
funktion getRGBColor(hex)
{
var colorValue;

om (hex[0] === ‘#’) {
hex = hex.substr(1);
}

colorValue = parseInt(hex) 16);

return {
r: colorValue >> 16,
g: (colorValue >> 8) & 255,
b: colorValue & 255
}
}

Kort sagt, vi skapar en array av färgvärden från Färg till En minskande intensiteten när du kommer till Färg B och öka dess intensitet.

Från #0096ff till #ff00f0
Zoomad representation av färg övergång

var gradienter = [
{r: 32, g: 144, b: 254},
{r: 41, g: 125, b: 253},
{r: 65, g: 112, b: 251},
{r: 91, g: 96, b: 250},
{r: 118, g: 81, b: 248},
{r: 145, g: 65, b: 246},
{r: 172, g: 49, b: 245},
{r: 197, g: 34, b: 244},
{r: 220 g: 21, b: 242},
{r: 241, g: 22, b: 242},
];

Ovan finns ett exempel på en lutning av 10 färger värden från #0096ff till #ff00f0.

Gråskala representation av färg övergång

Nu när vi har gråskala representation av bilden, kan vi använda det för att mappa den till tvåfärgat lutning värden.

Den tvåfärgat lutning har 256 färger medan gråskala har också 256 färger från svart (0) till vitt (255). Det innebär en gråskala färg värde kommer att mappa en lutning elementet index.

var gradientColors = createGradient(‘#0096ff’, ‘#ff00f0’);
var imageData = sammanhanget.getImageData(0, 0, canvas.bredd, canvas.höjd);
applyGradient(imageData.data);

for (var i = 0; i < data.längd; i += 4) {
// Get varje kanal färg värde
var redValue = data[i];
var greenValue = data[i+1];
var blueValue = data[i+2];

// En kartläggning färg värden för lutning index
// Byta gråskala färg värde med en färg för tvåfärgat gradient
data[i] = gradientColors[redValue].r;
data[i+1] = gradientColors[greenValue].g.
data[i+2] = gradientColors[blueValue].b;
data[i+3] = 255;
}

Live Demo

Slutsats

Detta ämne kan gå mer på djupet eller förklara mer effekter. Läxor för dig är att hitta olika algoritmer kan du ansöka om att dessa skelett exempel.

Att veta hur pixlar är strukturerade på ett arbetsytan gör att du kan skapa ett obegränsat antal effekter, till exempel sepia, färg blandning, en grön skärm effekt, bildflimmer/glitching, etc.

Du kan även skapa effekter på farten utan att använda en bild eller ett videoklipp:

Jetpack WordPress plugin som körs på denna webbplats, driver inte bara relaterade inlägg nedan, men säkerhet och backup, Wiki-stöd, sök på sajten, kommentera form, sociala nätverk, och mycket mer!