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