App’inizin NSImageView görünümünü rasterleştirmek için AppKit’in eşleştirilmiş bitmap önbelleğe alma yöntemlerini kullanın, ardından elde edilen piksel boyutlarını koruyun. Metal için öngörülebilir baytlara ihtiyacınız varsa bu bitmap’i açıkça tanımlanmış bir Core Graphics arabelleğine yeniden çizin.

Bu kılavuz, tümü bellekte oluşturulan sentetik bir görüntüyü ele alır. Amaç, görüntünün NSImageView içindeki sunumunun yeniden kullanılabilir bir rasterini elde etmek, yanlış konumlanmış veya tekrarlanan pikselleri teşhis etmek ve isteğe bağlı olarak rasteri bir Metal dokusuna aktarmaktır.

Görünümün sunumuna mı yoksa tam boyutlu bir görüntüye mi ihtiyacınız olduğuna karar verin

Görüntü ve onu gösteren görünümün sorumlulukları farklıdır. Görünüm, görüntüyü çerçevesine sığdırmak için ölçekleme modunu uygular. Varsayılanı scaleProportionallyDown olduğundan, özgün görüntüyü çıkarmak görünümün sunumunu her zaman yeniden oluşturmaz. Apple’ın imageScaling belgeleri bu davranışı açıklar.

API’yi seçmeden önce çıktıyı belirleyin:

İstenen çıktı Uygun başlangıç noktası Boyut sözleşmesi
Görüntü görünümünün çizdiği haliyle görüntü AppKit’in görünüm bitmap’ini önbelleğe alma yöntemleri Döndürülen bitmap’in piksel boyutlarını inceleyin
Tam olarak belirtilen boyutlarda sentetik çizim Doğrudan bir bitmap CGContext içine çizin Açıkça belirtilmiş tamsayı piksel genişliği ve yüksekliği
Metal dokusu için öngörülebilir baytlar Rasteri tanımlı bir bitmap bağlamına yeniden çizin Açıkça belirtilmiş piksel biçimi, satır adımı ve sahiplik

Apple, bitmapImageRepForCachingDisplay(in:) yöntemini istenen görünüm bölgesine uygun bir gösterim oluşturan yöntem olarak belgeler. Bu gösterimi cacheDisplay(in:to:) yöntemine aktarın; Apple, piksel biçimi uyumluluğu için özellikle bu ikiliyi kullanmanızı önerir. İkinci yöntem görünümü ve alt öğelerini çizer. Bitmap ayırma belgeleri, görünüm önbelleğe alma belgeleri.

Bu yöntemler isteğe bağlı bir çıktı çözünürlüğü parametresi sunmaz. Gereksiniminiz tam olarak 192 × 128 piksel ise sentetik çiziminizi doğrudan bu boyutta işleyin. Daha küçük bir görünüm rasterini sonradan büyütmek, hiç rasterleştirilmemiş ayrıntıları geri getiremez. Bu, görünüm önbelleğe almanın ek bir özelliği değil, iki farklı çıktı sözleşmesinin sonucudur.

Noktaları, pikselleri ve dikdörtgenleri birbirinden ayrı tutun

Yerel çizim bölgesinin tamamını isterken view.bounds kullanın. Bir görünümün bounds değeri kendi koordinat sistemini, frame değeri ise üst görünümün koordinat sistemindeki konumunu ve boyutunu belirtir. Bu nedenle yerel bir bölge bekleyen yönteme frame’i geçirmek istenmeyen bir kaymaya neden olabilir. Apple’ın bounds belgeleri.

Örnek bir boyut sözleşmesinde, 192 × 128 kaynak bitmap 96 × 64 mantıksal boyutlu bir görüntü olarak sunulabilir. Her iki değeri de koruyun: ilki örnekleri, ikincisi sunum boyutunu belirtir. NSImage başlatıcısı açık bir boyut kabul eder; bunun yerine sıfır verilirse kaynak görüntünün piksel boyutlarını kullanır. Apple’ın görüntü başlatıcısı belgeleri.

Mevcut bir görünümün arka plan geometrisini incelerken, yerel dikdörtgenini arka plan deposu koordinatlarına dönüştüren convertToBacking(_:) yöntemini kullanın. Döndürülen raster için pixelsWide ve pixelsHigh değerlerini ya da CGImage boyutlarını inceleyin. Apple, bir NSBitmapImageRep nesnesinin cgImage değerinin gösterimle aynı piksel boyutlarına sahip olduğunu belgeler. Arka plan dönüştürme belgeleri, bitmap CGImage belgeleri.

Bu döndürülen boyutlar yerine varsayılan bir 2× çarpanı kullanmayın. İstenen yerel dikdörtgeni, döndürülen piksel genişliği ve yüksekliğini ve amaçlanan mantıksal boyutu birlikte kaydedin. Böylece sonuç büyütülmüş, kırpılmış veya kaydırılmış göründüğünde somut bir karşılaştırma yapabilirsiniz.

Tam bir sentetik rasterleştirme örneği oluşturun

Aşağıdaki Objective-C programı asimetrik bir test görüntüsü oluşturur, bunu bellekteki bir görüntü görünümüne atar, görünümü rasterleştirir ve sonucu bir Core Graphics bitmap’ine normalleştirir. Hiçbir dosya oluşturmaz. Komut satırı geliştirici araçlarının yüklü olduğu bir Mac’te RasterDemo.m adıyla kaydedin.

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

Normalleştirme bağlamı sekiz bitlik bileşenler, en anlamlı konumda önceden çarpılmış alfa ve little-endian 32 bit depolama kullanır. Bu seçimler bir araya geldiğinde BGRA bayt sırası elde edilir: bellekte ilk olarak en düşük anlamlı mavi bileşen bulunur. Bu, Apple’ın premultipliedFirst ve byteOrder32Little tanımlarıyla uyumludur.

#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;
}

Program, görünüm önbelleğinin kaynağın 192 × 128 pikseliyle eşleştiğini varsaymak yerine gerçek çıktı boyutlarını bilerek bildirir. Camgöbeği işaretleyici, yön değişikliklerinin yinelenen dikey test çizgilerinden ayırt edilmesini sağlar. Gereksiniminiz tam kaynak çözünürlüğüyse görünüm üzerinden geçmek yerine NewTestImage() tarafından döndürülen CGImage nesnesini doğrudan kullanın.

Açık temizleme işlemi, normalleştirmeden önce hedef piksellerin saydam olmasını sağlar. Tek görüntü çizimi hedef dikdörtgeni doldurur; döşeme yapmaz. Apple, ilgili Core Graphics çizim işleminin hedef dikdörtgene ölçeklendiğini ve döşeme etkinleştirilmediği sürece tek kopya çizdiğini belgeler. Apple’ın görüntü çizme belgeleri.

Bu program incelendi ancak AppKit ve bir C derleyicisi bulunmayan Linux araştırma ortamında derlenemedi. Aşağıda kullanılan satır adımı hesaplamaları küçük ve deterministik bir Python hesabıyla denetlendi.

Kaymış veya tekrarlanan pikselleri sabit bir sırayla teşhis edin

Aşağıdaki sıra, yukarıdaki geometri ve bellek sözleşmelerinden türetilen bir tanılama yardımcısıdır. Belirtiler önce nereye bakılabileceğini gösterir; tek bir nedenin varlığını kanıtlamaz.

  1. İstenen dikdörtgeni denetleyin. Görüntü görünümünün tamamı için önbellek isteğini bounds ile karşılaştırın. Bir alt bölgeyi dışa aktarıyorsanız yerel başlangıç noktasını girdi sözleşmesinin bir parçası olarak koruyun.
  2. Ham baytlardan önce boyutları denetleyin. Önbelleğe alınan görüntünün genişlik ve yüksekliğini, sonraki her tüketiciye verilen boyutlarla karşılaştırın. Piksel genişliği yerine nokta cinsinden genişlik kullanılması arabelleğin yorumlanışını değiştirir.
  3. Satır adımını denetleyin. Her satır beklenen konumundan giderek daha uzağa başlıyorsa dönüşümleri değiştirmeden önce satır başlangıçları arasındaki mesafeyi inceleyin.
  4. Asimetrik işaretleyiciyle yönü denetleyin. Dikey tersine dönme, koordinat kurallarını incelemeyi gerektirir. Sabit bir kayma ise başlangıç noktalarını ve ötelemeleri incelemeyi gerektirir.
  5. Tekrarın kasıtlı olup olmadığını denetleyin. Döşemeyi etkinleştiren çizim çağrılarını inceleyin. Satır adımını değiştirmek, açıkça döşenmiş bir çizimi düzeltmez.
  6. Sunum ölçeklemesini en son denetleyin. Saklanan raster doğruysa ancak görüntülenen sonuç farklıysa çıktı görüntüsünün mantıksal boyutunu ve alıcı görüntü görünümünün ölçekleme ayarlarını karşılaştırın.

Dört baytlık pikseller için örnek 192 × 128 arabellek, satır başına 768 bayt kullanır ve 98,304 bayt gerektirir. Ancak örnekteki 64 baytlık satır hizalamasına sahip 191 × 128 arabellek de 768 baytlık bir satır adımı kullanır: her satırda dört dolgu baytı bulunur. 191 × 4 yani 764 bayt ilerlemek, sonraki her satırın dört bayt erken başlamasına neden olur. Apple, gerçek satır adımını bağlamın bytesPerRow özelliği üzerinden sunar.

base + y * bytesPerRow + x * 4 adres hesabını yalnızca burada oluşturulan normalleştirilmiş dört baytlık biçim için kullanın. Herhangi bir AppKit bitmap’inin kendine özgü bir biçim sözleşmesi vardır; bileşen düzenini yalnızca görünüşüne bakarak varsaymayın.

Benzer şekilde, koordinat değişikliklerini bilinçli biçimde uygulayın. Core Graphics, kullanıcı uzayının başlangıç noktasını ve ölçeğini değiştirmek için ayrı işlemler sunar. Dikey çevirme eklerseniz bu dönüşümü hangi aşamanın üstlendiğini belgeleyin ve başka bir dönüşüm eklemeden önce işaretleyiciyle doğrulayın. Öteleme belgeleri, ölçekleme belgeleri.

Görüntü döndürürken veya Metal’e yüklerken sahipliği koruyun

Örnek, CGImage nesnesini çizerken AppKit gösterimini bellekte tutar. Ardından normalleştirme bağlamından oluşturulan bir anlık görüntü döndürür. Apple, makeImage() yönteminin dahili olarak kopyalama sırasında yazma üzerine kopyalama kullanabilen bir kopya oluşturduğunu ve bağlama sonradan çizim yapmanın döndürülen görüntüyü değiştirmediğini belgeler. Bu, anlık görüntü oluşturulduktan sonra bağlamın serbest bırakılmasını destekler. Apple’ın bitmap anlık görüntüsü belgeleri.

Ham bitmap işaretçisinin ömrü farklıdır. Apple, bağlam null veri argümanıyla oluşturulmuş olsa bile bağlamın sahip olduğu bitmap verilerine erişim sağlar. Bu işaretçiyi etkin bağlama ait kabul edin; daha sonra kullanacak bir tüketici için baytları sahip olduğunuz belleğe kopyalayın veya tüketim tamamlanana kadar bağlamı etkin tutun. Apple’ın bitmap verileri belgeleri.

Metal’e yükleme için normalleştirilmiş bağlam hâlâ etkin durumdayken şu adımları uygulayın:

  1. Bağlamın piksel genişliği ve yüksekliğini kullanarak iki boyutlu bir doku oluşturun. Gösterilen BGRA bileşen düzeni için bgra8Unorm kullanın; Apple bunu BGRA sırasındaki dört adet sekiz bitlik normalleştirilmiş işaretsiz bileşen olarak tanımlar.
  2. CPU üzerinden yükleme için donanıma özgü bir paylaşımlı kipi zorlamak yerine sistemin varsayılan depolama kipini koruyun. Apple, Apple silicon aygıtlarda paylaşımlı kipin, Intel tabanlı Mac’lerdeki dokular içinse yönetilen kipin varsayılan olduğunu belgeler.
  3. Bağlamın veri işaretçisini ve gerçek satır adımını kullanarak tam doku bölgesi için, 0 mip düzeyinde replace(region:mipmapLevel:withBytes:bytesPerRow:) çağrısını yapın.
  4. Bağlamı serbest bırakmadan önce yüklemeyi tamamlayın. Dokuyu yeniden kullanırken, CPU dokuyu üzerine yazmadan önce bu dokuyu kullanan önceki GPU işlemlerinin de tamamlandığından emin olun.

Biçim ve depolama seçimleri, Apple’ın bgra8Unorm belgelerine ve kaynak depolama yönergelerine dayanır. Apple, değiştirme yönteminin sağlanan pikselleri hemen kopyaladığını ancak GPU işlemleriyle eşzamanlama yapmadığını belirtir. Özel bir doku için belgelenen yol, özel olmayan geçici bir doku oluşturup ardından blit uygulamaktır. Apple’ın doku değiştirme belgeleri.

Doku yüklemesi sağlanan baytları korur; yalnızca bileşen düzeninin eşleşmesi, işleme hattının geri kalanını tanımlamaz. Örnekten alınan önceden çarpılmış alfa ve sRGB kararlarını tüketicinin tasarımına da yansıtın. Normalleştirme, seçilen biçime yeniden çizimdir; bu nedenle olası her AppKit gösteriminden baytları birebir koruyan bir dönüşüm garantisi olarak açıklanmamalıdır.

Bu görev için güvenilir bir aktarım; rasteri piksel boyutları, mantıksal sunum boyutu, satır adımı, bileşen biçimi, yön kuralı ve sahibiyle birlikte sunar. Bu değerleri bir arada tutun, asimetrik sentetik bir çizimle sınayın ve ayrışmanın başladığı ilk aşamayı düzeltin. Böylece kaymış veya tekrarlanan sonuç izlenebilir bir sözleşme sorununa dönüşür.

Kaynaklar