Skip to content

Add Gray8 and Gray16 Pixel Formats #718

@JimBobSquarePants

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions