2014-06-06 94 views
1

我有一個8位PNG圖像(見附件)。但是當我使用Image.FromFile方法讀取它時,像素格式爲32位。由於這個原因,我無法修改調色板。如何將8位PNG圖像作爲8位PNG圖像讀取?

請幫幫我。

請參閱下面的代碼來閱讀文件並更新調色板。

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; 
     } 
    } 
+0

您的代碼不顯示如何閱讀圖像。 –

+0

問題是「Screen-SaverBouncing.png」是一個8位圖像(從Windows 7的屬性窗口中可以看到),但colorPalette.Entries.Length始終爲零。另外,如果我在IrfanViewer中打開這個圖像,我可以看到調色板,修改它並保存。如果我在這個新保存的文件上運行上面的代碼,那麼colorPalette.Entries.Length是256。 –

+0

現在解決了這個問題,因爲問題出在png圖片上,而不是代碼上。 –

回答

0

我有這個問題太,它似乎是一個包含透明度任何調色板的PNG圖像不能,儘管完全加載由.NET Framework所調色板的.NET函數可以這樣的文件。相比之下,如果文件採用gif格式,則沒有問題。

png中的透明度通過在標題中添加可選的「tRNS」塊來指定每個調色板條目的alpha值。 .Net類正確地讀取和應用這個,所以我不明白他們爲什麼堅持把圖像轉換爲32位。更重要的是,當存在透明度塊時,即使它將所有顏色標記爲完全不透明,也會發生錯誤總是

png格式的結構相當簡單;在識別字節之後,每個塊是內容大小的4個字節,然後是塊ID的4個ASCII字符,然後是塊內容本身,最後是4個字節的塊CRC值。

鑑於這種結構,該解決方案是相當簡單:

  • 文件讀入到一個字節數組。
  • 通過分析標頭確保它是一個調色板的png文件。
  • 通過從塊標題跳轉到塊標題來查找「tRNS」塊。
  • 從塊中讀取alpha值。
  • 創建一個包含圖像數據的新字節數組,但切割了「tRNS」塊。
  • 使用從調整後的字節數據創建的MemoryStream創建Bitmap對象,從而生成正確的8位圖像。
  • 使用提取的alpha數據修復調色板。

如果您執行檢查和回退功能,您可以使用此功能加載任何圖像,並且如果它恰好用透明度信息標識爲調色板png,它將執行修復。

我的代碼:

/// <summary> 
/// Image loading toolset class which corrects the bug that prevents paletted PNG images with transparency from being loaded as paletted. 
/// </summary> 
public class BitmapLoader 
{ 
    private static Byte[] PNG_IDENTIFIER = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; 

    /// <summary> 
    /// 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 
    /// </summary> 
    /// <param name="filename">Filename to load</param> 
    /// <returns>The loaded image</returns> 
    public static Bitmap LoadBitmap(String filename) 
    { 
     Byte[] data = File.ReadAllBytes(filename); 
     return LoadBitmap(data); 
    } 

    /// <summary> 
    /// 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 
    /// </summary> 
    /// <param name="data">File data to load</param> 
    /// <returns>The loaded image</returns> 
    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; 
    } 

    /// <summary> 
    /// 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. 
    /// </summary> 
    /// <param name="data">The bytes of the png image</param> 
    /// <param name="chunkName">The name of the chunk to find.</param> 
    /// <returns>The index of the start of the png chunk, or -1 if the chunk was not found.</returns> 
    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; 
    } 
} 

所提ImageUtils.CloneImage是,據我所知,加載位圖和從任何後備資源,如文件或流取消鏈接它的只有100%安全的方式。 It can be found here.