Untuk mengubah piksel mentah sintetis menjadi CGImage yang andal, tentukan dimensi, stride baris, urutan kanal, konvensi alfa, ruang warna, dan kepemilikannya secara bersamaan. Panduan ini membuat konverter BGRA yang tervalidasi, lalu menetapkan ukuran tampilan AppKit secara eksplisit untuk hasilnya.

Tugas pembaca adalah membuat gambar dalam memori untuk swatch, diagram, atau pratinjau yang dihasilkan oleh aplikasi Mac. Implementasi di bawah menerima satu format yang sengaja dibatasi. Urutan keputusan terpisah menjelaskan bagaimana keluaran Metal sintetis dapat mencapai batas konversi yang sama.

Apa saja yang harus ditetapkan dalam kontrak konversi?

Inisialisasi CGImage Apple untuk data mentah mengharuskan byte dari provider sesuai dengan parameter gambar yang diberikan. Lebar dan tingginya adalah piksel; bytesPerRow menjelaskan tata letak memori setiap baris horizontal. Parameter ini menjelaskan buffer masukan, jadi tetapkan semuanya sebelum membuat gambar. Dokumentasi inisialisasi gambar bitmap Apple.

Gunakan kontrak tingkat aplikasi ini untuk konverter dalam artikel ini. Aturan validasinya yang ketat merupakan pilihan desain agar masukan yang diterima tidak ambigu.

Properti Masukan atau perilaku yang diterima
Dimensi Lebar dan tinggi bilangan bulat positif, diukur dalam piksel
Komponen Empat komponen unsigned 8-bit per piksel
Urutan memori Byte berurutan adalah biru, hijau, merah, alfa
Alfa Komponen RGB sudah dikalikan dengan alfa (premultiplied)
Interpretasi warna sRGB
Stride baris Minimal width * 4 byte dan habis dibagi empat
Panjang buffer Tepat bytesPerRow * height, termasuk padding baris terakhir
Urutan baris Dipertahankan persis; tidak ada pembalikan vertikal otomatis
Kepemilikan Salin masukan ke penyimpanan milik sendiri yang immutable
Kegagalan Lemparkan error untuk masukan tidak valid atau konstruksi yang gagal
Penyajian Tentukan dimensi AppKit dalam point secara terpisah

Sebagai contoh, satu baris berisi dua piksel memiliki delapan byte aktif. Stride sebesar dua belas menambahkan empat byte padding setelah setiap baris. Dua baris seperti itu menempati dua puluh empat byte. Piksel pertama pada baris kedua dimulai pada byte kedua belas. Tata letak ini sengaja menggunakan padding: fixture yang dikemas rapat tidak akan mengungkap konverter yang keliru mengganti stride yang diberikan dengan width * 4.

Mewajibkan padding lengkap setelah baris terakhir menyederhanakan batas kepemilikan. Produsen yang menggunakan alokasi baris terakhir lebih pendek harus menormalkan keluarannya sebelum memanggil konverter ini. Menolak panjang yang tidak cocok juga mencegah penerimaan buffer yang lebih besar secara tidak sengaja ketika wilayah gambar yang dimaksud tidak jelas.

Bagaimana BGRA, urutan byte, alfa, dan ruang warna saling terkait?

Untuk format ini, gabungkan CGImageAlphaInfo.premultipliedFirst dengan CGBitmapInfo.byteOrder32Little. Apple mendefinisikan yang pertama sebagai alfa pada bit paling signifikan dengan komponen warna yang sudah dikalikan alfa, dan yang kedua sebagai tata letak 32-bit little-endian. Jika digabungkan, definisi tersebut menghasilkan urutan byte BGRA pada alamat yang meningkat. Alfa premultiplied-first, urutan bitmap 32-bit little-endian.

Karena itu, kata “first” memerlukan pengaturan urutan byte untuk menentukan posisi alfa dalam memori. Untuk warna merah opak, empat byte yang digunakan di sini adalah 0, 0, 255, 255. Memberikan byte RGBA dengan pengaturan ini akan menukar merah dan biru. Mengubah metadata tidak akan mengatur ulang buffer yang sudah ada.

Premultiplikasi juga memiliki arti konkret. Dalam konvensi 8-bit ilustratif ini, warna merah penuh dengan alfa 128 menjadi kira-kira 0, 0, 128, 128 dalam memori BGRA. Konverter memeriksa bahwa setiap komponen warna yang tersimpan tidak lebih besar daripada alfa. Pemeriksaan ini mendeteksi nilai yang mustahil untuk format premultiplied ternormalisasi yang dipilih, meskipun tidak dapat membuktikan cara produsen menghitung setiap komponen.

Tetapkan CGColorSpace.sRGB hanya jika byte memang dimaksudkan untuk interpretasi tersebut. Apple menetapkan kolorimetri sRGB sekaligus fungsi transfer nonlinearnya. Label ruang warna saja tidak mengubah nilai komponen RGB linear menjadi nilai sRGB. Definisi sRGB dari Apple.

Konverter ini melakukan validasi dan konstruksi gambar. Jika produsen menghasilkan alfa straight, urutan RGBA, komponen floating-point, atau nilai warna linear, tambahkan langkah normalisasi eksplisit sebelum batas ini, atau implementasikan kontrak yang didokumentasikan secara terpisah untuk format tersebut.

Bagaimana Swift dapat membuat gambar dengan penyimpanan yang dimiliki sendiri?

Gunakan batas penyalinan untuk gambar desktop kecil yang dihasilkan. Apple mendokumentasikan bahwa CFDataCreate selalu menyalin buffer yang diberikan ke penyimpanan internal, dan CGDataProvider(data:) membaca objek CFData. Inisialisasi CGImage mempertahankan providernya. Dengan demikian, gambar tidak terikat pada pointer array sementara. Menyalin byte ke CFData, membuat provider yang didukung CFData.

Berikut program baris perintah Swift macOS yang lengkap. Simpan sebagai main.swift dan jalankan swift main.swift di Mac dengan alat pengembang terpasang. Program ini membuat kedua objek gambar dan mencetak dimensinya; program ini tidak membuat jendela. Program Swift telah diperiksa, tetapi tidak dapat dikompilasi di lingkungan Linux ini. Jumlah byte fixture, offset, dan batas komponen premultiplied diperiksa secara terpisah dengan 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)")
}

Perkalian yang diperiksa dijalankan sebelum pengindeksan atau penyalinan buffer apa pun. Setelah pemeriksaan panjang dan stride berhasil, setiap piksel aktif berada di dalam array yang diberikan. Padding dipertahankan, tetapi tidak disertakan dalam validasi piksel. Kasus error membedakan dimensi, stride, panjang, nilai alfa, dan kegagalan konstruksi yang tidak valid; aplikasi nyata dapat menampilkan atau mencatat error tersebut melalui penanganan error yang sudah ada.

Baris pertama fixture berisi merah opak dan hijau opak. Baris keduanya berisi biru opak dan merah yang kira-kira setengah transparan. decode: nil meminta agar pemetaan ulang decode tidak dilakukan, sedangkan shouldInterpolate: false meminta konstruksi tanpa penghalusan piksel. Parameter ini tidak mengubah ukuran atau menulis ulang masukan. Parameter konstruksi gambar dari Apple.

Untuk optimisasi di kemudian hari, Apple juga menyediakan inisialisasi provider akses langsung dengan callback pelepasan. Jalur tersebut memerlukan kontrak masa berlaku alokasi yang terpisah: pertahankan validitas alokasi selama Core Graphics menggunakannya, lalu lepaskan melalui callback. Perlakukan jalur itu sebagai implementasi berbeda yang kepemilikannya harus dirancang secara eksplisit. Inisialisasi provider data akses langsung.

Bagaimana dimensi piksel menjadi ukuran tampilan AppKit?

Pisahkan dimensi raster dan dimensi logis sebagai masukan tersendiri. Apple membedakan ukuran representasi yang diukur dalam point dari lebar dan tinggi yang diukur dalam piksel. NSImage(cgImage:size:) menerima ukuran gambar yang diinginkan; memberikan .zero membuatnya menggunakan angka dimensi piksel gambar sumber. Dimensi representasi gambar, inisialisasi CGImage AppKit.

Sebagai contoh keputusan ukuran, raster 400 × 200 piksel yang dimaksudkan untuk menyediakan dua piksel per point sebaiknya memiliki ukuran NSImage sebesar 200 × 100 point. Perhitungan tersebut menyatakan ukuran logis yang diinginkan. CGImage yang mendasarinya tetap berukuran 400 × 200 piksel; pembungkusnya tidak membuat sampel tambahan.

Program ini memilih dua point untuk setiap dimensi fixture dua pikselnya. Untuk pratinjau yang lebih besar, pilih ukuran logis eksplisit yang sesuai dengan tampilan. Pertahankan akses ke CGImage saat operasi lanjutan memerlukan dimensi raster yang tepat, karena Apple menyarankan agar tidak mengasumsikan detail NSImage selain kesetaraan penggambarannya dengan sumber.

Apa yang berubah saat piksel sintetis berasal dari Metal?

Metal menambahkan kontrak kesiapan sebelum konverter CPU dapat dijalankan. Apple mendefinisikan .bgra8Unorm sebagai empat komponen unsigned 8-bit ternormalisasi dalam urutan BGRA. .bgra8Unorm_srgb pasangannya juga menetapkan konversi antara sRGB dan ruang linear. Karena itu, kecocokan urutan kanal saja tidak memastikan bahwa isi tekstur memenuhi konvensi warna dan alfa premultiplied dalam artikel ini. Format BGRA8 ternormalisasi, format BGRA8 sRGB.

Gunakan urutan berikut untuk tekstur 2D sintetis dengan satu sampel pada level mip nol:

  1. Tetapkan format piksel tekstur, interpretasi warna, konvensi alfa, dan orientasi baris yang diinginkan.
  2. Selesaikan pekerjaan GPU yang menulis tekstur dan pastikan penyelesaiannya berhasil sebelum menerima keluarannya.
  3. Jika tekstur menggunakan penyimpanan managed, enkodekan sinkronisasi GPU-ke-CPU yang diperlukan dan tunggu hingga sinkronisasi tersebut selesai.
  4. Jika tekstur menggunakan penyimpanan private, blit ke tekstur yang sesuai dan dapat diakses CPU sebelum membacanya.
  5. Alokasikan buffer tujuan dengan ukuran yang sudah diperiksa, salin wilayah yang diminta menggunakan stride tujuan sebenarnya, lalu teruskan byte hasilnya melalui kontrak CPU.

Langkah-langkah ini mencerminkan dua persyaratan terdokumentasi yang berbeda. getBytes langsung menyalin di CPU, tetapi tidak menyinkronkan operasi tekstur GPU, dan Apple secara eksplisit melarang penggunaannya langsung pada tekstur private. Secara terpisah, synchronize(resource:) memperbarui salinan CPU dari sumber daya managed; akses CPU dilakukan setelah perintah tersebut selesai. Persyaratan pembacaan balik tekstur, sinkronisasi sumber daya managed.

Pilih stride tujuan untuk operasi pembacaan balik tertentu dan pertahankan nilainya selama konversi. Untuk kontrak BGRA yang terbatas ini, setiap baris aktif memerlukan empat byte per piksel. Tata letak staging blit mungkin memerlukan padding yang berbeda dari tata letak pembacaan balik CPU langsung; catat stride pada setiap batas, alih-alih mengasumsikan semua buffer memiliki pitch yang sama.

Bagaimana cara mendiagnosis ketidakcocokan kontrak?

Gunakan fixture empat piksel sebagai alat bantu pengambilan keputusan sebelum menguji grafik hasil generasi yang lebih besar. Diagnosis berikut merupakan kesimpulan dari nilai dan tata letaknya yang diketahui, bukan hasil pengukuran dari aplikasi.

Pengamatan Periksa terlebih dahulu
Merah dan biru bertukar posisi Byte kanal sebenarnya dibandingkan dengan flag alfa dan urutan byte
Baris pertama tampak masuk akal; baris berikutnya bergeser Stride sumber, stride tujuan, dan padding
Merah transparan tampak terlalu terang atau terlalu gelap Premultiplikasi dan interpretasi warna
Pola kedua baris bertukar posisi vertikal Urutan baris produsen dan transformasi gambar
Raster benar, tetapi ukuran logis salah Dimensi AppKit dalam point
Masukan yang dibuat CPU berfungsi; masukan yang dibuat GPU tidak Penyelesaian GPU, mode penyimpanan, sinkronisasi, dan tata letak pembacaan balik

Untuk orientasi, konverter ini menjamin baris dipertahankan byte demi byte. Periksa fixture asimetris pada tampilan tujuan yang sebenarnya. Jika hasilnya terbalik secara vertikal dibandingkan desain yang diinginkan, koreksi urutan baris di batas normalisasi yang terdokumentasi atau koreksi transformasi gambar. Nyatakan keputusan itu secara eksplisit agar dua tahap tidak sama-sama membalik gambar.

Kontrak konversi piksel mentah yang lengkap memungkinkan Anda mengidentifikasi masukan dan jaminan setiap tahap: produsen menyediakan piksel yang terdefinisi, konverter memvalidasi tata letaknya dan menyimpan salinan miliknya, lalu AppKit menerima ukuran logis yang disengaja. Tetapkan batas-batas tersebut dengan fixture sintetis kecil, lalu terapkan aturan dimensi, stride, warna, alfa, dan masa berlaku yang sama pada gambar yang lebih besar.

Sumber