I have written C# program which create an indexed packed png image from a given png image using pngcs1_1_4.
What you need to use pngcs is adding reference of dll to your Visual Studio project - Pngcs.dll and ICSharpCode.SharpZipLib.dll for .Net 2.0, and Pngcs45.dll for .Net4.5.
I have used .Net 2.0 version because my Visual Studio is 2010.
I have already written a program for generating indexed png in C# on this post - Create Indexed PNG Using C# .Net.
But the png encoder Microsoft officially provides does not support compression level parameter. That is, even if png is indexed, the size of png image is not sufficient for me :(
So I investigated other library which can handle png in C#, and finally found pncs!
Main features of my program:
- Use minimum bit per pixel as much as possible
- Image which has alpha channel is is not supported because pngcs library doesn't support color palette with alpha channel
- If given image has alpha channel but all alpha channel value is 255, remove alpha channel and create RGB indexed png image
Code for Create Indexed Packed PNG Image
using System; using System.IO; using Hjg.Pngcs; using Hjg.Pngcs.Chunks; namespace Utility { public class PngCsExample { public static void ToIndexed256(Stream input, Stream output) { var png1 = new PngReader(input); var pnginfo1 = png1.ImgInfo; if (pnginfo1.Indexed) { if (pnginfo1.Packed) { input.CopyTo(output); } else { Pack_UnpackedIndexedPng(output, png1); } return; } // 1st step: create color palette from source png and also create indexed color pixel lines used by the palette var builder = new PngChunkPLTEBuilder(); int channel = pnginfo1.Channels; int[][] indexedLine = new int[pnginfo1.Rows][]; for (int row = 0; row < indexedLine.Length; row++) { indexedLine[row] = new int[pnginfo1.Cols]; } var unpackFunc = pnginfo1.Packed ? (Func<ImageInfo, int[], int[], bool, int[]>)ImageLineHelper.Unpack : (imageInfo, src, unpack, scale) => src; int[] unpackedLine = pnginfo1.Packed ? new int[pnginfo1.Cols * channel] : new int[0]; if (channel == 1) { for (int row = 0, rows = pnginfo1.Rows; row < rows; row++) { int[] scaleline = ImageLineHelper.Unpack(pnginfo1, png1.ReadRow(row).Scanline, unpackedLine, false); for (int col = 0, len = scaleline.Length / channel, offset = 0; col < len; col++, offset = col * channel) { indexedLine[row][col] = builder.GetIndex(scaleline[offset], 0, 0); } } } else if (channel == 2) { for (int row = 0, rows = pnginfo1.Rows; row < rows; row++) { int[] scaleline = ImageLineHelper.Unpack(pnginfo1, png1.ReadRow(row).Scanline, unpackedLine, false); for (int col = 0, len = scaleline.Length / channel, offset = 0; col < len; col++, offset = col * channel) { indexedLine[row][col] = builder.GetIndex(scaleline[offset], scaleline[offset + 1], 0); } } } else if (channel == 3) { for (int row = 0, rows = pnginfo1.Rows; row < rows; row++) { int[] scaleline = ImageLineHelper.Unpack(pnginfo1, png1.ReadRow(row).Scanline, unpackedLine, false); for (int col = 0, len = scaleline.Length / channel, offset = 0; col < len; col++, offset = col * channel) { indexedLine[row][col] = builder.GetIndex(scaleline[offset], scaleline[offset + 1], scaleline[offset + 2]); } } } else if (channel == 4 || pnginfo1.Alpha) { for (int row = 0, rows = pnginfo1.Rows; row < rows; row++) { int[] scaleline = ImageLineHelper.Unpack(pnginfo1, png1.ReadRow(row).Scanline, unpackedLine, false); for (int col = 0, len = scaleline.Length / channel, offset = 0; col < len; col++, offset = col * channel) { if (scaleline[offset + 3] != 255) { throw new Exception("Sorry image which has alpha channel is not supported"); } indexedLine[row][col] = builder.GetIndex(scaleline[offset], scaleline[offset + 1], scaleline[offset + 2]); } } } png1.End(); // 2nd step: write the palette and the indexed color pixel lines to output png int bitDepth = DetermineBitDepth(builder); var pnginfo2 = new ImageInfo(pnginfo1.Cols, pnginfo1.Rows, bitDepth, false, false, true); var png2 = new PngWriter(output, pnginfo2); png2.CompLevel = 9; png2.GetMetadata().QueueChunk(builder.BuildPngChunkPaletteFromCurrentMap(pnginfo2)); PngChunkPLTE palette = builder.BuildPngChunkPaletteFromCurrentMap(pnginfo2); int[] packedLine = new int[pnginfo2.Cols]; var packFunc = pnginfo2.Packed ? (Func<ImageInfo, int[], int[], bool, int[]>)ImageLineHelper.Pack : (imageInfo, src, dst, scale) => src; for (int row = 0; row < pnginfo1.Rows; row++) { packedLine = packFunc(pnginfo2, indexedLine[row], packedLine, false); png2.WriteRow(packedLine, row); } png2.CopyChunksLast(png1, ChunkCopyBehaviour.COPY_ALL); png2.End(); } private static void Pack_UnpackedIndexedPng(Stream output, PngReader png1) { var pnginfo1 = png1.ImgInfo; var pnginfo2 = new ImageInfo(pnginfo1.Cols, pnginfo1.Rows, pnginfo1.BitDepth, pnginfo1.Alpha, pnginfo1.Greyscale, true); var png2 = new PngWriter(output, pnginfo2); png2.CompLevel = 9; png2.GetMetadata().QueueChunk(png1.GetMetadata().GetPLTE()); int[] packedLine = new int[pnginfo2.Cols]; for (int row = 0; row < pnginfo1.Rows; row++) { ImageLineHelper.Pack(pnginfo2, png1.ReadRow(row).Scanline, packedLine, false); png2.WriteRow(packedLine, row); } png1.End(); png2.CopyChunksLast(png1, ChunkCopyBehaviour.COPY_ALL); png2.End(); } private static int DetermineBitDepth(PngChunkPLTEBuilder builder) { if (builder.GetNumberOfColors() <= 2) { return 1; } else if (builder.GetNumberOfColors() <= 16) { return 4; } else if (builder.GetNumberOfColors() <= 256) { return 8; } throw new Exception("Number of colors reached more than 256! Reduce number of colors of original image first"); } } }
using System.Collections.Generic; using Hjg.Pngcs; using Hjg.Pngcs.Chunks; namespace Utility { public class PngChunkPLTEBuilder { private Dictionary<int, int> map = new Dictionary<int, int>(); private int index = -1; public int GetIndex(int r, int g, int b) { int key = (r << 16) | (g << 8) | b; if (map.ContainsKey(key)) { return map[key]; } index++; map[key] = index; return index; } public PngChunkPLTE BuildPngChunkPaletteFromCurrentMap(ImageInfo pnginfo) { var palette = new PngChunkPLTE(pnginfo); palette.SetNentries(map.Count); foreach(KeyValuePair<int, int> entry in map){ int key = entry.Key; palette.SetEntry( entry.Value, key >> 16, (key & 0xFF00) >> 8, key & 0x00FF ); } return palette; } public int GetNumberOfColors() { return map.Count; } } }
Example Result of Creating Indexed PNG Image
RGBA 32bpp to 292x292 8bpp indexed
The left image is the original 292x292 RGBA 32bpp png image created by Windows Paint and the right image is the 292x292 8bpp indexed png generated by the above code.
The size is reduced 12,717 bytes to 1,894 bytes!!
Single color 32RGBA to Single color 1bpp indexed
The left image is the original 512x512 RGBA 32bpp single color png image created by Windows Paint and the right image is the 512x512 1bpp indexed single color png generated by the above code.
The size is reduced 1,864 bytes to 126 bytes!!
コメント