Skip to content

Commit d614af1

Browse files
committed
Streamlinedd separation and softmask images, and addedd flatedecode for streams
1 parent bbb96ed commit d614af1

File tree

11 files changed

+157
-177
lines changed

11 files changed

+157
-177
lines changed
Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
1-
namespace Synercoding.FileFormats.Pdf;
1+
namespace Synercoding.FileFormats.Pdf;
22

3+
/// <summary>
4+
/// What method is used to generate a 1 component grayscale pixel byte array
5+
/// </summary>
36
public enum GrayScaleMethod
47
{
8+
/// <summary>
9+
/// Use the red channel
10+
/// </summary>
511
RedChannel,
12+
/// <summary>
13+
/// Use the green channel
14+
/// </summary>
615
GreenChannel,
16+
/// <summary>
17+
/// Use the blue channel
18+
/// </summary>
719
BlueChannel,
20+
/// <summary>
21+
/// Use the alpha channel
22+
/// </summary>
823
AlphaChannel,
24+
/// <summary>
25+
/// Use the average of the Red, Green and Blue channels.
26+
/// </summary>
927
AverageOfRGBChannels
1028
}

src/Synercoding.FileFormats.Pdf/Image.cs

Lines changed: 62 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Synercoding.FileFormats.Pdf.LowLevel;
44
using Synercoding.FileFormats.Pdf.LowLevel.Colors.ColorSpaces;
55
using Synercoding.FileFormats.Pdf.LowLevel.XRef;
6+
using System.IO.Compression;
67

78
namespace Synercoding.FileFormats.Pdf;
89

@@ -13,84 +14,19 @@ public class Image : IDisposable
1314
{
1415
private protected bool _disposed;
1516

16-
internal static Image Get(TableBuilder tableBuilder, Image<Rgba32> image)
17-
{
18-
return new Image(tableBuilder.ReserveId(), _encodeToJpg(image), image.Width, image.Height, DeviceRGB.Instance, GetMask(tableBuilder, image));
19-
}
20-
21-
internal static SoftMask? GetMask(TableBuilder tableBuilder, Image<Rgba32> image)
22-
{
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-
}
34-
35-
internal static Stream AsImageByteStream(Image<Rgba32> image, GrayScaleMethod grayScaleMethod)
36-
{
37-
var ms = new MemoryStream();
38-
39-
image.ProcessPixelRows(accessor =>
40-
{
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-
});
67-
68-
ms.Position = 0;
69-
70-
return ms;
71-
}
72-
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)
17+
internal Image(PdfReference id, Stream jpgStream, int width, int height, ColorSpace colorSpace, Image? softMask, params StreamFilter[] filters)
8218
{
8319
Reference = id;
8420

8521
Width = width;
8622
Height = height;
8723
RawStream = jpgStream;
8824
ColorSpace = colorSpace;
89-
DecodeArray = decodeArray;
9025
SoftMask = softMask;
26+
Filters = filters;
9127
}
9228

93-
internal SoftMask? SoftMask { get; private set; }
29+
internal Image? SoftMask { get; private set; }
9430

9531
internal Stream RawStream { get; private set; }
9632

@@ -112,12 +48,9 @@ internal Image(PdfReference id, Stream jpgStream, int width, int height, PdfName
11248
/// <summary>
11349
/// The name of the colorspace used in this <see cref="Image"/>
11450
/// </summary>
115-
public PdfName ColorSpace { get; }
51+
public ColorSpace ColorSpace { get; }
11652

117-
/// <summary>
118-
/// The decode array used in this <see cref="Image"/>
119-
/// </summary>
120-
public double[] DecodeArray { get; }
53+
internal StreamFilter[] Filters { get; } = Array.Empty<StreamFilter>();
12154

12255
/// <inheritdoc />
12356
public void Dispose()
@@ -142,9 +75,60 @@ private static Stream _encodeToJpg(SixLabors.ImageSharp.Image image)
14275
return ms;
14376
}
14477

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();
78+
internal static Image Get(TableBuilder tableBuilder, Image<Rgba32> image)
79+
{
80+
return new Image(tableBuilder.ReserveId(), _encodeToJpg(image), image.Width, image.Height, DeviceRGB.Instance, GetMask(tableBuilder, image), StreamFilter.DCTDecode);
81+
}
82+
83+
internal static Image? GetMask(TableBuilder tableBuilder, Image<Rgba32> image)
84+
{
85+
var hasTrans = image.Metadata.TryGetPngMetadata(out var pngMeta)
86+
&&
87+
(
88+
pngMeta.ColorType == SixLabors.ImageSharp.Formats.Png.PngColorType.RgbWithAlpha
89+
|| pngMeta.ColorType == SixLabors.ImageSharp.Formats.Png.PngColorType.GrayscaleWithAlpha
90+
);
91+
92+
return hasTrans
93+
? new Image(tableBuilder.ReserveId(), AsImageByteStream(image, GrayScaleMethod.AlphaChannel), image.Width, image.Height, DeviceGray.Instance, null, StreamFilter.FlateDecode)
94+
: null;
95+
}
96+
97+
internal static Stream AsImageByteStream(Image<Rgba32> image, GrayScaleMethod grayScaleMethod)
98+
{
99+
using (var byteStream = new MemoryStream())
100+
{
101+
image.ProcessPixelRows(accessor =>
102+
{
103+
for (int y = 0; y < accessor.Height; y++)
104+
{
105+
var pixelRow = accessor.GetRowSpan(y);
106+
107+
// pixelRow.Length has the same value as accessor.Width,
108+
// but using pixelRow.Length allows the JIT to optimize away bounds checks:
109+
for (int x = 0; x < pixelRow.Length; x++)
110+
{
111+
// Get a reference to the pixel at position x
112+
ref Rgba32 pixel = ref pixelRow[x];
113+
114+
var pixelValue = grayScaleMethod switch
115+
{
116+
GrayScaleMethod.AlphaChannel => pixel.A,
117+
GrayScaleMethod.RedChannel => pixel.R,
118+
GrayScaleMethod.GreenChannel => pixel.G,
119+
GrayScaleMethod.BlueChannel => pixel.B,
120+
GrayScaleMethod.AverageOfRGBChannels => (byte)( ( pixel.R + pixel.G + pixel.B ) / 3 ),
121+
_ => throw new NotImplementedException()
122+
};
123+
124+
byteStream.WriteByte(pixelValue);
125+
}
126+
}
127+
});
128+
129+
byteStream.Position = 0;
130+
131+
return PdfWriter.FlateEncode(byteStream);
132+
}
133+
}
150134
}

src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/StreamFilterExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public static PdfName ToPdfName(this StreamFilter streamFilter)
77
return streamFilter switch
88
{
99
StreamFilter.DCTDecode => PdfName.Get("DCTDecode"),
10+
StreamFilter.FlateDecode => PdfName.Get("FlateDecode"),
1011
_ => throw new NotImplementedException(),
1112
};
1213
}

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,7 @@ public PdfName AddJpgUnsafe(System.IO.Stream jpgStream, int originalWidth, int o
5757
{
5858
var id = _tableBuilder.ReserveId();
5959

60-
var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, null);
61-
62-
return AddImage(pdfImage);
63-
}
64-
65-
public PdfName AddJpgUnsafe(System.IO.Stream jpgStream, int originalWidth, int originalHeight, PdfName colorSpace, double[] decodeArray)
66-
{
67-
var id = _tableBuilder.ReserveId();
68-
69-
var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, decodeArray, null);
60+
var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight, colorSpace, null, StreamFilter.DCTDecode);
7061

7162
return AddImage(pdfImage);
7263
}

src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs

Lines changed: 26 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Synercoding.FileFormats.Pdf.LowLevel.Internal;
44
using Synercoding.FileFormats.Pdf.LowLevel.Text;
55
using Synercoding.FileFormats.Pdf.LowLevel.XRef;
6+
using System.IO.Compression;
67

78
namespace Synercoding.FileFormats.Pdf.LowLevel;
89

@@ -26,7 +27,8 @@ public ObjectStream Write(ContentStream contentStream)
2627
if (!_tableBuilder.TrySetPosition(contentStream.Reference, InnerStream.Position))
2728
return this;
2829

29-
_indirectStream(contentStream.Reference, contentStream.InnerStream.InnerStream);
30+
using (var flateStream = PdfWriter.FlateEncode(contentStream.InnerStream.InnerStream))
31+
_indirectStream(contentStream.Reference, flateStream, StreamFilter.FlateDecode);
3032

3133
return this;
3234
}
@@ -89,79 +91,46 @@ public ObjectStream Write(PageTree pageTree)
8991

9092
public ObjectStream Write(Image image)
9193
{
92-
if (image is SeparationImage spotImage)
93-
return Write(spotImage);
94-
if (image is SoftMask maskImage)
95-
return Write(maskImage);
96-
9794
if (!_tableBuilder.TrySetPosition(image.Reference, InnerStream.Position))
9895
return this;
9996

100-
_indirectStream(image.Reference, image.RawStream, image, static (image, dictionary) =>
101-
{
102-
dictionary
103-
.Write(PdfName.Get("Type"), PdfName.Get("XObject"))
104-
.Write(PdfName.Get("Subtype"), PdfName.Get("Image"))
105-
.Write(PdfName.Get("Width"), image.Width)
106-
.Write(PdfName.Get("Height"), image.Height)
107-
.Write(PdfName.Get("ColorSpace"), image.ColorSpace)
108-
.Write(PdfName.Get("BitsPerComponent"), 8)
109-
.Write(PdfName.Get("Decode"), image.DecodeArray)
110-
.WriteIfNotNull(PdfName.Get("SMask"), image.SoftMask?.Reference);
111-
}, StreamFilter.DCTDecode);
112-
113-
if (image.SoftMask != null)
114-
Write(image.SoftMask);
115-
116-
return this;
117-
}
118-
119-
public ObjectStream Write(SeparationImage spotImage)
120-
{
121-
if (!_tableBuilder.TrySetPosition(spotImage.Reference, InnerStream.Position))
122-
return this;
123-
124-
var separationId = _tableBuilder.GetSeparationId(spotImage.Separation);
125-
126-
_indirectStream(spotImage.Reference, spotImage.RawStream, (spotImage, separationId), static (tuple, dictionary) =>
97+
_indirectStream(image.Reference, image.RawStream, (image, _tableBuilder), static (tuple, dictionary) =>
12798
{
128-
var (image, sepId) = tuple;
129-
99+
var (image, tableBuilder) = tuple;
130100
dictionary
131101
.Write(PdfName.Get("Type"), PdfName.Get("XObject"))
132102
.Write(PdfName.Get("Subtype"), PdfName.Get("Image"))
133103
.Write(PdfName.Get("Width"), image.Width)
134104
.Write(PdfName.Get("Height"), image.Height)
135-
.Write(PdfName.Get("ColorSpace"), sepId)
136105
.Write(PdfName.Get("BitsPerComponent"), 8)
137-
.Write(PdfName.Get("Decode"), image.DecodeArray)
106+
.Write(PdfName.Get("Decode"), _decodeArray(image.ColorSpace))
138107
.WriteIfNotNull(PdfName.Get("SMask"), image.SoftMask?.Reference);
139-
});
140108

141-
if(spotImage.SoftMask != null)
142-
Write(spotImage.SoftMask);
143109

144-
return Write(spotImage.Separation);
145-
}
110+
if (image.ColorSpace is Separation separation)
111+
{
112+
var sepId = tableBuilder.GetSeparationId(separation);
113+
dictionary.Write(PdfName.Get("ColorSpace"), sepId);
114+
}
115+
else
116+
{
117+
dictionary.Write(PdfName.Get("ColorSpace"), image.ColorSpace.Name);
118+
}
119+
}, image.Filters);
146120

147-
public ObjectStream Write(SoftMask softMask)
148-
{
149-
if (!_tableBuilder.TrySetPosition(softMask.Reference, InnerStream.Position))
150-
return this;
121+
if (image.SoftMask != null)
122+
Write(image.SoftMask);
151123

152-
_indirectStream(softMask.Reference, softMask.RawStream, softMask, static (image, dictionary) =>
153-
{
154-
dictionary
155-
.Write(PdfName.Get("Type"), PdfName.Get("XObject"))
156-
.Write(PdfName.Get("Subtype"), PdfName.Get("Image"))
157-
.Write(PdfName.Get("Width"), image.Width)
158-
.Write(PdfName.Get("Height"), image.Height)
159-
.Write(PdfName.Get("ColorSpace"), DeviceGray.Instance.Name)
160-
.Write(PdfName.Get("BitsPerComponent"), 8)
161-
.Write(PdfName.Get("Decode"), image.DecodeArray);
162-
});
124+
if(image.ColorSpace is Separation separation)
125+
Write(separation);
163126

164127
return this;
128+
129+
static double[] _decodeArray(ColorSpace colorSpace)
130+
=> Enumerable.Range(0, colorSpace.Components)
131+
.Select(_ => new double[] { 0, 1 })
132+
.SelectMany(x => x)
133+
.ToArray();
165134
}
166135

167136
public ObjectStream Write(PdfPage page)

src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,13 +314,19 @@ public PdfDictionary WriteIfNotNull(PdfName key, Rectangle? rectangle)
314314
? Write(key, rectangle.Value)
315315
: this;
316316

317+
/// <summary>
318+
/// Writes a pdf reference to the dictionary if it is not null
319+
/// </summary>
320+
/// <param name="key">The key of the item in the dictionary</param>
321+
/// <param name="value">The reference to add.</param>
322+
/// <returns>The <see cref="PdfDictionary"/> to support chaining operations.</returns>
317323
public PdfDictionary WriteIfNotNull(PdfName key, PdfReference? value)
318324
=> value.HasValue
319325
? Write(key, value.Value)
320326
: this;
321327

322328
/// <summary>
323-
/// Write a number to the stream if it is not null
329+
/// Write a number to the dictionary if it is not null
324330
/// </summary>
325331
/// <param name="key">The key of the item in the dictionary</param>
326332
/// <param name="value">The number to write.</param>
@@ -331,7 +337,7 @@ public PdfDictionary WriteIfNotNull(PdfName key, int? value)
331337
: this;
332338

333339
/// <summary>
334-
/// Write a number to the stream if it is not null
340+
/// Write a number to the dictionary if it is not null
335341
/// </summary>
336342
/// <param name="key">The key of the item in the dictionary</param>
337343
/// <param name="value">The number to write.</param>

0 commit comments

Comments
 (0)