Para transformar pixels brutos sintéticos em um CGImage confiável, defina em conjunto suas dimensões, o stride das linhas, a ordem dos canais, a convenção de alfa, o espaço de cor e a propriedade dos dados. Este guia cria um conversor BGRA com validações e, em seguida, define um tamanho explícito de exibição no AppKit.

A tarefa do leitor é criar uma imagem na memória para uma amostra de cores, um diagrama ou uma prévia gerados por um app para Mac. A implementação abaixo aceita um formato deliberadamente restrito. Uma sequência de decisões separada explica como a saída sintética do Metal pode chegar ao mesmo ponto de conversão.

O que o contrato de conversão precisa especificar?

O inicializador CGImage baseado em dados brutos da Apple espera que os bytes do provedor correspondam aos parâmetros de imagem fornecidos. A largura e a altura são medidas em pixels; bytesPerRow descreve o layout de memória de cada linha horizontal. Esses parâmetros descrevem o buffer de entrada, portanto, defina-os antes de construir a imagem. Documentação da Apple sobre o inicializador de imagens bitmap.

Use este contrato no nível da aplicação para o conversor deste artigo. Suas regras estritas de validação são escolhas de projeto que tornam a entrada aceita inequívoca.

Propriedade Entrada ou comportamento aceito
Dimensões Largura e altura inteiras positivas, medidas em pixels
Componentes Quatro componentes de 8 bits sem sinal por pixel
Ordem na memória Bytes consecutivos em ordem azul, verde, vermelho, alfa
Alfa Os componentes RGB já estão pré-multiplicados pelo alfa
Interpretação de cores sRGB
Stride da linha Pelo menos width * 4 bytes e divisível por quatro
Comprimento do buffer Exatamente bytesPerRow * height, incluindo o preenchimento da última linha
Sequência de linhas Preservada exatamente; sem inversão vertical automática
Propriedade Copiar a entrada para um armazenamento imutável próprio
Falha Lançar um erro para entrada inválida ou falha na construção
Apresentação Escolher separadamente as dimensões em pontos no AppKit

Por exemplo, uma linha com dois pixels contém oito bytes ativos. Um stride de doze acrescenta quatro bytes de preenchimento após cada linha. Duas dessas linhas ocupam vinte e quatro bytes. O primeiro pixel da segunda linha começa no byte doze. Esse layout de exemplo tem preenchimento intencionalmente: um caso de teste compactado não revelaria um conversor que substitui incorretamente o stride fornecido por width * 4.

Exigir o preenchimento completo após a última linha simplifica o limite de propriedade. Um produtor que use uma alocação menor para a última linha precisa normalizar sua saída antes de chamar este conversor. Rejeitar um comprimento incompatível também evita aceitar por engano um buffer maior cuja região de imagem pretendida não esteja clara.

Como BGRA, a ordem dos bytes, o alfa e o espaço de cor se combinam?

Para este formato, combine CGImageAlphaInfo.premultipliedFirst com CGBitmapInfo.byteOrder32Little. A Apple define o primeiro como alfa nos bits mais significativos, com os componentes de cor já multiplicados pelo alfa, e o segundo como um layout de 32 bits little-endian. Juntas, essas definições resultam na sequência de bytes BGRA em endereços crescentes. Alfa premultiplicado primeiro, ordem bitmap little-endian de 32 bits.

Portanto, a palavra “first” depende da configuração da ordem dos bytes para determinar onde o alfa aparece na memória. Para vermelho opaco, os quatro bytes usados aqui são 0, 0, 255, 255. Fornecer bytes RGBA com essas configurações troca vermelho e azul. Alterar os metadados não reorganiza um buffer existente.

A premultiplicação também é concreta. Nesta convenção ilustrativa de 8 bits, uma cor vermelha intensa com alfa 128 torna-se aproximadamente 0, 0, 128, 128 na memória BGRA. O conversor verifica se cada componente de cor armazenado é menor ou igual ao alfa. Isso detecta valores impossíveis no formato normalizado pré-multiplicado escolhido, embora não prove como o produtor calculou cada componente.

Atribua CGColorSpace.sRGB somente quando os bytes tiverem essa interpretação pretendida. A Apple especifica tanto a colorimetria sRGB quanto sua função de transferência não linear. Um rótulo de espaço de cor, por si só, não transforma valores de componentes RGB lineares em valores sRGB. Definição de sRGB da Apple.

Este conversor realiza a validação e constrói a imagem. Se um produtor emitir alfa direto, ordem RGBA, componentes de ponto flutuante ou valores de cor lineares, adicione uma etapa explícita de normalização antes desse limite ou implemente um contrato documentado separadamente para esse formato.

Como o Swift pode construir a imagem com armazenamento próprio?

Use um limite de cópia para uma pequena imagem gerada para desktop. A Apple documenta que CFDataCreate sempre copia o buffer fornecido para o armazenamento interno, e CGDataProvider(data:) lê um objeto CFData. O inicializador CGImage mantém uma referência ao provedor. Isso evita vincular a imagem ao ponteiro de um array temporário. Copiar bytes para CFData, criar um provedor apoiado por CFData.

O código a seguir é um programa completo de linha de comando em Swift para macOS. Salve-o como main.swift e execute swift main.swift em um Mac com as ferramentas de desenvolvimento instaladas. Ele constrói os dois objetos de imagem e exibe suas dimensões; não cria uma janela. O programa Swift foi inspecionado, mas não pôde ser compilado neste ambiente Linux. A contagem de bytes, os deslocamentos e os limites dos componentes pré-multiplicados do caso de teste foram verificados separadamente com 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)")
}

As multiplicações verificadas são executadas antes de qualquer indexação ou cópia do buffer. Depois que as verificações de comprimento e stride são aprovadas, cada pixel ativo está dentro do array fornecido. O preenchimento é mantido, mas excluído da validação de pixels. Os casos de erro distinguem dimensões, stride, comprimento, valores de alfa e falhas de construção inválidos; um app real pode exibir ou registrar esses erros usando seu tratamento de erros existente.

A primeira linha armazenada do caso de teste contém vermelho e verde opacos. A segunda contém azul opaco e vermelho aproximadamente meio transparente. decode: nil não solicita remapeamento de decodificação, enquanto shouldInterpolate: false solicita a construção sem suavização de pixels. Esses parâmetros não redimensionam nem reescrevem a entrada. Parâmetros de construção de imagem da Apple.

Para uma otimização futura, a Apple também oferece um inicializador de provedor de acesso direto com um callback de liberação. Esse caminho exige um contrato separado para o tempo de vida da alocação: mantenha a alocação válida enquanto o Core Graphics a usar e libere-a por meio do callback. Trate-o como uma implementação diferente, cuja propriedade precisa ser projetada explicitamente. Inicializador de provedor de dados de acesso direto.

Como as dimensões em pixels se tornam um tamanho de exibição no AppKit?

Mantenha as dimensões raster e lógicas como entradas separadas. A Apple distingue os tamanhos de representação, medidos em pontos, de larguras e alturas medidas em pixels. NSImage(cgImage:size:) aceita o tamanho desejado da imagem; passar .zero faz com que ela adote numericamente as dimensões em pixels da imagem de origem. Dimensões da representação de imagem, inicializador CGImage do AppKit.

Como exemplo de decisão de dimensionamento, um raster de 400 × 200 pixels destinado a fornecer dois pixels por ponto deve ter tamanho NSImage de 200 × 100 pontos. Esse cálculo expressa o tamanho lógico pretendido. O CGImage subjacente continua com 400 × 200 pixels; o wrapper não cria amostras adicionais.

O programa escolhe dois pontos para cada dimensão do caso de teste de dois pixels. Para uma prévia maior, escolha um tamanho lógico explícito adequado à visualização. Mantenha o CGImage disponível quando uma operação posterior precisar das dimensões raster exatas, pois a Apple recomenda não presumir detalhes do wrapper NSImage além da equivalência de desenho com sua imagem de origem.

O que muda quando os pixels sintéticos vêm do Metal?

O Metal acrescenta um contrato de prontidão antes que o conversor de CPU possa ser executado. A Apple define .bgra8Unorm como quatro componentes normalizados de 8 bits sem sinal na ordem BGRA. Sua variante .bgra8Unorm_srgb também especifica a conversão entre sRGB e espaço linear. Portanto, corresponder apenas à ordem dos canais não garante que o conteúdo de uma textura satisfaça as convenções de cor e alfa pré-multiplicado deste artigo. Formato BGRA8 normalizado, formato BGRA8 sRGB.

Use esta sequência para uma textura 2D sintética de amostra única no nível de mip zero:

  1. Determine o formato de pixel da textura, a interpretação de cor, a convenção de alfa e a orientação pretendida das linhas.
  2. Conclua o trabalho da GPU que grava na textura e confirme que terminou com sucesso antes de aceitar a saída.
  3. Se a textura usar armazenamento gerenciado, codifique a sincronização necessária da GPU para a CPU e aguarde sua conclusão.
  4. Se a textura usar armazenamento privado, copie-a por blit para uma textura adequada e acessível à CPU antes de lê-la.
  5. Aloque um buffer de destino com tamanho verificado, copie a região solicitada usando o stride real do destino e, em seguida, passe os bytes resultantes pelo contrato da CPU.

Essas etapas refletem dois requisitos documentados distintos. getBytes copia imediatamente na CPU, mas não sincroniza com as operações de textura da GPU; além disso, a Apple proíbe explicitamente seu uso direto em texturas privadas. Separadamente, synchronize(resource:) atualiza a cópia da CPU de um recurso gerenciado; o acesso pela CPU ocorre após a conclusão desse comando. Requisitos de leitura da textura, sincronização de recursos gerenciados.

Escolha o stride de destino para a operação específica de leitura e preserve-o durante a conversão. No contrato BGRA restrito, cada linha ativa precisa de quatro bytes por pixel. Um layout de preparação por blit pode exigir preenchimento diferente de um layout de leitura direta pela CPU; registre o stride em cada limite, em vez de presumir que todos os buffers compartilham o mesmo passo.

Como diagnosticar uma incompatibilidade no contrato?

Use o caso de teste de quatro pixels como auxílio para tomar decisões antes de testar um gráfico gerado maior. Os diagnósticos a seguir são inferências baseadas nos valores e no layout conhecidos, não resultados medidos em um app.

Observação Verifique primeiro
Vermelho e azul trocam de lugar Bytes reais dos canais versus sinalizadores de alfa e ordem dos bytes
A primeira linha parece plausível; as seguintes ficam deslocadas Stride de origem, stride de destino e preenchimento
O vermelho translúcido parece claro ou escuro demais Premultiplicação e interpretação de cor
Os padrões das duas linhas trocam de posição vertical Ordem das linhas do produtor e transformações de desenho
O raster está correto, mas o tamanho lógico está errado Dimensões em pontos no AppKit
A entrada gerada pela CPU funciona; a gerada pela GPU não Conclusão da GPU, modo de armazenamento, sincronização e layout de leitura

Quanto à orientação, este conversor garante a preservação das linhas byte a byte. Inspecione o caso de teste assimétrico na visualização de destino real. Se o resultado estiver invertido verticalmente em relação ao design pretendido, corrija a ordem das linhas em um limite de normalização documentado ou ajuste a transformação de desenho. Mantenha essa decisão explícita para evitar que duas etapas invertam a imagem.

Um contrato completo de conversão de pixels brutos permite identificar o que cada etapa recebe e garante: o produtor fornece pixels definidos, o conversor valida seu layout e mantém uma cópia própria, e o AppKit recebe um tamanho lógico deliberado. Defina esses limites com um pequeno caso de teste sintético e, em seguida, aplique as mesmas regras de dimensões, stride, cor, alfa e tempo de vida a imagens maiores.

Fontes