Skip to content

Commit

Permalink
It is now possible to choose the edge behavior for tile reflection (#…
Browse files Browse the repository at this point in the history
…1141)

* Tile effect now using Cairo extensions to obtain sample

* Added other kinds of edges to `TileEffect`

* Moved `EdgeBehavior` `enum` out of `Warp`

* Added tests for different edge behaviors

* Moved extension to CairoExtensions

* No bilinear sampling for positions inside surface
  • Loading branch information
Lehonti authored Nov 21, 2024
1 parent 7fbfc97 commit e4f1c1e
Show file tree
Hide file tree
Showing 17 changed files with 173 additions and 71 deletions.
43 changes: 43 additions & 0 deletions Pinta.Core/Extensions/CairoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,49 @@ public static ColorBgra GetBilinearSampleWrapped (
}
}

public static ColorBgra GetBilinearSampleReflected (
this ImageSurface src,
ReadOnlySpan<ColorBgra> src_data,
int srcWidth,
int srcHeight,
float x,
float y
) => src.GetBilinearSampleClamped (
src_data,
srcWidth,
srcHeight,
ReflectCoord (x, srcWidth),
ReflectCoord (y, srcHeight));

public static ColorBgra GetBilinearSampleReflected (
this ImageSurface src,
float x,
float y
) => GetBilinearSampleClamped (
src,
ReflectCoord (x, src.Width),
ReflectCoord (y, src.Height));

private static float ReflectCoord (float value, int max)
{
bool reflection = false;

while (value < 0) {
value += max;
reflection = !reflection;
}

while (value > max) {
value -= max;
reflection = !reflection;
}

if (reflection)
value = max - value;

return value;
}

public static void TranslatePointsInPlace (this Span<PointI> points, PointI delta)
{
for (int i = 0; i < points.Length; ++i)
Expand Down
48 changes: 2 additions & 46 deletions Pinta.Effects/Algorithms/Warp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
using System.Collections.Generic;
using System.Drawing;
using Cairo;
using GLib.Internal;
using Pinta.Core;
using Pinta.Gui.Widgets;

namespace Pinta.Effects;

Expand All @@ -25,30 +25,6 @@ public interface IEffectData
EdgeBehavior EdgeBehavior { get; }
}

public enum EdgeBehavior
{
[Caption ("Clamp")]
Clamp,

[Caption ("Wrap")]
Wrap,

[Caption ("Reflect")]
Reflect,

[Caption ("Primary")]
Primary,

[Caption ("Secondary")]
Secondary,

[Caption ("Transparent")]
Transparent,

[Caption ("Original")]
Original,
}

public readonly record struct TransformData (double X, double Y);

public delegate TransformData TransformInverter (
Expand All @@ -67,26 +43,6 @@ public sealed record Settings (
private static bool IsOnSurface (this ImageSurface src, float u, float v)
=> (u >= 0 && u <= (src.Width - 1) && v >= 0 && v <= (src.Height - 1));

private static float ReflectCoord (float value, int max)
{
bool reflection = false;

while (value < 0) {
value += max;
reflection = !reflection;
}

while (value > max) {
value -= max;
reflection = !reflection;
}

if (reflection)
value = max - value;

return value;
}

public static Settings CreateSettings (
IEffectData warpData,
RectangleI selectionBounds,
Expand Down Expand Up @@ -155,7 +111,7 @@ private static ColorBgra GetSample (
return settings.edgeBehavior switch {
EdgeBehavior.Clamp => src.GetBilinearSampleClamped (preliminarySample.X, preliminarySample.Y),
EdgeBehavior.Wrap => src.GetBilinearSampleWrapped (preliminarySample.X, preliminarySample.Y),
EdgeBehavior.Reflect => src.GetBilinearSampleClamped (ReflectCoord (preliminarySample.X, src.Width), ReflectCoord (preliminarySample.Y, src.Height)),
EdgeBehavior.Reflect => src.GetBilinearSampleReflected (preliminarySample.X, preliminarySample.Y),
EdgeBehavior.Primary => settings.primaryColor,
EdgeBehavior.Secondary => settings.secondaryColor,
EdgeBehavior.Transparent => ColorBgra.Transparent,
Expand Down
27 changes: 27 additions & 0 deletions Pinta.Effects/Classes/EdgeBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Pinta.Gui.Widgets;

namespace Pinta.Effects;

public enum EdgeBehavior
{
[Caption ("Clamp")]
Clamp,

[Caption ("Wrap")]
Wrap,

[Caption ("Reflect")]
Reflect,

[Caption ("Primary")]
Primary,

[Caption ("Secondary")]
Secondary,

[Caption ("Transparent")]
Transparent,

[Caption ("Original")]
Original,
}
2 changes: 1 addition & 1 deletion Pinta.Effects/Effects/DentsEffect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,5 @@ public sealed class DentsData : EffectData, Warp.IEffectData
[Caption ("Center Offset")]
public CenterOffset<double> CenterOffset { get; set; }

public Warp.EdgeBehavior EdgeBehavior { get; set; } = Warp.EdgeBehavior.Wrap;
public EdgeBehavior EdgeBehavior { get; set; } = EdgeBehavior.Wrap;
}
2 changes: 1 addition & 1 deletion Pinta.Effects/Effects/PolarInversionEffect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,5 @@ public sealed class PolarInversionData : EffectData, Warp.IEffectData
[Caption ("Center Offset")]
public CenterOffset<double> CenterOffset { get; set; }

public Warp.EdgeBehavior EdgeBehavior { get; set; } = Warp.EdgeBehavior.Reflect;
public EdgeBehavior EdgeBehavior { get; set; } = EdgeBehavior.Reflect;
}
72 changes: 50 additions & 22 deletions Pinta.Effects/Effects/TileEffect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ public TileData Data
=> (TileData) EffectData!;

private readonly IChromeService chrome;
private readonly IPaletteService palette;
public TileEffect (IServiceProvider services)
{
chrome = services.GetService<IChromeService> ();
palette = services.GetService<IPaletteService> ();
EffectData = new TileData ();
}

Expand All @@ -55,6 +57,7 @@ private sealed record TileSettings (
float cos,
float tileScale,
float adjustedIntensity,
EdgeBehavior edgeBehavior,
IReadOnlyList<PointD> antiAliasPoints,
Func<float, float> waveFunction);

Expand All @@ -76,6 +79,7 @@ private TileSettings CreateSettings (ImageSurface source)
cos: cos,
tileScale: (float) Math.PI / tileSize,
adjustedIntensity: preliminaryIntensity * preliminaryIntensity / 10 * Math.Sign (preliminaryIntensity),
edgeBehavior: Data.EdgeBehavior,
antiAliasPoints: InitializeAntiAliasPoints (ANTI_ALIAS_LEVEL, antiAliasSample, sin, cos),
waveFunction: GetWaveFunction (Data.WaveType)
);
Expand Down Expand Up @@ -130,14 +134,16 @@ protected override void Render (
}

// Algorithm Code Ported From PDN
private static ColorBgra GetFinalPixelColor (
private ColorBgra GetFinalPixelColor (
ImageSurface source,
TileSettings settings,
ReadOnlySpan<ColorBgra> sourceData,
PixelOffset pixel)
{
Span<ColorBgra> samples = stackalloc ColorBgra[settings.antiAliasPoints.Count];

ColorBgra original = sourceData[pixel.memoryOffset];

float i = pixel.coordinates.X - settings.halfWidth;
float j = pixel.coordinates.Y - settings.halfHeight;

Expand Down Expand Up @@ -166,34 +172,53 @@ private static ColorBgra GetFinalPixelColor (
float finalV = settings.sin * transformedS + settings.cos * transformedT;

// Translate back to image coordinates
int unwrappedSampleX = (int) (settings.halfWidth + finalU);
int unwrappedSampleY = (int) (settings.halfHeight + finalV);

// Ensure coordinates wrap around the image dimensions

int wrappedSampleX = (unwrappedSampleX + settings.size.Width) % settings.size.Width;
int wrappedSampleY = (unwrappedSampleY + settings.size.Height) % settings.size.Height;

int adjustedSampleX =
(wrappedSampleX < 0)
? (wrappedSampleX + settings.size.Width) % settings.size.Width
: wrappedSampleX;
int adjustedSampleY =
(wrappedSampleY < 0)
? (wrappedSampleY + settings.size.Height) % settings.size.Height
: wrappedSampleY;

PointI samplePosition = new (adjustedSampleX, adjustedSampleY);
float preliminaryX = settings.halfWidth + finalU;
float preliminaryY = settings.halfHeight + finalV;

samples[p] = source.GetColorBgra (
samples[p] = GetSample (
settings,
source,
sourceData,
settings.size.Width,
samplePosition);
original,
preliminaryX,
preliminaryY);
}

return ColorBgra.Blend (samples);
}

private ColorBgra GetSample (
TileSettings settings,
ImageSurface source,
ReadOnlySpan<ColorBgra> sourceData,
ColorBgra original,
float preliminaryX,
float preliminaryY)
{
if (IsOnSurface (settings, preliminaryX, preliminaryY)) {
int floorX = (int) preliminaryX;
int floorY = (int) preliminaryY;
int rowOffset = floorY * settings.size.Width;
int columnOffset = floorX;
int pixelOffset = rowOffset + columnOffset;
return sourceData[pixelOffset];
}

return settings.edgeBehavior switch {
EdgeBehavior.Clamp => source.GetBilinearSampleClamped (sourceData, settings.size.Width, settings.size.Height, preliminaryX, preliminaryY),
EdgeBehavior.Wrap => source.GetBilinearSampleWrapped (sourceData, settings.size.Width, settings.size.Height, preliminaryX, preliminaryY),
EdgeBehavior.Reflect => source.GetBilinearSampleReflected (sourceData, settings.size.Width, settings.size.Height, preliminaryX, preliminaryY),
EdgeBehavior.Primary => palette.PrimaryColor.ToColorBgra (),
EdgeBehavior.Secondary => palette.SecondaryColor.ToColorBgra (),
EdgeBehavior.Transparent => ColorBgra.Transparent,
EdgeBehavior.Original => original,
_ => throw new ArgumentException ($"{nameof (settings.edgeBehavior)} is out of range", nameof (settings)),
};
}

private static bool IsOnSurface (TileSettings settings, float u, float v)
=> (u >= 0 && u <= (settings.size.Width - 1) && v >= 0 && v <= (settings.size.Height - 1));

public sealed class TileData : EffectData
{
[Caption ("Rotation"), MinimumValue (-45), MaximumValue (45)]
Expand All @@ -207,6 +232,9 @@ public sealed class TileData : EffectData

[Caption ("Tile Type")]
public TileType WaveType { get; set; } = TileType.SharpEdges;

[Caption ("Edge Behavior")]
public EdgeBehavior EdgeBehavior { get; set; } = EdgeBehavior.Wrap;
}
}

Expand Down
Binary file modified tests/Pinta.Effects.Tests/Assets/tile1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/Pinta.Effects.Tests/Assets/tile10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/Pinta.Effects.Tests/Assets/tile2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/Pinta.Effects.Tests/Assets/tile3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/Pinta.Effects.Tests/Assets/tile4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/Pinta.Effects.Tests/Assets/tile5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/Pinta.Effects.Tests/Assets/tile6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/Pinta.Effects.Tests/Assets/tile7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/Pinta.Effects.Tests/Assets/tile8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/Pinta.Effects.Tests/Assets/tile9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 49 additions & 1 deletion tests/Pinta.Effects.Tests/EffectsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public void Dents7 ()
{
DentsEffect effect = new (Utilities.CreateMockServices ());
effect.Data.CenterOffset = new CenterOffset<double> (0, 0);
effect.Data.EdgeBehavior = Warp.EdgeBehavior.Clamp;
effect.Data.EdgeBehavior = EdgeBehavior.Clamp;
Utilities.TestEffect (effect, "dents7.png");
}

Expand Down Expand Up @@ -512,6 +512,54 @@ public void Tile4 ()
Utilities.TestEffect (effect, "tile4.png");
}

[Test]
public void Tile5 ()
{
TileEffect effect = new (Utilities.CreateMockServices ());
effect.Data.EdgeBehavior = EdgeBehavior.Clamp;
Utilities.TestEffect (effect, "tile5.png");
}

[Test]
public void Tile6 ()
{
TileEffect effect = new (Utilities.CreateMockServices ());
effect.Data.EdgeBehavior = EdgeBehavior.Reflect;
Utilities.TestEffect (effect, "tile6.png");
}

[Test]
public void Tile7 ()
{
TileEffect effect = new (Utilities.CreateMockServices ());
effect.Data.EdgeBehavior = EdgeBehavior.Primary;
Utilities.TestEffect (effect, "tile7.png");
}

[Test]
public void Tile8 ()
{
TileEffect effect = new (Utilities.CreateMockServices ());
effect.Data.EdgeBehavior = EdgeBehavior.Secondary;
Utilities.TestEffect (effect, "tile8.png");
}

[Test]
public void Tile9 ()
{
TileEffect effect = new (Utilities.CreateMockServices ());
effect.Data.EdgeBehavior = EdgeBehavior.Transparent;
Utilities.TestEffect (effect, "tile9.png");
}

[Test]
public void Tile10 ()
{
TileEffect effect = new (Utilities.CreateMockServices ());
effect.Data.EdgeBehavior = EdgeBehavior.Original;
Utilities.TestEffect (effect, "tile10.png");
}

[Test]
public void Twist1 ()
{
Expand Down

0 comments on commit e4f1c1e

Please sign in to comment.