This is the code I use to encode images into RTF.
It's not the nicest code, but it gets the job done.
public enum FormatHexStyles
{
LittleEndian2Bytes,
LittleEndian4Bytes,
BigEndian2Bytes,
BigEndian4Bytes
}
public static class ImageUtil
{
public const int HMM_PER_INCH = 2540;
public const int TWIPS_PER_INCH = 1440;
public const float DEFAULT_DPIX = 96.0F;
public const float DEFAULT_DPIY = 96.0F;
public static readonly string[] HEX_X2_TABLE =
{
"00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f",
"10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f",
"20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f",
"30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f",
"40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f",
"50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f",
"60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f",
"70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f",
"80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f",
"90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f",
"a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af",
"b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf",
"c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf",
"d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df",
"e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee","ef",
"f0","f1","f2","f3","f4","f5","f6","f7","f8","f9","fa","fb","fc","fd","fe","ff"
};
public static int RoundTo4ByteBoundary( int aValue )
{
bool negative = false;
if( aValue < 0 )
{
negative = true;
aValue = -aValue;
}
int real = aValue & ~0x3;
int mod = aValue & 0x3;
if( mod != 0 )
real += 0x4;
if( negative )
return -real;
else
return real;
}
public static string ConvertToHex( int aValue, FormatHexStyles aHexFormat )
{
if( aValue < 0 )
throw new ArgumentOutOfRangeException( string.Format(
"Invalid value '{0}' specified; negative values are not supported",
aValue ));
string hex;
switch( aHexFormat )
{
case FormatHexStyles.LittleEndian2Bytes:
if( aValue > 0xFFFF )
throw new ArgumentOutOfRangeException( string.Format(
"Invalid value '{0}' specified; allowed range [0..{1}]",
aValue,
0xFFFF ));
hex =
HEX_X2_TABLE[aValue & 0xFF] +
HEX_X2_TABLE[(aValue & 0xFF00) >> 8];
break;
case FormatHexStyles.LittleEndian4Bytes:
if( aValue > 0x7FFFFFFF )
throw new ArgumentOutOfRangeException( string.Format(
"Invalid value '{0}' specified; allowed range [0..{1}]",
aValue,
0x7FFFFFFF ));
hex =
HEX_X2_TABLE[aValue & 0xFF] +
HEX_X2_TABLE[(aValue & 0xFF00) >> 8] +
HEX_X2_TABLE[(aValue & 0xFF0000) >> 16] +
HEX_X2_TABLE[(aValue & 0x7F000000) >> 24];
break;
case FormatHexStyles.BigEndian2Bytes:
if( aValue > 0xFFFF )
throw new ArgumentOutOfRangeException( string.Format(
"Invalid value '{0}' specified; allowed range [0..{1}]",
aValue,
0xFFFF ));
hex =
HEX_X2_TABLE[(aValue & 0xFF00) >> 8] +
HEX_X2_TABLE[aValue & 0xFF];
break;
case FormatHexStyles.BigEndian4Bytes:
if( aValue > 0x7FFFFFFF )
throw new ArgumentOutOfRangeException( string.Format(
"Invalid value '{0}' specified; allowed range [0..{1}]",
aValue,
0x7FFFFFFF ));
hex =
HEX_X2_TABLE[(aValue & 0x7F000000) >> 24] +
HEX_X2_TABLE[(aValue & 0xFF0000) >> 16] +
HEX_X2_TABLE[(aValue & 0xFF00) >> 8] +
HEX_X2_TABLE[aValue & 0xFF];
break;
default:
throw new InvalidOperationException( string.Format(
"Invalid FormatHexStyles encountered; {0} not supported",
aHexFormat ));
}
return hex;
}
public static Size CalcSize( Image anImage, Size aNewSize )
{
if( anImage == null )
return Size.Empty;
Size size;
if( aNewSize.Width <= 0 &&
aNewSize.Height <= 0 )
{
size = new Size( anImage.Width, anImage.Height );
}
else if(
aNewSize.Width > 0 &&
aNewSize.Height > 0 )
{
size = aNewSize;
}
else if( aNewSize.Height <= 0 )
{
float f = (float)aNewSize.Width / (float)anImage.Width;
int h = (int)((float)anImage.Height * f);
size = new Size( aNewSize.Width, h );
}
else
{
float f = (float)aNewSize.Height / (float)anImage.Height;
int w = (int)((float)anImage.Width * f);
size = new Size( w, aNewSize.Height );
}
return size;
}
public static string ConvertToRtf( Bitmap anImage, Color anImageBackcolor )
{
return ConvertToRtf(
anImage,
anImageBackcolor,
anImage.Width,
anImage.Height,
DEFAULT_DPIX,
DEFAULT_DPIY );
}
public static string ConvertToRtf(
Bitmap anImage,
Color aBackgroundColor,
int aWidth,
int aHeight,
float aDpiX,
float aDpiY )
{
if( anImage == null )
return "<No image available>";
int intWidth = anImage.Width;
int intHeight = anImage.Height;
float w = intWidth;
float h = intHeight;
float hmm = HMM_PER_INCH;
float twip = TWIPS_PER_INCH;
int picw = (int)Math.Round((w/aDpiX)*hmm);
int pich = (int)Math.Round((h/aDpiY)*hmm);
SizeF size = CalcSize(anImage, new Size(aWidth,aHeight));
int picwgoal = (int)Math.Round((size.Width/aDpiX)*twip);
int pichgoal = (int)Math.Round((size.Height/aDpiY)*twip);
int sizeA = ((RoundTo4ByteBoundary(intWidth*3)*intHeight)/2)+56;
int sizeB = sizeA - 0x16;
int sizeC = RoundTo4ByteBoundary(intWidth*3)*intHeight;
string maskRtfPicHeader = @"\pict\wmetafile8\picw{0}\pich{1}\picwgoal{2}\pichgoal{3} ";
string maskRtfWmfHeader = @"010009000003{0}0000{1}0000050000000b0200000000050000000c02{2}{3}{1}430f2000cc000000{4}{5}00000000{2}{3}00000000";
string maskRtfBmpHeader = @"28000000{0}{1}0100180000000000{2}c40e0000c40e00000000000000000000";
string maskRtf = @"{0}{1}{2}{3}{4}030000000000{5}";
string rtfPicHeader = string.Format(
maskRtfPicHeader,
picw,
pich,
picwgoal,
pichgoal );
string rtfWmfHeader = string.Format(
maskRtfWmfHeader,
ConvertToHex( sizeA, FormatHexStyles.LittleEndian4Bytes ),
ConvertToHex( sizeB, FormatHexStyles.LittleEndian4Bytes ),
ConvertToHex( pich, FormatHexStyles.LittleEndian2Bytes ),
ConvertToHex( picw, FormatHexStyles.LittleEndian2Bytes ),
ConvertToHex( intHeight, FormatHexStyles.LittleEndian2Bytes ),
ConvertToHex( intWidth, FormatHexStyles.LittleEndian2Bytes ));
string rtfBmpHeader = string.Format(
maskRtfBmpHeader,
ConvertToHex( intWidth, FormatHexStyles.LittleEndian4Bytes ),
ConvertToHex( intHeight, FormatHexStyles.LittleEndian4Bytes ),
ConvertToHex( sizeC, FormatHexStyles.LittleEndian4Bytes ));
BitmapData bmpData = anImage.LockBits(
new Rectangle(0, 0, intWidth, intHeight),
ImageLockMode.ReadOnly,
anImage.PixelFormat );
uint[] pixelsARGB = new uint[intWidth*intHeight];
unsafe
{
switch( anImage.PixelFormat )
{
case PixelFormat.Format32bppArgb:
case PixelFormat.Format32bppPArgb:
case PixelFormat.Format32bppRgb:
uint* ptrARGB = (uint*)bmpData.Scan0;
for( int y = 0; y < intHeight; y++ )
{
int yOffset = y*intWidth;
for( int x = 0; x < intWidth; x++ )
pixelsARGB[yOffset+x] = ptrARGB[yOffset+x];
}
anImage.UnlockBits( bmpData );
break;
case PixelFormat.Format8bppIndexed:
byte* ptrPalette = (byte*)bmpData.Scan0;
uint[] colorsPalette = new uint[anImage.Palette.Entries.Length];
for( int col=0; col < anImage.Palette.Entries.Length; col++ )
colorsPalette[col] = (uint)anImage.Palette.Entries[col].ToArgb();
for( int y = 0; y < intHeight; y++ )
{
int yOffset = y*intWidth;
for( int x = 0; x < intWidth; x++ )
pixelsARGB[yOffset+x] = colorsPalette[ptrPalette[yOffset+x]];
}
anImage.UnlockBits( bmpData );
break;
default:
anImage.UnlockBits( bmpData );
for( int y = 0; y < intHeight; y++ )
{
int yOffset = y*intWidth;
for( int x = 0; x < intWidth; x++ )
pixelsARGB[yOffset+x] = (uint)anImage.GetPixel(x,y).ToArgb();
}
break;
}
}
bool doBlend = aBackgroundColor != Color.Empty;
float backR = aBackgroundColor.R;
float backG = aBackgroundColor.G;
float backB = aBackgroundColor.B;
int alignRGB = intWidth*3;
int alignDWORD = RoundTo4ByteBoundary(alignRGB);
int alignStride = alignDWORD-alignRGB;
bool hasStride = alignStride > 0;
string stride = null;
if( hasStride )
{
StringBuilder sbStride = new StringBuilder();
for( int i = 0; i < alignStride; i++ )
sbStride.Append( HEX_X2_TABLE[0] );
stride = sbStride.ToString();
}
StringBuilder sbData = new StringBuilder();
for( int y = intHeight-1; y >= 0; y-- )
{
int yOffset = y*intWidth;
for( int x = 0; x < intWidth; x++ )
{
uint pix = pixelsARGB[yOffset+x];
float dr = (pix & 0x00FF0000) >> 16;
float dg = (pix & 0x0000FF00) >> 8;
float db = (pix & 0x000000FF);
if( doBlend )
{
float t = ((float)((pix & 0xFF000000) >> 24))/255F;
dr -= backR;
dg -= backG;
db -= backB;
dr = (dr*t) + backR;
dg = (dg*t) + backG;
db = (db*t) + backB;
}
sbData.Append( HEX_X2_TABLE[(int)db] );
sbData.Append( HEX_X2_TABLE[(int)dg] );
sbData.Append( HEX_X2_TABLE[(int)dr] );
}
if( hasStride )
sbData.Append( stride );
}
string rtfBmpData = sbData.ToString();
string rtfChunk = string.Format(
maskRtf,
'{',
rtfPicHeader,
rtfWmfHeader,
rtfBmpHeader,
rtfBmpData,
'}' );
return rtfChunk;
}
}