เบราว์เซอร์สมัยใหม่รองรับการเล่นวิดีโอผ่านองค์ประกอบ <video> เบราว์เซอร์ส่วนใหญ่ยังสามารถเข้าถึงกล้องผ่าน MediaDevices.getUserMedia() API ได้อีกด้วย แต่ถึงแม้ว่าทั้งสองสิ่งนี้จะรวมกัน เราก็ไม่สามารถเข้าถึงและจัดการพิกเซลเหล่านี้ได้โดยตรง
โชคดีที่เบราว์เซอร์มี Canvas API ที่ช่วยให้เราสามารถวาดกราฟิกโดยใช้ JavaScript จริงๆ แล้วเราสามารถวาดภาพลงใน <canvas> จากวิดีโอได้ ซึ่งช่วยให้เราจัดการและแสดงพิกเซลเหล่านั้นได้
สิ่งที่คุณเรียนรู้ที่นี่เกี่ยวกับวิธีจัดการพิกเซลจะช่วยให้คุณมีพื้นฐานในการทำงานกับรูปภาพและวิดีโอทุกประเภทหรือแหล่งที่มา ไม่ใช่แค่ผืนผ้าใบ
เพิ่มรูปภาพลงบนผืนผ้าใบก่อนที่เราจะเริ่มวิดีโอ เรามาดูวิธีเพิ่มรูปภาพลงบนผืนผ้าใบกันก่อน
<img src><div> <canvas id=Canvas class=video></canvas></div>
เราสร้างองค์ประกอบรูปภาพเพื่อแสดงรูปภาพที่จะวาดบนผืนผ้าใบ หรือคุณสามารถใช้วัตถุรูปภาพใน JavaScript
var canvas;var context;function init() { var image = document.getElementById('SourceImage'); canvas = document.getElementById('Canvas'); บริบท = 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.height; 0);}window.addEventListener('โหลด', init);โค้ดด้านบนจะดึงรูปภาพทั้งหมดลงบนผืนผ้าใบ
ดูภาพเพ้นท์บนภาพแคนวาสโดย Welling Guzman (@wellingguzman) บน CodePen
ตอนนี้เราสามารถเริ่มเล่นกับพิกเซลเหล่านั้นได้แล้ว!
อัพเดตข้อมูลภาพข้อมูลภาพบนผืนผ้าใบช่วยให้เราจัดการและเปลี่ยนพิกเซลได้
คุณลักษณะ data คือออบเจ็กต์ ImageData ซึ่งมีคุณสมบัติสามประการ ได้แก่ ความกว้าง ความสูง และข้อมูล ซึ่งทั้งหมดนี้แสดงถึงบางสิ่งบางอย่างตามภาพต้นฉบับ คุณสมบัติทั้งหมดนี้เป็นแบบอ่านอย่างเดียว สิ่งที่เราสนใจคือข้อมูล ซึ่งเป็นอาร์เรย์หนึ่งมิติที่แสดงโดยวัตถุ Uint8ClampedArray ที่มีข้อมูลสำหรับแต่ละพิกเซลในรูปแบบ RGBA
แม้ว่าคุณสมบัติข้อมูลเป็นแบบอ่านอย่างเดียว แต่ก็ไม่ได้หมายความว่าเราไม่สามารถเปลี่ยนค่าของมันได้ ซึ่งหมายความว่าเราไม่สามารถกำหนดอาร์เรย์อื่นให้กับคุณสมบัตินี้ได้
// รับข้อมูลรูปภาพแคนวาส var imageData = context.getImageData(0, 0, canvas.width, canvas.height);image.data = new Uint8ClampedArray(); // WRONGimage.data[1] = 0;
คุณอาจถามว่าวัตถุ Uint8ClampedArray มีค่าเท่าใด นี่คือคำอธิบายจาก MDN:
อาร์เรย์ประเภท Uint8ClampedArray แสดงถึงอาร์เรย์ของจำนวนเต็ม 8 บิตที่ไม่ได้ลงนามซึ่งจะถูกยึดไว้ที่ 0-255 หากคุณระบุค่านอกช่วง [0,255] 0 หรือ 255 จะถูกตั้งค่า หากคุณระบุค่าที่ไม่ใช่จำนวนเต็ม จำนวนเต็มจะถูกตั้งค่า เนื้อหาเริ่มต้นเป็น 0 เมื่อสร้างแล้ว องค์ประกอบในอาร์เรย์สามารถอ้างอิงได้โดยใช้วิธีการของอ็อบเจ็กต์ หรือใช้ไวยากรณ์การจัดทำดัชนีอาร์เรย์มาตรฐาน (เช่น การใช้เครื่องหมายวงเล็บ)
กล่าวโดยสรุป อาร์เรย์นี้จะเก็บค่าตั้งแต่ 0 ถึง 255 ในแต่ละตำแหน่ง ซึ่งทำให้เป็นโซลูชันที่สมบูรณ์แบบสำหรับรูปแบบ RGBA เนื่องจากแต่ละส่วนจะแสดงด้วยค่าตั้งแต่ 0 ถึง 255
สี 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
// ใช้ปุ่มเพื่อทริกเกอร์ปุ่ม effectvar = 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)
ฟังก์ชั่น invertColors(data) { สำหรับ (var i = 0; i < data.length; i+= 4) { data[i] = data[i] ^ 255; // Invert Red data[i+1] = data[i +1] ^ 255; // กลับด้านข้อมูลสีเขียว [i+2] = ข้อมูล [i+2] ^ 255; // กลับด้านสีน้ำเงิน }} ฟังก์ชั่น onClick () { var imageData = context.getImageData(0, 0, canvas.width, canvas.height); invertColors(imageData.data); // อัปเดตแคนวาสด้วยข้อมูลใหม่ context.putImageData(imageData, 0, 0);}เรากำลังเพิ่มการวนซ้ำ 4 แทนที่จะเป็น 1 เหมือนที่เราเคยทำมาก่อน ดังนั้นเราจึงสามารถเติม 4 องค์ประกอบในอาร์เรย์จากพิกเซลหนึ่งไปอีกพิกเซล แต่ละพิกเซล
ค่าอัลฟ่าไม่มีผลกับการกลับสี ดังนั้นเราจึงข้ามไป
ความสว่างและคอนทราสต์ความสว่างของรูปภาพสามารถปรับได้โดยใช้สูตรต่อไปนี้: newValue = currentValue + 255 * (ความสว่าง / 100)
ปัจจัย = (259 * (ความคมชัด + 255)) / (255 * (259 - ความคมชัด))สี = GetPixelColor(x, y)newRed = ตัดทอน (ปัจจัย * (สีแดง (สี) - 128) + 128)newGreen = ตัดทอน ( ปัจจัย * (สีเขียว (สี) - 128) + 128)newBlue = ตัดทอน (ปัจจัย * (สีน้ำเงิน (สี) - 128) + 128)
การคำนวณหลักคือการหาค่าคอนทราสต์ที่จะใช้กับค่าสีแต่ละค่า การตัดทอนเป็นฟังก์ชันที่ทำให้แน่ใจว่าค่าจะอยู่ระหว่าง 0 ถึง 255
มาเขียนฟังก์ชันเหล่านี้ลงใน JavaScript:
ฟังก์ชั่นใช้ความสว่าง (ข้อมูล, ความสว่าง) { สำหรับ (var i = 0; i < data.length; i+= 4) { data[i] += 255 * (ความสว่าง / 100); data[i+1] += 255 * (ความสว่าง / 100); data[i+2] += 255 * (ความสว่าง / 100); }} ฟังก์ชั่น truncateColor(value) { if (value < 0) { value = 0; } else if (value > 255) { value = 255; } คืนค่า;} ฟังก์ชัน ApplyContrast(data, contrast) { var factor = (259.0 * (contrast + 255.0)) / ( 255.0 * (259.0 - ความคมชัด)); สำหรับ (var i = 0; i < data.length; i+= 4) { data[i] = ตัดทอนสี (ปัจจัย * (ข้อมูล [i] - 128.0) + 128.0); ข้อมูล [i+1] = ตัดทอนสี (ปัจจัย * (ข้อมูล [i+1] - 128.0) + 128.0); ข้อมูล [i+2] = ตัดทอนสี ( ปัจจัย * (ข้อมูล [i+2] - 128.0) + 128.0); }}ในกรณีนี้คุณไม่จำเป็นต้องใช้ฟังก์ชัน truncateColor เนื่องจาก Uint8ClampedArray จะตัดทอนค่า แต่เพื่อแปลอัลกอริทึมที่เราเพิ่มเข้าไป
สิ่งหนึ่งที่ควรจำก็คือ หากคุณใช้ความสว่างหรือคอนทราสต์ ข้อมูลภาพจะถูกเขียนทับและไม่สามารถกลับสู่สถานะก่อนหน้าได้ หากเราต้องการรีเซ็ตเป็นสถานะดั้งเดิม ข้อมูลภาพต้นฉบับจะต้องถูกจัดเก็บแยกต่างหากเพื่อใช้อ้างอิง การทำให้ฟังก์ชันอื่นๆ เข้าถึงตัวแปรรูปภาพได้จะมีประโยชน์ เนื่องจากคุณสามารถใช้รูปภาพเพื่อวาดภาพแคนวาสและรูปภาพต้นฉบับใหม่ได้
var image = document.getElementById('SourceImage'); function redrawImage() { context.drawImage(รูปภาพ, 0, 0);} ใช้วิดีโอเพื่อให้ใช้งานได้กับวิดีโอ เราจะใช้สคริปต์รูปภาพเริ่มต้นและโค้ด HTML และทำการแก้ไขเล็กน้อย
HTMLเปลี่ยนองค์ประกอบรูปภาพขององค์ประกอบวิดีโอโดยแทนที่บรรทัดต่อไปนี้:
<img src>
...ด้วยสิ่งนี้:
<วิดีโอ src></วิดีโอ>
จาวาสคริปต์
แทนที่บรรทัดนี้:
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(วิดีโอ);});การเล่นเหตุการณ์จะเล่นอย่างน้อยสองสามเฟรมเมื่อมีข้อมูลเพียงพอที่จะเล่นสื่อ
เราไม่สามารถดูวิดีโอใด ๆ ที่แสดงบนผืนผ้าใบได้เนื่องจากเราแสดงเฉพาะเฟรมแรกเท่านั้น เราต้องดำเนินการ DrawFrame ทุกๆ n มิลลิวินาทีเพื่อให้ทันกับอัตราเฟรมของวิดีโอ
ภายใน DrawFrame เราเรียก DrawFrame อีกครั้งทุกๆ 10ms
ฟังก์ชั่น DrawFrame (วิดีโอ) { context.drawImage (วิดีโอ, 0, 0); setTimeout (ฟังก์ชั่น () { DrawFrame (วิดีโอ); }, 10);}หลังจากดำเนินการ DrawFrame เราจะสร้างลูปที่ดำเนินการ DrawFrame ทุกๆ 10 มิลลิวินาที - มีเวลาเพียงพอสำหรับให้วิดีโอซิงค์ภายในแคนวาส
เพิ่มเอฟเฟกต์ให้กับวิดีโอเราสามารถใช้ฟังก์ชันเดียวกับที่เราสร้างไว้ก่อนหน้านี้เพื่อเปลี่ยนสีได้:
ฟังก์ชั่น invertColors(data) { สำหรับ (var i = 0; i < data.length; i+= 4) { data[i] = data[i] ^ 255; // Invert Red data[i+1] = data[i +1] ^ 255; // กลับด้านข้อมูลสีเขียว [i+2] = ข้อมูล [i+2] ^ 255; // กลับด้านสีน้ำเงิน }}และเพิ่มสิ่งนี้ลงในฟังก์ชัน DrawFrame:
ฟังก์ชั่น DrawFrame (วิดีโอ) { context.drawImage (วิดีโอ, 0, 0); var imageData = context.getImageData (0, 0, canvas.width, canvas.height); context.putImageData (imageData, 0, 0); setTimeout (ฟังก์ชัน () { DrawFrame (วิดีโอ); }, 10);}เราสามารถเพิ่มปุ่มและสลับเอฟเฟกต์ได้:
ฟังก์ชั่น DrawFrame (วิดีโอ) { context.drawImage (วิดีโอ, 0, 0); if (applyEffect) { var imageData = context.getImageData (0, 0, canvas.width, canvas.height); .putImageData(imageData, 0, 0); } setTimeout(ฟังก์ชั่น () { DrawFrame(วิดีโอ); }, 10);} ใช้กล้องเราจะเก็บโค้ดเดียวกันกับที่เราใช้กับวิดีโอ ข้อแตกต่างเพียงอย่างเดียวคือเราจะใช้ MediaDevices.getUserMedia เพื่อเปลี่ยนสตรีมวิดีโอจากไฟล์เป็นสตรีมจากกล้อง
MediaDevices.getUserMedia เป็น API ใหม่ที่เลิกใช้งาน API ก่อนหน้า MediaDevices.getUserMedia() เบราว์เซอร์ยังคงรองรับเวอร์ชันเก่า และเบราว์เซอร์บางตัวไม่รองรับเวอร์ชันใหม่ และเราต้องใช้โพลีฟิลเพื่อให้แน่ใจว่าเบราว์เซอร์รองรับเวอร์ชันใดเวอร์ชันหนึ่ง
ขั้นแรก ให้ลบแอตทริบิวต์ src ออกจากองค์ประกอบวิดีโอ:
<video><code></pre><pre><code>// ตั้งค่าแหล่งที่มาของวิดีโอเป็นฟังก์ชั่นสตรีมของกล้อง initCamera(stream) { video.src = window.URL.createObjectURL(stream);}if (เนวิเกเตอร์ .mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia ({วิดีโอ: จริง, เสียง: false}) . จากนั้น (initCamera) .catch(console.error) );}การสาธิตสด
ผลทุกสิ่งที่เราได้พูดถึงไปแล้วคือรากฐานที่เราต้องการเพื่อสร้างเอฟเฟกต์ต่างๆ สำหรับวิดีโอหรือรูปภาพ เราสามารถใช้เอฟเฟ็กต์ต่างๆ มากมายโดยการแปลงแต่ละสีอย่างอิสระ
ระดับสีเทาการแปลงสีเป็นระดับสีเทาสามารถทำได้หลายวิธีโดยใช้สูตร/เทคนิคที่แตกต่างกัน เพื่อหลีกเลี่ยงปัญหาที่ลึกเกินไป ฉันจะแสดงสูตรห้าสูตรโดยใช้เครื่องมือ GIMP desaturate และ Luma:
สีเทา = 0.21R + 0.72G + 0.07B // LuminosityGray = (R + G + B) 3 // ความสว่างเฉลี่ยสีเทา = 0.299R + 0.587G + 0.114B // rec601 standardGray = 0.2126R + 0.7152G + 0.0722B / /ITU-RBT.709 มาตรฐานสีเทา = 0.2627R + 0.6780G + 0.0593B // ITU-R BT.2100 มาตรฐาน
สิ่งที่เราต้องการค้นหาโดยใช้สูตรเหล่านี้คือระดับความสว่างของแต่ละสีพิกเซล ค่ามีตั้งแต่ 0 (สีดำ) ถึง 255 (สีขาว) ค่าเหล่านี้จะสร้างเอฟเฟกต์ระดับสีเทา (ขาวดำ)
ซึ่งหมายความว่าสีที่สว่างที่สุดจะใกล้เคียงกับ 255 มากที่สุด และสีที่เข้มที่สุดจะอยู่ใกล้กับ 0 มากที่สุด
การสาธิตสด
ทูโทนความแตกต่างระหว่างเอฟเฟ็กต์ดูโอโทนและเอฟเฟ็กต์ระดับสีเทาคือมีการใช้สองสี ในระดับสีเทา คุณจะมีการไล่ระดับสีจากสีดำเป็นสีขาว ในขณะที่ในดูโอโทน คุณจะมีการไล่ระดับสีจากสีใดก็ได้ไปเป็นสีอื่น (จากสีน้ำเงินเป็นสีชมพู)
เมื่อใช้ค่าความเข้มของระดับสีเทา เราสามารถแทนที่มันด้วยค่าการไล่ระดับสีได้
เราจำเป็นต้องสร้างการไล่ระดับสีจาก ColorA ถึง ColorB
ฟังก์ชั่น createGradient(colorA, colorB) { // ค่าของการไล่ระดับสีจาก colorA ถึง colorB var = []; // ค่าสีสูงสุดคือ 255 var maxValue = 255; // แปลงค่าสีฐานสิบหกเป็น RGB object var from = getRGBColor(colorA); var to = getRGBColor(colorB); // สร้าง 256 สีจากสี A ถึงสี B สำหรับ (var i = 0; i <= maxValue; i++) { // IntensityB จะเปลี่ยนจาก 0 ถึง 255 // IntensityA จะเปลี่ยนจาก 255 เป็น 0 // IntensityA จะลดลงในขณะที่ความเข้ม B จะเพิ่มขึ้น // ความหมายคือ 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 }; การไล่ระดับสี;}// ฟังก์ชันตัวช่วยในการแปลงค่าฐานสิบหก 6 หลักเป็นสี 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: ค่าสี & 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 , ก.: 96, ข: 250}, {r: 118, ก: 81, ข: 248}, {r: 145, g: 65, b: 246}, {r: 172, g: 49, b: 245}, {r: 197, g: 34, b: 244}, {r: 220, g: 21 , ข: 242}, {r: 241, ก: 22, ข: 242},];การแสดงการเปลี่ยนสีตามมาตราส่วน
ด้านบนนี้คือตัวอย่างการไล่ระดับสี 10 ค่าสีตั้งแต่ #0096ff ถึง #ff00f0
การแสดงระดับสีเทาของการเปลี่ยนสี
ตอนนี้เรามีการแสดงรูปภาพในระดับสีเทาแล้ว เราสามารถใช้มันเพื่อแมปกับค่าการไล่ระดับสีดูโอโทนได้
การไล่ระดับสีดูโอโทนมี 256 สี ในขณะที่ระดับสีเทาก็มี 256 สีตั้งแต่สีดำ (0) ไปจนถึงสีขาว (255) นั่นหมายความว่าค่าสีระดับสีเทาจะแมปกับดัชนีองค์ประกอบการไล่ระดับสี
var การไล่ระดับสี = 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]; // การแมปค่าสีกับดัชนีการไล่ระดับสี // การแทนที่ค่าสีระดับสีเทาด้วยสีสำหรับข้อมูลการไล่ระดับสีดูโอโทน[i] = การไล่ระดับสี redValue] .r; data[i+1] = การไล่ระดับสี[greenValue].g; data[i+2] = การไล่ระดับสี[blueValue].b;การสาธิตสด
สรุปแล้วหัวข้อนี้อาจมีข้อมูลเชิงลึกมากขึ้นหรืออธิบายความหมายเพิ่มเติมได้ การบ้านสำหรับคุณคือการค้นหาอัลกอริธึมต่างๆ ที่สามารถนำไปใช้กับตัวอย่างโครงกระดูกเหล่านี้ได้
การทำความเข้าใจโครงสร้างของพิกเซลบนผืนผ้าใบจะช่วยให้คุณสามารถสร้างเอฟเฟกต์ได้ไม่จำกัดจำนวน เช่น ซีเปีย การผสมสี เอฟเฟกต์หน้าจอสีเขียว การกะพริบ/ข้อบกพร่องของภาพ ฯลฯ
คุณสามารถสร้างเอฟเฟกต์ได้ทันทีโดยไม่ต้องใช้รูปภาพหรือวิดีโอ
ข้างต้นคือเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการศึกษาของทุกคน ฉันหวังว่าทุกคนจะสนับสนุน VeVb Wulin Network