diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs
index afc1472d0..0e4f953f3 100644
--- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs
@@ -1,15 +1,32 @@
-namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
-{
- using System.Collections.Generic;
+using System.Collections.Generic;
- using SixLabors.Primitives;
+using SixLabors.Primitives;
+namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
+{
+ ///
+ /// Represents decompressed, unprocessed jpeg data with spectral space -s.
+ ///
internal interface IRawJpegData
{
+ ///
+ /// Gets the image size in pixels.
+ ///
Size ImageSizeInPixels { get; }
+ ///
+ /// Gets the number of coponents.
+ ///
int ComponentCount { get; }
+ ///
+ /// Gets the color space
+ ///
+ JpegColorSpace ColorSpace { get; }
+
+ ///
+ /// Gets the components.
+ ///
IEnumerable Components { get; }
///
diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorSpace.cs
new file mode 100644
index 000000000..da353d279
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorSpace.cs
@@ -0,0 +1,20 @@
+namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
+{
+ ///
+ /// Identifies the colorspace of a Jpeg image
+ ///
+ internal enum JpegColorSpace
+ {
+ Undefined = 0,
+
+ GrayScale,
+
+ Ycck,
+
+ Cmyk,
+
+ RGB,
+
+ YCbCr
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
index 6ff71af63..445578ff5 100644
--- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
@@ -4,14 +4,12 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
-using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
+
using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
@@ -119,6 +117,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
///
public SubsampleRatio SubsampleRatio { get; private set; }
+ public JpegColorSpace ColorSpace { get; private set; }
+
///
/// Gets the component array
///
@@ -592,22 +592,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
0,
image.Height,
y =>
- {
- // TODO: Simplify + optimize + share duplicate code across converter methods
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
-
- for (int x = 0; x < image.Width; x++)
{
- byte cyan = this.ycbcrImage.YChannel[yo + x];
- byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)];
- byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)];
-
- TPixel packed = default(TPixel);
- this.PackCmyk(ref packed, cyan, magenta, yellow, x, y);
- pixels[x, y] = packed;
- }
- });
+ // TODO: Simplify + optimize + share duplicate code across converter methods
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
+
+ for (int x = 0; x < image.Width; x++)
+ {
+ byte cyan = this.ycbcrImage.YChannel[yo + x];
+ byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)];
+ byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)];
+
+ TPixel packed = default(TPixel);
+ this.PackCmyk(ref packed, cyan, magenta, yellow, x, y);
+ pixels[x, y] = packed;
+ }
+ });
}
this.AssignResolution(image);
@@ -691,34 +691,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
using (PixelAccessor pixels = image.Lock())
{
Parallel.For(
- 0,
- image.Height,
- image.Configuration.ParallelOptions,
- y =>
- {
- // TODO. This Parallel loop doesn't give us the boost it should.
- ref byte ycRef = ref this.ycbcrImage.YChannel[0];
- ref byte cbRef = ref this.ycbcrImage.CbChannel[0];
- ref byte crRef = ref this.ycbcrImage.CrChannel[0];
- fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables)
- {
- // TODO: Simplify + optimize + share duplicate code across converter methods
- int yo = this.ycbcrImage.GetRowYOffset(y);
- int co = this.ycbcrImage.GetRowCOffset(y);
-
- for (int x = 0; x < image.Width; x++)
- {
- int cOff = co + (x / scale);
- byte yy = Unsafe.Add(ref ycRef, yo + x);
- byte cb = Unsafe.Add(ref cbRef, cOff);
- byte cr = Unsafe.Add(ref crRef, cOff);
-
- TPixel packed = default(TPixel);
- YCbCrToRgbTables.Pack(ref packed, tables, yy, cb, cr);
- pixels[x, y] = packed;
- }
- }
- });
+ 0,
+ image.Height,
+ image.Configuration.ParallelOptions,
+ y =>
+ {
+ // TODO. This Parallel loop doesn't give us the boost it should.
+ ref byte ycRef = ref this.ycbcrImage.YChannel[0];
+ ref byte cbRef = ref this.ycbcrImage.CbChannel[0];
+ ref byte crRef = ref this.ycbcrImage.CrChannel[0];
+ fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables)
+ {
+ // TODO: Simplify + optimize + share duplicate code across converter methods
+ int yo = this.ycbcrImage.GetRowYOffset(y);
+ int co = this.ycbcrImage.GetRowCOffset(y);
+
+ for (int x = 0; x < image.Width; x++)
+ {
+ int cOff = co + (x / scale);
+ byte yy = Unsafe.Add(ref ycRef, yo + x);
+ byte cb = Unsafe.Add(ref cbRef, cOff);
+ byte cr = Unsafe.Add(ref crRef, cOff);
+
+ TPixel packed = default(TPixel);
+ YCbCrToRgbTables.Pack(ref packed, tables, yy, cb, cr);
+ pixels[x, y] = packed;
+ }
+ }
+ });
}
this.AssignResolution(image);
@@ -921,12 +921,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
- if (profile[0] == 'E' &&
- profile[1] == 'x' &&
- profile[2] == 'i' &&
- profile[3] == 'f' &&
- profile[4] == '\0' &&
- profile[5] == '\0')
+ if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0'
+ && profile[5] == '\0')
{
this.isExif = true;
this.MetaData.ExifProfile = new ExifProfile(profile);
@@ -951,18 +947,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.InputProcessor.ReadFull(identifier, 0, Icclength);
remaining -= Icclength; // we have read it by this point
- if (identifier[0] == 'I' &&
- identifier[1] == 'C' &&
- identifier[2] == 'C' &&
- identifier[3] == '_' &&
- identifier[4] == 'P' &&
- identifier[5] == 'R' &&
- identifier[6] == 'O' &&
- identifier[7] == 'F' &&
- identifier[8] == 'I' &&
- identifier[9] == 'L' &&
- identifier[10] == 'E' &&
- identifier[11] == '\0')
+ if (identifier[0] == 'I' && identifier[1] == 'C' && identifier[2] == 'C' && identifier[3] == '_'
+ && identifier[4] == 'P' && identifier[5] == 'R' && identifier[6] == 'O' && identifier[7] == 'F'
+ && identifier[8] == 'I' && identifier[9] == 'L' && identifier[10] == 'E' && identifier[11] == '\0')
{
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
@@ -999,11 +986,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
remaining -= 13;
// TODO: We should be using constants for this.
- this.isJfif = this.Temp[0] == 'J' &&
- this.Temp[1] == 'F' &&
- this.Temp[2] == 'I' &&
- this.Temp[3] == 'F' &&
- this.Temp[4] == '\x00';
+ this.isJfif = this.Temp[0] == 'J' && this.Temp[1] == 'F' && this.Temp[2] == 'I' && this.Temp[3] == 'F'
+ && this.Temp[4] == '\x00';
if (this.isJfif)
{
@@ -1178,7 +1162,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
int width = (this.Temp[3] << 8) + this.Temp[4];
this.ImageSizeInPixels = new Size(width, height);
-
+
if (this.Temp[5] != this.ComponentCount)
{
throw new ImageFormatException("SOF has wrong length");
@@ -1204,7 +1188,43 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
component.InitializeDerivedData(this);
}
+ this.ColorSpace = this.DeduceJpegColorSpace();
+
this.SubsampleRatio = ComponentUtils.GetSubsampleRatio(this.Components);
}
+
+ private JpegColorSpace DeduceJpegColorSpace()
+ {
+ switch (this.ComponentCount)
+ {
+ case 1: return JpegColorSpace.GrayScale;
+ case 3: return this.IsRGB() ? JpegColorSpace.RGB : JpegColorSpace.YCbCr;
+ case 4:
+
+ if (!this.adobeTransformValid)
+ {
+ throw new ImageFormatException(
+ "Unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata");
+ }
+
+ // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe
+ // See https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
+ // TODO: YCbCrA?
+ if (this.adobeTransform == OrigJpegConstants.Adobe.ColorTransformYcck)
+ {
+ return JpegColorSpace.Ycck;
+ }
+ else if (this.adobeTransform == OrigJpegConstants.Adobe.ColorTransformUnknown)
+ {
+ // Assume CMYK
+ return JpegColorSpace.Cmyk;
+ }
+
+ goto default;
+
+ default:
+ throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces.");
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
index 058681870..e56e91207 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
@@ -1,4 +1,7 @@
+using System;
+
using SixLabors.ImageSharp.Formats.Jpeg.Common;
+using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
@@ -20,6 +23,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output = output;
}
+ [Theory]
+ [InlineData(TestImages.Jpeg.Baseline.Testorig420, JpegColorSpace.YCbCr)]
+ [InlineData(TestImages.Jpeg.Baseline.Jpeg400, JpegColorSpace.GrayScale)]
+ [InlineData(TestImages.Jpeg.Baseline.Ycck, JpegColorSpace.Ycck)]
+ [InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorSpace.Cmyk)]
+ public void ColorSpace_IsDeducedCorrectly(string imageFile, object expectedColorSpaceValue)
+ {
+ var expecteColorSpace = (JpegColorSpace)expectedColorSpaceValue;
+
+ using (OrigJpegDecoderCore decoder = JpegFixture.ParseStream(imageFile, true))
+ {
+ Assert.Equal(expecteColorSpace, decoder.ColorSpace);
+ }
+ }
+
[Fact]
public void ComponentScalingIsCorrect_1ChannelJpeg()
{
diff --git a/tests/Images/Input/Jpg/baseline/jpeg420small.jpg.dctdump b/tests/Images/Input/Jpg/baseline/jpeg420small.jpg.dctdump
deleted file mode 100644
index 15ba4698a..000000000
Binary files a/tests/Images/Input/Jpg/baseline/jpeg420small.jpg.dctdump and /dev/null differ