/// <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>Reads JPEG SOF values and returns them in a <see cref="JpegDirectory"/>.</summary> public JpegDirectory Extract(byte[] segmentBytes, JpegSegmentType segmentType) { var directory = new JpegDirectory(); // The value of TagCompressionType is determined by the segment type found directory.Set(JpegDirectory.TagCompressionType, (int)segmentType - (int)JpegSegmentType.Sof0); SequentialReader reader = new SequentialByteArrayReader(segmentBytes); 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 < (int)componentCount; i++) { int componentId = reader.GetByte(); int samplingFactorByte = reader.GetByte(); int 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.GetNullTerminatedString(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.Length - 2; var compressedProfile = reader.GetBytes(bytesLeft); using (var inflaterStream = new InflaterInputStream(new MemoryStream(compressedProfile))) yield return new IccReader().Extract(new IndexedCapturingReader(inflaterStream)); } 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.GetNullTerminatedString(maxLengthBytes: 79); var bytesLeft = bytes.Length - keyword.Length - 1; var value = reader.GetNullTerminatedString(bytesLeft); 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.iTXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedString(maxLengthBytes: 79); var compressionFlag = reader.GetSByte(); var compressionMethod = reader.GetSByte(); var languageTag = reader.GetNullTerminatedString(bytes.Length); var translatedKeyword = reader.GetNullTerminatedString(bytes.Length); var 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) { using (var inflaterStream = new DeflateStream(new MemoryStream(bytes, bytes.Length - bytesLeft, bytesLeft), CompressionMode.Decompress)) text = new StreamReader(inflaterStream).ReadToEnd(); } 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 (text != 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(text); } else { var textPairs = new List<KeyValuePair> { new KeyValuePair(keyword, text) }; 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.Utc); 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; } }
/// <exception cref="PngProcessingException"/> /// <exception cref="System.IO.IOException"/> private static IEnumerable<Directory> ProcessChunk([NotNull] PngChunk chunk) { // For more guidance: // http://www.w3.org/TR/PNG-Decoders.html#D.Text-chunk-processing // http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.iCCP // by spec, PNG is generally supposed to use this encoding const string defaultEncodingName = "ISO-8859-1"; var defaultEncoding = Encoding.GetEncoding(defaultEncodingName); 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 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(defaultEncoding); var bytesLeft = bytes.Length - keyword.Length - 1; var value = reader.GetNullTerminatedStringValue(bytesLeft, defaultEncoding); 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.iTXt) { var reader = new SequentialByteArrayReader(bytes); var keyword = reader.GetNullTerminatedStringValue(maxLengthBytes: 79).ToString(defaultEncoding); var compressionFlag = reader.GetSByte(); var compressionMethod = reader.GetSByte(); var languageTag = reader.GetNullTerminatedStringValue(bytes.Length, defaultEncoding); var translatedKeyword = reader.GetNullTerminatedStringValue(bytes.Length, defaultEncoding); var bytesLeft = bytes.Length - keyword.Length - 1 - 1 - 1 - languageTag.Bytes.Length - 1 - translatedKeyword.Bytes.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, defaultEncoding)) }; 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; } }