且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

在RichTextBox中插入图像而不会丢失质量(WMF)

更新时间:2023-02-13 22:14:14

当你从EMF转换为WMF时,你是从32位文件转换为16位文件 - 什么是WMF? [ ^ ]。

因此,质量损失是不可避免的。
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.


这是我用来将图像编码成RTF的代码。

这不是***的代码,但它完成了工作。





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;
    }
}


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