فن التظليل (Dithering): صور جميلة بِت واحد
كيف تصنع خوارزميات التظليل تدرجات رمادية بالأبيض والأسود فقط، من Floyd-Steinberg إلى الضوضاء الزرقاء، مع أمثلة تطبيقية عملية.

تُعد لعبة Return of the Obra Dinn للمطور Lucas Pope من أكثر الألعاب إثارةً للإعجاب بصريًا، وهي تعتمد على لونين فقط: الأسود والأبيض. بت واحد لكل بكسل. لا رمادي، لا تدرجات، لا anti-aliasing. كل بكسل إما مضاء أو مطفأ. ومع ذلك تنجح اللعبة في عرض عالم ثلاثي الأبعاد كامل، فيه شخصيات يمكن التعرف عليها، وإحساس بالعمق، وإضاءة تخلق أجواءً، وكل ذلك بفضل تقنية تظليل ذكية.
التظليل (Dithering) هو تقنية ترتيب البكسلات السوداء والبيضاء في أنماط تخدع العين فتراها كدرجات وسطى بين الأبيض والأسود. ظهرت هذه التقنية منذ بدايات رسوميات الحاسوب، وهي تشهد اليوم عودة قوية في تطوير الألعاب وتصميم المواقع، وفي مشكلة متزايدة الأهمية وهي عرض الصور على شاشات الحبر الإلكتروني (e-ink). الخوارزميات التي تقف وراءها أنيقة ومتنوعة بشكل مفاجئ، وأكثر فائدة مما قد تتوقع.
لماذا ينجح التظليل
النظام البصري لديك لا يعالج البكسلات كل واحد على حدة، بل يدمج المناطق ككتل. عندما تنظر إلى نمط من بكسلات بيضاء وسوداء متناوبة، لا ترى نقاطًا منفصلة، بل ترى رمادي. المنطقة التي فيها 25% من البكسلات بيضاء تبدو كرمادي بنسبة 25%، والمنطقة التي فيها 75% بيضاء تبدو كرمادي بنسبة 75%. أي أن كثافة البكسلات البيضاء في منطقة ما هي التي تتحكم في السطوع المُدرَك.
هذا هو نفس المبدأ الذي تقوم عليه طباعة الـ halftone في الصحف. تلك النقاط ذات الأحجام والمسافات المختلفة تخلق وهم الدرجات المتصلة من حبر أسود خالص على ورق أبيض. والتظليل الرقمي هو المكافئ لذلك، لكنه يستخدم أنماط بكسلات بدلًا من أحجام النقاط.
التحدي ليس في إنشاء التدرج الرمادي، فهذا أمر بسيط. التحدي هو تحقيقه دون عيوب بصرية: لا تشريط (انتقالات مفاجئة بين الأنماط)، ولا انحيازات اتجاهية (أنماط تُكوّن خطوطًا مرئية)، ولا تأثيرات moiré (أنماط تداخل تنشأ بين التظليل وشبكة الشاشة).
تظليل العتبة: الطريقة الساذجة
أبسط أنواع التظليل: نقارن سطوع كل بكسل بعتبة (عادةً 50%). إن كان أسطع من العتبة يصبح أبيض، وإن كان أغمق يصبح أسود. النتيجة صورة عالية التباين بظلال أشبه بالصورة الظلية، دون أي تدرج على الإطلاق. مفيد للنصوص والرسوم الخطية، لكنه سيئ للصور الفوتوغرافية أو مشاهد ثلاثية الأبعاد.
import numpy as np
from PIL import Image
def threshold_dither(image, threshold=128):
"""Binary threshold — no dithering at all, really."""
gray = np.array(image.convert('L'), dtype=float)
return Image.fromarray((gray > threshold).astype(np.uint8) * 255)
التظليل المرتب: أنماط على شبكة
يستخدم التظليل المرتب مصفوفة عتبات (تُسمى أيضًا مصفوفة Bayer) تُغيّر قيمة العتبة عبر نمط بلاطات متكرر. فبدلًا من مقارنة كل بكسل بالعتبة نفسها، يُقارَن كل بكسل بقيمة مختلفة من المصفوفة بحسب موقعه.
def ordered_dither(image, matrix_size=4):
"""Ordered dithering with Bayer matrix."""
gray = np.array(image.convert('L'), dtype=float)
h, w = gray.shape
# 4×4 Bayer matrix (values 0-15, normalized to 0-255)
bayer_4x4 = np.array([
[ 0, 8, 2, 10],
[12, 4, 14, 6],
[ 3, 11, 1, 9],
[15, 7, 13, 5]
]) * (255 / 16)
# Tile the matrix across the image
threshold = np.tile(bayer_4x4, (h // 4 + 1, w // 4 + 1))[:h, :w]
return Image.fromarray((gray > threshold).astype(np.uint8) * 255)
ينتج التظليل المرتب نمط تقاطع خطوط (crosshatch) مميزًا يسهل التعرف عليه فورًا. وفي الدقات المنخفضة تظهر بنية الشبكة المنتظمة بوضوح، فيمنح الصورة طابعًا آليًا بنكهة retro. وفي كثير من السياقات هذا اختيار جمالي مقصود، إذ يحرص كثير من الألعاب والفنانين على استخدام النمط المرتب لمصفوفة Bayer بحجم 8×8 لما له من شكل مميز.
صُممت مصفوفة Bayer بعناية بحيث يضيف كل مستوى من العتبة بكسلات جديدة في مواضع متباعدة قدر الإمكان عن البكسلات الموجودة. وهذا يقلل التكتل ويعطي أكثر توزيع متساوٍ للنقاط عند كل مستوى سطوع. كما أنه سريع للغاية، إذ يتطلب مقارنة واحدة لكل بكسل دون الاعتماد على الجيران، مما يجعله مناسبًا للعرض في الوقت الفعلي.
نشر الخطأ (Error Diffusion): Floyd-Steinberg وأخواته
يتبع تظليل نشر الخطأ نهجًا مختلفًا تمامًا. فبدلًا من استخدام مصفوفة عتبات ثابتة، يعالج البكسلات بالتسلسل ويوزّع «الخطأ»، أي الفرق بين قيمة البكسل الأصلية والنتيجة المكممة (أبيض أو أسود)، على البكسلات المجاورة التي لم تُعالج بعد.
def floyd_steinberg_dither(image):
"""Floyd-Steinberg error diffusion dithering."""
gray = np.array(image.convert('L'), dtype=float)
h, w = gray.shape
for y in range(h):
for x in range(w):
old_pixel = gray[y, x]
new_pixel = 255.0 if old_pixel > 128 else 0.0
gray[y, x] = new_pixel
error = old_pixel - new_pixel
# Distribute error to neighbors
# Floyd-Steinberg diffusion kernel:
# * 7/16
# 3/16 5/16 1/16
if x + 1 < w:
gray[y, x + 1] += error * 7 / 16
if y + 1 < h:
if x - 1 >= 0:
gray[y + 1, x - 1] += error * 3 / 16
gray[y + 1, x] += error * 5 / 16
if x + 1 < w:
gray[y + 1, x + 1] += error * 1 / 16
return Image.fromarray(gray.astype(np.uint8))
ينتج تظليل Floyd-Steinberg نتائج أكثر طبيعية بكثير من التظليل المرتب. فتوزيع البكسلات لا يحمل أي نمط شبكي مرئي، ويتعامل مع التدرجات الناعمة بشكل رائع. تتجمع النقاط بشكل عضوي، فيقترب الناتج من طباعة الـ halftone الفوتوغرافية.
العيب أنه تسلسلي. فكل بكسل يعتمد على الخطأ الناتج من البكسلات التي سبقته، لذلك لا يمكن توزيع العمل بسهولة على عدة معالجات متوازية. ولهذا فهو أبطأ من التظليل المرتب، وأصعب في التنفيذ داخل خطوط معالجة الـ shaders. بالنسبة للصور المعالجة مسبقًا، هذا لا يهم. أما للعرض ثلاثي الأبعاد في الوقت الفعلي بـ 60 إطارًا في الثانية، فهذه مشكلة حقيقية.
تظليل الضوضاء الزرقاء: أفضل ما في العالمين
تُعد الضوضاء الزرقاء (Blue Noise) حاليًا أحدث ما توصلت إليه التقنية في تطبيقات كثيرة. فهي تستخدم مصفوفة عتبات مثل التظليل المرتب (فتكون سريعة وقابلة للتوازي)، لكن قيم المصفوفة مرتبة بحيث تنتج توزيعًا من نوع الضوضاء الزرقاء. أي أن النقاط موزعة عشوائيًا لكن بحد أدنى للمسافة بينها، فتتجنب التكتل الموجود في الضوضاء البيضاء، وتتجنب النمط الشبكي للتظليل المرتب.
جاء مصطلح «الضوضاء الزرقاء» من معالجة الإشارات. فالضوضاء الزرقاء تتركز معظم طاقتها في الترددات العالية، مثل الضوء الأزرق. وبلغة المكان، هذا يعني أن النمط لا يحمل بنية واسعة النطاق (لا شبكة مرئية ولا انحياز اتجاهي)، لكن النقاط موزعة بتباعد متساوٍ محليًا (لا تكتل ولا فجوات). وهو النمط الذي تحصل عليه لو رميت السهام على لوحة بشرط أن يبعد كل سهم عن الآخر بمسافة دنيا.
توليد مصفوفة ضوضاء زرقاء جيدة صعب بشكل مفاجئ، فهي مسألة تحسين تتطلب التكرار حتى يتطابق الطيف الترددي مع الشكل المطلوب. لكن بمجرد أن تمتلك المصفوفة، يصبح استخدامها بنفس سرعة تظليل Bayer: مقارنة واحدة لكل بكسل.
نهج Obra Dinn: التظليل المُسقَط كرويًا
تظليل Return of the Obra Dinn ذكي بشكل خاص لأنه يحل مشكلة فريدة في الرسوميات ثلاثية الأبعاد: الثبات الزمني (temporal stability). عندما يُطبَّق نمط تظليل قياسي في فضاء الشاشة، فإن تحرك الكاميرا يجعل النمط «يتمايل»، فالنمط يبقى ثابتًا على الشاشة بينما تتحرك الهندسة ثلاثية الأبعاد من تحته. وهذا يخلق وميضًا مزعجًا للغاية.
حل Pope هو ربط نمط التظليل بالفضاء ثلاثي الأبعاد بدلًا من فضاء الشاشة. يُسقَط نمط التظليل على كرة تحيط بالكاميرا، ثم يُؤخذ منه عينات لكل بكسل بحسب اتجاه النظر. عندما تدور الكاميرا، يدور نمط التظليل معها. وعندما تتحرك الأجسام في فضاء العالم، يبقى تظليلها ثابتًا لأنه مشتق من موقعها في العالم، لا من موقعها على الشاشة.
هذه تقنية يمكنك تكييفها لأي تطبيق ثلاثي الأبعاد يستخدم التظليل أو تأثيرات الـ halftone. الفكرة الأساسية: إن أردت الثبات الزمني، فيجب أن تكون قيم العتبات دالة على شيء ثابت (موقع في العالم، أو اتجاه النظر)، لا على شيء يتغير في كل إطار (إحداثيات الشاشة).
تطبيقات عملية تتجاوز الجماليات الretro
التظليل ليس مقتصرًا على الجماليات الرجعية في الألعاب، بل له تطبيقات عملية حقيقية في التطوير الحديث.
- شاشات الحبر الإلكتروني. تمتلك أجهزة القراءة الإلكترونية وشاشات الإعلانات من نوع e-ink قدرة محدودة على عرض الرمادي (عادةً 16 مستوى). والتظليل ضروري لعرض الصور الفوتوغرافية والتدرجات على هذه الأجهزة. واختيار خوارزمية التظليل يؤثر مباشرة في وضوح القراءة وجودة الصورة.
- تقليل حجم الملفات. الصورة المظللة ببت واحد تساوي 1/8 من الحجم الخام لصورة رمادية 8 بت. وبالنسبة لتحميل الصور الزخرفية على الويب، يمكن أن تكون ملفات PNG المظللة أصغر بكثير من بدائلها الرمادية. ويستخدم بعض مصممي المواقع الصور المظللة عمدًا لأسباب جمالية وأدائية معًا.
- تكميم الألوان (Color quantization). عند تقليل صورة ملونة كاملة إلى لوحة ألوان محدودة (مثل GIF أو PNG المفهرس أو الشاشات ذات الألوان المحدودة)، يمنع التظليل التشريط الذي يحدث عندما تُربط التدرجات الناعمة بقيم لونية منفصلة. وهكذا تتعامل مشفرات GIF مع المشكلة باستخدام Floyd-Steinberg في فضاء الألوان.
- تشريط التدرجات في الرسوميات. قد تُظهر أهداف العرض منخفضة الدقة (8 بت لكل قناة) تشريطًا واضحًا في التدرجات الناعمة. وإضافة تظليل في فضاء الشاشة (ولو بمقدار ±0.5 من القيمة) تكسر هذا التشريط دون ضوضاء مرئية. ومعظم محركات الألعاب الحديثة تفعل ذلك تلقائيًا.
- الطباعة الحرارية وإيصالات الطباعة. الطابعات الحرارية أحادية البت بطبيعتها، فكل نقطة إما تُسخَّن (أسود) أو لا. ويحول التظليل الصور والرسوم إلى شكل قابل للطباعة. واختيار الخوارزمية هو ما يحدد إن كانت صورة الإيصال واضحة أم مجرد بقعة.
تطبيق التظليل في الـ Shader
في التطبيقات الفورية، يمكن تنفيذ التظليل المرتب والضوضاء الزرقاء بكفاءة كـ fragment shaders.
// GLSL fragment shader for Bayer 8x8 ordered dithering
precision mediump float;
uniform sampler2D uTexture;
varying vec2 vTexCoord;
// Bayer 8x8 matrix lookup via bit manipulation (no texture needed)
float bayer8x8(vec2 pos) {
ivec2 p = ivec2(mod(pos, 8.0));
int index = p.x + p.y * 8;
// Bit-reversal trick for Bayer matrix values
int value = 0;
int x = p.x ^ p.y;
int y = p.y;
for (int i = 0; i < 3; i++) {
value = value * 4 + (x & 1) * 2 + (y & 1);
x >>= 1;
y >>= 1;
}
return float(value) / 64.0;
}
void main() {
vec4 color = texture2D(uTexture, vTexCoord);
float luminance = dot(color.rgb, vec3(0.299, 0.587, 0.114));
float threshold = bayer8x8(gl_FragCoord.xy);
float dithered = step(threshold, luminance);
gl_FragColor = vec4(vec3(dithered), 1.0);
}
في حالة الضوضاء الزرقاء، عادةً ما تُحسب مسبقًا وتُخزَّن في texture، ثم تُؤخذ منها عينات داخل الـ shader. ويمكن تكرار الـ texture كبلاطات، فتغطي texture بحجم 64×64 أو 128×128 الشاشة كاملة عند تكرارها. تكلفة كل بكسل هي عينة texture واحدة ومقارنة واحدة، وهي شبه مجانية على وحدات معالجة الرسوميات الحديثة.
اختيار الخوارزمية المناسبة
لكل نهج من نهج التظليل حالة استخدام يكون فيها الخيار الأفضل.
- العتبة (Threshold): النصوص والرسوم الخطية والرسوم عالية التباين حيث التفاصيل أهم من المدى اللوني.
- المرتب (Bayer): العرض الفوري حين تريد جمالية retro أو halftone مقصودة. سريع، قابل للتوازي، ومناسب لوحدة معالجة الرسوميات.
- Floyd-Steinberg: معالجة الصور الثابتة حين تكون الجودة هي الأهم. الأفضل للصور الفوتوغرافية والصور ذات التدرجات الناعمة.
- الضوضاء الزرقاء (Blue noise): العرض الفوري حين تريد نتائج طبيعية بلا أنماط مرئية. وهو الخيار الأعم والأفضل للتطبيقات الحديثة.
- الكروي/فضاء العالم (Spherical/world-space): الرسوميات ثلاثية الأبعاد حين يكون الثبات الزمني حاسمًا. ضروري للعرض ثلاثي الأبعاد بالبت الواحد مثل Obra Dinn.
التظليل من تلك المواضيع التي تخدع فكرتها البسيطة، «رتّب النقاط لتبدو كظلال»، عن عمق خيارات التنفيذ. فكل خوارزمية تمثل مقايضة مختلفة بين الجودة والسرعة والطابع البصري. وفي عصر تتزايد فيه أهمية شاشات الحبر الإلكتروني والاتصالات منخفضة النطاق والجماليات الرجعية، صار فهم هذه المقايضات أكثر عملية من أي وقت مضى.


