/// <summary> /// Loads the thumbnail and display info for the file /// </summary> /// <param name="inputStream">The file data stream</param> /// <param name="fileExtension">The file extension</param> /// <param name="width">The thumbnail width</param> /// <param name="manager">The manager</param> /// <returns>The thumbnail data</returns> public ArchiveFileThumbnailData LoadThumbnail(ArchiveFileStream inputStream, FileExtension fileExtension, int width, IArchiveDataManager manager) { // Load the file GF file = GetFileContent(inputStream, manager); // Load the raw bitmap data RawBitmapData rawBmp = file.GetRawBitmapData(width, (int)(file.Height / ((double)file.Width / width))); var format = rawBmp.PixelFormat == PixelFormat.Format32bppArgb ? PixelFormats.Bgra32 : PixelFormats.Bgr24; // Get a thumbnail source BitmapSource thumbnailSource = BitmapSource.Create(rawBmp.Width, rawBmp.Height, 96, 96, format, null, rawBmp.PixelData, (rawBmp.Width * format.BitsPerPixel + 7) / 8); // Get the thumbnail with the specified size return(new ArchiveFileThumbnailData(thumbnailSource, new DuoGridItemViewModel[] { new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Img_Size)), text: $"{file.Width}x{file.Height}"), new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Img_HasAlpha)), text: new GeneratedLocString(() => $"{file.PixelFormat.SupportsTransparency()}")), new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Img_Mipmaps)), text: $"{file.ExclusiveMipmapCount}"), new DuoGridItemViewModel( header: new ResourceLocString(nameof(Resources.Archive_FileInfo_Format)), text: $"{file.PixelFormat.ToString().Replace("Format_", "")}", minUserLevel: UserLevel.Technical), })); }
/// <summary> /// Converts the file data from the specified format /// </summary> /// <param name="inputFormat">The format to convert from</param> /// <param name="outputFormat">The format to convert to</param> /// <param name="currentFileStream">The current file stream</param> /// <param name="inputStream">The input file data stream to convert from</param> /// <param name="outputStream">The output stream for the converted data</param> /// <param name="manager">The manager</param> public void ConvertFrom(FileExtension inputFormat, FileExtension outputFormat, ArchiveFileStream currentFileStream, ArchiveFileStream inputStream, ArchiveFileStream outputStream, IArchiveDataManager manager) { // Load the bitmap using Bitmap bmp = new(inputStream.Stream); // Load the current file GF gf = GetFileContent(currentFileStream, manager); // IDEA: If bmp is not in supported format, then convert it? RawBitmapData rawBitmapData; // Get the bitmap lock using (BitmapLock bmpLock = new(bmp)) { // Get the raw bitmap data rawBitmapData = new RawBitmapData(bmp.Width, bmp.Height, bmpLock.Pixels, bmp.PixelFormat); // Force the new pixel format to be 888 or 8888 if set to do so if (Services.Data.Archive_GF_ForceGF8888Import) { gf.PixelFormat = gf.PixelFormat.SupportsTransparency() ? GF_Format.Format_32bpp_BGRA_8888 : GF_Format.Format_24bpp_BGR_888; } // Check if the format should be updated for transparency if (Services.Data.Archive_GF_UpdateTransparency != UserData_Archive_GF_TransparencyMode.PreserveFormat) { // NOTE: Only 24 and 32 bpp bitmaps are supported // Check if the imported file is transparent bool?isTransparent = bmp.PixelFormat switch { PixelFormat.Format32bppArgb => (Services.Data.Archive_GF_UpdateTransparency == UserData_Archive_GF_TransparencyMode.UpdateBasedOnPixelFormat || bmpLock.UtilizesAlpha()), PixelFormat.Format24bppRgb => false, _ => null }; // NOTE: Currently only supported for formats with 3 or 4 channels // Check if the format should be updated for transparency if (gf.Channels >= 3 && isTransparent != null) { // Update the format gf.PixelFormat = isTransparent.Value ? GF_Format.Format_32bpp_BGRA_8888 : GF_Format.Format_24bpp_BGR_888; } } } byte oldRepeatByte = gf.RepeatByte; OpenSpaceSettings settings = manager.Context !.GetSettings <OpenSpaceSettings>(); // Import the bitmap gf.ImportFromBitmap(settings, rawBitmapData, Services.Data.Archive_GF_GenerateMipmaps); Logger.Debug("The repeat byte has been updated for a .gf file from {0} to {1}", oldRepeatByte, gf.RepeatByte); // Serialize the data to get the bytes manager.Context.WriteStreamData(outputStream.Stream, gf, name: outputStream.Name, leaveOpen: true); }
/// <summary> /// Converts the import file data from the input stream to the output stream /// </summary> /// <param name="fileBytes">The file bytes</param> /// <param name="inputStream">The input stream to import from</param> /// <param name="outputStream">The destination stream</param> /// <param name="format">The file format to use</param> public void ConvertImportData(byte[] fileBytes, Stream inputStream, Stream outputStream, FileExtension format) { // Load the bitmap using var bmp = new Bitmap(inputStream); // Load the current file OpenSpaceGFFile gf = GetFileContent(fileBytes); // IDEA: If bmp is not in supported format, then convert it? RawBitmapData rawBitmapData; // Get the bitmap lock using (var bmpLock = new BitmapLock(bmp)) { // Get the raw bitmap data rawBitmapData = new RawBitmapData(bmp.Width, bmp.Height, bmpLock.Pixels, bmp.PixelFormat); // Force the new pixel format to be 888 or 8888 if set to do so if (RCPServices.Data.Archive_GF_ForceGF8888Import) { gf.GFPixelFormat = gf.GFPixelFormat.SupportsTransparency() ? OpenSpaceGFFormat.Format_32bpp_BGRA_8888 : OpenSpaceGFFormat.Format_24bpp_BGR_888; } // Check if the format should be updated for transparency if (RCPServices.Data.Archive_GF_UpdateTransparency != Archive_GF_TransparencyMode.PreserveFormat) { // NOTE: Only 24 and 32 bpp bitmaps are supported // Check if the imported file is transparent var isTransparent = bmp.PixelFormat switch { PixelFormat.Format32bppArgb => (RCPServices.Data.Archive_GF_UpdateTransparency == Archive_GF_TransparencyMode.UpdateBasedOnPixelFormat || bmpLock.UtilizesAlpha()), PixelFormat.Format24bppRgb => false, _ => (bool?)null }; // NOTE: Currently only supported for formats with 3 or 4 channels // Check if the format should be updated for transparency if (gf.Channels >= 3 && isTransparent != null) { // Update the format gf.GFPixelFormat = isTransparent.Value ? OpenSpaceGFFormat.Format_32bpp_BGRA_8888 : OpenSpaceGFFormat.Format_24bpp_BGR_888; } } } // Import the bitmap gf.ImportFromBitmap(Settings, rawBitmapData, RCPServices.Data.Archive_GF_GenerateMipmaps); // Serialize the data to get the bytes BinarySerializableHelpers.WriteToStream(gf, outputStream, Settings, RCPServices.App.GetBinarySerializerLogger()); } #endregion }
/// <summary> /// Imports a bitmap image into the file, keeping the structure based on the properties and generating mipmaps if needed. This will reset the mipmaps, requiring them to be generated again. /// </summary> /// <param name="gf">The GF file data</param> /// <param name="settings">The serializer settings</param> /// <param name="bmp">The bitmap data to import from</param> /// <param name="generateMipmaps">Indicates if mipmaps should be generated for the image</param> public static void ImportFromBitmap(this GF gf, OpenSpaceSettings settings, RawBitmapData bmp, bool generateMipmaps) { // Helper method for writing the pixel data void WritePixelData(RawBitmapData bitmapData, long offset) { // Make sure the pixel format is supported if (bitmapData.PixelFormat != PixelFormat.Format32bppArgb && bitmapData.PixelFormat != PixelFormat.Format24bppRgb) { throw new Exception($"The bitmap pixel format {bitmapData.PixelFormat} is not supported for importing"); } // Get the number of bitmap channels int bmpChannels = Image.GetPixelFormatSize(bitmapData.PixelFormat) / 8; // Get the format GF_Format format = gf.PixelFormat; byte[] bmpColorData = new byte[4]; for (uint y = 0; y < bitmapData.Height; y++) { for (uint x = 0; x < bitmapData.Width; x++) { // Get the offsets for the pixel colors var pixelOffset = (bitmapData.Width * y + x) * gf.Channels + offset; // NOTE: We reverse the Y-axis here since the .gf images are always flipper vertically var rawOffset = (bitmapData.Width * (bitmapData.Height - y - 1) + x) * bmpChannels; // Get the bitmap color bytes for this pixel bmpColorData[0] = bitmapData.PixelData[rawOffset + 0]; bmpColorData[1] = bitmapData.PixelData[rawOffset + 1]; bmpColorData[2] = bitmapData.PixelData[rawOffset + 2]; bmpColorData[3] = bmpChannels == 4 ? bitmapData.PixelData[rawOffset + 3] : (byte)255; // Get the pixels foreach (var b in gf.GetGfPixelBytes(format, bmpColorData)) { gf.PixelData[pixelOffset] = b; pixelOffset++; } } } } // Set size gf.Width = bmp.Width; gf.Height = bmp.Height; // Set the pixel count gf.PixelCount = gf.Width * gf.Height; // Update the mipmap count if (generateMipmaps && gf.SupportsMipmaps(settings)) { gf.MipmapsCount = gf.DeterminePreferredMipmapsCount(); } else { gf.MipmapsCount = 0; } // Enumerate each mipmap size foreach ((int Width, int Height)size in gf.GetExclusiveMipmapSizes()) { // Get the mipmap pixel count var count = size.Width * size.Height; // Add to the total pixel count gf.PixelCount += count; } // Create the data array gf.PixelData = new byte[gf.Channels * gf.PixelCount]; // Set the main pixel data WritePixelData(bmp, 0); // Keep track of the offset long mipmapOffset = gf.Width * gf.Height * gf.Channels; // Generate mipmaps if available if (gf.ExclusiveMipmapCount > 0) { // Get the bitmap using Bitmap bitmap = bmp.GetBitmap(); // Generate every mipmap foreach ((int Width, int Height)size in gf.GetExclusiveMipmapSizes()) { // Resize the bitmap using Bitmap resizedBmp = bitmap.ResizeImage(size.Width, size.Height, false); // Write the mipmap WritePixelData(new RawBitmapData(resizedBmp), mipmapOffset); // Increase the index mipmapOffset += size.Height * size.Width * gf.Channels; } } // Update the repeat byte gf.UpdateRepeatByte(); }