[英]How to read 8-bit PNG image as 8-bit PNG image only?
我有一个 8 位 PNG 图像(见附件)。 但是当我使用 Image.FromFile 方法读取它时,像素格式为 32 位。 因此,我无法修改调色板。
请帮我。
有关我用于读取文件和更新调色板的代码,请参见下文。
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;
}
}
我也遇到了这个问题,似乎任何包含透明度的调色板 png 图像都无法加载为 .Net 框架调色板,尽管 .Net 函数可以完美地编写这样的文件。 相反,如果文件是 gif 格式,则没有问题。
png 中的透明度通过在标题中添加可选的“tRNS”块来工作,以指定每个调色板条目的 alpha。 .Net 类正确读取并应用它,所以我真的不明白他们为什么坚持在之后将图像转换为 32 位。 更重要的是,当透明度块存在时,错误总是会发生,即使它将所有颜色标记为完全不透明。
png 格式的结构相当简单; 在识别字节之后,每个块是内容大小的 4 个字节,然后是块 id 的 4 个 ASCII 字符,然后是块内容本身,最后是一个 4 字节的块 CRC 值。
鉴于这种结构,解决方案相当简单:
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 chunk
while (offset + 12 <= 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% 安全的加载位图并将其与任何支持资源(如文件或流)断开链接的方法。 在这里能找到它。
或者,您可以只从MemoryStream
创建图像并让MemoryStream
保持打开状态。 显然,对于引用简单数组而不是外部资源的流,尽管IDisposable
流处于打开状态,但这不会给垃圾收集带来任何问题。 它有点不那么整洁和干净,但要简单得多。 创建loadedImage
的代码就变成了:
MemoryStream ms = new MemoryStream(data)
Bitmap loadedImage = new Bitmap(ms);
我有一个类似的问题,我必须读取以 8bppIndexed 像素格式保存的 PNG 文件的 8 位值。
当您尝试使用新位图(文件名)读取 8 位索引 PNg 图像时,打开的文件为 32 位格式。
我的解决方案是通过提供 pixelformat 来使用 lockbits,如下所示:
Bitmap b = new Bitmap(filename);
BitmapData data = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format8bppIndexed);
这样我就可以扫描数据并获得我保存的原始 8 位值。
希望能帮助到你。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.