public static unsafe byte[] GetBytes(BitmapSource bitmap, uint bcrFlags) { uint stride = StructUtil.CalcStride((ushort)bitmap.Format.BitsPerPixel, bitmap.PixelWidth); byte[] buffer = new byte[stride * bitmap.PixelHeight]; bitmap.CopyPixels(buffer, (int)stride, 0); var clrs = bitmap.Palette == null ? null : bitmap.Palette.Colors.Select(c => new RGBQUAD { rgbRed = c.R, rgbBlue = c.B, rgbGreen = c.G }).ToArray(); BITMAP_WRITE_REQUEST req = new BITMAP_WRITE_REQUEST { dpiX = bitmap.DpiX, dpiY = bitmap.DpiY, imgWidth = bitmap.PixelWidth, imgHeight = bitmap.PixelHeight, imgStride = stride, imgTopDown = true, imgColorTable = clrs, }; BITMASKS masks = default; uint getBitmask(IList <byte> mask) { uint result = 0; int shift = 0; for (int i = 0; i < mask.Count; i++) { result = result | (uint)(mask[i] << shift); shift += 8; } return(result); } if (bitmap.Format.Masks != null && bitmap.Format.Masks.Count == 3) { var wpfmasks = bitmap.Format.Masks; masks.maskBlue = getBitmask(wpfmasks[0].Mask); masks.maskGreen = getBitmask(wpfmasks[1].Mask); masks.maskRed = getBitmask(wpfmasks[2].Mask); } else if (bitmap.Format.Masks != null && bitmap.Format.Masks.Count == 4) { var wpfmasks = bitmap.Format.Masks; masks.maskBlue = getBitmask(wpfmasks[0].Mask); masks.maskGreen = getBitmask(wpfmasks[1].Mask); masks.maskRed = getBitmask(wpfmasks[2].Mask); masks.maskAlpha = getBitmask(wpfmasks[3].Mask); } fixed(byte *ptr = buffer) return(BitmapCore.WriteToBMP(ref req, ptr, masks, (ushort)bitmap.Format.BitsPerPixel, bcrFlags)); }
/// <inheritdoc/> public override byte[] GetBytes(Bitmap bitmap, BitmapWriterFlags wFlags) { // default - this will cause GDI to convert the pixel format to bgra32 if we don't know the format directly var gdiFmt = PixelFormat.Format32bppArgb; var coreFmt = BitmapCorePixelFormat.Bgra32; var pxarr = Formats.Where(f => f.gdiFmt == bitmap.PixelFormat).ToArray(); if (pxarr.Length > 0) { var px = pxarr.First(); gdiFmt = px.gdiFmt; coreFmt = px.coreFmt; } var colorTable = bitmap.Palette.Entries.Select(e => new RGBQUAD { rgbBlue = e.B, rgbGreen = e.G, rgbRed = e.R }).ToArray(); var dlock = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, gdiFmt); var buf = (byte *)dlock.Scan0; BITMAP_WRITE_REQUEST req = new BITMAP_WRITE_REQUEST { dpiX = 0, dpiY = 0, imgWidth = bitmap.Width, imgHeight = bitmap.Height, imgStride = (uint)dlock.Stride, imgTopDown = true, imgColorTable = colorTable, }; var bytes = BitmapCore.WriteToBMP(ref req, buf, coreFmt, (uint)wFlags); bitmap.UnlockBits(dlock); return(bytes); }
public static byte[] WriteToBMP(ref BITMAP_WRITE_REQUEST info, byte *sourceBufferStart, BITMASKS masks, ushort nbits, uint bcwFlags) { bool skipFileHeader = (bcwFlags & BC_WRITE_SKIP_FH) > 0; bool forceV5 = (bcwFlags & BC_WRITE_V5) > 0; bool forceInfo = (bcwFlags & BC_WRITE_VINFO) > 0; if (forceV5 && forceInfo) { throw new ArgumentException("ForceV5 and ForceInfo flags can not be used at the same time."); } // NOT SUPPORTED RIGHT NOW bool iccEmbed = false; byte[] iccProfileData = new byte[0]; var hasAlpha = masks.maskAlpha != 0; if (nbits < 16 && (info.imgColorTable == null || info.imgColorTable.Length == 0)) { throw new InvalidOperationException("A indexed bitmap must have a color table / palette."); } //int dpiToPelsPM(double dpi) //{ // if (Math.Round(dpi) == 96d) return 0; // return (int)Math.Round(dpi / 0.0254d); //} uint paletteSize = info.imgColorTable == null ? 0 : (uint)info.imgColorTable.Length; var fhSize = (uint)Marshal.SizeOf <BITMAPFILEHEADER>(); var quadSize = (uint)Marshal.SizeOf <RGBQUAD>(); byte[] buffer; uint pxOffset, pxSize; // BI_BITFIELDS is not valid for 24bpp, so if the masks are not RGB we need to use a V5 header. //var nonStandard24bpp = nbits == 24 && !BitmapCorePixelFormat2.Bgr24.IsMatch(24, masks); BitmapCompressionMode compr = BitmapCompressionMode.BI_RGB; // some parsers do not respect the v5 header masks unless BI_BITFIELDS is used... // this is true of Chrome (only for 16bpp) and is also true of FireFox (16 and 32bpp) if (nbits == 16 || nbits == 32) { compr = BitmapCompressionMode.BI_BITFIELDS; } // write V5 header if embedded color profile or has alpha data if (forceV5 || hasAlpha || iccEmbed) { var v5Size = (uint)Marshal.SizeOf <BITMAPV5HEADER>(); // Typical structure: // - BITMAPFILEHEADER (Optional) // - BITMAPV5HEADER // - * Note, never write BI_BITFIELDS at the end of a V5 header, these masks are contained within the header itself // - Color Table (Optional) // - Pixel Data // - Embedded Color Profile (Optional) var fh = new BITMAPFILEHEADER { bfType = BFH_BM, }; var v5 = new BITMAPV5HEADER { bV5Size = v5Size, bV5Planes = 1, bV5BitCount = nbits, bV5Height = info.imgTopDown ? -info.imgHeight : info.imgHeight, bV5Width = info.imgWidth, bV5Compression = compr, bV5XPelsPerMeter = 0, bV5YPelsPerMeter = 0, bV5RedMask = masks.maskRed, bV5GreenMask = masks.maskGreen, bV5BlueMask = masks.maskBlue, bV5AlphaMask = masks.maskAlpha, bV5ClrImportant = paletteSize, bV5ClrUsed = paletteSize, bV5SizeImage = (uint)(info.imgStride * info.imgHeight), bV5CSType = ColorSpaceType.LCS_sRGB, bV5Intent = Bv5Intent.LCS_GM_IMAGES, }; uint offset = skipFileHeader ? 0 : fhSize; offset += v5Size; offset += paletteSize * quadSize; // fh offset points to beginning of pixel data fh.bfOffBits = pxOffset = offset; pxSize = v5.bV5SizeImage; offset += v5.bV5SizeImage; if (iccEmbed) { v5.bV5CSType = ColorSpaceType.PROFILE_EMBEDDED; v5.bV5ProfileData = offset; v5.bV5ProfileSize = (uint)iccProfileData.Length; offset += v5.bV5ProfileSize; } // fh size must be total file size fh.bfSize = offset; buffer = new byte[offset]; offset = 0; if (!skipFileHeader) { StructUtil.SerializeTo(fh, buffer, ref offset); } StructUtil.SerializeTo(v5, buffer, ref offset); if (info.imgColorTable != null) { foreach (var p in info.imgColorTable) { StructUtil.SerializeTo(p, buffer, ref offset); } } Marshal.Copy((IntPtr)sourceBufferStart, buffer, (int)offset, (int)v5.bV5SizeImage); offset += v5.bV5SizeImage; if (iccEmbed) { Buffer.BlockCopy(iccProfileData, 0, buffer, (int)offset, iccProfileData.Length); } } else { var infoSize = (uint)Marshal.SizeOf <BITMAPINFOHEADER>(); // Typical structure: // - BITMAPFILEHEADER (Optional) // - BITMAPINFOHEADER // - BI_BITFIELDS (Optional) // - Color Table (Optional) // - Pixel Data // this would be ideal, we can specify transparency in VINFO headers... but many applications incl FireFox do not support this. // if (hasAlpha) compr = BitmapCompressionMode.BI_ALPHABITFIELDS; var fh = new BITMAPFILEHEADER { bfType = BFH_BM, }; var vinfo = new BITMAPINFOHEADER { bV5Size = infoSize, bV5Planes = 1, bV5BitCount = nbits, bV5Height = info.imgTopDown ? -info.imgHeight : info.imgHeight, bV5Width = info.imgWidth, bV5Compression = compr, bV5XPelsPerMeter = 0, bV5YPelsPerMeter = 0, bV5ClrImportant = paletteSize, bV5ClrUsed = paletteSize, bV5SizeImage = (uint)(info.imgStride * info.imgHeight), }; uint offset = skipFileHeader ? 0 : fhSize; offset += infoSize; if (compr == BitmapCompressionMode.BI_BITFIELDS) { offset += sizeof(uint) * 3; } offset += paletteSize * quadSize; // fh offset points to beginning of pixel data fh.bfOffBits = pxOffset = offset; pxSize = vinfo.bV5SizeImage; offset += vinfo.bV5SizeImage; // fh size must be total file size fh.bfSize = offset; buffer = new byte[offset]; offset = 0; if (!skipFileHeader) { StructUtil.SerializeTo(fh, buffer, ref offset); } StructUtil.SerializeTo(vinfo, buffer, ref offset); if (compr == BitmapCompressionMode.BI_BITFIELDS) { Buffer.BlockCopy(masks.BITFIELDS(), 0, buffer, (int)offset, sizeof(uint) * 3); offset += sizeof(uint) * 3; } if (info.imgColorTable != null) { foreach (var p in info.imgColorTable) { StructUtil.SerializeTo(p, buffer, ref offset); } } Marshal.Copy((IntPtr)sourceBufferStart, buffer, (int)offset, (int)vinfo.bV5SizeImage); } return(buffer); }
public static byte[] WriteToBMP(ref BITMAP_WRITE_REQUEST info, byte *sourceBufferStart, BitmapCorePixelFormat fmt, uint bcwFlags) { return(WriteToBMP(ref info, sourceBufferStart, fmt.Masks, fmt.BitsPerPixel, bcwFlags)); }