png의 투명도는 헤더에 선택적 "tRNS"청크를 추가하여 각 팔레트 항목의 알파를 지정함으로써 작동합니다. .NET 클래스는이를 올바르게 읽고 적용하므로 이미지를 32 비트로 변환해야하는 이유를 실제로 이해하지 못합니다. 게다가, 버그은 모든 색을 완전히 불투명하게 표시하더라도 투명도 청크가있을 때 발생합니다.
png 형식의 구조는 매우 간단합니다. 식별 바이트 다음에 각 청크는 내용 크기의 4 바이트, 청크 ID에 대한 4 ASCII 문자, 청크 콘텐츠 자체 및 마지막으로 4 바이트 청크 CRC 값입니다. 이 구조상
는, 용액은 매우 간단하다
- 바이트 배열로 파일을 읽는다.
- 머리글을 분석하여 팔레트 화 된 png 파일인지 확인하십시오.
- 청크 헤더에서 청크 헤더로 이동하여 "tRNS"청크를 찾습니다.
- 청크에서 알파 값을 읽습니다.
- 이미지 데이터를 포함하지만 "tRNS"청크가 잘린 새 바이트 배열을 만듭니다.
- 조정 된 바이트 데이터에서 생성 된
MemoryStream
을 사용하여 Bitmap
개체를 생성하면 올바른 8 비트 이미지가 생성됩니다.
- 추출 된 알파 데이터를 사용하여 색상 표를 수정하십시오.
수표 및 폴백을 올바르게 수행하면이 기능으로 이미지를로드 할 수 있으며 투명도 정보가 포함 된 팔레트가있는 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.
코드는 이미지를 읽는 방법을 보여주지 않습니다. –
"Screen-SaverBouncing.png"는 8 비트 이미지 (Windows 7의 속성 창에서 볼 수 있음)이지만 colorPalette.Entries.Length는 항상 0입니다. 또한 IrfanViewer에서이 이미지를 열면 색 팔레트를보고 수정하고 저장합니다. 새로 저장된 파일에서 위의 코드를 실행하면 colorPalette.Entries.Length는 256입니다. –
문제는 코드가 아닌 png 이미지에서 발생한 문제이므로 해결되었습니다. –