更快的 JPEG2000 查看器






4.50/5 (2投票s)
使用 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
中已知。
- PageUp、PageDown、Home、End 用于在目录中导航 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.exe 的 STDERR
将显示在消息框中,并且在 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;
关注点
下面提供了一些更有趣的链接