---
title: "Apply a perspective transform to an image in C#"
url: https://recursiveai.org/oneimage/cookbook/perspective-transform-image/
description: "Map four image corners with OneImage Perspective in C#. Set corner order, expand the output canvas, and save a transformed PNG with runnable code."
version: "1.11.1"
---

# Apply a perspective transform to an image in C#

**OneImage** is a free, open-source image library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneImage) · [GitHub](https://github.com/recursive-ai-oss/oneimage-csharp) · [Docs](https://recursiveai.org/oneimage/docs/) · [Download example (.zip)](https://recursiveai.org/oneimage/cookbook/perspective-transform-image/perspective-transform-image.zip)

Map an image to four destination corners you supply; corners are not detected automatically.

```shell
dotnet add package RecursiveAI.OneImage
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using OneImage;

// input.png is in the zip.
Image.Load("input.png").Perspective(
    [(30, 10), (210, 30),
     (190, 170), (10, 140)])
    .Save("perspective.png");
Console.WriteLine("Wrote perspective.png");
```

```text
Wrote perspective.png
```

Unzip [the example](https://recursiveai.org/oneimage/cookbook/perspective-transform-image/perspective-transform-image.zip), then `dotnet run`.

## The full program

The example creates a 200 × 140 white image with a blue rectangle, a red stripe, and a 20-pixel border. The pixel construction below defines the complete input, so you can also run it without a sample file.

```csharp
using OneImage;

// Blue rectangle with a red stripe on white.
const int width = 200, height = 140;
var pixels = new Rgba32[width * height];
var white = new Rgba32(255, 255, 255, 255);
var blue = new Rgba32(20, 100, 220, 255);
var red = new Rgba32(220, 40, 30, 255);
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
    bool inside = x >= 20 && x < 180
        && y >= 20 && y < 120;
    bool stripe = x >= 90 && x < 110;
    pixels[y * width + x] = inside
        ? (stripe ? red : blue)
        : white;
}
Image.FromPixels<Rgba32>(pixels, width, height)
    .Save("input.png");

Image warped = Image.Load("input.png")
    .Perspective(
        [(30, 10), (210, 30),
         (190, 170), (10, 140)]);
warped.Save("perspective.png");
Console.WriteLine(
    $"perspective.png: {warped.Width}" +
    $"x{warped.Height}");
```

`dotnet run` prints:

```text
perspective.png: 200x160
```

`Perspective` is lazy and `Save` renders it. Points that are not four finite corners of a convex quadrilateral throw `ArgumentException`.

## Variations

**Keep the original canvas size.** Set `expand: false` and fill uncovered areas with white:

```csharp
using OneImage;

Image warped = Image.Load("input.png").Perspective(
    [(30, 10), (210, 30), (190, 170), (10, 140)],
    expand: false,
    background: new Rgba32(255, 255, 255, 255));
warped.Save("fixed-canvas.png");
Console.WriteLine($"fixed-canvas.png: {warped.Width}x{warped.Height}");
```

```text
fixed-canvas.png: 200x140
```

**Straighten four source corners into a rectangle.** Run the full program first to create `perspective.png`. Its known corner coordinates below are relative to the expanded image’s origin. For a photographed document, replace them with your measured document corners, in perimeter order, and choose the desired rectangle size. This does not detect corners automatically.

```csharp
using OneImage;

var matrix = ProjectiveMatrix.FromQuads(
    [(20, 0), (200, 20),
     (180, 160), (0, 130)],
    [(0, 0), (200, 0),
     (200, 140), (0, 140)]);
Image rectified = Image.Load("perspective.png")
    .Perspective(matrix, expand: false)
    .Crop(0, 0, 200, 140);
rectified.Save("straightened.png");
Console.WriteLine(
    $"straightened.png: {rectified.Width}" +
    $"x{rectified.Height}");
```

```text
straightened.png: 200x140
```

`FromQuads` maps source points to destination points. `expand: false` keeps the input canvas while applying that mapping; `Crop` selects the 200 × 140 destination rectangle. The [geometry guide](https://recursiveai.org/oneimage/docs/guide/#geometry) also describes the matrix and transform-builder overloads.

## Related recipes

- [Blur an image](https://recursiveai.org/oneimage/cookbook/blur-image/)
- [Remove a uniform image border](https://recursiveai.org/oneimage/cookbook/trim-image-border/)
- [Create a thumbnail](https://recursiveai.org/oneimage/cookbook/create-thumbnail/)

## The short answer

Call `Image.Perspective` with four destination points in this order: top-left, top-right, bottom-right, bottom-left. Save the returned image to render the transform. By default, OneImage expands the canvas to the transformed bounding box and fills uncovered areas with transparent black.

## How do I do a perspective transform on an image in C#?

The snippet at the top maps the whole source rectangle to a quadrilateral. Its points are destination coordinates, not four source coordinates to crop from a photograph. The source is the entire 200 × 140 sample; the destination is the four-point shape shown in the code. OneImage calculates the projective mapping and samples the source for each output pixel.

The returned image is a normal OneImage image, so the example saves it as PNG. PNG retains the transparent areas outside the mapped shape. Use an explicit opaque background, as in the variation, if you want those areas filled with a color instead.

This operation changes geometry. It does not find a page, recognize text, or decide which corners belong to a document in a photograph. Supply a matrix or corner points computed by your application when you need a particular mapping.

### What order should the four perspective corners be in?

Use top-left, top-right, bottom-right, then bottom-left, following the source rectangle around its perimeter. The [geometry guide](https://recursiveai.org/oneimage/docs/guide/#geometry) gives the same order. Crossing the point order can create an invalid shape instead of the transform you intended. The four points must be finite and form a convex quadrilateral.

Coordinates describe pixel edges, not the centers of the corner pixels. The [published API](https://github.com/recursive-ai-oss/oneimage-csharp/blob/a0650f30eb30585a927ff4af0ae7d7e1dc7400cf/src/OneImage/Image.Perspective.cs) describes a source rectangle extending to the image’s full width and height. This matters when you construct a matrix yourself rather than using the four-point overload.

OneImage also accepts a `ProjectiveMatrix` or a `ProjectiveTransformBuilder`. Those forms are useful when the application already has a transform or is composing geometric operations. They do not change the meaning of the four-point overload into automatic corner detection.

### Why does a perspective transform change the image size?

With `expand: true`, the default, the output covers the transformed bounds. In the example, destination x coordinates run from 10 to 210 and y coordinates from 10 to 170, giving a 200 × 160 result. The output origin moves to the minimum corner of that bounding box. A pure translation therefore does not create an extra margin when expansion is enabled.

Use `expand: false` to retain the source dimensions. The variation writes 200 × 140 pixels, so parts of the destination quadrilateral outside that canvas are clipped. It is the right option for a fixed-size layout, not a guarantee that every transformed point remains visible.

The default sampling filter is Catmull-Rom, with linear-light, premultiplied-alpha processing. For substantial reductions, resize before applying the perspective transform: the API documents that its sampling kernels are not widened for downscaling. An output beyond the engine’s image-size limits throws `ImageLimitException`.
