private static IEnumerable <Face> ParseFaces(byte[] bytes, int firstRecordOffset, int posOffset, int recordLength)
        {
            if (bytes == null)
            {
                yield break;
            }

            var reader = new ByteArrayReader(bytes, isMotorolaByteOrder: false);

            int faceCount = reader.GetUInt16(0);

            if (faceCount == 0 || bytes.Length < firstRecordOffset + faceCount * recordLength)
            {
                yield break;
            }

            posOffset += firstRecordOffset;

            for (int i = 0, recordOffset = firstRecordOffset; i < faceCount; i++, recordOffset += recordLength, posOffset += recordLength)
            {
                yield return(new Face(
                                 x: reader.GetUInt16(posOffset),
                                 y: reader.GetUInt16(posOffset + 2),
                                 width: reader.GetUInt16(posOffset + 4),
                                 height: reader.GetUInt16(posOffset + 6),
                                 name: recordLength == 44 ? reader.GetString(recordOffset, 20, Encoding.UTF8).Trim(' ', '\0') : null,
                                 age: recordLength == 44 ? Age.FromPanasonicString(reader.GetString(recordOffset + 28, 20, Encoding.UTF8).Trim(' ', '\0')) : null));
            }
        }
Example #2
0
        public virtual Face[] GetRecognizedFaces()
        {
            sbyte[] bytes = GetByteArray(TagFaceRecognitionInfo);
            if (bytes == null)
            {
                return(null);
            }
            RandomAccessReader reader = new ByteArrayReader(bytes);

            reader.SetMotorolaByteOrder(false);
            try
            {
                int faceCount = reader.GetUInt16(0);
                if (faceCount == 0)
                {
                    return(null);
                }
                Face[] faces = new Face[faceCount];
                for (int i = 0; i < faceCount; i++)
                {
                    int    offset = 4 + i * 44;
                    string name   = Sharpen.Extensions.Trim(reader.GetString(offset, 20, "ASCII"));
                    string age    = Sharpen.Extensions.Trim(reader.GetString(offset + 28, 20, "ASCII"));
                    faces[i] = new Face(reader.GetUInt16(offset + 20), reader.GetUInt16(offset + 22), reader.GetUInt16(offset + 24), reader.GetUInt16(offset + 26), name, Age.FromPanasonicString(age));
                }
                return(faces);
            }
            catch (IOException)
            {
                return(null);
            }
        }
Example #3
0
        public virtual Face[] GetDetectedFaces()
        {
            sbyte[] bytes = GetByteArray(TagFaceDetectionInfo);
            if (bytes == null)
            {
                return(null);
            }
            RandomAccessReader reader = new ByteArrayReader(bytes);

            reader.SetMotorolaByteOrder(false);
            try
            {
                int faceCount = reader.GetUInt16(0);
                if (faceCount == 0)
                {
                    return(null);
                }
                Face[] faces = new Face[faceCount];
                for (int i = 0; i < faceCount; i++)
                {
                    int offset = 2 + i * 8;
                    faces[i] = new Face(reader.GetUInt16(offset), reader.GetUInt16(offset + 2), reader.GetUInt16(offset + 4), reader.GetUInt16(offset + 6), null, null);
                }
                return(faces);
            }
            catch (IOException)
            {
                return(null);
            }
        }
Example #4
0
        private string GetTransformDescription(int tag)
        {
            var values = Directory.GetByteArray(tag);

            if (values == null)
            {
                return(null);
            }

            IndexedReader reader = new ByteArrayReader(values);

            try
            {
                int val1 = reader.GetUInt16(0);
                int val2 = reader.GetUInt16(2);
                if (val1 == -1 && val2 == 1)
                {
                    return("Slim Low");
                }
                if (val1 == -3 && val2 == 2)
                {
                    return("Slim High");
                }
                if (val1 == 0 && val2 == 0)
                {
                    return("Off");
                }
                if (val1 == 1 && val2 == 1)
                {
                    return("Stretch Low");
                }
                if (val1 == 3 && val2 == 2)
                {
                    return("Stretch High");
                }

                return("Unknown (" + val1 + " " + val2 + ")");
            }
            catch (IOException)
            {
                return(null);
            }
        }
        private string GetTagDataString(int tagType)
        {
            try
            {
                sbyte[] bytes = _directory.GetByteArray(tagType);
                if (bytes == null)
                {
                    return(_directory.GetString(tagType));
                }
                RandomAccessReader reader = new ByteArrayReader(bytes);
                int iccTagType            = reader.GetInt32(0);
                switch (iccTagType)
                {
                case IccTagTypeText:
                {
                    try
                    {
                        return(Sharpen.Runtime.GetStringForBytes(bytes, 8, bytes.Length - 8 - 1, "ASCII"));
                    }
                    catch (UnsupportedEncodingException)
                    {
                        return(Sharpen.Runtime.GetStringForBytes(bytes, 8, bytes.Length - 8 - 1));
                    }
                    goto case IccTagTypeDesc;
                }

                case IccTagTypeDesc:
                {
                    int stringLength = reader.GetInt32(8);
                    return(Sharpen.Runtime.GetStringForBytes(bytes, 12, stringLength - 1));
                }

                case IccTagTypeSig:
                {
                    return(IccReader.GetStringFromInt32(reader.GetInt32(8)));
                }

                case IccTagTypeMeas:
                {
                    int    observerType   = reader.GetInt32(8);
                    float  x              = reader.GetS15Fixed16(12);
                    float  y              = reader.GetS15Fixed16(16);
                    float  z              = reader.GetS15Fixed16(20);
                    int    geometryType   = reader.GetInt32(24);
                    float  flare          = reader.GetS15Fixed16(28);
                    int    illuminantType = reader.GetInt32(32);
                    string observerString;
                    switch (observerType)
                    {
                    case 0:
                    {
                        observerString = "Unknown";
                        break;
                    }

                    case 1:
                    {
                        observerString = "1931 2°";
                        break;
                    }

                    case 2:
                    {
                        observerString = "1964 10°";
                        break;
                    }

                    default:
                    {
                        observerString = Sharpen.Extensions.StringFormat("Unknown %d", observerType);
                        break;
                    }
                    }
                    string geometryString;
                    switch (geometryType)
                    {
                    case 0:
                    {
                        geometryString = "Unknown";
                        break;
                    }

                    case 1:
                    {
                        geometryString = "0/45 or 45/0";
                        break;
                    }

                    case 2:
                    {
                        geometryString = "0/d or d/0";
                        break;
                    }

                    default:
                    {
                        geometryString = Sharpen.Extensions.StringFormat("Unknown %d", observerType);
                        break;
                    }
                    }
                    string illuminantString;
                    switch (illuminantType)
                    {
                    case 0:
                    {
                        illuminantString = "unknown";
                        break;
                    }

                    case 1:
                    {
                        illuminantString = "D50";
                        break;
                    }

                    case 2:
                    {
                        illuminantString = "D65";
                        break;
                    }

                    case 3:
                    {
                        illuminantString = "D93";
                        break;
                    }

                    case 4:
                    {
                        illuminantString = "F2";
                        break;
                    }

                    case 5:
                    {
                        illuminantString = "D55";
                        break;
                    }

                    case 6:
                    {
                        illuminantString = "A";
                        break;
                    }

                    case 7:
                    {
                        illuminantString = "Equi-Power (E)";
                        break;
                    }

                    case 8:
                    {
                        illuminantString = "F8";
                        break;
                    }

                    default:
                    {
                        illuminantString = Sharpen.Extensions.StringFormat("Unknown %d", illuminantType);
                        break;
                    }
                    }
                    return(Sharpen.Extensions.StringFormat("%s Observer, Backing (%s, %s, %s), Geometry %s, Flare %d%%, Illuminant %s", observerString, x, y, z, geometryString, (long)System.Math.Round(flare * 100), illuminantString));
                }

                case IccTagTypeXyzArray:
                {
                    StringBuilder res   = new StringBuilder();
                    int           count = (bytes.Length - 8) / 12;
                    for (int i = 0; i < count; i++)
                    {
                        float x = reader.GetS15Fixed16(8 + i * 12);
                        float y = reader.GetS15Fixed16(8 + i * 12 + 4);
                        float z = reader.GetS15Fixed16(8 + i * 12 + 8);
                        if (i > 0)
                        {
                            res.Append(", ");
                        }
                        res.Append("(").Append(x).Append(", ").Append(y).Append(", ").Append(z).Append(")");
                    }
                    return(Sharpen.Extensions.ConvertToString(res));
                }

                case IccTagTypeMluc:
                {
                    int           int1 = reader.GetInt32(8);
                    StringBuilder res  = new StringBuilder();
                    res.Append(int1);
                    //int int2 = reader.getInt32(12);
                    //System.err.format("int1: %d, int2: %d\n", int1, int2);
                    for (int i = 0; i < int1; i++)
                    {
                        string str = IccReader.GetStringFromInt32(reader.GetInt32(16 + i * 12));
                        int    len = reader.GetInt32(16 + i * 12 + 4);
                        int    ofs = reader.GetInt32(16 + i * 12 + 8);
                        string name;
                        try
                        {
                            name = Sharpen.Runtime.GetStringForBytes(bytes, ofs, len, "UTF-16BE");
                        }
                        catch (UnsupportedEncodingException)
                        {
                            name = Sharpen.Runtime.GetStringForBytes(bytes, ofs, len);
                        }
                        res.Append(" ").Append(str).Append("(").Append(name).Append(")");
                    }
                    //System.err.format("% 3d: %s, len: %d, ofs: %d, \"%s\"\n", i, str, len,ofs,name);
                    return(Sharpen.Extensions.ConvertToString(res));
                }

                case IccTagTypeCurv:
                {
                    int           num = reader.GetInt32(8);
                    StringBuilder res = new StringBuilder();
                    for (int i = 0; i < num; i++)
                    {
                        if (i != 0)
                        {
                            res.Append(", ");
                        }
                        res.Append(FormatDoubleAsString(((float)reader.GetUInt16(12 + i * 2)) / 65535.0, 7, false));
                    }
                    //res+=String.format("%1.7g",Math.round(((float)iccReader.getInt16(b,12+i*2))/0.065535)/1E7);
                    return(Sharpen.Extensions.ConvertToString(res));
                }

                default:
                {
                    return(Sharpen.Extensions.StringFormat("%s(0x%08X): %d bytes", IccReader.GetStringFromInt32(iccTagType), iccTagType, bytes.Length));
                }
                }
            }
            catch (IOException)
            {
                // TODO decode these values during IccReader.extract so we can report any errors at that time
                // It is convention to return null if a description cannot be formulated.
                // If an error is to be reported, it should be done during the extraction process.
                return(null);
            }
        }
        public void ProcessChunk(string fourCc, byte[] payload)
        {
            switch (fourCc)
            {
            case "EXIF":
            {
                // We have seen WebP images with and without the preamble here. It's likely that some software incorrectly
                // copied an entire JPEG segment into the WebP image. Regardless, we can handle it here.
                var reader = ExifReader.StartsWithJpegExifPreamble(payload)
                        ? new ByteArrayReader(payload, ExifReader.JpegSegmentPreambleLength)
                        : new ByteArrayReader(payload);
                _directories.AddRange(new ExifReader().Extract(reader));
                break;
            }

            case "ICCP":
            {
                _directories.Add(new IccReader().Extract(new ByteArrayReader(payload)));
                break;
            }

            case "XMP ":
            {
                _directories.Add(new XmpReader().Extract(payload));
                break;
            }

            case "VP8X":
            {
                if (payload.Length != 10)
                {
                    break;
                }

                string?error          = null;
                var    reader         = new ByteArrayReader(payload, isMotorolaByteOrder: false);
                var    isAnimation    = false;
                var    hasAlpha       = false;
                var    widthMinusOne  = -1;
                var    heightMinusOne = -1;
                try
                {
                    // Flags
                    // bit 0: has fragments
                    // bit 1: is animation
                    // bit 2: has XMP
                    // bit 3: has Exif
                    // bit 4: has alpha
                    // big 5: has ICC
                    isAnimation = reader.GetBit(1);
                    hasAlpha    = reader.GetBit(4);

                    // Image size
                    widthMinusOne  = reader.GetInt24(4);
                    heightMinusOne = reader.GetInt24(7);
                }
                catch (IOException e)
                {
                    error = "Exception reading WebpRiff chunk 'VP8X' : " + e.Message;
                }

                var directory = new WebPDirectory();
                if (error == null)
                {
                    directory.Set(WebPDirectory.TagImageWidth, widthMinusOne + 1);
                    directory.Set(WebPDirectory.TagImageHeight, heightMinusOne + 1);
                    directory.Set(WebPDirectory.TagHasAlpha, hasAlpha);
                    directory.Set(WebPDirectory.TagIsAnimation, isAnimation);
                }
                else
                {
                    directory.AddError(error);
                }
                _directories.Add(directory);
                break;
            }

            case "VP8L":
            {
                if (payload.Length < 5)
                {
                    break;
                }

                var reader = new ByteArrayReader(payload, isMotorolaByteOrder: false);

                string?error          = null;
                var    widthMinusOne  = -1;
                var    heightMinusOne = -1;
                try
                {
                    // https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#2_riff_header

                    // Expect the signature byte
                    if (reader.GetByte(0) != 0x2F)
                    {
                        break;
                    }
                    var b1 = reader.GetByte(1);
                    var b2 = reader.GetByte(2);
                    var b3 = reader.GetByte(3);
                    var b4 = reader.GetByte(4);
                    // 14 bits for width
                    widthMinusOne = (b2 & 0x3F) << 8 | b1;
                    // 14 bits for height
                    heightMinusOne = (b4 & 0x0F) << 10 | b3 << 2 | (b2 & 0xC0) >> 6;
                }
                catch (IOException e)
                {
                    error = "Exception reading WebpRiff chunk 'VP8L' : " + e.Message;
                }

                var directory = new WebPDirectory();
                if (error == null)
                {
                    directory.Set(WebPDirectory.TagImageWidth, widthMinusOne + 1);
                    directory.Set(WebPDirectory.TagImageHeight, heightMinusOne + 1);
                }
                else
                {
                    directory.AddError(error);
                }
                _directories.Add(directory);
                break;
            }

            case "VP8 ":
            {
                if (payload.Length < 10)
                {
                    break;
                }

                var reader = new ByteArrayReader(payload, isMotorolaByteOrder: false);

                string?error  = null;
                var    width  = 0;
                var    height = 0;
                try
                {
                    // https://tools.ietf.org/html/rfc6386#section-9.1
                    // https://github.com/webmproject/libwebp/blob/master/src/enc/syntax.c#L115

                    // Expect the signature bytes
                    if (reader.GetByte(3) != 0x9D ||
                        reader.GetByte(4) != 0x01 ||
                        reader.GetByte(5) != 0x2A)
                    {
                        break;
                    }
                    width  = reader.GetUInt16(6);
                    height = reader.GetUInt16(8);
                }
                catch (IOException e)
                {
                    error = "Exception reading WebpRiff chunk 'VP8' : " + e.Message;
                }

                var directory = new WebPDirectory();
                if (error == null)
                {
                    directory.Set(WebPDirectory.TagImageWidth, width);
                    directory.Set(WebPDirectory.TagImageHeight, height);
                }
                else
                {
                    directory.AddError(error);
                }
                _directories.Add(directory);
                break;
            }
            }
        }
        private string GetTagDataString(int tagType)
        {
            try
            {
                var bytes = Directory.GetByteArray(tagType);
                if (bytes == null)
                {
                    return(Directory.GetString(tagType));
                }

                var reader = new ByteArrayReader(bytes);

                var iccTagType = (IccTagType)reader.GetInt32(0);

                switch (iccTagType)
                {
                case IccTagType.Text:
                {
#if !NETSTANDARD1_3
                    try
                    {
                        return(Encoding.ASCII.GetString(bytes, 8, bytes.Length - 8 - 1));
                    }
                    catch
#endif
                    {
                        return(Encoding.UTF8.GetString(bytes, 8, bytes.Length - 8 - 1));
                    }
                }

                case IccTagType.Desc:
                {
                    var stringLength = reader.GetInt32(8);
                    return(Encoding.UTF8.GetString(bytes, 12, stringLength - 1));
                }

                case IccTagType.Sig:
                {
                    return(IccReader.GetStringFromUInt32(reader.GetUInt32(8)));
                }

                case IccTagType.Meas:
                {
                    var observerType   = reader.GetInt32(8);
                    var x              = reader.GetS15Fixed16(12);
                    var y              = reader.GetS15Fixed16(16);
                    var z              = reader.GetS15Fixed16(20);
                    var geometryType   = reader.GetInt32(24);
                    var flare          = reader.GetS15Fixed16(28);
                    var illuminantType = reader.GetInt32(32);

                    string observerString;
                    switch (observerType)
                    {
                    case 0:
                        observerString = "Unknown";
                        break;

                    case 1:
                        observerString = "1931 2\u00b0";
                        break;

                    case 2:
                        observerString = "1964 10\u00b0";
                        break;

                    default:
                        observerString = $"Unknown ({observerType})";
                        break;
                    }

                    string geometryString;
                    switch (geometryType)
                    {
                    case 0:
                        geometryString = "Unknown";
                        break;

                    case 1:
                        geometryString = "0/45 or 45/0";
                        break;

                    case 2:
                        geometryString = "0/d or d/0";
                        break;

                    default:
                        geometryString = $"Unknown ({observerType})";
                        break;
                    }

                    string illuminantString;
                    switch (illuminantType)
                    {
                    case 0:
                        illuminantString = "unknown";
                        break;

                    case 1:
                        illuminantString = "D50";
                        break;

                    case 2:
                        illuminantString = "D65";
                        break;

                    case 3:
                        illuminantString = "D93";
                        break;

                    case 4:
                        illuminantString = "F2";
                        break;

                    case 5:
                        illuminantString = "D55";
                        break;

                    case 6:
                        illuminantString = "A";
                        break;

                    case 7:
                        illuminantString = "Equi-Power (E)";
                        break;

                    case 8:
                        illuminantString = "F8";
                        break;

                    default:
                        illuminantString = $"Unknown ({illuminantType})";
                        break;
                    }

                    return($"{observerString} Observer, Backing ({x:0.###}, {y:0.###}, {z:0.###}), Geometry {geometryString}, Flare {(long)Math.Round(flare*100)}%, Illuminant {illuminantString}");
                }

                case IccTagType.XyzArray:
                {
                    var res   = new StringBuilder();
                    var count = (bytes.Length - 8) / 12;

                    for (var i = 0; i < count; i++)
                    {
                        var x = reader.GetS15Fixed16(8 + i * 12);
                        var y = reader.GetS15Fixed16(8 + i * 12 + 4);
                        var z = reader.GetS15Fixed16(8 + i * 12 + 8);
                        if (i > 0)
                        {
                            res.Append(", ");
                        }
                        res.AppendFormat("({0:0.####}, {1:0.####}, {2:0.####})", x, y, z);
                    }

                    return(res.ToString());
                }

                case IccTagType.Mluc:
                {
                    var int1 = reader.GetInt32(8);
                    var res  = new StringBuilder();
                    res.Append(int1);
                    for (var i = 0; i < int1; i++)
                    {
                        var    str = IccReader.GetStringFromUInt32(reader.GetUInt32(16 + i * 12));
                        var    len = reader.GetInt32(16 + i * 12 + 4);
                        var    ofs = reader.GetInt32(16 + i * 12 + 8);
                        string name;
                        try
                        {
                            name = Encoding.BigEndianUnicode.GetString(bytes, ofs, len);
                        }
                        catch
                        {
                            name = Encoding.UTF8.GetString(bytes, ofs, len);
                        }
                        res.Append(" ").Append(str).Append("(").Append(name).Append(")");
                    }
                    return(res.ToString());
                }

                case IccTagType.Curv:
                {
                    var num = reader.GetInt32(8);
                    var res = new StringBuilder();
                    for (var i = 0; i < num; i++)
                    {
                        if (i != 0)
                        {
                            res.Append(", ");
                        }
                        res.Append(FormatDoubleAsString(reader.GetUInt16(12 + i * 2) / 65535.0, 7, false));
                    }
                    return(res.ToString());
                }

                default:
                {
                    return($"{IccReader.GetStringFromUInt32(unchecked((uint)iccTagType))} (0x{(int)iccTagType:X8}): {bytes.Length} bytes");
                }
                }
            }
            catch (IOException)
            {
                // TODO decode these values during IccReader.extract so we can report any errors at that time
                // It is convention to return null if a description cannot be formulated.
                // If an error is to be reported, it should be done during the extraction process.
                return(null);
            }
        }
        public string GetJpegQualityString()
        {
            try
            {
                var b = Directory.GetByteArray(PhotoshopDirectory.TagJpegQuality);

                if (b == null)
                {
                    return(Directory.GetString(PhotoshopDirectory.TagJpegQuality));
                }

                var reader = new ByteArrayReader(b);

                int q = reader.GetUInt16(0);
                int f = reader.GetUInt16(2);
                int s = reader.GetUInt16(4);

                var q1 = q <= 0xFFFF && q >= 0xFFFD
                    ? q - 0xFFFC
                    : q <= 8
                        ? q + 4
                        : q;

                string quality;
                switch (q)
                {
                case 0xFFFD:
                case 0xFFFE:
                case 0xFFFF:
                case 0:
                    quality = "Low";
                    break;

                case 1:
                case 2:
                case 3:
                    quality = "Medium";
                    break;

                case 4:
                case 5:
                    quality = "High";
                    break;

                case 6:
                case 7:
                case 8:
                    quality = "Maximum";
                    break;

                default:
                    quality = "Unknown";
                    break;
                }

                string format;
                switch (f)
                {
                case 0x0000:
                    format = "Standard";
                    break;

                case 0x0001:
                    format = "Optimised";
                    break;

                case 0x0101:
                    format = "Progressive";
                    break;

                default:
                    format = $"Unknown (0x{f:X4})";
                    break;
                }

                var scans = s >= 1 && s <= 3
                    ? (s + 2).ToString()
                    : $"Unknown (0x{s:X4})";

                return($"{q1} ({quality}), {format} format, {scans} scans");
            }
            catch
            {
                return(null);
            }
        }
Example #9
0
        public virtual string GetJpegQualityString()
        {
            try
            {
                sbyte[] b = _directory.GetByteArray(PhotoshopDirectory.TagJpegQuality);
                if (b == null)
                {
                    return(_directory.GetString(PhotoshopDirectory.TagJpegQuality));
                }
                RandomAccessReader reader = new ByteArrayReader(b);
                int q = reader.GetUInt16(0);
                // & 0xFFFF;
                int f = reader.GetUInt16(2);
                // & 0xFFFF;
                int s = reader.GetUInt16(4);
                int q1;
                if (q <= unchecked ((int)(0xFFFF)) && q >= unchecked ((int)(0xFFFD)))
                {
                    q1 = q - unchecked ((int)(0xFFFC));
                }
                else
                {
                    if (q <= 8)
                    {
                        q1 = q + 4;
                    }
                    else
                    {
                        q1 = q;
                    }
                }
                string quality;
                switch (q)
                {
                case unchecked ((int)(0xFFFD)):
                case unchecked ((int)(0xFFFE)):
                case unchecked ((int)(0xFFFF)):
                case 0:
                {
                    quality = "Low";
                    break;
                }

                case 1:
                case 2:
                case 3:
                {
                    quality = "Medium";
                    break;
                }

                case 4:
                case 5:
                {
                    quality = "High";
                    break;
                }

                case 6:
                case 7:
                case 8:
                {
                    quality = "Maximum";
                    break;
                }

                default:
                {
                    quality = "Unknown";
                    break;
                }
                }
                string format;
                switch (f)
                {
                case unchecked ((int)(0x0000)):
                {
                    format = "Standard";
                    break;
                }

                case unchecked ((int)(0x0001)):
                {
                    format = "Optimised";
                    break;
                }

                case unchecked ((int)(0x0101)):
                {
                    format = "Progressive ";
                    break;
                }

                default:
                {
                    format = Sharpen.Extensions.StringFormat("Unknown 0x%04X", f);
                    break;
                }
                }
                string scans = s >= 1 && s <= 3 ? Sharpen.Extensions.StringFormat("%d", s + 2) : Sharpen.Extensions.StringFormat("Unknown 0x%04X", s);
                return(Sharpen.Extensions.StringFormat("%d (%s), %s format, %s scans", q1, quality, format, scans));
            }
            catch (IOException)
            {
                return(null);
            }
        }
        public void ProcessChunk(string fourCc, byte[] payload)
        {
            switch (fourCc)
            {
            case "EXIF":
            {
                _directories.AddRange(new ExifReader().Extract(new ByteArrayReader(payload)));
                break;
            }

            case "ICCP":
            {
                _directories.Add(new IccReader().Extract(new ByteArrayReader(payload)));
                break;
            }

            case "XMP ":
            {
                _directories.Add(new XmpReader().Extract(payload));
                break;
            }

            case "VP8X":
            {
                if (payload.Length != 10)
                {
                    break;
                }

                string error          = null;
                var    reader         = new ByteArrayReader(payload, isMotorolaByteOrder: false);
                var    isAnimation    = false;
                var    hasAlpha       = false;
                var    widthMinusOne  = -1;
                var    heightMinusOne = -1;
                try
                {
                    // Flags
//                      var hasFragments = reader.GetBit(0);
                    isAnimation = reader.GetBit(1);
//                      var hasXmp = reader.GetBit(2);
//                      var hasExif = reader.GetBit(3);
                    hasAlpha = reader.GetBit(4);
//                      var hasIcc = reader.GetBit(5);
                    // Image size
                    widthMinusOne  = reader.GetInt24(4);
                    heightMinusOne = reader.GetInt24(7);
                }
                catch (IOException e)
                {
                    error = "Exception reading WebpRiff chunk 'VP8X' : " + e.Message;
                }

                var directory = new WebPDirectory();
                if (error == null)
                {
                    directory.Set(WebPDirectory.TagImageWidth, widthMinusOne + 1);
                    directory.Set(WebPDirectory.TagImageHeight, heightMinusOne + 1);
                    directory.Set(WebPDirectory.TagHasAlpha, hasAlpha);
                    directory.Set(WebPDirectory.TagIsAnimation, isAnimation);
                }
                else
                {
                    directory.AddError(error);
                }
                _directories.Add(directory);
                break;
            }

            case "VP8L":
            {
                if (payload.Length < 5)
                {
                    break;
                }

                var reader = new ByteArrayReader(payload, isMotorolaByteOrder: false);

                string error          = null;
                var    widthMinusOne  = -1;
                var    heightMinusOne = -1;
                try
                {
                    // https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification#2_riff_header

                    // Expect the signature byte
                    if (reader.GetByte(0) != 0x2F)
                    {
                        break;
                    }
                    var b1 = reader.GetByte(1);
                    var b2 = reader.GetByte(2);
                    var b3 = reader.GetByte(3);
                    var b4 = reader.GetByte(4);
                    // 14 bits for width
                    widthMinusOne = (b2 & 0x3F) << 8 | b1;
                    // 14 bits for height
                    heightMinusOne = (b4 & 0x0F) << 10 | b3 << 2 | (b2 & 0xC0) >> 6;
                }
                catch (IOException e)
                {
                    error = "Exception reading WebpRiff chunk 'VP8L' : " + e.Message;
                }

                var directory = new WebPDirectory();
                if (error == null)
                {
                    directory.Set(WebPDirectory.TagImageWidth, widthMinusOne + 1);
                    directory.Set(WebPDirectory.TagImageHeight, heightMinusOne + 1);
                }
                else
                {
                    directory.AddError(error);
                }
                _directories.Add(directory);
                break;
            }

            case "VP8 ":
            {
                if (payload.Length < 10)
                {
                    break;
                }

                var reader = new ByteArrayReader(payload, isMotorolaByteOrder: false);

                string error  = null;
                var    width  = 0;
                var    height = 0;
                try
                {
                    // https://tools.ietf.org/html/rfc6386#section-9.1
                    // https://github.com/webmproject/libwebp/blob/master/src/enc/syntax.c#L115

                    // Expect the signature bytes
                    if (reader.GetByte(3) != 0x9D ||
                        reader.GetByte(4) != 0x01 ||
                        reader.GetByte(5) != 0x2A)
                    {
                        break;
                    }
                    width  = reader.GetUInt16(6);
                    height = reader.GetUInt16(8);
                }
                catch (IOException e)
                {
                    error = "Exception reading WebpRiff chunk 'VP8' : " + e.Message;
                }

                var directory = new WebPDirectory();
                if (error == null)
                {
                    directory.Set(WebPDirectory.TagImageWidth, width);
                    directory.Set(WebPDirectory.TagImageHeight, height);
                }
                else
                {
                    directory.AddError(error);
                }
                _directories.Add(directory);
                break;
            }
            }
        }