Pour rasteriser la NSImageView de votre app, utilisez les méthodes AppKit jumelées de mise en cache bitmap, puis préservez les dimensions en pixels obtenues. Si vous avez besoin d’octets prévisibles pour Metal, redessinez ce bitmap dans un tampon Core Graphics défini explicitement.
Ce guide porte sur une image synthétique générée entièrement en mémoire. L’objectif est d’obtenir une rasterisation réutilisable de son affichage dans une NSImageView, de diagnostiquer les pixels mal placés ou répétés, et éventuellement de transférer la rasterisation vers une texture Metal.
Déterminez si vous avez besoin de l’affichage de la vue ou d’une image de dimensions exactes
Une image et la vue qui l’affiche ont des responsabilités différentes. La vue applique son mode de mise à l’échelle pour adapter l’image à son cadre. Le mode par défaut est scaleProportionallyDown, donc extraire l’image d’origine ne reproduit pas nécessairement son affichage dans la vue. La documentation d’Apple sur imageScaling décrit ce comportement.
Choisissez la sortie avant de choisir l’API :
| Sortie souhaitée | Point de départ approprié | Contrat de dimensions |
|---|---|---|
| L’image telle qu’elle est dessinée par la vue d’image | Méthodes AppKit de mise en cache bitmap d’une vue | Vérifiez les dimensions en pixels du bitmap renvoyé |
| Un dessin synthétique de dimensions précises | Dessinez directement dans un CGContext bitmap |
Largeur et hauteur explicites en pixels entiers |
| Des octets prévisibles pour une texture Metal | Redessinez la rasterisation dans un contexte bitmap défini | Format de pixels, foulée et propriété explicites |
Apple indique que bitmapImageRepForCachingDisplay(in:) crée une représentation adaptée à la région de vue demandée. Transmettez cette représentation à cacheDisplay(in:to:) ; Apple recommande spécifiquement cette association pour assurer la compatibilité du format de pixels. La seconde méthode dessine la vue et ses descendants. Documentation sur l’allocation de bitmap, documentation sur la mise en cache de vues.
Ces méthodes ne permettent pas de spécifier une résolution de sortie arbitraire. Si vous avez besoin d’exactement 192 × 128 pixels, effectuez le rendu de votre dessin synthétique directement à cette taille. Agrandir ensuite une rasterisation de vue plus petite ne peut pas restaurer des détails qui n’ont jamais été rasterisés. Cela découle des deux contrats de sortie différents, et ne constitue pas une capacité supplémentaire de la mise en cache de vues.
Distinguez les points, les pixels et les rectangles
Utilisez view.bounds pour demander toute la région de dessin locale. Les limites d’une vue décrivent son propre système de coordonnées ; son cadre décrit sa position et sa taille dans le système de coordonnées de sa supervue. Transmettre le cadre à une méthode qui attend une région locale peut donc introduire un décalage involontaire. Documentation d’Apple sur bounds.
À titre d’exemple de contrat de dimensions, un bitmap source de 192 × 128 peut être affiché comme une image logique de 96 × 64. Préservez les deux valeurs : la première décrit les échantillons, tandis que la seconde décrit la taille d’affichage. L’initialiseur NSImage accepte une taille explicite ; si vous indiquez zéro, il adopte les dimensions en pixels de l’image source. Documentation d’Apple sur l’initialiseur d’image.
Pour diagnostiquer la géométrie de la surface de rendu d’une vue existante, utilisez convertToBacking(_:), qui convertit son rectangle local en coordonnées de la surface de rendu. Pour la rasterisation effectivement renvoyée, examinez pixelsWide et pixelsHigh, ou les dimensions de son CGImage. Apple précise que cgImage d’un NSBitmapImageRep a les mêmes dimensions en pixels que la représentation. Documentation sur la conversion vers les coordonnées de la surface de rendu, documentation sur CGImage des bitmaps.
Ne remplacez pas ces dimensions renvoyées par un multiplicateur 2× présumé. Notez ensemble le rectangle local demandé, la largeur et la hauteur en pixels renvoyées, ainsi que la taille logique prévue. Vous disposerez ainsi d’une comparaison concrète si le résultat semble agrandi, recadré ou décalé.
Créez un exemple complet de rasterisation synthétique
Le programme Objective-C suivant génère une image de test asymétrique, l’affecte à une vue d’image en mémoire, rasterise la vue, puis normalise le résultat dans un bitmap Core Graphics. Il ne crée aucun fichier. Enregistrez-le sous le nom RasterDemo.m sur un Mac où les outils de développement en ligne de commande sont installés.
clang -fobjc-arc RasterDemo.m -framework Cocoa -o RasterDemo
./RasterDemo
Le contexte de normalisation utilise des composants de huit bits, un alpha prémultiplié à la position de poids fort et un stockage 32 bits petit-boutiste. Ces choix produisent ensemble un ordre d’octets BGRA : le composant bleu de poids faible apparaît en premier en mémoire. Cela découle des définitions d’Apple de premultipliedFirst et de 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;
}
Le programme affiche volontairement les dimensions réelles de sa sortie au lieu d’affirmer que le cache de la vue correspond aux 192 × 128 pixels de la source. Le repère cyan permet de distinguer les changements d’orientation des lignes de test verticales répétées. Si vous avez besoin de la résolution exacte de la source, utilisez directement le CGImage renvoyé par NewTestImage() au lieu de passer par la vue.
L’effacement explicite définit les pixels transparents de destination avant la normalisation. Le dessin d’une seule image remplit le rectangle de destination ; il ne crée pas de mosaïque. Apple indique que l’opération de dessin Core Graphics correspondante adapte l’échelle au rectangle de destination et dessine une seule copie, sauf si la mosaïque est activée. Documentation d’Apple sur le dessin d’images.
Ce programme a été examiné, mais n’a pas pu être compilé dans l’environnement de recherche Linux, qui ne dispose ni d’AppKit ni d’un compilateur C. Les calculs de foulée ci-dessous ont été vérifiés avec un petit calcul Python déterministe.
Diagnostiquez les pixels décalés ou répétés dans un ordre précis
La séquence suivante est un outil de diagnostic fondé sur les contrats de géométrie et de mémoire décrits plus haut. Les symptômes indiquent ce qu’il faut examiner en premier, sans établir une cause unique.
- Vérifiez le rectangle demandé. Pour obtenir l’image entière de la vue, comparez la demande de mise en cache avec
bounds. Si vous exportez une région, incluez son origine locale dans le contrat d’entrée. - Vérifiez les dimensions avant les octets bruts. Comparez la largeur et la hauteur de l’image mise en cache aux dimensions transmises à chaque consommateur ultérieur. Remplacer une largeur en pixels par une largeur en points modifie l’interprétation du tampon.
- Vérifiez la foulée des lignes. Si chaque ligne commence progressivement plus loin que prévu, examinez la distance entre les débuts de ligne avant de modifier les transformations.
- Vérifiez l’orientation avec le repère asymétrique. Une inversion verticale invite à examiner les conventions de coordonnées. Un décalage constant invite à examiner les origines et les translations.
- Vérifiez si la répétition est intentionnelle. Examinez les appels de dessin qui activent la mosaïque. Modifier la foulée des lignes ne corrigera pas un dessin explicitement répété en mosaïque.
- Vérifiez la mise à l’échelle de l’affichage en dernier. Si la rasterisation stockée est correcte, mais que le résultat affiché diffère, comparez la taille logique de l’image de sortie et les réglages de mise à l’échelle de la vue d’image qui la reçoit.
Pour des pixels de quatre octets, le tampon d’exemple de 192 × 128 comporte 768 octets par ligne et nécessite 98,304 octets. Mais un tampon de 191 × 128 avec l’alignement des lignes de 64 octets de l’exemple a également une foulée de 768 octets : chaque ligne comprend quatre octets de remplissage. Avancer de 191 × 4, soit 764, ferait commencer chaque ligne suivante quatre octets trop tôt. Apple expose la foulée réelle via la propriété bytesPerRow du contexte.
N’utilisez le calcul d’adresse base + y * bytesPerRow + x * 4 que pour le format normalisé de quatre octets défini ici. Un bitmap AppKit quelconque possède son propre contrat de format ; n’en déduisez pas la disposition des composants à partir de son seul aspect.
De même, appliquez les changements de coordonnées de manière délibérée. Core Graphics propose des opérations distinctes pour modifier l’origine et l’échelle de l’espace utilisateur. Si vous introduisez une inversion verticale, indiquez quelle étape effectue cette conversion et vérifiez-la avec le repère avant d’en ajouter une autre. Documentation sur la translation, documentation sur la mise à l’échelle.
Préservez la propriété des données lors du renvoi d’une image ou du transfert vers Metal
L’exemple conserve la représentation AppKit en vie pendant le dessin de son CGImage. Il renvoie ensuite un instantané créé à partir du contexte de normalisation. Apple indique que makeImage() crée une copie, éventuellement au moyen d’une copie sur écriture en interne, et que les dessins ultérieurs dans le contexte ne modifient pas l’image renvoyée. Cela permet de libérer le contexte après la création de l’instantané. Documentation d’Apple sur les instantanés bitmap.
Un pointeur de bitmap brut a une durée de vie différente. Apple permet d’accéder aux données bitmap appartenant au contexte même lorsque celui-ci a été créé avec un argument de données nul. Considérez ce pointeur comme appartenant au contexte tant que celui-ci est en vie ; pour un consommateur ultérieur, copiez les octets dans un stockage qui vous appartient ou gardez le contexte en vie jusqu’à la fin de la consommation. Documentation d’Apple sur les données bitmap.
Pour un transfert vers Metal, effectuez ces étapes tant que le contexte normalisé est encore en vie :
- Créez une texture bidimensionnelle avec la largeur et la hauteur en pixels du contexte. Utilisez
bgra8Unormpour la disposition de composants BGRA présentée ; Apple la définit comme quatre composants normalisés non signés de huit bits, dans l’ordre BGRA. - Pour un transfert depuis le processeur, conservez le mode de stockage par défaut du système au lieu d’imposer un mode partagé spécifique au matériel. Apple indique que le mode par défaut est partagé sur Apple silicon et géré pour les textures sur les Mac à processeur Intel.
- Appelez
replace(region:mipmapLevel:withBytes:bytesPerRow:)pour la région entière de la texture, au niveau de mipmap0, en utilisant le pointeur de données du contexte et sa foulée réelle. - Terminez le transfert avant de libérer le contexte. Pour réutiliser la texture, assurez-vous également que les opérations GPU antérieures utilisant cette texture sont terminées avant que le processeur ne la remplace.
Les choix de format et de stockage suivent la documentation d’Apple sur bgra8Unorm et ses conseils sur le stockage des ressources. Apple précise que la méthode de remplacement copie immédiatement les pixels fournis, mais ne synchronise pas les opérations GPU. Pour une texture privée, la procédure documentée consiste à utiliser une texture temporaire non privée, puis un blit. Documentation d’Apple sur le remplacement de texture.
Le transfert vers la texture préserve les octets fournis ; faire correspondre uniquement la disposition des composants ne définit pas le reste de votre pipeline de rendu. Reprenez les choix de l’exemple concernant l’alpha prémultiplié et sRGB dans la conception du consommateur. La normalisation consiste à redessiner dans un format choisi ; elle ne doit donc pas être présentée comme une garantie de conversion identique octet par octet à partir de toutes les représentations AppKit possibles.
Pour cette tâche, un transfert fiable comprend la rasterisation ainsi que ses dimensions en pixels, sa taille logique d’affichage, sa foulée de lignes, son format de composants, sa convention d’orientation et son propriétaire. Gardez ces valeurs ensemble, testez avec un dessin synthétique asymétrique et corrigez la première étape où elles divergent. Vous pourrez ainsi remonter jusqu’au contrat responsable d’un résultat décalé ou répété.
Sources
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