﻿Imports System.Drawing
Imports System.Drawing.Imaging

Imports System.IO

Imports System.Runtime.InteropServices

Namespace JCUtilities
    Namespace Imaging


        Public Class TiffImage

            Public Enum Compression
                CCITT4 = 4
                CCITT3 = 3
                LZW = 2
                Rle = 5
                None = 6
            End Enum

            Private _tiffFile As Bitmap
            Private _tiffPages As New PageCollection 'Bitmap arraylist
            Private _compression As Compression = Compression.LZW  'Default compression is set to LZW compression.
            Private _fileInfo As FileInfo

            '********************************************
            '             CONSTRUCTORS
            '********************************************

            ''' <summary>
            ''' All temporary pixelformats for the individual pages stored are in Format32bppPArgb.
            ''' Creates a new TiffImage object from the specified file (full path is required).
            ''' </summary>
            ''' <param name="filename"> The full path to the .tif file.</param>
            ''' <remarks></remarks>
            Public Sub New(ByVal filename As String, ByVal TempFolder As String)

                If Not File.Exists(filename) Then
                    Throw New FileNotFoundException("The file '" + filename + "' could not be found.")
                End If

                _tiffFile = Bitmap.FromFile(filename)
                SplitPages()
                _tiffFile.Dispose()
            End Sub

            '********************************************
            '              PROPERTIES
            '********************************************

            ''' <summary>
            ''' Sets the System.Drawing.Imaging.EncoderValue for this .tif image.
            ''' The default compression type is EnvoderValue.CompressionLZW.
            ''' </summary>
            ''' <value>Sets the new EncoderValue for this .tif Image.</value>
            ''' <returns>Returns the EncoderValue for this .tif Image.</returns>
            ''' <remarks></remarks>
            Public Property CompressionType() As Compression
                Get
                    Return _compression
                End Get
                Set(ByVal value As Compression)
                    _compression = value
                End Set
            End Property

            ''' <summary>
            ''' Returns a Page object which has exposed methods for editing.
            ''' </summary>
            ''' <param name="i"></param>
            ''' <value>index of what page to return</value>
            ''' <returns></returns>
            ''' <remarks></remarks>
            Public ReadOnly Property Page(ByVal i As Integer) As Page
                Get
                    Return _tiffPages(i)
                End Get
            End Property

            '********************************************
            '             PUBLIC FUNCTIONS
            '********************************************

            ''' <summary>
            ''' Saves the tif and the changes made to the specified filename
            ''' </summary>
            ''' <param name="filename">Full path for the new file to be saved to.</param>
            ''' <remarks>Exampe: ...Save("C:\mypath\mytif.tif")</remarks>
            Public Sub Save(ByVal filename As String)

                Dim SaveEncoder As Encoder = Encoder.SaveFlag
                Dim CompressionEncoder As Encoder = Encoder.Compression

                Dim imageCodec As ImageCodecInfo = GetCodecInfo("image/tiff")

                Dim MultiTiffPage As New EncoderParameter(SaveEncoder, CLng(EncoderValue.MultiFrame))
                Dim Frame As New EncoderParameter(SaveEncoder, CLng(EncoderValue.FrameDimensionPage))
                Dim Flush As New EncoderParameter(SaveEncoder, CLng(EncoderValue.Flush))
                Dim Compression As New EncoderParameter(CompressionEncoder, CLng(Me.CompressionType))

                Dim FirstPage As Page = CType(_tiffPages(0), Page)

                If Not FirstPage.Bitmap.PixelFormat = PixelFormat.Format1bppIndexed Then
                    ConvertToFormat1bppIndexedFrom32bppPArgb(FirstPage)
                End If

                Dim EncodeParams As New EncoderParameters(2)
                EncodeParams.Param(0) = Compression
                EncodeParams.Param(1) = MultiTiffPage

                FirstPage.Bitmap.Save(filename, imageCodec, EncodeParams)

                EncodeParams.Param(1) = Frame

                For i As Integer = 1 To _tiffPages.Count - 1

                    Dim tempP As Page = CType(_tiffPages(i), Page)

                    If Not tempP.Bitmap.PixelFormat = PixelFormat.Format1bppIndexed Then
                        ConvertToFormat1bppIndexedFrom32bppPArgb(tempP)
                    End If
                    FirstPage.Bitmap.SaveAdd(tempP.Image, EncodeParams)

                Next

                EncodeParams.Param(1) = Flush
                FirstPage.Bitmap.SaveAdd(EncodeParams)

            End Sub

            ''' <summary>
            ''' Save the tif file in PDF format.
            ''' </summary>
            ''' <param name="filename">The pdf file name to save this file to.</param>
            ''' <remarks>Example: ...SaveAsPDF("C:\mypath\mypdf.pdf")</remarks>
            Public Sub SaveAsPDF(ByVal filename As String)
                Dim pdfinfo As New FileInfo(filename)
                Dim tifpath As String = pdfinfo.Name.Replace(".pdf", ".tif").Replace(".PDF", ".tif")

                Save(pdfinfo.DirectoryName + "\" + tifpath)
                Dx_Viewer.ViewerControl.ConvertTiff2Pdf(pdfinfo.DirectoryName + "\" + tifpath, filename)
            End Sub

            '*******************************************
            '            PRIVATE FUNTIONS
            '*******************************************

            ''' <summary>
            ''' Splits the file passed in the constructor up into individual bitmap objects and stores them in memory for editing 
            ''' and later combination if so.  The objects are stored in the private _tiffPages BitmapCollection.
            ''' </summary>
            ''' <remarks></remarks>
            Private Sub SplitPages()

                For i As Integer = 0 To _tiffFile.GetFrameCount(FrameDimension.Page) - 1 'Loop through each page (frame).

                    Dim MS As New MemoryStream
                    _tiffFile.SelectActiveFrame(FrameDimension.Page, i) 'Select a tif page specified by the index i
                    _tiffFile.Save(MS, ImageFormat.Tiff)
                    Dim page As New Page(Bitmap.FromStream(MS))
                    ConvertToFormat32bppPArgb(page)
                    _tiffPages.Add(page)

                    MS.Flush()
                    MS.Close()
                Next

            End Sub

            ''' <summary>
            ''' Returns the ImageCodecInfo Object represented by the name passed.
            ''' </summary>
            ''' <param name="name">Name of the MimeType of the ImageCodecInfo object (Example: "image/tiff").</param>
            ''' <returns></returns>
            ''' <remarks></remarks>
            Private Function GetCodecInfo(ByVal name As String) As ImageCodecInfo

                Dim codecs() As ImageCodecInfo = ImageCodecInfo.GetImageEncoders()

                For Each info As ImageCodecInfo In codecs

                    If String.Equals(info.MimeType, name) Then
                        Return info
                    End If

                Next
                Return Nothing
            End Function

            ''' <summary>
            ''' Converts indivdual pixels to and indexed pixel type (2 colors)
            ''' </summary>
            ''' <param name="x"></param>
            ''' <param name="y"></param>
            ''' <param name="bmd"></param>
            ''' <param name="pixel"></param>
            ''' <remarks></remarks>
            Private Sub SetIndexedPixel(ByVal x As Integer, ByVal y As Integer, ByVal bmd As BitmapData, ByVal pixel As Boolean)

                Dim index As Integer = y * bmd.Stride + (x >> 3)

                Dim p As Byte = Marshal.ReadByte(bmd.Scan0, index)

                Dim mask As Byte = &H80 >> (x And &H7)

                If pixel Then

                    p = p Or mask

                Else

                    p = p And CByte(mask ^ &HFF)

                End If

                Marshal.WriteByte(bmd.Scan0, index, p)

            End Sub 'SetIndexedPixel

            ''' <summary>
            ''' Converts the page's pixeltype to Format32bppPArgb if it's current pixelformat is different.
            ''' </summary>
            ''' <param name="page">Page object to convert.</param>
            ''' <remarks></remarks>
            Private Sub ConvertToFormat32bppPArgb(ByRef page As Page)

                Dim temp As New Bitmap(page.Bitmap.Width, page.Bitmap.Height, PixelFormat.Format32bppPArgb)

                Dim g As Graphics = Graphics.FromImage(CType(temp, Image))
                g.DrawImage(page.Image, 0, 0, page.Image.Width, page.Image.Height)
                g.Dispose()

                page = New Page(temp)

            End Sub

            ''' <summary>
            ''' Converts the page's pixeltype to Format1bppIndexed if it's current pixelformat is different.
            ''' Only works if the page's current pixelformat is Format32bppPArgb.
            ''' </summary>
            ''' <param name="page">Page Object to convert</param>
            ''' <remarks></remarks>
            Private Sub ConvertToFormat1bppIndexedFrom32bppPArgb(ByRef page As Page)

                Dim tifpage As Bitmap = page.Bitmap

                Dim bmdo As BitmapData = tifpage.LockBits(New Rectangle(0, 0, tifpage.Width, tifpage.Height), ImageLockMode.ReadOnly, tifpage.PixelFormat)

                'and the new 1bpp bitmap
                Dim bm As New Bitmap(tifpage.Width, tifpage.Height, PixelFormat.Format1bppIndexed)
                Dim bmdn As BitmapData = bm.LockBits(New Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed)

                'for diagnostics
                Dim dt As DateTime = DateTime.Now

                'scan through the pixels Y by X
                Dim y As Integer

                For y = 0 To tifpage.Height - 1

                    Dim x As Integer

                    For x = 0 To tifpage.Width - 1
                        'generate the address of the colour pixel
                        Dim index As Integer = y * bmdo.Stride + x * 4

                        'check its brightness
                        If Color.FromArgb(Marshal.ReadByte(bmdo.Scan0, index + 2), Marshal.ReadByte(bmdo.Scan0, index + 1), Marshal.ReadByte(bmdo.Scan0, index)).GetBrightness() > 0.5F Then

                            Me.SetIndexedPixel(x, y, bmdn, True) 'set it if its bright.

                        End If

                    Next x

                Next y

                'tidy up

                bm.UnlockBits(bmdn)
                tifpage.UnlockBits(bmdo)
                tifpage.Dispose()

                page = New Page(bm)

            End Sub

        End Class


        ''' <summary>
        ''' Collection class for storing Page objects.  Inherits from the ArrayList class.
        ''' </summary>
        ''' <remarks>Created for visual cohesion.</remarks>
        Public Class PageCollection
            Inherits Collections.ArrayList

            Public Sub New()
                MyBase.New()
            End Sub

        End Class

        ''' <summary>
        ''' Class for holding an individual tiff file capable of manipulation.
        ''' </summary>
        ''' <remarks></remarks>
        Public Class Page

            Private _page As Bitmap
            Private _edited As Boolean = False

            '**************************************
            '            CONSTRUCTORS
            '**************************************

            Public Sub New(ByVal page As Bitmap)
                _page = page
            End Sub

            '**************************************
            '          PRIVATE FUNCTIONS
            '**************************************

            Private Sub Edit()
                _edited = True
            End Sub

            '**************************************
            '            PROPERTIES
            '**************************************

            Public ReadOnly Property isEdited() As Boolean
                Get
                    Return _edited
                End Get
            End Property

            Public ReadOnly Property Bitmap() As Bitmap
                Get
                    Return _page
                End Get
            End Property

            Public ReadOnly Property Image() As Image
                Get
                    Return CType(_page, Image)
                End Get
            End Property

            '**************************************
            '            PUBLIC FUNCTIONS
            '**************************************

            Public Sub RotateFlip(ByVal type As RotateFlipType)
                _page.RotateFlip(type)
                Edit()
            End Sub

            Public Sub DrawImage(ByVal img As Image, ByVal point As Point)
                Dim g As Graphics = Graphics.FromImage(CType(_page, Image))
                g.DrawImage(img, point)
                g.Dispose()
                Edit()
            End Sub

            Public Sub DrawImage(ByVal img As Image, ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer)
                Dim g As Graphics = Graphics.FromImage(CType(_page, Image))
                g.DrawImage(img, x, y, width, height)
                g.Dispose()
                Edit()
            End Sub

            Public Sub DrawLine(ByVal pen As Pen, ByVal x1 As Single, ByVal y1 As Single, ByVal x2 As Single, ByVal y2 As Single)
                Dim g As Graphics = Graphics.FromImage(CType(_page, Image))
                g.DrawLine(pen, x1, y1, x2, y2)
                g.Dispose()
                Edit()
            End Sub

            Public Sub DrawRectangle(ByVal pen As Pen, ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer)
                Dim g As Graphics = Graphics.FromImage(CType(_page, Image))
                g.DrawRectangle(pen, x, y, width, height)
                g.Dispose()
                Edit()
            End Sub

            Public Sub DrawEllipse(ByVal pen As Pen, ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer)
                Dim g As Graphics = Graphics.FromImage(CType(_page, Image))
                g.DrawEllipse(pen, x, y, width, height)
                g.Dispose()
                Edit()
            End Sub

            Public Sub FillRectangle(ByVal brush As Brush, ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer)
                Dim g As Graphics = Graphics.FromImage(CType(_page, Image))
                g.FillRectangle(brush, x, y, width, height)
                g.Dispose()
                Edit()
            End Sub

            Public Sub FillEllipse(ByVal brush As Brush, ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer)
                Dim g As Graphics = Graphics.FromImage(CType(_page, Image))
                g.FillEllipse(brush, x, y, width, height)
                g.Dispose()
                Edit()
            End Sub

            Public Sub DrawString(ByVal value As String, ByVal font As Font, ByVal brush As Brush, ByVal x As Single, ByVal y As Single)
                Dim g As Graphics = Graphics.FromImage(CType(_page, Image))
                g.DrawString(value, font, brush, x, y)
                g.Dispose()
                Edit()
            End Sub

        End Class

    End Namespace
End Namespace