Skip to content

Commit bbb96ed

Browse files
committed
Add support for transparent and separtion images
1 parent f862897 commit bbb96ed

File tree

16 files changed

+293
-60
lines changed

16 files changed

+293
-60
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ The PDF files created in this library are not fully PDF 1.7 compliant, because t
4141
This shortcoming shall be remedied when broader font support is implemented.
4242

4343
### Sample program images
44-
The sample project called *Synercoding.FileFormats.Pdf.ConsoleTester* uses multiple images. Those images were taken from [Pexels.com](https://www.pexels.com/royalty-free-images/) and are licensed under the [Pexels License](https://www.pexels.com/photo-license/).
44+
The sample project called *Synercoding.FileFormats.Pdf.ConsoleTester* uses multiple images.
45+
Those images were taken from:
46+
- [Pexels.com](https://www.pexels.com/royalty-free-images/) and are licensed under the [Pexels License](https://www.pexels.com/photo-license/)
47+
- [FreePngImg.com](https://freepngimg.com/png/59872-jaguar-panther-royalty-free-cougar-black-cheetah) and are licensed under [Creative Commons (CC BY-NC 4.0)](https://creativecommons.org/licenses/by-nc/4.0/)
4548

4649
## Sample usage
4750

samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using SixLabors.ImageSharp.PixelFormats;
12
using Synercoding.FileFormats.Pdf.Extensions;
23
using Synercoding.FileFormats.Pdf.LowLevel.Colors;
34
using Synercoding.FileFormats.Pdf.LowLevel.Colors.ColorSpaces;
@@ -63,7 +64,7 @@ public static void Main(string[] args)
6364
});
6465

6566
using (var forestStream = File.OpenRead("Pexels_com/android-wallpaper-art-backlit-1114897.jpg"))
66-
using (var forestImage = SixLabors.ImageSharp.Image.Load(forestStream))
67+
using (var forestImage = SixLabors.ImageSharp.Image.Load<Rgba32>(forestStream))
6768
{
6869
var scale = (double)forestImage.Width / forestImage.Height;
6970

@@ -81,7 +82,7 @@ public static void Main(string[] args)
8182
page.TrimBox = trimBox;
8283

8384
using (var barrenStream = File.OpenRead("Pexels_com/arid-barren-desert-1975514.jpg"))
84-
using (var barrenImage = SixLabors.ImageSharp.Image.Load(barrenStream))
85+
using (var barrenImage = SixLabors.ImageSharp.Image.Load<Rgba32>(barrenStream))
8586
{
8687
var scale = (double)barrenImage.Width / barrenImage.Height;
8788

@@ -175,7 +176,7 @@ public static void Main(string[] args)
175176
page.TrimBox = trimBox;
176177

177178
using (var forestStream = File.OpenRead("Pexels_com/android-wallpaper-art-backlit-1114897.jpg"))
178-
using (var forestImage = SixLabors.ImageSharp.Image.Load(forestStream))
179+
using (var forestImage = SixLabors.ImageSharp.Image.Load<Rgba32>(forestStream))
179180
{
180181
var scale = (double)forestImage.Width / forestImage.Height;
181182

@@ -187,7 +188,7 @@ public static void Main(string[] args)
187188
});
188189

189190
using (var blurStream = File.OpenRead("Pexels_com/4k-wallpaper-blur-bokeh-1484253.jpg"))
190-
using (var blurImage = SixLabors.ImageSharp.Image.Load(blurStream))
191+
using (var blurImage = SixLabors.ImageSharp.Image.Load<Rgba32>(blurStream))
191192
{
192193
var reusedImage = writer.AddImage(blurImage);
193194

@@ -226,6 +227,33 @@ public static void Main(string[] args)
226227
});
227228
});
228229
}
230+
231+
using (var pantherPngStream = File.OpenRead("FreePngImage_com/59872-jaguar-panther-royalty-free-cougar-black-cheetah.png"))
232+
using (var pantherImage = SixLabors.ImageSharp.Image.Load<Rgba32>(pantherPngStream))
233+
{
234+
var pantherImg = writer.AddImage(pantherImage);
235+
var transparentPanther = writer.AddSeparationImage(new Separation(LowLevel.PdfName.Get("White"), PredefinedColors.Yellow), pantherImage, GrayScaleMethod.AlphaChannel);
236+
237+
writer.AddPage(page =>
238+
{
239+
page.MediaBox = mediaBox;
240+
page.TrimBox = trimBox;
241+
242+
var scale = (double)transparentPanther.Width / transparentPanther.Height;
243+
var pantherSize = new Rectangle(0, 0, 216, 216 / scale, Unit.Millimeters);
244+
245+
page.Content.AddImage(pantherImage, pantherSize);
246+
247+
page.Content.WrapInState(pantherImage, (image, content) =>
248+
{
249+
content.SetExtendedGraphicsState(new ExtendedGraphicsState()
250+
{
251+
Overprint = true
252+
});
253+
content.AddImage(transparentPanther, pantherSize);
254+
});
255+
});
256+
}
229257
}
230258
}
231259
}

samples/Synercoding.FileFormats.Pdf.ConsoleTester/Synercoding.FileFormats.Pdf.ConsoleTester.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@
1515
</None>
1616
</ItemGroup>
1717

18+
<ItemGroup>
19+
<None Update="FreePngImage_com\*.png">
20+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
21+
</None>
22+
</ItemGroup>
23+
1824
</Project>

src/Synercoding.FileFormats.Pdf/Extensions/IPageContentContextExtensions.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using SixLabors.ImageSharp.PixelFormats;
12
using Synercoding.FileFormats.Pdf.Internals;
23
using Synercoding.FileFormats.Pdf.LowLevel.Colors.ColorSpaces;
34
using Synercoding.FileFormats.Pdf.LowLevel.Text;
@@ -61,7 +62,7 @@ public static IPageContentContext AddImage(this IPageContentContext context, Sys
6162
/// <returns>The same <paramref name="context"/> to enable chaining operations.</returns>
6263
public static IPageContentContext AddImage(this IPageContentContext context, System.IO.Stream stream)
6364
{
64-
using var image = SixLabors.ImageSharp.Image.Load(stream);
65+
using var image = SixLabors.ImageSharp.Image.Load<Rgba32>(stream);
6566

6667
return context.AddImage(image);
6768
}
@@ -73,7 +74,7 @@ public static IPageContentContext AddImage(this IPageContentContext context, Sys
7374
/// <param name="image">The image to place</param>
7475
/// <param name="matrix">The placement matrix to use</param>
7576
/// <returns>The same <paramref name="context"/> to enable chaining operations.</returns>
76-
public static IPageContentContext AddImage(this IPageContentContext context, SixLabors.ImageSharp.Image image, Matrix matrix)
77+
public static IPageContentContext AddImage(this IPageContentContext context, SixLabors.ImageSharp.Image<Rgba32> image, Matrix matrix)
7778
{
7879
return context.WrapInState((image, matrix), static (tuple, context) =>
7980
{
@@ -89,7 +90,7 @@ public static IPageContentContext AddImage(this IPageContentContext context, Six
8990
/// <param name="image">The image to place</param>
9091
/// <param name="rectangle">The rectangle of where to place the image.</param>
9192
/// <returns>The same <paramref name="context"/> to enable chaining operations.</returns>
92-
public static IPageContentContext AddImage(this IPageContentContext context, SixLabors.ImageSharp.Image image, Rectangle rectangle)
93+
public static IPageContentContext AddImage(this IPageContentContext context, SixLabors.ImageSharp.Image<Rgba32> image, Rectangle rectangle)
9394
=> context.AddImage(image, rectangle.AsPlacementMatrix());
9495

9596
/// <summary>
@@ -98,7 +99,7 @@ public static IPageContentContext AddImage(this IPageContentContext context, Six
9899
/// <param name="context">The context to add the image to.</param>
99100
/// <param name="image">The image to place</param>
100101
/// <returns>The same <paramref name="context"/> to enable chaining operations.</returns>
101-
public static IPageContentContext AddImage(this IPageContentContext context, SixLabors.ImageSharp.Image image)
102+
public static IPageContentContext AddImage(this IPageContentContext context, SixLabors.ImageSharp.Image<Rgba32> image)
102103
{
103104
var name = context.RawContentStream.Resources.AddImage(image);
104105

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Synercoding.FileFormats.Pdf;
2+
3+
public enum GrayScaleMethod
4+
{
5+
RedChannel,
6+
GreenChannel,
7+
BlueChannel,
8+
AlphaChannel,
9+
AverageOfRGBChannels
10+
}
Lines changed: 82 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,84 @@
11
using SixLabors.ImageSharp;
2+
using SixLabors.ImageSharp.PixelFormats;
23
using Synercoding.FileFormats.Pdf.LowLevel;
34
using Synercoding.FileFormats.Pdf.LowLevel.Colors.ColorSpaces;
5+
using Synercoding.FileFormats.Pdf.LowLevel.XRef;
46

57
namespace Synercoding.FileFormats.Pdf;
68

79
/// <summary>
810
/// Class representing an image inside a pdf
911
/// </summary>
10-
public sealed class Image : IDisposable
12+
public class Image : IDisposable
1113
{
12-
private bool _disposed;
14+
private protected bool _disposed;
1315

14-
internal Image(PdfReference id, SixLabors.ImageSharp.Image image)
16+
internal static Image Get(TableBuilder tableBuilder, Image<Rgba32> image)
1517
{
16-
Reference = id;
17-
18-
var ms = new MemoryStream();
19-
image.SaveAsJpeg(ms, new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder()
20-
{
21-
Quality = 100,
22-
ColorType = SixLabors.ImageSharp.Formats.Jpeg.JpegEncodingColor.YCbCrRatio444
23-
});
24-
Width = image.Width;
25-
Height = image.Height;
26-
ColorSpace = DeviceRGB.Instance.Name;
27-
DecodeArray = new double[] { 0, 1, 0, 1, 0, 1 };
28-
ms.Position = 0;
29-
RawStream = ms;
18+
return new Image(tableBuilder.ReserveId(), _encodeToJpg(image), image.Width, image.Height, DeviceRGB.Instance, GetMask(tableBuilder, image));
3019
}
3120

32-
internal Image(PdfReference id, Stream jpgStream, int width, int height, ColorSpace colorSpace)
21+
internal static SoftMask? GetMask(TableBuilder tableBuilder, Image<Rgba32> image)
3322
{
34-
Reference = id;
23+
var hasTrans = image.Metadata.TryGetPngMetadata(out var pngMeta)
24+
&&
25+
(
26+
pngMeta.ColorType == SixLabors.ImageSharp.Formats.Png.PngColorType.RgbWithAlpha
27+
|| pngMeta.ColorType == SixLabors.ImageSharp.Formats.Png.PngColorType.GrayscaleWithAlpha
28+
);
29+
30+
return hasTrans
31+
? new SoftMask(tableBuilder.ReserveId(), AsImageByteStream(image, GrayScaleMethod.AlphaChannel), image.Width, image.Height)
32+
: null;
33+
}
3534

36-
Width = width;
37-
Height = height;
38-
RawStream = jpgStream;
35+
internal static Stream AsImageByteStream(Image<Rgba32> image, GrayScaleMethod grayScaleMethod)
36+
{
37+
var ms = new MemoryStream();
3938

40-
var (csName, decodeArray) = colorSpace switch
39+
image.ProcessPixelRows(accessor =>
4140
{
42-
DeviceCMYK cmyk => (cmyk.Name, new double[] { 0, 1, 0, 1, 0, 1, 0, 1 }),
43-
DeviceRGB rgb => (rgb.Name, new double[] { 0, 1, 0, 1, 0, 1 }),
44-
_ => throw new ArgumentOutOfRangeException(nameof(colorSpace), $"The provided color space {colorSpace} is currently not supported.")
45-
};
41+
for (int y = 0; y < accessor.Height; y++)
42+
{
43+
var pixelRow = accessor.GetRowSpan(y);
44+
45+
// pixelRow.Length has the same value as accessor.Width,
46+
// but using pixelRow.Length allows the JIT to optimize away bounds checks:
47+
for (int x = 0; x < pixelRow.Length; x++)
48+
{
49+
// Get a reference to the pixel at position x
50+
ref Rgba32 pixel = ref pixelRow[x];
51+
52+
var pixelValue = grayScaleMethod switch
53+
{
54+
GrayScaleMethod.AlphaChannel => pixel.A,
55+
GrayScaleMethod.RedChannel => pixel.R,
56+
GrayScaleMethod.GreenChannel => pixel.G,
57+
GrayScaleMethod.BlueChannel => pixel.B,
58+
GrayScaleMethod.AverageOfRGBChannels => (byte)( ( pixel.R + pixel.G + pixel.B ) / 3 ),
59+
_ => throw new NotImplementedException()
60+
};
61+
62+
ms.WriteByte(pixelValue);
63+
64+
}
65+
}
66+
});
4667

47-
ColorSpace = csName;
48-
DecodeArray = decodeArray;
68+
ms.Position = 0;
69+
70+
return ms;
4971
}
5072

51-
internal Image(PdfReference id, Stream jpgStream, int width, int height, PdfName colorSpace, double[] decodeArray)
73+
//internal Image(PdfReference id, SixLabors.ImageSharp.Image image)
74+
// : this(id, _encodeToJpg(image), image.Width, image.Height, DeviceRGB.Instance, null)
75+
//{ }
76+
77+
internal Image(PdfReference id, Stream jpgStream, int width, int height, ColorSpace colorSpace, SoftMask? softMask)
78+
: this(id, jpgStream, width, height, colorSpace.Name, _decodeArray(colorSpace), softMask)
79+
{ }
80+
81+
internal Image(PdfReference id, Stream jpgStream, int width, int height, PdfName colorSpace, double[] decodeArray, SoftMask? softMask)
5282
{
5383
Reference = id;
5484

@@ -57,8 +87,11 @@ internal Image(PdfReference id, Stream jpgStream, int width, int height, PdfName
5787
RawStream = jpgStream;
5888
ColorSpace = colorSpace;
5989
DecodeArray = decodeArray;
90+
SoftMask = softMask;
6091
}
6192

93+
internal SoftMask? SoftMask { get; private set; }
94+
6295
internal Stream RawStream { get; private set; }
6396

6497
/// <summary>
@@ -95,4 +128,23 @@ public void Dispose()
95128
_disposed = true;
96129
}
97130
}
131+
132+
private static Stream _encodeToJpg(SixLabors.ImageSharp.Image image)
133+
{
134+
var ms = new MemoryStream();
135+
image.SaveAsJpeg(ms, new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder()
136+
{
137+
Quality = 100,
138+
ColorType = SixLabors.ImageSharp.Formats.Jpeg.JpegEncodingColor.YCbCrRatio444
139+
});
140+
ms.Position = 0;
141+
142+
return ms;
143+
}
144+
145+
private static double[] _decodeArray(ColorSpace colorSpace)
146+
=> Enumerable.Range(0, colorSpace.Components)
147+
.Select(_ => new double[] { 0, 1 })
148+
.SelectMany(x => x)
149+
.ToArray();
98150
}

src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using SixLabors.ImageSharp.PixelFormats;
12
using Synercoding.FileFormats.Pdf.Internals;
23
using Synercoding.FileFormats.Pdf.LowLevel.Colors.ColorSpaces;
34
using Synercoding.FileFormats.Pdf.LowLevel.Text;
@@ -56,7 +57,7 @@ public PdfName AddJpgUnsafe(System.IO.Stream jpgStream, int originalWidth, int o
5657
{
5758
var id = _tableBuilder.ReserveId();
5859

59-
var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace);
60+
var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, null);
6061

6162
return AddImage(pdfImage);
6263
}
@@ -65,16 +66,14 @@ public PdfName AddJpgUnsafe(System.IO.Stream jpgStream, int originalWidth, int o
6566
{
6667
var id = _tableBuilder.ReserveId();
6768

68-
var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, decodeArray);
69+
var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, decodeArray, null);
6970

7071
return AddImage(pdfImage);
7172
}
7273

73-
public PdfName AddImage(SixLabors.ImageSharp.Image image)
74+
public PdfName AddImage(SixLabors.ImageSharp.Image<Rgba32> image)
7475
{
75-
var id = _tableBuilder.ReserveId();
76-
77-
var pdfImage = new Image(id, image);
76+
var pdfImage = Image.Get(_tableBuilder, image);
7877

7978
return AddImage(pdfImage);
8079
}
@@ -108,7 +107,7 @@ internal PdfName AddSeparation(Separation separation)
108107

109108
var key = PREFIX_SEPARATION + System.Threading.Interlocked.Increment(ref _separationCounter).ToString().PadLeft(6, '0');
110109
var name = PdfName.Get(key);
111-
_separations[separation] = (name, _tableBuilder.ReserveId());
110+
_separations[separation] = (name, _tableBuilder.GetSeparationId(separation));
112111

113112
return name;
114113
}

0 commit comments

Comments
 (0)