Suchen des Bildtyps aus NSData oder UIImage

70

Ich lade ein Bild von einer URL, die von einem Drittanbieter bereitgestellt wurde. Die URL enthält keine Dateierweiterung (oder keinen Dateinamen) (da es sich um eine verdeckte URL handelt). Ich kann die Daten daraus (in Form von NSData) nehmen und in ein UIImage laden und gut anzeigen.

Ich möchte diese Daten in einer Datei speichern. Ich weiß jedoch nicht, in welchem ​​Format die Daten vorliegen (PNG, JPG, BMP). Ich nehme an, es ist JPG (da es sich um ein Bild aus dem Internet handelt), aber gibt es eine programmatische Möglichkeit, dies sicher herauszufinden? Ich habe mich in StackOverflow und in der Dokumentation umgesehen und konnte nichts finden.

TIA.


Bearbeiten: Brauche ich wirklich die Dateierweiterung? Ich speichere es auf einem externen Speicher (Amazon S3), aber wenn man bedenkt, dass es immer im Kontext von iOS oder einem Browser verwendet wird (beide scheinen bei der Interpretation der Daten ohne Erweiterung in Ordnung zu sein), ist dies möglicherweise kein Problem .

pschang
quelle
Warum willst du das wissen? Wenn das UIImage es gut anzeigt, sehe ich keinen Grund, warum Sie es ohne die Erweiterung nicht beibehalten können.
Kennytm
1
Das Bild wird in Zukunft auch auf einer Website angezeigt. Ich sehe jetzt, dass Browser das Rohbild (ohne Dateierweiterung) korrekt anzeigen können.
Pschang

Antworten:

144

Wenn Sie NSData für die Bilddatei haben, können Sie den Inhaltstyp anhand des ersten Bytes erraten:

+ (NSString *)contentTypeForImageData:(NSData *)data {
    uint8_t c;
    [data getBytes:&c length:1];

    switch (c) {
    case 0xFF:
        return @"image/jpeg";
    case 0x89:
        return @"image/png";
    case 0x47:
        return @"image/gif";
    case 0x49:
    case 0x4D:
        return @"image/tiff";
    }
    return nil;
}
wl.
quelle
4
Gibt es eine gleichwertige Lösung, um Dateitypen anderer Art zu identifizieren ... wie z. B. RTF, Mov, MP3?
Devarshi
5
Dies hilft mir nicht, wenn ich ein UIImage-Objekt identifiziere. Um NSData aus einem UIImage zu extrahieren, muss ich entweder UIImagePNGRepresentation () oder UIImageJPEGRepresentation () verwenden, die die imageData selbst konvertieren.
Shyam Bhat
Leider bietet UIImage keinen Zugriff auf den Bildkontext. Sie können den Bildtyp nicht ohne Bezugnahme auf den ursprünglichen Bytestream ermitteln.
Nzeltzer
Ein alternativer Ansatz: CGImageSource (Teil des ImageIO-Frameworks) kann Ihnen die entsprechende einheitliche Typkennung für Bilddaten bereitstellen.
Nzeltzer
2
Wusste jemand Bytes für PDF, Wort (doc, docx), Excel (xls, xlsx), Powerpoint (ppt, pptx), Text, RTF ..
Arun Gupta
29

Um die Antwort von wl. Zu verbessern , finden Sie hier eine viel erweiterte und präzisere Möglichkeit, den MIME-Typ des Bildes anhand der Signatur vorherzusagen. Der Code wurde weitgehend von PHPs ext / standard / image.c inspiriert .

- (NSString *)mimeTypeByGuessingFromData:(NSData *)data {

    char bytes[12] = {0};
    [data getBytes:&bytes length:12];

    const char bmp[2] = {'B', 'M'};
    const char gif[3] = {'G', 'I', 'F'};
    const char swf[3] = {'F', 'W', 'S'};
    const char swc[3] = {'C', 'W', 'S'};
    const char jpg[3] = {0xff, 0xd8, 0xff};
    const char psd[4] = {'8', 'B', 'P', 'S'};
    const char iff[4] = {'F', 'O', 'R', 'M'};
    const char webp[4] = {'R', 'I', 'F', 'F'};
    const char ico[4] = {0x00, 0x00, 0x01, 0x00};
    const char tif_ii[4] = {'I','I', 0x2A, 0x00};
    const char tif_mm[4] = {'M','M', 0x00, 0x2A};
    const char png[8] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
    const char jp2[12] = {0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a};


    if (!memcmp(bytes, bmp, 2)) {
        return @"image/x-ms-bmp";
    } else if (!memcmp(bytes, gif, 3)) {
        return @"image/gif";
    } else if (!memcmp(bytes, jpg, 3)) {
        return @"image/jpeg";
    } else if (!memcmp(bytes, psd, 4)) {
        return @"image/psd";
    } else if (!memcmp(bytes, iff, 4)) {
        return @"image/iff";
    } else if (!memcmp(bytes, webp, 4)) {
        return @"image/webp";
    } else if (!memcmp(bytes, ico, 4)) {
        return @"image/vnd.microsoft.icon";
    } else if (!memcmp(bytes, tif_ii, 4) || !memcmp(bytes, tif_mm, 4)) {
        return @"image/tiff";
    } else if (!memcmp(bytes, png, 8)) {
        return @"image/png";
    } else if (!memcmp(bytes, jp2, 12)) {
        return @"image/jp2";
    }

    return @"application/octet-stream"; // default type

}

Die obige Methode erkennt die folgenden Bildtypen:

  • image/x-ms-bmp (bmp)
  • image/gif (gif)
  • image/jpeg (jpg, jpeg)
  • image/psd (psd)
  • image/iff (iff)
  • image/webp (webp)
  • image/vnd.microsoft.icon (ico)
  • image/tiff (tif, tiff)
  • image/png (png)
  • image/jp2 (jp2)

Leider gibt es keine einfache Möglichkeit, diese Art von Informationen von einer UIImageInstanz abzurufen, da auf die gekapselten Bitmap-Daten nicht zugegriffen werden kann.

akashivskyy
quelle
3
Tolle Liste, danke. Ich habe Probleme mit dem neuen .heic-Bildtyp, der in iOS11 verwendet wird. Könnten Sie Ihre Antwort aktualisieren, um diesen neuen Typ aufzunehmen?
Alfonso
15

Die Lösung von @Tai Le für Swift 3 besteht darin, ganze Daten einem Byte-Array zuzuweisen. Wenn ein Bild groß ist, kann es zum Absturz kommen. Diese Lösung weist nur ein einzelnes Byte zu:

import Foundation

public extension Data {
    var fileExtension: String {
        var values = [UInt8](repeating:0, count:1)
        self.copyBytes(to: &values, count: 1)

        let ext: String
        switch (values[0]) {
        case 0xFF:
            ext = ".jpg"
        case 0x89:
            ext = ".png"
        case 0x47:
            ext = ".gif"
        case 0x49, 0x4D :
            ext = ".tiff"
        default:
            ext = ".png"
        }
        return ext
    }
}
ccoroom
quelle
9

Wenn Sie das Bild von einer URL abrufen, können Sie vermutlich die HTTP-Antwortheader überprüfen. Enthält der Content-TypeHeader etwas Nützliches? (Ich würde mir das vorstellen, da ein Browser das Bild wahrscheinlich korrekt anzeigen könnte und dies nur tun könnte, wenn der Inhaltstyp entsprechend eingestellt wäre.)

Dave DeLong
quelle
Oh ja. Daran hatte ich nicht gedacht! Gute Idee.
Pschang
6

Swift3-Version:

let data: Data = UIImagePNGRepresentation(yourImage)!

extension Data {
    var format: String {
        let array = [UInt8](self)
        let ext: String
        switch (array[0]) {
        case 0xFF:
            ext = "jpg"
        case 0x89:
            ext = "png"
        case 0x47:
            ext = "gif"
        case 0x49, 0x4D :
            ext = "tiff"
        default:
            ext = "unknown"
        }
        return ext
    }
}
Tai Le
quelle
Wenn Sie UIImagePNGRepresentation verwenden, um die UIImage-Daten abzurufen, gibt Ihre Dateneigenschaft immer PNG zurück. Übrigens in Swift 3 oder höher ist Dataes bereits eine Sammlung von Bytes, warum nicht einfach first?
Leo Dabus
3

Eine Alternative zur akzeptierten Antwort besteht darin, die UTI des Bildes mit zu überprüfen image I/O frameWork. Sie können den Bildtyp von UTI erreichen. Versuche dies:

CGImageSourceRef imgSrc = CGImageSourceCreateWithData((CFDataRef)data, NULL);
NSString *uti = (NSString*)CGImageSourceGetType(imgSrc);
NSLog(@"%@",uti);

Die UTI eines GIF-Bildes lautet beispielsweise "com.compuserve.gif" und die UTI des PNG-Bildes lautet "public.png". ABER Sie können keine UTI aus einem image I/O frameWorknicht erkannten Bild erzielen .

ooOlly
quelle
2

Um den Bildtyp von einem zu erhalten UIImage, können Sie die Typkennung (UTI) aus den zugrunde liegenden Quarzbilddaten abrufen:

extension UIImage {
    var typeIdentifier: String? {
        cgImage?.utType as String?
    }
}

Um die Bildtypkennung von einer URL zu erhalten, hängt es davon ab, ob die URL auf eine lokale Ressource verweist oder nicht:

extension URL {
    // for local resources (fileURLs)
    var typeIdentifier: String? { (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier }
    // for non local resources (web) you can get it asyncronously
    func asyncTypeIdentifier(completion: @escaping ((String?, Error?) -> Void)) {
        var request = URLRequest(url: self)
        request.httpMethod = "HEAD"
        URLSession.shared.dataTask(with: request) { _ , response , error in
            completion((response as? HTTPURLResponse)?.mimeType, error)
        }.resume()
    }
}

let imageURL = URL(string: "https://i.stack.imgur.com/varL9.jpg")!
imageURL.asyncTypeIdentifier { typeIdentifier, error in
    guard let typeIdentifier = typeIdentifier, error == nil else { return }
    print("typeIdentifier:", typeIdentifier)
}
Leo Dabus
quelle
1

Wenn es Ihnen wirklich wichtig ist, müssen Sie den Bytestream untersuchen. Ein JPEG beginnt mit den Bytes FF D8. Ein PNG beginnt mit 89 50 4E 47 0D 0A 1A 0A. Ich weiß nicht, ob BMP einen ähnlichen Header hat, aber ich glaube nicht, dass Sie 2010 zu wahrscheinlich auf solche im Web stoßen werden.

Aber ist es dir wirklich wichtig? Können Sie es nicht einfach als unbekanntes Bild behandeln und Cocoa Touch die Arbeit machen lassen?

Steven Fisher
quelle
1
Nun, ich speichere es auf einem Drittanbieter (Amazon S3) und beabsichtige schließlich, dieses Bild auch auf einer Website zu verwenden. Obwohl ich jetzt sehe, dass der Browser das Bild unabhängig von einer Dateierweiterung rendern kann. Und wir wissen, dass es iOS UIImage egal ist. Also ist es vielleicht egal?
Pschang
Ich vermute, dass es keine Rolle spielt, aber wenn Sie daran interessiert sind, dass der IE die Dateien ohne Erweiterungen richtig macht, sollten Sie sie wahrscheinlich auch testen.
Steven Fisher
1

Implementieren Sie eine Signaturprüfung für jedes bekannte Bildformat. Hier ist eine schnelle Objective-C-Funktion, die dies für PNG-Daten erledigt:

// Verify that NSData contains PNG data by checking the signature

- (BOOL) isPNGData:(NSData*)data
{
  // Verify that the PNG file signature matches

  static const
  unsigned char   png_sign[8] = {137, 80, 78, 71, 13, 10, 26, 10};

  unsigned char   sig[8] = {0, 0, 0, 0, 0, 0, 0, 0};

  if ([data length] <= 8) {
    return FALSE;
  }

  [data getBytes:&sig length:8];

  BOOL same = (memcmp(sig, png_sign, 8) == 0);

  return same;
}
MoDJ
quelle
1

Meine verbesserte Lösung basierend auf @ccoroom

//  Data+ImageContentType.swift

import Foundation

extension Data {  
    enum ImageContentType: String {
        case jpg, png, gif, tiff, unknown

        var fileExtension: String {
            return self.rawValue
        }
    }

    var imageContentType: ImageContentType {

        var values = [UInt8](repeating: 0, count: 1)

        self.copyBytes(to: &values, count: 1)

        switch (values[0]) {
        case 0xFF:
            return .jpg
        case 0x89:
            return .png
        case 0x47:
           return .gif
        case 0x49, 0x4D :
           return .tiff
        default:
            return .unknown
        }
    }
}

Einige Anwendungsbeispiele:

//load some image
do {
    let imageData = try Data(contentsOf: URL(string: "https://myServer/images/test.jpg")!)
} catch {
    print("Unable to load image: \(error)")
}

//content type check
guard [Data.ImageContentType.jpg,
       Data.ImageContentType.png].contains(imageData.imageContentType) else {
    print("unsupported image type")
            return
        }

//set file extension
let image = "myImage.\(imageData.imageContentType.fileExtension)" //myImage.jpg
Peter Kreinz
quelle