Jump to content

Basic Clipboard Transformations


MJW

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
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...