/// <exception cref="PngProcessingException"/> public PngHeader([NotNull] byte[] bytes) { if (bytes.Length != 13) { throw new PngProcessingException("PNG header chunk must have exactly 13 data bytes"); } var reader = new SequentialByteArrayReader(bytes); ImageWidth = reader.GetInt32(); ImageHeight = reader.GetInt32(); BitsPerSample = reader.GetByte(); var colorTypeNumber = reader.GetByte(); var colorType = PngColorType.FromNumericValue(colorTypeNumber); if (colorType == null) { throw new PngProcessingException("Unexpected PNG color type: " + colorTypeNumber); } ColorType = colorType; CompressionType = reader.GetByte(); FilterMethod = reader.GetByte(); InterlaceMethod = reader.GetByte(); }
/// <summary> /// Inserts a new Xmp App1 segment to the output memory stream. /// The segment will be inserted -2 bytes from the current position. /// </summary> /// <param name="xmp">Xmp document to be inserted</param> /// <param name="input">Stream of bytes in the original file</param> /// <param name="output">Stream of bytes in the newly composed file</param> /// <param name="nextSegmentIdentifier">The identifier of the segment that began at position -2</param> /// <param name="nextSegmentTypeByte">The identifier of the segment that began at position -2</param> /// <returns>True, indicating that the Xmp content was inserted</returns> private static bool InsertApp1XmpSegment(XDocument xmp, SequentialByteArrayReader input, MemoryStream output, byte nextSegmentIdentifier, byte nextSegmentTypeByte) { // 1. remember the segment that comes after byte[] nextSegmentMarker = new byte[] { nextSegmentIdentifier, nextSegmentTypeByte }; // 2. open a new App1 segment in place of the recently encountered segment output.Seek(-2, SeekOrigin.Current); // reference: http://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_JPEG_files byte[] app1marker = new byte[] { 0xFF, 0xE1 }; output.Write(app1marker, 0, app1marker.Length); // 3. prepare the new Xmp segment byte[] segmentBytes = ByteArrayFromXmpXDocument(xmp); byte[] lengthMark = BytesFromSegmentLength(segmentBytes.Length, input.IsMotorolaByteOrder); byte highByte = lengthMark[0]; byte lowByte = lengthMark[1]; // 4. write the new segment to the output output.WriteByte(highByte); output.WriteByte(lowByte); output.Write(segmentBytes, 0, segmentBytes.Length); // 5. now write the segment marker that was replaced // (in the next iteration it will just proceed with that segment) output.Write(nextSegmentMarker, 0, nextSegmentMarker.Length); return(true); }
public string GetBackgroundColorDescription() { var bytes = Directory.GetByteArray(PngDirectory.TagBackgroundColor); if (bytes == null || !Directory.TryGetInt32(PngDirectory.TagColorType, out int colorType)) { return(null); } var reader = new SequentialByteArrayReader(bytes); try { switch (colorType) { case 0: case 4: // TODO do we need to normalise these based upon the bit depth? return($"Greyscale Level {reader.GetUInt16()}"); case 2: case 6: return($"R {reader.GetUInt16()}, G {reader.GetUInt16()}, B {reader.GetUInt16()}"); case 3: return($"Palette Index {reader.GetByte()}"); } } catch (IOException) { return(null); } return(null); }
public virtual void Extract(sbyte[] segmentBytes, Com.Drew.Metadata.Metadata metadata, JpegSegmentType segmentType) { JpegDirectory directory = new JpegDirectory(); metadata.AddDirectory(directory); // The value of TAG_COMPRESSION_TYPE is determined by the segment type found directory.SetInt(JpegDirectory.TagCompressionType, segmentType.byteValue - JpegSegmentType.Sof0.byteValue); SequentialReader reader = new SequentialByteArrayReader(segmentBytes); try { directory.SetInt(JpegDirectory.TagDataPrecision, reader.GetUInt8()); directory.SetInt(JpegDirectory.TagImageHeight, reader.GetUInt16()); directory.SetInt(JpegDirectory.TagImageWidth, reader.GetUInt16()); short componentCount = reader.GetUInt8(); directory.SetInt(JpegDirectory.TagNumberOfComponents, componentCount); // for each component, there are three bytes of data: // 1 - Component ID: 1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q // 2 - Sampling factors: bit 0-3 vertical, 4-7 horizontal // 3 - Quantization table number for (int i = 0; i < (int)componentCount; i++) { int componentId = reader.GetUInt8(); int samplingFactorByte = reader.GetUInt8(); int quantizationTableNumber = reader.GetUInt8(); JpegComponent component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber); directory.SetObject(JpegDirectory.TagComponentData1 + i, component); } } catch (IOException ex) { directory.AddError(ex.Message); } }
/// <exception cref="Com.Drew.Imaging.Png.PngProcessingException"/> public PngHeader(sbyte[] bytes) { if (bytes.Length != 13) { throw new PngProcessingException("PNG header chunk must have 13 data bytes"); } SequentialReader reader = new SequentialByteArrayReader(bytes); try { _imageWidth = reader.GetInt32(); _imageHeight = reader.GetInt32(); _bitsPerSample = reader.GetInt8(); sbyte colorTypeNumber = reader.GetInt8(); PngColorType colorType = PngColorType.FromNumericValue(colorTypeNumber); if (colorType == null) { throw new PngProcessingException("Unexpected PNG color type: " + colorTypeNumber); } _colorType = colorType; _compressionType = reader.GetInt8(); _filterMethod = reader.GetInt8(); _interlaceMethod = reader.GetInt8(); } catch (IOException e) { // Should never happen throw new PngProcessingException(e); } }
public string GetBackgroundColorDescription() { var bytes = Directory.GetByteArray(PngDirectory.TagBackgroundColor); if (bytes == null) { return(null); } var reader = new SequentialByteArrayReader(bytes); try { // TODO do we need to normalise these based upon the bit depth? switch (bytes.Length) { case 1: return($"Palette Index {reader.GetByte()}"); case 2: return($"Greyscale Level {reader.GetUInt16()}"); case 6: return($"R {reader.GetUInt16()}, G {reader.GetUInt16()}, B {reader.GetUInt16()}"); } } catch (IOException) { return(null); } return(null); }
/// <summary> /// Copies an App1 segment and may replace it with Xmp. /// </summary> /// <param name="xmp">Xmp content that shall be used</param> /// <param name="input">Stream of bytes in the original file</param> /// <param name="output">Stream of bytes in the newly composed file</param> /// <param name="wroteXmp"></param> /// <returns>indicates if the Xmp content was written to this segment</returns> private static bool CopyOrUpdateApp1Segment(XDocument xmp, SequentialByteArrayReader input, MemoryStream output, bool wroteXmp) { // 1. read the segment index that encodes the segment length byte highByte = input.GetByte(); byte lowByte = input.GetByte(); int segmentLength = SegmentLengthFromBytes(highByte, lowByte, input.IsMotorolaByteOrder); // 2. read the rest of the segment byte[] segmentBytes = input.GetBytes(segmentLength); // 3. if this is the XMP segment, overwrite it with the new segment int preambleLength = XmpReader.JpegSegmentPreamble.Length; if (segmentBytes.Length >= preambleLength && XmpReader.JpegSegmentPreamble.Equals(Encoding.UTF8.GetString(segmentBytes, 0, preambleLength), StringComparison.OrdinalIgnoreCase)) { // overwrite the payload segmentBytes = ByteArrayFromXmpXDocument(xmp); // and update the segment length!! byte[] lengthMark = BytesFromSegmentLength(segmentBytes.Length, input.IsMotorolaByteOrder); highByte = lengthMark[0]; lowByte = lengthMark[1]; // indicate that the Xmp was written to this segment wroteXmp = true; } // 4. only now write the entire segment (index + payload) output.WriteByte(highByte); output.WriteByte(lowByte); output.Write(segmentBytes, 0, segmentBytes.Length); return(wroteXmp); }
/// <exception cref="PngProcessingException"/> public PngChromaticities(byte[] bytes) { if (bytes.Length != 8 * 4) { throw new PngProcessingException("Invalid number of bytes"); } var reader = new SequentialByteArrayReader(bytes); try { WhitePointX = reader.GetInt32(); WhitePointY = reader.GetInt32(); RedX = reader.GetInt32(); RedY = reader.GetInt32(); GreenX = reader.GetInt32(); GreenY = reader.GetInt32(); BlueX = reader.GetInt32(); BlueY = reader.GetInt32(); } catch (IOException ex) { throw new PngProcessingException(ex); } }
private void ProcessCameraSettings([NotNull] byte[] bytes) { var reader = new SequentialByteArrayReader(bytes); var count = bytes.Length / 4; for (var i = 0; i < count; i++) { Set(CameraSettings.Offset + i, reader.GetInt32()); } }
/// <exception cref="PngProcessingException"/> public PngHeader(byte[] bytes) { if (bytes.Length != 13) { throw new PngProcessingException("PNG header chunk must have exactly 13 data bytes"); } var reader = new SequentialByteArrayReader(bytes); ImageWidth = reader.GetInt32(); ImageHeight = reader.GetInt32(); BitsPerSample = reader.GetByte(); ColorType = PngColorType.FromNumericValue(reader.GetByte()); CompressionType = reader.GetByte(); FilterMethod = reader.GetByte(); InterlaceMethod = reader.GetByte(); }
private void ProcessCameraSettings(sbyte[] bytes) { SequentialByteArrayReader reader = new SequentialByteArrayReader(bytes); reader.SetMotorolaByteOrder(true); int count = bytes.Length / 4; try { for (int i = 0; i < count; i++) { int value = reader.GetInt32(); SetInt(OlympusMakernoteDirectory.CameraSettings.Offset + i, value); } } catch (IOException e) { // Should never happen, given that we check the length of the bytes beforehand. Sharpen.Runtime.PrintStackTrace(e); } }
private void ProcessCameraSettings([NotNull] byte[] bytes) { var reader = new SequentialByteArrayReader(bytes) { IsMotorolaByteOrder = true }; var count = bytes.Length / 4; try { for (var i = 0; i < count; i++) { Set(CameraSettings.Offset + i, reader.GetInt32()); } } catch (IOException e) { // Should never happen, given that we check the length of the bytes beforehand. Console.WriteLine(e); } }
public virtual string GetBackgroundColorDescription() { sbyte[] bytes = _directory.GetByteArray(PngDirectory.TagBackgroundColor); int? colorType = _directory.GetInteger(PngDirectory.TagColorType); if (bytes == null || colorType == null) { return(null); } SequentialReader reader = new SequentialByteArrayReader(bytes); try { switch (colorType) { case 0: case 4: { // TODO do we need to normalise these based upon the bit depth? return(Sharpen.Extensions.StringFormat("Greyscale Level %d", reader.GetUInt16())); } case 2: case 6: { return(Sharpen.Extensions.StringFormat("R %d, G %d, B %d", reader.GetUInt16(), reader.GetUInt16(), reader.GetUInt16())); } case 3: { return(Sharpen.Extensions.StringFormat("Palette Index %d", reader.GetUInt8())); } } } catch (IOException) { return(null); } return(null); }
/// <summary>Reads JPEG SOF values and returns them in a <see cref="JpegDirectory"/>.</summary> public JpegDirectory Extract(JpegSegment segment) { var directory = new JpegDirectory(); // The value of TagCompressionType is determined by the segment type found directory.Set(JpegDirectory.TagCompressionType, (int)segment.Type - (int)JpegSegmentType.Sof0); SequentialReader reader = new SequentialByteArrayReader(segment.Bytes); try { directory.Set(JpegDirectory.TagDataPrecision, reader.GetByte()); directory.Set(JpegDirectory.TagImageHeight, reader.GetUInt16()); directory.Set(JpegDirectory.TagImageWidth, reader.GetUInt16()); var componentCount = reader.GetByte(); directory.Set(JpegDirectory.TagNumberOfComponents, componentCount); // For each component, there are three bytes of data: // 1 - Component ID: 1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q // 2 - Sampling factors: bit 0-3 vertical, 4-7 horizontal // 3 - Quantization table number for (var i = 0; i < componentCount; i++) { var componentId = reader.GetByte(); var samplingFactorByte = reader.GetByte(); var quantizationTableNumber = reader.GetByte(); var component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber); directory.Set(JpegDirectory.TagComponentData1 + i, component); } } catch (IOException ex) { directory.AddError(ex.Message); } return(directory); }
/// <exception cref="PngProcessingException"/> /// <exception cref="System.IO.IOException"/> private static IEnumerable <Directory> ProcessChunk([NotNull] PngChunk chunk) { var chunkType = chunk.ChunkType; var bytes = chunk.Bytes; if (chunkType == PngChunkType.IHDR) { var header = new PngHeader(bytes); var directory = new PngDirectory(PngChunkType.IHDR); directory.Set(PngDirectory.TagImageWidth, header.ImageWidth); directory.Set(PngDirectory.TagImageHeight, header.ImageHeight); directory.Set(PngDirectory.TagBitsPerSample, header.BitsPerSample); directory.Set(PngDirectory.TagColorType, header.ColorType.NumericValue); directory.Set(PngDirectory.TagCompressionType, header.CompressionType); directory.Set(PngDirectory.TagFilterMethod, header.FilterMethod); directory.Set(PngDirectory.TagInterlaceMethod, header.InterlaceMethod); yield return(directory); } else if (chunkType == PngChunkType.PLTE) { var directory = new PngDirectory(PngChunkType.PLTE); directory.Set(PngDirectory.TagPaletteSize, bytes.Length / 3); yield return(directory); } else if (chunkType == PngChunkType.tRNS) { var directory = new PngDirectory(PngChunkType.tRNS); directory.Set(PngDirectory.TagPaletteHasTransparency, 1); yield return(directory); } else if (chunkType == PngChunkType.sRGB) { int srgbRenderingIntent = unchecked ((sbyte)bytes[0]); var directory = new PngDirectory(PngChunkType.sRGB); directory.Set(PngDirectory.TagSrgbRenderingIntent, srgbRenderingIntent); yield return(directory); } else if (chunkType == PngChunkType.cHRM) { var chromaticities = new PngChromaticities(bytes); var directory = new PngChromaticitiesDirectory(); directory.Set(PngChromaticitiesDirectory.TagWhitePointX, chromaticities.WhitePointX); directory.Set(PngChromaticitiesDirectory.TagWhitePointY, chromaticities.WhitePointY); directory.Set(PngChromaticitiesDirectory.TagRedX, chromaticities.RedX); directory.Set(PngChromaticitiesDirectory.TagRedY, chromaticities.RedY); directory.Set(PngChromaticitiesDirectory.TagGreenX, chromaticities.GreenX); directory.Set(PngChromaticitiesDirectory.TagGreenY, chromaticities.GreenY); directory.Set(PngChromaticitiesDirectory.TagBlueX, chromaticities.BlueX); directory.Set(PngChromaticitiesDirectory.TagBlueY, chromaticities.BlueY); yield return(directory); } else if (chunkType == PngChunkType.gAMA) { var gammaInt = ByteConvert.ToInt32BigEndian(bytes); var directory = new PngDirectory(PngChunkType.gAMA); directory.Set(PngDirectory.TagGamma, gammaInt / 100000.0); yield return(directory); } else if (chunkType == PngChunkType.iCCP) { var reader = new SequentialByteArrayReader(bytes); var profileName = reader.GetNullTerminatedStringValue(maxLengthBytes: 79); var directory = new PngDirectory(PngChunkType.iCCP); directory.Set(PngDirectory.TagIccProfileName, profileName); var compressionMethod = reader.GetSByte(); if (compressionMethod == 0) { // Only compression method allowed by the spec is zero: deflate // This assumes 1-byte-per-char, which it is by spec. var bytesLeft = bytes.Length - profileName.Bytes.Length - 2; var compressedProfile = reader.GetBytes(bytesLeft); using (var inflaterStream = new DeflateStream(new MemoryStream(compressedProfile), CompressionMode.Decompress)) // using (var inflaterStream = new InflaterInputStream(new MemoryStream(compressedProfile))) { var iccDirectory = new IccReader().Extract(new IndexedCapturingReader(inflaterStream)); iccDirectory.Parent = directory; yield return(iccDirectory); } } else { directory.AddError("Invalid compression method value"); } yield return(directory); } else if (chunkType == PngChunkType.bKGD) { var directory = new PngDirectory(PngChunkType.bKGD); directory.Set(PngDirectory.TagBackgroundColor, bytes); yield return(directory); } else if (chunkType == PngChunkType.tEXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(_latin1Encoding); var bytesLeft = bytes.Length - keyword.Length - 1; var value = reader.GetNullTerminatedStringValue(bytesLeft, _latin1Encoding); var textPairs = new List <KeyValuePair> { new KeyValuePair(keyword, value) }; var directory = new PngDirectory(PngChunkType.iTXt); directory.Set(PngDirectory.TagTextualData, textPairs); yield return(directory); } else if (chunkType == PngChunkType.zTXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(_latin1Encoding); var compressionMethod = reader.GetSByte(); var bytesLeft = bytes.Length - keyword.Length - 1 - 1 - 1 - 1; byte[] textBytes = null; if (compressionMethod == 0) { using (var inflaterStream = new DeflateStream(new MemoryStream(bytes, bytes.Length - bytesLeft, bytesLeft), CompressionMode.Decompress)) using (var decompStream = new MemoryStream()) { #if !NET35 inflaterStream.CopyTo(decompStream); #else byte[] buffer = new byte[256]; int count; int totalBytes = 0; while ((count = inflaterStream.Read(buffer, 0, 256)) > 0) { decompStream.Write(buffer, 0, count); totalBytes += count; } #endif textBytes = decompStream.ToArray(); } } else { var directory = new PngDirectory(PngChunkType.zTXt); directory.AddError("Invalid compression method value"); yield return(directory); } if (textBytes != null) { if (keyword == "XML:com.adobe.xmp") { // NOTE in testing images, the XMP has parsed successfully, but we are not extracting tags from it as necessary yield return(new XmpReader().Extract(textBytes)); } else { var textPairs = new List <KeyValuePair> { new KeyValuePair(keyword, new StringValue(textBytes, _latin1Encoding)) }; var directory = new PngDirectory(PngChunkType.zTXt); directory.Set(PngDirectory.TagTextualData, textPairs); yield return(directory); } } } else if (chunkType == PngChunkType.iTXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(_latin1Encoding); var compressionFlag = reader.GetSByte(); var compressionMethod = reader.GetSByte(); // TODO we currently ignore languageTagBytes and translatedKeywordBytes var languageTagBytes = reader.GetNullTerminatedBytes(bytes.Length); var translatedKeywordBytes = reader.GetNullTerminatedBytes(bytes.Length); var bytesLeft = bytes.Length - keyword.Length - 1 - 1 - 1 - languageTagBytes.Length - 1 - translatedKeywordBytes.Length - 1; byte[] textBytes = null; if (compressionFlag == 0) { textBytes = reader.GetNullTerminatedBytes(bytesLeft); } else if (compressionFlag == 1) { if (compressionMethod == 0) { using (var inflaterStream = new DeflateStream(new MemoryStream(bytes, bytes.Length - bytesLeft, bytesLeft), CompressionMode.Decompress)) using (var decompStream = new MemoryStream()) { #if !NET35 inflaterStream.CopyTo(decompStream); #else byte[] buffer = new byte[256]; int count; int totalBytes = 0; while ((count = inflaterStream.Read(buffer, 0, 256)) > 0) { decompStream.Write(buffer, 0, count); totalBytes += count; } #endif textBytes = decompStream.ToArray(); } } else { var directory = new PngDirectory(PngChunkType.iTXt); directory.AddError("Invalid compression method value"); yield return(directory); } } else { var directory = new PngDirectory(PngChunkType.iTXt); directory.AddError("Invalid compression flag value"); yield return(directory); } if (textBytes != null) { if (keyword == "XML:com.adobe.xmp") { // NOTE in testing images, the XMP has parsed successfully, but we are not extracting tags from it as necessary yield return(new XmpReader().Extract(textBytes)); } else { var textPairs = new List <KeyValuePair> { new KeyValuePair(keyword, new StringValue(textBytes, _latin1Encoding)) }; var directory = new PngDirectory(PngChunkType.iTXt); directory.Set(PngDirectory.TagTextualData, textPairs); yield return(directory); } } } else if (chunkType == PngChunkType.tIME) { var reader = new SequentialByteArrayReader(bytes); var year = reader.GetUInt16(); var month = reader.GetByte(); int day = reader.GetByte(); int hour = reader.GetByte(); int minute = reader.GetByte(); int second = reader.GetByte(); var directory = new PngDirectory(PngChunkType.tIME); try { var time = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified); directory.Set(PngDirectory.TagLastModificationTime, time); } catch (ArgumentOutOfRangeException e) { directory.AddError("Error constructing DateTime: " + e.Message); } yield return(directory); } else if (chunkType == PngChunkType.pHYs) { var reader = new SequentialByteArrayReader(bytes); var pixelsPerUnitX = reader.GetInt32(); var pixelsPerUnitY = reader.GetInt32(); var unitSpecifier = reader.GetSByte(); var directory = new PngDirectory(PngChunkType.pHYs); directory.Set(PngDirectory.TagPixelsPerUnitX, pixelsPerUnitX); directory.Set(PngDirectory.TagPixelsPerUnitY, pixelsPerUnitY); directory.Set(PngDirectory.TagUnitSpecifier, unitSpecifier); yield return(directory); } else if (chunkType.Equals(PngChunkType.sBIT)) { var directory = new PngDirectory(PngChunkType.sBIT); directory.Set(PngDirectory.TagSignificantBits, bytes); yield return(directory); } }
private static BitReader CreateReader(params byte[] sourceData) { var sr = new SequentialByteArrayReader(sourceData); return(new BitReader(sr)); }
/// <exception cref="Com.Drew.Imaging.Png.PngProcessingException"/> /// <exception cref="System.IO.IOException"/> private static void ProcessChunk([NotNull] Com.Drew.Metadata.Metadata metadata, [NotNull] PngChunk chunk) { PngChunkType chunkType = chunk.GetChunkType(); sbyte[] bytes = chunk.GetBytes(); if (chunkType.Equals(PngChunkType.Ihdr)) { PngHeader header = new PngHeader(bytes); PngDirectory directory = new PngDirectory(PngChunkType.Ihdr); directory.SetInt(PngDirectory.TagImageWidth, header.GetImageWidth()); directory.SetInt(PngDirectory.TagImageHeight, header.GetImageHeight()); directory.SetInt(PngDirectory.TagBitsPerSample, header.GetBitsPerSample()); directory.SetInt(PngDirectory.TagColorType, header.GetColorType().GetNumericValue()); directory.SetInt(PngDirectory.TagCompressionType, header.GetCompressionType()); directory.SetInt(PngDirectory.TagFilterMethod, header.GetFilterMethod()); directory.SetInt(PngDirectory.TagInterlaceMethod, header.GetInterlaceMethod()); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.Plte)) { PngDirectory directory = new PngDirectory(PngChunkType.Plte); directory.SetInt(PngDirectory.TagPaletteSize, bytes.Length / 3); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.tRNS)) { PngDirectory directory = new PngDirectory(PngChunkType.tRNS); directory.SetInt(PngDirectory.TagPaletteHasTransparency, 1); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.sRGB)) { int srgbRenderingIntent = new SequentialByteArrayReader(bytes).GetInt8(); PngDirectory directory = new PngDirectory(PngChunkType.sRGB); directory.SetInt(PngDirectory.TagSrgbRenderingIntent, srgbRenderingIntent); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.cHRM)) { PngChromaticities chromaticities = new PngChromaticities(bytes); PngChromaticitiesDirectory directory = new PngChromaticitiesDirectory(); directory.SetInt(PngChromaticitiesDirectory.TagWhitePointX, chromaticities.GetWhitePointX()); directory.SetInt(PngChromaticitiesDirectory.TagWhitePointX, chromaticities.GetWhitePointX()); directory.SetInt(PngChromaticitiesDirectory.TagRedX, chromaticities.GetRedX()); directory.SetInt(PngChromaticitiesDirectory.TagRedY, chromaticities.GetRedY()); directory.SetInt(PngChromaticitiesDirectory.TagGreenX, chromaticities.GetGreenX()); directory.SetInt(PngChromaticitiesDirectory.TagGreenY, chromaticities.GetGreenY()); directory.SetInt(PngChromaticitiesDirectory.TagBlueX, chromaticities.GetBlueX()); directory.SetInt(PngChromaticitiesDirectory.TagBlueY, chromaticities.GetBlueY()); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.gAMA)) { int gammaInt = new SequentialByteArrayReader(bytes).GetInt32(); PngDirectory directory = new PngDirectory(PngChunkType.gAMA); directory.SetDouble(PngDirectory.TagGamma, gammaInt / 100000.0); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.iCCP)) { SequentialReader reader = new SequentialByteArrayReader(bytes); string profileName = reader.GetNullTerminatedString(79); PngDirectory directory = new PngDirectory(PngChunkType.iCCP); directory.SetString(PngDirectory.TagIccProfileName, profileName); sbyte compressionMethod = reader.GetInt8(); if (compressionMethod == 0) { // Only compression method allowed by the spec is zero: deflate // This assumes 1-byte-per-char, which it is by spec. int bytesLeft = bytes.Length - profileName.Length - 2; sbyte[] compressedProfile = reader.GetBytes(bytesLeft); InflaterInputStream inflateStream = new InflaterInputStream(new ByteArrayInputStream(compressedProfile)); new IccReader().Extract(new RandomAccessStreamReader(inflateStream), metadata); inflateStream.Close(); } metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.bKGD)) { PngDirectory directory = new PngDirectory(PngChunkType.bKGD); directory.SetByteArray(PngDirectory.TagBackgroundColor, bytes); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.tEXt)) { SequentialReader reader = new SequentialByteArrayReader(bytes); string keyword = reader.GetNullTerminatedString(79); int bytesLeft = bytes.Length - keyword.Length - 1; string value = reader.GetNullTerminatedString(bytesLeft); IList <KeyValuePair> textPairs = new AList <KeyValuePair>(); textPairs.Add(new KeyValuePair(keyword, value)); PngDirectory directory = new PngDirectory(PngChunkType.iTXt); directory.SetObject(PngDirectory.TagTextualData, textPairs); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.iTXt)) { SequentialReader reader = new SequentialByteArrayReader(bytes); string keyword = reader.GetNullTerminatedString(79); sbyte compressionFlag = reader.GetInt8(); sbyte compressionMethod = reader.GetInt8(); string languageTag = reader.GetNullTerminatedString(bytes.Length); string translatedKeyword = reader.GetNullTerminatedString(bytes.Length); int bytesLeft = bytes.Length - keyword.Length - 1 - 1 - 1 - languageTag.Length - 1 - translatedKeyword.Length - 1; string text = null; if (compressionFlag == 0) { text = reader.GetNullTerminatedString(bytesLeft); } else { if (compressionFlag == 1) { if (compressionMethod == 0) { text = StringUtil.FromStream(new InflaterInputStream(new ByteArrayInputStream(bytes, bytes.Length - bytesLeft, bytesLeft))); } else { PngDirectory directory = new PngDirectory(PngChunkType.iTXt); directory.AddError("Invalid compression method value"); metadata.AddDirectory(directory); } } else { PngDirectory directory = new PngDirectory(PngChunkType.iTXt); directory.AddError("Invalid compression flag value"); metadata.AddDirectory(directory); } } if (text != null) { if (keyword.Equals("XML:com.adobe.xmp")) { // NOTE in testing images, the XMP has parsed successfully, but we are not extracting tags from it as necessary new XmpReader().Extract(text, metadata); } else { IList <KeyValuePair> textPairs = new AList <KeyValuePair>(); textPairs.Add(new KeyValuePair(keyword, text)); PngDirectory directory = new PngDirectory(PngChunkType.iTXt); directory.SetObject(PngDirectory.TagTextualData, textPairs); metadata.AddDirectory(directory); } } } else { if (chunkType.Equals(PngChunkType.tIME)) { SequentialByteArrayReader reader = new SequentialByteArrayReader(bytes); int year = reader.GetUInt16(); int month = reader.GetUInt8() - 1; int day = reader.GetUInt8(); int hour = reader.GetUInt8(); int minute = reader.GetUInt8(); int second = reader.GetUInt8(); Sharpen.Calendar calendar = Sharpen.Calendar.GetInstance(Sharpen.Extensions.GetTimeZone("UTC")); //noinspection MagicConstant calendar.Set(year, month, day, hour, minute, second); PngDirectory directory = new PngDirectory(PngChunkType.tIME); directory.SetDate(PngDirectory.TagLastModificationTime, calendar.GetTime()); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.pHYs)) { SequentialByteArrayReader reader = new SequentialByteArrayReader(bytes); int pixelsPerUnitX = reader.GetInt32(); int pixelsPerUnitY = reader.GetInt32(); sbyte unitSpecifier = reader.GetInt8(); PngDirectory directory = new PngDirectory(PngChunkType.pHYs); directory.SetInt(PngDirectory.TagPixelsPerUnitX, pixelsPerUnitX); directory.SetInt(PngDirectory.TagPixelsPerUnitY, pixelsPerUnitY); directory.SetInt(PngDirectory.TagUnitSpecifier, unitSpecifier); metadata.AddDirectory(directory); } else { if (chunkType.Equals(PngChunkType.sBIT)) { PngDirectory directory = new PngDirectory(PngChunkType.sBIT); directory.SetByteArray(PngDirectory.TagSignificantBits, bytes); metadata.AddDirectory(directory); } } } } } } } } } } } } } }
protected override void Populate(WavFormatDirectory directory, byte[] payload) { var reader = new SequentialByteArrayReader(payload, isMotorolaByteOrder: false); Populate(directory, reader, payload.Length); }
public static MemoryStream SubstituteXmp([NotNull] byte[] original, [NotNull] XDocument xmp) { SequentialByteArrayReader input = new SequentialByteArrayReader(original); MemoryStream output = new MemoryStream(); try { // The Xmp-substitution loops over the original bytes and copies them into a // MemoryStream (ms). If an App1-segment containing Xmp data is encountered, // the new Xmp bytes are written to the MemoryStream instead of the old bytes. // If no Xmp-containing App1 is encountered before other segments, a new App1- // segment with the Xmp-data is inserted. // All non-App1-fragments are just copied as they are. bool wroteXmp = false; // The following loop scans over the input, copies it to the output and locks in // when it encounters a segment marker. // Reference: http://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_JPEG_files while (true) { // Find the segment marker. Markers are zero or more 0xFF bytes, followed // by a 0xFF and then a byte not equal to 0x00 or 0xFF. var segmentIdentifier = input.GetByte(); output.WriteByte(segmentIdentifier); var segmentTypeByte = input.GetByte(); output.WriteByte(segmentTypeByte); // Read until we have a 0xFF byte followed by a byte that is not 0xFF or 0x00 while (segmentIdentifier != 0xFF || segmentTypeByte == 0xFF || segmentTypeByte == 0) { segmentIdentifier = segmentTypeByte; segmentTypeByte = input.GetByte(); output.WriteByte(segmentTypeByte); } var segmentType = (JpegSegmentType)segmentTypeByte; // the algorithm above stopped at a segment marker. This may be an opportunity // to update or insert an App1-Xmp segment if (!wroteXmp && segmentType == JpegSegmentType.App1) { // An App1 segment was encountered and might be a candidate for updating the Xmp // Copy the segment (if it already contains Xmp, it will be updated) wroteXmp = CopyOrUpdateApp1Segment(xmp, input, output, wroteXmp); } else if (!wroteXmp && segmentType != JpegSegmentType.Soi && segmentType != JpegSegmentType.App0) { // file begins with Soi (App0) (App1) ... // At this point we have encountered a segment that should not be earlier than an App1. // Also, the files does not contain an App1-Xmp segment yet. // Therefore we must insert a new App1-Xmp segment now. wroteXmp = InsertApp1XmpSegment(xmp, input, output, segmentIdentifier, segmentTypeByte); } // then continues with copying until the next segment or the end of the input } } catch (IOException ex) { // we expect a IOException when the sequential reader reaches the end of the original data if (ex.Message != "End of data reached.") { throw new JpegProcessingException("An error occured while trying to write Xml to the buffer.", ex); } } catch (Exception ex) { throw new JpegProcessingException("An error occured while trying to write Xml to the buffer.", ex); } return(output); }
public IEnumerable <Directory> Extract(IndexedReader reader) { var header = reader.GetUInt32(0); if (header != 0x46464600) { var flirHeaderDirectory = new FlirHeaderDirectory(); flirHeaderDirectory.AddError("Unexpected FFF header bytes."); yield return(flirHeaderDirectory); yield break; } var headerDirectory = new FlirHeaderDirectory(); headerDirectory.Set(FlirHeaderDirectory.TagCreatorSoftware, reader.GetNullTerminatedStringValue(4, 16)); yield return(headerDirectory); var baseIndexOffset = reader.GetUInt32(24); var indexCount = reader.GetUInt32(28); var indexOffset = checked ((int)baseIndexOffset); for (int i = 0; i < indexCount; i++) { var mainType = (FlirMainTagType)reader.GetUInt16(indexOffset); var subType = reader.GetUInt16(indexOffset + 2); // 1=BE, 2=LE, 3=PNG; 1 for other record types var version = reader.GetUInt32(indexOffset + 4); var id = reader.GetUInt32(indexOffset + 8); var dataOffset = reader.GetInt32(indexOffset + 12); var dataLength = reader.GetInt32(indexOffset + 16); if (mainType == FlirMainTagType.Pixels) { var reader2 = reader.WithShiftedBaseOffset(dataOffset); // should be 0x0002 if byte order is correct var marker = reader2.GetUInt16(0); if (marker > 0x0100) { reader2 = reader2.WithByteOrder(!reader2.IsMotorolaByteOrder); } var directory = new FlirRawDataDirectory(); directory.Set(FlirRawDataDirectory.TagRawThermalImageWidth, reader2.GetUInt16(FlirRawDataDirectory.TagRawThermalImageWidth)); directory.Set(FlirRawDataDirectory.TagRawThermalImageHeight, reader2.GetUInt16(FlirRawDataDirectory.TagRawThermalImageHeight)); directory.Set(FlirRawDataDirectory.TagRawThermalImageType, reader2.GetUInt16(FlirRawDataDirectory.TagRawThermalImageType)); if (ExtractRawThermalImage) { directory.Set(FlirRawDataDirectory.TagRawThermalImage, reader2.GetBytes(32, dataLength - 32)); } yield return(directory); } else if (mainType == FlirMainTagType.BasicData) { var reader2 = reader.WithShiftedBaseOffset(dataOffset); // should be 0x0002 if byte order is correct var marker = reader2.GetUInt16(0); if (marker > 0x0100) { reader2 = reader2.WithByteOrder(!reader2.IsMotorolaByteOrder); } var directory = new FlirCameraInfoDirectory(); directory.Set(TagEmissivity, reader2.GetFloat32(TagEmissivity)); directory.Set(TagObjectDistance, reader2.GetFloat32(TagObjectDistance)); directory.Set(TagReflectedApparentTemperature, reader2.GetFloat32(TagReflectedApparentTemperature)); directory.Set(TagAtmosphericTemperature, reader2.GetFloat32(TagAtmosphericTemperature)); directory.Set(TagIRWindowTemperature, reader2.GetFloat32(TagIRWindowTemperature)); directory.Set(TagIRWindowTransmission, reader2.GetFloat32(TagIRWindowTransmission)); directory.Set(TagRelativeHumidity, reader2.GetFloat32(TagRelativeHumidity)); directory.Set(TagPlanckR1, reader2.GetFloat32(TagPlanckR1)); directory.Set(TagPlanckB, reader2.GetFloat32(TagPlanckB)); directory.Set(TagPlanckF, reader2.GetFloat32(TagPlanckF)); directory.Set(TagAtmosphericTransAlpha1, reader2.GetFloat32(TagAtmosphericTransAlpha1)); directory.Set(TagAtmosphericTransAlpha2, reader2.GetFloat32(TagAtmosphericTransAlpha2)); directory.Set(TagAtmosphericTransBeta1, reader2.GetFloat32(TagAtmosphericTransBeta1)); directory.Set(TagAtmosphericTransBeta2, reader2.GetFloat32(TagAtmosphericTransBeta2)); directory.Set(TagAtmosphericTransX, reader2.GetFloat32(TagAtmosphericTransX)); directory.Set(TagCameraTemperatureRangeMax, reader2.GetFloat32(TagCameraTemperatureRangeMax)); directory.Set(TagCameraTemperatureRangeMin, reader2.GetFloat32(TagCameraTemperatureRangeMin)); directory.Set(TagCameraTemperatureMaxClip, reader2.GetFloat32(TagCameraTemperatureMaxClip)); directory.Set(TagCameraTemperatureMinClip, reader2.GetFloat32(TagCameraTemperatureMinClip)); directory.Set(TagCameraTemperatureMaxWarn, reader2.GetFloat32(TagCameraTemperatureMaxWarn)); directory.Set(TagCameraTemperatureMinWarn, reader2.GetFloat32(TagCameraTemperatureMinWarn)); directory.Set(TagCameraTemperatureMaxSaturated, reader2.GetFloat32(TagCameraTemperatureMaxSaturated)); directory.Set(TagCameraTemperatureMinSaturated, reader2.GetFloat32(TagCameraTemperatureMinSaturated)); directory.Set(TagCameraModel, reader2.GetNullTerminatedStringValue(TagCameraModel, 32)); directory.Set(TagCameraPartNumber, reader2.GetNullTerminatedStringValue(TagCameraPartNumber, 16)); directory.Set(TagCameraSerialNumber, reader2.GetNullTerminatedStringValue(TagCameraSerialNumber, 16)); directory.Set(TagCameraSoftware, reader2.GetNullTerminatedStringValue(TagCameraSoftware, 16)); directory.Set(TagLensModel, reader2.GetNullTerminatedStringValue(TagLensModel, 32)); directory.Set(TagLensPartNumber, reader2.GetNullTerminatedStringValue(TagLensPartNumber, 16)); directory.Set(TagLensSerialNumber, reader2.GetNullTerminatedStringValue(TagLensSerialNumber, 16)); directory.Set(TagFieldOfView, reader2.GetFloat32(TagFieldOfView)); directory.Set(TagFilterModel, reader2.GetNullTerminatedStringValue(TagFilterModel, 16)); directory.Set(TagFilterPartNumber, reader2.GetNullTerminatedStringValue(TagFilterPartNumber, 32)); directory.Set(TagFilterSerialNumber, reader2.GetNullTerminatedStringValue(TagFilterSerialNumber, 32)); directory.Set(TagPlanckO, reader2.GetInt32(TagPlanckO)); directory.Set(TagPlanckR2, reader2.GetFloat32(TagPlanckR2)); directory.Set(TagRawValueRangeMin, reader2.GetUInt16(TagRawValueRangeMin)); directory.Set(TagRawValueRangeMax, reader2.GetUInt16(TagRawValueRangeMax)); directory.Set(TagRawValueMedian, reader2.GetUInt16(TagRawValueMedian)); directory.Set(TagRawValueRange, reader2.GetUInt16(TagRawValueRange)); var dateTimeBytes = reader2.GetBytes(TagDateTimeOriginal, 10); var dateTimeReader = new SequentialByteArrayReader(dateTimeBytes, isMotorolaByteOrder: false); var tm = dateTimeReader.GetUInt32(); var ss = dateTimeReader.GetUInt32() & 0xffff; var tz = dateTimeReader.GetInt16(); directory.Set(TagDateTimeOriginal, new DateTimeOffset(DateUtil.FromUnixTime(tm - tz * 60).AddSeconds(ss / 1000d), TimeSpan.FromMinutes(tz))); directory.Set(TagFocusStepCount, reader2.GetUInt16(TagFocusStepCount)); directory.Set(TagFocusDistance, reader2.GetFloat32(TagFocusDistance)); directory.Set(TagFrameRate, reader2.GetUInt16(TagFrameRate)); yield return(directory); } indexOffset += 32; } }
/// <exception cref="PngProcessingException"/> /// <exception cref="System.IO.IOException"/> private static IEnumerable <Directory> ProcessChunk(PngChunk chunk) { var chunkType = chunk.ChunkType; var bytes = chunk.Bytes; if (chunkType == PngChunkType.IHDR) { var header = new PngHeader(bytes); var directory = new PngDirectory(PngChunkType.IHDR); directory.Set(PngDirectory.TagImageWidth, header.ImageWidth); directory.Set(PngDirectory.TagImageHeight, header.ImageHeight); directory.Set(PngDirectory.TagBitsPerSample, header.BitsPerSample); directory.Set(PngDirectory.TagColorType, header.ColorType.NumericValue); directory.Set(PngDirectory.TagCompressionType, header.CompressionType); directory.Set(PngDirectory.TagFilterMethod, header.FilterMethod); directory.Set(PngDirectory.TagInterlaceMethod, header.InterlaceMethod); yield return(directory); } else if (chunkType == PngChunkType.PLTE) { var directory = new PngDirectory(PngChunkType.PLTE); directory.Set(PngDirectory.TagPaletteSize, bytes.Length / 3); yield return(directory); } else if (chunkType == PngChunkType.tRNS) { var directory = new PngDirectory(PngChunkType.tRNS); directory.Set(PngDirectory.TagPaletteHasTransparency, 1); yield return(directory); } else if (chunkType == PngChunkType.sRGB) { int srgbRenderingIntent = unchecked ((sbyte)bytes[0]); var directory = new PngDirectory(PngChunkType.sRGB); directory.Set(PngDirectory.TagSrgbRenderingIntent, srgbRenderingIntent); yield return(directory); } else if (chunkType == PngChunkType.cHRM) { var chromaticities = new PngChromaticities(bytes); var directory = new PngChromaticitiesDirectory(); directory.Set(PngChromaticitiesDirectory.TagWhitePointX, chromaticities.WhitePointX); directory.Set(PngChromaticitiesDirectory.TagWhitePointY, chromaticities.WhitePointY); directory.Set(PngChromaticitiesDirectory.TagRedX, chromaticities.RedX); directory.Set(PngChromaticitiesDirectory.TagRedY, chromaticities.RedY); directory.Set(PngChromaticitiesDirectory.TagGreenX, chromaticities.GreenX); directory.Set(PngChromaticitiesDirectory.TagGreenY, chromaticities.GreenY); directory.Set(PngChromaticitiesDirectory.TagBlueX, chromaticities.BlueX); directory.Set(PngChromaticitiesDirectory.TagBlueY, chromaticities.BlueY); yield return(directory); } else if (chunkType == PngChunkType.gAMA) { var gammaInt = ByteConvert.ToInt32BigEndian(bytes); var directory = new PngDirectory(PngChunkType.gAMA); directory.Set(PngDirectory.TagGamma, gammaInt / 100000.0); yield return(directory); } else if (chunkType == PngChunkType.iCCP) { var reader = new SequentialByteArrayReader(bytes); var profileName = reader.GetNullTerminatedStringValue(maxLengthBytes: 79); var directory = new PngDirectory(PngChunkType.iCCP); directory.Set(PngDirectory.TagIccProfileName, profileName); var compressionMethod = reader.GetSByte(); if (compressionMethod == 0) { // Only compression method allowed by the spec is zero: deflate // This assumes 1-byte-per-char, which it is by spec. var bytesLeft = bytes.Length - profileName.Bytes.Length - 2; // http://george.chiramattel.com/blog/2007/09/deflatestream-block-length-does-not-match.html // First two bytes are part of the zlib specification (RFC 1950), not the deflate specification (RFC 1951). reader.Skip(2); bytesLeft -= 2; var compressedProfile = reader.GetBytes(bytesLeft); IccDirectory?iccDirectory = null; Exception? ex = null; try { using var inflaterStream = new DeflateStream(new MemoryStream(compressedProfile), CompressionMode.Decompress); iccDirectory = new IccReader().Extract(new IndexedCapturingReader(inflaterStream)); iccDirectory.Parent = directory; } catch (Exception e) { ex = e; } if (iccDirectory != null) { yield return(iccDirectory); } else if (ex != null) { directory.AddError($"Exception decompressing {nameof(PngChunkType.iCCP)} chunk: {ex.Message}"); } } else { directory.AddError("Invalid compression method value"); } yield return(directory); } else if (chunkType == PngChunkType.bKGD) { var directory = new PngDirectory(PngChunkType.bKGD); directory.Set(PngDirectory.TagBackgroundColor, bytes); yield return(directory); } else if (chunkType == PngChunkType.tEXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(_latin1Encoding); var bytesLeft = bytes.Length - keyword.Length - 1; var value = reader.GetNullTerminatedStringValue(bytesLeft, _latin1Encoding); var textPairs = new List <KeyValuePair> { new KeyValuePair(keyword, value) }; var directory = new PngDirectory(PngChunkType.tEXt); directory.Set(PngDirectory.TagTextualData, textPairs); yield return(directory); } else if (chunkType == PngChunkType.zTXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(_latin1Encoding); var compressionMethod = reader.GetSByte(); var bytesLeft = bytes.Length - keyword.Length - 1 - 1 - 1 - 1; byte[]? textBytes = null; if (compressionMethod == 0) { if (!TryDeflate(bytes, bytesLeft, out textBytes, out string?errorMessage)) { var directory = new PngDirectory(PngChunkType.zTXt); directory.AddError($"Exception decompressing {nameof(PngChunkType.zTXt)} chunk with keyword \"{keyword}\": {errorMessage}"); yield return(directory); } } else { var directory = new PngDirectory(PngChunkType.zTXt); directory.AddError("Invalid compression method value"); yield return(directory); } if (textBytes != null) { foreach (var directory in ProcessTextChunk(keyword, textBytes)) { yield return(directory); } } } else if (chunkType == PngChunkType.iTXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(_latin1Encoding); var compressionFlag = reader.GetSByte(); var compressionMethod = reader.GetSByte(); // TODO we currently ignore languageTagBytes and translatedKeywordBytes var languageTagBytes = reader.GetNullTerminatedBytes(bytes.Length); var translatedKeywordBytes = reader.GetNullTerminatedBytes(bytes.Length); var bytesLeft = bytes.Length - keyword.Length - 1 - 1 - 1 - languageTagBytes.Length - 1 - translatedKeywordBytes.Length - 1; byte[]? textBytes = null; if (compressionFlag == 0) { textBytes = reader.GetNullTerminatedBytes(bytesLeft); } else if (compressionFlag == 1) { if (compressionMethod == 0) { if (!TryDeflate(bytes, bytesLeft, out textBytes, out string?errorMessage)) { var directory = new PngDirectory(PngChunkType.iTXt); directory.AddError($"Exception decompressing {nameof(PngChunkType.iTXt)} chunk with keyword \"{keyword}\": {errorMessage}"); yield return(directory); } } else { var directory = new PngDirectory(PngChunkType.iTXt); directory.AddError("Invalid compression method value"); yield return(directory); } } else { var directory = new PngDirectory(PngChunkType.iTXt); directory.AddError("Invalid compression flag value"); yield return(directory); } if (textBytes != null) { foreach (var directory in ProcessTextChunk(keyword, textBytes)) { yield return(directory); } } } else if (chunkType == PngChunkType.tIME) { var reader = new SequentialByteArrayReader(bytes); var year = reader.GetUInt16(); var month = reader.GetByte(); int day = reader.GetByte(); int hour = reader.GetByte(); int minute = reader.GetByte(); int second = reader.GetByte(); var directory = new PngDirectory(PngChunkType.tIME); if (DateUtil.IsValidDate(year, month, day) && DateUtil.IsValidTime(hour, minute, second)) { var time = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified); directory.Set(PngDirectory.TagLastModificationTime, time); } else { directory.AddError($"PNG tIME data describes an invalid date/time: year={year} month={month} day={day} hour={hour} minute={minute} second={second}"); } yield return(directory); } else if (chunkType == PngChunkType.pHYs) { var reader = new SequentialByteArrayReader(bytes); var pixelsPerUnitX = reader.GetInt32(); var pixelsPerUnitY = reader.GetInt32(); var unitSpecifier = reader.GetSByte(); var directory = new PngDirectory(PngChunkType.pHYs); directory.Set(PngDirectory.TagPixelsPerUnitX, pixelsPerUnitX); directory.Set(PngDirectory.TagPixelsPerUnitY, pixelsPerUnitY); directory.Set(PngDirectory.TagUnitSpecifier, unitSpecifier); yield return(directory); } else if (chunkType.Equals(PngChunkType.sBIT)) { var directory = new PngDirectory(PngChunkType.sBIT); directory.Set(PngDirectory.TagSignificantBits, bytes); yield return(directory); } yield break; IEnumerable <Directory> ProcessTextChunk(string keyword, byte[] textBytes) { if (keyword == "XML:com.adobe.xmp") { yield return(new XmpReader().Extract(textBytes)); } else if (keyword == "Raw profile type xmp") { if (TryProcessRawProfile(out int byteCount)) { yield return(new XmpReader().Extract(textBytes, 0, byteCount)); } else { yield return(ReadTextDirectory(keyword, textBytes, chunkType)); } } else if (keyword == "Raw profile type exif" || keyword == "Raw profile type APP1") { if (TryProcessRawProfile(out _)) { foreach (var exifDirectory in new ExifReader().Extract(new ByteArrayReader(textBytes))) { yield return(exifDirectory); } } else { yield return(ReadTextDirectory(keyword, textBytes, chunkType)); } } else if (keyword == "Raw profile type icc" || keyword == "Raw profile type icm") { if (TryProcessRawProfile(out _)) { yield return(new IccReader().Extract(new ByteArrayReader(textBytes))); } else { yield return(ReadTextDirectory(keyword, textBytes, chunkType)); } } else if (keyword == "Raw profile type iptc") { if (TryProcessRawProfile(out int byteCount)) { yield return(new IptcReader().Extract(new SequentialByteArrayReader(textBytes), byteCount)); } else { yield return(ReadTextDirectory(keyword, textBytes, chunkType)); } } else { yield return(ReadTextDirectory(keyword, textBytes, chunkType)); } PngDirectory ReadTextDirectory(string keyword, byte[] textBytes, PngChunkType pngChunkType) { var textPairs = new[] { new KeyValuePair(keyword, new StringValue(textBytes, _latin1Encoding)) }; var directory = new PngDirectory(pngChunkType); directory.Set(PngDirectory.TagTextualData, textPairs); return(directory); } bool TryProcessRawProfile(out int byteCount) { // Raw profiles have form "\n<name>\n<length>\n<hex>\n" if (textBytes.Length == 0 || textBytes[0] != '\n') { byteCount = default; return(false); } var i = 1; // Skip name while (i < textBytes.Length && textBytes[i] != '\n') { i++; } if (i == textBytes.Length) { byteCount = default; return(false); } // Read length int length = 0; while (true) { i++; var c = (char)textBytes[i]; if (c == ' ') { continue; } if (c == '\n') { break; } if (c >= '0' && c <= '9') { length *= 10; length += c - '0'; } else { byteCount = default; return(false); } } i++; // We should be at the ASCII-encoded hex data. Walk through the remaining bytes, re-writing as raw bytes // starting at offset zero in the array. We have to skip \n characters. // Validate the data can be correctly parsed before modifying it in-place, because if parsing fails later // consumers may want the unmodified data. // Each row must have 72 characters (36 bytes once decoded) separated by \n const int rowCharCount = 72; int charsInRow = rowCharCount; for (int j = i; j < length + i; j++) { byte c = textBytes[j]; if (charsInRow-- == 0) { if (c != '\n') { byteCount = default; return(false); } charsInRow = rowCharCount; continue; } if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F')) { byteCount = default; return(false); } } byteCount = length; var writeIndex = 0; charsInRow = rowCharCount; while (length > 0) { var c1 = textBytes[i++]; if (charsInRow-- == 0) { Debug.Assert(c1 == '\n'); charsInRow = rowCharCount; continue; } var c2 = textBytes[i++]; charsInRow--; var n1 = ParseHexNibble(c1); var n2 = ParseHexNibble(c2); length--; textBytes[writeIndex++] = (byte)((n1 << 4) | n2); } return(writeIndex == byteCount); static int ParseHexNibble(int h) { if (h >= '0' && h <= '9') { return(h - '0'); } if (h >= 'a' && h <= 'f') { return(10 + (h - 'a')); } if (h >= 'A' && h <= 'F') { return(10 + (h - 'A')); } Debug.Fail("Should not reach here"); throw new InvalidOperationException(); } }
/// <exception cref="PngProcessingException"/> /// <exception cref="System.IO.IOException"/> private static IEnumerable <Directory> ProcessChunk([NotNull] PngChunk chunk) { var chunkType = chunk.ChunkType; var bytes = chunk.Bytes; if (chunkType == PngChunkType.IHDR) { var header = new PngHeader(bytes); var directory = new PngDirectory(PngChunkType.IHDR); directory.Set(PngDirectory.TagImageWidth, header.ImageWidth); directory.Set(PngDirectory.TagImageHeight, header.ImageHeight); directory.Set(PngDirectory.TagBitsPerSample, header.BitsPerSample); directory.Set(PngDirectory.TagColorType, header.ColorType.NumericValue); directory.Set(PngDirectory.TagCompressionType, header.CompressionType); directory.Set(PngDirectory.TagFilterMethod, header.FilterMethod); directory.Set(PngDirectory.TagInterlaceMethod, header.InterlaceMethod); yield return(directory); } else if (chunkType == PngChunkType.PLTE) { var directory = new PngDirectory(PngChunkType.PLTE); directory.Set(PngDirectory.TagPaletteSize, bytes.Length / 3); yield return(directory); } else if (chunkType == PngChunkType.tRNS) { var directory = new PngDirectory(PngChunkType.tRNS); directory.Set(PngDirectory.TagPaletteHasTransparency, 1); yield return(directory); } else if (chunkType == PngChunkType.sRGB) { int srgbRenderingIntent = unchecked ((sbyte)bytes[0]); var directory = new PngDirectory(PngChunkType.sRGB); directory.Set(PngDirectory.TagSrgbRenderingIntent, srgbRenderingIntent); yield return(directory); } else if (chunkType == PngChunkType.cHRM) { var chromaticities = new PngChromaticities(bytes); var directory = new PngChromaticitiesDirectory(); directory.Set(PngChromaticitiesDirectory.TagWhitePointX, chromaticities.WhitePointX); directory.Set(PngChromaticitiesDirectory.TagWhitePointY, chromaticities.WhitePointY); directory.Set(PngChromaticitiesDirectory.TagRedX, chromaticities.RedX); directory.Set(PngChromaticitiesDirectory.TagRedY, chromaticities.RedY); directory.Set(PngChromaticitiesDirectory.TagGreenX, chromaticities.GreenX); directory.Set(PngChromaticitiesDirectory.TagGreenY, chromaticities.GreenY); directory.Set(PngChromaticitiesDirectory.TagBlueX, chromaticities.BlueX); directory.Set(PngChromaticitiesDirectory.TagBlueY, chromaticities.BlueY); yield return(directory); } else if (chunkType == PngChunkType.gAMA) { var gammaInt = ByteConvert.ToInt32BigEndian(bytes); var directory = new PngDirectory(PngChunkType.gAMA); directory.Set(PngDirectory.TagGamma, gammaInt / 100000.0); yield return(directory); } else if (chunkType == PngChunkType.iCCP) { var reader = new SequentialByteArrayReader(bytes); var profileName = reader.GetNullTerminatedStringValue(maxLengthBytes: 79); var directory = new PngDirectory(PngChunkType.iCCP); directory.Set(PngDirectory.TagIccProfileName, profileName); var compressionMethod = reader.GetSByte(); if (compressionMethod == 0) { // Only compression method allowed by the spec is zero: deflate // This assumes 1-byte-per-char, which it is by spec. var bytesLeft = bytes.Length - profileName.Bytes.Length - 2; // http://george.chiramattel.com/blog/2007/09/deflatestream-block-length-does-not-match.html // First two bytes are part of the zlib specification (RFC 1950), not the deflate specification (RFC 1951). reader.GetByte(); reader.GetByte(); bytesLeft -= 2; var compressedProfile = reader.GetBytes(bytesLeft); IccDirectory iccDirectory = null; Exception ex = null; try { using (var inflaterStream = new DeflateStream(new MemoryStream(compressedProfile), CompressionMode.Decompress)) { iccDirectory = new IccReader().Extract(new IndexedCapturingReader(inflaterStream)); iccDirectory.Parent = directory; } } catch (Exception e) { ex = e; } if (ex == null) { yield return(iccDirectory); } else { directory.AddError($"Exception decompressing {nameof(PngChunkType.iCCP)} chunk: {ex.Message}"); } } else { directory.AddError("Invalid compression method value"); } yield return(directory); } else if (chunkType == PngChunkType.bKGD) { var directory = new PngDirectory(PngChunkType.bKGD); directory.Set(PngDirectory.TagBackgroundColor, bytes); yield return(directory); } else if (chunkType == PngChunkType.tEXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(_latin1Encoding); var bytesLeft = bytes.Length - keyword.Length - 1; var value = reader.GetNullTerminatedStringValue(bytesLeft, _latin1Encoding); var textPairs = new List <KeyValuePair> { new KeyValuePair(keyword, value) }; var directory = new PngDirectory(PngChunkType.tEXt); directory.Set(PngDirectory.TagTextualData, textPairs); yield return(directory); } else if (chunkType == PngChunkType.zTXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(_latin1Encoding); var compressionMethod = reader.GetSByte(); var bytesLeft = bytes.Length - keyword.Length - 1 - 1 - 1 - 1; byte[] textBytes = null; if (compressionMethod == 0) { using (var inflaterStream = new DeflateStream(new MemoryStream(bytes, bytes.Length - bytesLeft, bytesLeft), CompressionMode.Decompress)) { Exception ex = null; try { textBytes = ReadStreamToBytes(inflaterStream); } catch (Exception e) { ex = e; } // Work-around no yield-return from catch blocks if (ex != null) { var directory = new PngDirectory(PngChunkType.zTXt); directory.AddError($"Exception decompressing {nameof(PngChunkType.zTXt)} chunk with keyword \"{keyword}\": {ex.Message}"); yield return(directory); } } } else { var directory = new PngDirectory(PngChunkType.zTXt); directory.AddError("Invalid compression method value"); yield return(directory); } if (textBytes != null) { if (keyword == "XML:com.adobe.xmp") { // NOTE in testing images, the XMP has parsed successfully, but we are not extracting tags from it as necessary yield return(new XmpReader().Extract(textBytes)); } else { var textPairs = new List <KeyValuePair> { new KeyValuePair(keyword, new StringValue(textBytes, _latin1Encoding)) }; var directory = new PngDirectory(PngChunkType.zTXt); directory.Set(PngDirectory.TagTextualData, textPairs); yield return(directory); } } } else if (chunkType == PngChunkType.iTXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(_latin1Encoding); var compressionFlag = reader.GetSByte(); var compressionMethod = reader.GetSByte(); // TODO we currently ignore languageTagBytes and translatedKeywordBytes var languageTagBytes = reader.GetNullTerminatedBytes(bytes.Length); var translatedKeywordBytes = reader.GetNullTerminatedBytes(bytes.Length); var bytesLeft = bytes.Length - keyword.Length - 1 - 1 - 1 - languageTagBytes.Length - 1 - translatedKeywordBytes.Length - 1; byte[] textBytes = null; if (compressionFlag == 0) { textBytes = reader.GetNullTerminatedBytes(bytesLeft); } else if (compressionFlag == 1) { if (compressionMethod == 0) { using (var inflaterStream = new DeflateStream(new MemoryStream(bytes, bytes.Length - bytesLeft, bytesLeft), CompressionMode.Decompress)) { Exception ex = null; try { textBytes = ReadStreamToBytes(inflaterStream); } catch (Exception e) { ex = e; } // Work-around no yield-return from catch blocks if (ex != null) { var directory = new PngDirectory(PngChunkType.iTXt); directory.AddError($"Exception decompressing {nameof(PngChunkType.iTXt)} chunk with keyword \"{keyword}\": {ex.Message}"); yield return(directory); } } } else { var directory = new PngDirectory(PngChunkType.iTXt); directory.AddError("Invalid compression method value"); yield return(directory); } } else { var directory = new PngDirectory(PngChunkType.iTXt); directory.AddError("Invalid compression flag value"); yield return(directory); } if (textBytes != null) { if (keyword == "XML:com.adobe.xmp") { // NOTE in testing images, the XMP has parsed successfully, but we are not extracting tags from it as necessary yield return(new XmpReader().Extract(textBytes)); } else { var textPairs = new List <KeyValuePair> { new KeyValuePair(keyword, new StringValue(textBytes, _latin1Encoding)) }; var directory = new PngDirectory(PngChunkType.iTXt); directory.Set(PngDirectory.TagTextualData, textPairs); yield return(directory); } } } else if (chunkType == PngChunkType.tIME) { var reader = new SequentialByteArrayReader(bytes); var year = reader.GetUInt16(); var month = reader.GetByte(); int day = reader.GetByte(); int hour = reader.GetByte(); int minute = reader.GetByte(); int second = reader.GetByte(); var directory = new PngDirectory(PngChunkType.tIME); if (DateUtil.IsValidDate(year, month, day) && DateUtil.IsValidTime(hour, minute, second)) { var time = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Unspecified); directory.Set(PngDirectory.TagLastModificationTime, time); } else { directory.AddError($"PNG tIME data describes an invalid date/time: year={year} month={month} day={day} hour={hour} minute={minute} second={second}"); } yield return(directory); } else if (chunkType == PngChunkType.pHYs) { var reader = new SequentialByteArrayReader(bytes); var pixelsPerUnitX = reader.GetInt32(); var pixelsPerUnitY = reader.GetInt32(); var unitSpecifier = reader.GetSByte(); var directory = new PngDirectory(PngChunkType.pHYs); directory.Set(PngDirectory.TagPixelsPerUnitX, pixelsPerUnitX); directory.Set(PngDirectory.TagPixelsPerUnitY, pixelsPerUnitY); directory.Set(PngDirectory.TagUnitSpecifier, unitSpecifier); yield return(directory); } else if (chunkType.Equals(PngChunkType.sBIT)) { var directory = new PngDirectory(PngChunkType.sBIT); directory.Set(PngDirectory.TagSignificantBits, bytes); yield return(directory); } }
protected override void Populate(WavFactDirectory directory, byte[] payload) { var reader = new SequentialByteArrayReader(payload, isMotorolaByteOrder: false); directory.Set(TagSampleLength, reader.GetUInt32()); }