최신 브라우저는 <video>
요소를 통해 비디오 재생을 지원합니다. 대부분의 브라우저는 MediaDevices.getUserMedia() API를 통해 카메라에 액세스할 수도 있습니다. 하지만 이 두 가지가 결합되더라도 우리는 이 픽셀에 직접 접근하거나 조작할 수 없습니다.
다행히 브라우저에는 JavaScript를 사용하여 그래픽을 그릴 수 있는 Canvas API가 있습니다. 실제로 비디오 자체에서 <canvas>
에 이미지를 그릴 수 있으며, 이를 통해 해당 픽셀을 조작하고 표시할 수 있습니다.
여기에서 픽셀을 조작하는 방법에 대해 배우는 내용은 캔버스뿐만 아니라 모든 종류 또는 소스의 이미지와 비디오 작업을 위한 기초를 제공합니다.
캔버스에 이미지 추가영상을 시작하기 전에 캔버스에 이미지를 추가하는 방법을 살펴보겠습니다.
<img src><div> <canvas id=Canvas 클래스=비디오></canvas></div>
캔버스에 그릴 이미지를 표현하기 위해 이미지 요소를 생성합니다. 또는 JavaScript에서 Image 객체를 사용할 수 있습니다.
var canvas;var context;function init() { var image = document.getElementById('SourceImage'); canvas = document.getElementById('Canvas'); context = canvas.getContext('2d'); // 또는 // var image = new Image(); // image.onload = function () { // drawImage(image) // } // image.src = 'image.jpg';}function drawImage(image) { // 캔버스를 이미지와 동일한 너비와 높이로 설정 canvas.width = image.width; canvas.height = image.drawImage(image, 0, 0);}window.addEventListener('load', init);
위의 코드는 전체 이미지를 캔버스에 그립니다.
CodePen에서 Welling Guzman(@wellingguzman)이 만든 캔버스에 그림 그리기 이미지를 확인하세요.
이제 해당 픽셀을 가지고 놀 수 있습니다!
이미지 데이터 업데이트캔버스의 이미지 데이터를 사용하면 픽셀을 조작하고 변경할 수 있습니다.
데이터 속성은 너비, 높이 및 데이터의 세 가지 속성을 갖는 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]픽셀 데이터 변경
우리가 할 수 있는 가장 빠른 작업 중 하나는 모든 RGBA 값을 255로 변경하여 모든 픽셀을 흰색으로 설정하는 것입니다.
// 버튼을 사용하여 효과를 트리거합니다.varbutton = document.getElementById('Button');button.addEventListener('click', onClick);functionchangeToWhite(data) { for (var i = 0; i < data.length; i++) { 데이터[i] = 255; }}function 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).
Factor = (259 * (대비 + 255)) / (255 * (259 - 대비))color = GetPixelColor(x, y)newRed = Truncate(factor * (Red(color) - 128) + 128)newGreen = Truncate( 인수 * (녹색(색상) - 128) + 128)newBlue = Truncate(계수 * (파란색(색상) - 128) + 128)
주요 계산은 각 색상 값에 적용될 대비 요소를 얻는 것입니다. 잘림은 값이 0에서 255 사이에 유지되도록 하는 기능입니다.
다음 함수를 JavaScript로 작성해 보겠습니다.
function applyBrightness(데이터, 밝기) { for (var i = 0; i < data.length; i+= 4) { data[i] += 255 * (밝기 / 100) data[i+1] += 255 * (밝기 / 100); data[i+2] += 255 * (밝기 / 100) }}function truncateColor(value); (값 < 0) { 값 = 0; } else if (값 > 255) { 값 = 255; } 반환 값;}function applyContrast(데이터, 대비) { var 인자 = (259.0 * (대비 + 255.0)) / ( 255.0 * (259.0 - 대비)); for (var i = 0; i < data.length; i+= 4) { data[i] = truncateColor(인수 * (데이터[i] - 128.0) + 128.0); data[i+1] = truncateColor(인수 * (data[i+1] - 128.0) + 128.0) data[i+2] = truncateColor( 인수 * (데이터[i+2] - 128.0) + 128.0) }}
이 경우 Uint8ClampedArray가 값을 자르기 때문에 truncateColor 함수가 필요하지 않지만 여기에 추가한 알고리즘을 변환하기 위한 것입니다.
한 가지 기억해야 할 점은 밝기나 대비를 적용하면 이미지 데이터가 덮어쓰여 이전 상태로 돌아갈 수 없다는 점입니다. 원래 상태로 재설정하려면 원본 이미지 데이터를 별도로 보관하여 참조해야 합니다. 이미지를 사용하여 캔버스와 원본 이미지를 다시 그릴 수 있으므로 다른 기능에서 이미지 변수에 액세스할 수 있도록 유지하는 것이 도움이 됩니다.
var image = document.getElementById('SourceImage'); function redrawImage() { context.drawImage(image, 0, 0);}비디오 사용
비디오에 적용하기 위해 초기 이미지 스크립트와 HTML 코드를 가져와서 약간 수정하겠습니다.
HTML다음 줄을 교체하여 비디오 요소의 이미지 요소를 변경합니다.
<img 소스>
...이것으로:
<비디오 소스></video>
자바스크립트
다음 줄을 바꾸세요:
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 내에서는 10ms마다 drawFrame을 다시 호출합니다.
function drawFrame(video) { context.drawImage(video, 0, 0); setTimeout(function () { drawFrame(video); }, 10);}
drawFrame을 실행한 후 10ms마다 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 함수에 추가하세요.
function drawFrame(video) { context.drawImage(video, 0, 0); var imageData = context.getImageData(0, 0, canvas.width, canvas.height); context.putImageData(imageData, 0, 0); setTimeout(function () { drawFrame(video); }, 10);}
버튼을 추가하고 효과를 전환할 수 있습니다.
function 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>// 비디오 소스를 카메라 스트림으로 설정function initCamera(stream) { video.src = window.URL.createObjectURL(stream);}if (navigator .mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({비디오: true, 오디오: false}) .then(initCamera) .catch(console.error) );}
라이브 데모
효과지금까지 다룬 모든 내용은 비디오나 이미지에 다양한 효과를 만드는 데 필요한 기초입니다. 각 색상을 독립적으로 변환하여 다양한 효과를 사용할 수 있습니다.
그레이스케일색상을 회색조로 변환하는 것은 다양한 수식/기술을 사용하여 여러 가지 방법으로 수행할 수 있습니다. 문제에 너무 깊이 빠져드는 것을 피하기 위해 김프 채도 감소 도구와 Luma를 기반으로 한 5가지 수식을 보여 드리겠습니다.
회색 = 0.21R + 0.72G + 0.07B // 광도Gray = (R + G + B) ¼ 3 // 평균 밝기Gray = 0.299R + 0.587G + 0.114B // Rec601 표준Gray = 0.2126R + 0.7152G + 0.0722B / /ITU-RBT.709 StandardGray = 0.2627R + 0.6780G + 0.0593B // ITU-R BT.2100 표준
이 공식을 사용하여 우리가 찾고자 하는 것은 각 픽셀 색상의 밝기 수준입니다. 값 범위는 0(검은색)부터 255(흰색)까지입니다. 이 값은 회색조(흑백) 효과를 생성합니다.
즉, 가장 밝은 색상은 255에 가장 가깝고 가장 어두운 색상은 0에 가장 가깝습니다.
라이브 데모
투톤이중톤 효과와 회색조 효과의 차이점은 두 가지 색상이 사용된다는 점입니다. 회색조에는 검은색에서 흰색으로의 그라데이션이 있는 반면, 이중톤에는 모든 색상에서 다른 색상(파란색에서 분홍색으로)으로의 그라데이션이 있습니다.
그레이스케일의 강도 값을 사용하여 이를 그라데이션 값으로 대체할 수 있습니다.
ColorA에서 ColorB로 그라데이션을 만들어야 합니다.
function createGradient(colorA, colorB) { // colorA에서 colorB까지의 그라데이션 값 vargradient = []; // 최대 색상 값은 255입니다. var maxValue = 255 // 16진수 색상 값을 RGB로 변환합니다. object var from = getRGBColor(colorA); var to = getRGBColor(colorB); // 색상 A에서 색상 B까지 256개의 색상을 생성합니다. for (var i = 0; i <= maxValue; i++) { // IntensityB는 0에서 255로 변경됩니다. // IntensityA는 255에서 0으로 변경됩니다. // IntensityA는 강도를 감소시키고 IntensityB는 증가합니다. // 이는 ColorA가 견고하게 시작하여 천천히 ColorB // 다르게 보면 A 색상의 투명도는 증가하고 B 색상의 투명도는 감소합니다. var 강도B = i; var 강도A = maxValue - 강도B; 아래 공식은 강도에 따라 두 색상을 결합합니다. // (IntensityA * ColorA + IntensityB * ColorB) / maxValue 그래디언트[i] = { r: (intensityA*from.r + IntensityB*to.r) / maxValue, g: ( 강도A*from.g + 강도B*to.g) / maxValue, b: (강도A*from.b + 강도B*to.b) / maxValue } } return gradient;}// 6자리 16진수 값을 RGB 색상 객체로 변환하는 도우미 함수function getRGBColor(hex){ var colorValue; if (hex[0] === '#') { hex = hex.substr(1); } colorValue = parsInt(hex, 16); return { r: colorValue >> 16, g: (colorValue >> 8) & 255, b: 색상값 & 255 }}
즉, A 색상부터 시작하여 강도를 낮추고 B 색상으로 이동하여 강도를 높이는 일련의 색상 값을 만듭니다.
#0096ff부터 #ff00f0까지
var 기울기 = [ {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가지 색상이 있습니다. 즉, 회색조 색상 값이 그라데이션 요소 인덱스에 매핑됩니다.
vargradientColors = 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; 데이터[i+1] = 그래디언트Colors[greenValue].g; 데이터[i+2] = 그래디언트Colors[blueValue].b;
라이브 데모
결론적으로이 주제는 더 심층적일 수도 있고 더 많은 의미를 설명할 수도 있습니다. 여러분의 숙제는 이러한 뼈대 예제에 적용할 수 있는 다양한 알고리즘을 찾는 것입니다.
캔버스의 픽셀 구조를 이해하면 세피아, 색상 혼합, 녹색 화면 효과, 이미지 깜박임/글리치 등과 같은 효과를 무제한으로 만들 수 있습니다.
이미지나 비디오를 사용하지 않고도 즉석에서 효과를 만들 수도 있습니다.
위의 내용은 이 기사의 전체 내용입니다. 모든 분들의 학습에 도움이 되기를 바랍니다. 또한 모든 분들이 VeVb Wulin Network를 지지해 주시길 바랍니다.