Skip to content

Commit

Permalink
Rework PixImageMipMap
Browse files Browse the repository at this point in the history
  • Loading branch information
hyazinthh committed Jun 25, 2024
1 parent 61c13a7 commit a299ee6
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 139 deletions.
6 changes: 4 additions & 2 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
- Renamed CIeLuvf to CieLuvf
- Removed IPix and related types:
- `PixCubeMap` and `PixCube`: Use `PixImageCube` instead.
- `PixMipMap2d`: Use `PixImageMipmap` instead.
- `PixMipMap2d`: Use `PixImageMipMap` instead.
- Reworked `PixImageMipMap`:
- Removed generic `PixImageMipMap<T>`
- Removed `MipMapOptions`, static `Create()` method takes multiple optional parameters instead.
- Cleanup of `PixImage` and `PixVolume`:
- Deprecated `Array` in favor of `Data`
- Deprecated `PixImage.IntStride` in favor of `Stride` and `StrideL`
- `PixVolume` implements `IPixImage3d`
- Added `PixVolume.BytesPerChannel`
- Added `PixVolume.ToCanonicalDenseLayout`
- Added `Add PixImage<T>.Transformed` (abstract `PixImage.Transformed` is renamed to `PixImage.TransformedPixImage`)
Expand Down
199 changes: 71 additions & 128 deletions src/Aardvark.Base.Tensors.CSharp/PixImage/PixImageMipMap.cs
Original file line number Diff line number Diff line change
@@ -1,81 +1,79 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Aardvark.Base
{
public class PixImageMipMap
{
public PixImage[] ImageArray;

#region MipMapOptions

public class MipMapOptions
{
public int Count;
public bool PowerOfTwo;
public ImageInterpolation Interpolation;

public static MipMapOptions Default = new MipMapOptions()
{
Count = 0,
Interpolation = ImageInterpolation.Cubic,
PowerOfTwo = false,
};
}

#endregion
/// <summary>
/// Array of images representing the mipmap levels.
/// </summary>
public PixImage[] Images;

#region Constructors

public PixImageMipMap() { }
/// <summary>
/// Creates an empty mipmap.
/// </summary>
public PixImageMipMap() => Images = Array.Empty<PixImage>();

public PixImageMipMap(params PixImage[] images) { ImageArray = images; }
/// <summary>
/// Creates a mipmap from the given images.
/// </summary>
/// <param name="images">An array of images representing the mipmap levels.</param>
public PixImageMipMap(params PixImage[] images) => Images = images;

/// <summary>
/// Creates a mipmap consisting of a single image.
/// </summary>
/// <param name="image">The single image of the mipmap.</param>
public PixImageMipMap(PixImage image) => Images = new[] { image };

#endregion

#region Properties

public int ImageCount { get { return ImageArray.Length; } }

public IEnumerable<PixImage> Images { get { return ImageArray; } }
/// <summary>
/// Number of images in the mipmap array.
/// </summary>
public int Count => Images?.Length ?? 0;

#endregion
/// <summary>
/// Returns the base image of the mipmap array or null if it is empty.
/// </summary>
public PixImage BaseImage => Count > 0 ? Images[0] : null;

#region Static Creators
/// <summary>
/// Returns the size of the base image, or V2i.Zero if the mipmap array is empty.
/// </summary>
public V2i BaseSize => BaseImage?.Size ?? V2i.Zero;

// Helper class to create PixImageMipMap from untyped PixImage
private static class Dispatch
/// <summary>
/// Gets or sets an image of the mipmap array.
/// </summary>
public PixImage this[int index]
{
private static class CreateDispatcher
{
public static PixImageMipMap<T> Create<T>(PixImage<T> image, MipMapOptions options)
=> new PixImageMipMap<T>(image, options);
}
get => Images[index];
set => Images[index] = value;
}

private const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
private static readonly MethodInfo s_createMethod = typeof(CreateDispatcher).GetMethod("Create", flags);
#region Obsolete

public static PixImageMipMap Create(PixImage image, MipMapOptions options)
{
var mi = s_createMethod.MakeGenericMethod(image.PixFormat.Type);
return (PixImageMipMap)mi.Invoke(null, new object[] { image, options });
}
}
[Obsolete("Use Count instead.")]
public int LevelCount => Count;

public static IEnumerable<PixImageMipMap> Create(IEnumerable<PixImage> pixies, MipMapOptions options)
=> pixies.Select(pix => Dispatch.Create(pix, options));
[Obsolete("Use Count instead.")]
public int ImageCount => Count;

public static IEnumerable<PixImageMipMap> Create(IEnumerable<PixImage> pixies)
=> Create(pixies, MipMapOptions.Default);
[Obsolete("Use Images instead.")]
public PixImage[] ImageArray => Images;

public static PixImageMipMap Create(PixImage pix)
=> Dispatch.Create(pix, MipMapOptions.Default);
[Obsolete("Use BaseSize instead.")]
public V2i BaseImageSize => BaseSize;

public static PixImageMipMap Create(PixImage pix, MipMapOptions options)
=> Dispatch.Create(pix, options);
#endregion

#endregion

#region Load from file

Expand Down Expand Up @@ -150,95 +148,40 @@ public static PixImageMipMap Load(Stream stream, IPixLoader loader = null)

#endregion

#endregion

#region IPixMipMap2d

public PixFormat PixFormat
{
get { if (ImageArray.IsEmptyOrNull()) new ArgumentException("PixMipMap is empty"); return ImageArray[0].PixFormat; }
}

public int LevelCount
{
get { return ImageArray.Length; }
}

public PixImage this[int level]
{
get { return ImageArray[level]; }
}

#endregion
}

public class PixImageMipMap<T> : PixImageMipMap
{
#region Properties

new public IEnumerable<PixImage<T>> Images { get { return (PixImage<T>[])ImageArray; } }
public IEnumerable<V2i> ImageSizes { get { return from image in ImageArray select image.Size; } }
public V2i BaseImageSize { get { return ImageArray[0].Size; } }

#endregion

#region Constructors

public PixImageMipMap(PixImage<T> singleImage)
{
ImageArray = new PixImage<T>[] { singleImage };
BuildMipMaps(MipMapOptions.Default);
}

public PixImageMipMap(PixImage<T> singleImage, MipMapOptions options)
{
ImageArray = new PixImage<T>[] { singleImage };
BuildMipMaps(options);
}

#endregion

#region Methods
#region Generation

/// <summary>
/// Builds mipmap pyramid using supplied MipMapOptions.
/// Throws an exception if mips are already present (this function ensures correct MipMapOptions)
/// Builds a mipmap for the given image.
/// </summary>
private void BuildMipMaps(MipMapOptions options)
/// <param name="image">The base image of the mipmap.</param>
/// <param name="maxCount">The maximum number of levels to generate. Ignored if non-positive.</param>
/// <param name="interpolation">The used interpolation method.</param>
/// <param name="powerOfTwo">If true, the base image is resized to have power-of-two dimensions.</param>
/// <returns></returns>
public static PixImageMipMap Create(PixImage image, ImageInterpolation interpolation = ImageInterpolation.Cubic, int maxCount = 0, bool powerOfTwo = false)
{
if (ImageArray == null || ImageArray.Length == 0)
throw new ArgumentException("No valid PixImage available.");

if (ImageArray.Length > 1)
throw new ArgumentException("PixImageMipMap already contains image pyramid");

var size = ImageArray[0].Size;
var baseSize = image.Size;
var baseImage = image;

if (options.PowerOfTwo && (!Fun.IsPowerOfTwo(size.X) || !Fun.IsPowerOfTwo(size.Y)))
if (powerOfTwo && (!Fun.IsPowerOfTwo(baseSize.X) || !Fun.IsPowerOfTwo(baseSize.Y)))
{
var powerOfTwoSize = new V2i(size.X.NextPowerOfTwo(), size.Y.NextPowerOfTwo());
ImageArray[0] = ((PixImage<T>)ImageArray[0]).Resized(powerOfTwoSize);
Report.Line(2, "Resizing PixImage to size of power of two");
size = powerOfTwoSize;
//throw new ArgumentException("Can only build mip maps for PixImages with size of power of two");
baseSize = Fun.NextPowerOfTwo(baseSize);
baseImage = image.ResizedPixImage(baseSize, interpolation);
}

var count = options.Count == 0
? Fun.MipmapLevels(size)
: options.Count;
var count = Fun.MipmapLevels(baseSize);
if (maxCount > 0 && count > maxCount) count = maxCount;

var mipMaps = new PixImage<T>[count];
mipMaps[0] = (PixImage<T>)ImageArray[0];
var images = new PixImage[count];
images[0] = baseImage;

for (int i = 1; i < count; i++)
for (var i = 1; i < count; i++)
{
size = Fun.MipmapLevelSize(size, 1);
mipMaps[i] = mipMaps[i - 1].Resized(size, options.Interpolation);
var size = Fun.MipmapLevelSize(baseSize, i);
images[i] = images[i - 1].ResizedPixImage(size, interpolation);
}

ImageArray = mipMaps;
//ImagesContainer.FromPixImageArray(m_images);
//InfoOfSet = new Info(Kind.MipMapPyramid, m_images.Length, m_images.Select(x => x.Size).ToArray());
return new PixImageMipMap(images);
}

#endregion
Expand Down
2 changes: 1 addition & 1 deletion src/Aardvark.Base.Tensors/PixImage/PixImageCube.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module FSharpPixImageCubeExtensions =
|> Array.mapi (fun i mipMap ->
let side = unbox<CubeSide> i
let (newSide, trafo) = m side
newSide, PixImageMipMap (mipMap.ImageArray |> Array.map (fun pi -> pi.TransformedPixImage(trafo)))
newSide, PixImageMipMap (mipMap.Images |> Array.map (fun pi -> pi.TransformedPixImage(trafo)))
)
|> Map.ofArray
|> Map.toArray
Expand Down
16 changes: 8 additions & 8 deletions src/Tests/Aardvark.Base.Tests/Images/PixImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,55 +18,55 @@ public void MipMapCreate1x1()
{
var pix = new PixImage<byte>(1, 1, 4);
var mip = PixImageMipMap.Create(pix);
Assert.IsTrue(mip.LevelCount == 1);
Assert.IsTrue(mip.Count == 1);
}

[Test]
public void MipMapCreate2x2()
{
var pix = new PixImage<byte>(2, 2, 4);
var mip = PixImageMipMap.Create(pix);
Assert.IsTrue(mip.LevelCount == 2);
Assert.IsTrue(mip.Count == 2);
}

[Test]
public void MipMapCreate3x3()
{
var pix = new PixImage<byte>(3, 3, 4);
var mip = PixImageMipMap.Create(pix);
Assert.IsTrue(mip.LevelCount == 2);
Assert.IsTrue(mip.Count == 2);
}

[Test]
public void MipMapCreate256()
{
var pix = new PixImage<byte>(256, 256, 4);
var mip = PixImageMipMap.Create(pix);
Assert.IsTrue(mip.LevelCount == 9);
Assert.IsTrue(mip.Count == 9);
}

[Test]
public void MipMapCreate255()
{
var pix = new PixImage<byte>(255, 255, 4);
var mip = PixImageMipMap.Create(pix);
Assert.IsTrue(mip.LevelCount == 8);
Assert.IsTrue(mip.Count == 8);
}

[Test]
public void MipMapCreate257()
{
var pix = new PixImage<byte>(257, 257, 4);
var mip = PixImageMipMap.Create(pix);
Assert.IsTrue(mip.LevelCount == 9);
Assert.IsTrue(mip.Count == 9);
}

[Test]
public void MipMapCreate57x43()
{
var pix = new PixImage<byte>(57, 43, 4);
var mip = PixImageMipMap.Create(pix);
Assert.IsTrue(mip.LevelCount == 6);
Assert.IsTrue(mip.Count == 6);
//level 0: 57x43
//level 1: 28x21
//level 2: 14x10
Expand All @@ -80,7 +80,7 @@ public void MipMapCreate57x11()
{
var pix = new PixImage<byte>(57, 11, 4);
var mip = PixImageMipMap.Create(pix);
Assert.IsTrue(mip.LevelCount == 6);
Assert.IsTrue(mip.Count == 6);
//level 0: 57x11
//level 1: 28x5
//level 2: 14x2
Expand Down

0 comments on commit a299ee6

Please sign in to comment.