mirror of https://github.com/SixLabors/ImageSharp
8 changed files with 935 additions and 204 deletions
@ -0,0 +1,146 @@ |
|||
// <copyright file="JpegPixelArea.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Runtime.CompilerServices; |
|||
using ImageSharp.Memory; |
|||
|
|||
/// <summary>
|
|||
/// Represents a section of the jpeg component data laid out in pixel order.
|
|||
/// </summary>
|
|||
internal struct JpegPixelArea : IDisposable |
|||
{ |
|||
private readonly int imageWidth; |
|||
|
|||
private readonly int imageHeight; |
|||
|
|||
private Buffer<byte> componentData; |
|||
|
|||
private int rowStride; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="JpegPixelArea"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="imageWidth">The image width</param>
|
|||
/// <param name="imageHeight">The image height</param>
|
|||
/// <param name="numberOfComponents">The number of components</param>
|
|||
public JpegPixelArea(int imageWidth, int imageHeight, int numberOfComponents) |
|||
{ |
|||
this.imageWidth = imageWidth; |
|||
this.imageHeight = imageHeight; |
|||
this.Width = 0; |
|||
this.Height = 0; |
|||
this.NumberOfComponents = numberOfComponents; |
|||
this.componentData = null; |
|||
this.rowStride = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of components
|
|||
/// </summary>
|
|||
public int NumberOfComponents { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the width
|
|||
/// </summary>
|
|||
public int Width { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the height
|
|||
/// </summary>
|
|||
public int Height { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Organsizes the decoded jpeg components into a linear array ordered by component.
|
|||
/// This must be called before attempting to retrieve the data.
|
|||
/// </summary>
|
|||
/// <param name="components">The jpeg component blocks</param>
|
|||
/// <param name="width">The pixel area width</param>
|
|||
/// <param name="height">The pixel area height</param>
|
|||
public void LinearizeBlockData(ComponentBlocks components, int width, int height) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
int numberOfComponents = this.NumberOfComponents; |
|||
this.rowStride = width * numberOfComponents; |
|||
|
|||
float scaleX = this.imageWidth / (float)width; |
|||
float scaleY = this.imageHeight / (float)height; |
|||
this.componentData = new Buffer<byte>(width * height * numberOfComponents); |
|||
const uint Mask3Lsb = 0xFFFFFFF8; // Used to clear the 3 LSBs
|
|||
|
|||
using (var xScaleBlockOffset = new Buffer<int>(width)) |
|||
{ |
|||
for (int i = 0; i < numberOfComponents; i++) |
|||
{ |
|||
ref Component component = ref components.Components[i]; |
|||
float componentScaleX = component.ScaleX * scaleX; |
|||
float componentScaleY = component.ScaleY * scaleY; |
|||
int offset = i; |
|||
Buffer<short> output = component.Output; |
|||
int blocksPerScanline = (component.BlocksPerLine + 1) << 3; |
|||
|
|||
// Precalculate the xScaleBlockOffset
|
|||
int j; |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
j = 0 | (int)(x * componentScaleX); |
|||
xScaleBlockOffset[x] = (int)((j & Mask3Lsb) << 3) | (j & 7); |
|||
} |
|||
|
|||
// Linearize the blocks of the component
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
j = 0 | (int)(y * componentScaleY); |
|||
int index = blocksPerScanline * (int)(j & Mask3Lsb) | ((j & 7) << 3); |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
this.componentData[offset] = (byte)output[index + xScaleBlockOffset[x]]; |
|||
offset += numberOfComponents; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a <see cref="Span{Byte}"/> representing the row 'y' beginning from the the first byte on that row.
|
|||
/// </summary>
|
|||
/// <param name="y">The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the pixel area.</param>
|
|||
/// <returns>The <see cref="Span{TPixel}"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Span<byte> GetRowSpan(int y) |
|||
{ |
|||
this.CheckCoordinates(y); |
|||
return this.componentData.Slice(y * this.rowStride, this.rowStride); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.componentData?.Dispose(); |
|||
this.componentData = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks the coordinates to ensure they are within bounds.
|
|||
/// </summary>
|
|||
/// <param name="y">The y-coordinate of the row. Must be greater than zero and less than the height of the area.</param>
|
|||
/// <exception cref="ArgumentOutOfRangeException">
|
|||
/// Thrown if the coordinates are not within the bounds of the image.
|
|||
/// </exception>
|
|||
[Conditional("DEBUG")] |
|||
private void CheckCoordinates(int y) |
|||
{ |
|||
if (y < 0 || y >= this.Height) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the area bounds."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
namespace ImageSharp.Formats.Jpeg.Port.Components |
|||
{ |
|||
using System.Runtime.CompilerServices; |
|||
using ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Provides 8-bit lookup tables for converting from YCbCr to Rgb colorspace.
|
|||
/// Methods to build the tables are based on libjpeg implementation.
|
|||
/// </summary>
|
|||
internal struct YCbCrToRgbTables |
|||
{ |
|||
/// <summary>
|
|||
/// The red red-chrominance table
|
|||
/// </summary>
|
|||
public static int[] CrRTable = new int[256]; |
|||
|
|||
/// <summary>
|
|||
/// The blue blue-chrominance table
|
|||
/// </summary>
|
|||
public static int[] CbBTable = new int[256]; |
|||
|
|||
/// <summary>
|
|||
/// The green red-chrominance table
|
|||
/// </summary>
|
|||
public static int[] CrGTable = new int[256]; |
|||
|
|||
/// <summary>
|
|||
/// The green blue-chrominance table
|
|||
/// </summary>
|
|||
public static int[] CbGTable = new int[256]; |
|||
|
|||
// Speediest right-shift on some machines and gives us enough accuracy at 4 decimal places.
|
|||
private const int ScaleBits = 16; |
|||
|
|||
private const int Half = 1 << (ScaleBits - 1); |
|||
|
|||
private const int MinSample = 0; |
|||
|
|||
private const int HalfSample = 128; |
|||
|
|||
private const int MaxSample = 255; |
|||
|
|||
/// <summary>
|
|||
/// Initializes the YCbCr tables
|
|||
/// </summary>
|
|||
public static void Create() |
|||
{ |
|||
for (int i = 0, x = -128; i <= 255; i++, x++) |
|||
{ |
|||
// i is the actual input pixel value, in the range 0..255
|
|||
// The Cb or Cr value we are thinking of is x = i - 128
|
|||
// Cr=>R value is nearest int to 1.402 * x
|
|||
CrRTable[i] = RightShift((Fix(1.402F) * x) + Half); |
|||
|
|||
// Cb=>B value is nearest int to 1.772 * x
|
|||
CbBTable[i] = RightShift((Fix(1.772F) * x) + Half); |
|||
|
|||
// Cr=>G value is scaled-up -0.714136286
|
|||
CrGTable[i] = (-Fix(0.714136286F)) * x; |
|||
|
|||
// Cb => G value is scaled - up - 0.344136286 * x
|
|||
// We also add in Half so that need not do it in inner loop
|
|||
CbGTable[i] = ((-Fix(0.344136286F)) * x) + Half; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Optimized method to pack bytes to the image from the YCbCr color space.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="packed">The packed pixel.</param>
|
|||
/// <param name="y">The y luminance component.</param>
|
|||
/// <param name="cb">The cb chroma component.</param>
|
|||
/// <param name="cr">The cr chroma component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void PackYCbCr<TPixel>(ref TPixel packed, byte y, byte cb, byte cr) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
byte r = (byte)(y + CrRTable[cr]).Clamp(0, 255); |
|||
|
|||
// The values for the G calculation are left scaled up, since we must add them together before rounding.
|
|||
byte g = (byte)(y + RightShift(CbGTable[cb] + CrGTable[cr])).Clamp(0, 255); |
|||
|
|||
byte b = (byte)(y + CbBTable[cb]).Clamp(0, 255); |
|||
|
|||
packed.PackFromRgba32(new Rgba32(r, g, b, 255)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Optimized method to pack bytes to the image from the YccK color space.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="packed">The packed pixel.</param>
|
|||
/// <param name="y">The y luminance component.</param>
|
|||
/// <param name="cb">The cb chroma component.</param>
|
|||
/// <param name="cr">The cr chroma component.</param>
|
|||
/// <param name="k">The keyline component.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void PackYccK<TPixel>(ref TPixel packed, byte y, byte cb, byte cr, byte k) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
int c = (MaxSample - (y + CrRTable[cr])).Clamp(0, 255); |
|||
|
|||
// The values for the G calculation are left scaled up, since we must add them together before rounding.
|
|||
int m = (MaxSample - (y + RightShift(CbGTable[cb] + CrGTable[cr]))).Clamp(0, 255); |
|||
|
|||
int cy = (MaxSample - (y + CbBTable[cb])).Clamp(0, 255); |
|||
|
|||
byte r = (byte)((c * k) / MaxSample); |
|||
byte g = (byte)((m * k) / MaxSample); |
|||
byte b = (byte)((cy * k) / MaxSample); |
|||
|
|||
packed.PackFromRgba32(new Rgba32(r, g, b, MaxSample)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int Fix(float x) |
|||
{ |
|||
return (int)((x * (1L << ScaleBits)) + 0.5F); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int RightShift(int x) |
|||
{ |
|||
return x >> ScaleBits; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:33e3546a64df7fa1d528441926421b193e399a83490a6307762fb7eee9640bf0 |
|||
size 611572 |
|||
Loading…
Reference in new issue