Skip to content

Add Gray8 and Gray16 Pixel Formats #718

Description

@JimBobSquarePants

Currently if someone wants to read/write opaque grayscale png files they need to convert the image to and from Rgb24 or Rgba32 which requires the user to consume more memory than necessary.

https://stackoverflow.com/questions/52407878/load-and-save-opaque-8-bit-png-files-using-imagesharp?rq=1

I propose we do the following:

Create the following IPixel implementations Gray8 and Gray16.

Gray8

/// <summary>
/// Packed pixel type containing one 8-bit unsigned normalized value ranging from 0 to 255.
// <para>
/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form.
/// </para>
/// </summary>
public struct Gray8 : IPixel<Gray8>
{
    /// <summary>
    /// Gets or sets the luminance component.
    /// </summary>
    public ushort L;

   // etc...
}

Gray16

/// <summary>
/// Packed pixel type containing one 16-bit unsigned normalized value ranging from 0 to 635535.
/// <para>
/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form.
/// </para>
/// </summary>
public struct Gray16 : IPixel<Gray16>
{
    /// <summary>
    /// Gets or sets the luminance component.
    /// </summary>
    public ushort L;

   // etc...
}

Add the additional methods to IPixel

/// <summary>
/// Packs the pixel from an <see cref="Gray8"/> value.
/// </summary>
/// <param name="source">The <see cref="Gray8"/> value.</param>
void PackFromGray8(Gray8 source);

/// <summary>
/// Converts the pixel to <see cref="Gray8"/> format.
/// </summary>
/// <param name="dest">The destination pixel to write to.</param>
void ToGray8(ref Gray8 dest)

/// <summary>
/// Packs the pixel from an <see cref="Gray16"/> value.
/// </summary>
/// <param name="source">The <see cref="Gray16"/> value.</param>
void PackFromGray16(Gray16 source);

/// <summary>
/// Converts the pixel to <see cref="Gray16"/> format.
/// </summary>
/// <param name="dest">The destination pixel to write to.</param>
void ToGray16(ref Gray16 dest)

Add the following methods to PixelOperations (These can be added to the T4 template).

/// <summary>
/// A helper for <see cref="PackFromGray8(ReadOnlySpan{Gray8}, Span{TPixel}, int)"/> that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with <see cref="Gray8"/> layout.
/// </summary>
/// <param name="sourceBytes">The <see cref="ReadOnlySpan{T}"/> to the source bytes.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>
internal void PackFromGray8Bytes(ReadOnlySpan<byte> sourceBytes, Span<TPixel> destPixels, int count);
		
/// <summary>
/// Converts 'count' pixels in 'sourcePixels` span to a span of <see cref="Gray8"/>-s.
/// Bulk version of <see cref="IPixel.ToGray8(ref Gray8)"/>.
/// </summary>
/// <param name="sourcePixels">The span of source pixels</param>
/// <param name="dest">The destination span of <see cref="Gray8"/> data.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void ToGray8(ReadOnlySpan<TPixel> sourcePixels, Span<Gray8> dest, int count);

/// <summary>
/// A helper for <see cref="ToGray8(ReadOnlySpan{TPixel}, Span{Gray8}, int)"/> that expects a byte span as destination.
/// The layout of the data in 'destBytes' must be compatible with <see cref="Gray8"/> layout.
/// </summary>
/// <param name="sourceColors">The <see cref="Span{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="Span{T}"/> to the destination bytes.</param>
/// <param name="count">The number of pixels to convert.</param>
internal void ToGray8Bytes(ReadOnlySpan<TPixel> sourceColors, Span<byte> destBytes, int count);

/// <summary>
/// A helper for <see cref="PackFromGray16(ReadOnlySpan{Gray16}, Span{TPixel}, int)"/> that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with <see cref="Gray16"/> layout.
/// </summary>
/// <param name="sourceBytes">The <see cref="ReadOnlySpan{T}"/> to the source bytes.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>
internal void PackFromGray16Bytes(ReadOnlySpan<byte> sourceBytes, Span<TPixel> destPixels, int count);
		
/// <summary>
/// Converts 'count' pixels in 'sourcePixels` span to a span of <see cref="Gray16"/>-s.
/// Bulk version of <see cref="IPixel.ToGray16(ref Gray16)"/>.
/// </summary>
/// <param name="sourcePixels">The span of source pixels</param>
/// <param name="dest">The destination span of <see cref="Gray16"/> data.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void ToGray16(ReadOnlySpan<TPixel> sourcePixels, Span<Gray16> dest, int count);

/// <summary>
/// A helper for <see cref="ToGray16(ReadOnlySpan{TPixel}, Span{Gray16}, int)"/> that expects a byte span as destination.
/// The layout of the data in 'destBytes' must be compatible with <see cref="Gray16"/> layout.
/// </summary>
/// <param name="sourceColors">The <see cref="Span{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="Span{T}"/> to the destination bytes.</param>
/// <param name="count">The number of pixels to convert.</param>
internal void ToGray16Bytes(ReadOnlySpan<TPixel> sourceColors, Span<byte> destBytes, int count);

Conversion between these formats and Rgb24 and similar would use the ITU-R Recommendation BT.709 which is the algorithm used by libpng and currently implemented by our codec. We would replace the current code with our new API methods.

// Conversion from Rgb24 to Gray16 using ITU-R recommendation 709 to match libpng.
const float RX = .2126F;
const float GX = .7152F;
const float BX = .0722F;

ushort luminance = (ushort)((RX * rgb.R) + (GX * rgb.G) + (BX * rgb.B));

Add appropriate unit tests to existing pixel formats.

This is not a particularly difficult task, just time consuming. I would consider it appropriate for newcomers to the library.

Activity

  1. added this to the 1.0.0-rc1 milestone on Sep 30, 2018
  2. jongleur1983 commented on Oct 4, 2018

    @jongleur1983
    Contributor

    working on it.

  3. jongleur1983 commented on Oct 4, 2018

    @jongleur1983
    Contributor

    I stumble over the t4 code generation as I'm not sure how the result is supposed to look like.
    Up to now I just added the following to the generation:

    GeneratePackFromMethodUsingPackFromArgb32("Gray8", "Unsafe.Add(ref sourceRef, i).ToArgb32(ref argb);");
    GenerateToDestFormatMethods("Gray8");
    
    GeneratePackFromMethodUsingPackFromArgb32("Gray16", "Unsafe.Add(ref sourceRef, i).ToArgb32(ref argb);");
    GenerateToDestFormatMethods("Gray16");`
    

    This generates the required methods and the generated code compiles, but I'm not sure that's what is wanted here.
    In fact it looks quite different to the other calls that already exist.

    Beside that locally I have the IPixel enhancements and the two TPixel implementations.
    Unit tests are missing yet.

  4. added 10 commits that reference this issue on Oct 6, 2018
  5. added a commit that references this issue on Oct 23, 2018
  6. added a commit that references this issue on Nov 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions