Sign in to follow this  
MJW

Basic Clipboard Transformations

Recommended Posts

This is a CodeLab plugin that provides the code to scale, rotate, and offset a clipboard image. As a plugin, it's not too useful, but it might provide a starting point for writing plugins that use the clipboard.

 

The DLL (in case you want to see what it does): Basic Clipboard Transformations.zip

 

The code:

Spoiler

 


// Name: Basic Clipboard Transformations
// Submenu: Test
// Author: MJW
// Title: Basic Clipboard Transformations
// Submenu: Test
// Version: 1.0.*
// Desc: Basic transformations of the clipboard image as a starting pooint for other plugins.
// Keywords: clipboard transformations
// URL:
// Help:
#region UICode
DoubleSliderControl Amount1 = 1; // [0,10] Clipboard Image Size
DoubleSliderControl Amount2 = 0; // [-1,1] Clipboard Image XY Proportion
PanSliderControl Amount3 = Pair.Create(0.000,0.000); // Clipboard Image Offset
DoubleSliderControl Amount4 = 1; // [0,2] Clipboard Image Offset Range
AngleControl Amount5 = 0; // [-180,180] Clipboard Image Rotation
CheckboxControl Amount6 = false; // [0,1] Offset Relative to Selection
#endregion

//-----------
// Constants.
//-----------
const double degToRad = Math.PI / 180.0;
const double MaxXYScale = 12.0f;  // Maximum XY Proportion scale factor.
readonly float XYPropConst = (float)((Math.Sqrt(MaxXYScale) + 1.0) / (Math.Sqrt(MaxXYScale) - 1.0));

//------------------
// Global variables.
//------------------
float Mxx, Mxy, Mxw, Myx, Myy, Myw;
bool showedCbErrorMsg = false;
bool noClipboardImg = true;

//*****************************************************************************************
//----------------------------------------------------------------------------
// Setup routine to initalize variable based on the control values.
// Called from OnSetRenderInfo after ProcessControlValues has been called.
//
// Because this is done before the render threads run, the "global" variables
// can be freely changed, unlike when the setup is done in the render threads.
//-----------------------------------------------------------------------------
void SetupRendering(Surface dst, Surface src)
{
    int width = dst.Width, height = dst.Height;

    //-------------------------------------------------------------------------------------
    // Assign meaningful variable names.
    // The Image Offset Range, if needed would often be a hardcoded value.
    // When the value is one, the clipboard image just disappears out of the canvas or the
    // the selection when the offsets are their maximum or minimum values. If a wider range
    // of motion is needed, the Image Offset Range value can be increased.
    //--------------------------------------------------------------------------------------
    double ImgScale = Amount1;  // Scale factor for image.
    double ImgXYProp = Amount2; // XY proportion -- stretches image in X or Y.
    Pair<double, double> ImgOffset = Amount3;   // Offset from center of canvas or selection
    double ImgOffsetRange = Amount4;    // Offset range. Normally not needed.
    double ImgRotation = Amount5;   // Counter-clockwise rotation.
    bool ImgOffsetToSel = Amount6;  // If true, image will be centered in selection.

    noClipboardImg = false;
    if (Img == null)
    {
        if (!showedCbErrorMsg)
        {
            MessageBox.Show("This plugin requires a Clipboard image.", "Basic Clipboard Transformations", MessageBoxButtons.OK, MessageBoxIcon.Error);
            showedCbErrorMsg = true;
        }

        // Maybe I should just render using white, but this (sort of) lets the user know
        // there's no clipboard image.
        noClipboardImg = true;
        return;
    }

    // Get the scale and offset for use when accessing the clipboard.
    // Scale for center of the selection.
    // Maximim shifts will move image just out of the selection (or window).
    float recipScale = (float)ImgScale;
    if (recipScale < 0.0005f)    // Don't let scaling be zero.
        recipScale = 0.0005f;
    float scale = 1.0f / recipScale;

    float imgXYProp = (float)ImgXYProp;
    float xPropScale = (XYPropConst - imgXYProp) / (XYPropConst + imgXYProp);
    float yPropScale = 1.0f / xPropScale;
    scale *= (float)Math.Sqrt(0.5 * (xPropScale * xPropScale + yPropScale * yPropScale));
    float xScale = scale * xPropScale;
    float yScale = scale * yPropScale;
    float shiftX = (float)(ImgOffsetRange * ImgOffset.First);
    float shiftY = (float)(ImgOffsetRange * ImgOffset.Second);

    // Set up to offset relative to window or selection.
    int winLeft, winRight, winTop, winBottom;
    if (ImgOffsetToSel)
    {
        // Selection.
        Rectangle selection = EnvironmentParameters.GetSelection(src.Bounds).GetBoundsInt();
        winLeft = selection.Left; winRight = selection.Right;
        winTop = selection.Top; winBottom = selection.Bottom;       
    }
    else
    {         
        // Window.
        winLeft = 0; winRight = width;
        winTop = 0; winBottom = height;
    }
    float winHalfWidth = 0.5f * (float)(winRight - winLeft);
    float winHalfHeight = 0.5f * (float)(winBottom - winTop);
    float winCenterX = 0.5f * (float)(winRight + winLeft);
    float winCenterY = 0.5f * (float)(winBottom + winTop);
    float imgHalfWidth = 0.5f * (float)Img.Width;
    float imgHalfHeight = 0.5f * (float)Img.Height;
    float bbHalfWidth = imgHalfWidth / xScale;
    float bbHalfHeight = imgHalfHeight / yScale;

    // Rotation
    if (ImgRotation != 0.0)
    {
        float cos, sin;
        double rotation = -degToRad * ImgRotation;
        cos = (float)Math.Cos(rotation); sin = (float)Math.Sin(rotation);

        // Compute the x, y scaling factors.
        Mxx = xScale * cos;
        Myy = yScale * cos;
        Mxy = xScale * sin;
        Myx = -yScale * sin;

        // Get the bounding rectangle, which determines the shift range.
        float absCos = (float)Math.Abs(cos), absSin = (float)Math.Abs(sin);
        float tempHalfWidth = bbHalfWidth;
        bbHalfWidth = absCos * bbHalfWidth + absSin * bbHalfHeight;
        bbHalfHeight = absCos * bbHalfHeight + absSin * tempHalfWidth;
    }
    else
    {
        // No rotation, so do it the easy way.
        Mxx = xScale; Myy = yScale; Mxy = Myx = 0.0f;
    }

    float tempX = winCenterX + shiftX * (bbHalfWidth + winHalfWidth);
    float tempY = winCenterY + shiftY * (bbHalfHeight + winHalfHeight);
    Mxw = imgHalfWidth - Mxx * tempX - Mxy * tempY;
    Myw = imgHalfHeight - Myx * tempX - Myy * tempY;
}

//*****************************************************************************************
//----------------------
// The rendering loops.
//----------------------
void Render(Surface dst, Surface src, Rectangle rect)
{
    SetupRendering(dst, src);
    
    int left = rect.Left, right = rect.Right, top = rect.Top, bottom = rect.Bottom;

    if (noClipboardImg)
    {
        for (int y = top; y < bottom; y++)
        {
            if (IsCancelRequested)
                return;             
            for (int x = left; x < right; x++)
            {
                dst[x, y] = src[x, y];
            }
        }
    }
    else
    {
        for (int y = top; y < bottom; y++)
        {
            if (IsCancelRequested)
                return;               
            for (int x = left; x < right; x++)
            {
                float fx = x, fy = y;
                float tx = Mxx * fx + Mxy * fy + Mxw;
                float ty = Myx * fx + Myy * fy + Myw;
                dst[x, y] = Img.GetBilinearSample(tx, ty);
            }
        }
    }
}

// Setup for getting an image from the clipboard
protected Surface Img
{
    get
    {
        if (_img != null)
            return _img;
        else
        {
            Thread t = new Thread(new ThreadStart(GetImageFromClipboard));
            t.SetApartmentState(ApartmentState.STA);
            t.Start();
            t.Join();
            return _img;
        }
    }
}
private Surface _img = null;
private void GetImageFromClipboard()
{
    Bitmap aimg = null;
    IDataObject clippy;
    try
    {
        clippy = Clipboard.GetDataObject();
        if (clippy != null)
        {
            if (Clipboard.ContainsData("PNG"))
            {
                // Handle bitmaps with transparency
                Object png_object = Clipboard.GetData("PNG");
                if (png_object is MemoryStream)
                {
                    MemoryStream png_stream = png_object as MemoryStream;
                    aimg = (Bitmap)Image.FromStream(png_stream);
                }
            }
            else if (clippy.GetDataPresent(DataFormats.Bitmap))
            {
                // If that didn't work, try bitmaps without transparency
                aimg = (Bitmap)clippy.GetData(typeof(Bitmap));
            }
        }
    }
    catch (Exception)
    {
    }
    if (aimg != null)
    {
        _img = Surface.CopyFromBitmap(aimg);
    }
    else
    {
        _img = null;
    }
}

 

 

 

 

  • Upvote 3

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this