Detect if PNG file is corrupted in Objective C

2020-02-29 03:50发布

问题:

I'm downloading jpgs and pngs using NSURLRequest. This works ok but sometimes the files are corrupted. I have seen Catching error: Corrupt JPEG data: premature end of data segment and have this working for jpgs. Does anyone know of a way to do the same for pngs? ie Programatically check if the png data is valid...

回答1:

The PNG format has several built in checks. Each "chunk" has a CRC32 check, but to check that you'd need to read the full file.

A more basic check (not foolproof, of course) would be to read the start and ending of the file.

The first 8 bytes should always be the following (decimal) values { 137, 80, 78, 71, 13, 10, 26, 10 } (ref). In particular, the bytes second-to-fourth correspond to the ASCII string "PNG".

In hexadecimal:

89 50 4e 47 0d 0a 1a 0a
.. P  N  G  ...........

You can also check the last 12 bytes of the file (IEND chunk). The middle 4 bytes should correspond to the ASCII string "IEND". More specifically the last 12 bytes should be (in hexa):

00 00 00 00 49 45 4e 44 ae 42 60 82
........... I  E  N  D  ...........

(Strictly speaking, it's not really obligatory for a PNG file to end with those 12 bytes, the IEND chunk itself signals the end of the PNG stream and so a file could in principle have extra trailing bytes which would be ignored by the PNG reader. In practice, this is extremely improbable).



回答2:

Just as in Catching error: Corrupt JPEG data: premature end of data segment here is code snippet for PNG:

- (BOOL)dataIsValidPNG:(NSData *)data
{
    if (!data || data.length < 12)
    {
        return NO;
    }

    NSInteger totalBytes = data.length;
    const char *bytes = (const char *)[data bytes];

    return (bytes[0] == (char)0x89 && // PNG
            bytes[1] == (char)0x50 &&
            bytes[2] == (char)0x4e &&
            bytes[3] == (char)0x47 &&
            bytes[4] == (char)0x0d &&
            bytes[5] == (char)0x0a &&
            bytes[6] == (char)0x1a &&
            bytes[7] == (char)0x0a &&

            bytes[totalBytes - 12] == (char)0x00 && // IEND
            bytes[totalBytes - 11] == (char)0x00 &&
            bytes[totalBytes - 10] == (char)0x00 &&
            bytes[totalBytes - 9] == (char)0x00 &&
            bytes[totalBytes - 8] == (char)0x49 &&
            bytes[totalBytes - 7] == (char)0x45 &&
            bytes[totalBytes - 6] == (char)0x4e &&
            bytes[totalBytes - 5] == (char)0x44 &&
            bytes[totalBytes - 4] == (char)0xae &&
            bytes[totalBytes - 3] == (char)0x42 &&
            bytes[totalBytes - 2] == (char)0x60 &&
            bytes[totalBytes - 1] == (char)0x82);
}


回答3:

Nicer version of dataIsValidPNG:

BOOL dataIsValidPNG(NSData *data) {

    if (!data) {
        return NO;
    }

    const NSInteger totalBytes = data.length;
    const char *bytes = (const char *)[data bytes];
    const char start[] = { '\x89',  'P',  'N',  'G', '\r', '\n', '\x1a', '\n' };
    const char end[]   = {   '\0', '\0', '\0', '\0',  'I',  'E',    'N',  'D', '\xAE', 'B', '`', '\x82' };

    if (totalBytes < (sizeof(start) + sizeof(end))) {
        return NO;
    }

    return (memcmp(bytes, start, sizeof(start)) == 0) &&
           (memcmp(bytes + (totalBytes - sizeof(end)), end, sizeof(end)) == 0);
}