/// <summary> /// Gets the IHDR chunk of the parent image and throws an exception if it is not present. /// </summary> /// <returns>The IHDR chunk of the parent image.</returns> /// <exception cref="ChunkNotFoundException" /> protected IHDRChunk GetAssertIHDRChunk() { IHDRChunk chunk = _image.Chunks["IHDR"] as IHDRChunk; if (chunk == null) { throw new ChunkNotFoundException("IHDR chunk not found."); } return(chunk); }
public tRNSChunk(byte[] data, PNGImage image) : base(data, image) { IHDRChunk hdr = this.GetAssertIHDRChunk(); switch (hdr.ColorType) { case ColorType.Grayscale: this.AssertDataLength(data, 2); _grayValue = Utils.BytesToUShort(data, 4, Utils.Endianness.Big); break; case ColorType.GrayscaleWithAlpha: throw new InvalidChunkDataException("The tRNS chunk cannot appear for the grayscale with alpha color type."); case ColorType.Truecolor: this.AssertDataLength(data, 6); _redValue = Utils.BytesToUShort(data, 4, Utils.Endianness.Big); _greenValue = Utils.BytesToUShort(data, 6, Utils.Endianness.Big); _blueValue = Utils.BytesToUShort(data, 8, Utils.Endianness.Big); break; case ColorType.TruecolorWithAlpha: throw new InvalidChunkDataException("The tRNS chunk cannot appear for the truecolor with alpha color type."); case ColorType.IndexedColor: if (data.Length - 4 > this.GetAssertPLTEChunk().Entries.Length) { throw new InvalidChunkLengthException("tRNS chunk must contain the same amount of entries as the palette chunk or less."); } _paletteAlpha = new byte[data.Length - 4]; for (int i = 0; i < data.Length - 4; i++) { _paletteAlpha[i] = data[i + 4]; } break; default: throw new InvalidChunkDataException("Invalid color type."); } }
protected override void WriteChunkData(MemoryStream ms) { IHDRChunk hdr = this.GetAssertIHDRChunk(); BinaryWriter bw = new BinaryWriter(ms); switch (hdr.ColorType) { case ColorType.Grayscale: bw.Write(Utils.UShortToBytes(_grayValue, Utils.Endianness.Big)); break; case ColorType.GrayscaleWithAlpha: throw new InvalidChunkDataException("The tRNS chunk cannot appear for the grayscale with alpha color type."); case ColorType.Truecolor: bw.Write(Utils.UShortToBytes(_redValue, Utils.Endianness.Big)); bw.Write(Utils.UShortToBytes(_greenValue, Utils.Endianness.Big)); bw.Write(Utils.UShortToBytes(_blueValue, Utils.Endianness.Big)); break; case ColorType.TruecolorWithAlpha: throw new InvalidChunkDataException("The tRNS chunk cannot appear for the truecolor with alpha color type."); case ColorType.IndexedColor: if (_paletteAlpha.Length > this.GetAssertPLTEChunk().Entries.Length) { throw new InvalidChunkLengthException("tRNS chunk must contain the same amount of entries as the palette chunk or less."); } bw.Write(_paletteAlpha); break; default: throw new InvalidChunkDataException("Invalid color type."); } }
public Color GetEffectiveBackgroundColor() { IHDRChunk hdr = this.GetAssertIHDRChunk(); switch (hdr.ColorType) { case ColorType.Grayscale: case ColorType.GrayscaleWithAlpha: if (hdr.BitDepth == 1) { return(Utils.MakeGray((_backgroundColor.Gray & 0x1) * 255)); } else if (hdr.BitDepth == 2) { return(Utils.MakeGray((_backgroundColor.Gray & 0x3) * 255 / 3)); } else if (hdr.BitDepth == 4) { return(Utils.MakeGray((_backgroundColor.Gray & 0xf) * 255 / 15)); } else if (hdr.BitDepth == 8) { return(Utils.MakeGray(_backgroundColor.Gray & 0xff)); } else if (hdr.BitDepth == 16) { return(Utils.MakeGray(_backgroundColor.Gray * 255 / 65535)); } else { throw new InvalidChunkDataException("Invalid bit depth."); } case ColorType.Truecolor: case ColorType.TruecolorWithAlpha: if (hdr.BitDepth == 8) { return(Color.FromArgb( _backgroundColor.Red & 0xff, _backgroundColor.Green & 0xff, _backgroundColor.Blue & 0xff)); } else if (hdr.BitDepth == 16) { return(Color.FromArgb( _backgroundColor.Red * 255 / 65535, _backgroundColor.Green * 255 / 65535, _backgroundColor.Blue * 255 / 65535)); } else { throw new InvalidChunkDataException("Invalid bit depth."); } case ColorType.IndexedColor: PLTEChunk plte = this.GetAssertPLTEChunk(); return(plte.Entries[_backgroundColor.Index]); default: throw new InvalidChunkDataException("Invalid color type."); } }
/// <summary> /// Checks the image's chunks for conformance to the PNG specification. /// </summary> public void Verify() { // verify the critical chunks // IHDR and IEND if (_chunks["IHDR"] == null) { throw new InvalidImageException("Critical chunk IHDR was not found."); } if (_chunks[0].Type != "IHDR") { throw new InvalidImageException("IHDR chunk must appear first."); } if (_chunks["IEND"] == null) { throw new InvalidImageException("Critical chunk IEND was not found."); } if (_chunks[_chunks.Count - 1].Type != "IEND") { throw new InvalidImageException("IEND chunk must appear last."); } // PLTE IHDRChunk hdr = _chunks["IHDR"] as IHDRChunk; if (hdr.ColorType == ColorType.IndexedColor) { if (_chunks["PLTE"] == null) { throw new InvalidImageException( "Critical chunk PLTE required for color type 3 (indexed color) was not found."); } } if (hdr.ColorType == ColorType.Grayscale || hdr.ColorType == ColorType.GrayscaleWithAlpha) { if (_chunks["PLTE"] != null) { throw new InvalidImageException( "PLTE chunk cannot appear for color types 0 and 4 (grayscale and grayscale with alpha)."); } } // IDAT if (_chunks["IDAT"] == null) { throw new InvalidImageException("Critical chunk IDAT was not found."); } // verify chunk counts Dictionary <Type, int> counts = new Dictionary <Type, int>(); foreach (Chunk c in _chunks) { if (!counts.ContainsKey(c.GetType())) { counts.Add(c.GetType(), 1); } else { counts[c.GetType()]++; } } foreach (Type t in counts.Keys) { if (_classes.ContainsValue(t)) { ChunkAttribute a = t.GetCustomAttributes(typeof(ChunkAttribute), true)[0] as ChunkAttribute; if (a.AllowMultiple == false && counts[t] > 1) { throw new InvalidImageException( string.Format("Multiple instances ({0}) of {1} chunk are not allowed.", counts[t], a.Type)); } } } // verify ancillary chunks if (_chunks["iCCP"] != null && _chunks["sRGB"] != null) { throw new InvalidImageException("iCCP chunk and sRGB chunk cannot be both present."); } // verify chunk ordering int plteLoc, idatLoc; plteLoc = _chunks.IndexOf("PLTE"); idatLoc = _chunks.IndexOf("IDAT"); // verify that IDAT chunks are consecutive bool idatEnded = false; for (int i = idatLoc; i < _chunks.Count; i++) { if (_chunks[i].Type == "IDAT" && idatEnded) { throw new InvalidImageException("IDAT chunks must be consecutive."); } else if (_chunks[i].Type != "IDAT" && !idatEnded) { idatEnded = true; } } // PLTE must be before IDAT if (plteLoc >= idatLoc) { throw new InvalidImageException("PLTE chunk must be before first IDAT chunk."); } // verify other chunks' ordering string[] beforePLTEbeforeIDAT = new string[] { "cHRM", "gAMA", "iCCP", "sBIT", "sRGB" }; string[] afterPLTEbeforeIDAT = new string[] { "bKGD", "hIST", "tRNS" }; string[] beforeIDAT = new string[] { "pHYs", "sPLT" }; for (int i = 0; i < _chunks.Count; i++) { if (Utils.ArrayContains <string>(beforePLTEbeforeIDAT, _chunks[i].Type)) { if (((plteLoc == -1) ? false : (i >= plteLoc)) || i >= idatLoc) { throw new InvalidImageException( string.Format("{0} chunk must be before PLTE chunk and first IDAT chunk.", _chunks[i].Type)); } } if (Utils.ArrayContains <string>(afterPLTEbeforeIDAT, _chunks[i].Type)) { if (((plteLoc == -1) ? false : (i <= plteLoc)) || i >= idatLoc) { throw new InvalidImageException( string.Format("{0} must be after PLTE chunk and before first IDAT chunk.", _chunks[i].Type)); } } if (Utils.ArrayContains <string>(beforeIDAT, _chunks[i].Type)) { if (i >= idatLoc) { throw new InvalidImageException( string.Format("{0} must be before first IDAT chunk.", _chunks[i].Type)); } } } }
/// <summary> /// Saves the PNG image to a stream. /// </summary> /// <param name="s">The stream to write to.</param> public void Save(Stream s) { IHDRChunk hdr = _image.Chunks["IHDR"] as IHDRChunk; int width = (int)hdr.Width; int height = (int)hdr.Height; _scanlineLength = Utils.IntCeilDiv(Utils.GetBitsPerPixel(hdr.ColorType, hdr.BitDepth) * width, 8) + 1; _bpp = Utils.GetBytesPerPixel(hdr.ColorType, hdr.BitDepth); // bytes per pixel byte bitDepth = hdr.BitDepth; MemoryStream data = new MemoryStream(); BinaryWriter bwdata = new BinaryWriter(data); byte[] scanline = null; for (int line = 0; line < height; line++) { byte[] prevLine = scanline; scanline = new byte[_scanlineLength - 1]; switch (hdr.ColorType) { case ColorType.Grayscale: if (bitDepth == 1) { for (int i = 0; i < Utils.IntCeilDiv(width, 8); i++) { byte b = 0; for (int j = 0; j < 8; j++) { if (i * 8 + j < hdr.Width) { Color c = _bitmap.GetPixel(i * 8 + j, line); byte value = (byte)((c.R + c.G + c.B) / 3); b |= (byte)(((value / 255) >> (7 - j)) & 0x1); } } scanline[i] = b; } } else if (bitDepth == 2) { for (int i = 0; i < Utils.IntCeilDiv(width, 4); i++) { byte b = 0; for (int j = 0; j < 4; j++) { if (i * 4 + j < hdr.Width) { Color c = _bitmap.GetPixel(i * 4 + j, line); byte value = (byte)((c.R + c.G + c.B) / 3); b |= (byte)(((value * 3 / 255) >> ((3 - j) * 2)) & 0x3); } } scanline[i] = b; } } else if (bitDepth == 4) { for (int i = 0; i < Utils.IntCeilDiv(width, 2); i++) { byte b = 0; Color c = _bitmap.GetPixel(i * 2, line); byte value = (byte)((c.R + c.G + c.B) / 3); b |= (byte)((value * 15 / 255) << 4); if (i * 2 + 1 < width) { c = _bitmap.GetPixel(i * 2 + 1, line); value = (byte)((c.R + c.G + c.B) / 3); b |= (byte)(value * 15 / 255); } scanline[i] = b; } } else if (bitDepth == 8) { for (int i = 0; i < width; i++) { Color c = _bitmap.GetPixel(i, line); scanline[i] = (byte)((c.R + c.G + c.B) / 3); } } else if (bitDepth == 16) { for (int i = 0; i < width; i++) { Color c = _bitmap.GetPixel(i, line); byte value = (byte)((c.R + c.G + c.B) / 3); scanline[i * 2] = (byte)((value * 65535 / 255) >> 8); scanline[i * 2 + 1] = (byte)((value * 65535 / 255) & 0xff); } } break; case ColorType.GrayscaleWithAlpha: if (bitDepth == 8) { for (int i = 0; i < width; i++) { Color c = _bitmap.GetPixel(i, line); scanline[i * 2] = (byte)((c.R + c.G + c.B) / 3); scanline[i * 2 + 1] = c.A; } } else if (bitDepth == 16) { for (int i = 0; i < width; i++) { Color c = _bitmap.GetPixel(i, line); byte value = (byte)((c.R + c.G + c.B) / 3); scanline[i * 4] = (byte)((value * 65535 / 255) >> 8); scanline[i * 4 + 1] = (byte)((value * 65535 / 255) & 0xff); scanline[i * 4 + 2] = (byte)((c.A * 65535 / 255) >> 8); scanline[i * 4 + 3] = (byte)((c.A * 65535 / 255) & 0xff); } } break; case ColorType.IndexedColor: break; case ColorType.Truecolor: if (bitDepth == 8) { for (int i = 0; i < width; i++) { Color c = _bitmap.GetPixel(i, line); scanline[i * 3] = c.R; scanline[i * 3 + 1] = c.G; scanline[i * 3 + 2] = c.B; } } else if (bitDepth == 16) { for (int i = 0; i < width; i++) { Color c = _bitmap.GetPixel(i, line); scanline[i * 6] = (byte)((c.R * 65535 / 255) >> 8); scanline[i * 6 + 1] = (byte)((c.R * 65535 / 255) & 0xff); scanline[i * 6 + 2] = (byte)((c.G * 65535 / 255) >> 8); scanline[i * 6 + 3] = (byte)((c.G * 65535 / 255) & 0xff); scanline[i * 6 + 4] = (byte)((c.B * 65535 / 255) >> 8); scanline[i * 6 + 5] = (byte)((c.B * 65535 / 255) & 0xff); } } break; case ColorType.TruecolorWithAlpha: if (bitDepth == 8) { for (int i = 0; i < width; i++) { Color c = _bitmap.GetPixel(i, line); scanline[i * 4] = c.R; scanline[i * 4 + 1] = c.G; scanline[i * 4 + 2] = c.B; scanline[i * 4 + 3] = c.A; } } else if (bitDepth == 16) { for (int i = 0; i < width; i++) { Color c = _bitmap.GetPixel(i, line); scanline[i * 8] = (byte)((c.R * 65535 / 255) >> 8); scanline[i * 8 + 1] = (byte)((c.R * 65535 / 255) & 0xff); scanline[i * 8 + 2] = (byte)((c.G * 65535 / 255) >> 8); scanline[i * 8 + 3] = (byte)((c.G * 65535 / 255) & 0xff); scanline[i * 8 + 4] = (byte)((c.B * 65535 / 255) >> 8); scanline[i * 8 + 5] = (byte)((c.B * 65535 / 255) & 0xff); scanline[i * 8 + 6] = (byte)((c.A * 65535 / 255) >> 8); scanline[i * 8 + 7] = (byte)((c.A * 65535 / 255) & 0xff); } } break; default: throw new Exception("Invalid color type."); } byte[] filtered = this.Filter(scanline, line, this.GetBestFilterMethod(scanline, line, prevLine), prevLine); bwdata.Write(filtered); } this.SaveChunks(data.ToArray()); _image.Write(s, true); bwdata.Close(); }
/// <summary> /// Reads the image pixels and stores them in Bitmap. /// </summary> /// <param name="data">The image bytes.</param> private void Read(byte[] data) { // optimized for speed (it's very slow already) IHDRChunk hdr = _image.Chunks["IHDR"] as IHDRChunk; int width = (int)hdr.Width; int height = (int)hdr.Height; byte bitDepth = hdr.BitDepth; _bitmap = CreateBitmap(hdr.ColorType, hdr.BitDepth, width, height); _scanlineLength = Utils.IntCeilDiv(Utils.GetBitsPerPixel(hdr.ColorType, hdr.BitDepth) * width, 8) + 1; _bpp = Utils.GetBytesPerPixel(hdr.ColorType, hdr.BitDepth); // bytes per pixel byte[] decoded = null; // palette PLTEChunk palette = _image.Chunks["PLTE"] as PLTEChunk; #region tRNS Chunk tRNSChunk trns = _image.Chunks["tRNS"] as tRNSChunk; int tGray1 = -1; int tGray2 = -1; int tGray4 = -1; int tGray8 = -1; int tGray16 = -1; Color tRgb8 = Color.FromArgb(0, 0, 0, 0); Color tRgb16 = Color.FromArgb(0, 0, 0, 0); int[] tPalette = null; if (trns != null) { tGray1 = trns.Gray & 0x1; tGray2 = trns.Gray & 0x3; tGray4 = trns.Gray & 0xf; tGray8 = trns.Gray & 0xff; tGray16 = trns.Gray; tRgb8 = Color.FromArgb(trns.Red & 0xff, trns.Green & 0xff, trns.Blue & 0xff); tRgb16 = Color.FromArgb(trns.Red * 255 / 65535, trns.Green * 255 / 65535, trns.Blue * 255 / 65535); if (palette != null) { tPalette = new int[palette.Entries.Length]; for (int i = 0; i < tPalette.Length; i++) { if (i < trns.PaletteAlpha.Length) { tPalette[i] = trns.PaletteAlpha[i]; } else { tPalette[i] = 255; } } } } else { if (palette != null) { tPalette = new int[palette.Entries.Length]; for (int i = 0; i < tPalette.Length; i++) { tPalette[i] = 255; } } } #endregion if (hdr.InterlaceMethod == InterlaceMethod.Adam7) { #region Adam7 for (int pass = 0; pass < _adam7.Length; pass++) { } #endregion } else { #region Normal for (int line = 0; line < data.Length / _scanlineLength; line++) { decoded = this.Defilter(data, line, decoded); switch (hdr.ColorType) { case ColorType.Grayscale: if (bitDepth == 1) { for (int i = 0; i < Utils.IntCeilDiv(width, 8); i++) { int[] pixels = new int[8]; for (int j = 0; j < 8; j++) { pixels[j] = (decoded[i] >> (7 - j)) & 0x1; } for (int j = 0; j < 8; j++) { if (i * 8 + j < hdr.Width) { _bitmap.SetPixel(i * 8 + j, line, Utils.MakeGray(pixels[j] == tGray1 ? 0 : 255, pixels[j] * 255)); } } } } else if (bitDepth == 2) { for (int i = 0; i < Utils.IntCeilDiv(width, 4); i++) { int[] pixels = new int[4]; for (int j = 0; j < 4; j++) { pixels[j] = (decoded[i] >> ((3 - j) * 2)) & 0x3; } for (int j = 0; j < 4; j++) { if (i * 4 + j < hdr.Width) { _bitmap.SetPixel(i * 4 + j, line, Utils.MakeGray(pixels[j] == tGray2 ? 0 : 255, pixels[j] * 255 / 3)); } } } } else if (bitDepth == 4) { for (int i = 0; i < Utils.IntCeilDiv(width, 2); i++) { int pixel1 = decoded[i] >> 4; // upper four bits int pixel2 = decoded[i] & 0xf; // lower two bits _bitmap.SetPixel(i * 2, line, Utils.MakeGray( pixel1 == tGray4 ? 0 : 255, pixel1 * 255 / 15)); if (i * 2 + 1 < hdr.Width) { _bitmap.SetPixel(i * 2 + 1, line, Utils.MakeGray( pixel2 == tGray4 ? 0 : 255, pixel2 * 255 / 15)); } } } else if (bitDepth == 8) { for (int i = 0; i < hdr.Width; i++) { _bitmap.SetPixel(i, line, Utils.MakeGray(decoded[i] == tGray8 ? 0 : 255, decoded[i])); } } else if (bitDepth == 16) { for (int i = 0; i < hdr.Width; i++) { int value = Utils.BytesToUShort(decoded, i * 2, Utils.Endianness.Big); _bitmap.SetPixel(i, line, Utils.MakeGray( value == tGray16 ? 0 : 255, value * 255 / 65535)); } } break; case ColorType.GrayscaleWithAlpha: if (bitDepth == 8) { for (int i = 0; i < hdr.Width; i++) { _bitmap.SetPixel(i, line, Color.FromArgb( decoded[i * 2 + 1], decoded[i * 2], decoded[i * 2], decoded[i * 2] )); } } else if (bitDepth == 16) { for (int i = 0; i < hdr.Width; i++) { _bitmap.SetPixel(i, line, Color.FromArgb( Utils.BytesToUShort(decoded, i * 4 + 2, Utils.Endianness.Big) * 255 / 65535, Utils.BytesToUShort(decoded, i * 4, Utils.Endianness.Big) * 255 / 65535, Utils.BytesToUShort(decoded, i * 4, Utils.Endianness.Big) * 255 / 65535, Utils.BytesToUShort(decoded, i * 4, Utils.Endianness.Big) * 255 / 65535 )); } } break; case ColorType.IndexedColor: if (bitDepth == 1) { for (int i = 0; i < Utils.IntCeilDiv(width, 8); i++) { int[] pixels = new int[8]; for (int j = 0; j < 8; j++) { pixels[j] = (decoded[i] >> (7 - j)) & 0x1; } for (int j = 0; j < 8; j++) { if (i * 8 + j < hdr.Width) { _bitmap.SetPixel(i * 8 + j, line, Color.FromArgb(tPalette[pixels[j]], palette.Entries[pixels[j]])); } } } } else if (bitDepth == 2) { for (int i = 0; i < Utils.IntCeilDiv(width, 4); i++) { int[] pixels = new int[4]; for (int j = 0; j < 4; j++) { pixels[j] = (decoded[i] >> ((3 - j) * 2)) & 0x3; } for (int j = 0; j < 4; j++) { if (i * 4 + j < hdr.Width) { _bitmap.SetPixel(i * 4 + j, line, Color.FromArgb(tPalette[pixels[j]], palette.Entries[pixels[j]])); } } } } else if (bitDepth == 4) { for (int i = 0; i < Utils.IntCeilDiv(width, 2); i++) { int pixel1 = decoded[i] >> 4; // upper four bits int pixel2 = decoded[i] & 0xf; // lower two bits _bitmap.SetPixel(i * 2, line, Color.FromArgb(tPalette[pixel1], palette.Entries[pixel1])); if (i * 2 + 1 < hdr.Width) { _bitmap.SetPixel(i * 2 + 1, line, Color.FromArgb(tPalette[pixel2], palette.Entries[pixel2])); } } } else if (bitDepth == 8) { for (int i = 0; i < hdr.Width; i++) { _bitmap.SetPixel(i, line, Color.FromArgb(tPalette[decoded[i]], palette.Entries[decoded[i]])); } } break; case ColorType.Truecolor: if (bitDepth == 8) { for (int i = 0; i < hdr.Width; i++) { Color c = Color.FromArgb( decoded[i * 3], decoded[i * 3 + 1], decoded[i * 3 + 2] ); _bitmap.SetPixel(i, line, Color.FromArgb( Utils.ColorsEqual(c, tRgb8) ? 0 : 255, c)); } } else if (bitDepth == 16) { // .NET doesn't support 16-bit bit depths for (int i = 0; i < hdr.Width; i++) { Color c = Color.FromArgb( Utils.BytesToUShort(decoded, i * 6, Utils.Endianness.Big) * 255 / 65535, Utils.BytesToUShort(decoded, i * 6 + 2, Utils.Endianness.Big) * 255 / 65535, Utils.BytesToUShort(decoded, i * 6 + 4, Utils.Endianness.Big) * 255 / 65535 ); _bitmap.SetPixel(i, line, Color.FromArgb( Utils.ColorsEqual(c, tRgb16) ? 0 : 255, c)); } } break; case ColorType.TruecolorWithAlpha: if (bitDepth == 8) { for (int i = 0; i < hdr.Width; i++) { _bitmap.SetPixel(i, line, Color.FromArgb( decoded[i * 4 + 3], decoded[i * 4], decoded[i * 4 + 1], decoded[i * 4 + 2])); } } else if (bitDepth == 16) { for (int i = 0; i < hdr.Width; i++) { _bitmap.SetPixel(i, line, Color.FromArgb( Utils.BytesToUShort(decoded, i * 8 + 6, Utils.Endianness.Big) * 255 / 65535, Utils.BytesToUShort(decoded, i * 8, Utils.Endianness.Big) * 255 / 65535, Utils.BytesToUShort(decoded, i * 8 + 2, Utils.Endianness.Big) * 255 / 65535, Utils.BytesToUShort(decoded, i * 8 + 4, Utils.Endianness.Big) * 255 / 65535 )); } } break; default: throw new Exception("Invalid color type."); } } #endregion } }