TIFF图片切片转存PNG/JPG失败:Image.FromStream报错参数无效
问题分析与解决方案
你的代码在TIFF切片保存时正常工作,但转JPG/PNG时出现"参数无效"错误,核心原因是:Image.FromStream需要读取完整的图像格式数据(带文件头、像素格式描述等),而你传入的是未经编码的原始像素字节流,系统无法识别。
TIFF保存逻辑之所以可行,是因为你手动设置了TIFF格式所需的所有元数据(宽度、高度、像素格式等),而JPG/PNG的Image.FromStream没有这些上下文信息,无法解析原始像素。
修正后的代码(DO_JPEG块替换)
将原代码中#if DO_JPEG到#endif的块替换为以下内容,通过创建Bitmap对象直接操作像素数据,再导出为JPG/PNG:
#if DO_JPEG using (Bitmap bitmap = CreateBitmapFromRawPixels(W, H, samplesPerPixel, bitsPerSample, photo, output, nz)) { // 根据文件名后缀自动选择保存格式 ImageFormat format = TileName.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ? ImageFormat.Png : ImageFormat.Jpeg; bitmap.Save(TileName, format); } #endif // 新增辅助方法:从原始像素创建Bitmap private static Bitmap CreateBitmapFromRawPixels(int width, int height, int samplesPerPixel, int bitsPerSample, int photo, byte[] rawPixels, int rowByteSize) { PixelFormat pixelFormat = GetPixelFormat(samplesPerPixel, bitsPerSample, photo); Bitmap bitmap = new Bitmap(width, height, pixelFormat); // 灰度图需要手动设置调色板 if (pixelFormat == PixelFormat.Format8bppIndexed) { ColorPalette palette = bitmap.Palette; for (int i = 0; i < 256; i++) { palette.Entries[i] = Color.FromArgb(i, i, i); } bitmap.Palette = palette; } // 锁定Bitmap内存,直接写入像素数据 BitmapData bmpData = bitmap.LockBits( new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat ); try { IntPtr bufferPtr = bmpData.Scan0; int stride = bmpData.Stride; for (int row = 0; row < height; row++) { int rawRowOffset = row * rowByteSize; int bmpRowOffset = row * stride; // 根据像素格式处理字节顺序(如RGB转BGR) if (pixelFormat == PixelFormat.Format24bppRgb) { // TIFF的RGB顺序转为Bitmap的BGR顺序 for (int col = 0; col < rowByteSize; col += 3) { byte r = rawPixels[rawRowOffset + col]; byte g = rawPixels[rawRowOffset + col + 1]; byte b = rawPixels[rawRowOffset + col + 2]; Marshal.WriteByte(bufferPtr, bmpRowOffset + col, b); Marshal.WriteByte(bufferPtr, bmpRowOffset + col + 1, g); Marshal.WriteByte(bufferPtr, bmpRowOffset + col + 2, r); } } else if (pixelFormat == PixelFormat.Format32bppArgb) { // TIFF的RGBA顺序转为Bitmap的BGRA顺序 for (int col = 0; col < rowByteSize; col += 4) { byte r = rawPixels[rawRowOffset + col]; byte g = rawPixels[rawRowOffset + col + 1]; byte b = rawPixels[rawRowOffset + col + 2]; byte a = rawPixels[rawRowOffset + col + 3]; Marshal.WriteByte(bufferPtr, bmpRowOffset + col, b); Marshal.WriteByte(bufferPtr, bmpRowOffset + col + 1, g); Marshal.WriteByte(bufferPtr, bmpRowOffset + col + 2, r); Marshal.WriteByte(bufferPtr, bmpRowOffset + col + 3, a); } } else { // 灰度图等无需字节顺序转换的格式,直接复制整行 Marshal.Copy(rawPixels, rawRowOffset, bufferPtr + bmpRowOffset, rowByteSize); } } } finally { bitmap.UnlockBits(bmpData); } return bitmap; } // 新增辅助方法:映射TIFF像素格式到Bitmap像素格式 private static PixelFormat GetPixelFormat(int samplesPerPixel, int bitsPerSample, int photo) { if (samplesPerPixel == 1 && bitsPerSample == 8 && photo == (int)TiffTag.PHOTOMETRIC.MINISBLACK) { return PixelFormat.Format8bppIndexed; } else if (samplesPerPixel == 3 && bitsPerSample == 8 && photo == (int)TiffTag.PHOTOMETRIC.RGB) { return PixelFormat.Format24bppRgb; } else if (samplesPerPixel == 4 && bitsPerSample == 8 && photo == (int)TiffTag.PHOTOMETRIC.RGB) { return PixelFormat.Format32bppArgb; } else { throw new NotSupportedException($"不支持的像素格式:samplesPerPixel={samplesPerPixel}, bitsPerSample={bitsPerSample}"); } }
关键修复点说明
- 像素格式映射:根据TIFF的元数据(采样数、位深、光度解释)匹配对应的
PixelFormat,确保Bitmap能正确解析原始像素。 - 字节顺序转换:Bitmap的24/32位格式采用BGR/BGRA字节顺序,而TIFF通常使用RGB/RGBA,必须手动交换通道顺序。
- Stride处理:Bitmap的行字节数(Stride)会自动对齐到4字节边界,需逐行复制像素而非直接拷贝整个数组。
- 灰度图调色板:8位索引灰度图需要手动生成灰度调色板,否则会显示异常颜色。
内容的提问来源于stack exchange,提问作者gpol
相关产品推荐
相关产品推荐

