using NetVips; using PrivaPub.Domain.Media; namespace PrivaPub.Tests.Domain { public class MediaProcessingTests { static byte[] JpegWithMetadata(int width, int height) { using var image = (Image.Black(width, height, bands: 3) + new double[] { 200, 40, 90 }).Cast(Enums.BandFormat.Uchar); using var tagged = image.Mutate(m => { m.Set(GValue.GStrType, "exif-ifd0-ImageDescription", "where I live"); m.Set(GValue.GStrType, "exif-ifd0-Artist", "Alice Smith"); m.Set(GValue.GStrType, "exif-ifd2-UserComment", "at home"); m.Set(GValue.BlobType, "xmp-data", System.Text.Encoding.UTF8.GetBytes("")); }); return tagged.WriteToBuffer(".jpg"); } // SVG, PDF and the other formats libvips could read never load from an upload, whatever it claims to be [Fact] public void Only_the_upload_formats_are_ever_loaded() { var svg = ""u8.ToArray(); Assert.Throws(() => MediaService.Inspect(svg)); Assert.Throws(() => MediaService.Inspect("%PDF-1.4\n1 0 obj<<>>endobj\ntrailer<<>>\n%%EOF"u8.ToArray())); using var colour = (Image.Black(8, 8, bands: 3) + 100).Cast(Enums.BandFormat.Uchar); foreach (var format in new[] { ".jpg", ".png", ".gif", ".webp" }) Assert.Equal(8, MediaService.Inspect(colour.WriteToBuffer(format)).Width); Assert.Throws(() => MediaService.Inspect(colour.WriteToBuffer(".tif"))); } // the header tells how big an image would decode before anything is decoded [Fact] public void The_header_says_how_big_an_image_would_decode() { using var huge = Image.Black(8000, 6000); var header = MediaService.Inspect(huge.WriteToBuffer(".png")); Assert.Equal(48_000_000, header.Pixels); Assert.False(header.Animated); using var frame = (Image.Black(40, 30, bands: 3) + 60).Cast(Enums.BandFormat.Uchar); using var frames = Image.Arrayjoin(new[] { frame, frame + 80, frame + 160 }, across: 1).Cast(Enums.BandFormat.Uchar); using var paged = frames.Mutate(m => m.Set(GValue.GIntType, "page-height", 30)); var gif = MediaService.Inspect(paged.WriteToBuffer(".gif")); Assert.True(gif.Animated); Assert.Equal(3, gif.Pages); Assert.Equal(40 * 30 * 3, gif.Pixels); } [Fact] public void Uploaded_images_lose_every_kind_of_metadata() { var input = JpegWithMetadata(800, 600); using (var original = Image.NewFromBuffer(input)) Assert.Contains("exif-data", original.GetFields()); var processed = MediaService.ProcessImage(input, 4096, 640); using var output = Image.NewFromBuffer(processed.Bytes); var fields = output.GetFields(); Assert.DoesNotContain("exif-data", fields); Assert.DoesNotContain("xmp-data", fields); Assert.DoesNotContain("iptc-data", fields); Assert.DoesNotContain(fields, f => f.StartsWith("exif-ifd")); Assert.Equal((800, 600), (processed.Width, processed.Height)); Assert.Equal("image/jpeg", processed.ContentType); using var preview = Image.NewFromBuffer(processed.Preview); Assert.Equal(640, Math.Max(preview.Width, preview.Height)); Assert.DoesNotContain("exif-data", preview.GetFields()); } [Fact] public void Large_images_are_capped() { var processed = MediaService.ProcessImage(JpegWithMetadata(5000, 2500), 4096, 640); Assert.Equal((4096, 2048), (processed.Width, processed.Height)); } [Fact] public void A_blurhash_is_well_formed() { var processed = MediaService.ProcessImage(JpegWithMetadata(64, 64), 4096, 640); Assert.Equal(28, processed.Blurhash.Length); Assert.Equal('L', processed.Blurhash[0]); } [Fact] public void A_white_image_encodes_white_as_its_average() { var pixels = Enumerable.Range(0, 16 * 16).SelectMany(_ => new byte[] { 255, 255, 255 }).ToArray(); var hash = Blurhash.Encode(pixels, 16, 16); Assert.Equal(28, hash.Length); Assert.Equal("TSUA", hash[2..6]); } } }