Skip to content

Commit

Permalink
Implement ConvertFormat for Bitmap
Browse files Browse the repository at this point in the history
This change implements Bitmap.ConvertFormat (see dotnet#8827). Until we get through API review some of the methods are under `[RequiresPreviewFeatures]`.
  • Loading branch information
JeremyKuhne committed Feb 1, 2024
1 parent 27713b2 commit 8a8e48e
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 68 deletions.
5 changes: 5 additions & 0 deletions src/System.Drawing.Common/src/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@
global using PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode;
global using TextRenderingHint = System.Drawing.Text.TextRenderingHint;

#if NET9_0_OR_GREATER
global using DitherType = System.Drawing.Imaging.DitherType;
global using PaletteType = System.Drawing.Imaging.PaletteType;
#endif

global using GdiPlus = Windows.Win32.Graphics.GdiPlus;
6 changes: 6 additions & 0 deletions src/System.Drawing.Common/src/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ ColorLUTParams
ColorMatrixEffectGuid
CreateIconFromResourceEx
DeviceCapabilities
DitherType
DMBIN_*
DMPAPER_*
DMRES_*
Expand Down Expand Up @@ -52,6 +53,7 @@ GdipAddPathString
GdipBeginContainer
GdipBeginContainer2
GdipBitmapApplyEffect
GdipBitmapConvertFormat
GdipBitmapGetPixel
GdipBitmapLockBits
GdipBitmapSetPixel
Expand Down Expand Up @@ -362,6 +364,7 @@ GdipImageGetFrameDimensionsCount
GdipImageGetFrameDimensionsList
GdipImageRotateFlip
GdipImageSelectActiveFrame
GdipInitializePalette
GdipInvertMatrix
GdipIsClipEmpty
GdipIsEmptyRegion
Expand Down Expand Up @@ -556,6 +559,9 @@ LANG_JAPANESE
LevelsEffectGuid
LevelsParams
PALETTEENTRY
PaletteFlags
PaletteType
PixelFormat*
PRINTER_ENUM_CONNECTIONS
PRINTER_ENUM_LOCAL
PRINTER_INFO_4W
Expand Down
66 changes: 66 additions & 0 deletions src/System.Drawing.Common/src/System/Drawing/Bitmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -380,5 +380,71 @@ public void ApplyEffect(Effect effect, Rectangle area = default)
GC.KeepAlive(this);
GC.KeepAlive(effect);
}

/// <summary>
/// Converts the bitmap to the specified <paramref name="format"/> using the given <paramref name="ditherType"/>.
/// The original pixel data is replaced with the new format.
/// </summary>
/// <param name="format">The new pixel format.</param>
/// <param name="ditherType">
/// The dithering algorithm. Pass <see cref="DitherType.None"/> when the conversion does not reduce the bit depth
/// of the pixel data.
/// </param>
/// <param name="paletteType">
/// The palette type to use when the pixel format is indexed.
/// </param>
/// <param name="palette">
/// Pointer to a <see cref="ColorPalette"/> that specifies the palette whose indexes are stored in the pixel data
/// of the converted bitmap. This palette (called the actual palette) does not have to have the type specified by
/// the <paramref name="paletteType"/> parameter. The <paramref name="paletteType"/> parameter specifies a standard
/// palette that can be used by any of the ordered or spiral dithering algorithms. If the actual palette has a type
/// other than that specified by the <paramref name="paletteType"/> parameter, then
/// <see cref="ConvertFormat(PixelFormat, DitherType, PaletteType, ColorPalette?, float)"/> performs a nearest-color
/// conversion from the standard palette to the actual palette.
/// </param>
/// <param name="alphaThresholdPercent">
/// Real number in the range 0 through 100 that specifies which pixels in the source bitmap will map to the
/// transparent color in the converted bitmap. A value of 0 specifies that none of the source pixels map to the
/// transparent color. A value of 100 specifies that any pixel that is not fully opaque will map to the transparent
/// color. A value of t specifies that any source pixel less than t percent of fully opaque will map to the
/// transparent color. Note that for the alpha threshold to be effective, the palette must have a transparent
/// color. If the palette does not have a transparent color, pixels with alpha values below the threshold will
/// map to color that most closely matches (0, 0, 0, 0), usually black.
/// </param>
[RequiresPreviewFeatures]
public void ConvertFormat(
PixelFormat format,
DitherType ditherType,
PaletteType paletteType,
ColorPalette? palette,
float alphaThresholdPercent)
{
if (palette is null)
{
PInvoke.GdipBitmapConvertFormat(
this.Pointer(),
(int)format,
(GdiPlus.DitherType)ditherType,
(GdiPlus.PaletteType)paletteType,
null,
alphaThresholdPercent).ThrowIfFailed();
}
else
{
using var buffer = palette.ConvertToBuffer();
fixed (void* b = buffer)
{
PInvoke.GdipBitmapConvertFormat(
this.Pointer(),
(int)format,
(GdiPlus.DitherType)ditherType,
(GdiPlus.PaletteType)paletteType,
(GdiPlus.ColorPalette*)b,
alphaThresholdPercent).ThrowIfFailed();
}
}

GC.KeepAlive(this);
}
#endif
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Versioning;

namespace System.Drawing.Imaging;

/// <summary>
Expand All @@ -21,16 +23,40 @@ public sealed unsafe class ColorPalette
/// </summary>
public Color[] Entries => _entries;

internal ColorPalette(int count) => _entries = new Color[count];

internal ColorPalette() => _entries = new Color[1];

private ColorPalette(int flags, Color[] entries)
{
_flags = flags;
_entries = entries;
}

#if NET9_0_OR_GREATER
/// <summary>
/// Create a custom color palette.
/// </summary>
/// <param name="entries">Color entries for the palette.</param>
public ColorPalette(params Color[] entries) : this(0, entries)
{
}

/// <summary>
/// Create a standard color palette.
/// </summary>
public ColorPalette(PaletteType paletteType)
{
ColorPalette palette = InitializePalette(paletteType, 0, useTransparentColor: false, bitmap: null);
_flags = palette.Flags;
_entries = palette.Entries;
}

/// <summary>
/// Create an optimal color palette based on the colors in a given bitmap.
/// </summary>
/// <inheritdoc cref="InitializePalette(PaletteType, int, bool, IPointer{GpBitmap}?)"/>
[RequiresPreviewFeatures]
public static ColorPalette CreateOptimalPalette(int colorCount, bool useTransparentColor, Bitmap bitmap) =>
InitializePalette((PaletteType)GdiPlus.PaletteType.PaletteTypeOptimal, colorCount, useTransparentColor, bitmap);
#endif

// Memory layout is:
// UINT Flags
// UINT Count
Expand All @@ -55,4 +81,37 @@ internal BufferScope<uint> ConvertToBuffer()

return buffer;
}

#if NET9_0_OR_GREATER
/// <summary>
/// Initializes a standard, optimal, or custom color palette.
/// </summary>
/// <param name="paletteType">The palette type.</param>
/// <param name="colorCount">
/// The number of colors you want to have in an optimal palette based on a the specified bitmap.
/// </param>
/// <param name="useTransparentColor"><see langword="true"/> to include the transparent color in the palette.</param>
internal static ColorPalette InitializePalette(
PaletteType paletteType,
int colorCount,
bool useTransparentColor,
IPointer<GpBitmap>? bitmap)
{
// Reserve the largest possible buffer for the palette.
using BufferScope<uint> buffer = new(256 + sizeof(GdiPlus.ColorPalette) / sizeof(uint));
buffer[1] = 256;
fixed (void* b = buffer)
{
PInvoke.GdipInitializePalette(
(GdiPlus.ColorPalette*)b,
(GdiPlus.PaletteType)paletteType,
colorCount,
useTransparentColor,
bitmap is null ? null : bitmap.Pointer).ThrowIfFailed();
}

GC.KeepAlive(bitmap);
return ConvertFromBuffer(buffer);
}
#endif
}
70 changes: 70 additions & 0 deletions src/System.Drawing.Common/src/System/Drawing/Imaging/DitherType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NET9_0_OR_GREATER

namespace System.Drawing.Imaging;

/// <summary>
/// Algorithm for dithering images with a reduced color palette.
/// </summary>
public enum DitherType
{
/// <summary>
/// No dithering is performed. Pixels in the source bitmap are mapped to the nearest color in the palette specified
/// by the palette parameter of the <see cref="Bitmap.ConvertFormat(PixelFormat, DitherType, PaletteType, ColorPalette?, float)"/>
/// method. This algorithm can be used with any palette other than <see cref="PaletteType.Custom"/>.
/// </summary>
None = GdiPlus.DitherType.DitherTypeNone,

/// <summary>
/// No dithering is performed. Pixels in the source bitmap are mapped to the nearest color in the palette specified
/// by the palette parameter of the <see cref="Bitmap.ConvertFormat(PixelFormat, DitherType, PaletteType, ColorPalette?, float)"/>
/// method. This algorithm can be used with any palette.
/// </summary>
Solid = GdiPlus.DitherType.DitherTypeSolid,

/// <summary>
/// You can use this algorithm to perform dithering based on the colors in one of the standard fixed palettes. You
/// can also use this algorithm to convert a bitmap to a 16-bits-per-pixel format that has no palette.
/// </summary>
Ordered4x4 = GdiPlus.DitherType.DitherTypeOrdered4x4,

/// <summary>
/// Dithering is performed using the colors in one of the standard fixed palettes.
/// </summary>
Ordered8x8 = GdiPlus.DitherType.DitherTypeOrdered8x8,

/// <summary>
/// Dithering is performed using the colors in one of the standard fixed palettes.
/// </summary>
Ordered16x16 = GdiPlus.DitherType.DitherTypeOrdered16x16,

/// <summary>
/// Dithering is performed using the colors in one of the standard fixed palettes.
/// </summary>
Spiral4x4 = GdiPlus.DitherType.DitherTypeSpiral4x4,

/// <summary>
/// Dithering is performed using the colors in one of the standard fixed palettes.
/// </summary>
Spiral8x8 = GdiPlus.DitherType.DitherTypeSpiral8x8,

/// <summary>
/// Dithering is performed using the colors in one of the standard fixed palettes.
/// </summary>
DualSpiral4x4 = GdiPlus.DitherType.DitherTypeDualSpiral4x4,

/// <summary>
/// Dithering is performed using the colors in one of the standard fixed palettes.
/// </summary>
DualSpiral8x8 = GdiPlus.DitherType.DitherTypeDualSpiral8x8,

/// <summary>
/// Dithering is performed based on the palette specified by the palette parameter of the
/// <see cref="Bitmap.ConvertFormat(PixelFormat, DitherType, PaletteType, ColorPalette?, float)"/> method.
/// This algorithm can be used with any palette.
/// </summary>
ErrorDiffusion = GdiPlus.DitherType.DitherTypeErrorDiffusion
}
#endif
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Drawing.Imaging;

/// <summary>
/// Specifies the type of color data in the system palette. The data can be color data with alpha, grayscale only,
/// or halftone data.
/// Specifies the type of color data in the system palette. The data can be color data with alpha, grayscale only,
/// or halftone data.
/// </summary>
[Flags]
public enum PaletteFlags
{
/// <summary>
/// Specifies alpha data.
/// Specifies alpha data.
/// </summary>
HasAlpha = 0x0001,
HasAlpha = GdiPlus.PaletteFlags.PaletteFlagsHasAlpha,

/// <summary>
/// Specifies grayscale data.
/// Specifies grayscale data.
/// </summary>
GrayScale = 0x0002,
GrayScale = GdiPlus.PaletteFlags.PaletteFlagsGrayScale,

/// <summary>
/// Specifies halftone data.
/// Specifies halftone data.
/// </summary>
Halftone = 0x0004
Halftone = GdiPlus.PaletteFlags.PaletteFlagsHalftone
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NET9_0_OR_GREATER

namespace System.Drawing.Imaging;

/// <summary>
/// Color palette types.
/// </summary>
public enum PaletteType
{
/// <summary>
/// An arbitrary custom palette specified by the caller.
/// </summary>
Custom = GdiPlus.PaletteType.PaletteTypeCustom,

/// <summary>
/// A palette that has two colors. This palette type is suitable for bitmaps that store 1 bit per pixel.
/// </summary>
FixedBW = GdiPlus.PaletteType.PaletteTypeFixedBW,

/// <summary>
/// A palette based on two intensities each (off or full) for the red, green, and blue channels. Also contains the
/// 16 colors of the system palette. Because all eight of the on/off combinations of red, green, and blue are
/// already in the system palette, this palette is the same as the system palette. This palette type is suitable
/// for bitmaps that store 4 bits per pixel.
/// </summary>
FixedHalftone8 = GdiPlus.PaletteType.PaletteTypeFixedHalftone8,

/// <summary>
/// A palette based on three intensities each for the red, green, and blue channels. Also contains the 16 colors of
/// the system palette. Eight of the 16 system palette colors are among the 27 three-intensity combinations of red,
/// green, and blue, so the total number of colors in the palette is 35. If the palette also includes the transparent
/// color, the total number of colors is 36.
/// </summary>
FixedHalftone27 = GdiPlus.PaletteType.PaletteTypeFixedHalftone27,

/// <summary>
/// A palette based on four intensities each for the red, green, and blue channels. Also contains the 16 colors of
/// the system palette. Eight of the 16 system palette colors are among the 64 four-intensity combinations of red,
/// green, and blue, so the total number of colors in the palette is 72. If the palette also includes the transparent
/// color, the total number of colors is 73.
/// </summary>
FixedHalftone64 = GdiPlus.PaletteType.PaletteTypeFixedHalftone64,

/// <summary>
/// A palette based on five intensities each for the red, green, and blue channels. Also contains the 16 colors of
/// the system palette. Eight of the 16 system palette colors are among the 125 five-intensity combinations of red,
/// green, and blue, so the total number of colors in the palette is 133. If the palette also includes the transparent
/// color, the total number of colors is 134.
/// </summary>
FixedHalftone125 = GdiPlus.PaletteType.PaletteTypeFixedHalftone125,

/// <summary>
/// A palette based on six intensities each for the red, green, and blue channels. Also contains the 16 colors of
/// the system palette. Eight of the 16 system palette colors are among the 216 six-intensity combinations of red,
/// green, and blue, so the total number of colors in the palette is 224. If the palette also includes the transparent
/// color, the total number of colors is 225. This palette is sometimes called the Windows halftone palette or
/// the Web palette.
/// </summary>
FixedHalftone216 = GdiPlus.PaletteType.PaletteTypeFixedHalftone216,

/// <summary>
/// A palette based on 6 intensities of red, 7 intensities of green, and 6 intensities of blue. The system palette
/// is not included. The total number of colors is 252. If the palette also includes the transparent color, the
/// total number of colors is 253.
/// </summary>
FixedHalftone252 = GdiPlus.PaletteType.PaletteTypeFixedHalftone252,

/// <summary>
/// A palette based on 8 intensities of red, 8 intensities of green, and 4 intensities of blue. The system palette
/// is not included. The total number of colors is 256. If the transparent color is included in this palette, it
/// must replace one of the RGB combinations.
/// </summary>
FixedHalftone256 = GdiPlus.PaletteType.PaletteTypeFixedHalftone256
}
#endif
Loading

0 comments on commit 8a8e48e

Please sign in to comment.