Rastrera appens NSImageView med AppKits parade metoder för bitmappscachning och bevara sedan de resulterande pixeldimensionerna. Om du behöver förutsägbara bytevärden för Metal ritar du om bitmappen till en uttryckligen definierad Core Graphics-buffert.

Den här guiden behandlar en syntetisk bild som genereras helt i minnet. Målet är att få en återanvändbar rasterbild av hur den visas i en NSImageView, diagnostisera felplacerade eller upprepade pixlar och vid behov överföra rasterbilden till en Metal-textur.

Välj om du behöver vyns presentation eller en bild med exakt storlek

En bild och vyn som visar den har olika uppgifter. Vyn använder sitt skalningsläge för att anpassa bilden till sin ram. Standardvärdet är scaleProportionallyDown, så att extrahera originalbilden återskapar inte nödvändigtvis hur den visas i vyn. Apples dokumentation om imageScaling beskriver detta beteende.

Välj utdata innan du väljer API:et:

Önskad utdata Lämplig startpunkt Dimensionskontrakt
Bilden så som den ritas av bildvyn AppKits metoder för vyernas bitmappscachning Kontrollera den returnerade bitmappens pixeldimensioner
En syntetisk teckning med exakt angivna dimensioner Rita direkt till en CGContext för bitmapp Uttrycklig heltalsbredd och -höjd i pixlar
Förutsägbara bytevärden för en Metal-textur Rita om rasterbilden till en definierad bitmappskontext Uttryckligt pixelformat, radsteg och ägarskap

Apple dokumenterar bitmapImageRepForCachingDisplay(in:) som en metod som skapar en representation som passar för det begärda vyområdet. Skicka representationen till cacheDisplay(in:to:); Apple rekommenderar uttryckligen denna kombination för kompatibilitet med pixelformat. Den andra metoden ritar vyn och dess underordnade vyer. Dokumentation om bitmappsallokering, dokumentation om vycachning.

Dessa metoder ger inget argument för godtycklig utdataupplösning. Om kravet är exakt 192 × 128 pixlar ska du rendera din syntetiska teckning direkt i den storleken. Om du skalar upp en mindre rasterbild från vyn i efterhand kan du inte återskapa detalj som aldrig rastrerades. Det är en följd av de två olika utdatakontrakten, inte någon ytterligare förmåga hos vycachning.

Håll isär punkter, pixlar och rektanglar

Använd view.bounds när du begär hela det lokala ritområdet. En vygräns beskriver vyns eget koordinatsystem, medan dess ram beskriver dess position och storlek i den överordnade vyns koordinatsystem. Om du skickar ramen till en metod som förväntar sig ett lokalt område kan det därför uppstå en oavsiktlig förskjutning. Apples dokumentation om bounds.

Som ett exempel på dimensionskontrakt kan en källbitmapp på 192 × 128 visas som en logisk bild på 96 × 64. Bevara båda värdena: det första beskriver bildpunkterna, medan det andra beskriver presentationsstorleken. NSImage-initieraren tar emot en uttrycklig storlek; om du i stället anger noll används källbildens pixeldimensioner. Apples dokumentation om bildinitieraren.

När du diagnostiserar en befintlig vyns geometri i backinglagret använder du convertToBacking(_:), som konverterar dess lokala rektangel till koordinater i backinglagret. För den rasterbild som faktiskt returneras granskar du pixelsWide och pixelsHigh, eller dess CGImage-dimensioner. Apple dokumenterar att cgImage hos en NSBitmapImageRep har samma pixeldimensioner som representationen. Dokumentation om konvertering till backingkoordinater, dokumentation om bitmappens CGImage.

Ersätt inte de returnerade dimensionerna med en förmodad 2×-multiplikator. Anteckna det begärda lokala området, den returnerade bredden och höjden i pixlar samt den avsedda logiska storleken tillsammans. Då får du en konkret jämförelse om resultatet verkar förstorat, beskuret eller förskjutet.

Skapa ett komplett exempel på syntetisk rastrering

Följande Objective-C-program genererar en asymmetrisk testbild, tilldelar den till en bildvy i minnet, rastrerar vyn och normaliserar resultatet till en Core Graphics-bitmapp. Det skapar inga filer. Spara det som RasterDemo.m på en Mac med kommandoradsverktygen för utvecklare installerade.

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

Normaliseringskontexten använder åttabitars komponenter, premultiplicerad alfa i den mest signifikanta positionen och 32-bitars lagring i little-endian-format. Tillsammans ger de valen byteordningen BGRA: den minst signifikanta blå komponenten ligger först i minnet. Detta följer av Apples definitioner av premultipliedFirst och 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;
}

Programmet rapporterar medvetet de faktiska utdata dimensionerna i stället för att hävda att vycachen motsvarar källans 192 × 128 pixlar. Den cyanfärgade markören gör det möjligt att skilja orienteringsändringar från de upprepade vertikala testlinjerna. Om du behöver exakt källupplösning använder du CGImage som returneras av NewTestImage() direkt i stället för att gå via vyn.

Den uttryckliga rensningen skapar transparenta mål pixlar innan normaliseringen. Ritningen av den enskilda bilden fyller målrektangeln; den upprepar inte bilden. Apple dokumenterar att motsvarande Core Graphics-ritoperation skalar till målrektangeln och ritar en kopia om inte mönsterläggning är aktiverad. Apples dokumentation om bildritning.

Programmet granskades men kunde inte kompileras i Linux-forskningsmiljön, som saknar AppKit och en C-kompilator. Beräkningarna av radsteg som används nedan kontrollerades med en liten deterministisk Python-beräkning.

Diagnostisera förskjutna eller upprepade pixlar i en fast ordning

Följande sekvens är ett diagnostiskt hjälpmedel baserat på geometrin och minneskontrakten ovan. Symptomen antyder var du bör börja undersöka, men fastställer inte någon entydig orsak.

  1. Kontrollera den begärda rektangeln. För hela bildvyn jämför du cachningsbegäran med bounds. Om du exporterar en delregion ska du låta dess lokala origo ingå i indatakontaktet.
  2. Kontrollera dimensionerna före rådata. Jämför den cachade bildens bredd och höjd med dimensionerna som anges till varje senare konsument. Om en bredd i punkter ersätter en bredd i pixlar ändras hur bufferten tolkas.
  3. Kontrollera radsteget. Om varje rad börjar allt längre från sin förväntade position ska du undersöka avståndet mellan radernas startpunkter innan du justerar transformationer.
  4. Kontrollera orienteringen med den asymmetriska markören. Om bilden är vänd vertikalt ska du granska koordinatkonventionerna. Vid en konstant förskjutning ska du granska origo och translationer.
  5. Kontrollera om upprepningen är avsiktlig. Granska alla ritoperationer som aktiverar mönsterläggning. Ändrat radsteg korrigerar inte en uttryckligen mönsterlagd ritning.
  6. Kontrollera presentationsskalningen sist. Om den lagrade rasterbilden är korrekt men det visade resultatet skiljer sig ska du jämföra utdatabildens logiska storlek med skalningsinställningarna i den mottagande bildvyn.

För pixlar med fyra byte har bufferten på 192 × 128 i exemplet 768 byte per rad och kräver 98,304 byte. Men en buffert på 191 × 128 med exempelns 64-bytejustering av rader har också ett radsteg på 768 byte: varje rad innehåller fyra utfyllnadsbyte. Om du går vidare med 191 × 4, eller 764, börjar varje efterföljande rad fyra byte för tidigt. Apple exponerar det faktiska radsteget via kontextens bytesPerRow-egenskap.

Använd adressberäkningen base + y * bytesPerRow + x * 4 endast för det normaliserade formatet med fyra byte som definieras här. En godtycklig AppKit-bitmapp har sitt eget formatkontrakt; dra inte slutsatser om komponenternas layout enbart utifrån hur den ser ut.

Tillämpa också koordinatändringar medvetet. Core Graphics har separata operationer för att ändra origo och skala i användarkoordinater. Om du inför en vertikal vändning ska du dokumentera vilket steg som ansvarar för konverteringen och verifiera den mot markören innan du lägger till ytterligare en. Dokumentation om translation, dokumentation om skalning.

Bevara ägarskapet när du returnerar en bild eller laddar upp till Metal

Exemplet håller AppKit-representationen vid liv medan dess CGImage ritas. Därefter returnerar det en ögonblicksbild som skapas från normaliseringskontexten. Apple dokumenterar att makeImage() skapar en kopia, som internt kan använda kopiering vid skrivning, och att efterföljande ritning i kontexten inte ändrar den returnerade bilden. Därför kan kontexten frigöras efter att ögonblicksbilden skapats. Apples dokumentation om bitmappens ögonblicksbild.

En rå bitmappspekare har en annan livslängd. Apple tillhandahåller åtkomst till kontextägd bitmappsdata även när kontexten skapades med ett null-dataargument. Behandla pekaren som tillhörande den aktiva kontexten; för senare användning kopierar du bytena till lagring som du äger eller håller kontexten vid liv tills användningen är klar. Apples dokumentation om bitmappsdata.

För en Metal-uppladdning utför du följande steg medan den normaliserade kontexten fortfarande är aktiv:

  1. Skapa en tvådimensionell textur med kontextens pixeldimensioner. Använd bgra8Unorm för den demonstrerade BGRA-komponentlayouten; Apple definierar den som fyra åttabitars normaliserade komponenter utan tecken i BGRA-ordning.
  2. För uppladdning från processorn behåller du systemets standardläge för lagring i stället för att tvinga fram ett maskinvaruspecifikt delat läge. Apple dokumenterar delad lagring som standard på Apple Silicon och hanterad lagring som standard för texturer på Intel-baserade Mac-datorer.
  3. Anropa replace(region:mipmapLevel:withBytes:bytesPerRow:) för hela texturområdet och mipnivå 0, med kontextens datapekare och faktiska radsteg.
  4. Slutför uppladdningen innan kontexten frigörs. Om texturen återanvänds ska du också se till att tidigare GPU-arbete som använder den har slutförts innan processorn skriver över den.

Format- och lagringsvalen följer Apples dokumentation om bgra8Unorm och vägledning om resurslagring. Apple uppger att ersättningsmetoden omedelbart kopierar de angivna pixlarna men inte synkroniserar med GPU-operationer. För en privat textur är den dokumenterade metoden att använda en tillfällig textur som inte är privat och sedan utföra en blit. Apples dokumentation om texturersättning.

Texturuppladdningen bevarar de angivna bytena; komponentlayouten ensam definierar inte resten av renderingskedjan. Ta med exempelns beslut om premultiplicerad alfa och sRGB i utformningen av konsumenten. Normalisering innebär omritning till ett valt format och bör därför inte beskrivas som en garanti för byteidentisk konvertering från alla möjliga AppKit-representationer.

För den här uppgiften består en tillförlitlig överlämning av rasterbilden samt dess pixeldimensioner, logiska presentationsstorlek, radsteg, komponentformat, orienteringskonvention och ägare. Håll dessa värden tillsammans, testa med en asymmetrisk syntetisk teckning och korrigera det första steget där de avviker. Då blir ett förskjutet eller upprepat resultat ett spårbart kontraktsproblem.

Källor