Skip to content

Commit ed9bd16

Browse files
committed
Split WriteApplicationHeader into WriteStartOfImage and WriteJfifApplicationHeader. Do not write WriteJfifApplicationHeader with RGB.
1 parent b506e92 commit ed9bd16

File tree

4 files changed

+51
-36
lines changed

4 files changed

+51
-36
lines changed
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

44
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
55
{
66
/// <summary>
7-
/// Enumerates the quantization tables
7+
/// Enumerates the quantization tables.
88
/// </summary>
99
internal enum QuantIndex
1010
{
1111
/// <summary>
12-
/// The luminance quantization table index
12+
/// The luminance quantization table index.
1313
/// </summary>
1414
Luminance = 0,
1515

1616
/// <summary>
17-
/// The chrominance quantization table index
17+
/// The chrominance quantization table index.
1818
/// </summary>
1919
Chrominance = 1,
2020
}
21-
}
21+
}

src/ImageSharp/Formats/Jpeg/JpegColorType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public enum JpegColorType : byte
4141
/// <summary>
4242
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
4343
/// This ratio uses half of the vertical and one-fourth the horizontal color resolutions.
44-
///
44+
///
4545
/// Note: Not supported by the encoder.
4646
/// </summary>
4747
YCbCrRatio410 = 4,

src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,13 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
111111
this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable);
112112

113113
// Write the Start Of Image marker.
114-
this.WriteApplicationHeader(metadata);
114+
this.WriteStartOfImage();
115+
116+
// Do not write APP0 marker for RGB colorspace.
117+
if (this.colorType != JpegColorType.Rgb)
118+
{
119+
this.WriteJfifApplicationHeader(metadata);
120+
}
115121

116122
// Write Exif, ICC and IPTC profiles
117123
this.WriteProfiles(metadata);
@@ -212,52 +218,60 @@ private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref
212218
}
213219

214220
/// <summary>
215-
/// Writes the application header containing the JFIF identifier plus extra data.
221+
/// Write the start of image marker.
216222
/// </summary>
217-
/// <param name="meta">The image metadata.</param>
218-
private void WriteApplicationHeader(ImageMetadata meta)
223+
private void WriteStartOfImage()
219224
{
220-
// Write the start of image marker. Markers are always prefixed with 0xff.
225+
// Markers are always prefixed with 0xff.
221226
this.buffer[0] = JpegConstants.Markers.XFF;
222227
this.buffer[1] = JpegConstants.Markers.SOI;
223228

229+
this.outputStream.Write(this.buffer, 0, 2);
230+
}
231+
232+
/// <summary>
233+
/// Writes the application header containing the JFIF identifier plus extra data.
234+
/// </summary>
235+
/// <param name="meta">The image metadata.</param>
236+
private void WriteJfifApplicationHeader(ImageMetadata meta)
237+
{
224238
// Write the JFIF headers
225-
this.buffer[2] = JpegConstants.Markers.XFF;
226-
this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker
227-
this.buffer[4] = 0x00;
228-
this.buffer[5] = 0x10;
229-
this.buffer[6] = 0x4a; // J
239+
this.buffer[0] = JpegConstants.Markers.XFF;
240+
this.buffer[1] = JpegConstants.Markers.APP0; // Application Marker
241+
this.buffer[2] = 0x00;
242+
this.buffer[3] = 0x10;
243+
this.buffer[4] = 0x4a; // J
244+
this.buffer[5] = 0x46; // F
245+
this.buffer[6] = 0x49; // I
230246
this.buffer[7] = 0x46; // F
231-
this.buffer[8] = 0x49; // I
232-
this.buffer[9] = 0x46; // F
233-
this.buffer[10] = 0x00; // = "JFIF",'\0'
234-
this.buffer[11] = 0x01; // versionhi
235-
this.buffer[12] = 0x01; // versionlo
247+
this.buffer[8] = 0x00; // = "JFIF",'\0'
248+
this.buffer[9] = 0x01; // versionhi
249+
this.buffer[10] = 0x01; // versionlo
236250

237251
// Resolution. Big Endian
238-
Span<byte> hResolution = this.buffer.AsSpan(14, 2);
239-
Span<byte> vResolution = this.buffer.AsSpan(16, 2);
252+
Span<byte> hResolution = this.buffer.AsSpan(12, 2);
253+
Span<byte> vResolution = this.buffer.AsSpan(14, 2);
240254

241255
if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter)
242256
{
243257
// Scale down to PPI
244-
this.buffer[13] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits
258+
this.buffer[11] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits
245259
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution)));
246260
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution)));
247261
}
248262
else
249263
{
250264
// We can simply pass the value.
251-
this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits
265+
this.buffer[11] = (byte)meta.ResolutionUnits; // xyunits
252266
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution));
253267
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution));
254268
}
255269

256270
// No thumbnail
257-
this.buffer[18] = 0x00; // Thumbnail width
258-
this.buffer[19] = 0x00; // Thumbnail height
271+
this.buffer[16] = 0x00; // Thumbnail width
272+
this.buffer[17] = 0x00; // Thumbnail height
259273

260-
this.outputStream.Write(this.buffer, 0, 20);
274+
this.outputStream.Write(this.buffer, 0, 18);
261275
}
262276

263277
/// <summary>

tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,22 +162,23 @@ public void EncodeBaseline_CalliphoraPartial<TPixel>(TestImageProvider<TPixel> p
162162

163163
[Theory]
164164
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
165-
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)]
166-
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)]
167-
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)]
165+
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 158, 24, PixelTypes.Rgba32)]
168166
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)]
169167
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)]
168+
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)]
170169
public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
171170
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
172171

173172
[Theory]
173+
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)]
174174
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)]
175+
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 143, 81, PixelTypes.Rgba32)]
175176
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)]
176-
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)]
177+
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)]
178+
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)]
177179
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)]
178180
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)]
179181
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)]
180-
181182
public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
182183
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f));
183184

@@ -226,14 +227,14 @@ private static ImageComparer GetComparer(int quality, JpegColorType? colorType)
226227

227228
if (quality < 50)
228229
{
229-
tolerance *= 2.5f;
230+
tolerance *= 4.5f;
230231
}
231232
else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420)
232233
{
233-
tolerance *= 1.5f;
234+
tolerance *= 2.0f;
234235
if (colorType == JpegColorType.YCbCrRatio420)
235236
{
236-
tolerance *= 2f;
237+
tolerance *= 2.0f;
237238
}
238239
}
239240

0 commit comments

Comments
 (0)