Untuk merasterisasi NSImageView aplikasi Anda, gunakan metode caching bitmap AppKit secara berpasangan, lalu pertahankan dimensi piksel yang dihasilkan. Jika Anda memerlukan byte yang dapat diprediksi untuk Metal, gambar ulang bitmap tersebut ke buffer Core Graphics yang didefinisikan secara eksplisit.
Panduan ini membahas gambar sintetis yang sepenuhnya dibuat di memori. Tujuannya adalah memperoleh raster yang dapat digunakan kembali dari tampilannya di NSImageView, mendiagnosis piksel yang salah tempat atau berulang, dan secara opsional mentransfer raster ke tekstur Metal.
Tentukan apakah Anda memerlukan tampilan view atau gambar berukuran pasti
Gambar dan view yang menampilkannya memiliki fungsi yang berbeda. View menerapkan mode penskalaannya untuk menyesuaikan gambar ke dalam framenya. Nilai default-nya adalah scaleProportionallyDown, sehingga mengekstrak gambar asli belum tentu mereproduksi tampilan view tersebut. Dokumentasi imageScaling dari Apple menjelaskan perilaku ini.
Tentukan hasil yang diinginkan sebelum memilih API:
| Hasil yang diinginkan | Titik awal yang sesuai | Kontrak dimensi |
|---|---|---|
| Gambar sebagaimana digambar oleh image view | Metode caching bitmap view dari AppKit | Periksa dimensi piksel bitmap yang dikembalikan |
| Gambar sintetis dengan dimensi yang ditentukan secara persis | Gambar langsung ke CGContext bitmap |
Lebar dan tinggi piksel bilangan bulat yang eksplisit |
| Byte yang dapat diprediksi untuk tekstur Metal | Gambar ulang raster ke konteks bitmap yang terdefinisi | Format piksel, stride, dan kepemilikan yang eksplisit |
Apple mendokumentasikan bitmapImageRepForCachingDisplay(in:) sebagai metode untuk membuat representasi yang sesuai dengan area view yang diminta. Teruskan representasi tersebut ke cacheDisplay(in:to:); Apple secara khusus merekomendasikan pasangan ini untuk kompatibilitas format piksel. Metode kedua menggambar view beserta turunannya. Dokumentasi alokasi bitmap, dokumentasi caching view.
Metode-metode ini tidak menyediakan argumen resolusi keluaran sembarang. Jika Anda memerlukan tepat 192 × 128 piksel, render gambar sintetis Anda langsung dengan ukuran tersebut. Memperbesar raster view yang lebih kecil setelahnya tidak dapat mengembalikan detail yang tidak pernah dirasterisasi. Ini merupakan konsekuensi dari dua kontrak keluaran yang berbeda, bukan kemampuan tambahan dari caching view.
Pisahkan point, piksel, dan persegi panjang
Gunakan view.bounds saat meminta seluruh area gambar lokal. Bounds sebuah view menjelaskan sistem koordinatnya sendiri; frame menjelaskan posisi dan ukurannya dalam sistem koordinat superview. Karena itu, meneruskan frame ke metode yang mengharapkan area lokal dapat menimbulkan offset yang tidak diinginkan. Dokumentasi bounds dari Apple.
Sebagai contoh kontrak ukuran, bitmap sumber berukuran 192 × 128 dapat ditampilkan sebagai gambar logis berukuran 96 × 64. Pertahankan kedua nilai tersebut: yang pertama menjelaskan sampel, sedangkan yang kedua menjelaskan ukuran tampilan. Inisialisator NSImage menerima ukuran eksplisit; jika Anda memberikan nol, ukurannya akan mengikuti dimensi piksel gambar sumber. Dokumentasi inisialisator gambar Apple.
Saat mendiagnosis geometri backing view yang sudah ada, gunakan convertToBacking(_:), yang mengonversi persegi panjang lokalnya menjadi koordinat backing store. Untuk raster yang benar-benar dikembalikan, periksa pixelsWide dan pixelsHigh, atau dimensi CGImage-nya. Apple mendokumentasikan bahwa cgImage milik NSBitmapImageRep memiliki dimensi piksel yang sama dengan representasi tersebut. Dokumentasi konversi backing, dokumentasi CGImage bitmap.
Jangan mengganti dimensi yang dikembalikan dengan perkalian 2× berdasarkan asumsi. Catat persegi panjang lokal yang diminta, lebar dan tinggi piksel yang dikembalikan, serta ukuran logis yang diinginkan secara bersamaan. Dengan begitu, Anda memiliki pembanding konkret saat hasil tampak diperbesar, terpotong, atau bergeser.
Buat contoh rasterisasi sintetis yang lengkap
Program Objective-C berikut membuat gambar uji asimetris, menetapkannya ke image view dalam memori, merasterisasi view tersebut, lalu menormalkan hasilnya menjadi bitmap Core Graphics. Program ini tidak membuat file apa pun. Simpan sebagai RasterDemo.m di Mac yang telah memasang alat pengembang command-line.
clang -fobjc-arc RasterDemo.m -framework Cocoa -o RasterDemo
./RasterDemo
Konteks normalisasi menggunakan komponen delapan bit, alpha prakalikan pada posisi paling signifikan, dan penyimpanan 32-bit little-endian. Pilihan-pilihan ini bersama-sama menghasilkan urutan byte BGRA: komponen biru yang paling tidak signifikan muncul lebih dulu di memori. Hal ini mengikuti definisi Apple untuk premultipliedFirst dan byteOrder32Little.
#import <Cocoa/Cocoa.h>
static CGContextRef NewBitmap(size_t w, size_t h) {
// Illustrative allocation budget, not an API dimension limit.
if (w == 0 || h == 0 || w > 4096 || h > 4096) return NULL;
size_t rowBytes = (w * 4 + 63) & ~(size_t)63;
CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
if (!space) return NULL;
CGContextRef c = CGBitmapContextCreate(
NULL, w, h, 8, rowBytes, space,
kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(space);
return c;
}
static CGImageRef NewTestImage(void) {
CGContextRef c = NewBitmap(192, 128);
if (!c) return NULL;
CGContextSetRGBFillColor(c, 1, 1, 1, 1);
CGContextFillRect(c, CGRectMake(0, 0, 192, 128));
CGContextSetRGBFillColor(c, 0, 0, 0, 1);
for (size_t x = 0; x < 192; x += 16) {
CGContextFillRect(c, CGRectMake(x, 0, 1, 128));
}
CGContextSetRGBFillColor(c, 0, 0.7, 0.8, 1);
CGContextFillRect(c, CGRectMake(8, 92, 24, 20));
CGImageRef image = CGBitmapContextCreateImage(c);
CGContextRelease(c);
return image;
}
static CGImageRef NewNormalizedImage(CGImageRef input) {
size_t w = CGImageGetWidth(input);
size_t h = CGImageGetHeight(input);
CGContextRef c = NewBitmap(w, h);
if (!c) return NULL;
CGContextClearRect(c, CGRectMake(0, 0, w, h));
CGContextSetBlendMode(c, kCGBlendModeCopy);
CGContextSetInterpolationQuality(c, kCGInterpolationNone);
CGContextDrawImage(c, CGRectMake(0, 0, w, h), input);
NSLog(@"Normalized: %zu x %zu pixels; stride %zu bytes",
w, h, CGBitmapContextGetBytesPerRow(c));
CGImageRef result = CGBitmapContextCreateImage(c);
CGContextRelease(c);
return result;
}
int main(void) {
@autoreleasepool {
[NSApplication sharedApplication];
CGImageRef source = NewTestImage();
if (!source) { NSLog(@"Source allocation failed"); return 1; }
NSImage *image = [[NSImage alloc] initWithCGImage:source
size:NSMakeSize(96, 64)];
CGImageRelease(source);
NSImageView *view = [[NSImageView alloc]
initWithFrame:NSMakeRect(0, 0, 96, 64)];
view.image = image;
view.imageScaling = NSImageScaleNone;
view.imageAlignment = NSImageAlignCenter;
view.imageFrameStyle = NSImageFrameNone;
[view layoutSubtreeIfNeeded];
NSRect region = view.bounds;
NSBitmapImageRep *rep =
[view bitmapImageRepForCachingDisplayInRect:region];
if (!rep) { NSLog(@"View bitmap allocation failed"); return 1; }
[view cacheDisplayInRect:region toBitmapImageRep:rep];
CGImageRef cached = rep.CGImage;
if (!cached) { NSLog(@"View bitmap has no CGImage"); return 1; }
CGImageRef raster = NewNormalizedImage(cached);
if (!raster) { NSLog(@"Normalization failed"); return 1; }
NSImage *result = [[NSImage alloc] initWithCGImage:raster
size:region.size];
NSLog(@"Result logical size: %@", NSStringFromSize(result.size));
CGImageRelease(raster);
}
return 0;
}
Program ini sengaja melaporkan dimensi keluaran aktual, alih-alih menyatakan bahwa cache view cocok dengan piksel sumber berukuran 192 × 128. Penanda sian memudahkan membedakan perubahan orientasi dari garis uji vertikal yang berulang. Jika Anda memerlukan resolusi sumber secara persis, gunakan langsung CGImage yang dikembalikan oleh NewTestImage(), tanpa melewati view.
Penghapusan eksplisit menetapkan piksel tujuan transparan sebelum normalisasi. Penggambaran satu gambar mengisi persegi panjang tujuan; gambar tidak ditile. Apple mendokumentasikan bahwa operasi penggambaran Core Graphics terkait menskalakan gambar ke persegi panjang tujuan dan menggambar satu salinan kecuali tiling diaktifkan. Dokumentasi penggambaran gambar Apple.
Program ini telah diperiksa, tetapi tidak dapat dikompilasi di lingkungan riset Linux, yang tidak memiliki AppKit dan compiler C. Perhitungan stride yang digunakan di bawah ini telah diperiksa dengan kalkulasi Python deterministik sederhana.
Diagnosis piksel bergeser atau berulang secara berurutan
Urutan berikut merupakan panduan diagnosis yang diturunkan dari kontrak geometri dan memori di atas. Gejalanya menunjukkan bagian yang perlu diperiksa terlebih dahulu; gejala tersebut tidak memastikan satu penyebab tertentu.
- Periksa persegi panjang yang diminta. Untuk seluruh image view, bandingkan permintaan cache dengan
bounds. Jika mengekspor subwilayah, sertakan asal lokalnya dalam kontrak masukan. - Periksa dimensi sebelum byte mentah. Bandingkan lebar dan tinggi gambar yang di-cache dengan dimensi yang diberikan kepada setiap pemroses berikutnya. Menggunakan lebar dalam point sebagai pengganti lebar dalam piksel akan mengubah interpretasi buffer.
- Periksa stride baris. Jika setiap baris mulai semakin jauh dari posisi yang semestinya, periksa jarak antara awal baris sebelum menyesuaikan transformasi.
- Periksa orientasi dengan penanda asimetris. Pembalikan vertikal mengharuskan Anda memeriksa konvensi koordinat. Pergeseran konstan mengharuskan Anda memeriksa titik asal dan translasi.
- Periksa apakah pengulangan memang disengaja. Periksa setiap pemanggilan penggambaran yang mengaktifkan tiling. Mengubah stride baris tidak akan memperbaiki penggambaran yang secara eksplisit ditile.
- Terakhir, periksa penskalaan tampilan. Jika raster tersimpan sudah benar, tetapi hasil yang ditampilkan berbeda, bandingkan ukuran logis gambar keluaran dan pengaturan penskalaan image view penerima.
Untuk piksel empat byte, buffer contoh 192 × 128 memiliki 768 byte per baris dan memerlukan 98,304 byte. Namun buffer 191 × 128 dengan perataan baris 64 byte dalam contoh juga memiliki stride 768 byte: setiap baris mencakup empat byte padding. Melangkah sebesar 191 × 4, atau 764, akan memulai setiap baris berikutnya empat byte terlalu awal. Apple menyediakan stride aktual melalui properti bytesPerRow milik konteks.
Gunakan kalkulasi alamat base + y * bytesPerRow + x * 4 hanya untuk format empat byte ternormalisasi yang ditetapkan di sini. Bitmap AppKit sembarang memiliki kontrak formatnya sendiri; jangan menyimpulkan tata letak komponennya hanya dari tampilannya.
Demikian pula, terapkan perubahan koordinat dengan sengaja. Core Graphics menyediakan operasi terpisah untuk mengubah titik asal dan skala ruang pengguna. Jika Anda menerapkan pembalikan vertikal, dokumentasikan tahap yang menangani konversi tersebut dan verifikasikan dengan penanda sebelum menambahkan pembalikan lain. Dokumentasi translasi, dokumentasi skala.
Pertahankan kepemilikan saat mengembalikan gambar atau mengunggah ke Metal
Contoh ini mempertahankan representasi AppKit tetap hidup saat menggambar CGImage-nya. Lalu, contoh tersebut mengembalikan snapshot yang dibuat dari konteks normalisasi. Apple mendokumentasikan bahwa makeImage() membuat salinan, yang secara internal mungkin menggunakan copy-on-write, dan penggambaran berikutnya ke konteks tidak mengubah gambar yang dikembalikan. Karena itu, konteks dapat dilepas setelah snapshot dibuat. Dokumentasi snapshot bitmap Apple.
Penunjuk bitmap mentah memiliki masa hidup yang berbeda. Apple menyediakan akses ke data bitmap milik konteks meskipun konteks dibuat dengan argumen data null. Anggap penunjuk tersebut dimiliki konteks yang masih aktif; untuk pemroses berikutnya, salin byte ke penyimpanan milik Anda atau pertahankan konteks hingga pemrosesan selesai. Dokumentasi data bitmap Apple.
Untuk mengunggah ke Metal, lakukan langkah-langkah berikut selagi konteks normalisasi masih aktif:
- Buat tekstur dua dimensi dengan lebar dan tinggi piksel konteks. Gunakan
bgra8Unormuntuk tata letak komponen BGRA yang didemonstrasikan; Apple mendefinisikannya sebagai empat komponen unsigned ternormalisasi delapan bit dalam urutan BGRA. - Untuk mengunggah dari CPU, pertahankan mode penyimpanan default sistem dan jangan memaksakan mode bersama khusus perangkat keras. Apple mendokumentasikan default bersama pada Apple silicon dan default terkelola untuk tekstur pada Mac berbasis Intel.
- Panggil
replace(region:mipmapLevel:withBytes:bytesPerRow:)untuk seluruh wilayah tekstur, level mip0, dengan penunjuk data konteks dan stride baris aktual. - Selesaikan pengunggahan sebelum melepas konteks. Untuk penggunaan ulang tekstur, pastikan juga pekerjaan GPU sebelumnya yang menggunakan tekstur tersebut telah selesai sebelum CPU menimpanya.
Pilihan format dan penyimpanan mengikuti dokumentasi bgra8Unorm Apple dan panduan penyimpanan sumber daya. Apple menyatakan bahwa metode pengganti langsung menyalin piksel yang diberikan, tetapi tidak menyinkronkan operasi GPU. Untuk tekstur privat, jalur yang didokumentasikan adalah menggunakan tekstur sementara nonprivat, lalu melakukan blit. Dokumentasi penggantian tekstur Apple.
Pengunggahan tekstur mempertahankan byte yang diberikan; mencocokkan tata letak komponen saja tidak menentukan bagian lain dari alur render Anda. Sertakan keputusan alpha prakalikan dan sRGB dari contoh ini dalam rancangan pemroses. Normalisasi adalah penggambaran ulang ke format yang dipilih, jadi jangan menyebutnya sebagai jaminan konversi yang identik byte demi byte dari setiap representasi AppKit yang mungkin.
Untuk tugas ini, serah terima yang andal mencakup raster beserta dimensi piksel, ukuran tampilan logis, stride baris, format komponen, konvensi orientasi, dan pemiliknya. Simpan nilai-nilai tersebut bersama-sama, uji dengan gambar sintetis asimetris, dan perbaiki tahap pertama saat nilai-nilai itu mulai menyimpang. Dengan begitu, masalah hasil yang bergeser atau berulang dapat dilacak ke kontrak tertentu.
Sumber
cacheDisplay(in:to:) — Apple
imageScaling — Apple
bounds — Apple
init(cgImage:size:) — Apple
convertToBacking(_:) — Apple
cgImage — Apple
byteOrder32Little — Apple
draw(_:in:byTiling:) — Apple
bytesPerRow — Apple
translateBy(x:y:) — Apple
scaleBy(x:y:) — Apple
makeImage() — Apple
data — Apple
MTLPixelFormat.bgra8Unorm — Apple
Setting resource storage modes — Apple