Browse Source

DeduceJpegColorSpace()

af/merge-core
Anton Firszov 9 years ago
parent
commit
1c83ea59f9
  1. 25
      src/ImageSharp/Formats/Jpeg/Common/Decoder/IRawJpegData.cs
  2. 20
      src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorSpace.cs
  3. 162
      src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs
  4. 18
      tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs
  5. BIN
      tests/Images/Input/Jpg/baseline/jpeg420small.jpg.dctdump

25
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
{
/// <summary>
/// Represents decompressed, unprocessed jpeg data with spectral space <see cref="IJpegComponent"/>-s.
/// </summary>
internal interface IRawJpegData internal interface IRawJpegData
{ {
/// <summary>
/// Gets the image size in pixels.
/// </summary>
Size ImageSizeInPixels { get; } Size ImageSizeInPixels { get; }
/// <summary>
/// Gets the number of coponents.
/// </summary>
int ComponentCount { get; } int ComponentCount { get; }
/// <summary>
/// Gets the color space
/// </summary>
JpegColorSpace ColorSpace { get; }
/// <summary>
/// Gets the components.
/// </summary>
IEnumerable<IJpegComponent> Components { get; } IEnumerable<IJpegComponent> Components { get; }
/// <summary> /// <summary>

20
src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorSpace.cs

@ -0,0 +1,20 @@
namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder
{
/// <summary>
/// Identifies the colorspace of a Jpeg image
/// </summary>
internal enum JpegColorSpace
{
Undefined = 0,
GrayScale,
Ycck,
Cmyk,
RGB,
YCbCr
}
}

162
src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs

@ -4,14 +4,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.Common;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc; using SixLabors.ImageSharp.MetaData.Profiles.Icc;
@ -119,6 +117,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
/// </summary> /// </summary>
public SubsampleRatio SubsampleRatio { get; private set; } public SubsampleRatio SubsampleRatio { get; private set; }
public JpegColorSpace ColorSpace { get; private set; }
/// <summary> /// <summary>
/// Gets the component array /// Gets the component array
/// </summary> /// </summary>
@ -592,22 +592,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
0, 0,
image.Height, image.Height,
y => 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]; // TODO: Simplify + optimize + share duplicate code across converter methods
byte magenta = this.ycbcrImage.CbChannel[co + (x / scale)]; int yo = this.ycbcrImage.GetRowYOffset(y);
byte yellow = this.ycbcrImage.CrChannel[co + (x / scale)]; int co = this.ycbcrImage.GetRowCOffset(y);
TPixel packed = default(TPixel); for (int x = 0; x < image.Width; x++)
this.PackCmyk(ref packed, cyan, magenta, yellow, x, y); {
pixels[x, y] = packed; 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); this.AssignResolution(image);
@ -691,34 +691,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
using (PixelAccessor<TPixel> pixels = image.Lock()) using (PixelAccessor<TPixel> pixels = image.Lock())
{ {
Parallel.For( Parallel.For(
0, 0,
image.Height, image.Height,
image.Configuration.ParallelOptions, image.Configuration.ParallelOptions,
y => y =>
{ {
// TODO. This Parallel loop doesn't give us the boost it should. // TODO. This Parallel loop doesn't give us the boost it should.
ref byte ycRef = ref this.ycbcrImage.YChannel[0]; ref byte ycRef = ref this.ycbcrImage.YChannel[0];
ref byte cbRef = ref this.ycbcrImage.CbChannel[0]; ref byte cbRef = ref this.ycbcrImage.CbChannel[0];
ref byte crRef = ref this.ycbcrImage.CrChannel[0]; ref byte crRef = ref this.ycbcrImage.CrChannel[0];
fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables) fixed (YCbCrToRgbTables* tables = &yCbCrToRgbTables)
{ {
// TODO: Simplify + optimize + share duplicate code across converter methods // TODO: Simplify + optimize + share duplicate code across converter methods
int yo = this.ycbcrImage.GetRowYOffset(y); int yo = this.ycbcrImage.GetRowYOffset(y);
int co = this.ycbcrImage.GetRowCOffset(y); int co = this.ycbcrImage.GetRowCOffset(y);
for (int x = 0; x < image.Width; x++) for (int x = 0; x < image.Width; x++)
{ {
int cOff = co + (x / scale); int cOff = co + (x / scale);
byte yy = Unsafe.Add(ref ycRef, yo + x); byte yy = Unsafe.Add(ref ycRef, yo + x);
byte cb = Unsafe.Add(ref cbRef, cOff); byte cb = Unsafe.Add(ref cbRef, cOff);
byte cr = Unsafe.Add(ref crRef, cOff); byte cr = Unsafe.Add(ref crRef, cOff);
TPixel packed = default(TPixel); TPixel packed = default(TPixel);
YCbCrToRgbTables.Pack(ref packed, tables, yy, cb, cr); YCbCrToRgbTables.Pack(ref packed, tables, yy, cb, cr);
pixels[x, y] = packed; pixels[x, y] = packed;
} }
} }
}); });
} }
this.AssignResolution(image); this.AssignResolution(image);
@ -921,12 +921,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
byte[] profile = new byte[remaining]; byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining); this.InputProcessor.ReadFull(profile, 0, remaining);
if (profile[0] == 'E' && if (profile[0] == 'E' && profile[1] == 'x' && profile[2] == 'i' && profile[3] == 'f' && profile[4] == '\0'
profile[1] == 'x' && && profile[5] == '\0')
profile[2] == 'i' &&
profile[3] == 'f' &&
profile[4] == '\0' &&
profile[5] == '\0')
{ {
this.isExif = true; this.isExif = true;
this.MetaData.ExifProfile = new ExifProfile(profile); this.MetaData.ExifProfile = new ExifProfile(profile);
@ -951,18 +947,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
this.InputProcessor.ReadFull(identifier, 0, Icclength); this.InputProcessor.ReadFull(identifier, 0, Icclength);
remaining -= Icclength; // we have read it by this point remaining -= Icclength; // we have read it by this point
if (identifier[0] == 'I' && if (identifier[0] == 'I' && identifier[1] == 'C' && identifier[2] == 'C' && identifier[3] == '_'
identifier[1] == 'C' && && identifier[4] == 'P' && identifier[5] == 'R' && identifier[6] == 'O' && identifier[7] == 'F'
identifier[2] == 'C' && && identifier[8] == 'I' && identifier[9] == 'L' && identifier[10] == 'E' && identifier[11] == '\0')
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]; byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining); this.InputProcessor.ReadFull(profile, 0, remaining);
@ -999,11 +986,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
remaining -= 13; remaining -= 13;
// TODO: We should be using constants for this. // TODO: We should be using constants for this.
this.isJfif = this.Temp[0] == 'J' && this.isJfif = this.Temp[0] == 'J' && this.Temp[1] == 'F' && this.Temp[2] == 'I' && this.Temp[3] == 'F'
this.Temp[1] == 'F' && && this.Temp[4] == '\x00';
this.Temp[2] == 'I' &&
this.Temp[3] == 'F' &&
this.Temp[4] == '\x00';
if (this.isJfif) if (this.isJfif)
{ {
@ -1178,7 +1162,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
int width = (this.Temp[3] << 8) + this.Temp[4]; int width = (this.Temp[3] << 8) + this.Temp[4];
this.ImageSizeInPixels = new Size(width, height); this.ImageSizeInPixels = new Size(width, height);
if (this.Temp[5] != this.ComponentCount) if (this.Temp[5] != this.ComponentCount)
{ {
throw new ImageFormatException("SOF has wrong length"); throw new ImageFormatException("SOF has wrong length");
@ -1204,7 +1188,43 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort
component.InitializeDerivedData(this); component.InitializeDerivedData(this);
} }
this.ColorSpace = this.DeduceJpegColorSpace();
this.SubsampleRatio = ComponentUtils.GetSubsampleRatio(this.Components); 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.");
}
}
} }
} }

18
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;
using SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort;
using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
@ -20,6 +23,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
this.Output = output; 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] [Fact]
public void ComponentScalingIsCorrect_1ChannelJpeg() public void ComponentScalingIsCorrect_1ChannelJpeg()
{ {

BIN
tests/Images/Input/Jpg/baseline/jpeg420small.jpg.dctdump

Binary file not shown.
Loading…
Cancel
Save