使用LibTiff.net保存JPG压缩TIF时图像倾斜问题求助
JPG压缩TIF格式生成时图像倾斜问题排查与修复
问题场景
我使用以下VB.NET代码将图像保存为JPG压缩的TIF格式:
主代码
Using memStream As New IO.MemoryStream() Using output As BitMiracle.LibTiff.Classic.Tiff = BitMiracle.LibTiff.Classic.Tiff.ClientOpen("in-memory", "w", memStream, New BitMiracle.LibTiff.Classic.TiffStream()) Dim page As Integer = 0 Dim pages As Integer = InputImages.Count For Each inImage As Drawing.Image In InputImages output.SetField(BitMiracle.LibTiff.Classic.TiffTag.IMAGEWIDTH, inImage.Width) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.IMAGELENGTH, inImage.Height) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.COMPRESSION, BitMiracle.LibTiff.Classic.Compression.JPEG) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.PHOTOMETRIC, BitMiracle.LibTiff.Classic.Photometric.YCBCR) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.SUBFILETYPE, BitMiracle.LibTiff.Classic.FileType.PAGE) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.ROWSPERSTRIP, inImage.Height) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.ORIENTATION, BitMiracle.LibTiff.Classic.Orientation.TOPLEFT) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.XRESOLUTION, inImage.HorizontalResolution) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.YRESOLUTION, inImage.VerticalResolution) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.RESOLUTIONUNIT, BitMiracle.LibTiff.Classic.ResUnit.INCH) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.BITSPERSAMPLE, 8) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.SAMPLESPERPIXEL, 3) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.PLANARCONFIG, BitMiracle.LibTiff.Classic.PlanarConfig.CONTIG) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.YCBCRPOSITIONING, 1) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.YCBCRSUBSAMPLING, 1, 1) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.JPEGTABLESMODE, BitMiracle.LibTiff.Classic.JpegTablesMode.NONE) output.SetField(BitMiracle.LibTiff.Classic.TiffTag.PAGENUMBER, page, pages) Dim bytes As Byte() = GetImageRasterBytes(CType(inImage, Bitmap), PixelFormat.Format24bppRgb) convertSamples(bytes, inImage.Width, inImage.Height) output.WriteEncodedStrip(0, bytes, bytes.Length) page += 1 output.WriteDirectory() Next output.Close() Dim byteArray() As Byte = memStream.ToArray() Return byteArray End Using End Using
辅助函数
Private Function GetImageRasterBytes(ByVal bmp As Bitmap, ByVal format As PixelFormat) As Byte() Dim rect = New Rectangle(0, 0, bmp.Width, bmp.Height) Dim bits As Byte() Try Dim bmpdata = bmp.LockBits(rect, ImageLockMode.ReadWrite, format) bits = New Byte(bmpdata.Stride * bmpdata.Height - 1) {} System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, bits, 0, bits.Length) bmp.UnlockBits(bmpdata) Catch Return Nothing End Try Return bits End Function Private Sub convertSamples(ByVal data As Byte(), ByVal width As Integer, ByVal height As Integer) Dim stride As Integer = data.Length \ height Const samplesPerPixel As Integer = 3 For y As Integer = 0 To height - 1 Dim offset As Integer = stride * y Dim strideEnd As Integer = offset + width * samplesPerPixel Dim i As Integer = offset While i < strideEnd Dim temp As Byte = data(i + 2) data(i + 2) = data(i) data(i) = temp i += samplesPerPixel End While Next End Sub
运行后发现:1040x800的图像生成正常,但1035x800的图像会出现倾斜,同时控制台输出警告:in-memory: Warning, fractional scanline discarded。
问题原因
- Bitmap的Stride对齐机制:Windows GDI+的Bitmap会将每一行的字节数对齐到4的倍数(内存对齐优化)。对于24bpp格式(每个像素占3字节):
- 1040×3=3120,刚好是4的倍数,因此Stride等于实际像素行的字节数,数据无冗余。
- 1035×3=3105,不是4的倍数,GDI+会自动填充3个字节,将Stride调整为3108(4的最小倍数),此时每行数据包含3105个有效像素字节+3个冗余填充字节。
- 代码中
convertSamples函数仅处理了每行的有效像素部分,但保留了冗余填充字节。LibTiff按IMAGEWIDTH设置的1035来解析每行数据,导致冗余字节被误判为下一行的起始数据,最终出现图像倾斜。同时LibTiff检测到数据总长度与预期的扫描行长度不匹配,输出fractional scanline discarded警告。
修复方案
方案一:裁剪冗余字节,传递纯像素数据
修改辅助函数,处理后移除每行的冗余填充字节,生成仅包含有效像素的数组:
Private Sub convertSamplesAndTrimStride(ByRef data As Byte(), ByVal width As Integer, ByVal height As Integer) Dim originalStride As Integer = data.Length \ height Dim pixelRowLength As Integer = width * 3 ' 24bpp每个像素3字节 Dim trimmedData As New List(Of Byte)() For y As Integer = 0 To height - 1 Dim offset As Integer = originalStride * y ' 转换RGB到BGR Dim i As Integer = offset While i < offset + pixelRowLength Dim temp As Byte = data(i + 2) data(i + 2) = data(i) data(i) = temp i += 3 End While ' 提取有效行数据,丢弃冗余字节 trimmedData.AddRange(data.Skip(offset).Take(pixelRowLength)) Next ' 替换原数据为裁剪后的纯像素数组 data = trimmedData.ToArray() End Sub
将主代码中的convertSamples(bytes, inImage.Width, inImage.Height)替换为convertSamplesAndTrimStride(bytes, inImage.Width, inImage.Height)即可。
方案二:配置LibTiff识别实际Stride
在设置TIF标签时,添加STRIPBYTECOUNTS标签,告知LibTiff每个Strip的实际字节数(即Stride×Height):
' 在获取bytes数组后、转换像素前添加 Dim stride As Integer = bytes.Length \ inImage.Height output.SetField(BitMiracle.LibTiff.Classic.TiffTag.STRIPBYTECOUNTS, stride * inImage.Height)
这种方式不需要修改像素数据,让LibTiff按照实际的Stride读取每行内容。
内容的提问来源于stack exchange,提问作者JohnEightyOne
相关产品推荐
相关产品推荐

