Para rasterizar el NSImageView de tu app, usa los métodos emparejados de caché de mapas de bits de AppKit y conserva las dimensiones en píxeles resultantes. Si necesitas bytes predecibles para Metal, vuelve a dibujar ese mapa de bits en un búfer de Core Graphics definido explícitamente.
Esta guía abarca una imagen sintética generada por completo en memoria. El objetivo es obtener una rasterización reutilizable de cómo se presenta en un NSImageView, diagnosticar píxeles desplazados o repetidos y, opcionalmente, transferir la rasterización a una textura de Metal.
Elige si necesitas la presentación de la vista o una imagen de tamaño exacto
Una imagen y la vista que la muestra tienen responsabilidades distintas. La vista aplica su modo de escalado para ajustar la imagen a su marco. El valor predeterminado es scaleProportionallyDown, por lo que extraer la imagen original no necesariamente reproduce cómo la presenta la vista. La documentación de Apple sobre imageScaling describe este comportamiento.
Elige el resultado antes de elegir la API:
| Resultado deseado | Punto de partida adecuado | Contrato de dimensiones |
|---|---|---|
| La imagen tal como la dibuja la vista de imagen | Métodos de caché de mapas de bits de vistas de AppKit | Inspecciona las dimensiones en píxeles del mapa de bits devuelto |
| Un dibujo sintético con dimensiones especificadas exactas | Dibuja directamente en un CGContext de mapa de bits |
Ancho y alto explícitos en píxeles enteros |
| Bytes predecibles para una textura de Metal | Vuelve a dibujar la rasterización en un contexto de mapa de bits definido | Formato de píxel, paso de fila y propiedad explícitos |
Apple documenta bitmapImageRepForCachingDisplay(in:) como un método que crea una representación adecuada para la región de vista solicitada. Pasa esa representación a cacheDisplay(in:to:); Apple recomienda específicamente este emparejamiento por compatibilidad con el formato de píxel. El segundo método dibuja la vista y sus descendientes. Documentación sobre la asignación de mapas de bits, documentación sobre el almacenamiento en caché de vistas.
Estos métodos no exponen un argumento de resolución de salida arbitrario. Si necesitas exactamente 192 × 128 píxeles, renderiza el dibujo sintético directamente a ese tamaño. Cambiar después el tamaño de una rasterización de vista más pequeña no puede recuperar detalles que nunca se rasterizaron. Esto se debe a que los dos contratos de salida son distintos; el almacenamiento en caché de vistas no añade esa capacidad.
Mantén separados los puntos, los píxeles y los rectángulos
Usa view.bounds al solicitar toda la región de dibujo local. Los límites de una vista describen su propio sistema de coordenadas; su marco describe su posición y tamaño en el sistema de coordenadas de su supervista. Por lo tanto, pasar el marco a un método que espera una región local puede introducir un desplazamiento involuntario. Documentación de Apple sobre bounds.
Como ejemplo del contrato de tamaño, una imagen de origen de 192 × 128 píxeles podría mostrarse como una imagen lógica de 96 × 64. Conserva ambos valores: el primero describe las muestras y el segundo, el tamaño de presentación. El inicializador de NSImage acepta un tamaño explícito; si se proporciona cero, adopta las dimensiones en píxeles de la imagen de origen. Documentación de Apple sobre el inicializador de imagen.
Para diagnosticar la geometría del búfer de una vista existente, usa convertToBacking(_:), que convierte su rectángulo local a coordenadas del búfer de respaldo. Para la rasterización devuelta, inspecciona pixelsWide y pixelsHigh, o las dimensiones de su CGImage. Apple documenta que cgImage de un NSBitmapImageRep tiene las mismas dimensiones en píxeles que la representación. Documentación sobre la conversión al búfer de respaldo, documentación sobre CGImage de mapas de bits.
No sustituyas esas dimensiones devueltas por un multiplicador 2× supuesto. Registra conjuntamente el rectángulo local solicitado, el ancho y el alto devueltos en píxeles y el tamaño lógico previsto. Así tendrás una comparación concreta cuando el resultado parezca ampliado, recortado o desplazado.
Crea un ejemplo completo de rasterización sintética
El siguiente programa en Objective-C genera una imagen de prueba asimétrica, la asigna a una vista de imagen en memoria, rasteriza la vista y normaliza el resultado en un mapa de bits de Core Graphics. No crea archivos. Guárdalo como RasterDemo.m en un Mac con las herramientas de desarrollo de línea de comandos instaladas.
clang -fobjc-arc RasterDemo.m -framework Cocoa -o RasterDemo
./RasterDemo
El contexto de normalización usa componentes de ocho bits, alfa premultiplicado en la posición más significativa y almacenamiento de 32 bits en orden little-endian. En conjunto, estas opciones producen un orden de bytes BGRA: el componente azul menos significativo aparece primero en la memoria. Esto se deriva de las definiciones de Apple de premultipliedFirst y 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;
}
El programa informa deliberadamente las dimensiones reales de salida en lugar de afirmar que la caché de la vista coincide con los 192 × 128 píxeles de origen. El marcador cian permite distinguir los cambios de orientación de las líneas verticales de prueba repetidas. Si necesitas exactamente la resolución de origen, usa directamente el CGImage devuelto por NewTestImage() en lugar de pasar por la vista.
El borrado explícito establece píxeles de destino transparentes antes de la normalización. El dibujo de una sola imagen rellena el rectángulo de destino; no crea mosaicos. Apple documenta que la operación de dibujo correspondiente de Core Graphics escala la imagen al rectángulo de destino y dibuja una sola copia, a menos que se habilite la creación de mosaicos. Documentación de Apple sobre el dibujo de imágenes.
El programa se inspeccionó, pero no se pudo compilar en el entorno de investigación Linux, que carece de AppKit y de un compilador de C. Los cálculos del paso de fila usados a continuación se comprobaron con un pequeño cálculo determinista en Python.
Diagnostica los píxeles desplazados o repetidos en un orden fijo
La siguiente secuencia es una ayuda de diagnóstico derivada de los contratos de geometría y memoria descritos anteriormente. Los síntomas sugieren dónde empezar a inspeccionar, pero no determinan una causa única.
- Comprueba el rectángulo solicitado. Para toda la vista de imagen, compara la solicitud de caché con
bounds. Si exportas una subregión, mantén su origen local como parte del contrato de entrada. - Comprueba las dimensiones antes de los bytes sin procesar. Compara el ancho y el alto de la imagen almacenada en caché con las dimensiones proporcionadas a cada consumidor posterior. Sustituir un ancho en puntos por uno en píxeles cambia cómo se interpreta el búfer.
- Comprueba el paso de fila. Si cada fila empieza progresivamente más lejos de su posición esperada, inspecciona la distancia entre los inicios de fila antes de ajustar las transformaciones.
- Comprueba la orientación con el marcador asimétrico. Una inversión vertical indica que hay que inspeccionar las convenciones de coordenadas. Un desplazamiento constante indica que hay que inspeccionar los orígenes y las traslaciones.
- Comprueba si la repetición es intencional. Inspecciona cualquier llamada de dibujo que habilite la creación de mosaicos. Cambiar el paso de fila no corregirá un dibujo que use mosaicos de forma explícita.
- Comprueba el escalado de presentación al final. Si la rasterización almacenada es correcta, pero el resultado mostrado difiere, compara el tamaño lógico de la imagen de salida con la configuración de escalado de la vista de imagen receptora.
Para píxeles de cuatro bytes, el búfer ilustrativo de 192 × 128 tiene 768 bytes por fila y requiere 98,304 bytes. Sin embargo, un búfer de 191 × 128 con la alineación de filas de 64 bytes del ejemplo también tiene un paso de 768 bytes: cada fila incluye cuatro bytes de relleno. Avanzar 191 × 4, o 764, haría que cada fila subsiguiente empezara cuatro bytes antes. Apple expone el paso real mediante la propiedad bytesPerRow del contexto.
Usa el cálculo de dirección base + y * bytesPerRow + x * 4 solo para el formato normalizado de cuatro bytes establecido aquí. Un mapa de bits arbitrario de AppKit tiene su propio contrato de formato; no deduzcas la disposición de sus componentes solo por su apariencia.
Del mismo modo, aplica los cambios de coordenadas deliberadamente. Core Graphics ofrece operaciones independientes para cambiar el origen y la escala del espacio de usuario. Si introduces un volteo vertical, documenta qué etapa realiza esa conversión y compruébalo con el marcador antes de añadir otro. Documentación sobre la traslación, documentación sobre el escalado.
Conserva la propiedad al devolver una imagen o subirla a Metal
El ejemplo mantiene viva la representación de AppKit mientras dibuja su CGImage. Después devuelve una instantánea creada a partir del contexto de normalización. Apple documenta que makeImage() crea una copia, potencialmente mediante copia en escritura de forma interna, y que los dibujos posteriores en el contexto no modifican la imagen devuelta. Esto permite liberar el contexto después de crear la instantánea. Documentación de Apple sobre instantáneas de mapas de bits.
Un puntero a mapa de bits sin procesar tiene una vida útil distinta. Apple permite acceder a los datos de mapa de bits propiedad del contexto incluso cuando este se creó con un argumento de datos nulo. Trata ese puntero como propiedad del contexto activo; para un consumidor posterior, copia los bytes en un almacenamiento de tu propiedad o mantén vivo el contexto hasta que termine su uso. Documentación de Apple sobre datos de mapas de bits.
Para subir datos a Metal, sigue estos pasos mientras el contexto normalizado siga activo:
- Crea una textura bidimensional con el ancho y el alto en píxeles del contexto. Usa
bgra8Unormpara la disposición de componentes BGRA mostrada; Apple lo define como cuatro componentes normalizados sin signo de ocho bits en orden BGRA. - Para subir datos desde la CPU, conserva el modo de almacenamiento predeterminado del sistema en lugar de forzar un modo compartido específico del hardware. Apple documenta valores predeterminados compartidos en Apple silicon y gestionados para texturas en Mac con procesadores Intel.
- Llama a
replace(region:mipmapLevel:withBytes:bytesPerRow:)para toda la región de la textura, en el nivel de mipmap0, usando el puntero de datos del contexto y su paso de fila real. - Termina la subida antes de liberar el contexto. Si reutilizas la textura, comprueba también que el trabajo anterior de la GPU que la utiliza haya terminado antes de que la CPU sobrescriba su contenido.
Las opciones de formato y almacenamiento se ajustan a la documentación de Apple sobre bgra8Unorm y a las directrices sobre el almacenamiento de recursos. Apple afirma que el método de reemplazo copia de inmediato los píxeles proporcionados, pero no sincroniza las operaciones de la GPU. Para una textura privada, la vía documentada consiste en usar una textura temporal no privada y, después, una operación blit. Documentación de Apple sobre el reemplazo de texturas.
La subida a la textura conserva los bytes proporcionados; la coincidencia de la disposición de los componentes por sí sola no define el resto de tu canal de renderizado. Mantén las decisiones del ejemplo sobre alfa premultiplicado y sRGB en el diseño del consumidor. La normalización vuelve a dibujar en un formato elegido, así que no debe describirse como una garantía de conversión idéntica byte por byte desde cualquier representación posible de AppKit.
Para esta tarea, una transferencia fiable incluye la rasterización y sus dimensiones en píxeles, tamaño lógico de presentación, paso de fila, formato de componentes, convención de orientación y propietario. Mantén esos valores juntos, prueba con un dibujo sintético asimétrico y corrige la primera etapa en la que diverjan. Así, un resultado desplazado o repetido se convierte en un problema de contrato que se puede rastrear.
Fuentes
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