अपने ऐप के NSImageView को रास्टराइज़ करने के लिए, AppKit की जोड़ीदार बिटमैप-कैशिंग विधियों का उपयोग करें, फिर प्राप्त पिक्सेल आयामों को जस का तस रखें। यदि आपको Metal के लिए पूर्वानुमेय बाइट चाहिए, तो उस बिटमैप को स्पष्ट रूप से परिभाषित Core Graphics बफ़र में फिर से ड्रॉ करें।

इस गाइड में पूरी तरह मेमोरी में बनाई गई एक कृत्रिम छवि शामिल है। इसका उद्देश्य NSImageView में उसकी प्रस्तुति का पुनः उपयोग योग्य रास्टर प्राप्त करना, गलत जगह आए या दोहराए गए पिक्सेल का पता लगाना, और वैकल्पिक रूप से रास्टर को Metal टेक्सचर में स्थानांतरित करना है।

चुनें कि आपको व्यू की प्रस्तुति चाहिए या सटीक आकार की छवि

छवि और उसे दिखाने वाले व्यू की भूमिकाएँ अलग-अलग हैं। व्यू छवि को अपने फ़्रेम में फिट करने के लिए स्केलिंग मोड लागू करता है। इसका डिफ़ॉल्ट scaleProportionallyDown है, इसलिए मूल छवि निकालने से ज़रूरी नहीं कि व्यू की प्रस्तुति भी वैसी ही मिले। Apple का imageScaling दस्तावेज़ इस व्यवहार का वर्णन करता है।

API चुनने से पहले तय करें कि आउटपुट क्या चाहिए:

वांछित आउटपुट उपयुक्त शुरुआती तरीका आयाम संबंधी शर्त
इमेज व्यू द्वारा ड्रॉ की गई छवि AppKit की व्यू बिटमैप-कैशिंग विधियाँ लौटाए गए बिटमैप के पिक्सेल आयाम जाँचें
ठीक निर्दिष्ट आयामों पर बनाई गई कृत्रिम ड्रॉइंग सीधे बिटमैप CGContext में ड्रॉ करें पूर्णांक पिक्सेल चौड़ाई और ऊँचाई स्पष्ट रूप से तय करें
Metal टेक्सचर के लिए पूर्वानुमेय बाइट रास्टर को परिभाषित बिटमैप कॉन्टेक्स्ट में फिर से ड्रॉ करें पिक्सेल फ़ॉर्मैट, स्ट्राइड और स्वामित्व स्पष्ट रूप से तय करें

Apple bitmapImageRepForCachingDisplay(in:) को अनुरोधित व्यू क्षेत्र के लिए उपयुक्त रिप्रेज़ेंटेशन बनाने वाली विधि के रूप में दस्तावेज़ित करता है। उस रिप्रेज़ेंटेशन को cacheDisplay(in:to:) में पास करें; Apple पिक्सेल-फ़ॉर्मैट संगतता के लिए विशेष रूप से इस जोड़ी की अनुशंसा करता है। दूसरी विधि व्यू और उसके सभी अधीनस्थ व्यू ड्रॉ करती है। बिटमैप आवंटन का दस्तावेज़, व्यू कैशिंग का दस्तावेज़।

इन विधियों में मनमाने आउटपुट रिज़ॉल्यूशन के लिए कोई आर्ग्युमेंट नहीं है। यदि आपकी आवश्यकता ठीक 192 × 128 पिक्सेल की है, तो अपनी कृत्रिम ड्रॉइंग सीधे उसी आकार में रेंडर करें। बाद में छोटे व्यू रास्टर का आकार बढ़ाने से वह बारीकी वापस नहीं मिल सकती जो कभी रास्टराइज़ ही नहीं हुई। यह दोनों आउटपुट अनुबंधों का परिणाम है, व्यू कैशिंग की कोई अतिरिक्त क्षमता नहीं।

पॉइंट, पिक्सेल और आयतों को अलग रखें

पूरे स्थानीय ड्रॉइंग क्षेत्र का अनुरोध करते समय view.bounds का उपयोग करें। व्यू की bounds उसके अपने निर्देशांक तंत्र का वर्णन करती हैं; उसका frame superview के निर्देशांक तंत्र में उसकी स्थिति और आकार बताता है। इसलिए जिस विधि को स्थानीय क्षेत्र चाहिए, उसमें frame पास करने से अनचाहा ऑफ़सेट आ सकता है। Apple का bounds दस्तावेज़।

आकार संबंधी उदाहरण के लिए, 192 × 128 स्रोत बिटमैप को 96 × 64 तार्किक छवि के रूप में दिखाया जा सकता है। दोनों मान सुरक्षित रखें: पहला सैंपल बताता है, जबकि दूसरा प्रस्तुति का आकार। NSImage इनिशियलाइज़र स्पष्ट आकार स्वीकार करता है; इसके बजाय शून्य देने पर वह स्रोत छवि के पिक्सेल आयाम अपना लेता है। Apple का image initializer दस्तावेज़।

किसी मौजूदा व्यू की backing geometry जाँचते समय convertToBacking(_:) का उपयोग करें, जो उसके स्थानीय आयत को backing-store निर्देशांकों में बदलता है। वास्तव में लौटे रास्टर के लिए pixelsWide और pixelsHigh, या उसके CGImage आयाम जाँचें। Apple दस्तावेज़ित करता है कि NSBitmapImageRep का cgImage उसी रिप्रेज़ेंटेशन के पिक्सेल आयाम रखता है। Backing conversion का दस्तावेज़, bitmap CGImage का दस्तावेज़।

लौटाए गए आयामों की जगह अनुमानित 2× गुणक का उपयोग न करें। अनुरोधित स्थानीय आयत, लौटाई गई पिक्सेल चौड़ाई और ऊँचाई, और इच्छित तार्किक आकार—इन सबको साथ दर्ज करें। इससे नतीजा बड़ा, कटा हुआ या खिसका हुआ दिखने पर ठोस तुलना की जा सकती है।

कृत्रिम रास्टराइज़ेशन का पूरा उदाहरण बनाएँ

यह Objective-C प्रोग्राम एक असममित परीक्षण छवि बनाता है, उसे मेमोरी में बने इमेज व्यू को देता है, व्यू को रास्टराइज़ करता है और नतीजे को Core Graphics बिटमैप में सामान्यीकृत करता है। यह कोई फ़ाइल नहीं बनाता। कमांड-लाइन डेवलपर टूल इंस्टॉल किए हुए Mac पर इसे RasterDemo.m नाम से सहेजें।

clang -fobjc-arc RasterDemo.m -framework Cocoa -o RasterDemo
./RasterDemo

सामान्यीकरण कॉन्टेक्स्ट में आठ-बिट कंपोनेंट, सबसे महत्वपूर्ण स्थान पर premultiplied alpha, और little-endian 32-बिट स्टोरेज का उपयोग किया गया है। इन विकल्पों से मिलकर BGRA बाइट क्रम बनता है: मेमोरी में सबसे पहले least significant नीला कंपोनेंट आता है। यह Apple की premultipliedFirst और byteOrder32Little की परिभाषाओं के अनुरूप है।

#import <Cocoa/Cocoa.h>

static CGContextRef NewBitmap(size_t w, size_t h) {
    // Illustrative allocation budget, not an API dimension limit.
    if (w == 0 || h == 0 || w > 4096 || h > 4096) return NULL;
    size_t rowBytes = (w * 4 + 63) & ~(size_t)63;
    CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
    if (!space) return NULL;
    CGContextRef c = CGBitmapContextCreate(
        NULL, w, h, 8, rowBytes, space,
        kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(space);
    return c;
}

static CGImageRef NewTestImage(void) {
    CGContextRef c = NewBitmap(192, 128);
    if (!c) return NULL;
    CGContextSetRGBFillColor(c, 1, 1, 1, 1);
    CGContextFillRect(c, CGRectMake(0, 0, 192, 128));
    CGContextSetRGBFillColor(c, 0, 0, 0, 1);
    for (size_t x = 0; x < 192; x += 16) {
        CGContextFillRect(c, CGRectMake(x, 0, 1, 128));
    }
    CGContextSetRGBFillColor(c, 0, 0.7, 0.8, 1);
    CGContextFillRect(c, CGRectMake(8, 92, 24, 20));
    CGImageRef image = CGBitmapContextCreateImage(c);
    CGContextRelease(c);
    return image;
}

static CGImageRef NewNormalizedImage(CGImageRef input) {
    size_t w = CGImageGetWidth(input);
    size_t h = CGImageGetHeight(input);
    CGContextRef c = NewBitmap(w, h);
    if (!c) return NULL;
    CGContextClearRect(c, CGRectMake(0, 0, w, h));
    CGContextSetBlendMode(c, kCGBlendModeCopy);
    CGContextSetInterpolationQuality(c, kCGInterpolationNone);
    CGContextDrawImage(c, CGRectMake(0, 0, w, h), input);
    NSLog(@"Normalized: %zu x %zu pixels; stride %zu bytes",
          w, h, CGBitmapContextGetBytesPerRow(c));
    CGImageRef result = CGBitmapContextCreateImage(c);
    CGContextRelease(c);
    return result;
}

int main(void) {
    @autoreleasepool {
        [NSApplication sharedApplication];
        CGImageRef source = NewTestImage();
        if (!source) { NSLog(@"Source allocation failed"); return 1; }
        NSImage *image = [[NSImage alloc] initWithCGImage:source
                                                  size:NSMakeSize(96, 64)];
        CGImageRelease(source);
        NSImageView *view = [[NSImageView alloc]
                            initWithFrame:NSMakeRect(0, 0, 96, 64)];
        view.image = image;
        view.imageScaling = NSImageScaleNone;
        view.imageAlignment = NSImageAlignCenter;
        view.imageFrameStyle = NSImageFrameNone;
        [view layoutSubtreeIfNeeded];
        NSRect region = view.bounds;
        NSBitmapImageRep *rep =
            [view bitmapImageRepForCachingDisplayInRect:region];
        if (!rep) { NSLog(@"View bitmap allocation failed"); return 1; }
        [view cacheDisplayInRect:region toBitmapImageRep:rep];
        CGImageRef cached = rep.CGImage;
        if (!cached) { NSLog(@"View bitmap has no CGImage"); return 1; }
        CGImageRef raster = NewNormalizedImage(cached);
        if (!raster) { NSLog(@"Normalization failed"); return 1; }
        NSImage *result = [[NSImage alloc] initWithCGImage:raster
                                                   size:region.size];
        NSLog(@"Result logical size: %@", NSStringFromSize(result.size));
        CGImageRelease(raster);
    }
    return 0;
}

प्रोग्राम यह दावा करने के बजाय वास्तविक आउटपुट आयाम बताता है कि व्यू कैश स्रोत के 192 × 128 पिक्सेल से मेल खाता है। सियान मार्कर से दिशा में बदलाव को दोहराई गई ऊर्ध्वाधर परीक्षण रेखाओं से अलग पहचाना जा सकता है। यदि आपकी आवश्यकता स्रोत का सटीक रिज़ॉल्यूशन है, तो व्यू से गुज़ारने के बजाय NewTestImage() से सीधे लौटाए गए CGImage का उपयोग करें।

स्पष्ट clear ऑपरेशन सामान्यीकरण से पहले गंतव्य पिक्सेल को पारदर्शी बनाता है। एकल-छवि ड्रॉ गंतव्य आयत को भरती है; यह टाइल नहीं करती। Apple दस्तावेज़ित करता है कि संबंधित Core Graphics ड्रॉइंग ऑपरेशन गंतव्य आयत के अनुसार स्केल होता है और, जब तक टाइलिंग सक्षम न हो, छवि की एक ही प्रति ड्रॉ करता है। Apple का image drawing दस्तावेज़।

इस प्रोग्राम की जाँच की गई, लेकिन Linux शोध परिवेश में इसे कंपाइल नहीं किया जा सका क्योंकि वहाँ AppKit और C कंपाइलर उपलब्ध नहीं हैं। नीचे उपयोग की गई स्ट्राइड गणनाओं को एक छोटे, नियतात्मक Python हिसाब से जाँचा गया।

खिसके या दोहराए गए पिक्सेल का क्रमवार पता लगाएँ

नीचे दिया गया क्रम ऊपर बताए गए ज्यामिति और मेमोरी अनुबंधों से निकला एक निदान सहायक है। लक्षण बताते हैं कि पहले कहाँ जाँच करनी है; वे किसी एक निश्चित कारण को साबित नहीं करते।

  1. अनुरोधित आयत जाँचें। पूरे इमेज व्यू के लिए कैश अनुरोध की तुलना bounds से करें। यदि उपक्षेत्र एक्सपोर्ट कर रहे हैं, तो उसके स्थानीय मूल-बिंदु को इनपुट अनुबंध में शामिल रखें।
  2. कच्चे बाइट जाँचने से पहले आयाम जाँचें। कैश की गई छवि की चौड़ाई और ऊँचाई की तुलना हर बाद के उपभोक्ता को दिए गए आयामों से करें। पिक्सेल चौड़ाई की जगह पॉइंट चौड़ाई देने से बफ़र की व्याख्या बदल जाती है।
  3. पंक्ति स्ट्राइड जाँचें। यदि हर पंक्ति अपेक्षित स्थान से लगातार दूर शुरू होती है, तो ट्रांसफ़ॉर्म बदलने से पहले पंक्तियों के आरंभ के बीच की दूरी जाँचें।
  4. असममित मार्कर से दिशा जाँचें। ऊर्ध्वाधर उलटाव हो तो निर्देशांक परंपराएँ जाँचें। स्थिर विस्थापन हो तो मूल-बिंदु और ट्रांसलेशन जाँचें।
  5. जाँचें कि दोहराव जानबूझकर तो नहीं है। टाइलिंग सक्षम करने वाली किसी भी ड्रॉइंग कॉल को देखें। पंक्ति स्ट्राइड बदलने से स्पष्ट रूप से टाइल की गई ड्रॉ ठीक नहीं होगी।
  6. प्रस्तुति स्केलिंग सबसे अंत में जाँचें। यदि संग्रहित रास्टर सही है, लेकिन दिखाई गई छवि अलग है, तो आउटपुट छवि के तार्किक आकार और उसे लेने वाले इमेज व्यू की स्केलिंग सेटिंग की तुलना करें।

चार-बाइट पिक्सेल के लिए, उदाहरण वाला 192 × 128 बफ़र प्रति पंक्ति 768 बाइट लेता है और कुल 98,304 बाइट चाहिए। लेकिन उदाहरण की 64-बाइट पंक्ति संरेखण वाला 191 × 128 बफ़र भी 768 बाइट की स्ट्राइड रखता है: हर पंक्ति में चार पैडिंग बाइट शामिल होते हैं। 191 × 4, यानी 764, बाइट आगे बढ़ने पर हर अगली पंक्ति चार बाइट बहुत जल्दी शुरू होगी। Apple कॉन्टेक्स्ट की bytesPerRow प्रॉपर्टी से वास्तविक स्ट्राइड उपलब्ध कराता है।

base + y * bytesPerRow + x * 4 पता-गणना का उपयोग केवल यहाँ तय किए गए सामान्यीकृत चार-बाइट फ़ॉर्मैट के लिए करें। किसी भी AppKit बिटमैप का अपना फ़ॉर्मैट अनुबंध होता है; केवल उसके दिखने के आधार पर कंपोनेंट लेआउट का अनुमान न लगाएँ।

इसी तरह, निर्देशांक परिवर्तन सोच-समझकर लागू करें। Core Graphics यूज़र स्पेस का मूल-बिंदु और स्केल बदलने के लिए अलग-अलग ऑपरेशन देता है। यदि आप ऊर्ध्वाधर फ्लिप जोड़ते हैं, तो दर्ज करें कि किस चरण में यह रूपांतरण होता है और दूसरा फ्लिप जोड़ने से पहले मार्कर से इसकी पुष्टि करें। Translation का दस्तावेज़, scale का दस्तावेज़।

छवि लौटाते या Metal पर अपलोड करते समय स्वामित्व सुरक्षित रखें

नमूना अपने CGImage को ड्रॉ करते समय AppKit रिप्रेज़ेंटेशन को जीवित रखता है। फिर यह सामान्यीकरण कॉन्टेक्स्ट से बनाया गया स्नैपशॉट लौटाता है। Apple दस्तावेज़ित करता है कि makeImage() एक कॉपी बनाता है, जो अंदरूनी तौर पर copy-on-write का उपयोग कर सकती है, और कॉन्टेक्स्ट में बाद की ड्रॉइंग लौटाई गई छवि को नहीं बदलती। इससे स्नैपशॉट बनाने के बाद कॉन्टेक्स्ट रिलीज़ किया जा सकता है। Apple का bitmap snapshot दस्तावेज़।

कच्चे बिटमैप पॉइंटर का जीवनकाल अलग होता है। Apple कॉन्टेक्स्ट के स्वामित्व वाले बिटमैप डेटा तक पहुँच देता है, भले ही कॉन्टेक्स्ट null data आर्ग्युमेंट के साथ बनाया गया हो। उस पॉइंटर को जीवित कॉन्टेक्स्ट का हिस्सा मानें; बाद के उपभोक्ता के लिए बाइट को अपने स्वामित्व वाली स्टोरेज में कॉपी करें या उपयोग पूरा होने तक कॉन्टेक्स्ट जीवित रखें। Apple का bitmap data दस्तावेज़।

Metal पर अपलोड करने के लिए सामान्यीकृत कॉन्टेक्स्ट के जीवित रहते ये चरण पूरे करें:

  1. कॉन्टेक्स्ट की पिक्सेल चौड़ाई और ऊँचाई वाला द्वि-आयामी टेक्सचर बनाएँ। दिखाए गए BGRA कंपोनेंट लेआउट के लिए bgra8Unorm उपयोग करें; Apple इसे BGRA क्रम में चार आठ-बिट normalized unsigned कंपोनेंट के रूप में परिभाषित करता है।
  2. CPU अपलोड के लिए सिस्टम का डिफ़ॉल्ट स्टोरेज मोड बनाए रखें; हार्डवेयर-विशिष्ट shared मोड थोपें नहीं। Apple दस्तावेज़ित करता है कि Apple silicon पर shared डिफ़ॉल्ट होता है, जबकि Intel-आधारित Mac पर टेक्सचर के लिए managed डिफ़ॉल्ट होता है।
  3. कॉन्टेक्स्ट के डेटा पॉइंटर और वास्तविक पंक्ति स्ट्राइड का उपयोग करते हुए, पूरे टेक्सचर क्षेत्र और mip स्तर 0 के लिए replace(region:mipmapLevel:withBytes:bytesPerRow:) कॉल करें।
  4. कॉन्टेक्स्ट रिलीज़ करने से पहले अपलोड पूरा करें। टेक्सचर का फिर से उपयोग करते समय यह भी सुनिश्चित करें कि CPU द्वारा उसे ओवरराइट करने से पहले उस टेक्सचर का उपयोग करने वाला पिछला GPU काम पूरा हो चुका हो।

फ़ॉर्मैट और स्टोरेज विकल्प Apple के bgra8Unorm दस्तावेज़ और resource storage मार्गदर्शन पर आधारित हैं। Apple कहता है कि replacement विधि दिए गए पिक्सेल तुरंत कॉपी करती है, लेकिन GPU ऑपरेशनों के साथ सिंक्रोनाइज़ नहीं करती। private टेक्सचर के लिए दस्तावेज़ित तरीका है कि पहले non-private अस्थायी टेक्सचर बनाएँ, फिर blit करें। Apple का texture replacement दस्तावेज़।

टेक्सचर अपलोड दिए गए बाइट सुरक्षित रखता है; केवल कंपोनेंट लेआउट का मेल आपके पूरे रेंडरिंग पाइपलाइन को परिभाषित नहीं करता। नमूने में premultiplied-alpha और sRGB के लिए चुने गए निर्णय उपभोक्ता के डिज़ाइन में भी बनाए रखें। सामान्यीकरण चुने गए फ़ॉर्मैट में फिर से ड्रॉ करना है, इसलिए इसे हर संभावित AppKit रिप्रेज़ेंटेशन से बाइट-दर-बाइट समान रूपांतरण की गारंटी के रूप में वर्णित नहीं करना चाहिए।

इस काम के लिए विश्वसनीय हस्तांतरण में रास्टर के साथ उसके पिक्सेल आयाम, तार्किक प्रस्तुति आकार, पंक्ति स्ट्राइड, कंपोनेंट फ़ॉर्मैट, दिशा परंपरा और स्वामी की जानकारी शामिल होती है। इन मानों को साथ रखें, असममित कृत्रिम ड्रॉइंग से जाँचें, और जहाँ वे अलग होने लगें उस पहले चरण को ठीक करें। इससे खिसका या दोहराया गया नतीजा अनुबंध से जुड़ी ऐसी समस्या बन जाता है जिसका पता लगाया जा सकता है।

स्रोत