From 9e1b1d8c41411854cb75cb2858fdd42444d38685 Mon Sep 17 00:00:00 2001 From: Gerard Gunnewijk Date: Thu, 21 Nov 2024 15:19:34 +0100 Subject: [PATCH 1/2] Add support for bt601 and bt709 grayscale methods --- src/Synercoding.FileFormats.Pdf/GrayScaleMethod.cs | 10 +++++++++- src/Synercoding.FileFormats.Pdf/Image.cs | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Synercoding.FileFormats.Pdf/GrayScaleMethod.cs b/src/Synercoding.FileFormats.Pdf/GrayScaleMethod.cs index 96cdc55..90d4720 100644 --- a/src/Synercoding.FileFormats.Pdf/GrayScaleMethod.cs +++ b/src/Synercoding.FileFormats.Pdf/GrayScaleMethod.cs @@ -24,5 +24,13 @@ public enum GrayScaleMethod /// /// Use the average of the Red, Green and Blue channels. /// - AverageOfRGBChannels + AverageOfRGBChannels, + /// + /// The constants defined by ITU-R BT.601 are 0.299 red + 0.587 green + 0.114 blue. + /// + BT601, + /// + /// The constants defined by ITU-R BT.709 are 0.2126 red + 0.7152 green + 0.0722 blue. + /// + BT709, } diff --git a/src/Synercoding.FileFormats.Pdf/Image.cs b/src/Synercoding.FileFormats.Pdf/Image.cs index 019ffeb..063edbf 100644 --- a/src/Synercoding.FileFormats.Pdf/Image.cs +++ b/src/Synercoding.FileFormats.Pdf/Image.cs @@ -118,6 +118,8 @@ internal static Stream AsImageByteStream(Image image, GrayScaleMethod gr 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() }; From 4f73d3d888f6cc868ce6ebab7533ced96db61ea7 Mon Sep 17 00:00:00 2001 From: Gerard Gunnewijk Date: Thu, 21 Nov 2024 15:19:55 +0100 Subject: [PATCH 2/2] Add support for decode array for separation images --- .../Program.cs | 41 +++++++++------- src/Synercoding.FileFormats.Pdf/Image.cs | 49 ++++++++++++++----- .../LowLevel/Internal/PageResources.cs | 2 +- .../LowLevel/ObjectStream.cs | 2 +- src/Synercoding.FileFormats.Pdf/PdfWriter.cs | 13 +++-- 5 files changed, 71 insertions(+), 36 deletions(-) diff --git a/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs b/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs index 298ee5a..f11a3ef 100644 --- a/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs +++ b/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs @@ -226,33 +226,36 @@ public static void Main(string[] args) context.Stroke(); }); }); - } - - using (var pantherPngStream = File.OpenRead("FreePngImage_com/59872-jaguar-panther-royalty-free-cougar-black-cheetah.png")) - using (var pantherImage = SixLabors.ImageSharp.Image.Load(pantherPngStream)) - { - var pantherImg = writer.AddImage(pantherImage); - var transparentPanther = writer.AddSeparationImage(new Separation(LowLevel.PdfName.Get("White"), PredefinedColors.Yellow), pantherImage, GrayScaleMethod.AlphaChannel); - writer.AddPage(page => + using (var pantherPngStream = File.OpenRead("FreePngImage_com/59872-jaguar-panther-royalty-free-cougar-black-cheetah.png")) + using (var pantherSixImage = SixLabors.ImageSharp.Image.Load(pantherPngStream)) { - page.MediaBox = mediaBox; - page.TrimBox = trimBox; + var pantherImg = writer.AddImage(pantherSixImage); + var transparentPanther = writer.AddSeparationImage(new Separation(LowLevel.PdfName.Get("White"), PredefinedColors.Yellow), pantherSixImage, GrayScaleMethod.AlphaChannel, [0, 1]); + + writer.AddPage(page => + { + page.MediaBox = mediaBox; + page.TrimBox = trimBox; - var scale = (double)transparentPanther.Width / transparentPanther.Height; - var pantherSize = new Rectangle(0, 0, 216, 216 / scale, Unit.Millimeters); + var scale = (double)blurImage.Width / blurImage.Height; + page.Content.AddImage(reusedImage, new Rectangle(0, 0, scale * 303, 303, Unit.Millimeters)); - page.Content.AddImage(pantherImage, pantherSize); + scale = (double)transparentPanther.Width / transparentPanther.Height; + var pantherSize = new Rectangle(0, 0, 216, 216 / scale, Unit.Millimeters); - page.Content.WrapInState(pantherImage, (image, content) => - { - content.SetExtendedGraphicsState(new ExtendedGraphicsState() + page.Content.AddImage(pantherImg, pantherSize); + + page.Content.WrapInState(pantherSixImage, (image, content) => { - Overprint = true + content.SetExtendedGraphicsState(new ExtendedGraphicsState() + { + Overprint = true + }); + content.AddImage(transparentPanther, pantherSize); }); - content.AddImage(transparentPanther, pantherSize); }); - }); + } } } } diff --git a/src/Synercoding.FileFormats.Pdf/Image.cs b/src/Synercoding.FileFormats.Pdf/Image.cs index 063edbf..2ac07a4 100644 --- a/src/Synercoding.FileFormats.Pdf/Image.cs +++ b/src/Synercoding.FileFormats.Pdf/Image.cs @@ -3,7 +3,6 @@ using Synercoding.FileFormats.Pdf.LowLevel; using Synercoding.FileFormats.Pdf.LowLevel.Colors.ColorSpaces; using Synercoding.FileFormats.Pdf.LowLevel.XRef; -using System.IO.Compression; namespace Synercoding.FileFormats.Pdf; @@ -14,7 +13,7 @@ public class Image : IDisposable { private protected bool _disposed; - internal Image(PdfReference id, Stream jpgStream, int width, int height, ColorSpace colorSpace, Image? softMask, params StreamFilter[] filters) + internal Image(PdfReference id, Stream jpgStream, int width, int height, ColorSpace colorSpace, Image? softMask, double[]? decodeArray, params StreamFilter[] filters) { Reference = id; @@ -23,6 +22,7 @@ internal Image(PdfReference id, Stream jpgStream, int width, int height, ColorSp RawStream = jpgStream; ColorSpace = colorSpace; SoftMask = softMask; + DecodeArray = decodeArray; Filters = filters; } @@ -30,6 +30,8 @@ internal Image(PdfReference id, Stream jpgStream, int width, int height, ColorSp internal Stream RawStream { get; private set; } + internal double[]? DecodeArray { get; private set; } + /// /// A pdf reference object that can be used to reference to this object /// @@ -77,23 +79,46 @@ private static Stream _encodeToJpg(SixLabors.ImageSharp.Image image) internal static Image Get(TableBuilder tableBuilder, Image image) { - return new Image(tableBuilder.ReserveId(), _encodeToJpg(image), image.Width, image.Height, DeviceRGB.Instance, GetMask(tableBuilder, image), StreamFilter.DCTDecode); + return new Image(tableBuilder.ReserveId(), _encodeToJpg(image), image.Width, image.Height, DeviceRGB.Instance, GetMask(tableBuilder, image), null, StreamFilter.DCTDecode); } internal static Image? GetMask(TableBuilder tableBuilder, Image image) { - var hasTrans = image.Metadata.TryGetPngMetadata(out var pngMeta) - && - ( - pngMeta.ColorType == SixLabors.ImageSharp.Formats.Png.PngColorType.RgbWithAlpha - || pngMeta.ColorType == SixLabors.ImageSharp.Formats.Png.PngColorType.GrayscaleWithAlpha - ); - - return hasTrans - ? new Image(tableBuilder.ReserveId(), AsImageByteStream(image, GrayScaleMethod.AlphaChannel), image.Width, image.Height, DeviceGray.Instance, null, StreamFilter.FlateDecode) + return _hasTransparancy(image) + ? new Image(tableBuilder.ReserveId(), AsImageByteStream(image, GrayScaleMethod.AlphaChannel), image.Width, image.Height, DeviceGray.Instance, null, null, StreamFilter.FlateDecode) : null; } + private static bool _hasTransparancy(Image image) + { + if( image.Metadata.TryGetPngMetadata(out var pngMeta)) + { + if (pngMeta.ColorType == SixLabors.ImageSharp.Formats.Png.PngColorType.RgbWithAlpha) + return true; + if (pngMeta.ColorType == SixLabors.ImageSharp.Formats.Png.PngColorType.GrayscaleWithAlpha) + return true; + } + + bool hasTransparancy = false; + image.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + var row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + ref Rgba32 pixel = ref row[x]; + if (pixel.A != 0xFF) + { + hasTransparancy = true; + return; + } + } + } + }); + return hasTransparancy; + } + internal static Stream AsImageByteStream(Image image, GrayScaleMethod grayScaleMethod) { using (var byteStream = new MemoryStream()) diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs index 97e1028..0cd000b 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs @@ -57,7 +57,7 @@ public PdfName AddJpgUnsafe(System.IO.Stream jpgStream, int originalWidth, int o { var id = _tableBuilder.ReserveId(); - var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, null, StreamFilter.DCTDecode); + var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, null, null, StreamFilter.DCTDecode); return AddImage(pdfImage); } diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs index d6ec4d5..981b3cf 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs @@ -103,7 +103,7 @@ public ObjectStream Write(Image image) .Write(PdfName.Get("Width"), image.Width) .Write(PdfName.Get("Height"), image.Height) .Write(PdfName.Get("BitsPerComponent"), 8) - .Write(PdfName.Get("Decode"), _decodeArray(image.ColorSpace)) + .Write(PdfName.Get("Decode"), image.DecodeArray ?? _decodeArray(image.ColorSpace)) .WriteIfNotNull(PdfName.Get("SMask"), image.SoftMask?.Reference); diff --git a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs index 8851695..4c72d97 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs +++ b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs @@ -167,18 +167,25 @@ public Image AddImage(Image image) /// The to use. /// The image to use. /// The to use. + /// Optional decode array to use, default value is [ 0.0 1.0 ] /// The SeparationImage reference that can be used in pages - public Image AddSeparationImage(Separation separation, Image image, GrayScaleMethod grayScaleMethod) + public Image AddSeparationImage(Separation separation, Image image, GrayScaleMethod grayScaleMethod, double[]? decodeArray = null) { _throwWhenEndingWritten(); + decodeArray ??= new double[] { 0, 1 }; + if(decodeArray.Length != 2) + throw new ArgumentOutOfRangeException(nameof(decodeArray), "Length of decode array for separation images should be 2."); + if (decodeArray.Any(v => v < 0 || v > 1)) + throw new ArgumentOutOfRangeException(nameof(decodeArray), "All values of the decode array should be between 0 and 1."); + var id = _tableBuilder.ReserveId(); var mask = Image.GetMask(_tableBuilder, image); var imageStream = Image.AsImageByteStream(image, grayScaleMethod); - var pdfImage = new Image(id, imageStream, image.Width, image.Height, separation, mask, StreamFilter.FlateDecode); + var pdfImage = new Image(id, imageStream, image.Width, image.Height, separation, mask, decodeArray, StreamFilter.FlateDecode); _objectStream.Write(pdfImage); @@ -202,7 +209,7 @@ public Image AddJpgUnsafe(Stream jpgStream, int originalWidth, int originalHeigh var id = _tableBuilder.ReserveId(); - var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, null, StreamFilter.DCTDecode); + var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, null, null, StreamFilter.DCTDecode); _objectStream.Write(pdfImage);