You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 18:34:59