65.9K
CodeProject 正在变化。 阅读更多。
Home

更快的 JPEG2000 查看器

2014年1月6日

CPOL

6分钟阅读

viewsIcon

30636

downloadIcon

1151

使用 Kakadu 可执行演示包进行解码的简单 .jp2/.j2k 查看器

引言

目前大多数免费图像查看器都可以显示 JPEG2000 文件(例如 XnView、Irfan、FastStone、DIMIN 等),但它们的速度都慢得令人痛苦。读取一张 1000 万像素的图像可能需要大约 10 秒(取决于单线程 CPU 性能)。

 

通常使用两种开源 JPEG2000 实现(Jasper 和 OpenJPEG)或商业 Lura 的史前版本(在 XnView 中)。所有这些实现都是单线程的,并且没有进行速度优化。曾有人尝试创建 GPU 加速的 OpenJPEG (NVidia CUDA 或 OpenCL),但到目前为止只有编码器 (cuj2k) 可用。

这是一个尝试创建一个能够更快工作的这种格式的简单图像查看器。与上述免费库不同,这里使用的是全球领先的 JPEG2000 SDK 制造商 Kakadu Software 的演示可执行文件,因为其许可证并未禁止此类使用。

版权归新南威尔士大学(澳大利亚悉尼)的商业部门 NewSouth Innovations Pty Limited 所有。您可以免费试用这些可执行文件,甚至可以重新分发它们,只要此类使用或重新分发附有此版权声明且不用于商业用途。注意:二进制文件只能用于非商业目的。如有疑问,请联系 Taubman 博士。

背景

如果感兴趣,您可以访问 jpeg.org 了解此图像标准。

JPEG2000 旨在替代非常古老但仍几乎独占使用的 JPEG (.jpg) 有损图像格式。它提供了无损模式、Alpha 通道、超过 8 位/通道等额外功能,并且通常能产生更好的质量,因为它不是产生臭名昭著的 JPEG 伪影和颜色缺陷,而是选择性地使某些图像部分模糊。

由于编码和解码速度慢得惊人、标准/兼容性问题(一个解码器无法读取另一个编码器生成的图像)、几乎没有硬件支持以及结果有时比标准 JPEG 更差(例如,模糊的树枝比其周围的伪影更明显),这种格式并未取得预期成功。

尽管如此,JPEG2000 在许多领域仍有大量用户。大多数图形软件都可以生成 JPEG2000 文件。Kakadu 演示可执行文件包还为 BMP 和未压缩的 TIFF 提供了非常快速和高质量的编码器。

使用程序

这不仅适用于程序员,也适用于所有稍微高级的 Windows 用户

 

  • 假设您已安装 .NET 4 或更高版本。
  • 在本文中,您可以下载完整的 JPEG2000 查看器可执行文件,不包括 Kakadu 演示可执行文件包,您可以在其制造商的网站上找到它。
  • 将可执行文件放置在所需位置,并将 .jp2 和/或 .j2k 文件与其关联(您应该需要使用浏览按钮来找到它)。
  • 安装 Kakadu 演示包。
  • 检查随附的注册表文件 (viewer2000.reg)(在记事本等中),如果需要,修改 kdu_expand.exe 路径。运行注册表文件。
  • 可选地,安装一个内存盘(例如 SoftPerfect 内存盘等)并在注册表文件中设置其路径。

 

如果一切正常,双击 .jp2/.j2k 文件应在新查看器中显示它。

控件

使用了常见的键盘快捷键,例如在 XnView 中已知。

 

  • PageUpPageDownHomeEnd 用于在目录中导航 JPEG2000 文件。
  • / - 最佳适应窗口(这是默认设置)
  • * - 1:1 查看 - 在此模式下,您可以用鼠标左键拖动图像进行滚动,并使用鼠标滚轮进行放大/缩小
  • Esc 退出程序

 

其他特性

 

  • 全屏显示,无窗口栏和任务栏
  • 文件名显示在左上角

 

Using the Code

对于想要自定义查看器的人,您将需要 VS 2010 Express 及更高版本。代码的一些解释。

MainWindow.xaml

图像上的拖动和缩放支持取自这篇很棒的 CodeProject 文章。您可以在那里查看其工作原理的详细信息。

        <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> 

scrollviewer 跨越两个网格线以全屏显示图像,但标签必须在一个狭窄的独立网格线中,因为它位于 scrollviewer 的上方并消耗鼠标事件。

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

MainWindow.xaml.cs(代码背后)

除了构造函数 MainWindow() 中的一些检查和初始化外,还需要提及一些事情。

控件在这里进行数据绑定。这不是最佳实践,但对于像这样的极简应用程序来说没问题。

this.DataContext = this; 

保存缩放值的隐藏滑块设置为其中间位置,这意味着 1:1 缩放。因此,在按下 * 键后,用户可以从该显示中放大/缩小。

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

Expand() 方法中,调用 kdu_expand.exe,只带两个参数 - 要解码的 JPEG2000 文件和将加载并显示的临时位图文件名。

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; 

新增 (2014-02-01)

如果由于不兼容的 JPEG2000 文件而导致解码失败,则 kdu_expand.exeSTDERR 将显示在消息框中,并且在 ClearTemporaryImage() 方法中,先前解码的文件将替换为空白位图。
注意:我发现特别是 Jasper 1.700 编码的文件在通道定义方面存在某种程度的错误。不幸的是,kdu_expand 会因此而遇到问题,您必须使用其他查看器。

可以使用 SSSE3 版本 kdu_buffered_expand.exe,它甚至比 kdu_expand.exe 更快。这是通过包含的注册表文件设置的,现在是默认设置,因为大多数人都拥有该扩展指令集。

图像按需解码。如果存在预测逻辑(提前解码下一张图像并将在内存中解码的上一张图像保留),则从用户的角度来看,查看器性能可能会更好。这尚未实现,因为它应该与查看方向配合工作,考虑文件设置位置并使用工作线程,这将使代码更加复杂。

OnPropertyChanged("ImageSource"); 

已解码的 .BMP 图像已替换的事实是从 PerformChange() 方法外部通知的,因为 ImageSource 属性没有 setter。从技术上讲,如果 ImageSource 属性是 string,返回 .BMP 的 URI(路径),那就足够了。然而,存在一个已知错误(或者至少是 WPF 的一个奇怪行为),即它会阻止对位图源文件的访问,因为它曾被用作图像源。解决此行为的方法是使用 FileStream 代替

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

图像必须具有 96dpi 分辨率才能使用 WPF 正确显示。由于 kdu_expand.exe 未设置此值,我们必须在代码中进行设置。

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);
 }  

Stretch 属性用于在最佳适应模式和 1:1 模式之间切换。它在 Window_KeyDown 事件处理程序中设置。每种模式都需要以不同的方式设置包含图像的 ScrollViewer 滚动条。为了简单起见,它是直接完成的(不是最佳实践)。

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;  

注意:在统一拉伸(最佳适应)模式下,我们需要禁用滚动条以防止 ScrollViewer 为图像提供无限空间,从而强制图像调整大小。在 1:1 视图下,我们需要滚动功能,但我们不希望滚动条可见——用户用鼠标拖动图像。

#region SCROLL_ZOOM 

遵循上述 CodeProject 文章 中的逻辑。

新增 (2015-03-06)

查看器现在还支持其他常见的图像类型

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

这样,您可以将其用作包含各种图像类型的文件夹的简单图像查看器。

如果图像不需要通过 kdu_expand 解码(其扩展名在支持的类型中,但不是 .jp2 或 .j2k),则会跳过 Expand() 方法中的解码。在这种情况下,ImageSource 属性使用原始图像,因为它 natively supported by .NET。

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

 

关注点

 

 

下面提供了一些更有趣的链接

© . All rights reserved.