drupal hit counter
Jerry Huang | apps and developing apps

Jerry Huang apps and developing apps

Serializing image in WP7

15. April 2011 03:23 by Jerry in Windows Phone Development

This post is to celebrate my 2nd app - Smart Dial has been approved in marketplace.

None of the build in imaging classes in Windows Phone or Silverlight is serializable, that's why I have to build my own class.Cool

To serialize a custom object, the class need to implement IXmlSerializable interface. So my class inherits BitmapSource and then implement IXmlSerializable.

The idea of the whole thing is actually pretty simple. Just covert the image into byte array, and then transform to Base64 string, and backward for deserialization. With such approach, we are able to ship the image within XML(i.e. pure text). This is how my app (Smart Dial) to transfer contact photos between WP7 and server.

 Since my Smart Dial solution consists 2 client programs in handset device and desktop computer, I made the class compatible with both silverlight (WP7) and Winform (base on .Net 3.5). However, somehow I can't load the image source to itself in winform version. The BitmapSource in winform doesn't contain the SetSource method, I have use a stupid property (Source).

Simply copy and paste and compile and enjoyLaughing

[code language=C#]   

using System;
using System.Windows;
using System.Xml.Serialization;
using System.IO;
using System.Windows.Media.Imaging;
using System.Xml.Schema;
using System.Xml;
 #if !SILVERLIGHT
using System.Drawing;
#endif
namespace SmartDial.Data
{

    public class SerializableImage : BitmapSource, IXmlSerializable
    {
 #if SILVERLIGHT
        private string ImageToString(BitmapSource image)
#else
        private string ImageToString(Bitmap image)
#endif
        {
           
            byte[] byteArray;

            using (MemoryStream stream = new MemoryStream())
            {
 #if SILVERLIGHT
                WriteableBitmap bmp = new WriteableBitmap((BitmapSource)image);
                bmp.SaveJpeg(stream, bmp.PixelWidth, bmp.PixelHeight, 0, 100);
                byteArray = stream.ToArray();
#else
                image.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
                stream.Flush();
                byteArray = stream.ToArray();
#endif
              
            }

            return Convert.ToBase64String(byteArray);
        }
        public SerializableImage() { }
 #if SILVERLIGHT
       
        public SerializableImage(Uri uri)
        {

            using (Stream stream = Application.GetResourceStream(uri).Stream)
            {
                this.SetSource(stream);
            }
        }

#else
        public SerializableImage(Image image)
        {
            this.Source = (Bitmap)image;
        }
        protected override Freezable CreateInstanceCore() { return new SerializableImage(); }
        public Bitmap Source { get;  set; }
#endif

        private void StringToImage(string imageString)
        {
            if (imageString == null)
                throw new ArgumentNullException("imageString");

            byte[] array = Convert.FromBase64String(imageString);
 #if SILVERLIGHT
            this.SetSource (new MemoryStream(array));
#else
            Source = (Bitmap)Image.FromStream(new MemoryStream(array));
#endif
          

        }


        #region IXmlSerializable Members

        public XmlSchema GetSchema()
        {
            throw new NotImplementedException();
        }

        public void ReadXml(XmlReader reader)
        {


            if (reader.IsEmptyElement || !reader.Read())
            {
                return;
            }
            XmlSerializer imgSerializer = new XmlSerializer(typeof(string));
            while (reader.NodeType != XmlNodeType.EndElement)
            {
                reader.ReadStartElement("SerializedImage");

                string img = (string)imgSerializer.Deserialize(reader);
                reader.ReadEndElement();

                reader.MoveToContent();

                StringToImage(img);
            }
            reader.ReadEndElement();
        }

        public void WriteXml(XmlWriter writer)
        {
            XmlSerializer imgSerializer = new XmlSerializer(typeof(string));

            writer.WriteStartElement("SerializedImage");
 #if SILVERLIGHT
            string img = ImageToString( this);
#else
            string img = ImageToString(this.Source);
#endif
            imgSerializer.Serialize(writer, img);
            writer.WriteEndElement();

        }

        #endregion
    }
}

[/code]

HD2: a tour to TOILET

9. April 2011 12:10 by Jerry in

the victim, naked...

the murderer, he is keep committing to crimes afterward BTW

still alive...yea, I knew, thanks to DFT...

coming back...

faith regained.

new feature in v1.4

6. April 2011 03:03 by Jerry in IP CAM

The new 1.4 version ships with a feature: upload / download your camera config files to/from server.

In the Setup screen, click the expand button at application bar (the "..." at right down corner), then click the "upload/download camera", enjoy! Laughing

My first app on Windows Marketplace now

17. March 2011 20:21 by Jerry in IP CAM

Finally, my first Windows Phone 7 application published!Laughing 

Search "ip cam control" in the Marketplace with your Windows Phone device!!

At the end of below page, I provided a windows version to test if my app compatible with your camera. Please have a try if you are interested.

more detail...

 

DbMoto comments and experience

3. September 2010 13:00 by Jerry in Product Experience

The previous blog I wrote last night is something about DbMoto. Today I found that my feeling is a bit more than that, so I list out the summary how I feel after using this product.

  1. It’s basically a good tool for syncalizing data from DB2 to SQL, in my case. Easy to setup and use comparing with some jumbo products. It provides a nice GUI tool to setup and manage replications.
  2. Not very good for real time change data capture, currently we set the interval to 30 minutes, i.e. data will sync from DB2 to SQL every 30 minutes. There was a time we set to 5 minute but it seems there were some problems. I am not sure about the detail (I'm not in the AS400 team), what I heard fromt my colleague is that the receiver in AS400 caused the CPU overran.
  3. The product and the company have a long history and the market share is growing. The support is ok; so far they solved all problems we hit, as I can remember.
  4. I believe the engine is base on .Net framework 2.0, so it needs to run on a Windows server. This is ok for us but some people insist the performance is better if a product is base on non-Microsoft server.
  5. Very good extensibility, we can write our own script into the replication. We are using VB.NET as script language, but I guess it also supports other .NET languages. The script will be compiled to .NET binary code so the efficiency will be better than those dynamically compile.
  6. Scripting is good but also brings the biggest problem – there is no way to debug the script in development. What we are doing now, is to write log for debugging. For development, ideally we hope the dbmoto engine somehow can call into Visual Studio so that we may set breakpoint, watch the value of local variables, etc. Due to this, we avoid to put something too complicated in the scripts.

Overall, I will say dbmoto is a light-weight to medium change data capture engine, to keep 2 different databases identical. If your requirement is not about some sort of comprehensive ETL solution, just to sync data from one place to another, DbMoto should be a good tool for that. However, it might not suit for you if require extremely low latency (i.e real time) syncalization. Hmmm, but real-time is a big challenge for all products.

The more stupid you assume the consumer is, the better implementation your framework would be

2. September 2010 23:29 by Jerry in Framework Design

DbMoto is a light-weight to medium change data capture product in my opinion. It basically sync data from one database to another, to keep 2 database identical, even target and source are not the same kind of database (from DB2 to SQL for example).

One of the strengths of DbMoto is that you can write your own script in a replication, so that it could satisfy some complicated business needs. In my case, we used VB.NET as the scripting language. Recently we found some error about deadlock in the log. Originally we thought the problem was from SQL database, but we couldn't find any log entry in SQL server. We fired a ticket for vendor support. Later on they told us to use SyncLock on the TargetConnection object. So I change the script to use Monitor (better than SyncLock) instead. The code goes like:

[code language=VB.NET]

 Try
        System.Threading.Monitor.Enter(TargetConnection)
        cmd = TargetConnection.CreateCommand
        ...
  Catch e As Exception
        ...

  Finally
        System.Threading.Monitor.Exit(TargetConnection)
  End Try
[/code]

From this accident, I feel this should be a minor problem in the architecture of DbMoto, that's how the title came into my mind. In short, the object provided by Dbmoto is not thread-safe. From the point of view of framework design, when writing something will be used in multi-threads environment, it's better to make sure the objects that explosing to consumer are thread-safe. Rather than leaving the problem to sub-classes, why not just handle them internally?

scan barcode from TIFF

31. July 2010 09:25 by Jerry in Cool stuff

http://www.codeproject.com/KB/graphics/BarcodeImaging3.aspx

The ariticle above is really something awesome, you may easily scan barcode in an image, pure managed code. It supports Code39, Code39ex, Code128, EAN, EAN-2, EAN-5.

Together with below utility class, I used it in an application to scan barcode from tiff

[code language=C#]

using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;  
using System.Collections;

namespace AMA.Util
{
 /// <summary>
 /// Summary description for TiffManager.
 /// </summary>
 public class TiffManager : IDisposable
 {
  private string _ImageFileName;
  private int _PageNumber;
  private Image image;
  private string _TempWorkingDir;

  public TiffManager(string imageFileName)
  {
   this._ImageFileName=imageFileName;
   image=Image.FromFile(_ImageFileName);
   GetPageNumber();
  }
  
  public TiffManager(){
  }

  /// <summary>
  /// Read the image file for the page number.
  /// </summary>
  private void GetPageNumber(){
   Guid objGuid=image.FrameDimensionsList[0];
   FrameDimension objDimension=new FrameDimension(objGuid);

   //Gets the total number of frames in the .tiff file
   _PageNumber=image.GetFrameCount(objDimension);
   
   return;
  }

  /// <summary>
  /// Read the image base string,
  /// Assert(GetFileNameStartString(@"c:\test\abc.tif"),"abc")
  /// </summary>
  /// <param name="strFullName"></param>
  /// <returns></returns>
  private string GetFileNameStartString(string strFullName){
   int posDot=_ImageFileName.LastIndexOf(".");
   int posSlash=_ImageFileName.LastIndexOf(@"\");
   return _ImageFileName.Substring(posSlash+1,posDot-posSlash-1);
  }

  /// <summary>
  /// This function will output the image to a TIFF file with specific compression format
  /// </summary>
  /// <param name="outPutDirectory">The splited images' directory</param>
  /// <param name="format">The codec for compressing</param>
  /// <returns>splited file name array list</returns>
  public ArrayList SplitTiffImage(string outPutDirectory,EncoderValue format)
  {
   string fileStartString=outPutDirectory+"\\"+GetFileNameStartString(_ImageFileName);
   ArrayList splitedFileNames=new ArrayList();
   try{
    Guid objGuid=image.FrameDimensionsList[0];
    FrameDimension objDimension=new FrameDimension(objGuid);

    //Saves every frame as a separate file.
    Encoder enc=Encoder.Compression;
    int curFrame=0;
    for (int i=0;i<_PageNumber;i++)
    {
     image.SelectActiveFrame(objDimension,curFrame);
     EncoderParameters ep=new EncoderParameters(1);
     ep.Param[0]=new EncoderParameter(enc,(long)format);
     ImageCodecInfo info=GetEncoderInfo("image/tiff");
     
     //Save the master bitmap
     string fileName=string.Format("{0}{1}.TIF",fileStartString,i.ToString());
     image.Save(fileName,info,ep);
     splitedFileNames.Add(fileName);

     curFrame++;
    } 
   }catch (Exception){
    throw;
   }
   
   return splitedFileNames;
  }

  /// <summary>
  /// This function will join the TIFF file with a specific compression format
  /// </summary>
  /// <param name="imageFiles">string array with source image files</param>
  /// <param name="outFile">target TIFF file to be produced</param>
  /// <param name="compressEncoder">compression codec enum</param>
  public void JoinTiffImages(string[] imageFiles,string outFile,EncoderValue compressEncoder)
  {
   try{
    //If only one page in the collection, copy it directly to the target file.
    if (imageFiles.Length==1)
    {
     File.Copy(imageFiles[0],outFile,true);
     return;
    }

    //use the save encoder
    Encoder enc=Encoder.SaveFlag;

    EncoderParameters ep=new EncoderParameters(2);
    ep.Param[0]=new EncoderParameter(enc,(long)EncoderValue.MultiFrame);
    ep.Param[1] = new EncoderParameter(Encoder.Compression,(long)compressEncoder);

    Bitmap pages=null;
    int frame=0;
    ImageCodecInfo info=GetEncoderInfo("image/tiff");


    foreach(string strImageFile in imageFiles)
    {
     if(frame==0)
     {
      pages=(Bitmap)Image.FromFile(strImageFile);

      //save the first frame
      pages.Save(outFile,info,ep);
     }
     else
     {
      //save the intermediate frames
      ep.Param[0]=new EncoderParameter(enc,(long)EncoderValue.FrameDimensionPage);

      Bitmap bm=(Bitmap)Image.FromFile(strImageFile);
      pages.SaveAdd(bm,ep);
     }       

     if(frame==imageFiles.Length-1)
     {
      //flush and close.
      ep.Param[0]=new EncoderParameter(enc,(long)EncoderValue.Flush);
      pages.SaveAdd(ep);
     }

     frame++;
    }
   }catch (Exception){
    throw;
   }
   
   return;
  }

  /// <summary>
  /// This function will join the TIFF file with a specific compression format
  /// </summary>
  /// <param name="imageFiles">array list with source image files</param>
  /// <param name="outFile">target TIFF file to be produced</param>
  /// <param name="compressEncoder">compression codec enum</param>
  public void JoinTiffImages(ArrayList imageFiles,string outFile,EncoderValue compressEncoder)
  {
   try
   {
    //If only one page in the collection, copy it directly to the target file.
    if (imageFiles.Count==1){
     File.Copy((string)imageFiles[0],outFile,true);
     return;
    }

    //use the save encoder
    Encoder enc=Encoder.SaveFlag;

    EncoderParameters ep=new EncoderParameters(2);
    ep.Param[0]=new EncoderParameter(enc,(long)EncoderValue.MultiFrame);
    ep.Param[1] = new EncoderParameter(Encoder.Compression,(long)compressEncoder);

    Bitmap pages=null;
    int frame=0;
    ImageCodecInfo info=GetEncoderInfo("image/tiff");


    foreach(string strImageFile in imageFiles)
    {
     if(frame==0)
     {
      pages=(Bitmap)Image.FromFile(strImageFile);

      //save the first frame
      pages.Save(outFile,info,ep);
     }
     else
     {
      //save the intermediate frames
      ep.Param[0]=new EncoderParameter(enc,(long)EncoderValue.FrameDimensionPage);

      Bitmap bm=(Bitmap)Image.FromFile(strImageFile);
      pages.SaveAdd(bm,ep);
      bm.Dispose();
     }       

     if(frame==imageFiles.Count-1)
     {
      //flush and close.
      ep.Param[0]=new EncoderParameter(enc,(long)EncoderValue.Flush);
      pages.SaveAdd(ep);
     }

     frame++;
    }
   }
   catch (Exception ex)
   {
#if DEBUG
    Console.WriteLine(ex.Message);
#endif
    throw;
   }
   
   return;
  }

  /// <summary>
  /// Remove a specific page within the image object and save the result to an output image file.
  /// </summary>
  /// <param name="pageNumber">page number to be removed</param>
  /// <param name="compressEncoder">compress encoder after operation</param>
  /// <param name="strFileName">filename to be outputed</param>
  /// <returns></</returns>
  public void RemoveAPage(int pageNumber,EncoderValue compressEncoder,string strFileName){
   try
   {
    //Split the image files to single pages.
    ArrayList arrSplited=SplitTiffImage(this._TempWorkingDir,compressEncoder);
    
    //Remove the specific page from the collection
    string strPageRemove=string.Format("{0}\\{1}{2}.TIF",_TempWorkingDir,GetFileNameStartString(this._ImageFileName),pageNumber);
    arrSplited.Remove(strPageRemove);

    JoinTiffImages(arrSplited,strFileName,compressEncoder);
   }
   catch(Exception)
   {
    throw;
   }

   return;
  }

  /// <summary>
  /// Getting the supported codec info.
  /// </summary>
  /// <param name="mimeType">description of mime type</param>
  /// <returns>image codec info</returns>
  private ImageCodecInfo GetEncoderInfo(string mimeType){
   ImageCodecInfo[] encoders=ImageCodecInfo.GetImageEncoders();
   for (int j=0;j<encoders.Length;j++){
    if (encoders[j].MimeType==mimeType)
     return encoders[j];
   }
   
   throw new Exception( mimeType + " mime type not found in ImageCodecInfo" );
  }

  /// <summary>
  /// Return the memory steam of a specific page
  /// </summary>
  /// <param name="pageNumber">page number to be extracted</param>
  /// <returns>image object</returns>
  public Image GetSpecificPage(int pageNumber)
  {
   MemoryStream ms=null;
   Image retImage=null;
   try
   {
                ms=new MemoryStream();
    Guid objGuid=image.FrameDimensionsList[0];
    FrameDimension objDimension=new FrameDimension(objGuid);

    image.SelectActiveFrame(objDimension,pageNumber);
    image.Save(ms,ImageFormat.Bmp);
    
    retImage=Image.FromStream(ms);

    return retImage;
   }
   catch (Exception)
   {
    ms.Close();
    retImage.Dispose();
    throw;
   }
  }

  /// <summary>
  /// Convert the existing TIFF to a different codec format
  /// </summary>
  /// <param name="compressEncoder"></param>
  /// <returns></returns>
  public void ConvertTiffFormat(string strNewImageFileName,EncoderValue compressEncoder)
  {
   //Split the image files to single pages.
   ArrayList arrSplited=SplitTiffImage(this._TempWorkingDir,compressEncoder);
   JoinTiffImages(arrSplited,strNewImageFileName,compressEncoder);

   return;
  }

  /// <summary>
  /// Image file to operate
  /// </summary>
  public string ImageFileName
  {
   get
   {
    return _ImageFileName;
   }
   set{
    _ImageFileName=value;
   }
  }

  /// <summary>
  /// Buffering directory
  /// </summary>
  public string TempWorkingDir
  {
   get
   {
    return _TempWorkingDir;
   }
   set{
    _TempWorkingDir=value;
   }
  }

  /// <summary>
  /// Image page number
  /// </summary>
  public int PageNumber
  {
   get
   {
    return _PageNumber;
   }
  }

 
  #region IDisposable Members

  public void Dispose()
  {
   image.Dispose();
   System.GC.SuppressFinalize(this);
  }

  #endregion
 }
}

[/code]

PDF to Tiff

18. July 2010 22:33 by Jerry in Old blog posts

I don’t know why, it seems to be hard to find something to convert PDF to Tiff over the web. Maybe TIFF is not that popular than other formats. Few months ago, I had a task at work to find a new way to generate tiff files out of crystal report. Previously we have a batch job that print the crystal report to a virtual printer to produce a tiff file. It was working fine, but from now and then the job hit an IO error couple times in a month. I was pissed off by being called at night (the job was running overnight). So I decided to revamp this piece of sxxx process.

After some searching, I realized that I need to solve 2 problems:

  • PDF to TIFF by using 3rd party component. I chose Ghostscript API, because it’s free and simple (one dll).
    The only issue of Ghostscript API (or other components) in this case is that, if the PDF has multiple pages, it produces multiple tiff files which that each tiff representing one page in the PDF. So it brings us to the next step;
  • Group all single page tiff files into one multiple pages TIFF

The source code provide below is running quite stable as batch job for a few months.

References

Ghostscript: http://www.ghostscript.com/ you need to put the gsdll32.dll in the same folder of your dll/exe

for step 2, the related source code was convert from C# in this article:
http://www.codeproject.com/KB/GDI-plus/SaveMultipageTiff.aspx
However, the ConvertToBitonal function has a bug. You need to dispose the original bitmap object (original.Dispose();) before returning destination.

Usage

ConvertFile("c:\2.tiff", "C:\temp\pdf\PWBSample.pdf", "c:\temp\tiff", "-IC:\Program Files\gs\gs8.70\lib;C:\Program Files\gs\fonts;C:/Windows/fonts")
 

Source Code

 

' Copyright (c) 2002 Dan Mount and Ghostgum Software Pty Ltd
'
' Permission is hereby granted, free of charge, to any person obtaining
' a copy of this software and associated documentation files (the
' "Software"), to deal in the Software without restriction, including
' without limitation the rights to use, copy, modify, merge, publish,
' distribute, sublicense, and/or sell copies of the Software, and to
' permit persons to whom the Software is furnished to do so, subject to
' the following conditions:
'
' The above copyright notice and this permission notice shall be
' included in all copies or substantial portions of the Software.
'
' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
' NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
' BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
' ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
' CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
' SOFTWARE.


' This is an example of how to call the Ghostscript DLL from
' Visual Basic.NET. There are two examples, one converts
' colorcir.ps to PDF, the other is like command line Ghostscript.
' The display device is not supported.
'
' This code is not compatible with VB6. There is another
' example which does work with VB6. Differences include:
' 1. VB.NET uses GCHandle to get pointer
' VB6 uses StrPtr/VarPtr
' 2. VB.NET Integer is 32bits, Long is 64bits
' VB6 Integer is 16bits, Long is 32bits
' 3. VB.NET uses IntPtr for pointers
' VB6 uses Long for pointers
' 4. VB.NET strings are always Unicode
' VB6 can create an ANSI string
' See the following URL for some VB6 / VB.NET details
' http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvb600/html/vb6tovbdotnet.asp

Option Explicit On

Imports System.Runtime.InteropServices
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Drawing

Module gsapi

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal dest As IntPtr, ByVal source As IntPtr, ByVal bytes As Long)

'------------------------------------------------
'UDTs Start
'------------------------------------------------
<StructLayout(LayoutKind.Sequential)> Public Structure GS_Revision
Public strProduct As IntPtr
Public strCopyright As IntPtr
Public intRevision As Integer
Public intRevisionDate As Integer
End Structure
'------------------------------------------------
'UDTs End
'------------------------------------------------

'------------------------------------------------
'Callback Functions Start
'------------------------------------------------
'These are only required if you use gsapi_set_stdio
Public Delegate Function StdioCallBack(ByVal handle As IntPtr, ByVal strptr As IntPtr, ByVal count As Integer) As Integer

Public Function gsdll_stdin(ByVal intGSInstanceHandle As IntPtr, ByVal strz As IntPtr, ByVal intBytes As Integer) As Integer
' This is dumb code that reads one byte at a time
' Ghostscript doesn't mind this, it is just very slow
If intBytes = 0 Then
gsdll_stdin = 0
Else
Dim ich As Integer = Console.Read()
If ich = -1 Then
gsdll_stdin = 0 ' EOF
Else
Dim bch As Byte = ich
Dim gcByte As GCHandle = GCHandle.Alloc(bch, GCHandleType.Pinned)
Dim ptrByte As IntPtr = gcByte.AddrOfPinnedObject()
CopyMemory(strz, ptrByte, 1)
ptrByte = IntPtr.Zero
gcByte.Free()
gsdll_stdin = 1
End If
End If
End Function

Public Function gsdll_stdout(ByVal intGSInstanceHandle As IntPtr, ByVal strz As IntPtr, ByVal intBytes As Integer) As Integer
' If you can think of a more efficient method, please tell me!
' We need to convert from a byte buffer to a string
' First we create a byte array of the appropriate size
Dim aByte(intBytes) As Byte
' Then we get the address of the byte array
Dim gcByte As GCHandle = GCHandle.Alloc(aByte, GCHandleType.Pinned)
Dim ptrByte As IntPtr = gcByte.AddrOfPinnedObject()
' Then we copy the buffer to the byte array
CopyMemory(ptrByte, strz, intBytes)
' Release the address locking
ptrByte = IntPtr.Zero
gcByte.Free()
' Then we copy the byte array to a string, character by character
Dim str As String = ""

For i As Integer = 0 To intBytes - 1
str = str + Chr(aByte(i))
Next
' Finally we output the message
Console.Write(str)
gsdll_stdout = intBytes
End Function

Public Function gsdll_stderr(ByVal intGSInstanceHandle As IntPtr, ByVal strz As IntPtr, ByVal intBytes As Integer) As Integer
gsdll_stderr = gsdll_stdout(intGSInstanceHandle, strz, intBytes)
End Function
'------------------------------------------------
'Callback Functions End
'------------------------------------------------

'------------------------------------------------
'API Calls Start
'------------------------------------------------
'Win32 API
'GhostScript API
' Public Declare Function gsapi_revision Lib "gsdll32.dll" (ByVal pGSRevisionInfo As IntPtr, ByVal intLen As Integer) As Integer
Public Declare Function gsapi_revision Lib "gsdll32.dll" (ByRef pGSRevisionInfo As GS_Revision, ByVal intLen As Integer) As Integer
Public Declare Function gsapi_new_instance Lib "gsdll32.dll" (ByRef lngGSInstance As IntPtr, ByVal lngCallerHandle As IntPtr) As Integer
Public Declare Function gsapi_set_stdio Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr, ByVal gsdll_stdin As StdioCallBack, ByVal gsdll_stdout As StdioCallBack, ByVal gsdll_stderr As StdioCallBack) As Integer
Public Declare Sub gsapi_delete_instance Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr)
Public Declare Function gsapi_init_with_args Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr, ByVal lngArgumentCount As Integer, ByVal lngArguments As IntPtr) As Integer
Public Declare Function gsapi_run_file Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr, ByVal strFileName As String, ByVal intErrors As Integer, ByVal intExitCode As Integer) As Integer
Public Declare Function gsapi_exit Lib "gsdll32.dll" (ByVal lngGSInstance As IntPtr) As Integer
'------------------------------------------------
'API Calls End
'------------------------------------------------

'------------------------------------------------
'User Defined Functions Start
'------------------------------------------------
Public Function StringToAnsiZ(ByVal str As String) As Byte()
' Convert a Unicode string to a null terminated Ansi string for Ghostscript.
' The result is stored in a byte array. Later you will need to convert
' this byte array to a pointer with GCHandle.Alloc(XXXX, GCHandleType.Pinned)
' and GSHandle.AddrOfPinnedObject()
Dim intElementCount As Integer
Dim intCounter As Integer
Dim aAnsi() As Byte
Dim bChar As Byte
intElementCount = Len(str)
ReDim aAnsi(intElementCount + 1)
For intCounter = 0 To intElementCount - 1
bChar = Asc(Mid(str, intCounter + 1, 1))
aAnsi(intCounter) = bChar
Next intCounter
aAnsi(intElementCount) = 0
StringToAnsiZ = aAnsi
End Function

Public Function AnsiZtoString(ByVal strz As IntPtr) As String
' We need to convert from a byte buffer to a string
Dim byteCh(1) As Byte
Dim bOK As Boolean = True
Dim gcbyteCh As GCHandle = GCHandle.Alloc(byteCh, GCHandleType.Pinned)
Dim ptrByte As IntPtr = gcbyteCh.AddrOfPinnedObject()
Dim j As Integer = 0
Dim str As String = ""
While bOK
' This is how to do pointer arithmetic!
Dim sPtr As New IntPtr(strz.ToInt64() + j)
CopyMemory(ptrByte, sPtr, 1)
If byteCh(0) = 0 Then
bOK = False
Else
str = str + Chr(byteCh(0))
End If
j = j + 1
End While
AnsiZtoString = str
End Function

Public Function CheckRevision(ByVal intRevision As Integer) As Boolean
' Check revision number of Ghostscript
Dim intReturn As Integer
Dim udtGSRevInfo As GS_Revision
Dim gcRevision As GCHandle
gcRevision = GCHandle.Alloc(udtGSRevInfo, GCHandleType.Pinned)
intReturn = gsapi_revision(udtGSRevInfo, 16)
Console.WriteLine("Revision = " & udtGSRevInfo.intRevision)
Console.WriteLine("RevisionDate = " & udtGSRevInfo.intRevisionDate)
Console.WriteLine("Product = " & AnsiZtoString(udtGSRevInfo.strProduct))
Console.WriteLine("Copyright = " & AnsiZtoString(udtGSRevInfo.strCopyright))

If udtGSRevInfo.intRevision = intRevision Then
CheckRevision = True
Else
CheckRevision = False
End If
gcRevision.Free()
End Function

Public Function CallGS(ByVal astrGSArgs() As String) As Boolean
Dim intReturn As Integer
Dim intGSInstanceHandle As IntPtr
Dim aAnsiArgs() As Object
Dim aPtrArgs() As IntPtr
Dim aGCHandle() As GCHandle
Dim intCounter As Integer
Dim intElementCount As Integer
'Dim iTemp As Integer
Dim callerHandle As IntPtr
Dim gchandleArgs As GCHandle
Dim intptrArgs As IntPtr

' Print out the revision details.
' If we want to insist on a particular version of Ghostscript
' we should check the return value of CheckRevision().
CheckRevision(704)

' Load Ghostscript and get the instance handle
intReturn = gsapi_new_instance(intGSInstanceHandle, callerHandle)
If (intReturn < 0) Then
Return (False)
End If

' Capture stdio
Dim stdinCallback As StdioCallBack
stdinCallback = AddressOf gsdll_stdin
Dim stdoutCallback As StdioCallBack
stdoutCallback = AddressOf gsdll_stdout
Dim stderrCallback As StdioCallBack
stderrCallback = AddressOf gsdll_stderr
intReturn = gsapi_set_stdio(intGSInstanceHandle, stdinCallback, stdoutCallback, stderrCallback)

If (intReturn >= 0) Then
' Convert the Unicode strings to null terminated ANSI byte arrays
' then get pointers to the byte arrays.
intElementCount = UBound(astrGSArgs)
ReDim aAnsiArgs(intElementCount)
ReDim aPtrArgs(intElementCount)
ReDim aGCHandle(intElementCount)
For intCounter = 0 To intElementCount
aAnsiArgs(intCounter) = StringToAnsiZ(astrGSArgs(intCounter))
aGCHandle(intCounter) = GCHandle.Alloc(aAnsiArgs(intCounter), GCHandleType.Pinned)
aPtrArgs(intCounter) = aGCHandle(intCounter).AddrOfPinnedObject()
Next
gchandleArgs = GCHandle.Alloc(aPtrArgs, GCHandleType.Pinned)
intptrArgs = gchandleArgs.AddrOfPinnedObject()
callerHandle = IntPtr.Zero

intReturn = gsapi_init_with_args(intGSInstanceHandle, intElementCount + 1, intptrArgs)

' Release the pinned memory
For intCounter = 0 To intElementCount
aGCHandle(intCounter).Free()
Next
gchandleArgs.Free()

' Stop the Ghostscript interpreter
gsapi_exit(intGSInstanceHandle)
End If

' release the Ghostscript instance handle
gsapi_delete_instance(intGSInstanceHandle)

If (intReturn >= 0) Then
CallGS = True
Else
CallGS = False
End If

End Function

Private Sub ClearTempFolder(ByVal path As String)
Dim folder As New DirectoryInfo(path)
Dim files() As FileInfo = folder.GetFiles()
For Each fi As FileInfo In files
fi.Delete()
Next
End Sub

Public Sub OutputToTiff(ByVal tifPath As String)
Dim folder As New DirectoryInfo(tmpFolder)
Dim files() As FileInfo = folder.GetFiles("*.tiff")
If files.Length > 0 Then
Dim imgs(files.Length - 1) As Image
SortByName(files)

For i As Integer = 0 To files.Length - 1
imgs(i) = Image.FromFile(files(i).FullName)
Next
saveMultipage(imgs, tifPath, "TIFF")
For Each img As Image In imgs
img.Dispose()
Next

'ClearTempFolder(tmpFolder)
End If
Directory.Delete(tmpFolder, True)
End Sub

Private tmpFolder As String

Private Function GetTempFolder() As String
Dim g As Guid = Guid.NewGuid()
While Directory.Exists(tmpFolder & "\" & g.ToString())
g = New Guid()
End While
Return tmpFolder & "\" & g.ToString()
End Function


Public Function ConvertFile(ByVal tifPath As String, ByVal pdfSource As String, ByVal tempFolder As String, ByVal startUpStr As String) As Boolean
tmpFolder = tempFolder

tmpFolder = GetTempFolder()
Directory.CreateDirectory(tmpFolder)
'ClearTempFolder("c:\temp\tiff")
Dim astrArgs(16) As String
astrArgs(0) = "accfax" 'The First Parameter is Ignored
astrArgs(1) = startUpStr
astrArgs(2) = "-dNOPAUSE"
astrArgs(3) = "-dBATCH"
astrArgs(4) = "-dSAFER"
astrArgs(5) = "-sDEVICE=tiffg4"
astrArgs(6) = "-r300"
astrArgs(7) = "-sOutputFile=" & tmpFolder & "\%03d.tiff"
astrArgs(8) = "-dDEVICEXRESOLUTION=204"
astrArgs(9) = "-dDEVICEYRESOLUTION=196"
astrArgs(10) = "-dDEVICEWIDTH=1686"
astrArgs(11) = "-dDEVICEHEIGHT=2292"
astrArgs(12) = "-dNOPLATFONTS"
astrArgs(13) = "-sFONTPATH=""c:\psfonts\"""
astrArgs(14) = "-c ""<< /Policies << /PageSize 5 >> /PageSize [595 842]/InputAttributes currentpagedevice /InputAttributes get mark exch {1index /Priority eq not {pop << /PageSize [595 842] >>} if } forall >>setpagedevice"" -f"""
astrArgs(15) = "-f"
astrArgs(16) = pdfSource
If CallGS(astrArgs) Then
OutputToTiff(tifPath)
End If

End Function

Private Function InteractiveGS() As Boolean
Dim astrArgs(2) As String
astrArgs(0) = "gs" 'The First Parameter is Ignored
astrArgs(1) = "-c"
astrArgs(2) = "systemdict /start get exec"
Return CallGS(astrArgs)
End Function


'------------------------------------------------
'User Defined Functions End
'------------------------------------------------

'Sub Main()
' ConvertFile()
' 'InteractiveGS()
' MsgBox("Done")
'End Sub

#Region "multi-pages tiff generation"
Private Function saveMultipage(ByVal bmp() As Image, ByVal location As String, ByVal type As String) As Boolean
If Not bmp Is Nothing Then
Try
Dim codecInfo As ImageCodecInfo = getCodecForstring(type)


For i As Integer = 0 To bmp.Length - 1
If bmp(i) Is Nothing Then
Exit For
End If
bmp(i) = CType(ConvertToBitonal(CType(bmp(i), Bitmap)), Image)
Next

If bmp.Length = 1 Then

Dim iparams As EncoderParameters = New EncoderParameters(1)
Dim iparam As Encoder = Encoder.Compression
Dim iparamPara As EncoderParameter = New EncoderParameter(iparam, CType((EncoderValue.CompressionCCITT4), Long))
iparams.Param(0) = iparamPara
bmp(0).Save(location, codecInfo, iparams)


ElseIf bmp.Length > 1 Then

Dim saveEncoder As Encoder
Dim compressionEncoder As Encoder
Dim SaveEncodeParam As EncoderParameter
Dim CompressionEncodeParam As EncoderParameter
Dim EncoderParams As EncoderParameters = New EncoderParameters(2)

saveEncoder = Encoder.SaveFlag
compressionEncoder = Encoder.Compression

' Save the first page (frame).
SaveEncodeParam = New EncoderParameter(saveEncoder, CType(EncoderValue.MultiFrame, Long))
CompressionEncodeParam = New EncoderParameter(compressionEncoder, CType(EncoderValue.CompressionCCITT4, Long))
EncoderParams.Param(0) = CompressionEncodeParam
EncoderParams.Param(1) = SaveEncodeParam

File.Delete(location)
bmp(0).Save(location, codecInfo, EncoderParams)



For i As Integer = 1 To bmp.Length - 1
If bmp(i) Is Nothing Then
Exit For
End If

SaveEncodeParam = New EncoderParameter(saveEncoder, CType(EncoderValue.FrameDimensionPage, Long))
CompressionEncodeParam = New EncoderParameter(compressionEncoder, CType(EncoderValue.CompressionCCITT4, Long))
EncoderParams.Param(0) = CompressionEncodeParam
EncoderParams.Param(1) = SaveEncodeParam
bmp(0).SaveAdd(bmp(i), EncoderParams)
bmp(i).Dispose()
Next

SaveEncodeParam = New EncoderParameter(saveEncoder, CType(EncoderValue.Flush, Long))
EncoderParams.Param(0) = SaveEncodeParam
bmp(0).SaveAdd(EncoderParams)
bmp(0).Dispose()
End If
Return True


Catch ee As System.Exception
Throw New Exception(ee.Message + " Error in saving as multipage ")
End Try
Else
Return False
End If

End Function

Private Function getCodecForstring(ByVal type As String) As ImageCodecInfo
Dim info() As ImageCodecInfo = ImageCodecInfo.GetImageEncoders()

Dim i As Integer
For i = 0 To info.Length - 1 Step i + 1
Dim EnumName As String = type.ToString()
If info(i).FormatDescription.Equals(EnumName) Then
Return info(i)
End If
Next

Return Nothing

End Function

Private Function ConvertToBitonal(ByVal original As Bitmap) As Bitmap
Dim source As Bitmap = Nothing

' If original bitmap is not already in 32 BPP, ARGB format, then convert
If original.PixelFormat <> PixelFormat.Format32bppArgb Then
source = New Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb)
source.SetResolution(original.HorizontalResolution, original.VerticalResolution)
Using g As Graphics = Graphics.FromImage(source)
g.DrawImageUnscaled(original, 0, 0)
End Using
Else
source = original
End If

' Lock source bitmap in memory
Dim sourceData As Imaging.BitmapData = source.LockBits(New Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)

' Copy image data to binary array
Dim imageSize As Integer = sourceData.Stride * sourceData.Height
Dim sourceBuffer(imageSize) As Byte
Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize)

' Unlock source bitmap
source.UnlockBits(sourceData)

' Create destination bitmap
Dim destination As Bitmap = New Bitmap(source.Width, source.Height, PixelFormat.Format1bppIndexed)

' Lock destination bitmap in memory
Dim destinationData As BitmapData = destination.LockBits(New Rectangle(0, 0, destination.Width, destination.Height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed)

' Create destination buffer
imageSize = destinationData.Stride * destinationData.Height
Dim destinationBuffer(imageSize) As Byte

Dim sourceIndex As Integer = 0
Dim destinationIndex As Integer = 0
Dim pixelTotal As Integer = 0
Dim destinationValue As Byte = 0
Dim pixelValue As Integer = 128
Dim height As Integer = source.Height
Dim width As Integer = source.Width
Dim threshold As Integer = 500

' Iterate lines

For y As Integer = 0 To height - 1
sourceIndex = y * sourceData.Stride
destinationIndex = y * destinationData.Stride
destinationValue = 0
pixelValue = 128

' Iterate pixels
For x As Integer = 0 To width - 1
' Compute pixel brightness (i.e. total of Red, Green, and Blue values)
pixelTotal = CType(sourceBuffer(sourceIndex + 1), Integer) + CType(sourceBuffer(sourceIndex + 2), Integer) + CType(sourceBuffer(sourceIndex + 3), Integer)
If pixelTotal > threshold Then
destinationValue += CType(pixelValue, Byte)
End If
If pixelValue = 1 Then
destinationBuffer(destinationIndex) = destinationValue
destinationIndex = destinationIndex + 1
destinationValue = 0
pixelValue = 128
Else
pixelValue = pixelValue >> 1
End If
sourceIndex += 4
Next
If pixelValue <> 128 Then
destinationBuffer(destinationIndex) = destinationValue
End If
Next

' Copy binary image data to destination bitmap
Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, imageSize)

' Unlock destination bitmap
destination.UnlockBits(destinationData)
original.Dispose()
' Return
Return destination
End Function

Private Function Compare(ByVal d1 As FileInfo, ByVal d2 As FileInfo) As Integer
Return d1.Name.CompareTo(d2.Name)
End Function

Private Sub SortByName(ByVal files As FileInfo())
Array.Sort(Of FileInfo)(files, New Comparison(Of FileInfo)(AddressOf Compare))

End Sub

#End Region

End Module