/// <summary> /// Compile input images into WAD texture file. /// </summary> /// <param name="outputFilename">Output wad file path.</param> /// <param name="images">Input image files.</param> /// <param name="names">Names of textures.</param> /// <param name="reserverLastPalColor">Reserve last color in palette if name starts with {.</param> public static void CreateWad(string outputFilename, string[] images, string[] names, Color alphaReplacementColor, bool reserverLastPalColor = false) { using (FileStream fs = new FileStream(outputFilename, FileMode.Create)) using (BinaryWriter bw = new BinaryWriter(fs)) { //Convert bitmaps to 8bpp format List <FreeImageBitmap> imgs = new List <FreeImageBitmap>(); for (int i = 0; i < images.Length; i++) { //Quantize images FreeImageBitmap originalImage = new FreeImageBitmap(images[i]); //If texture will be transparent, reserve last color if enabled bool reserveLastClr = (names[i].StartsWith("{") && reserverLastPalColor); bool isTransparentImage = originalImage.IsTransparent; bool is8Bpp = originalImage.BitsPerPixel == 8; int r = reserveLastClr ? 1 : 0; if (isTransparentImage) { originalImage.SwapColors(new RGBQUAD(Color.Transparent), new RGBQUAD(alphaReplacementColor), false); } originalImage.Quantize(FREE_IMAGE_QUANTIZE.FIQ_NNQUANT, MaxPaletteColors - r); originalImage.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_08_BPP); if (reserveLastClr) { if (isTransparentImage) { bool foundReplacementColor = false; for (int pindex = 0; pindex < originalImage.Palette.Length; pindex++) { RGBQUAD rgb = originalImage.Palette.GetValue(pindex); if (rgb.rgbRed == alphaReplacementColor.R && rgb.rgbGreen == alphaReplacementColor.G && rgb.rgbBlue == alphaReplacementColor.B) { var lastColor = originalImage.Palette.GetValue(MaxPaletteColors - 1); originalImage.Palette[pindex] = lastColor; originalImage.Palette[MaxPaletteColors - 1] = new RGBQUAD(alphaReplacementColor); originalImage.SwapPaletteIndices((byte)pindex, MaxPaletteColors - 1); foundReplacementColor = true; break; } } // If didn't found replacement, set directly last alpha color if (!foundReplacementColor) { originalImage.Palette[MaxPaletteColors - 1] = new RGBQUAD(alphaReplacementColor); } } else { originalImage.Palette[MaxPaletteColors - 1] = new RGBQUAD(alphaReplacementColor); } } imgs.Add(originalImage); } uint[] offsets = new uint[images.Length]; uint[] sizes = new uint[images.Length]; //WAD header bw.Write(WadHeaderId); bw.Write(images.Length); bw.Write(0); //This will be changed later //Write textures for (int i = 0; i < images.Length; i++) { uint posTextureStart = (uint)bw.BaseStream.Position; offsets[i] = posTextureStart; //Texture name byte[] name = CreateTextureName(names[i]); bw.Write(name, 0, name.Length); //Texture dimensions bw.Write(imgs[i].Width); bw.Write(imgs[i].Height); //Offsets uint posImage = (uint)(bw.BaseStream.Position - posTextureStart); bw.Write(posImage + 16); //image int pixelSize = ((imgs[i].Width) * (imgs[i].Height)); int m1 = ((imgs[i].Width / 2) * (imgs[i].Height / 2)); int m2 = ((imgs[i].Width / 4) * (imgs[i].Height / 4)); int m3 = ((imgs[i].Width / 8) * (imgs[i].Height / 8)); bw.Write((uint)(posImage + pixelSize + 16)); //mipmap1 bw.Write((uint)(posImage + pixelSize + m1 + 16)); //mipmap2 bw.Write((uint)(posImage + pixelSize + m1 + m2 + 16)); //mipmap3 //Write pixel data imgs[i].RotateFlip(RotateFlipType.RotateNoneFlipX); byte[] arr = new byte[imgs[i].Width * imgs[i].Height]; System.Runtime.InteropServices.Marshal.Copy(imgs[i].GetScanlinePointer(0), arr, 0, arr.Length); Array.Reverse(arr); bw.Write(arr); // //Mip map data int factor = 2; for (int a = 0; a < 3; a++) { int widthMM = (imgs[i].Width / factor); int heightMM = (imgs[i].Height / factor); using (FreeImageBitmap clBmp = new FreeImageBitmap(imgs[i])) { //TODO: Transparent png clBmp.Rescale(widthMM, heightMM, FREE_IMAGE_FILTER.FILTER_LANCZOS3); clBmp.Quantize(FREE_IMAGE_QUANTIZE.FIQ_NNQUANT, MaxPaletteColors, imgs[i].Palette); byte[] arrMM = new byte[widthMM * heightMM]; System.Runtime.InteropServices.Marshal.Copy(clBmp.GetScanlinePointer(0), arrMM, 0, arrMM.Length); Array.Reverse(arrMM); bw.Write(arrMM); } factor *= 2; } //Unknown 2 bytes bw.Write(new byte[] { 0x00, 0x01 }); //Write color palette for (int p = 0; p < imgs[i].Palette.Length; p++) { bw.Write(imgs[i].Palette[p].rgbRed); bw.Write(imgs[i].Palette[p].rgbGreen); bw.Write(imgs[i].Palette[p].rgbBlue); } //Padding bw.Write(new byte[] { 0x00, 0x00 }); sizes[i] = (uint)bw.BaseStream.Position - posTextureStart; } long posLumps = bw.BaseStream.Position; bw.Seek(8, SeekOrigin.Begin); bw.Write((uint)posLumps); bw.Seek((int)posLumps, SeekOrigin.Begin); //Write Lumps infos for (int i = 0; i < images.Length; i++) { bw.Write(offsets[i]); bw.Write(sizes[i]); bw.Write(sizes[i]); bw.Write((byte)0x43); bw.Write((byte)0); bw.Write(new byte[] { 0x00, 0x00 }); byte[] name = CreateTextureName(names[i]); bw.Write(name, 0, name.Length); } //Free resources for (int i = 0; i < imgs.Count; i++) { imgs[i].Dispose(); } } }
/// <summary> /// Create new sprite file from image files. /// </summary> /// <param name="outputPath">Output filename *.SPR</param> /// <param name="files">Input image paths.</param> /// <param name="spriteType">SprType</param> /// <param name="textFormat">SprTextFormat</param> /// <param name="palIndex">Which palette use from files</param> public static void CreateSpriteFile(string outputPath, string[] files, SprType spriteType, SprTextFormat textFormat, int palIndex, Color alphaReplacementColor) { List <FreeImageBitmap> images = files.Select(file => new FreeImageBitmap(file)).ToList(); //Retrieve maximum width, height int prevSize = 0; int maxW = 0, maxH = 0; for (int i = 0; i < images.Count; i++) { FreeImageBitmap image = images[i]; if ((image.Height + image.Width) > prevSize) { prevSize = image.Height + image.Width; maxW = image.Width; maxH = image.Height; } if (image.IsTransparent) { image.SwapColors(new RGBQUAD(Color.Transparent), new RGBQUAD(alphaReplacementColor), false); } } //Calc. bounding box float f = (float)Math.Sqrt((maxW >> 1) * (maxW >> 1) + (maxH >> 1) * (maxH >> 1)); using (BinaryWriter bw = new BinaryWriter(new FileStream(outputPath, FileMode.Create))) { //Write header first bw.Write(SpriteHeaderId.ToCharArray()); bw.Write(2); bw.Write((uint)spriteType); bw.Write((uint)textFormat); bw.Write(f); bw.Write(maxW); bw.Write(maxH); bw.Write(images.Count); bw.Write(0.0f); //Always 0 ? bw.Write(1); //Synch. type //Color palette bw.Write((ushort)MaxPaletteColors); //Always 256 ? if ((palIndex > (images.Count - 1)) || palIndex < images.Count) { palIndex = 0; } if (!images[palIndex].HasPalette || images[palIndex].Palette.Length != 256) { images[palIndex].ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_08_BPP); } Palette pal = images[palIndex].Palette; for (int i = 0; i < 256; i++) { bw.Write(pal[i].rgbRed); bw.Write(pal[i].rgbGreen); bw.Write(pal[i].rgbBlue); } //Write images for (int i = 0; i < images.Count; i++) { bw.Write(0); //group bw.Write(-(images[i].Width / 2)); //origin x bw.Write(images[i].Height / 2); //origin y bw.Write(images[i].Width); //w bw.Write(images[i].Height); //h byte[] arr = new byte[images[i].Width * images[i].Height]; images[i].RotateFlip(RotateFlipType.RotateNoneFlipX); System.Runtime.InteropServices.Marshal.Copy(images[i].GetScanlinePointer(0), arr, 0, arr.Length); Array.Reverse(arr); bw.Write(arr); } } //Free resources images.ForEach(image => image.Dispose()); }