Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for converting the format of a Bitmap #8827

Closed
reflectronic opened this issue Feb 28, 2021 · 12 comments · Fixed by #11101
Closed

Add support for converting the format of a Bitmap #8827

reflectronic opened this issue Feb 28, 2021 · 12 comments · Fixed by #11101
Assignees
Labels
api-approved (4) API was approved in API review, it can be implemented area-System.Drawing System.Drawing issues 🚧 work in progress Work that is current in progress
Milestone

Comments

@reflectronic
Copy link

reflectronic commented Feb 28, 2021

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);

Before and after sample of above code sample

API Proposal -- Updated: #8827 (comment)

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.

@reflectronic reflectronic added the api-suggestion (1) Early API idea and discussion, it is NOT ready for implementation label Feb 28, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Drawing System.Drawing issues untriaged The team needs to look at this issue in the next triage labels Feb 28, 2021
@ghost
Copy link

ghost commented Feb 28, 2021

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:

api-suggestion, area-System.Drawing, untriaged

Milestone: -

@JimBobSquarePants
Copy link

Spiral4x4,
Spiral8x8,
DualSpiral4x4,
DualSpiral8x8

I just wanna see what these look like!

@safern
Copy link
Member

safern commented Apr 8, 2021

@reflectronic I guess this would only work on Windows as this is not implemented in libgdiplus? What would the story for Unix be?

@safern safern removed the untriaged The team needs to look at this issue in the next triage label Apr 8, 2021
@safern safern added this to the Future milestone Apr 8, 2021
@reflectronic
Copy link
Author

What would the story for Unix be?

Setting myself up for a lot of work to contribute to libgdiplus :)

@safern
Copy link
Member

safern commented Apr 8, 2021

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.

@reflectronic
Copy link
Author

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:

#8833 also needs a small addition and needs re-review too (I have it posted as a comment).

@safern
Copy link
Member

safern commented Jul 16, 2021

Thanks, @reflectronic. Sure I will. I will mark these issues 7.0.0 for now so that we review them for .NET 7.

@safern safern removed this from the Future milestone Jul 16, 2021
@koszeggy
Copy link
Contributor

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.

@ViktorHofer ViktorHofer added this to the Future milestone Aug 4, 2022
@JeremyKuhne JeremyKuhne modified the milestones: Future, .NET 8.0 Mar 14, 2023
@JeremyKuhne JeremyKuhne transferred this issue from dotnet/runtime Mar 14, 2023
@JeremyKuhne JeremyKuhne modified the milestones: .NET 8.0, .NET 9.0 Aug 16, 2023
@JeremyKuhne JeremyKuhne self-assigned this Jan 31, 2024
@JeremyKuhne JeremyKuhne added the 🚧 work in progress Work that is current in progress label Jan 31, 2024
@JeremyKuhne
Copy link
Member

JeremyKuhne commented Feb 1, 2024

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 to CreateOptimalPalette
  • 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);
    }
}

@JeremyKuhne JeremyKuhne added api-ready-for-review (2) API is ready for formal API review; applied by the issue owner and removed api-suggestion (1) Early API idea and discussion, it is NOT ready for implementation labels Feb 1, 2024
JeremyKuhne added a commit to JeremyKuhne/winforms that referenced this issue Feb 1, 2024
This change implements Bitmap.ConvertFormat (see dotnet#8827). Until we get through API review some of the methods are under `[RequiresPreviewFeatures]`.
@JeremyKuhne
Copy link
Member

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

JeremyKuhne added a commit that referenced this issue Feb 1, 2024
This change implements Bitmap.ConvertFormat (see #8827). Until we get through API review some of the methods are under `[RequiresPreviewFeatures]`.
@JeremyKuhne
Copy link
Member

JeremyKuhne commented Feb 2, 2024

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
bill-gates-Format1bppIndexed
4BppIndexed
bill-gates-Format4bppIndexed
8bppIndexed
bill-gates-Format8bppIndexed
16bppArgb1555
bill-gates-Format16bppArgb1555
16bppRgb565
bill-gates-Format16bppRgb565

The original:
bill-gates

@terrajobst
Copy link
Member

terrajobst commented Feb 27, 2024

Video

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);
    }
}

@terrajobst terrajobst added api-approved (4) API was approved in API review, it can be implemented and removed api-ready-for-review (2) API is ready for formal API review; applied by the issue owner labels Feb 27, 2024
KlausLoeffelmann pushed a commit to KlausLoeffelmann/winforms that referenced this issue Mar 5, 2024
This change implements Bitmap.ConvertFormat (see dotnet#8827). Until we get through API review some of the methods are under `[RequiresPreviewFeatures]`.
JeremyKuhne added a commit to JeremyKuhne/winforms that referenced this issue Mar 21, 2024
This updates bitmap conversion and fixed color palette creation to match the final API approval and removes the preview flags.

Fixes dotnet#8827
JeremyKuhne added a commit that referenced this issue Mar 21, 2024
This updates bitmap conversion and fixed color palette creation to match the final API approval and removes the preview flags.

Fixes #8827
@github-actions github-actions bot locked and limited conversation to collaborators Apr 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved (4) API was approved in API review, it can be implemented area-System.Drawing System.Drawing issues 🚧 work in progress Work that is current in progress
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants