För att omvandla syntetiska råpixlar till en pålitlig CGImage, definiera deras dimensioner, radsteg, kanalföljd, alfakonvention, färgutrymme och ägande tillsammans. Den här guiden bygger en kontrollerad BGRA-omvandlare och ger sedan resultatet en explicit AppKit-visningsstorlek.
Uppgiften för läsaren är att skapa en minnesbild för en Mac-apps genererad provyta, diagram eller förhandsvisning. Implementeringen nedan accepterar ett avsiktligt smalt format. En separat beslutssekvens förklarar hur syntetisk Metal-utdata kan nå samma omvandlingsgräns.
Vad måste omvandlingskontraktet specificera?
Apples rådata-CGImage-initierare förväntar att providerns byte matchar de angivna bildparametrarna. Dess bredd och höjd är pixlar; bytesPerRow beskriver varje horisontella rads minneslayout. Dessa parametrar beskriver indatabufferten, så etablera dem innan du konstruerar bilden. Apples bitmap-bildinitierardokumentation.
Använd detta applikationsnivå-kontrakt för omvandlaren i den här artikeln. Dess strikta valideringsregler är designval som gör den accepterade indatan entydig.
| Egenskap | Accepterad indata eller beteende |
|---|---|
| Dimensioner | Positiva heltal för bredd och höjd, mätt i pixlar |
| Komponenter | Fyra osignerade 8-bitars komponenter per pixel |
| Minnesordning | På varandra följande byte är blå, grön, röd, alpha |
| Alpha | RGB-komponenter är redan premultiplicerade med alpha |
| Färgtoolkning | sRGB |
| Radsteg | Minst width * 4 byte och delbar med fyra |
| Buffertlängd | Exakt bytesPerRow * height, inklusive padding på sista raden |
| Radsekvens | Bevaras exakt; ingen automatisk vertikal omvändning |
| Ägande | Kopiera indata till oföränderligt ägt lager |
| Fel | Kasta ett fel vid ogiltig indata eller misslyckad konstruktion |
| Presentation | Välj AppKit-punktdimensioner separat |
Till exempel innehåller en två-pixel-rad åtta aktiva byte. Ett steg på tolv lägger till fyra padding-byte efter varje rad. Två sådana rader upptar tjugofyra byte. Den första pixeln på rad två börjar vid byte tolv. Den här utförda layouten är avsiktligt paddad: ett tunt packat fixture skulle inte avslöja en omvandlare som felaktigt ersätter width * 4 med det angivna steget.
Att kräva fullständig padding efter den sista raden förenklar ägandegränsen. En producent som använder en kortare sista-rad-allokering måste normalisera sin utdata innan den anropar denna omvandlare. Att avvisa en missmatchande längd förhindrar också oavsiktlig acceptans av en större buffert vars avsedda bildregion är otydlig.
Hur hänger BGRA, byteordning, alpha och färgutrymme ihop?
För detta format, kombinera CGImageAlphaInfo.premultipliedFirst med CGBitmapInfo.byteOrder32Little. Apple definierar den förre som alpha i de mest signifikanta bitarna med färgkomponenter redan multiplicerade med alpha, och den senare som en little-endian 32-bitars layout. Tillsammans ger dessa definitioner den ökande adress-byte-sekvensen BGRA. Premultiplicerad-först-alpha, 32-bitars little-endian bitmap-ordning.
Ordet "first" behöver alltså byteordningsinställningen för att avgöra var alpha visas i minnet. För opak röd är de fyra byte som används här 0, 0, 255, 255. Att leverera RGBA-byte med dessa inställningar byter plats på rött och blått. Att ändra metadata kan inte omordna en befintlig buffert.
Premultiplicering är lika konkret. I denna illustrativa 8-bitars konvention blir en full-röd färg med alpha 128 ungefär 0, 0, 128, 128 i BGRA-minne. Omvandlaren kontrollerar att varje lagrad färgkomponent inte är större än alpha. Detta fångar upp omöjliga värden för dess valda normaliserade premultiplicerade format, även om det inte kan bevisa hur en producent beräknade varje komponent.
Tilldela CGColorSpace.sRGB endast när byte har den avsedda tolkningen. Apple specificerar både sRGB-färgmetri och dess olinjära överföringsfunktion. En färgutrymmesetikett ensam omvandlar inte linjära RGB-komponentvärden till sRGB-värden. Apples sRGB-definition.
Den här omvandlaren utför validering och bildkonstruktion. Om en producent utsänder rakt alpha, RGBA-ordning, flyttalskomponenter eller linjära färgvärden, lägg till ett explicit normaliseringsteg före denna gräns, eller implementera ett separat dokumenterat kontrakt för det formatet.
Hur kan Swift bygga bilden med ägt lager?
Använd en kopieringsgräns för en liten genererad skrivbordsbild. Apple dokumenterar att CFDataCreate alltid kopierar den tillförda buffern till internt lager, och CGDataProvider(data:) läser ett CFData-objekt. CGImage-initieraren behåller sin provider. Detta undviker att binda bilden till en temporär arraypekare. Kopiera byte in i CFData, skapa en CFData-backad provider.
Följande är ett komplett macOS Swift-kommandoradsprogram. Spara det som main.swift och kör swift main.swift på en Mac med utvecklarverktyg installerade. Det konstruerar båda bildobjekten och skriver ut deras dimensioner; det skapar inget fönster. Swift-programmet granskades men kunde inte kompileras i denna Linux-miljö. Dess fixtures byteantal, offset och premultiplicerade komponentgränser kontrollerades separat med Python.
import AppKit
import CoreGraphics
import CoreFoundation
enum PixelError: Error {
case invalidDimensions, arithmeticOverflow
case invalidStride, invalidByteCount
case invalidPremultipliedPixel
case constructionFailed
}
func makeBGRAImage(
bytes: [UInt8], width: Int, height: Int, bytesPerRow: Int
) throws -> CGImage {
guard width > 0, height > 0 else {
throw PixelError.invalidDimensions
}
let (activeRowBytes, rowOverflow) =
width.multipliedReportingOverflow(by: 4)
guard !rowOverflow else { throw PixelError.arithmeticOverflow }
guard bytesPerRow >= activeRowBytes,
bytesPerRow % 4 == 0 else {
throw PixelError.invalidStride
}
let (requiredBytes, sizeOverflow) =
bytesPerRow.multipliedReportingOverflow(by: height)
guard !sizeOverflow else { throw PixelError.arithmeticOverflow }
guard bytes.count == requiredBytes else {
throw PixelError.invalidByteCount
}
for y in 0..<height {
for x in 0..<width {
let i = y * bytesPerRow + x * 4
let alpha = bytes[i + 3]
guard bytes[i] <= alpha,
bytes[i + 1] <= alpha,
bytes[i + 2] <= alpha else {
throw PixelError.invalidPremultipliedPixel
}
}
}
let copiedData: CFData? = bytes.withUnsafeBufferPointer { buffer in
CFDataCreate(nil, buffer.baseAddress, requiredBytes)
}
guard let data = copiedData,
let provider = CGDataProvider(data: data),
let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) else {
throw PixelError.constructionFailed
}
let info = CGBitmapInfo.byteOrder32Little.union(
CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
)
guard let image = CGImage(
width: width, height: height,
bitsPerComponent: 8, bitsPerPixel: 32,
bytesPerRow: bytesPerRow,
space: colorSpace, bitmapInfo: info,
provider: provider, decode: nil,
shouldInterpolate: false, intent: .defaultIntent
) else {
throw PixelError.constructionFailed
}
return image
}
// Two active pixels and four padding bytes per row.
let syntheticBytes: [UInt8] = [
0, 0, 255, 255, 0, 255, 0, 255, 0, 0, 0, 0,
255, 0, 0, 255, 0, 0, 128, 128, 0, 0, 0, 0
]
do {
let cgImage = try makeBGRAImage(
bytes: syntheticBytes, width: 2, height: 2, bytesPerRow: 12
)
let appKitImage = NSImage(
cgImage: cgImage, size: NSSize(width: 2, height: 2)
)
precondition(cgImage.width == 2 && cgImage.height == 2)
print("Pixels: \(cgImage.width) x \(cgImage.height)")
print("Points: \(appKitImage.size)")
} catch {
fatalError("Synthetic image conversion failed: \(error)")
}
De kontrollerade multiplikationerna körs innan något indexering eller buffertkopiering. När längd- och stegkontrollerna lyckas ligger varje aktiv pixel inne i den tillförda arrayen. Padding behålls men utesluts från pixelvalidering. Fellskiljer felaktiga dimensioner, steg, längd, alphavärden och konstruktionsfel; en riktig app kan presentera eller logga dessa fel genom sin befintliga felhantering.
Fixturens första lagrade rad innehåller opakt rött och opakt grönt. Dess andra innehåller opakt blått och ungefär halvtransparent rött. decode: nil begär ingen decode-omappning, medan shouldInterpolate: false begär konstruktion utan pixelsmutsning. Dessa parametrar ändrar inte storlek på eller skriver om indatan. Apples bildkonstruktionsparametrar.
För en framtida optimering tillhandahåller Apple också en direktåtkomst-provider-initierare med en frigöringscallback. Den sökvägen kräver ett separat allokering-livstidskontrakt: håll allokeringen giltig medan Core Graphics använder den, och frigör den via callbacken. Behandla det som en annan implementering vars ägande måste utformas explicit. Direktåtkomst-dataprovider-initierare.
Hur ska pixeldimensioner bli en AppKit-visningsstorlek?
Håll rasterdimensioner och logiska dimensioner som separata indata. Apple skiljer på representationsstorlekar mätta i punkter från bredder och höjder mätta i pixlar. NSImage(cgImage:size:) accepterar den önskade bildstorleken; att skicka .zero gör att den antar källbildens pixeldimensioner numeriskt. Bildrepresentationsdimensioner, AppKits CGImage-initierare.
Som en illustrativ storleksbeslut, bör en 400 × 200 pixel raster avsedd att leverera två pixlar per punkt ha en NSImage-storlek på 200 × 100 punkter. Den beräkningen uttrycker den avsedda logiska storleken. Den underliggande CGImage förblir 400 × 200 pixlar; wrapern skapar inga ytterligare sample.
Programmet väljer två punkter för varje dimension av sitt två-pixel-fixture. För en större förhandsvisning, välj en explicit logisk storlek lämplig för vyn. Behåll CGImage tillgänglig när en nedströms operation behöver exakta rasterdimensioner, eftersom Apple rådjer ifrån att anta detaljer om NSImage-wrapern utöver ritjämförighet med sin källa.
Vad ändras när syntetiska pixlar kommer från Metal?
Metal lägger till ett redo-kontrakt innan CPU-omvandlaren kan köra. Apple definierar .bgra8Unorm som fyra normaliserade osignerade 8-bitars komponenter i BGRA-ordning. Dess .bgra8Unorm_srgb-motsvarighet specificerar också konvertering mellan sRGB och linjärt utrymme. Att matcha kanalfordning ensam etablerar alltså inte att en texturs innehåll uppfyller denna artikels färg- och premultiplicerade-alpha-konventioner. BGRA8-normaliserat format, BGRA8 sRGB-format.
Använd denna sekvens för en syntetisk, enkelprovtagande 2D-textur på mip-nivå noll:
- Etablera texturens pixelformat, färgtoolkning, alfakonvention och avsedda radorientering.
- Avsluta GPU-arbetet som skriver texturen och verifiera framgångsrik slutförande innan du accepterar dess utdata.
- Om texturen använder hanterat lager, koda den nödvändiga GPU-till-CPU-synkroniseringen och vänta på att den slutförs.
- Om texturen använder privat lager, blitta den till en lämplig CPU-tillgänglig textur innan du läser den.
- Allokera en kontrollerad destinationsbuffert, kopiera den begärda regionen med dess faktiska destinationssteg, och skicka sedan de resulterande byten genom CPU-kontraktet.
Dessa steg speglar två distinkta dokumenterade krav. getBytes kopierar omedelbart på CPU men synkroniserar inte med GPU-texturoperationer, och Apple förbjuder explicit att använda den direkt på privata texturer. Separat, synchronize(resource:) uppdaterar CPU-kopian av en hanterad resurs; CPU-åtkomst följer efter att kommandot slutförts. Textur-avläsningskrav, hanterad-resurssynkronisering.
Välj destinationssteg för den specifika avläsningsoperationen och bevara det genom omvandlingen. För det smala BGRA-kontraktet behöver varje aktiv rad fyra byte per pixel. En blit-staging-layout kan kräva annorlunda padding än en direkt CPU-avläsningslayout; registrera steget vid varje gräns istället för att anta att varje buffert delar en pitch.
Hur kan du diagnostisera ett kontraktsmissmatch?
Använd det fyra-pixel-fixturen som ett beslutsstöd innan du testar en större genererad grafik. Följande diagnostik är slutsatser från dess kända värden och layout, snarare än målade resultat från en app.
| Observation | Kontrollera först |
|---|---|
| Rött och blått byter plats | Faktiska kanalbyte mot alpha- och byteordningsflaggor |
| Första raden ser rimlig ut; senare rader skjuter | Källsteg, destinationssteg och padding |
| Genomskinligt rött ser för ljust eller för mörkt ut | Premultiplicering och färgtoolkning |
| De två radmönstren byter vertikala positioner | Producerad radordning och rittransformer |
| Rastern är korrekt men logisk storlek är fel | AppKit-punktdimensioner |
| CPU-genererad indata fungerar; GPU-genererad indata fungerar inte | GPU-färdigställande, lagringsläge, synkronisering och avläsningslayout |
För orientering, lovar denna omvandlare byte-för-byte-radbevaring. Inspektera det asymmetriska fixturen i den faktiska destinationsvyn. Om resultatet är vertikalt omvänd relativt den avsedda designen, korrigera radordning vid en dokumenterad normaliseringsgräns eller korrigera rittransformen. Håll det beslutet explicit så att två steg inte båda vänder bilden.
Ett komplett råpixels-omvandlingskontrakt låter dig identifiera vad varje steg tar emot och garanterar: producenten levererar definierade pixlar, omvandlaren validerar deras layout och äger en kopia, och AppKit får en avsiktlig logisk storlek. Etablera dessa gränser med ett litet syntetiskt fixture, för sedan in samma dimensioner, steg, färg, alpha och livstidsregler i större bilder.
Källor
- init(width:height:bitsPerComponent:bitsPerPixel:bytesPerRow:space:bitmapInfo:provider:decode:shouldInterpolate:intent:) — Apple
- CGImageAlphaInfo.premultipliedFirst — Apple
- byteOrder32Little — Apple
- sRGB — Apple
- CFDataCreate(::_:) — Apple
- init(data:) — Apple
- init(dataInfo:data:size:releaseData:) — Apple
- NSImageRep — Apple
- init(cgImage:size:) — Apple
- MTLPixelFormat.bgra8Unorm — Apple
- MTLPixelFormat.bgra8Unorm_srgb — Apple
- getBytes(_:bytesPerRow:from:mipmapLevel:) — Apple
- synchronize(resource:) — Apple