various 'improvements'
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using OtterTex;
|
||||
using OtterImage = OtterTex.Image;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
@@ -11,7 +12,6 @@ using Microsoft.Extensions.Logging;
|
||||
using Lumina.Data.Files;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
/*
|
||||
* OtterTex made by Ottermandias
|
||||
@@ -33,6 +33,7 @@ public sealed class TextureDownscaleService
|
||||
|
||||
private readonly ConcurrentDictionary<string, Task> _activeJobs = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<string, string> _downscaledPaths = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly SemaphoreSlim _downscaleSemaphore = new(4);
|
||||
private static readonly IReadOnlyDictionary<int, TextureCompressionTarget> BlockCompressedFormatMap =
|
||||
new Dictionary<int, TextureCompressionTarget>
|
||||
{
|
||||
@@ -80,7 +81,10 @@ public sealed class TextureDownscaleService
|
||||
if (!filePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) return;
|
||||
if (_activeJobs.ContainsKey(hash)) return;
|
||||
|
||||
_activeJobs[hash] = Task.Run(() => DownscaleInternalAsync(hash, filePath, mapKind), CancellationToken.None);
|
||||
_activeJobs[hash] = Task.Run(async () =>
|
||||
{
|
||||
await DownscaleInternalAsync(hash, filePath, mapKind).ConfigureAwait(false);
|
||||
}, CancellationToken.None);
|
||||
}
|
||||
|
||||
public string GetPreferredPath(string hash, string originalPath)
|
||||
@@ -108,6 +112,7 @@ public sealed class TextureDownscaleService
|
||||
bool onlyDownscaleUncompressed = false;
|
||||
bool? isIndexTexture = null;
|
||||
|
||||
await _downscaleSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (!File.Exists(sourcePath))
|
||||
@@ -157,6 +162,15 @@ public sealed class TextureDownscaleService
|
||||
return;
|
||||
}
|
||||
|
||||
if (headerInfo is { } headerValue &&
|
||||
headerValue.Width <= targetMaxDimension &&
|
||||
headerValue.Height <= targetMaxDimension)
|
||||
{
|
||||
_downscaledPaths[hash] = sourcePath;
|
||||
_logger.LogTrace("Skipping downscale for index texture {Hash}; header dimensions {Width}x{Height} within target.", hash, headerValue.Width, headerValue.Height);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onlyDownscaleUncompressed && headerInfo.HasValue && IsBlockCompressedFormat(headerInfo.Value.Format))
|
||||
{
|
||||
_downscaledPaths[hash] = sourcePath;
|
||||
@@ -172,21 +186,20 @@ public sealed class TextureDownscaleService
|
||||
var height = rgbaInfo.Meta.Height;
|
||||
var requiredLength = width * height * bytesPerPixel;
|
||||
|
||||
var rgbaPixels = rgbaScratch.Pixels[..requiredLength].ToArray();
|
||||
var rgbaPixels = rgbaScratch.Pixels.Slice(0, requiredLength);
|
||||
using var originalImage = SixLabors.ImageSharp.Image.LoadPixelData<Rgba32>(rgbaPixels, width, height);
|
||||
|
||||
var targetSize = CalculateTargetSize(originalImage.Width, originalImage.Height, targetMaxDimension);
|
||||
if (targetSize.width == originalImage.Width && targetSize.height == originalImage.Height)
|
||||
{
|
||||
_downscaledPaths[hash] = sourcePath;
|
||||
_logger.LogTrace("Skipping downscale for index texture {Hash}; already within bounds.", hash);
|
||||
return;
|
||||
}
|
||||
|
||||
using var resized = IndexDownscaler.Downscale(originalImage, targetSize.width, targetSize.height, BlockMultiple);
|
||||
|
||||
var resizedPixels = new byte[targetSize.width * targetSize.height * 4];
|
||||
resized.CopyPixelDataTo(resizedPixels);
|
||||
|
||||
using var resizedScratch = ScratchImage.FromRGBA(resizedPixels, targetSize.width, targetSize.height, out var creationInfo).ThrowIfError(creationInfo);
|
||||
using var resizedScratch = CreateScratchImage(resized, targetSize.width, targetSize.height);
|
||||
using var finalScratch = resizedScratch.Convert(DXGIFormat.B8G8R8A8UNorm);
|
||||
|
||||
TexFileHelper.Save(destination, finalScratch);
|
||||
@@ -209,6 +222,7 @@ public sealed class TextureDownscaleService
|
||||
}
|
||||
finally
|
||||
{
|
||||
_downscaleSemaphore.Release();
|
||||
_activeJobs.TryRemove(hash, out _);
|
||||
}
|
||||
}
|
||||
@@ -227,6 +241,41 @@ public sealed class TextureDownscaleService
|
||||
return (resultWidth, resultHeight);
|
||||
}
|
||||
|
||||
private static ScratchImage CreateScratchImage(Image<Rgba32> image, int width, int height)
|
||||
{
|
||||
const int BytesPerPixel = 4;
|
||||
var requiredLength = width * height * BytesPerPixel;
|
||||
|
||||
static ScratchImage Create(ReadOnlySpan<byte> pixels, int width, int height)
|
||||
{
|
||||
var scratchResult = ScratchImage.FromRGBA(pixels, width, height, out var creationInfo);
|
||||
return scratchResult.ThrowIfError(creationInfo);
|
||||
}
|
||||
|
||||
if (image.DangerousTryGetSinglePixelMemory(out var pixelMemory))
|
||||
{
|
||||
var byteSpan = MemoryMarshal.AsBytes(pixelMemory.Span);
|
||||
if (byteSpan.Length < requiredLength)
|
||||
{
|
||||
throw new InvalidOperationException($"Image buffer shorter than expected ({byteSpan.Length} < {requiredLength}).");
|
||||
}
|
||||
|
||||
return Create(byteSpan.Slice(0, requiredLength), width, height);
|
||||
}
|
||||
|
||||
var rented = ArrayPool<byte>.Shared.Rent(requiredLength);
|
||||
try
|
||||
{
|
||||
var rentedSpan = rented.AsSpan(0, requiredLength);
|
||||
image.CopyPixelDataTo(rentedSpan);
|
||||
return Create(rentedSpan, width, height);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(rented);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsIndexMap(TextureMapKind kind)
|
||||
=> kind is TextureMapKind.Mask
|
||||
or TextureMapKind.Index;
|
||||
@@ -420,21 +469,6 @@ public sealed class TextureDownscaleService
|
||||
private static int ReduceDimension(int value)
|
||||
=> value <= 1 ? 1 : Math.Max(1, value / 2);
|
||||
|
||||
private static Image<Rgba32> ReduceLinearTexture(Image<Rgba32> source, int targetWidth, int targetHeight)
|
||||
{
|
||||
var clone = source.Clone();
|
||||
|
||||
while (clone.Width > targetWidth || clone.Height > targetHeight)
|
||||
{
|
||||
var nextWidth = Math.Max(targetWidth, Math.Max(BlockMultiple, clone.Width / 2));
|
||||
var nextHeight = Math.Max(targetHeight, Math.Max(BlockMultiple, clone.Height / 2));
|
||||
clone.Mutate(ctx => ctx.Resize(nextWidth, nextHeight, KnownResamplers.Lanczos3));
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
private static bool ShouldTrim(in TexMeta meta, int targetMaxDimension)
|
||||
{
|
||||
var depth = meta.Dimension == TexDimension.Tex3D ? Math.Max(1, meta.Depth) : 1;
|
||||
@@ -443,12 +477,7 @@ public sealed class TextureDownscaleService
|
||||
|
||||
private static bool ShouldTrimDimensions(int width, int height, int depth, int targetMaxDimension)
|
||||
{
|
||||
if (width <= targetMaxDimension || height <= targetMaxDimension)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (depth > 1 && depth <= targetMaxDimension)
|
||||
if (width <= targetMaxDimension && height <= targetMaxDimension && depth <= targetMaxDimension)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user