最新のブラウザは、 <video>要素を介したビデオ再生をサポートしています。ほとんどのブラウザは、MediaDevices.getUserMedia() API を通じてカメラにアクセスすることもできます。しかし、これら 2 つを組み合わせたとしても、これらのピクセルに直接アクセスして操作することはできません。
幸いなことに、ブラウザには、JavaScript を使用してグラフィックを描画できる Canvas API があります。実際にビデオ自体から<canvas>に画像を描画することができ、これによりそれらのピクセルを操作して表示することができます。
ピクセルの操作方法についてここで学んだことは、キャンバスだけでなく、あらゆる種類やソースの画像やビデオを操作するための基礎を提供します。
キャンバスに画像を追加ビデオを始める前に、キャンバスに画像を追加する方法を見てみましょう。
<img src><div> <canvas id=Canvas class=video></canvas></div>
キャンバスに描画する画像を表す image 要素を作成します。あるいは、JavaScript で Image オブジェクトを使用することもできます。
var Canvas;var context;function init() { var image = document.getElementById('SourceImage'); Canvas = document.getElementById('Canvas'); // または // var image = new Image() // image.onload = function () { //drawImage(image); // image.src = 'image.jpg';}functiondrawImage(image) { // キャンバスを画像と同じ幅と高さに設定します Canvas.width = image.width; context.drawImage(image, 0, 0);}window.addEventListener('load', init);上記のコードは、画像全体をキャンバスに描画します。
CodePen で Welling Guzman (@wellingguzman) によるキャンバス画像にペイントを確認してください。
これで、これらのピクセルを使って遊び始めることができます。
画像データを更新するキャンバス上の画像データを使用すると、ピクセルを操作したり変更したりできます。
data 属性は、幅、高さ、データという 3 つのプロパティを持つ ImageData オブジェクトであり、これらはすべて元の画像に基づいて何かを表します。これらのプロパティはすべて読み取り専用です。ここで重要なのはデータです。これは、RGBA 形式の各ピクセルのデータを含む Uint8ClampedArray オブジェクトによって表される 1 次元配列です。
データ プロパティが読み取り専用であっても、その値を変更できないわけではありません。これは、このプロパティに別の配列を割り当てることができないことを意味します。
// キャンバス画像のデータを取得します。var imageData = context.getImageData(0, 0, Canvas.width, Canvas.height);image.data = new Uint8ClampedArray(); // WRONGimage.data[1] = 0;
Uint8ClampedArray オブジェクトはどのような値を表すのかと疑問に思われるかもしれません。 MDN からの説明は次のとおりです。
Uint8ClampedArray 型の配列は、0 ~ 255 にクランプされた 8 ビットの符号なし整数の配列を表します。[0,255] の範囲外の値を指定した場合は、最も近い値の 0 または 255 が設定されます。整数が設定されます。内容は 0 に初期化されます。確立されると、配列内の要素は、オブジェクトのメソッドを使用するか、標準の配列インデックス構文 (つまり括弧表記を使用する) を使用して参照できます。
つまり、この配列は各位置に 0 ~ 255 の範囲の値を格納します。各部分が 0 ~ 255 の値で表されるため、RGBA 形式にとって完璧なソリューションになります。
RGBAカラー色は、赤、緑、青の組み合わせである RGBA 形式で表現できます。 A は、色の不透明度のアルファ値を表します。
配列内の各位置は、カラー (ピクセル) チャネル値を表します。
2x2 の画像がある場合は、16 ビット配列 (2x2 ピクセル x それぞれ 4 つの値) があります。
2x2画像を縮小
配列は次のようになります。
// 赤、緑、青、白[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255]ピクセルデータを変更する
最も簡単にできることの 1 つは、すべての RGBA 値を 255 に変更して、すべてのピクセルを白に設定することです。
// ボタンを使用してエフェクトをトリガーしますvar button = document.getElementById('Button');button.addEventListener('click', onClick);function changeToWhite(data) { for (var i = 0; i < data.length; i++) { data[i] = 255; }}関数 onClick() { var imageData = context.getImageData(0, 0, Canvas.width, Canvas.height); changeToWhite(imageData.data); // 新しいデータでキャンバスを更新します context.putImageData(imageData, 0, 0);}データは参照として渡されます。つまり、データに変更を加えると、渡されたパラメータの値が変更されます。
色を反転するあまり多くの計算を必要としない優れた効果は、画像の色を反転することです。
色の値は、XOR 演算子 (^) またはこの式 255 - 値 (値は 0 ~ 255 の間である必要があります) を使用して反転できます。
function invertColors(data) { for (var i = 0; i < data.length; i+= 4) { data[i] = data[i] ^ 255; // 赤を反転 data[i+1] = data[i; +1] ^ 255; // 緑を反転 data[i+2] = data[i+2] ^ 255; // 青を反転 }} function onClick() { var imageData = context.getImageData(0, 0, Canvas.width, Canvas.height); invertColors(imageData.data); // 新しいデータでキャンバスを更新します context.putImageData(imageData, 0, 0);}以前のようにループを 1 ではなく 4 ずつインクリメントするので、配列内の 4 つの要素をピクセルからピクセルまで、各ピクセルに埋めることができます。
アルファ値は色の反転には影響しないため、省略します。
明るさとコントラスト画像の明るさは、次の式を使用して調整できます: newValue = currentValue + 255 * (明るさ / 100)。
因子 = (259 * (コントラスト + 255)) / (255 * (259 - コントラスト))color = GetPixelColor(x, y)newRed = Truncate(factor * (Red(color) - 128) + 128)newGreen = Truncate(係数 * (緑(色) - 128) + 128)newBlue = 切り捨て(係数 * (青(色) - 128) + 128)
主な計算は、各色の値に適用されるコントラスト係数を取得することです。切り捨ては、値が 0 ~ 255 の間にあることを保証する関数です。
これらの関数を JavaScript に記述してみましょう。
function applyBrightness(data, 明るさ) { for (var i = 0; i < data.length; i+= 4) { data[i] += 255 * (明るさ / 100); (明るさ / 100); data[i+2] += 255 * (明るさ / 100) }}関数 truncateColor(value); (値 < 0) { 値 = 0; } else if (値 > 255) { 値 = 255; } 戻り値;}関数 applyContrast(データ, コントラスト) { var 係数 = (259.0 * (コントラスト + 255.0)) 255.0 * (259.0 - コントラスト)); for (var i = 0; i < data.length; i+= 4) { data[i] = truncateColor(factor * (data[i] - 128.0) + 128.0); data[i+1] = truncateColor(factor * (data[i+1] - 128.0) + 128.0);係数 * (データ[i+2] - 128.0) + 128.0);この場合、Uint8ClampedArray が値を切り捨てるため、truncateColor 関数は必要ありませんが、関数に追加したアルゴリズムを変換するためです。
注意すべき点は、明るさやコントラストを適用すると、画像データが上書きされ、前の状態に戻すことができないということです。元の状態に戻したい場合は、参照用に元の画像データを別途保存する必要があります。イメージ変数を他の関数からアクセスできるようにしておくと、イメージを使用してキャンバスや元のイメージを再描画できるため便利です。
var image = document.getElementById('SourceImage'); 関数 redrawImage() { context.drawImage(image, 0, 0);}ビデオを使用するビデオで機能させるために、最初の画像スクリプトと HTML コードを使用して、いくつかの小さな変更を加えます。
HTML次の行を置き換えて、video 要素の Image 要素を変更します。
<画像ソース>
...これで:
<ビデオソース></ビデオ>
JavaScript
この行を次のように置き換えます。
var image = document.getElementById('SourceImage');...次の行を追加します。
var video = document.getElementById('SourceVideo');ビデオの処理を開始するには、ビデオの再生準備ができるまで待つ必要があります。
video.addEventListener('canplay', function () { // キャンバスをビデオと同じ幅と高さに設定します Canvas.width = video.videoWidth; Canvas.height = video.videoHeight; // ビデオを再生します video.play( ); // フレームの描画を開始しますdrawFrame(video);});メディアを再生するのに十分なデータがある場合、イベント再生は少なくとも数フレーム再生されます。
最初のフレームのみが表示されているため、キャンバスに表示されているビデオは何も表示されません。ビデオのフレームレートを維持するには、n ミリ秒ごとにdrawFrame を実行する必要があります。
drawFrame 内では、10 ミリ秒ごとに再度drawFrame を呼び出します。
関数drawFrame(video) { context.drawImage(video, 0, 0); setTimeout(function () {drawFrame(video); }, 10);}drawFrame を実行した後、10 ミリ秒ごとにdrawFrame を実行するループを作成します。これは、キャンバス内でビデオの同期を維持するのに十分な時間です。
ビデオにエフェクトを追加する前に作成したのと同じ関数を使用して色を反転できます。
function invertColors(data) { for (var i = 0; i < data.length; i+= 4) { data[i] = data[i] ^ 255; // 赤を反転 data[i+1] = data[i; +1] ^ 255; // 緑を反転 data[i+2] = data[i+2] ^ 255; // 青を反転 }}これをdrawFrame関数に追加します。
関数drawFrame(video) { context.drawImage(video, 0, 0) = context.getImageData(0, 0, Canvas.width, Canvas.height); context.putImageData(imageData, 0, 0); setTimeout(関数() {drawFrame(ビデオ); }, 10);}ボタンを追加して効果を切り替えることができます。
関数drawFrame(video) { context.drawImage(video, 0, 0); if (applyEffect) { var imageData = context.getImageData(0, 0, Canvas.width, Canvas.height); .putImageData(imageData, 0, 0); } setTimeout(function () {drawFrame(video); }, 10);}カメラを使用するビデオに使用したものと同じコードを保持しますが、唯一の違いは、MediaDevices.getUserMedia を使用してビデオ ストリームをファイルからカメラ ストリームに変更することです。
MediaDevices.getUserMedia は、以前の API MediaDevices.getUserMedia() を廃止する新しい API です。ブラウザーは依然として古いバージョンをサポートしていますが、一部のブラウザーは新しいバージョンをサポートしていないため、ブラウザーがいずれかのバージョンをサポートしていることを確認するにはポリフィルに頼る必要があります。
まず、video 要素から src 属性を削除します。
<video><code></pre><pre><code>// ビデオのソースをカメラに設定します。 streamfunction initCamera(stream) { video.src = window.URL.createObjectURL(stream);}if (navigator .mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({video: true, audio: false}) .then(initCamera) .catch(console.error) );}ライブデモ
効果これまで説明してきたことはすべて、ビデオや画像にさまざまな効果を作成するために必要な基礎です。各色を個別に変換することで、さまざまな効果を使用できます。
グレースケールカラーをグレースケールに変換するには、さまざまな公式/テクニックを使用してさまざまな方法で行うことができます。問題が深くなりすぎないように、GIMP 彩度不飽和化ツールと Luma に基づいた 5 つの公式を紹介します。
グレー = 0.21R + 0.72G + 0.07B // 輝度グレー = (R + G + B) ÷ 3 // 平均輝度グレー = 0.299R + 0.587G + 0.114B // rec601 標準グレー = 0.2126R + 0.7152G + 0.0722B / /ITU-RBT.709 standardGray = 0.2627R + 0.6780G + 0.0593B // ITU-R BT.2100 規格
これらの式を使用して求めたいのは、各ピクセルの色の明るさのレベルです。値の範囲は 0 (黒) ~ 255 (白) です。これらの値は、グレースケール (白黒) 効果を作成します。
これは、最も明るい色が 255 に最も近く、最も暗い色が 0 に最も近いことを意味します。
ライブデモ
ツートーンデュオトーン効果とグレースケール効果の違いは、2 つの色が使用されることです。グレースケールでは黒から白へのグラデーションが発生しますが、デュオトーンでは任意の色から他の色 (青からピンク) へのグラデーションが発生します。
グレースケールの強度値を使用して、それをグラデーション値に置き換えることができます。
ColorA から ColorB へのグラデーションを作成する必要があります。
function createGradient(colorA, colorB) { // colorA から colorB までのグラデーションの値 var gradient = []; // 最大のカラー値は 255 です var maxValue = 255; // 16 進数のカラー値を RGB に変換しますobject var from = getRGBColor(colorA); var to = getRGBColor(colorB); // カラー A からカラー B までの 256 色を作成します (var i = 0; i) <= maxValue; i++) { // 強度 B は 0 から 255 に変化します // 強度 A は 255 から 0 に変化します // 強度 A は強度を減少させますが、強度 B は増加します // これは、ColorA が単色で開始され、徐々に に変化することを意味します。 ColorB // 別の見方をすると、色 A の透明度は増加し、色 B の透明度は減少します。以下の数式は、強度に基づいて 2 つの色を組み合わせます // (IntensityA * ColorA + IntensityB * ColorB) / maxValue gradient[i] = { r: (intensityA*from.r + infectionB*to.r) / maxValue, g: (強度A*from.g + 強度B*to.g) / maxValue, b: (強度A*from.b + 強度B*to.b) / maxValue }; gradient;}// 6 桁の 16 進値を RGB カラーに変換するヘルパー関数 objectfunction getRGBColor(hex){ var colorValue; if (hex[0] === '#') { hex = hex.substr(1); } colorValue = parseInt(hex, 16); return { r: colorValue >> 16, g: (colorValue >> 8) & 255, b: colorValue & 255 }}つまり、色 A から開始して強度を下げながら、色 B に進んで強度を上げながら、一連のカラー値を作成します。
#0096ff から #ff00f0 まで
var gradients = [ {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}、];スケーリングカラートランジションの表現
上は #0096ff から #ff00f0 までの 10 個のカラー値のグラデーションの例です。
色の遷移のグレースケール表現
画像のグレースケール表現ができたので、それを使用してそれをダブルトーン グラデーション値にマッピングできます。
ダブルトーン グラデーションには 256 色があり、グレースケールにも黒 (0) から白 (255) までの範囲の 256 色があります。つまり、グレースケールのカラー値がグラデーション要素のインデックスにマッピングされます。
var gradientColors = createGradient('#0096ff', '#ff00f0');var imageData = context.getImageData(0, 0, Canvas.width, Canvas.height);applyGradient(imageData.data);for (var i = 0; i < data.length; i += 4) { // 各チャネルのカラー値を取得します var redValue = data[i]; data[i+1]; var blueValue = data[i+2]; // カラー値をグラデーション インデックスにマッピング // グレースケール カラー値をダブルトーン グラデーションのカラーに置き換えます data[i] = gradientColors[ redValue] .r; data[i+1] = gradientColors[greenValue].g; data[i+2] = gradientColors[blueValue] = 255;}ライブデモ
結論はこのトピックはさらに詳しく説明されたり、より多くの影響が説明されたりする可能性があります。あなたへの宿題は、これらのスケルトンの例に適用できるさまざまなアルゴリズムを見つけることです。
キャンバス上のピクセルの構造を理解すると、セピア、色の混合、グリーン スクリーン効果、画像のフラッシュ/グリッチなど、無限の効果を作成できるようになります。
画像やビデオを使用せずにその場でエフェクトを作成することもできます
以上がこの記事の全内容です。皆様の学習のお役に立てれば幸いです。また、VeVb Wulin Network をご支援いただければ幸いです。