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.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Enumerates the quantization tables
/// Enumerates the quantization tables.
/// </summary>
internal enum QuantIndex
{
/// <summary>
/// The luminance quantization table index
/// The luminance quantization table index.
/// </summary>
Luminance = 0,
/// <summary>
/// The chrominance quantization table index
/// The chrominance quantization table index.
/// </summary>
Chrominance = 1,
}
}
}

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

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// 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.
///
///
/// Note: Not supported by the encoder.
/// </summary>
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);
// 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
this.WriteProfiles(metadata);
@ -212,52 +218,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Writes the application header containing the JFIF identifier plus extra data.
/// Write the start of image marker.
/// </summary>
/// <param name="meta">The image metadata.</param>
private void WriteApplicationHeader(ImageMetadata meta)
private void WriteStartOfImage()
{
// 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[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
this.buffer[2] = JpegConstants.Markers.XFF;
this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker
this.buffer[4] = 0x00;
this.buffer[5] = 0x10;
this.buffer[6] = 0x4a; // J
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP0; // Application Marker
this.buffer[2] = 0x00;
this.buffer[3] = 0x10;
this.buffer[4] = 0x4a; // J
this.buffer[5] = 0x46; // F
this.buffer[6] = 0x49; // I
this.buffer[7] = 0x46; // F
this.buffer[8] = 0x49; // I
this.buffer[9] = 0x46; // F
this.buffer[10] = 0x00; // = "JFIF",'\0'
this.buffer[11] = 0x01; // versionhi
this.buffer[12] = 0x01; // versionlo
this.buffer[8] = 0x00; // = "JFIF",'\0'
this.buffer[9] = 0x01; // versionhi
this.buffer[10] = 0x01; // versionlo
// Resolution. Big Endian
Span<byte> hResolution = this.buffer.AsSpan(14, 2);
Span<byte> vResolution = this.buffer.AsSpan(16, 2);
Span<byte> hResolution = this.buffer.AsSpan(12, 2);
Span<byte> vResolution = this.buffer.AsSpan(14, 2);
if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter)
{
// 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(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution)));
}
else
{
// 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(vResolution, (short)Math.Round(meta.VerticalResolution));
}
// No thumbnail
this.buffer[18] = 0x00; // Thumbnail width
this.buffer[19] = 0x00; // Thumbnail height
this.buffer[16] = 0x00; // Thumbnail width
this.buffer[17] = 0x00; // Thumbnail height
this.outputStream.Write(this.buffer, 0, 20);
this.outputStream.Write(this.buffer, 0, 18);
}
/// <summary>

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

@ -162,22 +162,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 158, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, 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)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
[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)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 143, 81, 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), 46, 8, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)]
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));
@ -226,14 +227,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (quality < 50)
{
tolerance *= 2.5f;
tolerance *= 4.5f;
}
else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420)
{
tolerance *= 1.5f;
tolerance *= 2.0f;
if (colorType == JpegColorType.YCbCrRatio420)
{
tolerance *= 2f;
tolerance *= 2.0f;
}
}

Loading…
Cancel
Save