Build a .NET Core Image Conversion MicroService with LEADTOOLS

Introduction

This post describes how to use LEADTOOLS (Windows or Linux) in .NET Core to convert almost any image to a PNG. This service can be used to display non-standard files in any modern web browser without installing plug-ins or third party code on the client.

Background

If you follow this tutorial, the resulting project demonstrates a quick and easy way to use 3rd party C API in .NET Core using DLLImport. Specifically, this project uses LEADTOOLS DLL/SO C API in .NET Core to convert files to PNG. Native .NET Core support is coming to LEADTOOLS, so keep this blog on your watch list or follow @LEADTOOLS on Twitter.

Software Prerequisites

.NET Core development on Windows with Visual Studio requires:

  • A supported version of the Windows client or operating system
  • Visual Studio 2015 Update 3.3 or later
  • NuGet Manager extension for Visual Studio
  • .NET Core Tooling Preview 2

The sample project depends on the following 3rd-party libraries:

About the Code

This project is an ASP.NET Core application that will convert any LEADTOOLS supported image formats to PNG. This same application can be deployed to Windows or Linux.

Create new ASP.NET Core Web Application Project

Create a new ASP.NET > ASP.NET Core Web Application

Select the Web API template

Add LEADTOOLS License File as an Embedded Resource

  1. Press Shift+Alt+A to add an existing item to the project. (or right-click the project and select Add > Existing Item…)
    Your LEADTOOLS license should be in the …\common\license folder of your LEADTOOLS installation folder

  2. Update the buildOptions section of the Project.json to embed the resource.
Example
      "buildOptions": {
         "emitEntryPoint": true,
         "preserveCompilationContext": true,
         "embed": [ "LEADTOOLS.LIC" ]
      },

Interop for the C API Library

The LTInterop class is the bridge between native C API LEADTOOLS Libraries and .NET Core. Below are three internal classes that correspond to Windows x86, Windows x64, and Linux (The Linux class is used for x86 and x64). These classes declare the correct extern of the C API for that platform. The static constructor in LTInterop will dynamically determine which platform the application is running and assign its delegate fields to the correct extern function.

Press Shift+Alt+C to create a new class file named LTInterop.cs and add the code from below. Make sure to update WinX64BinPath and WinX86BinPath (marked below) with the path to the LEADTOOLS CDLLVC10 dlls.

CS Code
using System;
using System.Runtime.InteropServices;
namespace ImageConvertService
{
   public static class LTInterop
   {
      // Update the following with the path to the LEADTOOLS CDLLVC10 DLLs.
      private const string WinX64BinPath = @"LEADTOOLS_BIN_CDLLVC10_x64_PATH_HERE";
      private const string WinX86BinPath = @"LEADTOOLS_BIN_CDLLVC10_Win32_PATH_HERE";
      static LTInterop()
      {
         var architecture = RuntimeInformation.OSArchitecture;
         if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
         {
            var oldPath = System.Environment.GetEnvironmentVariable("Path");
            if (architecture == Architecture.X86)
            {
               Environment.SetEnvironmentVariable("Path", WinX86BinPath + "; " + oldPath);
               Winx86.Apply();
            }
            else if (architecture == Architecture.X64)
            {
               Environment.SetEnvironmentVariable("Path", WinX64BinPath + "; " + oldPath);
               Winx64.Apply();
            }
         }
         else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
         {
            Linux.Apply();
         }
         else
         {
            throw new InvalidOperationException("Unsupported architecture");
         }
      }
      // Need to call one of the SetLicense functions only once.
      public delegate int _L_SetLicenseFile(string licenseFile, string developerKey);
      public static _L_SetLicenseFile L_SetLicenseFile;
      public delegate int _L_SetLicenseBuffer(byte[] pLicenseBuffer, IntPtr nSize, string pszDeveloperKey);
      public static _L_SetLicenseBuffer L_SetLicenseBuffer;
      public delegate int _L_FileConvert(string sourceFile, string destFile, int format, int width, int height, int bitsPerPixel, int qfactor, IntPtr loadOptions, IntPtr saveOptions, IntPtr fileInfo);
      public static _L_FileConvert L_FileConvert;
      private static class Winx86
      {
         private const string _ltkrn = "ltkrnu.dll";
         private const string _ltfil = "ltfilu.dll";
         private const CharSet _charset = CharSet.Unicode;
         [DllImport(_ltkrn, CharSet = _charset)]
         public static extern int L_SetLicenseFile(string licenseFile, string developerKey);
         [DllImport(_ltkrn, CharSet = _charset)]
         public static extern int L_SetLicenseBuffer(byte[] pLicenseBuffer, IntPtr nSize, string pszDeveloperKey);
         [DllImport(_ltfil, CharSet = _charset)]
         public static extern int L_FileConvert(string sourceFile, string destFile, int format, int width, int height, int bitsPerPixel, int qfactor, IntPtr loadOptions, IntPtr saveOptions, IntPtr fileInfo);
         
         public static void Apply()
         {
            LTInterop.L_SetLicenseFile = L_SetLicenseFile;
            LTInterop.L_SetLicenseBuffer = L_SetLicenseBuffer;
            LTInterop.L_FileConvert = L_FileConvert;
         }
      }
      private static class Winx64
      {
         private const string _ltkrn = @"ltkrnx.dll";
         private const string _ltfil = @"ltfilx.dll";
         private const CharSet _charset = CharSet.Unicode;
         [DllImport(_ltkrn, CharSet = _charset)]
         public static extern int L_SetLicenseFile(string licenseFile, string developerKey);
         [DllImport(_ltkrn, CharSet = _charset)]
         public static extern int L_SetLicenseBuffer(byte[] pLicenseBuffer, IntPtr nSize, string pszDeveloperKey);
         [DllImport(_ltfil, CharSet = _charset)]
         public static extern int L_FileConvert(string sourceFile, string destFile, int format, int width, int height, int bitsPerPixel, int qfactor, IntPtr loadOptions, IntPtr saveOptions, IntPtr fileInfo);
         
         public static void Apply()
         {
            LTInterop.L_SetLicenseFile = L_SetLicenseFile;
            LTInterop.L_SetLicenseBuffer = L_SetLicenseBuffer;
            LTInterop.L_FileConvert = L_FileConvert;
         }
      }
      private static class Linux
      {
         private const string _ltkrn = "libltkrn.so";
         private const string _ltfil = "libltfil.so";
         private const CharSet _charset = CharSet.Ansi;
         [DllImport(_ltkrn, CharSet = _charset)]
         public static extern int L_SetLicenseFileA(string licenseFile, string developerKey);
         [DllImport(_ltkrn, CharSet = _charset)]
         public static extern int L_SetLicenseBufferA(byte[] pLicenseBuffer, IntPtr nSize, string pszDeveloperKey);
         [DllImport(_ltfil, CharSet = _charset)]
         public static extern int L_FileConvertA(string sourceFile, string destFile, int format, int width, int height, int bitsPerPixel, int qfactor, IntPtr loadOptions, IntPtr saveOptions, IntPtr fileInfo);
         
         public static void Apply()
         {
            LTInterop.L_SetLicenseFile = L_SetLicenseFileA;
            LTInterop.L_SetLicenseBuffer = L_SetLicenseBufferA;
            LTInterop.L_FileConvert = L_FileConvertA;
         }
      }
   }
}

Set the LEADTOOLS License

Add the following method to Program.cs and call it first in your Main method.

private static void UnlockLeadtools()
{
   // TODO: Update with your LEADTOOLS developer key
   var key = @"Copy your LEADTOOLS license key here";
   var assembly = Assembly.GetEntryAssembly();
   
   // TODO: Make sure the namespace and resource name matches. Reminder: Case Sensitive - even the license file name!!!!
   var licStream = assembly.GetManifestResourceStream("ImageConvertService" + "." + "LEADTOOLS License file name here");
   if (licStream == null)
      throw new FileNotFoundException("Could not load license file from resource stream.");
   using (var ms = new MemoryStream())
   {
      licStream.CopyTo(ms);
      var ret = LTInterop.L_SetLicenseBuffer(ms.ToArray(), new IntPtr(ms.Length), key);
      if (ret != 1)
         throw new Exception("Error setting LEADTOOLS License : " + ret);
   }
}

Add the Imaging Controller

This class contains the convert method that takes a URL to an image or document as a parameter, downloads it to a temp file, converts it to PNG, then return the PNG file.

  1. Press Shift+Alt+C to create a new class file named ImagingController in the “Controllers” folder.
  2. Add the following code below.
Code Details
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace ImageConvertService.Controllers
{
    [Route("api/[controller]")]
    public class ImagingController : Controller
    {
        public async Task Get(Uri url)
        {
            const int FILE_PNG = 75; // From ltfil.h
            const string mimeType = "image/png";

            if (url == null)
                return Content("url cannot be null");
            if (!url.IsAbsoluteUri)
                return Content("url must be absolute. Make sure to include the protocol.");

            string sourceFile = null;
            string targetFile = null;

            try
            {
                // Download image to a temp file.
                sourceFile = Path.GetTempFileName();
                using (var client = new HttpClient())
                using (var request = new HttpRequestMessage(HttpMethod.Get, url.ToString()))
                using (Stream contentStream = await (await client.SendAsync(request)).Content.ReadAsStreamAsync())
                using (Stream fileStream = new FileStream(sourceFile, FileMode.Create, FileAccess.Write, FileShare.None, 3145728, true))
                {
                    await contentStream.CopyToAsync(fileStream);
                }

                // Call native L_FileConvert
                // https://www.leadtools.com/help/leadtools/v19/main/api/l_fileconvert.html
                targetFile = Path.GetTempFileName();
                int ret = LTInterop.L_FileConvert(sourceFile, targetFile, FILE_PNG, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
                if (ret != 1)
                    return Content("Error Converting File : " + ret);

                // Load converted file and return.
                byte[] buffer = System.IO.File.ReadAllBytes(targetFile);
                return File(buffer, mimeType);
            }
            finally
            {
                // Clean up
                if (sourceFile != null && System.IO.File.Exists(sourceFile))
                    System.IO.File.Delete(sourceFile);
                if (targetFile != null && System.IO.File.Exists(targetFile))
                    System.IO.File.Delete(targetFile);
            }
        }
    }
}      

Testing the service on Windows

  1. Run the service from Visual Studio. A web browser should open to the URL set in Project Properties > Debug > Launch URL & App URL
  2. Call the web method.
    • DICOM example: http://localhost:63077/api/Imaging/convert?url=http://demo.leadtools.com/images/dcm/image2.dcm
    • TIFF example: http://localhost:63077/api/Imaging/convert?url=http://demo.leadtools.com/images/tiff/ocr1.tif
A DICOM image converted to PNG

A TIFF image converted to PNG

Deploying to Linux

Prerequisites

  • Make sure you have .NET Core installed on your Linux machine.
  • Also, make sure you have the LEADTOOLS Linux binaries copied to your machine.

Publish your Project

  1. Right click on your project > Publish
  2. Enter an name for your Publish Profile
  3. Publish your project to a folder on your machine
  4. Copy the output to your Linux machine
Screen shots

Run your Project on Linux

We will be running this application from the command prompt and will be setting the LD_LIBRARY_PATH environment variable to the path of our LEADTOOLS native libraries with the command below:

         LD_LIBRARY_PATH=<Path to the LEADTOOLS Linux libraries>:$LD_LIBRARY_PATH dotnet <Path to .NET Core application DLL>

For Example:

         LD_LIBRARY_PATH=/home/torvalds/LEADTOOLS19/Bin/Lib/x64/:$LD_LIBRARY_PATH dotnet LEADTOOLS_NetCore_ImageConvertService.dll

Possible Future Improvements

  • Use redirection to directly load and save to a stream.

Troubleshoot

.NET Core 1.1.0

This project was built with .NET Core 1.0.1. If you use .NET Core 1.1.0, then it is possible that you will get the error below.

Can not find runtime target for framework

To solve the issue, update the project.json file to include a runtimes section as shown below.

Add runtimes section to project.json

History

  • 5 Jan 2017: Added information for .NET Core 1.1
  • 4 Jan 2017: Initial version

About 

Developer Advocate

This entry was posted in General Imaging and tagged , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *