Skip to content

Commit

Permalink
Merge pull request #1586 from ynse01/ynse01/jpeg-grayscale
Browse files Browse the repository at this point in the history
Grayscale Jpeg encoding
  • Loading branch information
JimBobSquarePants committed Mar 30, 2021
2 parents 08b0320 + 4d0fb40 commit bb72769
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> L8 -> Y conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct LuminanceForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;

/// <summary>
/// Temporal 8x8 block to hold TPixel data
/// </summary>
private GenericBlock8x8<TPixel> pixelBlock;

/// <summary>
/// Temporal RGB block
/// </summary>
private GenericBlock8x8<L8> l8Block;

public static LuminanceForwardConverter<TPixel> Create()
{
var result = default(LuminanceForwardConverter<TPixel>);
return result;
}

/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>)
/// </summary>
public void Convert(ImageFrame<TPixel> frame, int x, int y, ref RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows);

Span<L8> l8Span = this.l8Block.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToL8(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), l8Span);

ref Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref l8Span[0];

for (int i = 0; i < 64; i++)
{
ref L8 c = ref Unsafe.Add(ref l8Start, i);
yBlock[i] = c.PackedValue;
}
}
}
}
9 changes: 7 additions & 2 deletions src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

namespace SixLabors.ImageSharp.Formats.Jpeg
Expand All @@ -20,5 +20,10 @@ internal interface IJpegEncoderOptions
/// </summary>
/// <value>The subsample ratio of the jpg image.</value>
JpegSubsample? Subsample { get; }

/// <summary>
/// Gets the color type.
/// </summary>
JpegColorType? ColorType { get; }
}
}
}
21 changes: 21 additions & 0 deletions src/ImageSharp/Formats/Jpeg/JpegColorType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Provides enumeration of available JPEG color types.
/// </summary>
public enum JpegColorType : byte
{
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// </summary>
YCbCr = 0,

/// <summary>
/// Single channel, luminance.
/// </summary>
Luminance = 1
}
}
1 change: 1 addition & 0 deletions src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,7 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
this.Frame.MaxHorizontalFactor = maxH;
this.Frame.MaxVerticalFactor = maxV;
this.ColorSpace = this.DeduceJpegColorSpace();
this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
this.Frame.InitComponents();
this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn);
}
Expand Down
30 changes: 30 additions & 0 deletions src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
/// </summary>
public JpegSubsample? Subsample { get; set; }

/// <summary>
/// Gets or sets the color type, that will be used to encode the image.
/// </summary>
public JpegColorType? ColorType { get; set; }

/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
Expand All @@ -35,6 +40,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new JpegEncoderCore(this);
this.InitializeColorType<TPixel>(image);
encoder.Encode(image, stream);
}

Expand All @@ -50,7 +56,31 @@ public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, Cancellation
where TPixel : unmanaged, IPixel<TPixel>
{
var encoder = new JpegEncoderCore(this);
this.InitializeColorType<TPixel>(image);
return encoder.EncodeAsync(image, stream, cancellationToken);
}

/// <summary>
/// If ColorType was not set, set it based on the given image.
/// </summary>
private void InitializeColorType<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
// First inspect the image metadata.
if (this.ColorType == null)
{
JpegMetadata metadata = image.Metadata.GetJpegMetadata();
this.ColorType = metadata.ColorType;
}

// Secondly, inspect the pixel type.
if (this.ColorType == null)
{
bool isGrayscale =
typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);
this.ColorType = isGrayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
}
}
}
}
Loading

0 comments on commit bb72769

Please sign in to comment.