/// <summary> /// Reads atom data from <paramref name="stream"/>, invoking <paramref name="handler"/> for each atom encountered. /// </summary> /// <param name="stream">The stream to read atoms from.</param> /// <param name="handler">A callback function to handle each atom.</param> /// <param name="stopByBytes">The maximum number of bytes to process before discontinuing.</param> public static void ProcessAtoms([NotNull] Stream stream, [NotNull] Action <AtomCallbackArgs> handler, long stopByBytes = -1) { var reader = new SequentialStreamReader(stream); var seriesStartPos = stream.Position; while (stopByBytes == -1 || stream.Position < seriesStartPos + stopByBytes) { var atomStartPos = stream.Position; // Length of the atom's data, in bytes, including size bytes long atomSize; try { atomSize = reader.GetUInt32(); } catch (IOException) { // TODO don't use exception to trap end of stream return; } // Typically four ASCII characters, but may be non-printable. // By convention, lowercase 4CCs are reserved by Apple. var atomType = reader.GetUInt32(); if (atomSize == 1) { // Size doesn't fit in 32 bits so read the 64 bit size here atomSize = checked ((long)reader.GetUInt64()); } else { Debug.Assert(atomSize >= 8, "Atom should be at least 8 bytes long"); } var args = new AtomCallbackArgs(atomType, atomSize, stream, atomStartPos, reader); handler(args); if (args.Cancel) { return; } if (atomSize == 0) { return; } var toSkip = atomStartPos + atomSize - stream.Position; if (toSkip < 0) { throw new Exception("Handler moved stream beyond end of atom"); } reader.TrySkip(toSkip); } }
public AtomCallbackArgs(uint type, long size, Stream stream, long startPosition, SequentialStreamReader reader) { Type = type; Size = size; Stream = stream; StartPosition = startPosition; Reader = reader; }
protected override TgaFooter Extract(Stream stream, int offset) { var reader = new SequentialStreamReader(stream, isMotorolaByteOrder: false); return(new TgaFooter( extOffset: reader.GetInt32(), devOffset: reader.GetInt32(), signature: reader.GetBytes(_footerSignature.Length))); }
protected override TgaTagInfo[] Extract(Stream stream, int _) { var reader = new SequentialStreamReader(stream, isMotorolaByteOrder: false); var count = reader.GetUInt16(); var tags = new TgaTagInfo[count]; for (int i = 0; i < count; i++) { tags[i] = GetTag(reader); } return(tags);
/// <summary> /// Reads atom data from <paramref name="stream"/>, invoking <paramref name="handler"/> for each atom encountered. /// </summary> /// <param name="stream">The stream to read atoms from.</param> /// <param name="handler">A callback function to handle each atom.</param> /// <param name="stopByBytes">The maximum number of bytes to process before discontinuing.</param> public static void ProcessAtoms([NotNull] Stream stream, [NotNull] Action <AtomCallbackArgs> handler, long stopByBytes = -1) { var reader = new SequentialStreamReader(stream); var seriesStartPos = stream.Position; while (stopByBytes == -1 || stream.Position < seriesStartPos + stopByBytes) { var atomStartPos = stream.Position; try { // Check if the end of the stream is closer then 8 bytes to current position (Length of the atom's data + atom type) if (reader.IsCloserToEnd(8)) { return; } // Length of the atom's data, in bytes, including size bytes long atomSize = reader.GetUInt32(); // Typically four ASCII characters, but may be non-printable. // By convention, lowercase 4CCs are reserved by Apple. var atomType = reader.GetUInt32(); if (atomSize == 1) { // Check if the end of the stream is closer then 8 bytes if (reader.IsCloserToEnd(8)) { return; } // Size doesn't fit in 32 bits so read the 64 bit size here atomSize = checked ((long)reader.GetUInt64()); } else if (atomSize < 8) { // Atom should be at least 8 bytes long return; } var args = new AtomCallbackArgs(atomType, atomSize, stream, atomStartPos, reader); handler(args); if (args.Cancel) { return; } if (atomSize == 0) { return; } var toSkip = atomStartPos + atomSize - stream.Position; if (toSkip < 0) { throw new Exception("Handler moved stream beyond end of atom"); } // To avoid exception handling we can check if needed number of bytes are available if (!reader.IsCloserToEnd(toSkip)) { reader.TrySkip(toSkip); } } catch (IOException) { // Exception trapping is used when stream doesn't support stream length method only return; } } }
protected override void Populate(Stream stream, int offset, TgaExtensionDirectory directory) { var reader = new SequentialStreamReader(stream, isMotorolaByteOrder: false); var size = reader.GetUInt16(); if (size < ExtensionSize) { throw new ImageProcessingException("Invalid TGA extension size"); } var authorName = GetString(41); if (authorName.Length > 0) { directory.Set(TgaExtensionDirectory.TagAuthorName, authorName); } var comments = GetString(324); if (comments.Length > 0) { directory.Set(TgaExtensionDirectory.TagComments, comments); } if (TryGetDateTime(out var dateTime)) { directory.Set(TgaExtensionDirectory.TagDateTime, dateTime); } var jobName = GetString(41); if (jobName.Length > 0) { directory.Set(TgaExtensionDirectory.TagJobName, jobName); } if (TryGetTimeSpan(out var jobTime)) { directory.Set(TgaExtensionDirectory.TagJobTime, jobTime); } var softwareName = GetString(41); if (softwareName.Length > 0) { directory.Set(TgaExtensionDirectory.TagSoftwareName, softwareName); } var softwareVersion = GetSoftwareVersion(softwareName); if (softwareVersion.Length > 0) { directory.Set(TgaExtensionDirectory.TagSoftwareVersion, softwareVersion); } var keyColor = reader.GetUInt32(); if (keyColor != 0) { directory.Set(TgaExtensionDirectory.TagKeyColor, keyColor); } if (TryGetRational(out var aspectRatio)) { directory.Set(TgaExtensionDirectory.TagAspectRatio, aspectRatio); } if (TryGetRational(out var gamma)) { directory.Set(TgaExtensionDirectory.TagGamma, gamma); } var colorCorrectionOffset = reader.GetInt32(); if (colorCorrectionOffset != 0) { directory.Set(TgaExtensionDirectory.TagColorCorrectionOffset, colorCorrectionOffset); } var thumbnailOffset = reader.GetInt32(); if (thumbnailOffset != 0) { directory.Set(TgaExtensionDirectory.TagThumbnailOffset, thumbnailOffset); } var scanLineOffset = reader.GetInt32(); if (scanLineOffset != 0) { directory.Set(TgaExtensionDirectory.TagScanLineOffset, scanLineOffset); } var attributesType = reader.GetByte(); directory.Set(TgaExtensionDirectory.TagAttributesType, attributesType); string GetString(int length) { var buffer = new byte[length]; reader.GetBytes(buffer, 0, length); int i = 0; while (i < buffer.Length && buffer[i] != '\0') { ++i; } return(Encoding.ASCII.GetString(buffer, 0, i).TrimEnd()); } bool TryGetDateTime(out DateTime dateTime) { var month = reader.GetInt16(); var day = reader.GetInt16(); var year = reader.GetInt16(); var hour = reader.GetInt16(); var minute = reader.GetInt16(); var second = reader.GetInt16(); if (month == 0 && day == 0 && year == 0) { dateTime = DateTime.MinValue; return(false); } dateTime = new DateTime(year, month, day, hour, minute, second); return(true); } bool TryGetTimeSpan(out TimeSpan timeSpan) { var hours = reader.GetInt16(); var minutes = reader.GetInt16(); var seconds = reader.GetInt16(); if (hours == 0 && minutes == 0 && seconds == 0) { timeSpan = TimeSpan.Zero; return(false); } timeSpan = new TimeSpan(hours, minutes, seconds); return(true); } string GetSoftwareVersion(string softwareName) { var number = reader.GetUInt16(); var letter = reader.GetByte(); if (number == 0) { return(string.Empty); } var sb = new StringBuilder(); var denom = softwareName != "Paint Shop Pro" ? 100 : 0x100; sb.Append(number / denom); sb.Append('.'); sb.Append(number % denom); if (letter != 0 && letter != 0x20) { sb.Append((char)letter); } return(sb.ToString()); } bool TryGetRational(out Rational value) { var num = reader.GetUInt16(); var denom = reader.GetUInt16(); if (denom == 0) { value = default; return(false); } value = new Rational(num, denom); return(true); } }
private const int ExifTag = 0x45786966; // Exif public static DirectoryList ReadMetadata(Stream stream) { var directories = new List <Directory>(); // // Read all boxes from the file // var reader = new SequentialStreamReader(stream); var boxes = BoxReader.ReadBoxes(reader); // // Map those boxes to directories // ParseQuickTimeTest(); uint primaryItem = boxes.Descendant <PrimaryItemBox>()?.PrimaryItem ?? uint.MaxValue; var itemRefs = (boxes.Descendant <ItemReferenceBox>()?.Boxes ?? new SingleItemTypeReferenceBox[0]) .Where(i => i.Type == BoxTypes.ThmbTag || i.Type == BoxTypes.CdscTag).ToList(); ParseImageProperties(); if (stream.CanSeek) { ParseItemSegments(); } return(directories); void ParseQuickTimeTest() { if (boxes.Descendant <FileTypeBox>() is { } ftype) { var dir = new QuickTimeFileTypeDirectory(); if (ftype.MajorBrand > 0) { dir.Set(QuickTimeFileTypeDirectory.TagMajorBrand, ftype.MajorBrandString); } if (ftype.MinorBrand > 0) { dir.Set(QuickTimeFileTypeDirectory.TagMinorVersion, ftype.MinorBrandString); } if (ftype.CompatibleBrands.Count > 0) { dir.Set( QuickTimeFileTypeDirectory.TagCompatibleBrands, string.Join(", ", ftype.CompatibleBrandStrings.ToArray())); } directories.Add(dir); } } void ParseImageProperties() { uint[] allPrimaryTiles = (boxes.Descendant <ItemReferenceBox>()?.Boxes ?? new SingleItemTypeReferenceBox[0]) .SelectMany(i => i.FromItemId == primaryItem && i.Type == BoxTypes.DimgTag ? i.ToItemIds : new uint[0]) .ToArray(); var itemPropertyBox = boxes.Descendant <ItemPropertyBox>(); if (itemPropertyBox == null) { return; } var props = itemPropertyBox.Boxes.Descendant <ItemPropertyContainerBox>().Boxes; var associations = itemPropertyBox.Boxes.Descendant <ItemPropertyAssociationBox>(); ParsePropertyBoxes( "HEIC Primary Item Properties", ImageProperties(primaryItem, allPrimaryTiles, associations, props)); foreach (var itemRef in itemRefs) { ParsePropertyBoxes( "HEIC Thumbnail Properties", ImageProperties(itemRef.FromItemId, new uint[0], associations, props)); } return;