---
title: "Convert an image to grayscale in C#"
url: https://recursiveai.org/oneimage/cookbook/convert-to-grayscale/
description: "Convert an image to grayscale in C# with one call, with correct luminance, as a true 8-bit gray file, or to pure black and white. Runnable code with OneImage, on any OS."
version: "1.10.4"
---

# Convert an image to grayscale in C#

Load the image, call `Grayscale()`, and save. The gray is computed from Rec. 709 luminance in linear light, which keeps the brightness of the original, rather than from an average of the channels. Pass `toGrayFormat: true` to write a one-channel gray file. The program below converts NASA’s “Blue Marble” photograph (public domain).

```shell
dotnet new console -o Grayscale && cd Grayscale
dotnet add package RecursiveAI.OneImage --version 1.10.4
curl -L -o photo.jpg \
  https://images-assets.nasa.gov/image/as17-148-22727/as17-148-22727~orig.jpg
```

OneImage needs the .NET 10 SDK. Put this in `Program.cs` and run `dotnet run`:

```csharp
using OneImage;

Image photo = Image.Load("photo.jpg");

// Gray, still stored as RGB (R = G = B).
photo.Grayscale().Save("gray-rgb.png");

// Gray in one 8-bit channel: a smaller file.
photo.Grayscale(toGrayFormat: true).Save("gray-8bit.png");

foreach (string file in new[] { "gray-rgb.png", "gray-8bit.png" })
{
    ImageInfo info = Image.Identify(file);
    long kb = new FileInfo(file).Length / 1024;
    Console.WriteLine($"{file}: {info.Frames[0].Format}, {kb:N0} KB");
}
```

```text
gray-rgb.png: Rgb/U8/None, 5,835 KB
gray-8bit.png: Gray/U8/None, 4,030 KB
```

Keep the default RGB layout when more steps follow, such as compositing a colored logo; ask for the gray format when the gray image is the output. Transparency is kept either way. The output format follows the extension, and the image streams through in tiles, so a large photo is never held whole in memory.

## Linear light or stored values

The classic `0.3 R + 0.59 G + 0.11 B` formula weighs the stored, gamma-encoded values, which makes saturated colors too dark. One pure red pixel shows the difference:

```csharp
using OneImage;

Image red = Image.FromPixels<Rgb24>(new[] { new Rgb24(255, 0, 0) }, 1, 1);

Rgb24 linear = red.Grayscale().ToArray<Rgb24>()[0];               // the default
Rgb24 stored = red.Grayscale(linear: false).ToArray<Rgb24>()[0];  // stored values
Console.WriteLine($"linear light: {linear.R}, stored values: {stored.R}");
```

```text
linear light: 127, stored values: 54
```

Pure red has about 21% of white’s luminance, which is a mid gray once encoded for the screen. Pass `linear: false` to match a tool that weighs stored values.

## Pure black and white

For fax, thermal printers, e-ink or OCR, every pixel must be black or white. `Threshold` cuts at a level; `BinaryDither` keeps the tones as dot patterns:

```csharp
using OneImage;

Image photo = Image.Load("photo.jpg").Resize(width: 1000);

photo.Threshold(0.5).Save("threshold.png");   // 50% luminance and up is white
photo.BinaryDither(Dither.FloydSteinberg).Save("dither.png");

foreach (string file in new[] { "threshold.png", "dither.png" })
{
    Console.WriteLine($"{file}: {new FileInfo(file).Length / 1024:N0} KB");
}
```

```text
threshold.png: 36 KB
dither.png: 53 KB
```

For a phone photo of a page, where one level turns the shadowed part black, `AdaptiveThreshold()` compares each pixel with its neighborhood instead.

## Limits

- **JPEG gains little** from a one-channel file: its encoder already spends few bits on color. PNG and TIFF gain more.
- **Coming from System.Drawing?** `GetPixel`/`SetPixel` loops and `ColorMatrix` code throw on Linux since .NET 6; see [the “not supported on this platform” error](https://recursiveai.org/oneimage/articles/system-drawing-common-not-supported/).

`Sepia()`, `BlackWhite()` and `Invert()` are one call each; every color operation is in the [guide](https://recursiveai.org/oneimage/docs/guide/#operations).
