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); } }
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>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); }
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); }
/// <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); } }
/// <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); } }
/// <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="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); } } } } } } } } } } } } } }