Skip to content

Commit

Permalink
♻️ Try improving the transformation process in case of GIF.
Browse files Browse the repository at this point in the history
Sadly, it still seems that some generated GIFs break the Kraken Z while they do play fine in apps.
I'm now suspecting that the encoding of frames by ImageSharp being far from optimal is the problem. Valid is not enough (if it is)
NZXT CAM probably uses some kind of lossy LZW encoding to reduce frame data.
Symptom on the device is that some frames take longer to render, and we can observe the animation "resetting". Which I'm assuming is caused by two operations racing against eachother. One being the frame rendering supposedly being done in real time from the encoded frame data. The other being the animation timer.
When the animation timer gets sufficiently ahead of the frame rendering, the GIF gets frozen, with the display possibly being corrupted. (Depends on the gif)
This does not seem to brick the FW though. Displaying another image afterwards is still possible.
  • Loading branch information
hexawyz committed Feb 8, 2025
1 parent ffc9591 commit c5343d2
Showing 1 changed file with 48 additions and 2 deletions.
50 changes: 48 additions & 2 deletions src/Exo/Service/Exo.Service.Core/ImageStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,16 +436,62 @@ private void TransformImage(Stream stream, ImageFile originalImage, Rectangle so

if (sourceRectangle.Left + sourceRectangle.Width > image.Width || sourceRectangle.Top + sourceRectangle.Height > image.Height) throw new ArgumentException(nameof(sourceRectangle));

var resampler = KnownResamplers.Bicubic;
var maskColor = SixLabors.ImageSharp.Color.Black;
if (image.Metadata.TryGetFormatMetadata(SixLabors.ImageSharp.Formats.Gif.GifFormat.Instance, out var gifMetadata))
{
var isResampling = targetSize.Width != sourceRectangle.Width || targetSize.Height != sourceRectangle.Height;

if (!isResampling) resampler = KnownResamplers.NearestNeighbor;

// This is certainly not perfect. A global color would probably be a better choice if there are less local colors than global colors.
if (gifMetadata!.GlobalColorTable is not null)
{
maskColor = gifMetadata.GlobalColorTable.GetValueOrDefault().Span[gifMetadata.BackgroundColorIndex];
// Clear the palette if we intend to resample the image.
// Hopefully, this will produce better end results.
if (isResampling) gifMetadata.GlobalColorTable = null;
}
else
{
if (image.Frames[0].Metadata.TryGetFormatMetadata(SixLabors.ImageSharp.Formats.Gif.GifFormat.Instance, out var frameMetadata) &&
frameMetadata!.LocalColorTable is { } localColorTable)
{
maskColor = localColorTable.Span[frameMetadata.HasTransparency ? frameMetadata.TransparencyIndex : 0];
}

// Same as for the global metadata, clear the local palette for every frame.
// NB: This might break some images? (Thinking of true color gifs)
if (isResampling)
{
foreach (var frame in image.Frames)
{
if (frame.Metadata.TryGetFormatMetadata(SixLabors.ImageSharp.Formats.Gif.GifFormat.Instance, out frameMetadata))
{
frameMetadata!.LocalColorTable = null;
}
}
}
}
}

image.Mutate
(
ctx =>
{
ctx.AutoOrient()
.Crop(new(sourceRectangle.Left, sourceRectangle.Top, sourceRectangle.Width, sourceRectangle.Height))
.Resize(targetSize.Width, targetSize.Height, true);
.Resize
(
targetSize.Width,
targetSize.Height,
resampler,
true
);
if (applyCircularMask)
{
ctx.ApplyProcessor(new CircleCroppingProcessor(SixLabors.ImageSharp.Color.Black, (byte)(targetFormat == ImageFormat.Jpeg ? 3 : 0)));
// TODO: For GIF, should find the index of any existing
ctx.ApplyProcessor(new CircleCroppingProcessor(maskColor, (byte)(targetFormat == ImageFormat.Jpeg ? 3 : 2)));
}
}
);
Expand Down

0 comments on commit c5343d2

Please sign in to comment.