Click here to Skip to main content
Rate this: bad
good
Please Sign up or sign in to vote.
Hi,
 
I've searched the entire internet for two consecutive days, with little to no success on this issue:
 
To append images in the System.Windows.Forms.RichTextBox class without copying to clipboard or any other dirty tricks, I have stumbled upon the ExRichTextBox class by Khendys Gordon here on http://www.codeproject.com
 
This class can convert an Image type to the corresponding RTF string which is needed to display it in the RichTextBox.
 
The RTF's picture control MUST be .WMF data either in binary or hexadecimals (this is also what WordPad does when you insert an image). WordPad and the RichTextBox ignores every other control word (e.g. PNG, JPG etc.) for pictures in RTF. See page 148 in the RTF Specification
 
Now here is the problem: http://puu.sh/1XnKL.jpg (image was 19x25 so I zoomed a little bit)
 
- The left one is converted from PNG to WMF using ExRichTextBox
 
- The right one is directly added to WordPad (so WordPad did the conversion)
 
Now, in the ExRichTextBox class the conversion goes by the following:
 
1. Create EMF from the Bitmap/Image,
2. then convert to WMF by using unmanaged code functions (in GDI+ API)
3. finally convert the bytes to hexadecimals
 
Here is the code of the method which returns the WMF data when you pass an Image type. This method is in the ExRichTextBox class which inherits from System.Windows.Forms.RichTextBox:
 
[DllImportAttribute("gdiplus.dll")]
private static extern uint GdipEmfToWmfBits (IntPtr _hEmf, uint _bufferSize,
    byte[] _buffer, int _mappingMode, EmfToWmfBitsFlags _flags);
 

private string GetRtfImage(Image _image) {
 
    StringBuilder _rtf = null;
 
    // Used to store the enhanced metafile
    MemoryStream _stream = null;
 
    // Used to create the metafile and draw the image
    Graphics _graphics = null;
 
    // The enhanced metafile
    Metafile _metaFile = null;
 
    // Handle to the device context used to create the metafile
    IntPtr _hdc;
 
    try {
        _rtf = new StringBuilder();
        _stream = new MemoryStream();
 
        // Get a graphics context from the RichTextBox
        using(_graphics = this.CreateGraphics()) {
 
            // Get the device context from the graphics context
            _hdc = _graphics.GetHdc();
 
            // Create a new Enhanced Metafile from the device context
            _metaFile = new Metafile(_stream, _hdc);
 
            // Release the device context
            _graphics.ReleaseHdc(_hdc);
        }
 
        // Get a graphics context from the Enhanced Metafile
        using(_graphics = Graphics.FromImage(_metaFile)) {
 
            // Draw the image on the Enhanced Metafile
            _graphics.DrawImage(_image, new Rectangle(0, 0, _image.Width,
                _image.Height));
 
        }
 
        // Get the handle of the Enhanced Metafile
        IntPtr _hEmf = _metaFile.GetHenhmetafile();
 
        // A call to EmfToWmfBits with a null buffer return the size of the
        // buffer need to store the WMF bits.  Use this to get the buffer
        // size.
        uint _bufferSize = GdipEmfToWmfBits(_hEmf, 0, null, MM_ANISOTROPIC,
            EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
 
        // Create an array to hold the bits
        byte[] _buffer = new byte[_bufferSize];
 
        // A call to EmfToWmfBits with a valid buffer copies the bits into the
        // buffer an returns the number of bits in the WMF.  
        uint _convertedSize = GdipEmfToWmfBits(_hEmf, _bufferSize, _buffer,
            MM_ANISOTROPIC, EmfToWmfBitsFlags.EmfToWmfBitsFlagsDefault);
 
        // Append the bits to the RTF string
        for(int i = 0; i < _buffer.Length; ++i) {
            _rtf.Append(String.Format("{0:X2}", _buffer[i]));
        }
 
        return _rtf.ToString();
    }
    finally {
        if(_graphics != null)
            _graphics.Dispose();
        if(_metaFile != null)
            _metaFile.Dispose();
        if(_stream != null)
            _stream.Close();
    }
}
 
Question: WHY and WHERE does the image lose its quality?
Posted 5-Feb-13 1:52am
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 2

When you convert from EMF to WMF, you are converting from a 32bit file to 16bit file - What is WMF?[^].
As a result, loss in quality is unavoidable.
  Permalink  
Comments
QuantumHive at 5-Feb-13 13:35pm
   
If it is unavoidable, then how does Microsoft manage to do this in WordPad WITHOUT the quality loss? (As you can see clearly the difference in the image I provided)
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 3

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 )
        {
            // Check sign
            bool negative = false;
            if( aValue < 0 )
            {
                // Set negative flag and invert value
                negative = true;
                aValue = -aValue;
            }
 
            // Declare variables
            int real = aValue & ~0x3;
            int mod	 = aValue & 0x3;
 
            // Check if we must increment real with boundary base
            if( mod != 0 )
                real += 0x4;
 
            // Check if we must apply sign
            if( negative )
                return -real;
            else
                return real;
        }
 
        public static string ConvertToHex( int aValue, FormatHexStyles aHexFormat )
        {
            // Check lower range
            if( aValue < 0 )
                throw new ArgumentOutOfRangeException( string.Format(
                    "Invalid value '{0}' specified; negative values are not supported",
                    aValue ));
 
            // Declare variables
            string hex;
 
            // Check required format
            switch( aHexFormat )
            {
                case FormatHexStyles.LittleEndian2Bytes:
                    // Check upper range
                    if( aValue > 0xFFFF )
                        throw new ArgumentOutOfRangeException( string.Format(
                            "Invalid value '{0}' specified; allowed range [0..{1}]",
                            aValue,
                            0xFFFF ));
 
                    // Compute little endian 2 byte format
                    hex = 
                        HEX_X2_TABLE[aValue & 0xFF] + 
                        HEX_X2_TABLE[(aValue & 0xFF00) >> 8];
                    break;
 
                case FormatHexStyles.LittleEndian4Bytes:
                    // Check upper range
                    if( aValue > 0x7FFFFFFF )
                        throw new ArgumentOutOfRangeException( string.Format(
                            "Invalid value '{0}' specified; allowed range [0..{1}]",
                            aValue,
                            0x7FFFFFFF ));
 
                    // Compute little endian 4 byte format
                    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:
                    // Check upper range
                    if( aValue > 0xFFFF )
                        throw new ArgumentOutOfRangeException( string.Format(
                            "Invalid value '{0}' specified; allowed range [0..{1}]",
                            aValue,
                            0xFFFF ));
 
                    // Compute big endian 2 byte format
                    hex = 
                        HEX_X2_TABLE[(aValue & 0xFF00) >> 8] +
                        HEX_X2_TABLE[aValue & 0xFF];
                    break;
 
                case FormatHexStyles.BigEndian4Bytes:
                    // Check upper range
                    if( aValue > 0x7FFFFFFF )
                        throw new ArgumentOutOfRangeException( string.Format(
                            "Invalid value '{0}' specified; allowed range [0..{1}]",
                            aValue,
                            0x7FFFFFFF ));
 
                    // Compute big endian 4 byte format
                    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 converted hex
            return hex;
        }
 
        public static Size CalcSize( Image anImage, Size aNewSize )
        {
            // Check input
            if( anImage == null )
                return Size.Empty;
 
            // Declar variables
            Size size;
 
            // Determine resize with or without aspect
            if( aNewSize.Width <= 0 && 
                aNewSize.Height <= 0 )
            {
                // Keep original size
                size = new Size( anImage.Width, anImage.Height );
            }
            else if( 
                aNewSize.Width > 0 &&
                aNewSize.Height > 0 )
            {
                // Resize without aspect
                size = aNewSize;
            }
            else if( aNewSize.Height <= 0 )
            {
                // Compute new height with aspect
                float f = (float)aNewSize.Width / (float)anImage.Width;
                int   h = (int)((float)anImage.Height * f);
                size = new Size(  aNewSize.Width, h );
            }
            else
            {
                // Compute new with with aspect
                float f = (float)aNewSize.Height / (float)anImage.Height;
                int   w = (int)((float)anImage.Width * f);
                size = new Size( w, aNewSize.Height );
            }
 
            // Return computed size
            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 )
        {
            // Check input
            if( anImage == null )
                return "<No image available>";
 
            // Declare variables
            int	  intWidth	= anImage.Width;
            int   intHeight	= anImage.Height;
            float w			= intWidth;
            float h			= intHeight;
            float hmm		= HMM_PER_INCH;
            float twip		= TWIPS_PER_INCH;
 
            // Calculate effective image dimensions (in 0.01mm)
            int picw		= (int)Math.Round((w/aDpiX)*hmm);
            int pich		= (int)Math.Round((h/aDpiY)*hmm);
 
            // Compute new desired size of image
            SizeF size		= CalcSize(anImage, new Size(aWidth,aHeight));
 
            // Calculate requested image dimensions (in twips)
            int picwgoal	= (int)Math.Round((size.Width/aDpiX)*twip);
            int pichgoal	= (int)Math.Round((size.Height/aDpiY)*twip);
 
            // Calculate bytesize (DWORD aligned)
            int sizeA		= ((RoundTo4ByteBoundary(intWidth*3)*intHeight)/2)+56;
            int sizeB		= sizeA - 0x16;
            int sizeC		= RoundTo4ByteBoundary(intWidth*3)*intHeight;
 
            // Declare RTF masks
            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}";
 
            // Create headers
            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 ));
 
            // Obtain bitmap data
            BitmapData bmpData = anImage.LockBits( 
                new Rectangle(0, 0, intWidth, intHeight),
                ImageLockMode.ReadOnly, 
                anImage.PixelFormat );
 
            // Declare an array to hold the pixels of the bitmap. 
            uint[] pixelsARGB	= new uint[intWidth*intHeight];
 
            unsafe
            {
                // Read bitmap pixels
                switch( anImage.PixelFormat )
                {
                    case PixelFormat.Format32bppArgb:
                    case PixelFormat.Format32bppPArgb:
                    case PixelFormat.Format32bppRgb:
                        // Simply copy ARGB bitmap data into buffer
                        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];
                        }
 
                        // Release lock
                        anImage.UnlockBits( bmpData );
                        break;
 
                    case PixelFormat.Format8bppIndexed:
                        // Use palette to map ARGB values
                        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();
 
                        // Use color palette as source to create ARGB array
                        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]];
                        }
                        
                        // Release lock
                        anImage.UnlockBits( bmpData );
                        break;
 
                    default:
                        // Release lock first
                        anImage.UnlockBits( bmpData );
 
                        // In this case we default to the slow GetPixel method.
                        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;
                }
            }
 
            // Determine if we need to blend colors
            bool	doBlend	= aBackgroundColor != Color.Empty;
            float	backR	= aBackgroundColor.R;
            float	backG	= aBackgroundColor.G;
            float	backB	= aBackgroundColor.B;
 
            // Compute stride (i.e. 4byte alignment)
            int	alignRGB	= intWidth*3;
            int alignDWORD	= RoundTo4ByteBoundary(alignRGB);
            int alignStride	= alignDWORD-alignRGB;
 
            // Check if stride is required
            bool	hasStride	= alignStride > 0;
            string	stride		= null;
            if( hasStride )
            {
                // Align on DWORD boundary
                StringBuilder sbStride = new StringBuilder();
                for( int i = 0; i < alignStride; i++ )
                    sbStride.Append( HEX_X2_TABLE[0] );
                stride = sbStride.ToString();
            }
 
            // Create data buffer
            StringBuilder sbData = new StringBuilder();
            for( int y = intHeight-1; y >= 0; y-- )
            {
                // Iterate image from last row up to first row
                int yOffset = y*intWidth;
 
                // Iterate row from left pixel to right pixel
                for( int x = 0; x < intWidth; x++ )
                {
                    // Get ARGB pixel values
                    uint	pix	= pixelsARGB[yOffset+x];
                    float	dr	= (pix & 0x00FF0000) >> 16;
                    float	dg	= (pix & 0x0000FF00) >> 8;
                    float	db	= (pix & 0x000000FF);
 
                    // Blend pixel with background?
                    if( doBlend )
                    {
                        // Get A value and normaize (i.e. compute range [0..1])
                        float t	= ((float)((pix & 0xFF000000) >> 24))/255F;
 
                        // Convert RGB into delta's
                        dr	-= backR;
                        dg	-= backG;
                        db	-= backB;
 
                        // Mix backcolor with forecolor
                        dr	= (dr*t) + backR;
                        dg	= (dg*t) + backG;
                        db	= (db*t) + backB;
                    }
 
                    // Write hex RGB to buffer
                    sbData.Append( HEX_X2_TABLE[(int)db] );
                    sbData.Append( HEX_X2_TABLE[(int)dg] );
                    sbData.Append( HEX_X2_TABLE[(int)dr] );
                }
 
                // Align on DWORD boundary
                if( hasStride )
                    sbData.Append( stride );
            }
 
            // Flush stringbuilder
            string rtfBmpData = sbData.ToString();
 
            // Compose entire chunk
            string rtfChunk = string.Format(
                maskRtf,
                '{',
                rtfPicHeader,
                rtfWmfHeader,
                rtfBmpHeader,
                rtfBmpData,
                '}' );
 
            // Return result
            return rtfChunk;
        }
    }
  Permalink  
v2
Rate this: bad
good
Please Sign up or sign in to vote.

Solution 1

Yes. I agree with you. It definitely does lose quality. Good luck solving your problem.
  Permalink  

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

  Print Answers RSS
0 George Jonsson 215
1 Kornfeld Eliyahu Peter 169
2 OriginalGriff 120
3 PIEBALDconsult 110
4 BillWoodruff 85
0 OriginalGriff 6,165
1 DamithSL 4,658
2 Maciej Los 4,087
3 Kornfeld Eliyahu Peter 3,649
4 Sergey Alexandrovich Kryukov 3,294


Advertise | Privacy | Mobile
Web01 | 2.8.141220.1 | Last Updated 7 Nov 2013
Copyright © CodeProject, 1999-2014
All Rights Reserved. Terms of Service
Layout: fixed | fluid

CodeProject, 503-250 Ferrand Drive Toronto Ontario, M3C 3G8 Canada +1 416-849-8900 x 100