use try..finally to make sure rented arrays are returned

This commit is contained in:
Jesse Plamondon-Willard 2022-10-08 17:42:32 -04:00
parent 48d0f70ffd
commit 40d5cd7c05
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
2 changed files with 81 additions and 66 deletions

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using StardewValley; using StardewValley;
@ -64,20 +63,23 @@ namespace StardewModdingAPI.Framework.Content
{ {
int pixelCount = areaWidth * areaHeight; int pixelCount = areaWidth * areaHeight;
sourceData = ArrayPool<Color>.Shared.Rent(pixelCount); sourceData = ArrayPool<Color>.Shared.Rent(pixelCount);
try
// slower copying, line by line
for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++)
{ {
int sourceIndex = (y * source.Width) + areaX; // slower copying, line by line
int targetIndex = (y - areaY) * areaWidth; for (int y = areaY, maxY = areaY + areaHeight; y < maxY; y++)
Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth); {
int sourceIndex = (y * source.Width) + areaX;
int targetIndex = (y - areaY) * areaWidth;
Array.Copy(source.Data, sourceIndex, sourceData, targetIndex, areaWidth);
}
// apply
this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode);
}
finally
{
ArrayPool<Color>.Shared.Return(sourceData);
} }
// apply
this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode);
// return
ArrayPool<Color>.Shared.Return(sourceData);
} }
} }
} }
@ -98,13 +100,15 @@ namespace StardewModdingAPI.Framework.Content
// get source data // get source data
int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height;
Color[] sourceData = ArrayPool<Color>.Shared.Rent(pixelCount); Color[] sourceData = ArrayPool<Color>.Shared.Rent(pixelCount);
source.GetData(0, sourceArea, sourceData, 0, pixelCount); try
{
// apply source.GetData(0, sourceArea, sourceData, 0, pixelCount);
this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode); this.PatchImageImpl(sourceData, source.Width, source.Height, sourceArea.Value, targetArea.Value, patchMode);
}
// return finally
ArrayPool<Color>.Shared.Return(sourceData); {
ArrayPool<Color>.Shared.Return(sourceData);
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -207,41 +211,47 @@ namespace StardewModdingAPI.Framework.Content
// get target data // get target data
Color[] mergedData = ArrayPool<Color>.Shared.Rent(pixelCount); Color[] mergedData = ArrayPool<Color>.Shared.Rent(pixelCount);
target.GetData(0, targetArea, mergedData, 0, pixelCount); try
// merge pixels
for (int i = startIndex; i <= endIndex; i++)
{ {
int targetIndex = i - sourceoffset; target.GetData(0, targetArea, mergedData, 0, pixelCount);
// ref locals here? Not sure.
Color above = sourceData[i];
Color below = mergedData[targetIndex];
// shortcut transparency
if (above.A < MinOpacity)
continue;
if (below.A < MinOpacity || above.A == byte.MaxValue)
mergedData[targetIndex] = above;
// merge pixels // merge pixels
else for (int i = startIndex; i <= endIndex; i++)
{ {
// This performs a conventional alpha blend for the pixels, which are already int targetIndex = i - sourceoffset;
// premultiplied by the content pipeline. The formula is derived from
// https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
float alphaBelow = 1 - (above.A / 255f);
mergedData[targetIndex] = new Color(
r: (int)(above.R + (below.R * alphaBelow)),
g: (int)(above.G + (below.G * alphaBelow)),
b: (int)(above.B + (below.B * alphaBelow)),
alpha: Math.Max(above.A, below.A)
);
}
}
target.SetData(0, targetArea, mergedData, 0, pixelCount); // ref locals here? Not sure.
ArrayPool<Color>.Shared.Return(mergedData); Color above = sourceData[i];
Color below = mergedData[targetIndex];
// shortcut transparency
if (above.A < MinOpacity)
continue;
if (below.A < MinOpacity || above.A == byte.MaxValue)
mergedData[targetIndex] = above;
// merge pixels
else
{
// This performs a conventional alpha blend for the pixels, which are already
// premultiplied by the content pipeline. The formula is derived from
// https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/.
float alphaBelow = 1 - (above.A / 255f);
mergedData[targetIndex] = new Color(
r: (int)(above.R + (below.R * alphaBelow)),
g: (int)(above.G + (below.G * alphaBelow)),
b: (int)(above.B + (below.B * alphaBelow)),
alpha: Math.Max(above.A, below.A)
);
}
}
target.SetData(0, targetArea, mergedData, 0, pixelCount);
}
finally
{
ArrayPool<Color>.Shared.Return(mergedData);
}
} }
} }
} }

View File

@ -395,25 +395,30 @@ namespace StardewModdingAPI.Framework.ContentManagers
// premultiply pixels // premultiply pixels
int count = texture.Width * texture.Height; int count = texture.Width * texture.Height;
Color[] data = ArrayPool<Color>.Shared.Rent(count); Color[] data = ArrayPool<Color>.Shared.Rent(count);
texture.GetData(data, 0, count); try
bool changed = false;
for (int i = 0; i < count; i++)
{ {
ref Color pixel = ref data[i]; texture.GetData(data, 0, count);
if (pixel.A is (byte.MinValue or byte.MaxValue))
continue; // no need to change fully transparent/opaque pixels
data[i] = new Color(pixel.R * pixel.A / byte.MaxValue, pixel.G * pixel.A / byte.MaxValue, pixel.B * pixel.A / byte.MaxValue, pixel.A); // slower version: Color.FromNonPremultiplied(data[i].ToVector4()) bool changed = false;
changed = true; for (int i = 0; i < count; i++)
{
ref Color pixel = ref data[i];
if (pixel.A is (byte.MinValue or byte.MaxValue))
continue; // no need to change fully transparent/opaque pixels
data[i] = new Color(pixel.R * pixel.A / byte.MaxValue, pixel.G * pixel.A / byte.MaxValue, pixel.B * pixel.A / byte.MaxValue, pixel.A); // slower version: Color.FromNonPremultiplied(data[i].ToVector4())
changed = true;
}
if (changed)
texture.SetData(data, 0, count);
return texture;
}
finally
{
ArrayPool<Color>.Shared.Return(data);
} }
if (changed)
texture.SetData(data, 0, count);
// return
ArrayPool<Color>.Shared.Return(data);
return texture;
} }
/// <summary>Fix custom map tilesheet paths so they can be found by the content manager.</summary> /// <summary>Fix custom map tilesheet paths so they can be found by the content manager.</summary>