mirror of https://github.com/SixLabors/ImageSharp
9 changed files with 341 additions and 11 deletions
@ -0,0 +1,26 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
// <auto-generated />
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Common |
||||
|
{ |
||||
|
internal unsafe partial struct GenericBlock8x8<T> |
||||
|
{ |
||||
|
#pragma warning disable 169
|
||||
|
|
||||
|
// It's not allowed use fix-sized buffers with generics, need to place all the fields manually:
|
||||
|
private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7; |
||||
|
private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7; |
||||
|
private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7; |
||||
|
private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7; |
||||
|
private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7; |
||||
|
private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7; |
||||
|
private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7; |
||||
|
private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7; |
||||
|
|
||||
|
#pragma warning restore 169
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
<# |
||||
|
// Copyright (c) Six Labors and contributors. |
||||
|
// Licensed under the Apache License, Version 2.0. |
||||
|
#> |
||||
|
<#@ template debug="false" hostspecific="false" language="C#" #> |
||||
|
<#@ assembly name="System.Core" #> |
||||
|
<#@ import namespace="System.Linq" #> |
||||
|
<#@ import namespace="System.Text" #> |
||||
|
<#@ import namespace="System.Collections.Generic" #> |
||||
|
<#@ output extension=".cs" #> |
||||
|
// Copyright (c) Six Labors and contributors. |
||||
|
// Licensed under the Apache License, Version 2.0. |
||||
|
|
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
// <auto-generated /> |
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Common |
||||
|
{ |
||||
|
internal unsafe partial struct GenericBlock8x8<T> |
||||
|
{ |
||||
|
#pragma warning disable 169 |
||||
|
|
||||
|
// It's not allowed use fix-sized buffers with generics, need to place all the fields manually: |
||||
|
<# |
||||
|
PushIndent(" "); |
||||
|
Write(" "); |
||||
|
for (int y = 0; y < 8; y++) |
||||
|
{ |
||||
|
Write("private T "); |
||||
|
for (int x = 0; x < 8; x++) |
||||
|
{ |
||||
|
Write($"_y{y}_x{x}"); |
||||
|
if (x < 7) Write(", "); |
||||
|
} |
||||
|
WriteLine(";"); |
||||
|
} |
||||
|
PopIndent(); |
||||
|
#> |
||||
|
|
||||
|
#pragma warning restore 169 |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,128 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Jpeg.Common |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data.
|
||||
|
/// </summary>
|
||||
|
// ReSharper disable once InconsistentNaming
|
||||
|
internal unsafe partial struct GenericBlock8x8<T> |
||||
|
where T : struct |
||||
|
{ |
||||
|
public const int Size = 64; |
||||
|
|
||||
|
public const int SizeInBytes = Size * 3; |
||||
|
|
||||
|
public void LoadAndStretchEdges<TPixel>(IPixelSource<TPixel> source, int sourceX, int sourceY) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
var buffer = source.PixelBuffer as Buffer2D<T>; |
||||
|
if (buffer == null) |
||||
|
{ |
||||
|
throw new InvalidOperationException("LoadAndStretchEdges<TPixels>() is only valid for TPixel == T !"); |
||||
|
} |
||||
|
|
||||
|
this.LoadAndStretchEdges(buffer, sourceX, sourceY); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Load a 8x8 region of an image into the block.
|
||||
|
/// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image.
|
||||
|
/// </summary>
|
||||
|
public void LoadAndStretchEdges(Buffer2D<T> source, int sourceX, int sourceY) |
||||
|
{ |
||||
|
int width = Math.Min(8, source.Width - sourceX); |
||||
|
int height = Math.Min(8, source.Height - sourceY); |
||||
|
|
||||
|
if (width <= 0 || height <= 0) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint byteWidth = (uint)width * (uint)Unsafe.SizeOf<T>(); |
||||
|
int remainderXCount = 8 - width; |
||||
|
|
||||
|
ref byte blockStart = ref Unsafe.As<GenericBlock8x8<T>, byte>(ref this); |
||||
|
ref byte imageStart = ref Unsafe.As<T, byte>( |
||||
|
ref Unsafe.Add(ref source.GetRowSpan(sourceY).DangerousGetPinnableReference(), sourceX) |
||||
|
); |
||||
|
|
||||
|
int blockRowSizeInBytes = 8 * Unsafe.SizeOf<T>(); |
||||
|
int imageRowSizeInBytes = source.Width * Unsafe.SizeOf<T>(); |
||||
|
|
||||
|
for (int y = 0; y < height; y++) |
||||
|
{ |
||||
|
ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes); |
||||
|
ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); |
||||
|
|
||||
|
Unsafe.CopyBlock(ref d, ref s, byteWidth); |
||||
|
|
||||
|
ref T last = ref Unsafe.Add(ref Unsafe.As<byte, T>(ref d), width - 1); |
||||
|
|
||||
|
for (int x = 1; x <= remainderXCount; x++) |
||||
|
{ |
||||
|
Unsafe.Add(ref last, x) = last; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int remainderYCount = 8 - height; |
||||
|
|
||||
|
if (remainderYCount == 0) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes); |
||||
|
|
||||
|
for (int y = 1; y <= remainderYCount; y++) |
||||
|
{ |
||||
|
ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y); |
||||
|
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ONLY FOR GenericBlock instances living on the stack!
|
||||
|
/// </summary>
|
||||
|
public Span<T> AsSpanUnsafe() => new Span<T>(Unsafe.AsPointer(ref this), Size); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// FOR TESTING ONLY!
|
||||
|
/// Gets or sets a <see cref="Rgb24"/> value at the given index
|
||||
|
/// </summary>
|
||||
|
/// <param name="idx">The index</param>
|
||||
|
/// <returns>The value</returns>
|
||||
|
public T this[int idx] |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this); |
||||
|
return Unsafe.Add(ref selfRef, idx); |
||||
|
} |
||||
|
|
||||
|
set |
||||
|
{ |
||||
|
ref T selfRef = ref Unsafe.As<GenericBlock8x8<T>, T>(ref this); |
||||
|
Unsafe.Add(ref selfRef, idx) = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// FOR TESTING ONLY!
|
||||
|
/// Gets or sets a value in a row+coulumn of the 8x8 block
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The x position index in the row</param>
|
||||
|
/// <param name="y">The column index</param>
|
||||
|
/// <returns>The value</returns>
|
||||
|
public T this[int x, int y] |
||||
|
{ |
||||
|
get => this[(y * 8) + x]; |
||||
|
set => this[(y * 8) + x] = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,126 @@ |
|||||
|
// ReSharper disable InconsistentNaming
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jpeg.Common; |
||||
|
using SixLabors.ImageSharp.Helpers; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
using Xunit; |
||||
|
|
||||
|
public class GenericBlock8x8Tests |
||||
|
{ |
||||
|
public static Image<TPixel> CreateTestImage<TPixel>() |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
var image = new Image<TPixel>(10, 10); |
||||
|
using (PixelAccessor<TPixel> pixels = image.Lock()) |
||||
|
{ |
||||
|
for (int i = 0; i < 10; i++) |
||||
|
{ |
||||
|
for (int j = 0; j < 10; j++) |
||||
|
{ |
||||
|
var rgba = new Rgba32((byte)(i+1), (byte)(j+1), (byte)200, (byte)255); |
||||
|
var color = default(TPixel); |
||||
|
color.PackFromRgba32(rgba); |
||||
|
|
||||
|
pixels[i, j] = color; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return image; |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] |
||||
|
public void LoadAndStretchCorners_FromOrigo<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> s = provider.GetImage()) |
||||
|
{ |
||||
|
var d = default(GenericBlock8x8<TPixel>); |
||||
|
d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0); |
||||
|
|
||||
|
TPixel a = s.Frames.RootFrame[0, 0]; |
||||
|
TPixel b = d[0, 0]; |
||||
|
|
||||
|
Assert.Equal(s[0, 0], d[0, 0]); |
||||
|
Assert.Equal(s[1, 0], d[1, 0]); |
||||
|
Assert.Equal(s[7, 0], d[7, 0]); |
||||
|
Assert.Equal(s[0, 1], d[0, 1]); |
||||
|
Assert.Equal(s[1, 1], d[1, 1]); |
||||
|
Assert.Equal(s[7, 0], d[7, 0]); |
||||
|
Assert.Equal(s[0, 7], d[0, 7]); |
||||
|
Assert.Equal(s[7, 7], d[7, 7]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] |
||||
|
public unsafe void LoadAndStretchCorners_WithOffset<TPixel>(TestImageProvider<TPixel> provider) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (Image<TPixel> s = provider.GetImage()) |
||||
|
{ |
||||
|
var d = default(GenericBlock8x8<TPixel>); |
||||
|
d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7); |
||||
|
|
||||
|
Assert.Equal(s[6, 7], d[0, 0]); |
||||
|
Assert.Equal(s[6, 8], d[0, 1]); |
||||
|
Assert.Equal(s[7, 8], d[1, 1]); |
||||
|
|
||||
|
Assert.Equal(s[6, 9], d[0, 2]); |
||||
|
Assert.Equal(s[6, 9], d[0, 3]); |
||||
|
Assert.Equal(s[6, 9], d[0, 7]); |
||||
|
|
||||
|
Assert.Equal(s[7, 9], d[1, 2]); |
||||
|
Assert.Equal(s[7, 9], d[1, 3]); |
||||
|
Assert.Equal(s[7, 9], d[1, 7]); |
||||
|
|
||||
|
Assert.Equal(s[9, 9], d[3, 2]); |
||||
|
Assert.Equal(s[9, 9], d[3, 3]); |
||||
|
Assert.Equal(s[9, 9], d[3, 7]); |
||||
|
|
||||
|
Assert.Equal(s[9, 7], d[3, 0]); |
||||
|
Assert.Equal(s[9, 7], d[4, 0]); |
||||
|
Assert.Equal(s[9, 7], d[7, 0]); |
||||
|
|
||||
|
Assert.Equal(s[9, 9], d[3, 2]); |
||||
|
Assert.Equal(s[9, 9], d[4, 2]); |
||||
|
Assert.Equal(s[9, 9], d[7, 2]); |
||||
|
|
||||
|
Assert.Equal(s[9, 9], d[4, 3]); |
||||
|
Assert.Equal(s[9, 9], d[7, 7]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Indexer() |
||||
|
{ |
||||
|
var block = default(GenericBlock8x8<Rgb24>); |
||||
|
Span<Rgb24> span = block.AsSpanUnsafe(); |
||||
|
Assert.Equal(64, span.Length); |
||||
|
|
||||
|
for (int i = 0; i < 64; i++) |
||||
|
{ |
||||
|
span[i] = new Rgb24((byte)i, (byte)(2 * i), (byte)(3 * i)); |
||||
|
} |
||||
|
|
||||
|
Rgb24 expected00 = new Rgb24(0, 0, 0); |
||||
|
Rgb24 expected07 = new Rgb24(7, 14, 21); |
||||
|
Rgb24 expected11 = new Rgb24(9, 18, 27); |
||||
|
Rgb24 expected77 = new Rgb24(63, 126, 189); |
||||
|
Rgb24 expected67 = new Rgb24(62, 124, 186); |
||||
|
|
||||
|
Assert.Equal(expected00, block[0, 0]); |
||||
|
Assert.Equal(expected07, block[7, 0]); |
||||
|
Assert.Equal(expected11, block[1, 1]); |
||||
|
Assert.Equal(expected67, block[6, 7]); |
||||
|
Assert.Equal(expected77, block[7, 7]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue