mirror of https://github.com/SixLabors/ImageSharp
8 changed files with 468 additions and 2 deletions
@ -0,0 +1,54 @@ |
|||
// <copyright file="BlackIsZero1TiffColor.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Tiff |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using ImageSharp; |
|||
using ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Implements the 'BlackIsZero' photometric interpretation (optimised for bilevel images).
|
|||
/// </summary>
|
|||
internal static class BlackIsZero1TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="data">The buffer to read image data from.</param>
|
|||
/// <param name="pixels">The image buffer to write pixels to.</param>
|
|||
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
|
|||
/// <param name="top">The y-coordinate of the top of the image block.</param>
|
|||
/// <param name="width">The width of the image block.</param>
|
|||
/// <param name="height">The height of the image block.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, PixelAccessor<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint offset = 0; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x += 8) |
|||
{ |
|||
byte b = data[offset++]; |
|||
int maxShift = Math.Min(left + width - x, 8); |
|||
|
|||
for (int shift = 0; shift < maxShift; shift++) |
|||
{ |
|||
int bit = (b >> (7 - shift)) & 1; |
|||
byte intensity = (bit == 1) ? (byte)255 : (byte)0; |
|||
color.PackFromBytes(intensity, intensity, intensity, 255); |
|||
pixels[x + shift, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
// <copyright file="BlackIsZero4TiffColor.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Tiff |
|||
{ |
|||
using System.Runtime.CompilerServices; |
|||
using ImageSharp; |
|||
using ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 4-bit grayscale images).
|
|||
/// </summary>
|
|||
internal static class BlackIsZero4TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="data">The buffer to read image data from.</param>
|
|||
/// <param name="pixels">The image buffer to write pixels to.</param>
|
|||
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
|
|||
/// <param name="top">The y-coordinate of the top of the image block.</param>
|
|||
/// <param name="width">The width of the image block.</param>
|
|||
/// <param name="height">The height of the image block.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, PixelAccessor<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint offset = 0; |
|||
bool isOddWidth = (width & 1) == 1; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width - 1; x += 2) |
|||
{ |
|||
byte byteData = data[offset++]; |
|||
|
|||
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); |
|||
color.PackFromBytes(intensity1, intensity1, intensity1, 255); |
|||
pixels[x, y] = color; |
|||
|
|||
byte intensity2 = (byte)((byteData & 0x0F) * 17); |
|||
color.PackFromBytes(intensity2, intensity2, intensity2, 255); |
|||
pixels[x + 1, y] = color; |
|||
} |
|||
|
|||
if (isOddWidth) |
|||
{ |
|||
byte byteData = data[offset++]; |
|||
|
|||
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17); |
|||
color.PackFromBytes(intensity1, intensity1, intensity1, 255); |
|||
pixels[left + width - 1, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
// <copyright file="BlackIsZero8TiffColor.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Tiff |
|||
{ |
|||
using System.Runtime.CompilerServices; |
|||
using ImageSharp; |
|||
using ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 8-bit grayscale images).
|
|||
/// </summary>
|
|||
internal static class BlackIsZero8TiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="data">The buffer to read image data from.</param>
|
|||
/// <param name="pixels">The image buffer to write pixels to.</param>
|
|||
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
|
|||
/// <param name="top">The y-coordinate of the top of the image block.</param>
|
|||
/// <param name="width">The width of the image block.</param>
|
|||
/// <param name="height">The height of the image block.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, PixelAccessor<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
uint offset = 0; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
byte intensity = data[offset++]; |
|||
color.PackFromBytes(intensity, intensity, intensity, 255); |
|||
pixels[x, y] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// <copyright file="BlackIsZeroTiffColor.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Formats.Tiff |
|||
{ |
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
using ImageSharp; |
|||
using ImageSharp.PixelFormats; |
|||
|
|||
/// <summary>
|
|||
/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths).
|
|||
/// </summary>
|
|||
internal static class BlackIsZeroTiffColor |
|||
{ |
|||
/// <summary>
|
|||
/// Decodes pixel data using the current photometric interpretation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="data">The buffer to read image data from.</param>
|
|||
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
|
|||
/// <param name="pixels">The image buffer to write pixels to.</param>
|
|||
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
|
|||
/// <param name="top">The y-coordinate of the top of the image block.</param>
|
|||
/// <param name="width">The width of the image block.</param>
|
|||
/// <param name="height">The height of the image block.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, PixelAccessor<TPixel> pixels, int left, int top, int width, int height) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default(TPixel); |
|||
|
|||
BitReader bitReader = new BitReader(data); |
|||
float factor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f; |
|||
|
|||
for (int y = top; y < top + height; y++) |
|||
{ |
|||
for (int x = left; x < left + width; x++) |
|||
{ |
|||
int value = bitReader.ReadBits(bitsPerSample[0]); |
|||
float intensity = ((float)value) / factor; |
|||
color.PackFromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); |
|||
pixels[x, y] = color; |
|||
} |
|||
|
|||
bitReader.NextRow(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
// <copyright file="BlackIsZeroTiffColorTests.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Tests |
|||
{ |
|||
using System.Collections.Generic; |
|||
using Xunit; |
|||
|
|||
using ImageSharp.Formats.Tiff; |
|||
|
|||
public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase |
|||
{ |
|||
private static Rgba32 Gray000 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Gray128 = new Rgba32(128, 128, 128, 255); |
|||
private static Rgba32 Gray255 = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Gray0 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Gray8 = new Rgba32(136, 136, 136, 255); |
|||
private static Rgba32 GrayF = new Rgba32(255, 255, 255, 255); |
|||
private static Rgba32 Bit0 = new Rgba32(0, 0, 0, 255); |
|||
private static Rgba32 Bit1 = new Rgba32(255, 255, 255, 255); |
|||
|
|||
private static byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000, |
|||
0b11110000, |
|||
0b01110000, |
|||
0b10010000 }; |
|||
|
|||
private static Rgba32[][] Bilevel_Result4x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1 }, |
|||
new[] { Bit1, Bit1, Bit1, Bit1 }, |
|||
new[] { Bit0, Bit1, Bit1, Bit1 }, |
|||
new[] { Bit1, Bit0, Bit0, Bit1 }}; |
|||
|
|||
private static byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000, |
|||
0b11111111, 0b11111111, |
|||
0b01101001, 0b10100000, |
|||
0b10010000, 0b01100000}; |
|||
|
|||
private static Rgba32[][] Bilevel_Result12x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 }, |
|||
new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 }, |
|||
new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 }, |
|||
new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 }}; |
|||
|
|||
private static byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F, |
|||
0xFF, 0xFF, |
|||
0x08, 0x8F, |
|||
0xF0, 0xF8 }; |
|||
|
|||
private static Rgba32[][] Grayscale4_Result4x4 = new[] { new[] { Gray8, GrayF, Gray0, GrayF }, |
|||
new[] { GrayF, GrayF, GrayF, GrayF }, |
|||
new[] { Gray0, Gray8, Gray8, GrayF }, |
|||
new[] { GrayF, Gray0, GrayF, Gray8 }}; |
|||
|
|||
private static byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00, |
|||
0xFF, 0xF0, |
|||
0x08, 0x80, |
|||
0xF0, 0xF0 }; |
|||
|
|||
private static Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 }, |
|||
new[] { GrayF, GrayF, GrayF }, |
|||
new[] { Gray0, Gray8, Gray8 }, |
|||
new[] { GrayF, Gray0, GrayF }}; |
|||
|
|||
private static byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255, |
|||
255, 255, 255, 255, |
|||
000, 128, 128, 255, |
|||
255, 000, 255, 128 }; |
|||
|
|||
private static Rgba32[][] Grayscale8_Result4x4 = new[] { new[] { Gray128, Gray255, Gray000, Gray255 }, |
|||
new[] { Gray255, Gray255, Gray255, Gray255 }, |
|||
new[] { Gray000, Gray128, Gray128, Gray255 }, |
|||
new[] { Gray255, Gray000, Gray255, Gray128 }}; |
|||
|
|||
public static IEnumerable<object[]> Bilevel_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Bilevel_Result4x4 }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Offset(Bilevel_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 0, 4, 4, Offset(Bilevel_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 1, 4, 4, Offset(Bilevel_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 1, 4, 4, Offset(Bilevel_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Bilevel_Result12x4 }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Offset(Bilevel_Result12x4, 0, 0, 18, 6) }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 0, 12, 4, Offset(Bilevel_Result12x4, 1, 0, 18, 6) }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 1, 12, 4, Offset(Bilevel_Result12x4, 0, 1, 18, 6) }; |
|||
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 1, 12, 4, Offset(Bilevel_Result12x4, 1, 1, 18, 6) }; |
|||
} |
|||
} |
|||
|
|||
public static IEnumerable<object[]> Grayscale4_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Grayscale4_Result4x4 }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Offset(Grayscale4_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 0, 4, 4, Offset(Grayscale4_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 1, 4, 4, Offset(Grayscale4_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 1, 4, 4, Offset(Grayscale4_Result4x4, 1, 1, 6, 6) }; |
|||
|
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Grayscale4_Result3x4 }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Offset(Grayscale4_Result3x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 0, 3, 4, Offset(Grayscale4_Result3x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 1, 3, 4, Offset(Grayscale4_Result3x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 1, 3, 4, Offset(Grayscale4_Result3x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
public static IEnumerable<object[]> Grayscale8_Data |
|||
{ |
|||
get |
|||
{ |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Grayscale8_Result4x4 }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Offset(Grayscale8_Result4x4, 0, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 0, 4, 4, Offset(Grayscale8_Result4x4, 1, 0, 6, 6) }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 1, 4, 4, Offset(Grayscale8_Result4x4, 0, 1, 6, 6) }; |
|||
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 1, 4, 4, Offset(Grayscale8_Result4x4, 1, 1, 6, 6) }; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Bilevel_Data))] |
|||
[MemberData(nameof(Grayscale4_Data))] |
|||
[MemberData(nameof(Grayscale8_Data))] |
|||
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
BlackIsZeroTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Bilevel_Data))] |
|||
public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
BlackIsZero1TiffColor.Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Grayscale4_Data))] |
|||
public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
BlackIsZero4TiffColor.Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Grayscale8_Data))] |
|||
public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) |
|||
{ |
|||
AssertDecode(expectedResult, pixels => |
|||
{ |
|||
BlackIsZero8TiffColor.Decode(inputData, pixels, left, top, width, height); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue