Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Faster JPEG2000 Viewer

4.50/5 (2 votes)
6 Jan 2014CPOL6 min read 30.1K   1.1K  
Simple .jp2/.j2k viewer using Kakadu executables demonstration pack for decoding

This article appears in the Third Party Products and Tools section. Articles in this section are for the members only and must not be used to promote or advertise products in any way, shape or form. Please report any spam or advertising.

Introduction

Most of the free image viewers today can display JPEG2000 files (e.g. XnView, Irfan, FastStone, DIMIN, etc.) but all of them are painfully slow in doing so. Reading one 10 Mpix image may take some 10 seconds (depending on the single thread CPU performance).

 

Two OpenSource JPEG2000 implementations (Jasper and OpenJPEG) are commonly used or a prehistoric version of commercial Lura (in XnView). All of these implementations are single threaded and not speed-optimized. There have been attempts to create GPU accelerated OpenJPEG (NVidia CUDA or OpenCL) but only encoder (cuj2k) is available so far.

This is an attempt to create a simple image viewer of this format that would work faster. Instead of the free libraries mentioned, there is used a demonstration executable from the world leading JPEG2000 SDK manufacturer - Kakadu Software since the license does not disallow it.

Copyright is owned by NewSouth Innovations Pty Limited, commercial arm of the University of New South Wales, Sydney, Australia. You are free to trial these executables and even to re-distribute them, so long as such use or re-distribution is accompanied with this copyright notice and is not for commercial gain. Note: Binaries can only be used for non-commercial purposes. If in doubt, please contact Dr. Taubman.

Background

If interested, you may check jpeg.org on this image standard.

JPEG2000 was intended as a replacement to a very old but still almost exclusively used JPEG (.jpg) lossy image format. It offers extra features as lossless mode, alpha channel, more than 8 bits/channel, etc. and usually produces better quality since instead of infamous JPEG artifacts and colour deficiencies, it selectively makes some image parts blurry.

This format did not succeed as expected due to tremendously slower encoding and decoding, problems with standards/compatibility (one decoder fails reading image from another encoder), almost no hardware support and the fact, that the results sometimes are worse than standard JPEG - e.g. blurring tree branches is more visible than artifacts around them.

Nevertheless, JPEG2000 still has lots of users in various areas. Most of graphical software can produce JPEG2000 files. The Kakadu demonstration executables pack also offers very fast and high quality encoder for BMPs and uncompressed TIFFs.

Using the Program

This is also for non-programmers but for all slightly advanced Windows users:

 

  • Suppose you have .NET 4 or higher installed.
  • In this article, you can download the complete JPEG2000 viewer executable without Kakadu demonstration executables pack, which you can find at the website of its manufacturer.
  • Place the executable as desired and associate .jp2 and/or .j2k files with it (you should have to use the browse button to locate it).
  • Install the Kakadu demonstration pack.
  • Check the registry file (viewer2000.reg) included (in Notepad, etc.) and modify the kdu_expand.exe path if needed. Run the registry file.
  • Optionally, install a ramdisk (e.g. SoftPerfect ramdisk etc.) and set its path in the registry file.

 

If everything is OK, double clicking a .jp2/.j2k file shoud display it in a new viewer.

Controls

There are usual keyboard shortcuts used as known e.g. from XnView.

 

  • PageUp, PageDown, Home, End are used to navigate JPEG2000 files in the directory.
  • / - fit best in window (this is a default)
  • * - view 1:1 - in this mode you can left-drag image with your mouse to scroll it and use the mouse wheel to zoom in/out
  • Esc to quit the program

 

Other Features

 

  • Full screen display without window bar and taskbar
  • Filename displayed in the upper left corner

 

Using the Code

For those who want to customize the viewer, you will need VS 2010 Express and above. Some explanations of the code.

MainWindow.xaml

The drag and zoom support on the image was taken from this nice CodeProject artice. There you can check the details on how it works.

XML
        <ScrollViewer Grid.Row="0" Grid.RowSpan="2" x:Name="scrollViewer" 
Focusable="False" ScrollChanged="OnScrollViewerScrollChanged" MouseMove="OnMouseMove" 
MouseLeftButtonUp="OnMouseLeftButtonUp" PreviewMouseLeftButtonUp="OnMouseLeftButtonUp" 
PreviewMouseLeftButtonDown="OnMouseLeftButtonDown" PreviewMouseWheel="OnPreviewMouseWheel">
            <ScrollViewer.Style>
                <Style TargetType="{x:Type ScrollViewer}">
                    <Setter Property="HorizontalScrollBarVisibility" Value="Hidden"/>
                    <Setter Property="VerticalScrollBarVisibility" Value="Hidden"/>
                </Style>
            </ScrollViewer.Style>
            <Image x:Name="img" Source="{Binding ImageSource}" 
            Stretch="{Binding ImageStretch}" >
                <Image.LayoutTransform>
                    <TransformGroup>
                        <ScaleTransform x:Name="scaleTransform"/>
                    </TransformGroup>
                </Image.LayoutTransform>
            </Image>
        </ScrollViewer> 

The scrollviewer is spanned over both grid lines in order to display image on the full screen but the label must be in a narrow separate grid line as it is above the scrollviewer and consumes the mouse events.

XML
<Label Grid.Row="0" Foreground="Red" Content="{Binding ImageLabel}" /> 

MainWindow.xaml.cs (Code Behind)

Besides some checks and initializations in the contructor MainWindow(), a few things should be mentioned.

Controls are databound here. It is not the best practice but it's OK for the minimalistic application like this.

C#
this.DataContext = this; 

Hidden slider that holds the zoom value is set to its middle position and this means the zoom 1:1. Thus, after pressing the * key, the user can zoom in/out from that display.

C#
scaleTransform.ScaleX = 1f;
scaleTransform.ScaleY = 1f;
slider.Value = 10;  

In the Expand() method, the kdu_expand.exe is called with just two arguments - the JPEG2000 file to be decoded and the temporary bitmap file name, that will be loaded and displayed afterwards.

C#
int retCode = 0;
ProcessStartInfo ps = new ProcessStartInfo(kduPath, String.Format("-i \"{0}\" -o \"{1}\"", 
currentFileName, tempImage));
ps.CreateNoWindow = true;
ps.RedirectStandardError = true;
ps.UseShellExecute = false;
Process p = Process.Start(ps);
p.WaitForExit();
retCode = p.ExitCode;

if (retCode == (-1))
{
    MessageBox.Show(String.Format("{0}\n\n{1}", currentFileName, 
    p.StandardError.ReadToEnd()), "Error reading image", 
    MessageBoxButton.OK, MessageBoxImage.Error);
}
 
return retCode; 

New (2014-02-01)

If the decoding fails due to incompatible JPEG2000 file, the kdu_expand.exe STDERR is shown in a message box and a previous decoded file is replaced by a blank bitmap in ClearTemporaryImage() method.
Note: I've found that especially Jasper 1.700 encoded files have somehow malformed channel definition. Unfortunately, kdu_expand stumbles upon it and you have to use other viewer instead.

Instead of kdu_expand.exe, the SSSE3 version kdu_buffered_expand.exe which is even faster, can be used. This is set using the registry file included and it is the default now as most people have that extended instruction set available.

The images are decoded on demand. If there was a predictive logics (decoding the next image in advance and keeping the previous image decoded in the memory), the viewer performance from the user's point of view could be even better. This is not implemented yet as it should work with viewing direction, take into consideration file set position and use the worker thread which would make the code more complicated.

C#
OnPropertyChanged("ImageSource"); 

The fact that decoded .BMP image has been replaced is notified externally from the PerformChange() method because the ImageSource property does not have a setter. Technically, it would be enough, if the ImageSource property was a string, returning the URI (path) of the .BMP. However, there is a known bug (or, at least a weird behaviour of the WPF) that it blocks the access to the bitmap source file since it was once used as an image source. The workaround of this behaviour is in using the FileStream instead:

C#
BitmapImage bi = new BitmapImage();                    
using (FileStream stream = File.OpenRead(_imageSource))
{
    bi.BeginInit();
    bi.CacheOption = BitmapCacheOption.OnLoad;
    bi.StreamSource = stream;
    bi.EndInit();
}  

The images must have 96dpi resolution to be displayed correctly using the WPF. Since the kdu_expand.exe does not set it, we must do so in the code.

C#
public static BitmapSource ConvertBitmapTo96DPI(BitmapImage bitmapImage)
{
    double dpi = 96;
    int width = bitmapImage.PixelWidth;
    int height = bitmapImage.PixelHeight;
    int stride = width * 4; // 4 bytes per pixel
    byte[] pixelData = new byte[stride * height];
    bitmapImage.CopyPixels(pixelData, stride, 0);
    return BitmapSource.Create(width, height, dpi, dpi, PixelFormats.Bgra32, null, pixelData, stride);
 }  

The property Stretch is used for switching between the best fit and 1:1 modes. It is set from the Window_KeyDown event handler. Each mode requires the image containing ScrollViewer scrollbars set in a different way. For simplicity, it is done directly (not the best practice).

C#
case Key.Divide:
    ImageStretch = Stretch.Uniform;
    scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
    scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;                    
    break;
case Key.Multiply:
    ImageStretch = Stretch.None;
    scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
    scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
    slider.Value = 10;
    break;  

Note: With the uniform stretch (best fit), we need to disable the scrollbars in order to prevent the ScrollViewer from supplying the image with an unlimited space and thus force the image to resize. With the 1:1 view, we need the scrolling functionality but we don't want the scrollbars visible - user drags the image with their mouse.

C#
#region SCROLL_ZOOM 

Follows the logic from the CodeProject article mentioned above.

New (2015-03-06)

The viewer now also supports other common image types:

private static List<String> GetExtensions()
{
    List<String> extensions = new List<string>() { "jp2", "j2k", "jpg", "tif", "tiff", "png", "bmp", "hdp", "wdp" };
    return extensions;
}

This way you can use it as a simple image viewer for a folder with various image types.

If an image doesn't need to be decoded by the kdu_expand (its extension is among supported types but not .jp2 or .j2k), the decoding in Expand() method is skipped. In this case ImageSource property uses the original image as it is natively supported by .NET.

if (Path.GetExtension(currentFileName).ToLower() == ".jp2" || Path.GetExtension(currentFileName).ToLower() == ".j2k")
    _imageSource = tempImage;
else
    _imageSource = currentFileName;

 

Points of Interest

 

 

Some more interesting links are given below:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)