Phreak87 / LeptonicaSharp

Full featured wrapper for leptonica 1.77.0
Other
8 stars 5 forks source link

Pix To Bitmap #30

Closed fdncred closed 5 years ago

fdncred commented 5 years ago

I rewrote most of this code to work in VB.Net. Just call pix.Convert() and you'll get a dotnet bitmap, unless it's 16bpp. I've tested it on 1bpp, 8bpp, 24bpp, 32bpp. 24bpp may not be quite right. I'm not sure. My intent was to use this to replace your ToBitmap().

Public Function Convert(ByVal pix As Pix) As Bitmap
Dim pixelFormat As PixelFormat = GetPixelFormat(pix)
Dim depth As Integer = pix.d
Dim img As Bitmap = New Bitmap(pix.w, pix.h, pixelFormat)
Dim imgData As BitmapData = Nothing
Dim pixNew As Pix = Nothing

'Don't swap channels on 24bpp, which in Leptonica is d=32, spp=3
If (pix.d = 32 And pix.spp = 4) Or (pix.d = 8) Or (pix.d = 1) Then
    pixNew = _AllFunctions.pixEndianByteSwapNew(pix)
Else
    pixNew = pix
End If

Try
    Dim flag As Boolean = (pixelFormat And PixelFormat.Indexed) = PixelFormat.Indexed

    If flag Then
        TransferPalette(pixNew, img)
    End If

    imgData = img.LockBits(New Rectangle(0, 0, img.Width, img.Height), ImageLockMode.[WriteOnly], pixelFormat)

    If depth = 32 Then
        TransferData32(pixNew, imgData)
        'ElseIf depth = 16 Then
        '    TransferData16(pix, imgData)
    ElseIf depth = 8 Then
        TransferData8(pixNew, imgData)
    ElseIf depth = 1 Then
        TransferData1(pixNew, imgData)
    End If

    If pixNew.xres > 0 AndAlso pixNew.yres > 0 Then img.SetResolution(pixNew.xres, pixNew.yres)
    Return img
Catch __unusedException1__ As Exception
    img.Dispose()
    Throw
Finally

    If imgData IsNot Nothing Then
        img.UnlockBits(imgData)
    End If
End Try
End Function

'This also works
'Private Shared Sub TransferData32(pix As Pix, imgData As BitmapData)
'    Dim total_size As Integer = Math.Abs(imgData.Stride) * imgData.Height
'    Marshal.Copy(pix.data, 0, imgData.Scan0, total_size)
'End Sub
Private Shared Sub TransferData32(pix As Pix, imgData As BitmapData)
    Dim height As Integer = imgData.Height
    Dim width As Integer = imgData.Width
    Dim pixData As IntPtr = pix.Values.data
    Dim stride As Integer = pix.wpl * 4
    Dim curBytes() As Byte = {0, 0, 0, 0}
    Dim pixelIndex As Integer = 0
    Dim bytesPerPixel As Integer = 4
    Dim alphaMask As Integer = 1
    'This works
    'For y = 0 To height - 1
    '    For x = 0 To width - 1
    '        pixelIndex = (y * stride) + (x * bytesPerPixel)
    '        curBytes(0) = Marshal.ReadByte(pixData, pixelIndex)     'B
    '        curBytes(1) = Marshal.ReadByte(pixData, pixelIndex + 1) 'G
    '        curBytes(2) = Marshal.ReadByte(pixData, pixelIndex + 2) 'R
    '        curBytes(3) = Marshal.ReadByte(pixData, pixelIndex + 3) 'A
    '        Marshal.WriteByte(imgData.Scan0, pixelIndex, curBytes(0))
    '        Marshal.WriteByte(imgData.Scan0, pixelIndex + 1, curBytes(1))
    '        Marshal.WriteByte(imgData.Scan0, pixelIndex + 2, curBytes(2))
    '        Marshal.WriteByte(imgData.Scan0, pixelIndex + 3, curBytes(3))
    '    Next
    'Next
    Threading.Tasks.Parallel.For(0, height, Sub(y)
                                                For x As Integer = 0 To width
                                                    Dim valB As Integer = Marshal.ReadByte(pixData, (stride * y) + (x * bytesPerPixel))
                                                    Dim valG As Integer = Marshal.ReadByte(pixData, (stride * y) + (x * bytesPerPixel) + 1)
                                                    Dim valR As Integer = Marshal.ReadByte(pixData, (stride * y) + (x * bytesPerPixel) + 2)
                                                    Dim valA As Integer = Marshal.ReadByte(pixData, (stride * y) + (x * bytesPerPixel) + 3)
                                                    Marshal.WriteByte(imgData.Scan0, (stride * y) + (x * bytesPerPixel), CType(valB, Byte))
                                                    Marshal.WriteByte(imgData.Scan0, (stride * y) + (x * bytesPerPixel) + 1, CType(valG, Byte))
                                                    Marshal.WriteByte(imgData.Scan0, (stride * y) + (x * bytesPerPixel) + 2, CType(valR, Byte))
                                                    Marshal.WriteByte(imgData.Scan0, (stride * y) + (x * bytesPerPixel) + 3, CType(valA, Byte))
                                                Next
                                            End Sub)
End Sub

'Private Shared Sub TransferData16(pix As Pix, imgData As BitmapData)
'    Dim imgFormat As PixelFormat = imgData.PixelFormat
'    Dim height As Integer = imgData.Height
'    Dim width As Integer = imgData.Width
'    Dim pixData As IntPtr = pix.Values.data
'    Dim wpl As Integer = pix.wpl
'    For y As Integer = 0 To height - 1
'        For x As Integer = 0 To width - 1
'        Next
'    Next
'End Sub

Private Shared Sub TransferData8(pix As Pix, imgData As BitmapData)
    Dim height As Integer = imgData.Height
    Dim width As Integer = imgData.Width
    Dim pixData As IntPtr = pix.Values.data
    Dim stride As Integer = pix.wpl * 4
    Dim curByte As Byte = 0
    Dim pixelIndex As Integer = 0
    Dim bytesPerPixel As Integer = 1
    'This works
    'For y = 0 To height - 1
    '    For x = 0 To width - 1
    '        pixelIndex = (y * stride) + (x * bytesPerPixel)
    '        curByte = Marshal.ReadByte(pixData, pixelIndex)
    '        Marshal.WriteByte(imgData.Scan0, pixelIndex, curByte)
    '    Next
    'Next
    Threading.Tasks.Parallel.For(0, height, Sub(y)
                                                For x As Integer = 0 To width
                                                    Dim val As Integer = Marshal.ReadByte(pixData, (stride * y) + (x * bytesPerPixel))
                                                    Marshal.WriteByte(imgData.Scan0, (stride * y) + (x * bytesPerPixel), CType(val, Byte))
                                                Next
                                            End Sub)
End Sub
Private Shared Sub TransferData1(pix As Pix, imgData As BitmapData)
    Dim height As Integer = imgData.Height
    Dim width As Integer = imgData.Width
    Dim pixData As IntPtr = pix.Values.data
    Dim stride As Integer = pix.wpl * 4
    Dim bytesPerPixel As Integer = 1
    Dim outByte As Byte = 0
    For y = 0 To height - 1
        For x = 0 To width - 1 Step 8
            Dim index As Integer = (y * stride) + (x >> 3)
            Dim curPixel As Byte = 255 - Marshal.ReadByte(pix.Values.data, index)
            Marshal.WriteByte(imgData.Scan0, index, curPixel)
        Next
    Next
End Sub
Private Shared Sub TransferPalette(pix As Pix, img As Bitmap)
    Dim pallete As ColorPalette = img.Palette
    Dim maxColors As Integer = pallete.Entries.Length
    Dim lastColor As Integer = maxColors - 1
    Dim colormap As PixColormap = _AllFunctions.pixGetColormap(pix)
    Dim flag As Boolean = colormap IsNot Nothing AndAlso _AllFunctions.pixcmapGetCount(colormap) <= maxColors
    If flag Then
        Dim colormapCount As Integer = _AllFunctions.pixcmapGetCount(colormap)
        For i As Integer = 0 To colormapCount - 1
            pallete.Entries(i) = colormap.Array_Color(i)
        Next
    Else
        For j As Integer = 0 To maxColors - 1
            Dim value As Byte = CByte((j * 255 / lastColor))
            pallete.Entries(j) = Color.FromArgb(CInt(value), CInt(value), CInt(value))
        Next
    End If
    img.Palette = pallete
End Sub
Private Shared Function GetPixelFormat(pix As Pix) As PixelFormat
    Dim depth As Integer
    If (pix.d = 32 And pix.spp = 4) Then
        depth = 32
    ElseIf (pix.d = 32 And pix.spp = 3) Then
        depth = 24
    Else
        depth = pix.d
    End If

    If depth = 1 Then
        Return PixelFormat.Format1bppIndexed
    End If
    If depth = 8 Then
        Return PixelFormat.Format8bppIndexed
    End If
    If depth = 16 Then
        Return PixelFormat.Format16bppGrayScale
    End If
    If depth = 24 Then
        Return PixelFormat.Format32bppRgb
    End If
    If depth = 32 Then
        Return PixelFormat.Format32bppArgb
    End If
    Throw New InvalidOperationException(String.Format("Pix depth {0} is not supported.", pix.d))
End Function

Note: Updated Convert to use pixEndianByteSwapNew(pix) so it doesn't overwrite the original pix. Probably need to look at this closer from a memory cleanup perspective

Phreak87 commented 5 years ago

i work on a new implementation for ToBitmap based on the Leptonica Functions. and it works now out of the box (except some problems with 16bpp from 32BPP, but up from 1,2,4,8 works ...). Try Leptonicas Cat.007.jpg to see what i mean.

    Public Function ToBitmap() As Image
        Dim Size As Integer = 0
        Dim Bytes As Byte() = Nothing
        Dim PixU As Pix = Me

        If Me.d = 1 Then PixU = Me.pixConvertTo16
        _All.pixWriteMemBmp(Bytes, Size, Me)
        Dim MemStrm As New IO.MemoryStream(Bytes)
        Return New Bitmap(MemStrm, True)
    End Function
fdncred commented 5 years ago

I'll test it tomorrow. Mine matches Charles' Tesseract implementation and runs parallel. I'll be interested to see which is faster.

Phreak87 commented 5 years ago

Yes, that's a good question. I like to keep things simple, because Keep that structure in mind is hard enough. Can you check also the cat007 image and colormaps.

fdncred commented 5 years ago

Doesn't work on 1bpp images. I'm not sure why you'd convert a 1bpp to a 16bpp image anyway, especially when pixConvertTo16 doesn't allow depth 1-8 to be converted to 16bpp. I commented that conversion out.

Also, cat.007.jpg acts strangely. It seems to convert but I can't visualize it with my debugger visualizer, but I can save it. It doesn't have colormaps, so I'm not sure what you're talking about.

Phreak87 commented 5 years ago

Pix conversion don't works from 32 to 16 but for 1,2,4,8 to 16 is fine. pix to Bmp dont works without conversation for 1bpp! thats why i convert. (its possible to convert to 8 too ...) TestApplication - TestDisplay generates a 1Bpp Image, saves it via leptonica, transfers to Bmp and saves it and can display in a image box. i think thats fine! from a 32Bpp to 16Bpp i have psychodelic colors inside. (the gray of cat.007 are terrible colored). but you are also right. theres a "<= smaller, bigger =>" issue in convertto16 which causes a error if i try to convert.

maybe exactly this was your problem too.

fdncred commented 5 years ago

Yet another thing I disagree with you about. It's never a good idea to convert an image to a different format unless you can't display that format, such as a DPix or FPix.

Phreak87 commented 5 years ago

i full agree with you! its no good idea - its not my favourite to do things like that. Your Idea is great too but also not the perfect way i think. my fav is to copy the memory to a streamreader. maybe leptonica provides a other function for this case or ... leptonica export is wrong if Bitmap is 1Bpp on windows. In this case its a leptonica problem to handle. i ask dan bloomberg why this occurs.

Phreak87 commented 5 years ago

Lets wait for an answer: https://github.com/DanBloomberg/leptonica/issues/380#issue-366528322

fdncred commented 5 years ago

Committed preliminary code for Convert() with 4b2494b91aea91acdfb238fea59672226adeb1e3

Phreak87 commented 5 years ago

Oh. Please stop until we discussed about!

Darren Schroeder notifications@github.com schrieb am Do., 4. Okt. 2018, 14:32:

Committed preliminary code for Convert() with 4b2494b https://github.com/Phreak87/LeptonicaSharp/commit/4b2494b91aea91acdfb238fea59672226adeb1e3

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/Phreak87/LeptonicaSharp/issues/30#issuecomment-427000991, or mute the thread https://github.com/notifications/unsubscribe-auth/AHvBIOhq7CupOk_ZNV43cwC3BZ50muJPks5uhf_ogaJpZM4XEzSv .

Phreak87 commented 5 years ago

Lets get more descriptive:

Folder Extensions is a "User-Class-Folder". My generator will not touch files inside. i Updated my File with yours but uncommented the 2px conversion. with this your function is included but my version works too. Regions are now in english language. For more additions think about creating a FDNCRED.vb in this folder with your additions and changes like your ConvertTo-Function.

All files in Functions (e.g. Pageseg.vb) are generated from the builder. each change will be deleted with the next commit. Changes to this files should only occur in the wrappergen. Natives.vb, Functions.vb (Functions Folder in 1 File) structures.vb, structureextensions.vb and enumerations.vb is the same.

sln and vbproj are static files. changed to your version.

Thank you for providing your C# test-Application and changing my pix files to files of the image folder.

for changes in the most descriptive files (via wrappergen) please open a issue and i will change the generators base.

For this topic we wait for a answer of dan bloomberg. correct output from leptonica is the best way!

Phreak87 commented 5 years ago

Don't use 1 to 2 bpp conversion. Use this adapted code from fdncred especially for 1 bpp and Return the bitmap directly

Public Function ConvertTo1BPPBMP(ByVal Pix As Pix) As Bitmap Dim pixelFormat As PixelFormat = pixelFormat.Format1bppIndexed Dim img As Bitmap = New Bitmap(Pix.w, Pix.h, pixelFormat) Using pixNew As Pix = _All.pixEndianByteSwapNew(Pix) Try Dim imgData As BitmapData = img.LockBits(New Rectangle(0, 0, img.Width, img.Height), 2, pixelFormat) For y = 0 To imgData.Height - 1 For x = 0 To imgData.Width - 1 Step 8 Dim index As Integer = (y pixNew.wpl 4) + (x >> 3) If index > pixNew.DataStatic.Count - 1 Then Continue For Marshal.WriteByte(imgData.Scan0, index, Not pixNew.DataStatic(index)) Next Next : img.UnlockBits(imgData) : Return img Catch ex As Exception img.Dispose() End Try End Using Return Nothing End Function