निम्नलिखित कोड पर विचार करें:

public static Bitmap Create3x3Bitmap(PixelFormat pixelFormat)
{
    var bmp = new Bitmap(3, 3, pixelFormat);

    // I know SetPixel does not perform well, this is strictly for learning purposes

    bmp.SetPixel(0, 0, Color.Red);
    bmp.SetPixel(1, 0, Color.Lime);
    bmp.SetPixel(2, 0, Color.Blue);
    bmp.SetPixel(0, 1, Color.White);
    bmp.SetPixel(1, 1, Color.Gray);
    bmp.SetPixel(2, 1, Color.Black);
    bmp.SetPixel(0, 2, Color.Cyan);
    bmp.SetPixel(1, 2, Color.Fuchsia);
    bmp.SetPixel(2, 2, Color.Yellow);

    return bmp;
}

ऊपर दिए गए कोड को 3 x 3 Bitmap उदाहरण देना चाहिए, जिसे अगर बड़ा किया जाए, तो यह इस तरह दिखना चाहिए:

3 x 3 bitmap

मुझे लगा कि मैं Bitmap.LockBits विधि का उपयोग करके बिटमैप की पिक्सेल जानकारी को "स्कैन" कर सकता हूं। मैं 24 और 32 बिट्स आधारित PixelFormat मानों को pixelFormat तर्क के रूप में उपयोग करने में सफल रहा।

हालांकि, मुझे अभी तक यह समझ में नहीं आया है कि जब इसके बजाय PixelFormat.Format48bppRgb का उपयोग किया जाता है तो पिक्सेल सूचनाओं को कैसे निकाला जाए:

public void Test()
{
    using (var bmp = Create3x3Bitmap(PixelFormat.Format48bppRgb))
    {
        var lockRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        var data = bmp.LockBits(lockRect, ImageLockMode.ReadWrite, bmp.PixelFormat);
        var absStride = Math.Abs(data.Stride); // will be equal to 20
        var size = absStride * data.Height; // will be equal to 60

        byte[] scanData = new byte[size];

        Marshal.Copy(data.Scan0, scanData, 0, size);

        // ...
        // more stuff here, irrelevant for the actual question
        // ...

        bmp.UnlockBits(bitmapData);
    }
}

अगर मैं डीबगर का उपयोग करके उपरोक्त कोड चलाता हूं और Marshal.Copy पर कॉल के ठीक बाद ब्रेक करता हूं, तो मैं देख सकता हूं कि scanData बाइट सरणी में 60 बाइट्स हैं। मेरी समझ यह है कि तीन आरजीबी चैनलों में से प्रत्येक के लिए दो बाइट्स की आवश्यकता होती है। इसका मतलब प्रति पिक्सेल 6 बाइट्स है। वाई-अक्ष पर प्रत्येक "पंक्ति" के लिए दो अतिरिक्त अप्रयुक्त बाइट्स भी हैं, जो इस मामले में 3 पंक्तियां हैं।

तो अगर मुझे यह अधिकार मिल रहा है, तो मुझे पहली पंक्ति के लिए सरणी की सामग्री की व्याख्या कैसे करनी चाहिए:

array content

अब जो मुझे भ्रमित करता है वह यह है कि मुझे प्रत्येक चैनल के बाइट्स की जोड़ी की व्याख्या कैसे करनी चाहिए और इसे वापस मूल रंग में अनुवाद करना चाहिए। यह समझ में आता है कि पहले पिक्सेल (जो लाल है) के लिए, 0s की एक जोड़ी नीले और हरे चैनल दोनों के लिए मिलेगी। मैं इतना निश्चित नहीं था कि 0 और 32 को एक जोड़ी के रूप में क्या बनाया जाए जिसका अर्थ "फुल-ऑन रेड" होना चाहिए, लेकिन नेट के चारों ओर देखने पर मुझे समझ में आया कि यह सीमा 0 से 65535 के बजाय 0 से 8192 है, < a href="https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.pixelformat#remarks" rel="nofollow noreferrer">GDI+ की सीमा के कारण।

और निश्चित रूप से, BitConverter.ToUInt16 का उपयोग करके मुझे बाइट्स की उस जोड़ी के लिए 8192 का मान मिला। तो लाल पिक्सेल के लिए परिणाम समझ में आता है।

हालांकि, यह ग्रे पिक्सेल के प्रत्येक चैनल पर पाए जाने वाले "232, 6" जोड़े के लिए 1768 भी देता है (उपरोक्त छवि पर इंडेक्स 26 से 31)। और वह जहां मैं भ्रमित हूं। चूंकि ग्रे रंग के 8 बिट चैनल 0 से 255 के बीच में होते हैं, इसलिए मुझे 4095 या 4096 की उम्मीद होती, जो कि 0 से 8192 के बीच में होता है। ‍♂️

क्या चैनलों का प्रतिनिधित्व करने वाले बाइट जोड़े पर मेरी समझ भी सही है? यदि ऐसा है तो ग्रे रंग के लिए मुझे ये चैनल मान कैसे मिले?

3
Crono 19 जुलाई 2021, 23:55

2 जवाब

सबसे बढ़िया उत्तर

माइनर नाइट: आपने यह लिखा है:

वाई-अक्ष पर प्रत्येक "पंक्ति" के लिए दो अतिरिक्त खाली पिक्सेल भी हैं, जो इस मामले में 3 पंक्तियां हैं।

प्रत्येक पंक्ति के लिए दो अतिरिक्त बाइट्स हैं, जिससे स्ट्राइड को 18 बाइट्स के बजाय 20 बाइट्स बनाते हैं जो तीन 6-बाइट पिक्सेल मानों के लिए आवश्यक होंगे। यह GDI+ आवश्यकता को पूरा करता है कि बिटमैप स्ट्राइड 4 का गुणक हो।

जहां तक ​​सवाल खुद जाता है...

जैसा कि एक टिप्पणी में बताया गया है, प्रश्नोत्तर RGB मान PixelFormat.Format48bppRgb छवि केवल 0 से 255 तक क्यों हैं? होना चाहिए आपके लिए मददगार। वास्तव में, इस कथन में आपकी पहेली का कम से कम आधा उत्तर है:

GDI+ रंग चैनल में केवल 0 से 8192 तक के मानों की अनुमति देता है।

इसका मतलब यह है कि जब आप एक पिक्सेल को, उदाहरण के लिए, लाल पर सेट करते हैं, जिसका 24-बिट RGB मान (255,0,0) है, तो इसे (8192,0,0) का मान बनाकर 48-बिट्स तक बढ़ाया जाता है। ) यह (६५५३५,०,०) से अलग है, जिसकी अपेक्षा प्रति पिक्सेल १६ बिट के साथ की जा सकती है।

दूसरा हिस्सा यह है कि पिक्सेल चैनल दो-बाइट, छोटे-एंडियन, 16-बिट मानों के रूप में संग्रहीत होते हैं। जब आप एक बाइट 0 (0x00), उसके बाद एक बाइट 32 (0x20) देखते हैं, तो इसकी व्याख्या करने का तरीका यह है कि पहले बाइट को ushort वेरिएबल में स्टोर किया जाए, फिर शिफ्ट करें दूसरी बाइट 8 बिट्स द्वारा छोड़ी जाती है और उसी चर में पहले बाइट के साथ गठबंधन करती है। उदा.:

byte[] redChannelBytes = { 0x00, 0x20 };
ushort redChannel = redChannelBytes[0] | (redChannelBytes[1] << 8);

या आप बस < पर कॉल कर सकते हैं code>BitConverter.ToUInt16(byte[], int)

byte[] redChannelBytes = { 0x00, 0x20 };
ushort redChannel = BitConverter.ToUInt16(redChannelBytes, 0);
3
Peter Duniho 19 जुलाई 2021, 22:58

शायद मुझे थोड़ी देर हो गई है लेकिन मुझे लगता है कि स्वीकृत उत्तर में कुछ उपयोगी जानकारी जोड़ें।

चूंकि ग्रे रंग के 8 बिट चैनल 0 से 255 की सीमा के बीच में हैं, मुझे 4095 या 4096 की तरह कुछ की उम्मीद होगी, जो कि 0 से 8192 के बीच में है।

ऐसा इसलिए है क्योंकि 'साधारण' और 'विस्तृत' पिक्सेल स्वरूपों के बीच परिवर्तन रैखिक नहीं है। नियमित पिक्सेल प्रारूप (और Color संरचना भी) गामा सुधार = 2.2 के साथ रंगों का प्रतिनिधित्व करते हैं, जबकि विस्तृत पिक्सेल प्रारूपों में कोई गामा सुधार नहीं होता है (γ = 1.0)।

सही 13bpp स्तर प्राप्त करने के लिए, आप निम्न सूत्र का उपयोग कर सकते हैं:

outputLevel = 8192 * Math.Pow(inputLevel / 255d, 1d / gamma)

जो आपको inputLevel = 128 और gamma = 0.45 (= 1 / 2.2) के लिए 1770 देता है, जो कि 1768 के बहुत करीब है।

वास्तव में विंडोज थोड़ा धोखा दे रहा है जैसा कि आप कम inputLevel मानों के लिए नोटिस कर सकते हैं: उपरोक्त सूत्र आपको इनपुट स्तर <5 के लिए 0 देता है, हालांकि SetPixel को 48/64 बीपीपी प्रारूप और कम आरजीबी रंग मूल्यों के साथ आज़माएं और आप देख सकते हैं कि कच्चे आरजीबी घटक कभी शून्य नहीं होते हैं और वे हमेशा अलग होते हैं। इसलिए उचित गामा सुधार का उपयोग करने से पिक्सेल प्रारूप को चौड़ा करते समय हास्यास्पद रूप से जानकारी का नुकसान हो सकता है... इसलिए (प्रदर्शन के अलावा) मैं रंगों का अनुवाद करते समय लुकअप टेबल का उपयोग करें। लिंक किया गया स्रोत 48-बिट रंग परिवर्तनों की ओर इशारा करता है।

चीजों को और अधिक जटिल बनाने के लिए ये सभी विंडोज़ पर GDI+ का उपयोग करते समय ही लागू होते हैं। ReactOS कार्यान्वयन विस्तृत प्रारूपों के लिए एक सरल रैखिक परिवर्तन का उपयोग करता है, और यह भी विंडोज़ 13 बीपीपी रेंज के विपरीत पूर्ण 16 बिट रेंज का उपयोग करता है। और मोनो में libgdiplus कार्यान्वयन का समर्थन नहीं करता व्यापक प्रारूप बिल्कुल।

इसलिए लिंक किया गया स्रोत सबसे पहले यह जांचता है कि लुकअप टेबल का उपयोग करना है या नहीं। यह हमेशा वास्तविक अंतर्निहित व्यवहार का सम्मान करता है और वास्तविक GDI+ कार्यान्वयन के आधार पर पूर्ण 16bpp रेंज का भी उपयोग कर सकता है। आप चाहें तो बेझिझक पुस्तकालय का उपयोग कर सकते हैं। मुख्य प्रेरणाओं में से एक जटिलता को सरल बनाना और फास्ट किसी भी PixelFormat के बिटमैप में हेरफेर करने के लिए समाधान। यह वास्तविक अंतर्निहित कच्चे डेटा को प्रबंधित तरीके से एक्सेस करने की भी अनुमति देता है (अंतिम लिंक के तहत ReadRaw/WriteRaw उदाहरण देखें)।

0
György Kőszeg 4 सितंबर 2021, 13:28