/// <summary> /// Function to save the specified image file. /// </summary> /// <param name="name">The name of the file to write into.</param> /// <param name="image">The image to save.</param> /// <param name="pixelFormat">The pixel format for the image.</param> /// <param name="codec">[Optional] The codec to use when saving the image.</param> /// <returns>The updated working file.</returns> public IGorgonVirtualFile SaveImageFile(string name, IGorgonImage image, BufferFormat pixelFormat, IGorgonImageCodec codec = null) { if (codec == null) { codec = DefaultCodec; } // We absolutely need to have an extension, or else the texconv tool will not work. if ((codec.CodecCommonExtensions.Count > 0) && (!string.Equals(Path.GetExtension(name), codec.CodecCommonExtensions[0], StringComparison.OrdinalIgnoreCase))) { _log.Print("Adding extension to working file or else external tools may not be able to read it.", LoggingLevel.Verbose); name = Path.ChangeExtension(name, codec.CodecCommonExtensions[0]); } IGorgonVirtualFile workFile = ScratchArea.FileSystem.GetFile(name); var formatInfo = new GorgonFormatInfo(pixelFormat); // The file doesn't exist, so we need to create a dummy file. if (workFile == null) { using (Stream tempStream = ScratchArea.OpenStream(name, FileMode.Create)) { tempStream.WriteString("TEMP_WORKING_FILE"); } workFile = ScratchArea.FileSystem.GetFile(name); } _log.Print($"Working image file: '{workFile.FullPath}'.", LoggingLevel.Verbose); IGorgonVirtualFile result; // For compressed images, we need to rely on an external tool to do the job. if (formatInfo.IsCompressed) { _log.Print($"Pixel format [{pixelFormat}] is a block compression format, compressing using external tool...", LoggingLevel.Intermediate); if ((_compressor == null) || (!codec.SupportsBlockCompression)) { throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format)); } // Send the image data as uncompressed to our working file, so we can have something to compress. using (Stream outStream = ScratchArea.OpenStream(workFile.FullPath, FileMode.Create)) { codec.SaveToStream(image, outStream); } _log.Print($"Saving to working file '{workFile.FullPath}'...", LoggingLevel.Simple); result = _compressor.Compress(workFile, pixelFormat, image.MipCount); // Convert to an uncompressed format if we aren't already in that format. switch (pixelFormat) { case BufferFormat.BC5_SNorm when image.Format != BufferFormat.R8G8_SNorm: image.ConvertToFormat(BufferFormat.R8G8_SNorm); break; case BufferFormat.BC5_Typeless when image.Format != BufferFormat.R8G8_UNorm: case BufferFormat.BC5_UNorm when image.Format != BufferFormat.R8G8_UNorm: image.ConvertToFormat(BufferFormat.R8G8_UNorm); break; case BufferFormat.BC6H_Sf16 when image.Format != BufferFormat.R16G16B16A16_Float: case BufferFormat.BC6H_Typeless when image.Format != BufferFormat.R16G16B16A16_Float: case BufferFormat.BC6H_Uf16 when image.Format != BufferFormat.R16G16B16A16_Float: image.ConvertToFormat(BufferFormat.R16G16B16A16_Float); break; case BufferFormat.BC4_SNorm when image.Format != BufferFormat.R8G8_SNorm: image.ConvertToFormat(BufferFormat.R8_SNorm); break; case BufferFormat.BC4_Typeless when image.Format != BufferFormat.R8G8_UNorm: case BufferFormat.BC4_UNorm when image.Format != BufferFormat.R8G8_UNorm: image.ConvertToFormat(BufferFormat.R8_UNorm); break; case BufferFormat.BC1_Typeless when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC1_UNorm when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC2_Typeless when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC2_UNorm when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC3_Typeless when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC3_UNorm when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC7_Typeless when image.Format != BufferFormat.R8G8B8A8_UNorm: case BufferFormat.BC7_UNorm when image.Format != BufferFormat.R8G8B8A8_UNorm: image.ConvertToFormat(BufferFormat.R8G8B8A8_UNorm); break; case BufferFormat.BC1_UNorm_SRgb when image.Format != BufferFormat.R8G8B8A8_UNorm_SRgb: case BufferFormat.BC2_UNorm_SRgb when image.Format != BufferFormat.R8G8B8A8_UNorm_SRgb: case BufferFormat.BC3_UNorm_SRgb when image.Format != BufferFormat.R8G8B8A8_UNorm_SRgb: case BufferFormat.BC7_UNorm_SRgb when image.Format != BufferFormat.R8G8B8A8_UNorm_SRgb: image.ConvertToFormat(BufferFormat.R8G8B8A8_UNorm_SRgb); break; } if (result == null) { throw new GorgonException(GorgonResult.CannotRead, string.Format(Resources.GORIMG_ERR_COMPRESSED_FILE, formatInfo.Format)); } } else { // We've changed the pixel format, so convert prior to saving. if (pixelFormat != image.Format) { _log.Print($"Image pixel format [{image.Format}] is different than requested format of [{pixelFormat}], converting...", LoggingLevel.Intermediate); image.ConvertToFormat(pixelFormat); _log.Print($"Converted image '{workFile.Name}' to pixel format: [{pixelFormat}].", LoggingLevel.Simple); } _log.Print($"Saving to working file '{workFile.FullPath}'...", LoggingLevel.Simple); using (Stream outStream = ScratchArea.OpenStream(workFile.FullPath, FileMode.Create)) { codec.SaveToStream(image, outStream); } ScratchArea.FileSystem.Refresh(); result = ScratchArea.FileSystem.GetFile(workFile.FullPath); } return(result); }
/// <summary>Function to save the atlas data.</summary> /// <param name="atlas">The atlas data to save.</param> public void SaveAtlas(IReadOnlyDictionary <IContentFile, GorgonSprite> spriteFiles, GorgonTextureAtlas atlas) { IGorgonImage image = null; string directory = Path.GetDirectoryName(atlas.Textures[0].Texture.Name).FormatDirectory('/'); Stream outStream = null; try { _fileSystem.BeginBatch(); if (!_fileSystem.DirectoryExists(directory)) { _fileSystem.CreateDirectory(directory); } void WriteImageFile(Stream stream) => _defaultImageCodec.SaveToStream(image, stream); // Check the files to ensure they're not open for editing. foreach (GorgonTexture2DView texture in atlas.Textures) { IContentFile textureFile = _fileSystem.GetFile(texture.Texture.Name); if ((textureFile != null) && (textureFile.IsOpen)) { throw new IOException(string.Format(Resources.GORTAG_ERR_IMAGE_OPEN, textureFile.Path)); } } foreach ((GorgonSprite original, GorgonSprite sprite) in atlas.Sprites) { IContentFile oldFile = spriteFiles.FirstOrDefault(item => item.Value == original).Key; if ((oldFile != null) && (oldFile.IsOpen)) { throw new IOException(string.Format(Resources.GORTAG_ERR_IMAGE_OPEN, oldFile.Path)); } } // Write textures. foreach (GorgonTexture2DView texture in atlas.Textures) { image = texture.Texture.ToImage(); _fileSystem.WriteFile(texture.Texture.Name, WriteImageFile); image.Dispose(); } // Write out the updated sprites. foreach ((GorgonSprite original, GorgonSprite sprite) in atlas.Sprites) { IContentFile oldTextureFile = null; IContentFile textureFile = _fileSystem.GetFile(sprite.Texture.Texture.Name); IContentFile oldFile = spriteFiles.FirstOrDefault(item => item.Value == original).Key; if (oldFile.Metadata.DependsOn.TryGetValue(CommonEditorContentTypes.ImageType, out string oldTexturePath)) { oldTextureFile = _fileSystem.GetFile(oldTexturePath); } outStream = oldFile.OpenWrite(); _defaultSpriteCodec.Save(sprite, outStream); textureFile.LinkContent(oldFile); oldTextureFile?.UnlinkContent(oldFile); oldFile.RefreshMetadata(); oldFile.Refresh(); outStream.Dispose(); } } finally { outStream?.Dispose(); image?.Dispose(); _fileSystem.EndBatch(); } }