डिथरिंग की कला: एक बिट में खूबसूरत इमेज
Floyd-Steinberg से ब्लू नॉइज़ तक, डिथरिंग एल्गोरिदम कैसे सिर्फ काले और सफेद पिक्सल से शेडिंग बनाते हैं, व्यावहारिक उदाहरणों के साथ।

लुकास पोप का गेम Return of the Obra Dinn अब तक बने सबसे आकर्षक दिखने वाले गेम्स में से एक है, और इसमें ठीक दो रंग इस्तेमाल होते हैं: काला और सफेद। हर पिक्सल में एक बिट। न कोई ग्रे, न ग्रेडिएंट, न एंटी-एलियासिंग। हर पिक्सल या तो चालू है या बंद। फिर भी यह गेम पहचाने जा सकने वाले किरदारों, गहराई के एहसास और माहौल वाली रोशनी के साथ पूरी 3D दुनिया दिखा देता है, और यह सब होशियार डिथरिंग के दम पर होता है।
डिथरिंग वह तकनीक है जिसमें काले और सफेद पिक्सल को ऐसे पैटर्न में सजाया जाता है कि आपकी आँखों को बीच के शेड दिखने लगें। कंप्यूटर ग्राफ़िक्स के शुरुआती दिनों से यह तकनीक चली आ रही है, और आज फिर से इसका दौर लौट रहा है: गेम डेवलपमेंट में, वेब डिज़ाइन में, और ई-इंक स्क्रीन पर तस्वीरें दिखाने की बढ़ती ज़रूरत में। इसके पीछे के एल्गोरिदम सुंदर हैं, काफ़ी अलग-अलग तरह के हैं, और आप सोचेंगे उससे कहीं ज़्यादा काम के हैं।
डिथरिंग कैसे काम करती है
आपका विज़ुअल सिस्टम अलग-अलग पिक्सल को प्रोसेस नहीं करता, बल्कि इलाकों को एक साथ जोड़कर देखता है। जब आप काले और सफेद पिक्सल के बारी-बारी वाले पैटर्न को देखते हैं, तो आपको अलग-अलग बिंदु नहीं दिखते, आपको ग्रे दिखता है। जिस इलाके में 25% पिक्सल सफेद हैं, वह 25% ग्रे जैसा दिखेगा। जिसमें 75% सफेद हैं, वह 75% ग्रे जैसा। किसी क्षेत्र में सफेद पिक्सल का घनत्व ही तय करता है कि वह कितना चमकीला दिखेगा।
यही सिद्धांत अख़बारों की हाफ़टोन प्रिंटिंग के पीछे है। अलग-अलग आकार और दूरी वाले ये बिंदु सफेद कागज़ पर सिर्फ़ काली स्याही से भी लगातार टोन का भ्रम पैदा कर देते हैं। डिथरिंग इसका डिजिटल रूप है, जिसमें बिंदुओं के आकार की जगह पिक्सल के पैटर्न इस्तेमाल होते हैं।
असली चुनौती ग्रेस्केल बनाना नहीं है, वह तो सीधा काम है। असली चुनौती यह है कि यह बिना दिखने वाली गड़बड़ियों के हो: न बैंडिंग (पैटर्न के बीच अचानक बदलाव), न दिशात्मक पूर्वाग्रह (ऐसे पैटर्न जिनमें साफ़ लकीरें दिखें), और न मोइरे इफ़ेक्ट (डिथरिंग और डिस्प्ले ग्रिड के बीच बनने वाले इंटरफ़ेरेंस पैटर्न)।
थ्रेशोल्ड डिथरिंग: सीधा और सरल तरीका
सबसे सरल डिथरिंग: हर पिक्सल की चमक की तुलना एक थ्रेशोल्ड (आमतौर पर 50%) से की जाती है। थ्रेशोल्ड से ज़्यादा चमकदार हो तो सफेद, कम हो तो काला। इससे हाई-कॉन्ट्रास्ट वाली सिल्हूट बनती है, जिसमें ग्रेडेशन बिल्कुल नहीं होता। टेक्स्ट और लाइन आर्ट के लिए ठीक है, पर फ़ोटो या 3D रेंडरिंग के लिए बेकार।
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 matrix भी कहते हैं) का इस्तेमाल करती है, जो एक दोहराए जाने वाले टाइल पैटर्न में थ्रेशोल्ड की वैल्यू बदलता रहता है। हर पिक्सल को एक ही थ्रेशोल्ड से तोलने के बजाय, उसकी पोज़ीशन के हिसाब से मैट्रिक्स की अलग वैल्यू से तुलना की जाती है।
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)
ऑर्डर्ड डिथरिंग एक अलग तरह का क्रॉसहैच पैटर्न बनाती है, जो फ़ौरन पहचान में आ जाता है। कम रिज़ॉल्यूशन पर ग्रिड की नियमित बनावट साफ़ दिखती है, और इमेज को एक मशीनी, रेट्रो लुक मिलता है। कई संदर्भों में यह जानबूझकर चुना गया सौंदर्यबोध है, क्योंकि 8×8 Bayer matrix का पैटर्न इतना अलग दिखता है कि कई गेम और कलाकार उसे जानबूझकर इस्तेमाल करते हैं।
Bayer matrix ऐसे बनाया गया है कि हर थ्रेशोल्ड लेवल पर नए पिक्सल उन जगहों पर जुड़ें जो पहले से मौजूद पिक्सल से अधिकतम दूरी पर हों। इससे क्लंपिंग कम होती है और हर चमक के स्तर पर बिंदुओं का सबसे समान वितरण मिलता है। यह बेहद तेज़ भी है: हर पिक्सल पर सिर्फ़ एक तुलना, और किसी पड़ोसी पर निर्भरता नहीं। इसी वजह से यह रीयल-टाइम रेंडरिंग के लिए ठीक बैठता है।
एरर डिफ्यूज़न: 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 डिथरिंग ऑर्डर्ड डिथरिंग से कहीं ज़्यादा प्राकृतिक दिखने वाले नतीजे देती है। पिक्सल के वितरण में कोई साफ़ ग्रिड पैटर्न नहीं दिखता, और स्मूद ग्रेडिएंट को यह खूबसूरती से संभालती है। बिंदु जैविक तरीके से गुच्छों में बैठते हैं, जिससे नतीजा फ़ोटोग्राफ़िक हाफ़टोन प्रिंटिंग के ज़्यादा करीब लगता है।
नुकसान यह है कि यह क्रमिक (sequential) है। हर पिक्सल पहले प्रोसेस हो चुके पिक्सल के एरर पर निर्भर करता है, इसलिए इसे आसानी से पैरेलल नहीं चलाया जा सकता। इसी वजह से यह ऑर्डर्ड डिथरिंग से धीमा है और shader-आधारित रेंडरिंग पाइपलाइन में लागू करना मुश्किल है। पहले से प्रोसेस की गई इमेज के लिए यह कोई समस्या नहीं है। लेकिन 60fps पर रीयल-टाइम 3D रेंडरिंग के लिए यह दिक़्क़त है।
ब्लू नॉइज़ डिथरिंग: दोनों की अच्छाइयाँ एक साथ
ब्लू नॉइज़ डिथरिंग कई क्षेत्रों में मौजूदा सबसे उन्नत तरीका है। यह ऑर्डर्ड डिथरिंग की तरह एक थ्रेशोल्ड मैट्रिक्स इस्तेमाल करती है (इसलिए यह तेज़ है और पैरेलल चल सकती है), लेकिन मैट्रिक्स की वैल्यूज़ इस तरह व्यवस्थित होती हैं कि ब्लू नॉइज़ जैसा वितरण बने। यानी बिंदु रैंडम जगहों पर होते हैं, पर न्यूनतम दूरी की शर्त के साथ। इससे व्हाइट नॉइज़ का क्लंपिंग भी बचता है और ऑर्डर्ड डिथरिंग का ग्रिड पैटर्न भी।
'ब्लू नॉइज़' शब्द सिग्नल प्रोसेसिंग से आया है। ब्लू नॉइज़ की ज़्यादातर ऊर्जा हाई फ़्रीक्वेंसी पर होती है, जैसे नीली रोशनी। स्थानिक रूप से इसका मतलब है कि पैटर्न में कोई बड़े स्तर की बनावट नहीं होती (कोई ग्रिड नहीं, कोई दिशात्मक झुकाव नहीं), लेकिन स्थानीय स्तर पर बिंदु समान दूरी पर होते हैं (न गुच्छे, न खाली जगहें)। यह वैसा पैटर्न है जैसे आप एक बोर्ड पर डार्ट फेंकें, पर शर्त यह हो कि हर डार्ट बाकी सबसे कम से कम एक न्यूनतम दूरी पर गिरे।
अच्छा ब्लू नॉइज़ थ्रेशोल्ड मैट्रिक्स बनाना हैरानी की हद तक मुश्किल है। यह एक ऑप्टिमाइज़ेशन समस्या है, जिसमें फ़्रीक्वेंसी स्पेक्ट्रम के मनचाहे प्रोफ़ाइल से मिलने तक बार-बार सुधार करना पड़ता है। लेकिन एक बार मैट्रिक्स तैयार हो जाए, तो उसका इस्तेमाल Bayer डिथरिंग जितना ही तेज़ है: हर पिक्सल पर सिर्फ़ एक तुलना।
Obra Dinn का तरीका: गोलाकार मैप्ड डिथरिंग
Return of the Obra Dinn की डिथरिंग इसलिए खास है क्योंकि यह 3D रेंडरिंग की एक ऐसी समस्या हल करती है जो सिर्फ़ इसी में आती है: टेम्पोरल स्टेबिलिटी (समय के साथ स्थिरता)। जब कोई स्टैंडर्ड डिथरिंग पैटर्न स्क्रीन स्पेस में लगाया जाता है, तो कैमरा हिलाने पर पैटर्न 'तैरने' लगता है। पैटर्न स्क्रीन से चिपका रहता है, जबकि 3D ज्योमेट्री उसके नीचे खिसकती है। इससे टिमटिमाहट पैदा होती है जो बेहद ध्यान भटकाने वाली होती है।
पोप का हल: डिथरिंग पैटर्न को स्क्रीन के बजाय 3D स्पेस में मैप करना। पैटर्न को कैमरे के चारों ओर के एक गोले पर प्रोजेक्ट किया जाता है, और हर पिक्सल के लिए व्यू डायरेक्शन के आधार पर उसका सैंपल लिया जाता है। जब कैमरा घूमता है, तो डिथरिंग पैटर्न उसके साथ घूमता है। जब ऑब्जेक्ट वर्ल्ड स्पेस में चलते हैं, तो उनकी डिथरिंग स्थिर रहती है, क्योंकि वह उनकी स्क्रीन पोज़ीशन से नहीं, बल्कि वर्ल्ड पोज़ीशन से तय होती है।
यह ऐसी तकनीक है जिसे आप किसी भी 3D एप्लिकेशन में अपना सकते हैं जो डिथरिंग या हाफ़टोन इफ़ेक्ट इस्तेमाल करता है। मुख्य बात यह है: अगर आपको टेम्पोरल स्टेबिलिटी चाहिए, तो आपकी थ्रेशोल्ड वैल्यूज़ किसी स्थिर चीज़ का फ़ंक्शन होनी चाहिए (वर्ल्ड पोज़ीशन, व्यू डायरेक्शन), न कि किसी ऐसी चीज़ का जो हर फ़्रेम में बदलती है, जैसे स्क्रीन कोऑर्डिनेट।
रेट्रो सौंदर्य से आगे व्यावहारिक उपयोग
डिथरिंग सिर्फ़ रेट्रो गेम्स के लुक के लिए नहीं है। आधुनिक डेवलपमेंट में इसके असली व्यावहारिक उपयोग हैं।
- ई-इंक डिस्प्ले। ई-रीडर और ई-इंक साइनेज़ में सीमित ग्रेस्केल क्षमता होती है (आमतौर पर 16 स्तर)। इन उपकरणों पर फ़ोटो और ग्रेडिएंट दिखाने के लिए डिथरिंग ज़रूरी है। कौन सा डिथरिंग एल्गोरिदम चुना गया, इससे सीधे पढ़ने की सुविधा और इमेज की गुणवत्ता प्रभावित होती है।
- फ़ाइल साइज़ घटाना। 1-बिट डिथर्ड इमेज, 8-बिट ग्रेस्केल इमेज के रॉ साइज़ का 1/8 होती है। वेब पर सजावटी इमेज के लिए डिथर्ड PNG, ग्रेस्केल विकल्पों से कहीं छोटी हो सकती हैं। कुछ वेब डिज़ाइनर सौंदर्य और परफ़ॉर्मेंस, दोनों कारणों से जानबूझकर डिथर्ड इमेज इस्तेमाल करते हैं।
- कलर क्वांटाइज़ेशन। जब पूरे रंग वाली इमेज को सीमित पैलेट (GIF, indexed PNG, या पैलेट-सीमित डिस्प्ले) में बदला जाता है, तो डिथरिंग उस बैंडिंग को रोकती है जो स्मूद ग्रेडिएंट को अलग-अलग रंग वैल्यूज़ में मैप करने पर आती है। GIF एन्कोडर इसी तरह Floyd-Steinberg डिथरिंग को कलर स्पेस में लागू करते हैं।
- रेंडरिंग में ग्रेडिएंट बैंडिंग। कम प्रिसिज़न वाले रेंडर टार्गेट (हर चैनल 8-बिट) स्मूद ग्रेडिएंट में साफ़ बैंडिंग पैदा कर सकते हैं। स्क्रीन स्पेस डिथरिंग जोड़ने पर (वैल्यू में बस ±0.5 का बदलाव), बैंडिंग टूट जाती है और दिखने वाला शोर नहीं आता। ज़्यादातर आधुनिक गेम इंजन यह अपने आप करते हैं।
- थर्मल और रसीद प्रिंटिंग। थर्मल प्रिंटर स्वभाव से 1-बिट होते हैं। हर डॉट या तो गर्म होकर काला बनता है, या नहीं बनता। डिथरिंग फ़ोटो और ग्राफ़िक्स को प्रिंट होने लायक रूप में बदलती है। कौन सा एल्गोरिदम चुना गया, यह तय करता है कि आपकी रसीद की इमेज पहचानी जा सकेगी या बस एक धब्बा बनकर रह जाएगी।
शेडर में डिथरिंग लागू करना
रीयल-टाइम एप्लिकेशन के लिए ऑर्डर्ड डिथरिंग और ब्लू नॉइज़ डिथरिंग, दोनों को 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);
}
ब्लू नॉइज़ के लिए आम तौर पर नॉइज़ टेक्सचर पहले से तैयार करके shader में सैंपल किया जाता है। यह टेक्सचर टाइल किया जा सकता है, इसलिए 64×64 या 128×128 का ब्लू नॉइज़ टेक्सचर दोहराने पर पूरी स्क्रीन को कवर कर लेता है। हर पिक्सल की लागत एक टेक्सचर सैंपल और एक तुलना है, जो आधुनिक GPU पर लगभग मुफ़्त जैसी है।
सही एल्गोरिदम चुनना
हर डिथरिंग तरीके का एक उपयोग-क्षेत्र है जहाँ वह सबसे बेहतर विकल्प बनता है।
- थ्रेशोल्ड: टेक्स्ट, लाइन आर्ट और हाई-कॉन्ट्रास्ट ग्राफ़िक्स, जहाँ डिटेल टोनल रेंज से ज़्यादा ज़रूरी हो।
- ऑर्डर्ड (Bayer): रीयल-टाइम रेंडरिंग, जहाँ जानबूझकर रेट्रो/हाफ़टोन लुक चाहिए। तेज़, पैरेलल चलने योग्य, GPU के लिए अनुकूल।
- Floyd-Steinberg: स्टैटिक इमेज प्रोसेसिंग, जहाँ क्वालिटी सबसे ज़रूरी है। फ़ोटो और स्मूद ग्रेडिएंट वाली इमेज के लिए सबसे बढ़िया।
- ब्लू नॉइज़: रीयल-टाइम रेंडरिंग, जहाँ दिखने वाले पैटर्न के बिना प्राकृतिक नतीजे चाहिए। आधुनिक एप्लिकेशन के लिए सबसे अच्छा सामान्य-उद्देश्य विकल्प।
- गोलाकार/वर्ल्ड-स्पेस: 3D रेंडरिंग, जहाँ टेम्पोरल स्टेबिलिटी ज़रूरी हो। Obra Dinn जैसे 1-बिट 3D रेंडरिंग के लिए अनिवार्य।
डिथरिंग उन विषयों में से है जहाँ 'डॉट्स को शेड जैसा दिखाने के लिए सजाना' जैसी सरल अवधारणा के पीछे इम्प्लीमेंटेशन के चुनावों की गहराई छिपी है। हर एल्गोरिदम क्वालिटी, गति और दृश्य चरित्र के बीच अलग समझौता दर्शाता है। और ऐसे दौर में जहाँ ई-इंक डिस्प्ले, कम बैंडविड्थ वाले कनेक्शन और रेट्रो सौंदर्य, तीनों प्रासंगिक हैं, इन समझौतों को समझना पहले से कहीं ज़्यादा व्यावहारिक है।


