82 votes

Comment enregistrer une image NSImage dans un nouveau fichier ?

Comment puis-je enregistrer une image NSImage comme un nouveau fichier (png, jpg, ...) dans un certain répertoire ?

4 votes

J'ai ajouté une prime puisque quelqu'un a appelé la première option un hack laid et je ne peux pas sembler trouver facilement une réponse apparemment correcte et définitive sur google, plus de vote/réponses s'il vous plaît.

165voto

Pegolon Points 1904

Vous pourriez ajouter une catégorie à NSImage comme ceci

@interface NSImage(saveAsJpegWithName)
- (void) saveAsJpegWithName:(NSString*) fileName;
@end

@implementation NSImage(saveAsJpegWithName)

- (void) saveAsJpegWithName:(NSString*) fileName
{
    // Cache the reduced image
    NSData *imageData = [self TIFFRepresentation];
    NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
    NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
    imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];
    [imageData writeToFile:fileName atomically:NO];        
}

@end

L'appel à "TIFFRepresentation" est essentiel, sinon vous risquez de ne pas obtenir une image valide.

3 votes

Mais TIFF est RGB et jette toute information de transparence.

5 votes

Non, le format TIFF convient parfaitement aux images alpha et multi-pages. Qu'est-ce qui vous fait penser que ce n'est pas le cas ? C'est ce qu'Apple recommande comme format de destination pour les images rétiniennes (TIFF multi-représentation).

0 votes

Le format TIFF doit supporter l'alpha et la transparence fr.wikipedia.org/wiki/Transparency_(graphic)

50voto

garph0 Points 1134

Faites quelque chose comme ça :

NSBitmapImageRep *imgRep = [[image representations] objectAtIndex: 0];
NSData *data = [imgRep representationUsingType: NSPNGFileType properties: nil];
[data writeToFile: @"/path/to/file.png" atomically: NO];

7 votes

Vous devez obtenir la représentation de l'image bitmap d'une manière plus fiable que de supposer qu'il s'agit de la première représentation. Vous ne pouvez pas non plus supposer qu'il y en a une ; si l'image a été chargée à partir d'un PDF, par exemple, il y aura un NSPDFImageRep, et non un NSBitmapImageRep.

14 votes

C'est un hack bogué. Voir ci-dessous pour la réponse normale.

6 votes

Si on obtient un [* representationUsingType:properties:]: unrecognized selector sent to instance la solution de contournement est ici : NSCGImageSnapshotRep, comment obtenir les bitmapData

35voto

Arvin Points 1392

Je ne sais pas pour le reste d'entre vous, mais je préfère manger toute l'enchilada. Ce qui est décrit ci-dessus fonctionnera et il n'y a rien de mal à cela, mais j'ai trouvé quelques oublis. Je vais souligner ici mes observations :

  • D'abord la meilleure représentation est fournie qui semble être 72 DPI même si la résolution de l'image est supérieure à cela. Vous perdez donc résolution
  • Deuxièmement, qu'en est-il des images multi-pages telles que celles des GIF animés ou des PDFs. Allez-y, essayez un GIF animé, vous trouverez l'animation perdue.
  • Enfin, toutes les métadonnées telles que les données EXIF, GPS, etc... seront perdues.

Donc, si vous voulez convertir cette image, voulez-vous vraiment perdre tout cela ? Si vous souhaitez manger votre repas complet, alors lisez la suite...

Parfois, et je dis bien parfois, il n'y a rien de mieux que le bon vieux développement à l'ancienne. Oui, cela signifie que nous devons travailler un peu !

Commençons :

Je crée une catégorie dans NSData. Ce sont des méthodes de classe parce que vous voulez que ces choses soient thread safe et il n'y a rien de plus sûr que de mettre vos trucs sur la pile. Il existe deux types de méthodes, une pour la sortie d'images non multi-pages et une pour la sortie d'images multi-pages.

Liste des images uniques : JPG, PNG, BMP, JPEG-2000

Liste d'images multiples : PDF, GIF, TIFF

Créez d'abord un espace de données mutable en mémoire.

NSMutableData * imageData    = [NSMutableData data];

Ensuite, obtenez un CGImageSourceRef. Oui, ça semble déjà moche, n'est-ce pas ? Ce n'est pas si grave, continuons... Vous voulez vraiment l'image source, pas une représentation ou un morceau de données NSImage. Cependant, nous avons un petit problème. La source peut ne pas être compatible, donc assurez-vous de vérifier votre UTI avec ceux listés par CGImageSourceCopyTypeIdentifiers().

Un certain code :

CGImageSourceRef imageSource = nil;
if ( /* CHECK YOUR UTI HERE */ )
    return CGImageSourceCreateWithURL( (CFURLRef)aURL, nil );

NSImage * anImage = [[NSImage alloc] initWithContentsOfURL:aURL];

if ( anImage )
    return CGImageSourceCreateWithData( (CFDataRef)[anImage TIFFRepresentation], nil );

Attendez une seconde, pourquoi NSImage est là ? Et bien il y a certains formats qui n'ont pas de métadonnées et que CGImageSource ne supporte pas, mais ce sont des images valides. Un exemple est l'ancien style d'images PICT.

Maintenant, nous avons un CGImageSourceRef, assurez-vous qu'il n'est pas nul et ensuite, obtenons un CGImageDestinationRef. Wow, toutes ces références à suivre. Jusqu'à présent, nous en sommes à 2 !

Nous allons utiliser cette fonction : CGImageDestinationCreateWithData()

  • 1er Param est votre imageData (cast CFMutableDataRef)

  • Le deuxième paramètre est votre UTI de sortie, rappelez-vous la liste ci-dessus. (par exemple kUTTypePNG)

  • Le troisième paramètre est le nombre d'images à sauvegarder. Pour les fichiers d'images uniques, ce est 1, sinon vous pouvez simplement utiliser ce qui suit :

    CGImageSourceGetCount( imageSource ) ;

  • Le 4e paramètre est nul.

Vérifiez si vous avez ce CGImageDestinationRef et maintenant ajoutons-y nos images de la source... ceci inclura également toutes les métadonnées et conservera la résolution.

Pour les images multiples, nous bouclons :

for ( NSUInteger i = 0; i < count; ++i )
                CGImageDestinationAddImageFromSource( imageDest, imageSource, i, nil );

Pour une seule image, c'est une ligne de code à l'index 0 :

CGImageDestinationAddImageFromSource( imageDest, imageSource, 0, nil);

Ok, finalisez-le et écrivez-le sur le disque ou le conteneur de données :

CGImageDestinationFinalize( imageDest );

Ainsi, les données mutables du début contiennent maintenant toutes les données et métadonnées de l'image.

Avons-nous déjà terminé ? Presque, même avec la collecte des déchets, il faut faire le ménage ! Rappelez-vous que vous avez deux Ref, un pour la source et un pour la destination, donc faites un CFRelease().

Maintenant nous avons terminé et ce que vous obtenez est une image convertie conservant toutes ses métadonnées, sa résolution, etc...

Mes méthodes de catégorie NSData ressemblent à ceci :

+ (NSData *) JPGDataFromURL:(NSURL *)aURL;
+ (NSData *) PNGDataFromURL:(NSURL *)aURL;
+ (NSData *) BMPDataFromURL:(NSURL *)aURL;
+ (NSData *) JPG2DataFromURL:(NSURL *)aURL;

+ (NSData *) PDFDataFromURL:(NSURL *)aURL;
+ (NSData *) GIFDataFromURL:(NSURL *)aURL;
+ (NSData *) TIFFDataFromURL:(NSURL *)aURL;

Qu'en est-il du redimensionnement ou de l'ICO / ICNS ? C'est pour un autre jour, mais en résumé, il faut d'abord s'attaquer au redimensionnement...

  1. Créer un contexte avec une nouvelle taille : CGBitmapContextCreate()
  2. Récupère l'image à partir de l'index : CGImageSourceCreateImageAtIndex()
  3. Obtenir une copie des métadonnées : CGImageSourceCopyPropertiesAtIndex()
  4. Intégrez l'image dans le contexte : CGContextDrawImage()
  5. Obtenez l'image redimensionnée à partir du contexte : CGBitmapContextCreateImage()
  6. Maintenant, ajoutez l'image et les métadonnées au Ref Dest : CGImageDestinationAddImage()

Rincer et répéter pour les images multiples intégrées dans la source.

La seule différence entre une ICO et une ICNS est que l'une est une image unique tandis que l'autre est constituée de plusieurs images dans un seul fichier. Je parie que vous pouvez deviner lequel est lequel ?! ;-) Pour ces formats, vous devez redimensionner à une taille particulière, sinon une ERREUR s'ensuivra. Le processus est exactement le même lorsque vous utilisez le bon UTI, mais le redimensionnement est un peu plus strict.

J'espère que cela aidera d'autres personnes et que vous serez aussi rassasiés que moi maintenant !

Opps, j'ai oublié de mentionner. Lorsque vous obtenez l'objet NSData, faites-en ce que vous voulez, comme writeToFile, writeToURL, ou même créer un autre NSImage si vous le souhaitez.

Bon codage !

2 votes

C'est un bon début, certainement meilleur que les autres réponses (même si celles-ci sont mieux notées) mais ce n'est pas complet. Tout d'abord, la logique telle qu'elle est présentée ici ne traitera pas les fichiers PDF par elle-même (CGImageDestinationAddImageFromSource ne fonctionnera pas puisque la source de l'image sera nulle). Les fichiers PDF et les autres sources d'images non copiables nécessitent un traitement spécial (pour obtenir les images via des vignettes ou pour copier les données d'une autre manière). Deuxièmement, il ne copiera aucune méta-donnée par lui-même, vous devez le faire explicitement en récupérant le dictionnaire de propriétés de la source d'image et en l'ajoutant à la destination de l'image.

0 votes

Deux questions s'il vous plaît. 1) comment pouvez-vous être sûr que [anImage TIFFRepresentation] renvoie toujours quelque chose ? n'y a-t-il pas des NSImage qui n'ont pas de TIFFRepresentation ? 2) Pourriez-vous fournir un extrait de code fonctionnel pour au moins une (disons JPEPDataFromURL) de ces fonctions dans leur intégralité ? J'ai essayé de suivre vos instructions, et au moins pour moi - cela ne fonctionne pas (j'essaie d'enregistrer une trame de données vidéo entrante en tant que JPEG sur le disque, et mon entrée va du CMSampleBuffer de AVSession à travers un NSImage - et j'essaie d'utiliser votre code pour enregistrer cela dans un fichier.

18voto

neoneye Points 11545

Sauvegarder en PNG avec swift3

import AppKit

extension NSImage {
    @discardableResult
    func saveAsPNG(url: URL) -> Bool {
        guard let tiffData = self.tiffRepresentation else {
            print("failed to get tiffRepresentation. url: \(url)")
            return false
        }
        let imageRep = NSBitmapImageRep(data: tiffData)
        guard let imageData = imageRep?.representation(using: .PNG, properties: [:]) else {
            print("failed to get PNG representation. url: \(url)")
            return false
        }
        do {
            try imageData.write(to: url)
            return true
        } catch {
            print("failed to write to disk. url: \(url)")
            return false
        }
    }
}

0 votes

Let tiffData = image.tiffRepresentation ! let imageRep = NSBitmapImageRep(data : tiffData) let data = imageRep ?.representation(using : .PNG, properties : [ :]) do { try data ?.write(to : URL(fileURLWithPath : path3), options : NSData.WritingOptions() ) } catch { print("(erreur)") }

16voto

Charlton Provatas Points 830

Solution Swift 4.2

public extension NSImage {
    public func writePNG(toURL url: URL) {

        guard let data = tiffRepresentation,
              let rep = NSBitmapImageRep(data: data),
              let imgData = rep.representation(using: .png, properties: [.compressionFactor : NSNumber(floatLiteral: 1.0)]) else {

            Swift.print("\(self) Error Function '\(#function)' Line: \(#line) No tiff rep found for image writing to \(url)")
            return
        }

        do {
            try imgData.write(to: url)
        }catch let error {
            Swift.print("\(self) Error Function '\(#function)' Line: \(#line) \(error.localizedDescription)")
        }
    }
}

0 votes

Pouvez-vous expliquer pourquoi vous utilisez self.self au lieu de self ? Y a-t-il une différence ?

0 votes

Il s'avère qu'il n'y a aucune différence. Peut-être que c'était dans une version antérieure de swift, mais je pensais qu'il fallait imprimer le nom explicite de la classe, mais il semble que l'un ou l'autre retourne simplement <<Class-Name> <Memory-Address>>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X