diff --git a/GUI/GarExtract.cs b/GUI/GarExtract.cs index a216ddb7..c6c406bd 100644 --- a/GUI/GarExtract.cs +++ b/GUI/GarExtract.cs @@ -306,31 +306,35 @@ namespace GARbro.GUI void ExtractImage (ArcFile arc, Entry entry, ImageFormat target_format) { - using (var file = arc.OpenBinaryEntry (entry)) + try { - var src_format = ImageFormat.FindFormat (file); - if (null == src_format) - throw new InvalidFormatException (string.Format ("{1}: {0}", guiStrings.MsgUnableInterpretImage, entry.Name)); - file.Position = 0; - string target_ext = target_format.Extensions.FirstOrDefault() ?? ""; - string outname = FindUniqueFileName (entry.Name, target_ext); - if (src_format.Item1 == target_format) + using (var decoder = arc.OpenImage (entry)) { - // source format is the same as a target, copy file as is - using (var output = ArchiveFormat.CreateFile (outname)) - file.AsStream.CopyTo (output); - return; - } - ImageData image = src_format.Item1.Read (file, src_format.Item2); - if (m_adjust_image_offset) - { - image = AdjustImageOffset (image); - } - using (var outfile = ArchiveFormat.CreateFile (outname)) - { - target_format.Write (outfile, image); + var src_format = decoder.Format; // could be null + string target_ext = target_format.Extensions.FirstOrDefault() ?? ""; + string outname = FindUniqueFileName (entry.Name, target_ext); + if (src_format == target_format) + { + // source format is the same as a target, copy file as is + using (var output = ArchiveFormat.CreateFile (outname)) + decoder.Input.CopyTo (output); + return; + } + ImageData image = decoder.Image; + if (m_adjust_image_offset) + { + image = AdjustImageOffset (image); + } + using (var outfile = ArchiveFormat.CreateFile (outname)) + { + target_format.Write (outfile, image); + } } } + catch + { + throw new InvalidFormatException (string.Format ("{1}: {0}", guiStrings.MsgUnableInterpretImage, entry.Name)); + } } static ImageData AdjustImageOffset (ImageData image) diff --git a/GUI/ImagePreview.cs b/GUI/ImagePreview.cs index f1caab06..75c8bb1a 100644 --- a/GUI/ImagePreview.cs +++ b/GUI/ImagePreview.cs @@ -225,13 +225,9 @@ namespace GARbro.GUI { try { - using (var file = VFS.OpenBinaryStream (preview.Entry)) + using (var data = VFS.OpenImage (preview.Entry)) { - var data = ImageFormat.Read (file); - if (null != data) - SetPreviewImage (preview, data.Bitmap); - else - Trace.WriteLine ("Cannot parse image format", preview.Name); + SetPreviewImage (preview, data.Image.Bitmap); } } catch (Exception X) diff --git a/GameRes/ArcFile.cs b/GameRes/ArcFile.cs index d0d060ec..7afc2d31 100644 --- a/GameRes/ArcFile.cs +++ b/GameRes/ArcFile.cs @@ -217,6 +217,11 @@ namespace GameRes return BinaryStream.FromStream (input, entry.Name); } + public IImageDecoder OpenImage (Entry entry) + { + return m_interface.OpenImage (this, entry); + } + public ArchiveFileSystem CreateFileSystem () { if (m_interface.IsHierarchic) diff --git a/GameRes/FileSystem.cs b/GameRes/FileSystem.cs index 96ad69a5..879f00c2 100644 --- a/GameRes/FileSystem.cs +++ b/GameRes/FileSystem.cs @@ -722,6 +722,17 @@ namespace GameRes return m_vfs.Top.OpenView (entry); } + public static IImageDecoder OpenImage (Entry entry) + { + var fs = m_vfs.Top; + var arc_fs = fs as ArchiveFileSystem; + if (arc_fs != null) + return arc_fs.Source.OpenImage (entry); + + var input = fs.OpenBinaryStream (entry); + return new ImageStreamDecoder (input); + } + public static Stream OpenStream (string filename) { return m_vfs.Top.OpenStream (m_vfs.Top.FindFile (filename)); diff --git a/GameRes/GameRes.cs b/GameRes/GameRes.cs index c8da1a68..9de0bf48 100644 --- a/GameRes/GameRes.cs +++ b/GameRes/GameRes.cs @@ -197,6 +197,15 @@ namespace GameRes return arc.File.CreateStream (entry.Offset, entry.Size, entry.Name); } + /// + /// Open as image. Throws InvalidFormatException if entry is not an image. + /// + public virtual IImageDecoder OpenImage (ArcFile arc, Entry entry) + { + var input = arc.OpenBinaryEntry (entry); + return new ImageStreamDecoder (input); + } + /// /// Create file corresponding to in current directory and open it /// for writing. Overwrites existing file, if any. diff --git a/GameRes/Image.cs b/GameRes/Image.cs index 6a9e5d73..06d5d9b3 100644 --- a/GameRes/Image.cs +++ b/GameRes/Image.cs @@ -186,4 +186,70 @@ namespace GameRes public static ImageFormat Bmp { get { return s_BmpFormat.Value; } } public static ImageFormat Tga { get { return s_TgaFormat.Value; } } } + + public interface IImageDecoder : IDisposable + { + Stream Input { get; } + + /// + /// Underlying image format or null if image is not represented by any format. + /// + ImageFormat Format { get; } + + /// + /// Image parameters. + /// + ImageMetaData Info { get; } + + /// + /// Decoded image data. + /// + ImageData Image { get; } + } + + public sealed class ImageStreamDecoder : IImageDecoder + { + IBinaryStream m_file; + ImageFormat m_format; + ImageMetaData m_info; + ImageData m_image; + + public Stream Input { get { m_file.Position = 0; return m_file.AsStream; } } + + public ImageFormat Format { get { return m_format; } } + public ImageMetaData Info { get { return m_info; } } + + public ImageData Image + { + get + { + if (null == m_image) + { + m_file.Position = 0; + m_image = m_format.Read (m_file, m_info); + } + return m_image; + } + } + + public ImageStreamDecoder (IBinaryStream file) + { + m_file = file; + var format = ImageFormat.FindFormat (file); + if (null == format) + throw new InvalidFormatException(); + m_format = format.Item1; + m_info = format.Item2; + } + + bool m_disposed = false; + public void Dispose () + { + if (!m_disposed) + { + m_file.Dispose(); + m_disposed = true; + } + } + } }