public static unsafe void RemapPalette(FreeImageBitmap bitmap, FreeImageBitmap referenceImage, int numberOfColors) { using (FreeImageBitmap bitmap32 = bitmap.GetColorConvertedInstance(FREE_IMAGE_COLOR_DEPTH.FICD_32_BPP)) { if (referenceImage.Height < bitmap.Height || referenceImage.Width < bitmap.Width) { int h2 = referenceImage.Height; if (h2 < bitmap.Height) { h2 = bitmap.Height; } int w2 = referenceImage.Width; if (w2 < bitmap.Width) { w2 = bitmap.Width; } referenceImage.EnlargeCanvas <byte>(0, 0, w2 - referenceImage.Width, h2 - referenceImage.Height, 0); } //FreeImageBitmap reference32 = bitmap.GetColorConvertedInstance(FREE_IMAGE_COLOR_DEPTH.FICD_32_BPP); if (bitmap.ColorDepth != 8) { bitmap.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_08_BPP); } int *newPalette = (int *)(referenceImage.Palette.BaseAddress); int h = bitmap.Height; int w = bitmap.Width; //Ties are broken in ColorToIndex by popularity in the original image Dictionary <int, int> ColorToIndex = new Dictionary <int, int>(); int[] ColorHistogram = new int[256]; for (int y = 0; y < h; y++) { byte *srcScanline = (byte *)(referenceImage.GetScanlineFromTop8Bit(y).BaseAddress); for (int x = 0; x < w; x++) { int c = srcScanline[x]; ColorHistogram[c]++; } } //add each palette color to the dictionary for (int i = 0; i < numberOfColors; i++) { int c = newPalette[i] & 0xFFFFFF; if (ColorToIndex.ContainsKey(c)) { int otherIndex = ColorToIndex[c]; if (ColorHistogram[i] > ColorHistogram[otherIndex]) { ColorToIndex[c] = i; } } else { ColorToIndex[c] = i; } } for (int y = 0; y < h; y++) { int * trueColorScanline = (int *)(bitmap32.GetScanlineFromTop32Bit(y).BaseAddress); byte *srcScanline = (byte *)(bitmap.GetScanlineFromTop8Bit(y).BaseAddress); byte *referenceScanline = (byte *)(referenceImage.GetScanlineFromTop8Bit(y).BaseAddress); for (int x = 0; x < w; x++) { int srcColor = trueColorScanline[x] & 0xFFFFFF; int refIndex = referenceScanline[x]; int refColor = newPalette[refIndex] & 0xFFFFFF; if (srcColor == refColor) { srcScanline[x] = (byte)refIndex; } else { if (ColorToIndex.ContainsKey(srcColor)) { int newColorIndex = ColorToIndex[srcColor]; if (refColor == (newPalette[newColorIndex] & 0xFFFFFF)) { srcScanline[x] = (byte)refIndex; } else { srcScanline[x] = (byte)newColorIndex; } } else { float minDistance = float.MaxValue; int minIndex = 0; for (int i = 0; i < numberOfColors; i++) { int c = newPalette[i] & 0xFFFFFF; float distance = GetDistanceF(srcColor, c); if (distance < minDistance) { minIndex = i; minDistance = distance; } else if (distance == minDistance && ColorHistogram[i] > ColorHistogram[minIndex]) { minIndex = i; } } if (refColor == (newPalette[minIndex] & 0xFFFFFF)) { srcScanline[x] = (byte)refIndex; } else { srcScanline[x] = (byte)minIndex; } ColorToIndex.Add(srcColor, minIndex); } } } } bitmap.Palette.AsArray = referenceImage.Palette.AsArray; } }
public static void SaveImage(Stream stream, FreeImageBitmap bitmap) { string comment = bitmap.Comment; var pmsHeader = new PmsHeader(); bool readComment = false; if (!string.IsNullOrEmpty(comment)) { if (pmsHeader.ParseComment(comment)) { readComment = true; } } if (pmsHeader.colorDepth == 0) { pmsHeader.colorDepth = 8; } bool is8Bit = !readComment || pmsHeader.colorDepth == 8; if (!readComment) { pmsHeader.version = 1; pmsHeader.headerSize = 48; } if (pmsHeader.signature == 0) { pmsHeader.signature = 0x00004d50; } if (pmsHeader.version == 2 && pmsHeader.headerSize == 0) { pmsHeader.headerSize = 64; } if (pmsHeader.headerSize < 48) { pmsHeader.headerSize = 48; } if (pmsHeader.headerSize > 64) { pmsHeader.headerSize = 64; } pmsHeader.addressOfComment = 0; pmsHeader.height = bitmap.Height; pmsHeader.width = bitmap.Width; if (is8Bit) { if (bitmap.ColorDepth > 8) { if (bitmap.ColorDepth == 32) { bitmap.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_24_BPP); } bitmap.Quantize(FREE_IMAGE_QUANTIZE.FIQ_WUQUANT, 256); //throw new ArgumentException("image must be 8-bit"); } pmsHeader.addressOfPalette = pmsHeader.headerSize; pmsHeader.addressOfData = pmsHeader.addressOfPalette + 768; pmsHeader.colorDepth = 8; SaveHeader(stream, pmsHeader); SavePalette(stream, bitmap.Palette); SaveImageData8Bit(stream, bitmap); } else { bool hasAlphaChannel = false; var existingAlphaChannel = bitmap.GetChannel(FREE_IMAGE_COLOR_CHANNEL.FICC_ALPHA); if (existingAlphaChannel != null) { bool allPixelsOpaque = AllPixelsOpaque(existingAlphaChannel); hasAlphaChannel = !allPixelsOpaque; } if (bitmap.ColorDepth != 32) { bitmap.ConvertColorDepth(FREE_IMAGE_COLOR_DEPTH.FICD_32_BPP); } bool usingAlphaChannel = pmsHeader.shadowDepth == 8 || (pmsHeader.shadowDepth == 0 && hasAlphaChannel); pmsHeader.addressOfPalette = 0; pmsHeader.addressOfData = pmsHeader.headerSize; pmsHeader.colorDepth = 16; ushort[] image16 = new ushort[pmsHeader.width * pmsHeader.height]; byte[] image8 = null; if (usingAlphaChannel) { image8 = new byte[pmsHeader.width * pmsHeader.height]; } int o = 0; for (int y = 0; y < pmsHeader.height; y++) { var scanline = bitmap.GetScanlineFromTop32Bit(y); if (image8 == null) { for (int x = 0; x < pmsHeader.width; x++) { unchecked { int p = scanline[x]; int b = (p >> 0) & 0xFF; int g = (p >> 8) & 0xFF; int r = (p >> 16) & 0xFF; //int a = (p >> 24) & 0xFF; b = (b * 0x1F1F + 0x8000) >> 16; g = (g * 0x3F3F + 0x8000) >> 16; r = (r * 0x1F1F + 0x8000) >> 16; p = (b << 0) | (g << 5) | (r << 11); image16[o] = (ushort)p; o++; } } } else { for (int x = 0; x < pmsHeader.width; x++) { unchecked { int p = scanline[x]; int b = (p >> 0) & 0xFF; int g = (p >> 8) & 0xFF; int r = (p >> 16) & 0xFF; int a = (p >> 24) & 0xFF; b = (b * 0x1F1F + 0x8000) >> 16; g = (g * 0x3F3F + 0x8000) >> 16; r = (r * 0x1F1F + 0x8000) >> 16; p = (b << 0) | (g << 5) | (r << 11); image16[o] = (ushort)p; image8[o] = (byte)a; o++; } } } } MemoryStream ms = new MemoryStream(); SaveImageData16Bit(ms, image16, pmsHeader.width, pmsHeader.height); int imageSize = (int)ms.Length; if (usingAlphaChannel) { pmsHeader.addressOfPalette = imageSize + pmsHeader.addressOfData; } SaveHeader(stream, pmsHeader); ms.WriteTo(stream); ms.SetLength(0); if (usingAlphaChannel) { SaveImageData8Bit(stream, image8, pmsHeader.width, pmsHeader.height); } } }