Browse Source

Split WriteApplicationHeader into WriteStartOfImage and WriteJfifApplicationHeader. Do not write WriteJfifApplicationHeader with RGB.

pull/1734/head
Brian Popow 5 years ago
parent
commit
ed9bd16cd3
  1. 10
      src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs
  2. 2
      src/ImageSharp/Formats/Jpeg/JpegColorType.cs
  3. 58
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  4. 17
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

10
src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs

@ -1,21 +1,21 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// Enumerates the quantization tables /// Enumerates the quantization tables.
/// </summary> /// </summary>
internal enum QuantIndex internal enum QuantIndex
{ {
/// <summary> /// <summary>
/// The luminance quantization table index /// The luminance quantization table index.
/// </summary> /// </summary>
Luminance = 0, Luminance = 0,
/// <summary> /// <summary>
/// The chrominance quantization table index /// The chrominance quantization table index.
/// </summary> /// </summary>
Chrominance = 1, Chrominance = 1,
} }
} }

2
src/ImageSharp/Formats/Jpeg/JpegColorType.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// This ratio uses half of the vertical and one-fourth the horizontal color resolutions. /// This ratio uses half of the vertical and one-fourth the horizontal color resolutions.
/// ///
/// Note: Not supported by the encoder. /// Note: Not supported by the encoder.
/// </summary> /// </summary>
YCbCrRatio410 = 4, YCbCrRatio410 = 4,

58
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -111,7 +111,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable); this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable);
// Write the Start Of Image marker. // Write the Start Of Image marker.
this.WriteApplicationHeader(metadata); this.WriteStartOfImage();
// Do not write APP0 marker for RGB colorspace.
if (this.colorType != JpegColorType.Rgb)
{
this.WriteJfifApplicationHeader(metadata);
}
// Write Exif, ICC and IPTC profiles // Write Exif, ICC and IPTC profiles
this.WriteProfiles(metadata); this.WriteProfiles(metadata);
@ -212,52 +218,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <summary> /// <summary>
/// Writes the application header containing the JFIF identifier plus extra data. /// Write the start of image marker.
/// </summary> /// </summary>
/// <param name="meta">The image metadata.</param> private void WriteStartOfImage()
private void WriteApplicationHeader(ImageMetadata meta)
{ {
// Write the start of image marker. Markers are always prefixed with 0xff. // Markers are always prefixed with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.SOI; this.buffer[1] = JpegConstants.Markers.SOI;
this.outputStream.Write(this.buffer, 0, 2);
}
/// <summary>
/// Writes the application header containing the JFIF identifier plus extra data.
/// </summary>
/// <param name="meta">The image metadata.</param>
private void WriteJfifApplicationHeader(ImageMetadata meta)
{
// Write the JFIF headers // Write the JFIF headers
this.buffer[2] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker this.buffer[1] = JpegConstants.Markers.APP0; // Application Marker
this.buffer[4] = 0x00; this.buffer[2] = 0x00;
this.buffer[5] = 0x10; this.buffer[3] = 0x10;
this.buffer[6] = 0x4a; // J this.buffer[4] = 0x4a; // J
this.buffer[5] = 0x46; // F
this.buffer[6] = 0x49; // I
this.buffer[7] = 0x46; // F this.buffer[7] = 0x46; // F
this.buffer[8] = 0x49; // I this.buffer[8] = 0x00; // = "JFIF",'\0'
this.buffer[9] = 0x46; // F this.buffer[9] = 0x01; // versionhi
this.buffer[10] = 0x00; // = "JFIF",'\0' this.buffer[10] = 0x01; // versionlo
this.buffer[11] = 0x01; // versionhi
this.buffer[12] = 0x01; // versionlo
// Resolution. Big Endian // Resolution. Big Endian
Span<byte> hResolution = this.buffer.AsSpan(14, 2); Span<byte> hResolution = this.buffer.AsSpan(12, 2);
Span<byte> vResolution = this.buffer.AsSpan(16, 2); Span<byte> vResolution = this.buffer.AsSpan(14, 2);
if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter) if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter)
{ {
// Scale down to PPI // Scale down to PPI
this.buffer[13] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits this.buffer[11] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution))); BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution))); BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution)));
} }
else else
{ {
// We can simply pass the value. // We can simply pass the value.
this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits this.buffer[11] = (byte)meta.ResolutionUnits; // xyunits
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution)); BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution)); BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution));
} }
// No thumbnail // No thumbnail
this.buffer[18] = 0x00; // Thumbnail width this.buffer[16] = 0x00; // Thumbnail width
this.buffer[19] = 0x00; // Thumbnail height this.buffer[17] = 0x00; // Thumbnail height
this.outputStream.Write(this.buffer, 0, 20); this.outputStream.Write(this.buffer, 0, 18);
} }
/// <summary> /// <summary>

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

@ -162,22 +162,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 158, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)]
public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality) public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality); where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
[Theory] [Theory]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 143, 81, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)]
public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality) public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f)); where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f));
@ -226,14 +227,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (quality < 50) if (quality < 50)
{ {
tolerance *= 2.5f; tolerance *= 4.5f;
} }
else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420) else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420)
{ {
tolerance *= 1.5f; tolerance *= 2.0f;
if (colorType == JpegColorType.YCbCrRatio420) if (colorType == JpegColorType.YCbCrRatio420)
{ {
tolerance *= 2f; tolerance *= 2.0f;
} }
} }

Loading…
Cancel
Save