/// <exception cref="Com.Drew.Imaging.Png.PngProcessingException"/> /// <exception cref="System.IO.IOException"/> public virtual Iterable <PngChunk> Extract([NotNull] SequentialReader reader, [CanBeNull] ICollection <PngChunkType> desiredChunkTypes) { // // PNG DATA STREAM // // Starts with a PNG SIGNATURE, followed by a sequence of CHUNKS. // // PNG SIGNATURE // // Always composed of these bytes: 89 50 4E 47 0D 0A 1A 0A // // CHUNK // // 4 - length of the data field (unsigned, but always within 31 bytes), may be zero // 4 - chunk type, restricted to [65,90] and [97,122] (A-Za-z) // * - data field // 4 - CRC calculated from chunk type and chunk data, but not length // // CHUNK TYPES // // Critical Chunk Types: // // IHDR - image header, always the first chunk in the data stream // PLTE - palette table, associated with indexed PNG images // IDAT - image data chunk, of which there may be many // IEND - image trailer, always the last chunk in the data stream // // Ancillary Chunk Types: // // Transparency information: tRNS // Colour space information: cHRM, gAMA, iCCP, sBIT, sRGB // Textual information: iTXt, tEXt, zTXt // Miscellaneous information: bKGD, hIST, pHYs, sPLT // Time information: tIME // reader.SetMotorolaByteOrder(true); // network byte order if (!Arrays.Equals(PngSignatureBytes, reader.GetBytes(PngSignatureBytes.Length))) { throw new PngProcessingException("PNG signature mismatch"); } bool seenImageHeader = false; bool seenImageTrailer = false; IList <PngChunk> chunks = new AList <PngChunk>(); ICollection <PngChunkType> seenChunkTypes = new HashSet <PngChunkType>(); while (!seenImageTrailer) { // Process the next chunk. int chunkDataLength = reader.GetInt32(); PngChunkType chunkType = new PngChunkType(reader.GetBytes(4)); bool willStoreChunk = desiredChunkTypes == null || desiredChunkTypes.Contains(chunkType); sbyte[] chunkData = reader.GetBytes(chunkDataLength); // Skip the CRC bytes at the end of the chunk // TODO consider verifying the CRC value to determine if we're processing bad data reader.Skip(4); if (willStoreChunk && seenChunkTypes.Contains(chunkType) && !chunkType.AreMultipleAllowed()) { throw new PngProcessingException(Sharpen.Extensions.StringFormat("Observed multiple instances of PNG chunk '%s', for which multiples are not allowed", chunkType)); } if (chunkType.Equals(PngChunkType.Ihdr)) { seenImageHeader = true; } else { if (!seenImageHeader) { throw new PngProcessingException(Sharpen.Extensions.StringFormat("First chunk should be '%s', but '%s' was observed", PngChunkType.Ihdr, chunkType)); } } if (chunkType.Equals(PngChunkType.Iend)) { seenImageTrailer = true; } if (willStoreChunk) { chunks.Add(new PngChunk(chunkType, chunkData)); } seenChunkTypes.Add(chunkType); } return(chunks.AsIterable()); }
/// <exception cref="Com.Drew.Imaging.Png.PngProcessingException"/> /// <exception cref="System.IO.IOException"/> public virtual Iterable<PngChunk> Extract(SequentialReader reader, ICollection<PngChunkType> desiredChunkTypes) { // // PNG DATA STREAM // // Starts with a PNG SIGNATURE, followed by a sequence of CHUNKS. // // PNG SIGNATURE // // Always composed of these bytes: 89 50 4E 47 0D 0A 1A 0A // // CHUNK // // 4 - length of the data field (unsigned, but always within 31 bytes), may be zero // 4 - chunk type, restricted to [65,90] and [97,122] (A-Za-z) // * - data field // 4 - CRC calculated from chunk type and chunk data, but not length // // CHUNK TYPES // // Critical Chunk Types: // // IHDR - image header, always the first chunk in the data stream // PLTE - palette table, associated with indexed PNG images // IDAT - image data chunk, of which there may be many // IEND - image trailer, always the last chunk in the data stream // // Ancillary Chunk Types: // // Transparency information: tRNS // Colour space information: cHRM, gAMA, iCCP, sBIT, sRGB // Textual information: iTXt, tEXt, zTXt // Miscellaneous information: bKGD, hIST, pHYs, sPLT // Time information: tIME // reader.SetMotorolaByteOrder(true); // network byte order if (!Arrays.Equals(PngSignatureBytes, reader.GetBytes(PngSignatureBytes.Length))) { throw new PngProcessingException("PNG signature mismatch"); } bool seenImageHeader = false; bool seenImageTrailer = false; IList<PngChunk> chunks = new AList<PngChunk>(); ICollection<PngChunkType> seenChunkTypes = new HashSet<PngChunkType>(); while (!seenImageTrailer) { // Process the next chunk. int chunkDataLength = reader.GetInt32(); PngChunkType chunkType = new PngChunkType(reader.GetBytes(4)); sbyte[] chunkData = reader.GetBytes(chunkDataLength); // Skip the CRC bytes at the end of the chunk // TODO consider verifying the CRC value to determine if we're processing bad data reader.Skip(4); if (seenChunkTypes.Contains(chunkType) && !chunkType.AreMultipleAllowed()) { throw new PngProcessingException(Sharpen.Extensions.StringFormat("Observed multiple instances of PNG chunk '%s', for which multiples are not allowed", chunkType)); } if (chunkType.Equals(PngChunkType.Ihdr)) { seenImageHeader = true; } else { if (!seenImageHeader) { throw new PngProcessingException(Sharpen.Extensions.StringFormat("First chunk should be '%s', but '%s' was observed", PngChunkType.Ihdr, chunkType)); } } if (chunkType.Equals(PngChunkType.Iend)) { seenImageTrailer = true; } if (desiredChunkTypes == null || desiredChunkTypes.Contains(chunkType)) { chunks.Add(new PngChunk(chunkType, chunkData)); } seenChunkTypes.Add(chunkType); } return chunks.AsIterable(); }
/// <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); } } } } } } } } } } } } } }