So speichern Sie ein NSImage als neue Datei

75

Wie kann ich ein NSImage als neue Datei (png, jpg, ...) in einem bestimmten Verzeichnis speichern?

burki
quelle
4
Ich habe ein Kopfgeld hinzugefügt, da jemand die erste Option als hässlichen Hack bezeichnet hat und ich anscheinend keine leicht korrekte und eindeutige Antwort auf Google finden kann. Weitere Abstimmungen / Antworten bitte.
Roman A. Taycher

Antworten:

49

Mach so etwas:

NSBitmapImageRep *imgRep = [[image representations] objectAtIndex: 0];
NSData *data = [imgRep representationUsingType: NSPNGFileType properties: nil];
[data writeToFile: @"/path/to/file.png" atomically: NO];
garph0
quelle
6
Sie sollten die Bitmap-Bildwiederholung zuverlässiger erhalten, als wenn Sie davon ausgehen, dass es sich um die erste Darstellung handelt. Sie können auch nicht davon ausgehen, dass es eine gibt; Wenn das Bild beispielsweise aus einer PDF-Datei geladen wurde, gibt es einen NSPDFImageRep und keinen NSBitmapImageRep.
Peter Hosey
14
Das ist Buggy Hack. Siehe unten für eine regelmäßige Antwort.
Eonil
6
Wenn man eine bekommt, [* representationUsingType:properties:]: unrecognized selector sent to instanceist die Problemumgehung
Joshcodes
162

Sie können NSImage wie folgt eine Kategorie hinzufügen

@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

Der Aufruf von "TIFFRepresentation" ist wichtig, da Sie sonst möglicherweise kein gültiges Bild erhalten.

Pegolon
quelle
3
Aber TIFF ist RGB und wirft alle Transparenzinformationen weg.
Hot Licks
4
Nein, TIFF macht Alpha- und mehrseitige Bilder einwandfrei. Was hat dich auf die Idee gebracht, dass es nicht so ist? Dies empfiehlt Apple als Zielformat für Retina-Bilder (TIFFs mit mehreren Darstellungen).
uliwitness
TIFF sollte Alpha und Transparenz unterstützen ( en.wikipedia.org/wiki/Transparency_(graphic)
ColdSteel
Wird es Gif unterstützen?
ColdSteel
Was ist ImageProps? Was ist dieser Wert übergeben?
ColdSteel
34

Ich bin mir nicht sicher über den Rest von euch, aber ich esse lieber meine volle Enchilada. Was oben beschrieben wurde, wird funktionieren und nichts ist daran falsch, aber ich habe ein paar Dinge ausgelassen. Hier werde ich meine Beobachtungen hervorheben:

  • Zunächst wird die beste Darstellung bereitgestellt, die 72 DPI zu betragen scheint, selbst wenn die Bildauflösung größer ist. Sie verlieren also die Auflösung
  • Zweitens, was ist mit mehrseitigen Bildern, beispielsweise in animierten GIFs oder PDFs? Probieren Sie ein animiertes GIF aus. Sie werden feststellen, dass die Animation verloren gegangen ist
  • Schließlich gehen alle Metadaten wie EXIF, GPS usw. verloren.

Wenn Sie dieses Bild konvertieren möchten, möchten Sie das alles wirklich verlieren? Wenn Sie Ihre volle Mahlzeit essen möchten, dann lesen Sie weiter ...

Manchmal und ich meine manchmal gibt es nichts Besseres als eine gute alte Schulentwicklung. Ja das heißt, wir müssen ein bisschen arbeiten!

Lass uns anfangen:

Ich erstelle eine Kategorie in NSData. Dies sind Klassenmethoden, weil Sie möchten, dass diese Dinge threadsicher sind und es nichts Sichereres gibt, als Ihre Sachen auf den Stapel zu legen. Es gibt zwei Arten von Methoden, eine für die Ausgabe von nicht mehrseitigen Bildern und eine für die Ausgabe von mehrseitigen Bildern.

Liste der Einzelbilder: JPG, PNG, BMP, JPEG-2000

Liste mehrerer Bilder: PDF, GIF, TIFF

Erstellen Sie zunächst einen veränderlichen Datenraum im Speicher.

NSMutableData * imageData    = [NSMutableData data];

Zweitens erhalten Sie ein CGImageSourceRef. Ja, es klingt schon hässlich, nicht wahr? Es ist nicht so schlimm, lass uns weitermachen ... Sie möchten wirklich, dass das Quellbild keine Darstellung oder ein NSImage-Datenblock ist. Wir haben jedoch ein kleines Problem. Die Quelle ist möglicherweise nicht kompatibel. Überprüfen Sie daher Ihre UTI anhand der in CGImageSourceCopyTypeIdentifiers () aufgeführten Quellen.

Etwas 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 );

Warten Sie eine Sekunde, warum ist NSImage dort? Nun, es gibt einige Formate, die keine Metadaten haben und CGImageSource nicht unterstützt, aber dies sind gültige Bilder. Ein Beispiel sind PICT-Bilder im alten Stil.

Jetzt haben wir ein CGImageSourceRef, stellen Sie sicher, dass es nicht Null ist, und lassen Sie uns jetzt ein CGImageDestinationRef erhalten. Wow, all diese Schiedsrichter sind im Auge zu behalten. Bisher sind wir bei 2!

Wir werden diese Funktion verwenden: CGImageDestinationCreateWithData ()

  • 1. Parameter ist Ihre imageData (cast CFMutableDataRef)
  • 2nd Param ist Ihre Ausgabe-UTI. Denken Sie an die obige Liste. (zB kUTTypePNG)
  • 3. Parameter ist die Anzahl der zu speichernden Bilder. Für Einzelbilddateien ist dies 1, andernfalls können Sie einfach Folgendes verwenden:

    CGImageSourceGetCount (imageSource);

  • 4. Param ist Null.

Überprüfen Sie, ob Sie über dieses CGImageDestinationRef verfügen, und fügen Sie nun unsere Bilder aus der Quelle hinzu. Dies schließt auch alle Metadaten ein und behält die Auflösung bei.

Für mehrere Bilder schleifen wir:

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

Für ein einzelnes Bild ist es eine Codezeile bei Index 0:

CGImageDestinationAddImageFromSource( imageDest, imageSource, 0, nil);

Ok, finalisieren Sie es, das es auf die Festplatte oder den Datencontainer schreibt:

CGImageDestinationFinalize( imageDest );

Damit Mutable Data von Anfang an alle unsere Bilddaten und Metadaten enthält.

Sind wir schon fertig? Fast, auch mit Müllabfuhr muss man aufräumen! Denken Sie an zwei Refs, einen für die Quelle und einen für das Ziel. Führen Sie also eine CFRelease () durch.

Jetzt sind wir fertig und am Ende erhalten Sie ein konvertiertes Bild, das alle Metadaten, Auflösungen usw. beibehält.

Meine NSData-Kategoriemethoden sehen folgendermaßen aus:

+ (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;

Was ist mit Größenänderung oder ICO / ICNS? Dies ist für einen anderen Tag, aber zusammenfassend befassen Sie sich zuerst mit der Größenänderung ...

  1. Erstellen Sie einen Kontext mit neuer Größe: CGBitmapContextCreate ()
  2. Holen Sie sich eine Bildreferenz aus dem Index: CGImageSourceCreateImageAtIndex ()
  3. Holen Sie sich eine Kopie der Metadaten: CGImageSourceCopyPropertiesAtIndex ()
  4. Zeichnen Sie das Bild in den Kontext: CGContextDrawImage ()
  5. Ruft das Bild mit geänderter Größe aus dem Kontext ab: CGBitmapContextCreateImage ()
  6. Fügen Sie nun das Bild und die Metadaten zum Dest Ref hinzu: CGImageDestinationAddImage ()

Spülen und wiederholen Sie dies für mehrere in die Quelle eingebettete Bilder.

Der einzige Unterschied zwischen einem ICO und einem ICNS besteht darin, dass eines ein einzelnes Bild ist, während das andere mehrere Bilder in einer Datei enthält. Wetten, dass Sie erraten können, welches welches ist?! ;-) Für diese Formate müssen Sie die Größe auf eine bestimmte Größe verkleinern, da sonst ein FEHLER auftritt. Der Prozess ist zwar genau der gleiche, bei dem Sie die richtige UTI verwenden, aber die Größenänderung ist etwas strenger.

Ok, hoffe das hilft anderen da draußen und du bist so voll wie ich jetzt!

Opps, vergessen zu erwähnen. Wenn Sie das NSData-Objekt erhalten, tun Sie, was Sie möchten, z. B. writeToFile, writeToURL oder erstellen Sie ein anderes NSImage, wenn Sie möchten.

Viel Spaß beim Codieren!

Arvin
quelle
2
Dies ist ein guter Anfang, sicherlich besser als die anderen Antworten (obwohl diese höher bewertet werden), aber er ist nicht vollständig. Erstens verarbeitet die hier vorgestellte Logik PDF-Dateien nicht selbst (CGImageDestinationAddImageFromSource funktioniert nicht, da die Bildquelle null ist). PDF- und andere nicht kopierbare Bildquellen müssen speziell behandelt werden (um entweder Bilder über Miniaturansichten zu erhalten oder die Daten auf andere Weise zu kopieren). Zweitens werden keine Metadaten selbst kopiert. Sie müssen dies explizit tun, indem Sie das Eigenschaftenwörterbuch aus der Bildquelle abrufen und zum Bildziel hinzufügen.
Danielv
Zwei Fragen bitte. 1) Wie können Sie sicher sein, dass [anImage TIFFRepresentation] immer etwas zurückgibt? Gibt es keine NSImage's ohne TIFFRepresentation?. 2) Könnten Sie bitte ein Code-Snippet für mindestens eine (z. B. JPEPDataFromURL) dieser Funktionen vollständig bereitstellen? Ich habe versucht, Ihren Anweisungen zu folgen, und zumindest für mich - es wird nicht funktionieren (ich versuche, eingehende Videodaten als JPEG auf der Festplatte zu speichern, und meine Eingabe geht vom CMSampleBuffer von AVSession über ein NSImage - und ich versuche zu verwenden Ihr Code, um dies in einer Datei zu speichern.
Motti Shneor
15

Swift 4.2 Lösung

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)")
        }
    }
}
Charlton Provatas
quelle
Können Sie erklären, warum Sie self.selfanstelle von verwenden self? Gibt es einen Unterschied?
MartinW
Es stellt sich heraus, dass es keinen Unterschied gibt. Vielleicht war dies in einer früheren Version von Swift, aber ich dachte, es wurde der explizite Klassenname gedruckt, aber es sieht so aus, als ob entweder nur zurückkehren<<Class-Name> <Memory-Address>>
Charlton Provatas
14

Mit swift3 als PNG speichern

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
        }
    }
}
neoneye
quelle
lass tiffData = image.tiffRepresentation! let imageRep = NSBitmapImageRep (data: tiffData) let data = imageRep? .representation (unter Verwendung von: .PNG, Eigenschaften: [:]) do {try data? .write (to: URL (fileURLWithPath: path3), Optionen: NSData.WritingOptions ())} catch {print ("(error)")}
Hoffe,
5

Schneller Stil:

if let imgRep = image?.representations[0] as? NSBitmapImageRep
{
      if let data = imgRep.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: [:])
      {
           data.writeToFile("/path/to/file.png", atomically: false)
      }
}
Peter Kreinz
quelle
2

Um mit plattformübergreifendem Code zu helfen, habe ich eine Version implementiert UIImagePNGRepresentation(), die auf einem Mac ausgeführt wird (und verwendet wird NSImage).

#if os(macOS)

public func UIImagePNGRepresentation(_ image: NSImage) -> Data? {
    guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)
        else { return nil }
    let imageRep = NSBitmapImageRep(cgImage: cgImage)
    imageRep.size = image.size // display size in points
    return imageRep.representation(using: .png, properties: [:])
}

#endif

Verwendung:

if let data = UIImagePNGRepresentation(myImage) {
    do { try data.write(to: url, options: [.atomic]) }
    catch let error { print("Error writing image (\(error))") }
}
Robin Stewart
quelle
1

Nur noch ein garantierter Arbeitsansatz mit SWIFT:

Ich habe einen "Image Well", in dem der Benutzer jedes Bild ablegen kann. Und dieser "Image Well" hat eine Bildeigenschaft (vom Typ NSImage), auf die über eine Steckdose zugegriffen wird:

@IBOutlet weak var imageWell: NSImageView!

Und der Code, der dieses Bild speichert (Sie können es in die Schaltflächenaktion einfügen), lautet:

if imageWell.image != nil {
   let bMImg = NSBitmapImageRep(data: (imageWell?.image?.TIFFRepresentation)!)
   let dataToSave = bMImg?.representationUsingType(NSBitmapImageFileType.NSJPEGFileType, properties: [NSImageCompressionFactor : 1])
   dataToSave?.writeToFile("/users/user/desktop/image.jpg", atomically: true)
}

In der ersten Zeile des angegebenen Codes prüfen wir, ob unser Image Well ein Bild enthält.

In der 2. Zeile machen wir eine Bitmap-Darstellung dieses Bildes.

In der 3. Zeile konvertieren wir unsere BitmapRepresentation in einen JPG-Typ mit einem Komprimierungsfaktor von "1" (keine Komprimierung).

In der 4. Zeile speichern wir die JPG-Daten unter einem bestimmten Pfad. "atomar: wahr" bedeutet, dass die Datei als ein Stück gespeichert wird und dass wir sicher sind, dass die Operation erfolgreich sein wird.

NB: Sie können einen anderen NSBitmapImageFileType in der 3. Zeile verwenden, um Ihr Bild in einem anderen Format zu speichern. Es hat viel:

NSBitmapImageFileType.NSBMPFileType
NSBitmapImageFileType.NSGIFFileType
NSBitmapImageFileType.NSPNGFileType

etc.

Deutsch G.
quelle
0

Objektive Lösung mit und ohne Komprimierung

NSImage* image;

// No compression
NSData* data = [image TIFFRepresentation];
// With compression
NSData* data = [image
                TIFFRepresentationUsingCompression:NSTIFFCompressionLZW
                factor:0.5f];

if (data != nil) {
    NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithData:data];
    if (bitmap != nil) {
        NSData* bitmapData = [bitmap
                              representationUsingType:NSBitmapImageFileTypePNG
                              properties:@{}];
        if (bitmapData != nil) {
            [bitmapData
             writeToFile:<file_path>
             atomically:true];
        }
    }
}
Kritua
quelle