Per trasformare pixel grezzi sintetici in un CGImage affidabile, definisci insieme le dimensioni, il passo di riga, l’ordine dei canali, la convenzione alfa, lo spazio colore e la gestione della memoria. Questa guida crea un convertitore BGRA con controlli, quindi assegna al risultato una dimensione di visualizzazione esplicita in AppKit.

Il compito del lettore consiste nel creare un’immagine in memoria per un campione di colore, un diagramma o un’anteprima generati da un’app per Mac. L’implementazione qui sotto accetta un formato volutamente circoscritto. Una sequenza decisionale separata spiega come l’output sintetico di Metal possa raggiungere lo stesso punto di conversione.

Che cosa deve specificare il contratto di conversione?

L’inizializzatore di CGImage che usa dati grezzi si aspetta che i byte del provider corrispondano ai parametri dell’immagine forniti. Larghezza e altezza sono espresse in pixel; bytesPerRow descrive il layout in memoria di ogni riga orizzontale. Questi parametri descrivono il buffer di input, quindi stabiliscili prima di creare l’immagine. Documentazione Apple sull’inizializzatore per immagini bitmap.

Per il convertitore di questo articolo, usa il seguente contratto a livello applicativo. Le sue regole rigorose di convalida sono scelte progettuali che rendono univoci gli input accettati.

Proprietà Input o comportamento accettato
Dimensioni Larghezza e altezza intere positive, misurate in pixel
Componenti Quattro componenti unsigned a 8 bit per pixel
Ordine in memoria I byte consecutivi sono blu, verde, rosso, alfa
Alfa Le componenti RGB sono già premoltiplicate per alfa
Interpretazione del colore sRGB
Passo di riga Almeno width * 4 byte e divisibile per quattro
Lunghezza del buffer Esattamente bytesPerRow * height, incluso il padding dell’ultima riga
Sequenza delle righe Conservata esattamente; nessuna inversione verticale automatica
Gestione della memoria Copia dell’input in memoria immutabile di proprietà
Errori Generazione di un errore per input non validi o costruzione non riuscita
Presentazione Dimensioni in punti di AppKit scelte separatamente

Per esempio, una riga di due pixel contiene otto byte attivi. Un passo di dodici aggiunge quattro byte di padding dopo ogni riga. Due righe di questo tipo occupano ventiquattro byte. Il primo pixel della seconda riga inizia al byte dodici. Questo layout di esempio include deliberatamente del padding: un caso di prova senza padding non rivelerebbe un convertitore che sostituisce per errore il passo fornito con width * 4.

Richiedere il padding completo anche dopo l’ultima riga semplifica il confine di gestione della memoria. Un produttore che usa un’allocazione dell’ultima riga più corta deve normalizzare l’output prima di chiamare questo convertitore. Rifiutare lunghezze diverse impedisce inoltre di accettare per errore un buffer più grande la cui regione immagine prevista non è chiara.

Come si combinano BGRA, ordine dei byte, alfa e spazio colore?

Per questo formato, combina CGImageAlphaInfo.premultipliedFirst con CGBitmapInfo.byteOrder32Little. Apple definisce il primo valore come alfa nei bit più significativi, con le componenti di colore già moltiplicate per alfa, e il secondo come layout a 32 bit little-endian. Insieme, queste definizioni producono la sequenza di byte BGRA agli indirizzi crescenti. Alfa premoltiplicata in primo piano, ordine bitmap little-endian a 32 bit.

La parola «first» richiede quindi l’impostazione dell’ordine dei byte per determinare la posizione dell’alfa in memoria. Per il rosso opaco, i quattro byte usati qui sono 0, 0, 255, 255. Fornire byte RGBA con queste impostazioni scambia rosso e blu. Modificare i metadati non riordina un buffer esistente.

Anche la premoltiplicazione è concreta. In questa convenzione illustrativa a 8 bit, un rosso puro con alfa 128 diventa approssimativamente 0, 0, 128, 128 in memoria BGRA. Il convertitore controlla che ogni componente di colore memorizzata non superi l’alfa. In questo modo rileva valori impossibili per il formato premoltiplicato normalizzato scelto, anche se non può verificare come il produttore abbia calcolato ogni componente.

Assegna CGColorSpace.sRGB solo se i byte hanno questa interpretazione prevista. Apple specifica sia la colorimetria sRGB sia la relativa funzione di trasferimento non lineare. Un’etichetta dello spazio colore da sola non trasforma i valori delle componenti RGB lineari in valori sRGB. Definizione Apple di sRGB.

Questo convertitore convalida i dati e crea l’immagine. Se un produttore genera alfa non premoltiplicata, ordine RGBA, componenti in virgola mobile o valori di colore lineari, aggiungi un passaggio esplicito di normalizzazione prima di questo confine oppure implementa un contratto documentato separatamente per quel formato.

Come può Swift creare l’immagine usando memoria di proprietà?

Per una piccola immagine desktop generata, usa un passaggio che effettui una copia. Apple documenta che CFDataCreate copia sempre il buffer fornito nella propria memoria interna e che CGDataProvider(data:) legge un oggetto CFData. L’inizializzatore di CGImage conserva il provider. Così l’immagine non dipende dal puntatore temporaneo di un array. Copiare byte in CFData, creare un provider basato su CFData.

Quello che segue è un programma completo a riga di comando in Swift per macOS. Salvalo come main.swift ed esegui swift main.swift su un Mac con gli strumenti per sviluppatori installati. Crea entrambi gli oggetti immagine e ne stampa le dimensioni; non apre una finestra. Il programma Swift è stato esaminato, ma non è stato possibile compilarlo in questo ambiente Linux. Il numero di byte, gli offset e i limiti delle componenti premoltiplicate del caso di prova sono stati verificati separatamente con 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)")
}

Le moltiplicazioni con controllo vengono eseguite prima di qualsiasi indicizzazione o copia del buffer. Dopo il superamento dei controlli sulla lunghezza e sul passo, ogni pixel attivo si trova all’interno dell’array fornito. Il padding viene conservato, ma escluso dalla convalida dei pixel. I casi di errore distinguono dimensioni non valide, passo, lunghezza, valori alfa e problemi di costruzione; un’app reale può mostrare o registrare questi errori tramite la gestione degli errori già esistente.

La prima riga memorizzata del caso di prova contiene rosso e verde opachi. La seconda contiene blu opaco e rosso semitrasparente, con opacità approssimativamente dimezzata. decode: nil richiede di non rimappare i valori di decodifica, mentre shouldInterpolate: false richiede la creazione senza smussamento dei pixel. Questi parametri non ridimensionano né riscrivono l’input. Parametri di creazione delle immagini di Apple.

Per una successiva ottimizzazione, Apple fornisce anche un inizializzatore di provider ad accesso diretto con una callback di rilascio. Questo percorso richiede un contratto separato sulla durata di vita dell’allocazione: mantieni valida l’allocazione mentre Core Graphics la usa e rilasciala tramite la callback. Consideralo un’implementazione diversa, la cui gestione della memoria va progettata esplicitamente. Inizializzatore del provider di dati ad accesso diretto.

Come si traducono le dimensioni in pixel in dimensioni di visualizzazione di AppKit?

Considera separatamente le dimensioni raster e quelle logiche come input distinti. Apple distingue le dimensioni delle rappresentazioni, misurate in punti, dalla larghezza e dall’altezza misurate in pixel. NSImage(cgImage:size:) accetta la dimensione desiderata dell’immagine; passare .zero la fa adottare numericamente le dimensioni in pixel dell’immagine sorgente. Dimensioni delle rappresentazioni immagine, inizializzatore di AppKit per CGImage.

Come scelta illustrativa per il dimensionamento, un raster di 400 × 200 pixel destinato a fornire due pixel per punto dovrebbe avere una dimensione NSImage di 200 × 100 punti. Questo calcolo esprime la dimensione logica desiderata. Il CGImage sottostante resta di 400 × 200 pixel; il wrapper non crea campioni aggiuntivi.

Il programma assegna due punti per dimensione al suo caso di prova di due pixel. Per un’anteprima più grande, scegli una dimensione logica esplicita adatta alla vista. Conserva il CGImage disponibile quando un’operazione successiva richiede le dimensioni raster esatte, perché Apple sconsiglia di fare supposizioni sui dettagli del wrapper NSImage, oltre all’equivalenza del disegno con la sorgente.

Che cosa cambia quando i pixel sintetici provengono da Metal?

Metal aggiunge un requisito di disponibilità prima che possa essere eseguito il convertitore CPU. Apple definisce .bgra8Unorm come quattro componenti unsigned a 8 bit normalizzate nell’ordine BGRA. La variante .bgra8Unorm_srgb specifica anche la conversione tra sRGB e spazio lineare. La sola corrispondenza dell’ordine dei canali non dimostra quindi che il contenuto di una texture rispetti le convenzioni di colore e alfa premoltiplicata di questo articolo. Formato BGRA8 normalizzato, formato sRGB BGRA8.

Per una texture 2D sintetica con un solo campione al livello mip zero, segui questa sequenza:

  1. Stabilisci il formato dei pixel della texture, l’interpretazione del colore, la convenzione alfa e l’orientamento previsto delle righe.
  2. Completa il lavoro GPU che scrive sulla texture e verifica che sia terminato correttamente prima di accettarne l’output.
  3. Se la texture usa memoria gestita, codifica la sincronizzazione GPU-CPU richiesta e attendi che sia completata.
  4. Se la texture usa memoria privata, esegui un blit verso una texture adatta e accessibile dalla CPU prima di leggerla.
  5. Alloca un buffer di destinazione con controlli, copia la regione richiesta usando il passo effettivo della destinazione, quindi passa i byte risultanti al contratto CPU.

Questi passaggi rispecchiano due requisiti documentati distinti. getBytes copia immediatamente sul lato CPU, ma non sincronizza le operazioni GPU sulla texture; Apple vieta esplicitamente di usarlo direttamente su texture private. Separatamente, synchronize(resource:) aggiorna la copia CPU di una risorsa gestita; l’accesso della CPU avviene dopo il completamento di quel comando. Requisiti per la lettura delle texture, sincronizzazione delle risorse gestite.

Scegli il passo di destinazione per la specifica operazione di lettura e conservalo durante la conversione. Per il contratto BGRA circoscritto, ogni riga attiva richiede quattro byte per pixel. Il layout di staging di un blit può richiedere un padding diverso da quello di una lettura diretta da CPU; registra il passo a ogni confine invece di supporre che tutti i buffer abbiano lo stesso pitch.

Come diagnosticare un’incompatibilità nel contratto?

Prima di provare un’immagine generata più grande, usa come guida decisionale il caso di prova con quattro pixel. Le diagnosi seguenti derivano dai valori e dal layout noti; non sono risultati misurati in un’app.

Osservazione Primo controllo da fare
Rosso e blu si scambiano di posto Byte dei canali effettivi rispetto ai flag alfa e di ordine dei byte
La prima riga sembra corretta, ma le successive risultano spostate Passo sorgente, passo di destinazione e padding
Il rosso traslucido sembra troppo luminoso o troppo scuro Premoltiplicazione e interpretazione del colore
I motivi delle due righe si scambiano la posizione verticale Ordine delle righe del produttore e trasformazioni di disegno
Il raster è corretto, ma la dimensione logica è sbagliata Dimensioni in punti di AppKit
L’input generato dalla CPU funziona, quello generato dalla GPU no Completamento GPU, modalità di memoria, sincronizzazione e layout di lettura

Per l’orientamento, questo convertitore garantisce di conservare le righe byte per byte. Esamina il caso di prova asimmetrico nella vista di destinazione effettiva. Se il risultato è capovolto verticalmente rispetto al design previsto, correggi l’ordine delle righe in un confine di normalizzazione documentato oppure correggi la trasformazione di disegno. Rendi esplicita questa scelta, in modo che due passaggi non capovolgano entrambi l’immagine.

Un contratto completo di conversione da pixel grezzi ti consente di identificare che cosa riceve e garantisce ogni passaggio: il produttore fornisce pixel definiti, il convertitore convalida il layout e ne conserva una copia, e AppKit riceve una dimensione logica deliberata. Stabilisci questi confini con un piccolo caso di prova sintetico, quindi applica le stesse regole su dimensioni, passo, colore, alfa e durata della memoria alle immagini più grandi.

Fonti