Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ private static void _writePdf(string fileName, bool enableSubsetting = true)
using (var pantherSixImage = SixLabors.ImageSharp.Image.Load<Rgba32>(pantherPngStream))
{
var pantherImg = writer.AddImage(pantherSixImage);
var transparentPanther = writer.AddSeparationImage(pantherSixImage, new Separation(PdfName.Get("White"), PredefinedColors.Yellow), GrayScaleMethod.AlphaChannel, [0, 1]);
var transparentPanther = writer.AddSeparationImage(pantherSixImage, new Separation(PdfName.Get("White"), PredefinedColors.Yellow), GrayScaleMethods.AlphaChannel, [0, 1]);

writer.AddPage(page =>
{
Expand Down
90 changes: 82 additions & 8 deletions src/Synercoding.FileFormats.Pdf/Content/GrayScaleMethod.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,110 @@
using SixLabors.ImageSharp.PixelFormats;

namespace Synercoding.FileFormats.Pdf.Content;

/// <summary>
/// What method is used to generate a 1 component grayscale pixel byte array
/// </summary>
public enum GrayScaleMethod
public static class GrayScaleMethods
{
/// <summary>
/// Use the red channel
/// </summary>
RedChannel,
public static byte RedChannel(ref Rgba32 pixel) => pixel.R;
/// <summary>
/// Use the red channel
/// </summary>
public static byte RedChannel(ref Rgb24 pixel) => pixel.R;
/// <summary>
/// Use the green channel
/// </summary>
GreenChannel,
public static byte GreenChannel(ref Rgba32 pixel) => pixel.G;
/// <summary>
/// Use the green channel
/// </summary>
public static byte GreenChannel(ref Rgb24 pixel) => pixel.G;
/// <summary>
/// Use the blue channel
/// </summary>
public static byte BlueChannel(ref Rgba32 pixel) => pixel.B;
/// <summary>
/// Use the blue channel
/// </summary>
BlueChannel,
public static byte BlueChannel(ref Rgb24 pixel) => pixel.B;
/// <summary>
/// Use the alpha channel
/// </summary>
AlphaChannel,
public static byte AlphaChannel(ref Rgba32 pixel) => pixel.A;
/// <summary>
/// Use the average of the Red, Green and Blue channels.
/// </summary>
public static byte AverageOfRGBChannels(ref Rgba32 pixel) => (byte)( ( pixel.R + pixel.G + pixel.B ) / 3 );
/// <summary>
/// Use the average of the Red, Green and Blue channels.
/// </summary>
AverageOfRGBChannels,
public static byte AverageOfRGBChannels(ref Rgb24 pixel) => (byte)( ( pixel.R + pixel.G + pixel.B ) / 3 );
/// <summary>
/// The constants defined by ITU-R BT.601 are 0.299 red + 0.587 green + 0.114 blue.
/// </summary>
public static byte BT601(ref Rgba32 pixel) => (byte)( ( pixel.R * 0.299 ) + ( pixel.G * 0.587 ) + ( pixel.B * 0.114 ) );
/// <summary>
/// The constants defined by ITU-R BT.601 are 0.299 red + 0.587 green + 0.114 blue.
/// </summary>
BT601,
public static byte BT601(ref Rgb24 pixel) => (byte)( ( pixel.R * 0.299 ) + ( pixel.G * 0.587 ) + ( pixel.B * 0.114 ) );
/// <summary>
/// The constants defined by ITU-R BT.709 are 0.2126 red + 0.7152 green + 0.0722 blue.
/// </summary>
BT709,
public static byte BT709(ref Rgba32 pixel) => (byte)( ( pixel.R * 0.2126 ) + ( pixel.G * 0.7152 ) + ( pixel.B * 0.0722 ) );
/// <summary>
/// The constants defined by ITU-R BT.709 are 0.2126 red + 0.7152 green + 0.0722 blue.
/// </summary>
public static byte BT709(ref Rgb24 pixel) => (byte)( ( pixel.R * 0.2126 ) + ( pixel.G * 0.7152 ) + ( pixel.B * 0.0722 ) );

/// <summary>
/// Create a threshold method from an initial method. The threshold method will return the lowValue if the initial method returns a value less than the threshold, and the highValue otherwise.
/// </summary>
/// <param name="initialMethod">The initial method to use.</param>
/// <param name="threshold">The threshold value.</param>
/// <param name="lowValue">The value to return if the initial method returns a value less than the threshold.</param>
/// <param name="highValue">The value to return if the initial method returns a value greater than or equal to the threshold.</param>
/// <returns>The threshold method.</returns>
public static GrayScaleMethod32 Threshold(GrayScaleMethod32 initialMethod, byte threshold, byte lowValue = 0x00, byte highValue = 0xFF)
{
return (ref Rgba32 pixel) =>
{
var value = initialMethod(ref pixel);
return value < threshold ? lowValue : highValue;
};
}

/// <summary>
/// Create a threshold method from an initial method. The threshold method will return the lowValue if the initial method returns a value less than the threshold, and the highValue otherwise.
/// </summary>
/// <param name="initialMethod">The initial method to use.</param>
/// <param name="threshold">The threshold value.</param>
/// <param name="lowValue">The value to return if the initial method returns a value less than the threshold.</param>
/// <param name="highValue">The value to return if the initial method returns a value greater than or equal to the threshold.</param>
/// <returns>The threshold method.</returns>
public static GrayScaleMethod24 Threshold(GrayScaleMethod24 initialMethod, byte threshold, byte lowValue = 0x00, byte highValue = 0xFF)
{
return (ref Rgb24 pixel) =>
{
var value = initialMethod(ref pixel);
return value < threshold ? lowValue : highValue;
};
}
}

/// <summary>
/// Represents a method to convert a <see cref="Rgba32"/> pixel into a single byte.
/// </summary>
/// <param name="pixel">The pixel to convert</param>
/// <returns>The byte value the pixel represents.</returns>
public delegate byte GrayScaleMethod32(ref Rgba32 pixel);

/// <summary>
/// Represents a method to convert a <see cref="Rgb24"/> pixel into a single byte.
/// </summary>
/// <param name="pixel">The pixel to convert</param>
/// <returns>The byte value the pixel represents.</returns>
public delegate byte GrayScaleMethod24(ref Rgb24 pixel);
36 changes: 7 additions & 29 deletions src/Synercoding.FileFormats.Pdf/Content/PdfImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,14 @@ internal static PdfImage Get(TableBuilder tableBuilder, Image<Rgba32> image)
internal static PdfImage Get(TableBuilder tableBuilder, Image<Rgb24> image)
=> new PdfImage(tableBuilder.ReserveId(), _encodeToJpg(image), image.Width, image.Height, DeviceRGB.Instance, null, null, (PdfNames.DCTDecode, null));

internal static PdfImage GetSeparation(TableBuilder tableBuilder, Image<Rgb24> image, Separation separation, GrayScaleMethod grayScaleMethod, double[]? decodeArray = null)
internal static PdfImage GetSeparation(TableBuilder tableBuilder, Image<Rgb24> image, Separation separation, GrayScaleMethod24 grayScaleMethod, double[]? decodeArray = null)
{
using var grayScaleStream = AsGrayScaleByteStream(image, grayScaleMethod);

return new PdfImage(tableBuilder.ReserveId(), _flateEncode(grayScaleStream), image.Width, image.Height, separation, null, decodeArray, (PdfNames.FlateDecode, null));
}

internal static PdfImage GetSeparation(TableBuilder tableBuilder, Image<Rgba32> image, Separation separation, GrayScaleMethod grayScaleMethod, double[]? decodeArray = null)
internal static PdfImage GetSeparation(TableBuilder tableBuilder, Image<Rgba32> image, Separation separation, GrayScaleMethod32 grayScaleMethod, double[]? decodeArray = null)
{
using var grayScaleStream = AsGrayScaleByteStream(image, grayScaleMethod);

Expand All @@ -170,7 +170,7 @@ internal static PdfImage GetSeparation(TableBuilder tableBuilder, Image<Rgba32>
if (!_hasTransparancy(image))
return null;

using var grayScaleStream = AsGrayScaleByteStream(image, GrayScaleMethod.AlphaChannel);
using var grayScaleStream = AsGrayScaleByteStream(image, GrayScaleMethods.AlphaChannel);

return new PdfImage(tableBuilder.ReserveId(), _flateEncode(grayScaleStream), image.Width, image.Height, DeviceGray.Instance, null, null, (PdfNames.FlateDecode, null));
}
Expand Down Expand Up @@ -214,7 +214,7 @@ private static Stream _flateEncode(MemoryStream stream)
return new MemoryStream(bytes);
}

internal static MemoryStream AsGrayScaleByteStream(Image<Rgba32> image, GrayScaleMethod grayScaleMethod)
internal static MemoryStream AsGrayScaleByteStream(Image<Rgba32> image, GrayScaleMethod32 grayScaleMethod)
{
var byteStream = new MemoryStream();

Expand All @@ -231,17 +231,7 @@ internal static MemoryStream AsGrayScaleByteStream(Image<Rgba32> image, GrayScal
// Get a reference to the pixel at position x
ref Rgba32 pixel = ref pixelRow[x];

var pixelValue = grayScaleMethod switch
{
GrayScaleMethod.AlphaChannel => pixel.A,
GrayScaleMethod.RedChannel => pixel.R,
GrayScaleMethod.GreenChannel => pixel.G,
GrayScaleMethod.BlueChannel => pixel.B,
GrayScaleMethod.AverageOfRGBChannels => (byte)( ( pixel.R + pixel.G + pixel.B ) / 3 ),
GrayScaleMethod.BT601 => (byte)( ( pixel.R * 0.299 ) + ( pixel.G * 0.587 ) + ( pixel.B * 0.114 ) ),
GrayScaleMethod.BT709 => (byte)( ( pixel.R * 0.2126 ) + ( pixel.G * 0.7152 ) + ( pixel.B * 0.0722 ) ),
_ => throw new NotImplementedException()
};
var pixelValue = grayScaleMethod(ref pixel);

byteStream.WriteByte(pixelValue);
}
Expand All @@ -253,11 +243,8 @@ internal static MemoryStream AsGrayScaleByteStream(Image<Rgba32> image, GrayScal
return byteStream;
}

internal static MemoryStream AsGrayScaleByteStream(Image<Rgb24> image, GrayScaleMethod grayScaleMethod)
internal static MemoryStream AsGrayScaleByteStream(Image<Rgb24> image, GrayScaleMethod24 grayScaleMethod)
{
if (grayScaleMethod == GrayScaleMethod.AlphaChannel)
throw new ArgumentException($"Can not use alpha channel for images of pixel format {nameof(Rgb24)}.", nameof(grayScaleMethod));

var byteStream = new MemoryStream();

image.ProcessPixelRows(accessor =>
Expand All @@ -273,16 +260,7 @@ internal static MemoryStream AsGrayScaleByteStream(Image<Rgb24> image, GrayScale
// Get a reference to the pixel at position x
ref Rgb24 pixel = ref pixelRow[x];

var pixelValue = grayScaleMethod switch
{
GrayScaleMethod.RedChannel => pixel.R,
GrayScaleMethod.GreenChannel => pixel.G,
GrayScaleMethod.BlueChannel => pixel.B,
GrayScaleMethod.AverageOfRGBChannels => (byte)( ( pixel.R + pixel.G + pixel.B ) / 3 ),
GrayScaleMethod.BT601 => (byte)( ( pixel.R * 0.299 ) + ( pixel.G * 0.587 ) + ( pixel.B * 0.114 ) ),
GrayScaleMethod.BT709 => (byte)( ( pixel.R * 0.2126 ) + ( pixel.G * 0.7152 ) + ( pixel.B * 0.0722 ) ),
_ => throw new NotImplementedException()
};
var pixelValue = grayScaleMethod(ref pixel);

byteStream.WriteByte(pixelValue);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Synercoding.FileFormats.Pdf/Generation/PageResources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ public PdfName AddJpgUnsafe(Stream jpgStream, int originalWidth, int originalHei
return Add(pdfImage);
}

public PdfName Add(SixLabors.ImageSharp.Image<Rgba32> image, Separation separation, GrayScaleMethod grayScaleMethod = GrayScaleMethod.AverageOfRGBChannels)
public PdfName Add(SixLabors.ImageSharp.Image<Rgba32> image, Separation separation, GrayScaleMethod32? grayScaleMethod = null)
{
grayScaleMethod ??= GrayScaleMethods.AverageOfRGBChannels;
var pdfImage = PdfImage.GetSeparation(_tableBuilder, image, separation, grayScaleMethod);

return Add(pdfImage);
Expand Down
6 changes: 4 additions & 2 deletions src/Synercoding.FileFormats.Pdf/PdfWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,9 @@ public PdfImage AddImage(SixLabors.ImageSharp.Image<Rgb24> image)
/// <param name="grayScaleMethod">The method to convert to grayscale.</param>
/// <param name="decodeArray">Optional decode array for the image.</param>
/// <returns>The added PDF image.</returns>
public PdfImage AddSeparationImage(SixLabors.ImageSharp.Image<Rgba32> image, Separation separation, GrayScaleMethod grayScaleMethod = GrayScaleMethod.AverageOfRGBChannels, double[]? decodeArray = null)
public PdfImage AddSeparationImage(SixLabors.ImageSharp.Image<Rgba32> image, Separation separation, GrayScaleMethod32? grayScaleMethod = null, double[]? decodeArray = null)
{
grayScaleMethod ??= GrayScaleMethods.AverageOfRGBChannels;
var pdfImage = PdfImage.GetSeparation(_objectWriter.TableBuiler, image, separation, grayScaleMethod, decodeArray);

_objectWriter.Write(pdfImage.ToStreamObject(_cachedResources));
Expand All @@ -193,8 +194,9 @@ public PdfImage AddSeparationImage(SixLabors.ImageSharp.Image<Rgba32> image, Sep
/// <param name="grayScaleMethod">The method to convert to grayscale.</param>
/// <param name="decodeArray">Optional decode array for the image.</param>
/// <returns>The added PDF image.</returns>
public PdfImage AddSeparationImage(SixLabors.ImageSharp.Image<Rgb24> image, Separation separation, GrayScaleMethod grayScaleMethod = GrayScaleMethod.AverageOfRGBChannels, double[]? decodeArray = null)
public PdfImage AddSeparationImage(SixLabors.ImageSharp.Image<Rgb24> image, Separation separation, GrayScaleMethod24? grayScaleMethod = null, double[]? decodeArray = null)
{
grayScaleMethod ??= GrayScaleMethods.AverageOfRGBChannels;
var pdfImage = PdfImage.GetSeparation(_objectWriter.TableBuiler, image, separation, grayScaleMethod, decodeArray);

_objectWriter.Write(pdfImage.ToStreamObject(_cachedResources));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ public void Test_Add_ImageSharp_WithSeparation()
var separation = new Separation(PdfName.Get("SpotColor"), new RgbColor(0, 1, 0)); // Green

// Act
var name = _pageResources.Add(image, separation, GrayScaleMethod.AverageOfRGBChannels);
var name = _pageResources.Add(image, separation, GrayScaleMethods.AverageOfRGBChannels);

// Assert
Assert.NotNull(name);
Expand Down
Loading