Untuk menukar piksel mentah sintetik menjadi CGImage yang boleh dipercayai, takrifkan dimensinya, jarak baris, susunan saluran, konvensyen alfa, ruang warna dan pemilikan secara bersama. Panduan ini membina penukar BGRA yang disemak, kemudian menetapkan saiz paparan AppKit yang jelas untuk hasilnya.
Tugas pembaca ialah mencipta imej dalam memori untuk swatch, rajah atau pratonton yang dijana oleh aplikasi Mac. Pelaksanaan di bawah menerima satu format yang sengaja dihadkan skopnya. Satu urutan keputusan yang berasingan menerangkan cara output Metal sintetik boleh sampai ke sempadan penukaran yang sama.
Apakah yang mesti dinyatakan dalam kontrak penukaran?
Pemula CGImage data mentah Apple menjangkakan bait pembekal sepadan dengan parameter imej yang diberikan. Lebar dan tingginya ialah piksel; bytesPerRow menerangkan susun atur memori bagi setiap baris mendatar. Parameter ini menerangkan penimbal input, jadi tetapkan semuanya sebelum membina imej. Dokumentasi pemula imej bitmap Apple.
Gunakan kontrak peringkat aplikasi ini untuk penukar dalam artikel ini. Peraturan pengesahannya yang ketat ialah pilihan reka bentuk yang menjadikan input yang diterima tidak samar.
| Sifat | Input atau tingkah laku yang diterima |
|---|---|
| Dimensi | Lebar dan tinggi integer positif, diukur dalam piksel |
| Komponen | Empat komponen 8-bit tanpa tanda bagi setiap piksel |
| Susunan memori | Bait berturutan ialah biru, hijau, merah, alfa |
| Alfa | Komponen RGB sudah dipram darab dengan alfa |
| Tafsiran warna | sRGB |
| Jarak baris | Sekurang-kurangnya width * 4 bait dan boleh dibahagi dengan empat |
| Panjang penimbal | Tepat bytesPerRow * height, termasuk padding baris terakhir |
| Jujukan baris | Dikekalkan tepat; tiada pembalikan menegak automatik |
| Pemilikan | Salin input ke dalam storan milikan yang tidak boleh diubah |
| Kegagalan | Lempar ralat untuk input tidak sah atau pembinaan yang gagal |
| Persembahan | Pilih dimensi titik AppKit secara berasingan |
Sebagai contoh, baris dua piksel mengandungi lapan bait aktif. Jarak sebanyak dua belas menambah empat bait padding selepas setiap baris. Dua baris sedemikian menggunakan dua puluh empat bait. Piksel pertama baris kedua bermula pada bait kedua belas. Susun atur contoh ini sengaja menggunakan padding: ujian yang dibungkus rapat tidak akan mengesan penukar yang tersilap menggantikan jarak yang dibekalkan dengan width * 4.
Mewajibkan padding penuh selepas baris terakhir memudahkan sempadan pemilikan. Pengeluar yang menggunakan peruntukan baris terakhir yang lebih pendek mesti menormalkan outputnya sebelum memanggil penukar ini. Menolak panjang yang tidak sepadan juga mengelakkan penerimaan tidak sengaja bagi penimbal yang lebih besar sedangkan bahagian imej yang dimaksudkan tidak jelas.
Bagaimanakah BGRA, susunan bait, alfa dan ruang warna saling berkaitan?
Untuk format ini, gabungkan CGImageAlphaInfo.premultipliedFirst dengan CGBitmapInfo.byteOrder32Little. Apple mentakrifkan yang pertama sebagai alfa dalam bit paling signifikan dengan komponen warna yang sudah didarab dengan alfa, dan yang kedua sebagai susun atur 32-bit little-endian. Bersama-sama, takrifan ini menghasilkan jujukan bait alamat menaik BGRA. Alfa premultiplied-first, susunan bitmap 32-bit little-endian.
Oleh itu, perkataan “first” memerlukan tetapan susunan bait untuk menentukan kedudukan alfa dalam memori. Bagi merah legap, empat bait yang digunakan di sini ialah 0, 0, 255, 255. Membekalkan bait RGBA dengan tetapan ini akan menukar kedudukan merah dan biru. Mengubah metadata tidak dapat menyusun semula penimbal sedia ada.
Pemramdaraban juga mempunyai makna yang nyata. Dalam konvensyen 8-bit ilustrasi ini, warna merah penuh dengan alfa 128 menjadi kira-kira 0, 0, 128, 128 dalam memori BGRA. Penukar menyemak bahawa setiap komponen warna tersimpan tidak melebihi alfa. Ini mengesan nilai yang mustahil bagi format premultiplied ternormal pilihannya, walaupun ia tidak dapat membuktikan cara pengeluar mengira setiap komponen.
Tetapkan CGColorSpace.sRGB hanya apabila bait memang dimaksudkan untuk ditafsirkan sedemikian. Apple menetapkan kolorimetri sRGB serta fungsi pemindahan tak linearnya. Label ruang warna sahaja tidak menukarkan nilai komponen RGB linear kepada nilai sRGB. Takrif sRGB Apple.
Penukar ini melakukan pengesahan dan pembinaan imej. Jika pengeluar mengeluarkan alfa terus, susunan RGBA, komponen titik terapung atau nilai warna linear, tambahkan langkah penormalan yang jelas sebelum sempadan ini, atau laksanakan kontrak berasingan yang didokumenkan untuk format tersebut.
Bagaimanakah Swift boleh membina imej dengan storan milikan?
Gunakan sempadan penyalinan untuk imej desktop kecil yang dijana. Apple mendokumenkan bahawa CFDataCreate sentiasa menyalin penimbal yang dibekalkan ke dalam storan dalaman, dan CGDataProvider(data:) membaca objek CFData. Pemula CGImage mengekalkan pembekalnya. Ini mengelakkan imej daripada terikat pada penuding tatasusunan sementara. Menyalin bait ke dalam CFData, mencipta pembekal berasaskan CFData.
Berikut ialah program baris perintah Swift macOS yang lengkap. Simpan sebagai main.swift dan jalankan swift main.swift pada Mac yang mempunyai alat pembangun terpasang. Program ini membina kedua-dua objek imej dan mencetak dimensinya; ia tidak mencipta tetingkap. Kod Swift ini telah diperiksa tetapi tidak dapat dikompil dalam persekitaran Linux ini. Bilangan bait, ofset dan had komponen premultiplied bagi datanya diperiksa secara berasingan 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)")
}
Pendaraban yang disemak dijalankan sebelum sebarang pengindeksan atau penyalinan penimbal. Setelah semakan panjang dan jarak berjaya, setiap piksel aktif berada dalam tatasusunan yang dibekalkan. Padding dikekalkan tetapi tidak disertakan dalam pengesahan piksel. Kes ralat membezakan dimensi, jarak, panjang, nilai alfa dan kegagalan pembinaan yang tidak sah; aplikasi sebenar boleh memaparkan atau merekodkan ralat tersebut melalui pengendalian ralat sedia adanya.
Baris tersimpan pertama dalam data contoh mengandungi merah legap dan hijau legap. Baris kedua mengandungi biru legap dan merah yang kira-kira separuh telus. decode: nil meminta tiada pemetaan semula nyahkod, manakala shouldInterpolate: false meminta pembinaan tanpa pelicinan piksel. Parameter ini tidak mengubah saiz atau menulis semula input. Parameter pembinaan imej Apple.
Untuk pengoptimuman kemudian, Apple turut menyediakan pemula pembekal akses terus dengan panggil balik pelepasan. Laluan itu memerlukan kontrak jangka hayat peruntukan yang berasingan: pastikan peruntukan itu kekal sah semasa Core Graphics menggunakannya, dan lepaskannya melalui panggil balik. Anggap ia sebagai pelaksanaan berbeza yang pemilikannya mesti direka secara jelas. Pemula pembekal data akses terus.
Bagaimanakah dimensi piksel patut ditukar kepada saiz paparan AppKit?
Anggap dimensi raster dan dimensi logik sebagai input yang berasingan. Apple membezakan saiz representasi yang diukur dalam titik daripada lebar dan tinggi yang diukur dalam piksel. NSImage(cgImage:size:) menerima saiz imej yang dikehendaki; menghantar .zero menyebabkan ia menggunakan dimensi piksel imej sumber secara berangka. Dimensi representasi imej, pemula CGImage AppKit.
Sebagai contoh keputusan saiz, raster 400 × 200 piksel yang dimaksudkan untuk membekalkan dua piksel bagi setiap titik patut mempunyai saiz NSImage sebanyak 200 × 100 titik. Pengiraan itu menyatakan saiz logik yang dimaksudkan. CGImage yang mendasarinya kekal berukuran 400 × 200 piksel; pembalut itu tidak mencipta sampel tambahan.
Program ini memilih dua titik bagi setiap dimensi untuk data contoh dua pikselnya. Untuk pratonton yang lebih besar, pilih saiz logik yang jelas dan sesuai untuk paparan. Kekalkan CGImage jika operasi seterusnya memerlukan dimensi raster yang tepat, kerana Apple menasihatkan supaya tidak mengandaikan butiran pembalut NSImage selain kesetaraan lukisannya dengan sumber.
Apakah yang berubah apabila piksel sintetik datang daripada Metal?
Metal menambah kontrak kesediaan sebelum penukar CPU boleh dijalankan. Apple mentakrifkan .bgra8Unorm sebagai empat komponen 8-bit tanpa tanda ternormal dalam susunan BGRA. Padanan .bgra8Unorm_srgb turut menetapkan penukaran antara ruang sRGB dengan ruang linear. Oleh itu, padanan susunan saluran sahaja tidak memastikan kandungan tekstur memenuhi konvensyen warna dan alfa premultiplied artikel ini. Format ternormal BGRA8, format sRGB BGRA8.
Gunakan urutan ini untuk tekstur 2D sintetik bersampel tunggal pada aras mip sifar:
- Tetapkan format piksel tekstur, tafsiran warna, konvensyen alfa dan orientasi baris yang dimaksudkan.
- Selesaikan kerja GPU yang menulis tekstur dan sahkan kejayaan penyelesaiannya sebelum menerima outputnya.
- Jika tekstur menggunakan storan terurus, kodkan penyegerakan GPU-ke-CPU yang diperlukan dan tunggu sehingga penyegerakan itu selesai.
- Jika tekstur menggunakan storan peribadi, blitkannya ke tekstur yang sesuai dan boleh diakses CPU sebelum membacanya.
- Peruntukkan penimbal destinasi yang saiznya disemak, salin rantau yang diminta menggunakan jarak destinasi sebenar, kemudian hantar bait yang terhasil melalui kontrak CPU.
Langkah ini mencerminkan dua keperluan terdokumen yang berbeza. getBytes terus menyalin pada CPU tetapi tidak menyegerakkan operasi tekstur GPU, dan Apple secara jelas tidak membenarkan penggunaannya secara langsung pada tekstur peribadi. Secara berasingan, synchronize(resource:) mengemas kini salinan CPU bagi sumber terurus; akses CPU boleh dilakukan selepas arahan itu selesai. Keperluan pembacaan semula tekstur, penyegerakan sumber terurus.
Pilih jarak destinasi untuk operasi pembacaan semula yang khusus dan kekalkannya sepanjang penukaran. Bagi kontrak BGRA yang terhad ini, setiap baris aktif memerlukan empat bait bagi setiap piksel. Susun atur staging blit mungkin memerlukan padding berbeza daripada susun atur pembacaan semula CPU secara langsung; rekodkan jarak pada setiap sempadan dan bukannya mengandaikan semua penimbal berkongsi pitch yang sama.
Bagaimanakah ketidakpadanan kontrak boleh didiagnosis?
Gunakan data contoh empat piksel sebagai panduan keputusan sebelum menguji grafik janaan yang lebih besar. Diagnosis berikut ialah inferens daripada nilai dan susun aturnya yang diketahui, bukannya hasil yang diukur daripada aplikasi.
| Pemerhatian | Perkara pertama yang perlu diperiksa |
|---|---|
| Merah dan biru bertukar tempat | Bait saluran sebenar berbanding tanda alfa dan susunan bait |
| Baris pertama kelihatan munasabah; baris seterusnya beralih | Jarak sumber, jarak destinasi dan padding |
| Merah lut sinar kelihatan terlalu terang atau terlalu gelap | Pemramdaraban dan tafsiran warna |
| Corak dua baris bertukar kedudukan menegak | Susunan baris pengeluar dan transformasi lukisan |
| Raster betul tetapi saiz logik salah | Dimensi titik AppKit |
| Input yang dijana CPU berfungsi; input yang dijana GPU tidak | Penyelesaian GPU, mod storan, penyegerakan dan susun atur pembacaan semula |
Dari segi orientasi, penukar ini menjanjikan pengekalan baris bait demi bait. Periksa data contoh tak simetri dalam paparan destinasi sebenar. Jika hasilnya terbalik secara menegak berbanding reka bentuk yang dimaksudkan, betulkan susunan baris pada sempadan penormalan yang didokumenkan atau betulkan transformasi lukisan. Pastikan keputusan itu jelas supaya dua peringkat tidak kedua-duanya membalikkan imej.
Kontrak penukaran piksel mentah yang lengkap membolehkan anda mengenal pasti apa yang diterima dan dijamin oleh setiap peringkat: pengeluar membekalkan piksel yang ditakrifkan, penukar mengesahkan susun aturnya dan memiliki salinan, manakala AppKit menerima saiz logik yang disengajakan. Tetapkan sempadan ini dengan data contoh sintetik yang kecil, kemudian bawa peraturan dimensi, jarak, warna, alfa dan jangka hayat yang sama ke imej yang lebih besar.
Sumber
- 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