winforms
winforms copied to clipboard
Add support for converting the format of a Bitmap
Background
GDI+ 1.1 added support for converting the pixel format of an bitmap in-place. Naturally, one can specify a target pixel format, but the function accepts many other parameters to control aspects of the conversion. Unfortunately, the API is not very well designed — it's poorly documented, and many combinations of options seem to be outright ignored or result in vague errors — but a not insubstantial amount of people have wondered how to do this, and the workaround is slow, less flexible, and certainly not obvious.
When converting to an indexed pixel format, the ditherType
, paletteType
, and palette
parameters become relevant. Color palettes can be provided in order to constrain the colors in the converted image. There are effectively three categories of palettes:
- Custom palettes. All available colors are specified by the user.
- Optimal palettes. An optimal palette is created using an image, and consists of the best n colors (where n is specified by the user) from the image to be used when converting the image.
- Standard palettes. The user can specify one of many predefined fixed palettes, and may use them in combination with an ordered or spiral dither type to produce a halftone image.
When a standard fixed palette type is used, any dither type is valid. Otherwise, only None
, Solid
, and ErrorDiffusion
are valid. (As an exception, DitherTypeOrdered4x4
may be used when converting to a 16 bits-per-pixel format using any palette.) GDI+ will convert from a standard palette specified with paletteType
to a custom palette passed in palette
using a nearest-color conversion.
One can also specify an alpha threshold percent. Passing a value t specifies that a pixel that is less than t percent fully opaque will map to the transparent color. (If there is no transparent color, the color closest to black will be selected.)
This proposal is one of many to add missing GDI+ 1.1 functionality to System.Drawing
.
Usage Example
Bitmap bitmap = (Bitmap) Image.FromFile("immo.jpg");
ColorPalette palette = new ColorPalette(PaletteType.FixedHalftone8);
image.ConvertFormat(PixelFormat.Format8bppIndexed, DitherType.Ordered16x16, PaletteType.FixedHalftone8, palette);
API Proposal -- Updated: https://github.com/dotnet/winforms/issues/8827#issuecomment-1920392878
See the documentation for:
namespace System.Drawing.Imaging
{
+ public enum PaletteType
+ {
+ Custom,
+ Optimal,
+ FixedBW,
+ FixedHalftone8,
+ FixedHalftone27,
+ FixedHalftone64,
+ FixedHalftone125,
+ FixedHalftone216,
+ FixedHalftone252,
+ FixedHalftone256
+ }
+ public enum DitherType
+ {
+ None,
+ Solid,
+ ErrorDiffusion,
+ Ordered4x4,
+ Ordered8x8,
+ Ordered16x16,
+ Spiral4x4,
+ Spiral8x8,
+ DualSpiral4x4,
+ DualSpiral8x8
+ }
public sealed class ColorPalette
{
+ public ColorPalette(Color[] customColors);
+ public ColorPalette(PaletteType fixedPaletteType);
+ public static ColorPalette CreateOptimalPalette(int colors, Bitmap bitmap);
}
}
namespace System.Drawing
{
public sealed class Bitmap : System.Drawing.Image
{
! Overload for simplicity.
+ public void ConvertFormat(PixelFormat format);
+ public void ConvertFormat(PixelFormat format, DitherType ditherType, PaletteType paletteType, ColorPalette? palette, float alphaThresholdPercent);
}
}
This requires changes to libgdiplus in order to support it on non-Windows platforms.
Tagging subscribers to this area: @safern, @tarekgh See info in area-owners.md if you want to be subscribed.
Issue Details
Background
GDI+ 1.1 added support for converting the pixel format of an bitmap in-place. Naturally, one can specify a target pixel format, but the function accepts many other parameters to control aspects of the conversion. Unfortunately, the API is not very well designed — it's poorly documented, and many combinations of options seem to be outright ignored or result in vague errors — but a not insubstantial amount of people have wondered how to do this, and the workaround is slow, less flexible, and certainly not obvious.
When converting to an indexed pixel format, the ditherType
, paletteType
, and palette
parameters become relevant. Color palettes can be provided in order to constrain the colors in the converted image. There are effectively three categories of palettes:
- Custom palettes. All available colors are specified by the user.
- Optimal palettes. An optimal palette is created using an image, and consists of the best n colors (where n is specified by the user) from the image to be used when converting the image.
- Standard palettes. The user can specify one of many predefined fixed palettes, and may use them in combination with an ordered or spiral dither type to produce a halftone image.
When a standard fixed palette type is used, any ordered or spiral dither type is valid. Otherwise, only None
, Solid
, and ErrorDiffusion
are valid. (As an exception, DitherTypeOrdered4x4
may be used when converting to a 16 bits-per-pixel format using any palette.) GDI+ will convert from a standard palette specified with paletteType
to a custom palette passed in palette
using a nearest-color conversion.
One can also specify an alpha threshold percent. Passing a value t specifies that a pixel that is less than t percent fully opaque will map to the transparent color. (If there is no transparent color, the color closest to black will be selected.)
This proposal is one of many to add missing GDI+ 1.1 functionality to System.Drawing
.
API Proposal
See the documentation for:
namespace System.Drawing.Imaging
{
+ public enum PaletteType
+ {
+ Custom,
+ Optimal,
+ FixedBW,
+ FixedHalftone8,
+ FixedHalftone27,
+ FixedHalftone64,
+ FixedHalftone125,
+ FixedHalftone216,
+ FixedHalftone252,
+ FixedHalftone256
+ }
+ public enum DitherType
+ {
+ None,
+ Solid,
+ ErrorDiffusion,
+ Ordered4x4,
+ Ordered8x8,
+ Ordered16x16,
+ Spiral4x4,
+ Spiral8x8,
+ DualSpiral4x4,
+ DualSpiral8x8
+ }
public sealed class ColorPalette
{
+ public ColorPalette(Color[] customColors);
+ public ColorPalette(PaletteType fixedPaletteType);
+ public static ColorPalette CreateOptimalPalette(int colors, Bitmap bitmap);
}
}
namespace System.Drawing
{
public sealed class Bitmap : System.Drawing.Image
{
! I added the optional parameters. These seem like they are sensible defaults for .NET, but the native definition has no optional parameters.
+ public void ConvertFormat(PixelFormat format, DitherType ditherType = DitherType.None, PaletteType paletteType + PaletteType.Custom, ColorPalette? palette = null, float alphaThresholdPercent = 0);
}
}
This requires changes to libgdiplus in order to support it on non-Windows platforms.
Author: | reflectronic |
---|---|
Assignees: | - |
Labels: |
|
Milestone: | - |
Spiral4x4,
Spiral8x8,
DualSpiral4x4,
DualSpiral8x8
I just wanna see what these look like!
@reflectronic I guess this would only work on Windows as this is not implemented in libgdiplus? What would the story for Unix be?
What would the story for Unix be?
Setting myself up for a lot of work to contribute to libgdiplus :)
It is also OK to say these would be supported on Windows only and attribute them correctly so that the platform attributes catch that at compile time if someone tries to use these APIs on non-Windows.
Well, I suppose now that dotnet/designs#234 is out of the bag, that makes the Unix story a little easier. :-)
@safern Would you mind taking a look at some of the issues I’ve proposed and see if they can be marked ready for review? I’ll take up the implementation work for all of them. I know that you are busy, and that these issues are basically the lowest priority, so if you want to wait until 6.0 is fully locked down, I do not have a problem with that.
The full list should be:
- dotnet/winforms#8834
- dotnet/winforms#8835
- dotnet/winforms#8836
- dotnet/winforms#8837
- This one
- dotnet/winforms#8830
dotnet/winforms#8833 also needs a small addition and needs re-review too (I have it posted as a comment).
Thanks, @reflectronic. Sure I will. I will mark these issues 7.0.0
for now so that we review them for .NET 7.
I've just noticed this issue today. Shameless self promotion: I happen to have a library that supports converting pixel format of a GDI+ Bitmap
with optional quantizing and dithering.
It also supports async, parallelization (but it depends also on the ditherer), cancellation, reporting progress, can be used on Linux (though starting with .NET 6 it might need some tweaks) and does not require libgdiplus changes.
Minor tweaks from the original proposal.
- Not exposing
PaletteType.Optimal
as it is encapsulated in the API surface here. - 'params' on creating a custom color palette
- Add
useTransparentColor
toCreateOptimalPalette
- Add default parameters to
ConvertFormat
namespace System.Drawing.Imaging
{
+ public enum PaletteType
+ {
+ Custom,
+ FixedBW,
+ FixedHalftone8,
+ FixedHalftone27,
+ FixedHalftone64,
+ FixedHalftone125,
+ FixedHalftone216,
+ FixedHalftone252,
+ FixedHalftone256
+ }
+ public enum DitherType
+ {
+ None,
+ Solid,
+ ErrorDiffusion,
+ Ordered4x4,
+ Ordered8x8,
+ Ordered16x16,
+ Spiral4x4,
+ Spiral8x8,
+ DualSpiral4x4,
+ DualSpiral8x8
+ }
public sealed class ColorPalette
{
// The array is just used as is (not copied) and exposed in the Entries property, so we don't want to add a ReadOnlySpan overload
+ public ColorPalette(params Color[] customColors);
+ public ColorPalette(PaletteType fixedPaletteType);
+ public static ColorPalette CreateOptimalPalette(int colors, bool useTransparentColor, Bitmap bitmap);
}
}
namespace System.Drawing
{
public sealed class Bitmap : System.Drawing.Image
{
+ public void ConvertFormat(PixelFormat format);
+ public void ConvertFormat(PixelFormat format, DitherType ditherType, PaletteType paletteType = PaletteType.Custom, ColorPalette? palette = null, float alphaThresholdPercent = 0.0f);
}
}
@reflectronic Now that we've fully transferred ownership of System.Drawing to WinForms and transitioned to CsWin32 I'm pushing forward on getting these GDI+ features implemented. If you have any additional feedback please let me know.
Using these "auto-picked" arguments:
public void ConvertFormat(PixelFormat format)
{
PixelFormat currentFormat = PixelFormat;
int targetSize = ((int)format >> 8) & 0xff;
int sourceSize = ((int)currentFormat >> 8) & 0xff;
if (!format.HasFlag(PixelFormat.Indexed))
{
ConvertFormat(format, targetSize > sourceSize ? DitherType.None : DitherType.Solid);
return;
}
int paletteSize = targetSize switch { 1 => 2, 4 => 16, _ => 256 };
bool hasAlpha = format.HasFlag(PixelFormat.Alpha);
if (hasAlpha)
{
paletteSize++;
}
ColorPalette palette = ColorPalette.CreateOptimalPalette(paletteSize, hasAlpha, this);
ConvertFormat(format, DitherType.ErrorDiffusion, PaletteType.Custom, palette, .25f);
}
Here are some results:
1BppIndexed
4BppIndexed
8bppIndexed
16bppArgb1555
16bppRgb565
The original:
namespace System.Drawing.Imaging
{
public enum PaletteType
{
Custom,
FixedBlackAndWhite,
FixedHalftone8,
FixedHalftone27,
FixedHalftone64,
FixedHalftone125,
FixedHalftone216,
FixedHalftone252,
FixedHalftone256
}
public enum DitherType
{
None,
Solid,
ErrorDiffusion,
Ordered4x4,
Ordered8x8,
Ordered16x16,
Spiral4x4,
Spiral8x8,
DualSpiral4x4,
DualSpiral8x8
}
public sealed class ColorPalette
{
public ColorPalette(params Color[] customColors);
public ColorPalette(PaletteType fixedPaletteType);
public static ColorPalette CreateOptimalPalette(int colors,
bool useTransparentColor,
Bitmap bitmap);
}
}
namespace System.Drawing
{
public sealed class Bitmap : System.Drawing.Image
{
public void ConvertFormat(PixelFormat format);
public void ConvertFormat(PixelFormat format,
DitherType ditherType,
PaletteType paletteType = PaletteType.Custom,
ColorPalette? palette = null,
float alphaThresholdPercent = 0.0f);
}
}