Jump to content

GetPixel/SetPixel question


Mikal

Recommended Posts

Alright Im fairly new with C# (I downloaded CodeLab) and I wanted to write something that gets the RGB values of (x,y) then puts those values in another pixel (x,y). However I just realized that I wanted this to apply this to around 20,000 pixels lol. Any way I can copy/paste regions using C#?

Link to comment
Share on other sites

Perhaps this thread can be moved to Plugin Developer's Central, where it better fits.

 

To understand how plugins work, read the tutorials listed in the first comment in the Codelab thread.

 

Getting a pixel from an x, y location is done by assigning the source buffer value to a ColorBgra variable:

ColorBgra pixel;

pixel = src[x, y];

 

Setting a pixel is done by assigning the variable to location in the destination buffer:

 

dst[x, y] = pixel;

 

This is the basic CodeLab  rendering code:

#region UICode
int Amount1=0;    //[0,100]Slider 1 Description
int Amount2=0;    //[0,100]Slider 2 Description
int Amount3=0;    //[0,100]Slider 3 Description
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
    // Delete any of these lines you don't need
    Rectangle selection = EnvironmentParameters.GetSelection(src.Bounds).GetBoundsInt();
    int CenterX = ((selection.Right - selection.Left) / 2)+selection.Left;
    int CenterY = ((selection.Bottom - selection.Top) / 2)+selection.Top;
    ColorBgra PrimaryColor = (ColorBgra)EnvironmentParameters.PrimaryColor;
    ColorBgra SecondaryColor = (ColorBgra)EnvironmentParameters.SecondaryColor;
    int BrushWidth = (int)EnvironmentParameters.BrushWidth;

    ColorBgra CurrentPixel;
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        for (int x = rect.Left; x < rect.Right; x++)
        {
            CurrentPixel = src[x,y];
            // TODO: Add pixel processing code here
            // Access RGBA values this way, for example:
            // CurrentPixel.R = (byte)PrimaryColor.R;
            // CurrentPixel.G = (byte)PrimaryColor.G;
            // CurrentPixel.B = (byte)PrimaryColor.B;
            // CurrentPixel.A = (byte)PrimaryColor.A;
            dst[x,y] = CurrentPixel;
        }
    }
}


The thing that needs to be kept in mind is that the rendering code is called many times to render rectangles within the image. The rectangles are called Rectangles of Interest (ROIs). The pixels in the ROI represent destination pixels. You need to figure out how each destination pixel can be obtained. This sometimes complicates otherwise simple procedures.

 

To demonstrate, I wrote a plugin to do something along the lines of what I think you're trying to achieve. The user specifies a source location, a destination location, and a size. The plugin moves a rectangle of pixels whose upper-left corner is at the  source location to the rectangle at the destination location, while leaving the other pixels unchanged:

 

// Name: Move Rectangle
// Submenu: Test
// Author: MJW
// Title: Move Rectangle
// Version: 1.0
// Desc: Move a rectangle of pixels
// Keywords:
// URL:
// Help:
#region UICode
IntSliderControl Amount1 = 0; // [0,4000]Source X
IntSliderControl Amount2 = 0; // [0,4000]Source Y
IntSliderControl Amount3 = 50; // [0,4000]Destination X
IntSliderControl Amount4 = 50; // [0,4000]Destination Y
IntSliderControl Amount5 = 100; // [1,4000]Width
IntSliderControl Amount6 = 100; // [1,4000]Height
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
    int dstX = Amount3;
    int dstY = Amount4;
    int moveX = Amount1 - dstX;
    int moveY = Amount2 - dstY;
    int dstEndX = dstX + Amount5;
    int dstEndY = dstY + Amount6;
    int width = src.Width;
    int height = src.Height;
    
    ColorBgra CurrentPixel;
    
    
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        if ((y >= dstY) && (y < dstEndY))
        {
            // The row is within the destination rectange Y bounds; check for pixels also in the X bounds.
            for (int x = rect.Left; x < rect.Right; x++)
            {
                if ((x >= dstX) && (x < dstEndX))
                {
                    int srcX = x + moveX;
                    int srcY = y + moveY;
                    if ((srcX >= 0) && (srcX < width) && (srcY >= 0) && (srcY < height))
                    {
                        // Move pixel to the new rectange.
                        CurrentPixel = src[srcX, srcY];
                    }
                    else
                    {
                        // Source pixel is outside image.
                        CurrentPixel = ColorBgra.Transparent;
                    }         
                }
                else
                {
                    // The pixel is outside the X bounds of the destination rectangle.
                    CurrentPixel = src[x, y];
                }
                dst[x, y] = CurrentPixel;            
            }
        }  
        else
        {
            // The pixels are outside the Y bounds of the destination rectangle.
            for (int x = rect.Left; x < rect.Right; x++)
            {         
                dst[x, y] = src[x, y];
            }
        }        
    }         
}

The basic idea is that it goes through the pixels in the ROI. if the pixel is inside the destination rectangle, it copies the corresponding pixel from the source rectangle into the destination pixel. If the pixel is outside the destination rectangle, it copies the pixel from the same location from the source buffer. Note that for pixels inside the destination rectange, I need to test to see that the corresponding pixel in the source rectangle is inside the image. If it's not, I make it transparent.

 

  • Upvote 2
Link to comment
Share on other sites

@MJW that was beautiful! It worked perfectly. Thank you so much! The script only moves one selection however, and I didnt really think about it that much but I would have to re-edit this code 17 times (I need to move 17 regions into 17 other regions).  Im not sure if this would work but could I put the same function below it for a different region?

Link to comment
Share on other sites

@Mikal use CodeLab with this code to build your effect.

Close paint.net then find on your desktop the install"effect" and run it. It will copy the dll to the folder Effect.

Open paint.net and your effect should be available in a submenu (Test is you haven't changed it in the code).

Then you could change the parameters and see "live" the result in your canvas.

You shouldn't need to change the code.

Link to comment
Share on other sites

Moving more regions makes the job more complex (as you might imagine).

 

To do so, the first thing to do is to remove the optimization of testing the Y bounds outside the the inner X loop. With one region, that makes sense, because there's no reason to test rows of pixels that are outside the destination rectangle. With more than one region, it's far more trouble that it's worth. All the bounds-testing code should go inside the inner X loop.

 

What should the code do? It needs to loop through all the destination rectangles to see if the pixel is inside one of them. That requires testing against both the X and the Y bounds of each rectangle. Once a rectangle that contains the current pixel is found, the pixel from the corresponding source rectangle should be fetched as CurrentPixel, and the rectangle loop exited (probably with a break statement). Exiting isn't really necessary. It could continue to search all the rectangles. With exiting, the first rectangle has priority if destination rectangles overlap; without exiting, the last has priority.  Exiting is a bit more efficient. For simplicity, the source pixel at the original (X, Y) location should be loaded into CurrentPixel before entering the rectangle loop, so that if it makes it through all the destination rectangles without finding one that contains the current pixel, CurrentPixel will contain the correct value.

 

Probably the best way to write such a plugin is to first modify the original version by moving the Y-bounds test inside the X loop (which will actually slightly simplify the code). Once that works, change the single-rectangle test to a multiple-rectangle loop.

 

EDIT: There are plenty of variations. For instance, you could initialize a rectangleIndex variable to -1 before entering the bounds loop, and set it to the array index of the rectangle if one is found. The source pixel would then be loaded after the loop was exited. (The rectangle array would need to contain the bounds and the move-distances for each rectangle.  That's pretty much true no matter how you do it.)

 

EDIT 2: Just because I need to over-explain everything, I'll mention that for simplicity, instead of storing "move distances," it may be better to just store the source rectangle bounds in the array, then compute the srcX and srcY by adding the source bounds and subtracting the destination bounds. Saving two extra integer operations probably isn't worth the added complexity.

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...