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