تدعم المتصفحات الحديثة تشغيل الفيديو عبر العنصر <video> . يمكن لمعظم المتصفحات أيضًا الوصول إلى الكاميرا من خلال واجهة برمجة تطبيقات MediaDevices.getUserMedia(). ولكن حتى لو تم الجمع بين هذين الأمرين، فلا يمكننا الوصول مباشرة إلى هذه البكسلات ومعالجتها.
لحسن الحظ، تحتوي المتصفحات على واجهة برمجة التطبيقات Canvas التي تتيح لنا رسم الرسومات باستخدام 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'); // أو // var image = new Image(); // image.onload = function () { // drawImage(image); // image.src = 'image.jpg';}function drawImage(image) { // اضبط اللوحة القماشية بنفس عرض الصورة وارتفاعها Canvas.width = image.width; 0);}window.addEventListener('load', init);يقوم الكود أعلاه برسم الصورة بأكملها إلى اللوحة القماشية.
تحقق من صورة الطلاء على صورة قماشية بواسطة Welling Guzman (@wellingguzman) على CodePen.
الآن يمكننا أن نبدأ اللعب مع تلك البكسلات!
تحديث بيانات الصورةتسمح لنا بيانات الصورة الموجودة على اللوحة بمعالجة وحدات البكسل وتغييرها.
سمة البيانات هي كائن 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 بكسل × 4 قيم لكل منها).
تم تقليل الصورة بحجم 2x2
ستبدو المصفوفة كما يلي:
// أحمر أخضر أزرق أبيض [255، 0، 0، 255، 0، 255، 0، 255، 0، 0، 255، 255، 255، 255، 255، 255]تغيير بيانات البكسل
أحد أسرع الأشياء التي يمكننا القيام بها هو ضبط جميع وحدات البكسل على اللون الأبيض عن طريق تغيير جميع قيم RGBA إلى 255.
// استخدم زرًا لتشغيل التأثيرvar Button = document.getElementById('Button');button.addEventListener('click', onClick);functionchangeToWhite(data) { for (var i = 0; i < data.length; i++) { data[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; // عكس البيانات الخضراء[i+2] = data[i+2] ^ 255; // عكس اللون الأزرق }}function onClick() { var imageData = context.getImageData(0, 0, Canvas.width, Canvas.height); // تحديث اللوحة القماشية بالبيانات الجديدة context.putImageData(imageData, 0, 0);}نحن نقوم بزيادة الحلقة بمقدار 4 بدلاً من 1 كما فعلنا من قبل، حتى نتمكن من ملء 4 عناصر في المصفوفة من بكسل إلى بكسل، كل بكسل.
قيمة ألفا ليس لها أي تأثير على عكس الألوان، لذلك نقوم بتخطيها.
السطوع والتباينيمكن ضبط سطوع الصورة باستخدام الصيغة التالية: newValue = currentValue + 255 * (Brightness / 100).
العامل = (259 * (التباين + 255)) / (255 * (259 - التباين)) اللون = GetPixelColor(x, y)newRed = Truncate(factor * (Red(color) - 128) + 128)newGreen = Truncate( العامل * (أخضر (لون) - 128) + 128)newBlue = اقتطاع (عامل * (أزرق (لون) - 128) + 128)
الحساب الرئيسي هو الحصول على عامل التباين الذي سيتم تطبيقه على كل قيمة لون. الاقتطاع هو دالة تضمن بقاء القيمة بين 0 و255.
لنكتب هذه الوظائف في JavaScript:
دالة ApplyBrightness(data, Brightness) { for (var i = 0; i < data. length; i+= 4) { data[i] += 255 * (brightness / 100); data[i+1] += 255 * (brightness / 100); data[i+2] += 255 * (brightness / 100); (القيمة < 0) { value = 0 } else if (value > 255) { value = 255 } القيمة المرجعة؛}function ApplyContrast(data, Contrast) { var Factor = (259.0 * (contrast + 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 }}في هذه الحالة لا تحتاج إلى الدالة truncateColor لأن Uint8ClampedArray يقتطع القيم، ولكن لترجمة الخوارزمية التي أضفناها فيها.
شيء واحد يجب تذكره هو أنه إذا قمت بتطبيق السطوع أو التباين، فسيتم الكتابة فوق بيانات الصورة ولا يمكن إعادتها إلى حالتها السابقة. إذا أردنا إعادة التعيين إلى الحالة الأصلية، فيجب تخزين بيانات الصورة الأصلية بشكل منفصل للرجوع إليها. سيكون إبقاء متغير الصورة في متناول الوظائف الأخرى مفيدًا لأنه يمكنك استخدام الصورة لإعادة رسم اللوحة القماشية والصورة الأصلية.
var image = document.getElementById('SourceImage'); function redrawImage() { context.drawImage(image, 0, 0);} استخدم الفيديولجعله مناسبًا للفيديو، سنأخذ نص الصورة الأولي ورمز HTML ونجري بعض التعديلات الطفيفة.
HTMLقم بتغيير عنصر الصورة لعنصر الفيديو عن طريق استبدال السطر التالي:
<img سرك>
...مع هذا:
<فيديو سرك></فيديو>
جافا سكريبت
استبدل هذا السطر:
var image = document.getElementById('SourceImage');... أضف هذا السطر:
فار فيديو = document.getElementById('SourceVideo');لبدء معالجة الفيديو، علينا الانتظار حتى يصبح الفيديو جاهزًا للتشغيل.
video.addEventListener('canplay', function () { // اضبط اللوحة القماشية بنفس عرض وارتفاع الفيديو Canvas.width = video.videoWidth; Canvas.height = video.videoHeight; // تشغيل الفيديو video.play( ); // ابدأ في رسم الإطارات drawFrame(video);});يتم تشغيل الحدث لعدد قليل من الإطارات على الأقل عندما تكون هناك بيانات كافية لتشغيل الوسائط.
لا يمكننا رؤية أي مقطع فيديو معروض على اللوحة القماشية لأننا نعرض الإطار الأول فقط. يتعين علينا تنفيذ drawFrame كل n ميلي ثانية لمواكبة معدل إطارات الفيديو.
داخل drawFrame، نسمي drawFrame مرة أخرى كل 10 مللي ثانية.
وظيفة drawFrame(video) { context.drawImage(video, 0, 0);}بعد تنفيذ drawFrame، نقوم بإنشاء حلقة تنفذ drawFrame كل 10 مللي ثانية - وهو وقت كافٍ ليظل الفيديو متزامنًا داخل اللوحة القماشية.
إضافة تأثيرات إلى الفيديويمكننا استخدام نفس الوظيفة التي أنشأناها سابقًا لعكس الألوان:
function invertColors(data) { for (var i = 0; i < data. length; i+= 4) { data[i] = data[i] ^ 255; // عكس اللون الأحمر data[i+1] = data[i +1] ^ 255; // عكس البيانات الخضراء[i+2] = البيانات[i+2] ^ 255;وأضف هذا إلى وظيفة drawFrame:
وظيفة drawFrame(video) { context.drawImage(video, 0, 0); var imageData = context.getImageData(0, 0, Canvas.width, Canvas.height); 0, 0);يمكننا إضافة زر وتبديل التأثير:
function drawFrame(video) { context.drawImage(video, 0, 0); .putImageData(imageData, 0, 0); استخدام الكاميراسنحتفظ بنفس الكود الذي استخدمناه للفيديو، والفرق الوحيد هو أننا سنستخدم MediaDevices.getUserMedia لتغيير دفق الفيديو من ملف إلى دفق الكاميرا.
MediaDevices.getUserMedia عبارة عن واجهة برمجة تطبيقات جديدة تتجاهل واجهة برمجة التطبيقات السابقة MediaDevices.getUserMedia(). لا تزال المتصفحات تدعم الإصدارات الأقدم، وبعض المتصفحات لا تدعم الإصدارات الأحدث، وعلينا اللجوء إلى polyfills للتأكد من أن المتصفح يدعم أحدها.
أولاً، قم بإزالة سمة src من عنصر الفيديو:
<video><code></pre><pre><code>// اضبط مصدر الفيديو على دفق الكاميراfunction initCamera(stream) { video.src = window.URL.createObjectURL(stream);}if (navigator) .mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({فيديو: صحيح، الصوت: خطأ}) .then(initCamera) .catch(console.error) );}العرض المباشر
تأثيركل ما قمنا بتغطيته حتى الآن هو الأساس الذي نحتاجه لإنشاء تأثيرات مختلفة لمقاطع الفيديو أو الصور. يمكننا استخدام الكثير من التأثيرات المختلفة عن طريق تحويل كل لون بشكل مستقل.
تدرج الرمادييمكن إجراء تحويل الألوان إلى التدرج الرمادي بطرق مختلفة باستخدام صيغ/تقنيات مختلفة، لتجنب التعمق في المشكلة، سأعرض لك خمس صيغ تعتمد على أداة إزالة التشبع GIMP و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 StandardGray = 0.2627R + 0.6780G + 0.0593B // معيار ITU-R BT.2100
ما نريد العثور عليه باستخدام هذه الصيغ هو مستوى السطوع لكل لون بكسل. تتراوح القيمة من 0 (أسود) إلى 255 (أبيض). ستؤدي هذه القيم إلى إنشاء تأثير تدرج رمادي (أبيض وأسود).
وهذا يعني أن اللون الأفتح سيكون الأقرب إلى 255 واللون الأغمق سيكون الأقرب إلى 0.
العرض المباشر
نغمتينالفرق بين تأثير اللون الثنائي وتأثير التدرج الرمادي هو أنه يتم استخدام لونين. في التدرج الرمادي لديك تدرج من الأسود إلى الأبيض، بينما في اللون الثنائي لديك تدرج من أي لون إلى أي لون آخر (من الأزرق إلى الوردي).
باستخدام قيمة كثافة التدرج الرمادي، يمكننا استبدالها بقيمة التدرج.
نحتاج إلى إنشاء تدرج من ColorA إلى ColorB.
function createGradient(colorA, colorB) { // قيم التدرج من colorA إلى colorB var gradient = []; // الحد الأقصى لقيمة اللون هو 255 var maxValue = 255; // تحويل قيم الألوان السداسية إلى RGB الكائن var from = getRGBColor(colorA); var to = getRGBColor(colorB); // إنشاء 256 لونًا من اللون A إلى اللون B for (var i = 0; i <= maxValue; i++) { // ستنتقل IntensityB من 0 إلى 255 // ستنتقل IntensityA من 255 إلى 0 // ستقلل IntensityA من الشدة بينما ستزيد الشدة B // ما يعنيه هذا هو أن ColorA سيبدأ ثابتًا ويتحول ببطء إلى ColorB // إذا نظرت إليها بطريقة أخرى، فستزيد شفافية اللون A وستنخفض شفافية اللون B varكثافةB = i; تجمع الصيغة أدناه بين اللونين بناءً على كثافتهما // (IntensityA * ColorA + IntensityB * ColorB) / maxValue gradient[i] = { r: (intensityA*from.r + IntensityB*to.r) / maxValue, g: ( كثافةA*from.g +كثافةB*to.g) / maxValue, b: (intensityA*from.b +كثافةB*to.b) / maxValue }; gradient;}// وظيفة مساعدة لتحويل القيم السداسية المكونة من 6 أرقام إلى كائن لون RGB function 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
تدرجات فار = [ {r: 32، g: 144، b: 254}، {r: 41، g: 125، b: 253}، {r: 65، g: 112، b: 251}، {r: 91 ، ز: 96، ب: 250}، {ر: 118، ز: 81، ب: 248}، {ص: 145، ز: 65، ب: 246}، {ص: 172، ز: 49، ب: 245}، {ص: 197، ز: 34، ب: 244}، {ص: 220، ز: 21} ، ب: 242}، {ر: 241، ز: 22، ب: 242}،]؛تمثيل تحجيم التحولات اللونية
أعلاه مثال على التدرج اللوني لقيم 10 ألوان من #0096ff إلى #ff00f0.
تمثيل التدرج الرمادي للتحولات اللونية
الآن بعد أن أصبح لدينا تمثيل بتدرج رمادي للصورة، يمكننا استخدامه لتعيينه إلى قيمة التدرج اللوني الثنائي.
يحتوي التدرج الثنائي على 256 لونًا بينما يحتوي التدرج الرمادي أيضًا على 256 لونًا تتراوح من الأسود (0) إلى الأبيض (255). وهذا يعني أن قيمة اللون الرمادي سيتم تعيينها إلى فهرس عنصر التدرج.
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].b;العرض المباشر
ختاماًيمكن أن يكون هذا الموضوع أكثر تعمقًا أو يشرح المزيد من الآثار. الواجب المنزلي بالنسبة لك هو العثور على خوارزميات مختلفة يمكن تطبيقها على هذه الأمثلة الهيكلية.
سيسمح لك فهم بنية وحدات البكسل الموجودة على اللوحة القماشية بإنشاء عدد غير محدود من التأثيرات مثل البني الداكن، ومزج الألوان، وتأثيرات الشاشة الخضراء، ومضات/مواطن الخلل في الصورة، وما إلى ذلك.
يمكنك أيضًا إنشاء تأثيرات سريعة دون استخدام الصور أو مقاطع الفيديو
ما ورد أعلاه هو المحتوى الكامل لهذه المقالة وآمل أن يكون مفيدًا لدراسة الجميع وآمل أيضًا أن يدعم الجميع شبكة VeVb Wulin.