Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/ImageSharp/Formats/Jpeg/Components/Encoder/L8ToYConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

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

namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Provides 8-bit lookup tables for converting from L8 to Y colorspace.
/// </summary>
internal unsafe struct L8ToYConverter
{
/// <summary>
/// Initializes
/// </summary>
/// <returns>The initialized <see cref="L8ToYConverter"/></returns>
public static L8ToYConverter Create()
{
L8ToYConverter converter = default;
return converter;
}

/// <summary>
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ConvertPixelInto(
int l,
ref Block8x8F yResult,
int i) => yResult[i] = l;

public void Convert(Span<L8> l8Span, ref Block8x8F yBlock)
{
ref L8 l8Start = ref l8Span[0];

for (int i = 0; i < 64; i++)
{
ref L8 c = ref Unsafe.Add(ref l8Start, i);

this.ConvertPixelInto(
c.PackedValue,
ref yBlock,
i);
}
}
}
}
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 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>
/// The converter
/// </summary>
private L8ToYConverter converter;

/// <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>);
result.converter = L8ToYConverter.Create();
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;

this.converter.Convert(l8Span, ref yBlock);
}
}
}
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
}
}
20 changes: 20 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.EnrichColorType<TPixel>();
encoder.Encode(image, stream);
}

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

/// <summary>
/// If ColorType was not set, set it based on the given <typeparamref name="TPixel"/>.
/// </summary>
private void EnrichColorType<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.ColorType == null)
{
bool isGrayscale = typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be storing the JpegColorType in the JpegMetadata class upon decode and querying both places with the encoder options taking precedence.

La16 and La32 should trigger grayscale also.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added La16 and La32 to JpegEncoder and added test cases for both.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unresolving the conversation, since I think @JimBobSquarePants also asked for a decoder extension here, will try to explain what he likely meant in more detailed manner:

  • Define an optional JpegColorType? property in JpegMetadata
  • Detect JpegColorType in JpegDecoderCore and store it into the image's JpegMetadata.
  • In the encoder, also trigger the grayscale path in case if JpegMetadata.JpegColorType is set to JpegColorType.GrayScale

@ynse01 let us know if you feel comfortable and confident adding this. If not, I recommend to not block the PR on this, and open a tracking issue for the addition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave it a try. Please indicate if this is what you guys expect.

this.ColorType = isGrayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
}
}
}
}
Loading