¿Cómo leer una imagen PNG de 8 bits solo como una imagen PNG de 8 bits?

Tengo una imagen PNG de 8 bits (ver el adjunto). Pero cuando lo leí con el método Image.FromFile, el formato de píxel es de 32 bits. Debido a esto no puedo modificar la paleta.

Por favor, ayúdame.

Vea a continuación el código que estoy usando para leer el archivo y actualizar la paleta.

public static Image GetPreviewImage() { Bitmap updatedImage = null; try { // Reads the colors as a byte array byte[] paletteBytes = FetchColorPallette(); updatedImage = Image.FromFile(@"C:\Screen-SaverBouncing.png"); ColorPalette colorPalette = updatedImage.Palette; int j = 0; if (colorPalette.Entries.Length > 0) { for (int i = 0; i < paletteBytes.Length / 4; i++) { Byte AValue = Convert.ToByte(paletteBytes[j]); Byte RValue = Convert.ToByte(paletteBytes[j + 1]); Byte GValue = Convert.ToByte(paletteBytes[j + 2]); Byte BValue = Convert.ToByte(paletteBytes[j + 3]); j += 4; colorPalette.Entries[i] = Color.FromArgb(AValue, RValue, GValue, BValue); } updatedImage.Palette = colorPalette; ; } return updatedImage; } catch { throw; } } 

También tuve este problema, y ​​parece que cualquier imagen png con paleta que contenga transparencias no puede cargarse con la plataforma .Net, a pesar de que las funciones de .Net pueden escribir perfectamente un archivo de este tipo. Por el contrario, no tiene problemas con esto si el archivo está en formato gif.

La transparencia en png funciona agregando un fragmento “tRNS” opcional en el encabezado, para especificar el alfa de cada entrada de paleta. Las clases .Net leen y aplican esto correctamente, por lo que no entiendo realmente por qué insisten en convertir la imagen a 32 bits después. Además, el error siempre ocurre cuando está presente el fragmento de transparencia, incluso si marca todos los colores como totalmente opacos.

La estructura del formato png es bastante simple; después de los bytes de identificación, cada fragmento es de 4 bytes del tamaño del contenido, luego 4 caracteres ASCII para la identificación del fragmento, luego el contenido del mismo fragmento y, finalmente, un valor CRC del fragmento de 4 bytes.

Dada esta estructura, la solución es bastante simple:

  • Lea el archivo en una matriz de bytes.
  • Asegúrate de que sea un archivo png paleteado analizando el encabezado.
  • Encuentre el fragmento “tRNS” saltando de encabezado de fragmento a encabezado de fragmento.
  • Lee los valores alfa del fragmento.
  • Cree una nueva matriz de bytes que contenga los datos de la imagen, pero con el fragmento “tRNS” cortado.
  • Cree el objeto de Bitmap utilizando un MemoryStream creado a partir de los datos de bytes ajustados, lo que resulta en la imagen correcta de 8 bits.
  • Arregla la paleta de colores utilizando los datos alfa extraídos.

Si realiza las comprobaciones y los recursos de forma correcta, simplemente puede cargar cualquier imagen con esta función, y si se identifica como un png con información de transparencia, se realizará la corrección.

Mi código:

 ///  /// Image loading toolset class which corrects the bug that prevents paletted PNG images with transparency from being loaded as paletted. ///  public class BitmapLoader { private static Byte[] PNG_IDENTIFIER = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; ///  /// Loads an image, checks if it is a PNG containing palette transparency, and if so, ensures it loads correctly. /// The theory can be found at http://www.libpng.org/pub/png/book/chapter08.html ///  /// Filename to load /// The loaded image public static Bitmap LoadBitmap(String filename) { Byte[] data = File.ReadAllBytes(filename); return LoadBitmap(data); } ///  /// Loads an image, checks if it is a PNG containing palette transparency, and if so, ensures it loads correctly. /// The theory can be found at http://www.libpng.org/pub/png/book/chapter08.html ///  /// File data to load /// The loaded image public static Bitmap LoadBitmap(Byte[] data) { Byte[] transparencyData = null; if (data.Length > PNG_IDENTIFIER.Length) { // Check if the image is a PNG. Byte[] compareData = new Byte[PNG_IDENTIFIER.Length]; Array.Copy(data, compareData, PNG_IDENTIFIER.Length); if (PNG_IDENTIFIER.SequenceEqual(compareData)) { // Check if it contains a palette. // I'm sure it can be looked up in the header somehow, but meh. Int32 plteOffset = FindChunk(data, "PLTE"); if (plteOffset != -1) { // Check if it contains a palette transparency chunk. Int32 trnsOffset = FindChunk(data, "tRNS"); if (trnsOffset != -1) { // Get chunk Int32 trnsLength = GetChunkDataLength(data, trnsOffset); transparencyData = new Byte[trnsLength]; Array.Copy(data, trnsOffset + 8, transparencyData, 0, trnsLength); // filter out the palette alpha chunk, make new data array Byte[] data2 = new Byte[data.Length - (trnsLength + 12)]; Array.Copy(data, 0, data2, 0, trnsOffset); Int32 trnsEnd = trnsOffset + trnsLength + 12; Array.Copy(data, trnsEnd, data2, trnsOffset, data.Length - trnsEnd); data = data2; } } } } Bitmap loadedImage; using (MemoryStream ms = new MemoryStream(data)) using (Bitmap tmp = new Bitmap(ms)) loadedImage = ImageUtils.CloneImage(tmp); ColorPalette pal = loadedImage.Palette; if (pal.Entries.Length == 0 || transparencyData == null) return loadedImage; for (Int32 i = 0; i < pal.Entries.Length; i++) { if (i >= transparencyData.Length) break; Color col = pal.Entries[i]; pal.Entries[i] = Color.FromArgb(transparencyData[i], col.R, col.G, col.B); } loadedImage.Palette = pal; return loadedImage; } ///  /// Finds the start of a png chunk. This assumes the image is already identified as PNG. /// It does not go over the first 8 bytes, but starts at the start of the header chunk. ///  /// The bytes of the png image /// The name of the chunk to find. /// The index of the start of the png chunk, or -1 if the chunk was not found. private static Int32 FindChunk(Byte[] data, String chunkName) { if (chunkName.Length != 4 ) throw new ArgumentException("Chunk must be 4 characters!", "chunkName"); Byte[] chunkNamebytes = Encoding.ASCII.GetBytes(chunkName); if (chunkNamebytes.Length != 4) throw new ArgumentException("Chunk must be 4 characters!", "chunkName"); Int32 offset = PNG_IDENTIFIER.Length; Int32 end = data.Length; Byte[] testBytes = new Byte[4]; // continue until either the end is reached, or there is not enough space behind it for reading a new header while (offset < end && offset + 8 < end) { Array.Copy(data, offset + 4, testBytes, 0, 4); // Alternative for more visual debugging: //String currentChunk = Encoding.ASCII.GetString(testBytes); //if (chunkName.Equals(currentChunk)) // return offset; if (chunkNamebytes.SequenceEqual(testBytes)) return offset; Int32 chunkLength = GetChunkDataLength(data, offset); // chunk size + chunk header + chunk checksum = 12 bytes. offset += 12 + chunkLength; } return -1; } private static Int32 GetChunkDataLength(Byte[] data, Int32 offset) { if (offset + 4 > data.Length) throw new IndexOutOfRangeException("Bad chunk size in png image."); // Don't want to use BitConverter; then you have to check platform endianness and all that mess. Int32 length = data[offset + 3] + (data[offset + 2] << 8) + (data[offset + 1] << 16) + (data[offset] << 24); if (length < 0) throw new IndexOutOfRangeException("Bad chunk size in png image."); return length; } } 

La mencionada ImageUtils.CloneImage es, por lo que sé, la única forma 100% segura de cargar un bitmap y desvincularlo de cualquier recurso de respaldo como un archivo o flujo. Se puede encontrar aquí.