diff --git a/BlurHashSharp.sln b/BlurHashSharp.sln index 3a7ef93..2917211 100644 --- a/BlurHashSharp.sln +++ b/BlurHashSharp.sln @@ -19,6 +19,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benches", "benches", "{16AA EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlurHashSharp.Benches", "benches\BlurHashSharp.Benches\BlurHashSharp.Benches.csproj", "{E34874CB-32B1-402D-8E3C-071676468135}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlurHashSharp.ImageSharp", "src\BlurHashSharp.ImageSharp\BlurHashSharp.ImageSharp.csproj", "{32A419EF-8029-4033-BE24-122C83E211F8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlurHashSharp.ImageSharp.Tests", "tests\BlurHashSharp.ImageSharp.Tests\BlurHashSharp.ImageSharp.Tests.csproj", "{C6E91964-A3A2-467A-B202-C1367D484D69}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -92,6 +96,30 @@ Global {E34874CB-32B1-402D-8E3C-071676468135}.Release|x64.Build.0 = Release|Any CPU {E34874CB-32B1-402D-8E3C-071676468135}.Release|x86.ActiveCfg = Release|Any CPU {E34874CB-32B1-402D-8E3C-071676468135}.Release|x86.Build.0 = Release|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Debug|x64.ActiveCfg = Debug|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Debug|x64.Build.0 = Debug|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Debug|x86.ActiveCfg = Debug|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Debug|x86.Build.0 = Debug|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Release|Any CPU.Build.0 = Release|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Release|x64.ActiveCfg = Release|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Release|x64.Build.0 = Release|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Release|x86.ActiveCfg = Release|Any CPU + {32A419EF-8029-4033-BE24-122C83E211F8}.Release|x86.Build.0 = Release|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Debug|x64.ActiveCfg = Debug|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Debug|x64.Build.0 = Debug|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Debug|x86.Build.0 = Debug|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Release|Any CPU.Build.0 = Release|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Release|x64.ActiveCfg = Release|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Release|x64.Build.0 = Release|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Release|x86.ActiveCfg = Release|Any CPU + {C6E91964-A3A2-467A-B202-C1367D484D69}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {680BD7F7-37FA-44B3-8297-91C6EF00A870} = {22515C2C-F86B-404F-A0B0-60F0B8FE9B8C} @@ -99,5 +127,7 @@ Global {EA7DE5B7-8A37-4BF1-B10A-7CE7B982CD8A} = {AA9B09A6-896C-4CB8-8B56-7AA05697B218} {B075D075-93C4-4A19-8657-773593308FD4} = {AA9B09A6-896C-4CB8-8B56-7AA05697B218} {E34874CB-32B1-402D-8E3C-071676468135} = {16AA13F8-F18E-4C4D-98B9-B45E9E659E5B} + {32A419EF-8029-4033-BE24-122C83E211F8} = {22515C2C-F86B-404F-A0B0-60F0B8FE9B8C} + {C6E91964-A3A2-467A-B202-C1367D484D69} = {AA9B09A6-896C-4CB8-8B56-7AA05697B218} EndGlobalSection EndGlobal diff --git a/src/BlurHashSharp.ImageSharp/BlurHashEncoder.cs b/src/BlurHashSharp.ImageSharp/BlurHashEncoder.cs new file mode 100644 index 0000000..2d19823 --- /dev/null +++ b/src/BlurHashSharp.ImageSharp/BlurHashEncoder.cs @@ -0,0 +1,72 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.PixelFormats; + +namespace BlurHashSharp.ImageSharp; + +/// +/// The BlurHash encoder for use with the SkiaSharp image library. +/// +public static class BlurHashEncoder +{ + private static readonly Lazy Lazy = new(() => + { + var config = Configuration.Default.Clone(); + config.PreferContiguousImageBuffers = true; + return config; + }); + + private static Configuration Configuration => Lazy.Value; + + private static DecoderOptions DecoderOptions => + new DecoderOptions() + { + Configuration = Configuration + }; + + /// + /// Encodes the BlurHash representation of the image. + /// + /// The number x components. + /// The number y components. + /// The path to an encoded image on the file system. + /// BlurHash representation of the image. + public static string Encode(int xComponent, int yComponent, string filename) + { + using var image = Image.Load(DecoderOptions, filename); + return EncodeInternal(xComponent, yComponent, image); + } + + /// + /// Encodes the BlurHash representation of the image. + /// + /// The number x components. + /// The number y components. + /// The IO stream of an encoded image. + /// BlurHash representation of the image. + public static string Encode(int xComponent, int yComponent, Stream stream) + { + using var image = Image.Load(DecoderOptions, stream); + return EncodeInternal(xComponent, yComponent, image); + } + + private static string EncodeInternal(int xComponent, int yComponent, Image image) + { + var bytesPerRow = image.Width * (image.PixelType.BitsPerPixel / 8); + Span buffer; + if (image.DangerousTryGetSinglePixelMemory(out Memory memory)) + { + buffer = MemoryMarshal.AsBytes(memory.Span); + } + else + { + buffer = new byte[image.Height * bytesPerRow]; + image.CopyPixelDataTo(buffer); + } + + return CoreBlurHashEncoder.Encode(xComponent, yComponent, image.Width, image.Height, buffer, bytesPerRow, PixelFormat.RGB888); + } +} diff --git a/src/BlurHashSharp.ImageSharp/BlurHashSharp.ImageSharp.csproj b/src/BlurHashSharp.ImageSharp/BlurHashSharp.ImageSharp.csproj new file mode 100644 index 0000000..d2a689b --- /dev/null +++ b/src/BlurHashSharp.ImageSharp/BlurHashSharp.ImageSharp.csproj @@ -0,0 +1,32 @@ + + + + net6.0 + true + enable + true + + + + Bond_009 + 1.3.3 + BlurHash;image;bitmap;imagesharp + BlurHash encoder for use with the ImageSharp image library. + MIT + https://github.com/Bond-009/BlurHashSharp + README.md + + + + + + + + + + + + + + + diff --git a/src/BlurHashSharp.SkiaSharp/BlurHashSharp.SkiaSharp.csproj b/src/BlurHashSharp.SkiaSharp/BlurHashSharp.SkiaSharp.csproj index 5df586b..463a2d6 100644 --- a/src/BlurHashSharp.SkiaSharp/BlurHashSharp.SkiaSharp.csproj +++ b/src/BlurHashSharp.SkiaSharp/BlurHashSharp.SkiaSharp.csproj @@ -9,7 +9,7 @@ Bond_009 - 1.3.2 + 1.3.3 BlurHash;image;bitmap;skiasharp BlurHash encoder for use with the SkiaSharp image library. MIT diff --git a/src/BlurHashSharp/BlurHashSharp.csproj b/src/BlurHashSharp/BlurHashSharp.csproj index 6a166c1..aec5f56 100644 --- a/src/BlurHashSharp/BlurHashSharp.csproj +++ b/src/BlurHashSharp/BlurHashSharp.csproj @@ -10,7 +10,7 @@ Bond_009 - 1.3.2 + 1.3.3 BlurHash;image;bitmap BlurHash encoder for working with raw bitmaps in memory. MIT diff --git a/src/BlurHashSharp/CoreBlurHashEncoder.cs b/src/BlurHashSharp/CoreBlurHashEncoder.cs index e6e2c22..b0810ea 100644 --- a/src/BlurHashSharp/CoreBlurHashEncoder.cs +++ b/src/BlurHashSharp/CoreBlurHashEncoder.cs @@ -43,8 +43,18 @@ public static string Encode( static int ThrowPixelFormatArgumentException() => throw new ArgumentException("Invalid pixel format.", nameof(pixelFormat)); - int totalComponents = xComponents * yComponents; - int factorsLen = totalComponents * 3; + static int ThrowComponentArgumentOutOfRangeException(string paramName) + => throw new ArgumentOutOfRangeException(paramName, "Invalid number of components."); + + if (xComponents < 1 || xComponents > 9) + { + ThrowComponentArgumentOutOfRangeException(nameof(xComponents)); + } + + if (yComponents < 1 || yComponents > 9) + { + ThrowComponentArgumentOutOfRangeException(nameof(yComponents)); + } int bytesPerPixel = pixelFormat switch { @@ -55,6 +65,9 @@ static int ThrowPixelFormatArgumentException() _ => ThrowPixelFormatArgumentException() }; + int totalComponents = xComponents * yComponents; + int factorsLen = totalComponents * 3; + float[] rented = ArrayPool.Shared.Rent(factorsLen + height + width); try { diff --git a/tests/BlurHashSharp.ImageSharp.Tests/BlurHashEncoderTests.cs b/tests/BlurHashSharp.ImageSharp.Tests/BlurHashEncoderTests.cs new file mode 100644 index 0000000..213ae27 --- /dev/null +++ b/tests/BlurHashSharp.ImageSharp.Tests/BlurHashEncoderTests.cs @@ -0,0 +1,114 @@ +using System; +using System.IO; +using Xunit; + +namespace BlurHashSharp.ImageSharp.Tests; + +public sealed class BlurHashEncoderTests +{ + [Theory] + [InlineData("samples.ffmpeg.org/image-samples/delete_node_large.png", 4, 4, "UCL;mj%N?W~p?axsWEM}-,t6IbRn?bWEoct6")] + [InlineData("samples.ffmpeg.org/image-samples/digital-applications.png", 4, 4, "UORVqmDPaxMzn,ayofRj?]kDx[tPxuofV[oy")] + [InlineData("samples.ffmpeg.org/image-samples/ball.png", 4, 4, "U#M+8hyB}_-XyBkBrwjI~ErxIno{,_jIo{oy")] + [InlineData("samples.ffmpeg.org/image-samples/add_node_small.png", 4, 4, "UEKx6z%N?W~ot9-.t7D+%F-:N3D+?bj]oft6")] + [InlineData("samples.ffmpeg.org/image-samples/delete_node_small.png", 4, 4, "UIL4y*t9%F~o%Nxsa#Rk%J%LN1M}-;a#j@t6")] + [InlineData("samples.ffmpeg.org/image-samples/change_label_large.png", 4, 4, "UBMQ*N%NW7_2~pxtWFM}RhWF-;xs-;RjfPog")] + [InlineData("samples.ffmpeg.org/image-samples/open_large.png", 4, 4, "UIJkl}?ct0a%~m?ZN2ogoaD*oefSxwt2t1WC")] + [InlineData("samples.ffmpeg.org/image-samples/right_triangle.png", 4, 4, "UMC*tR??MkRYbLWEodkB8-RU%ZtNx]a#V^kB")] + [InlineData("samples.ffmpeg.org/image-samples/progress.png", 4, 4, "UREC3ZtI?J%4D#oPM_s;4,IUImM{X3xuogRi")] + [InlineData("samples.ffmpeg.org/image-samples/delete_member_small.png", 4, 4, "UMKBRS%Mt7N3?ZxsRkt5WEa$j=%K~o%LWFa#")] + [InlineData("samples.ffmpeg.org/image-samples/money-256.png", 4, 4, "UcP6:N^+oLIU8^V?j]oy?HD$WCkD-;RjWBR+")] + [InlineData("samples.ffmpeg.org/image-samples/delete_method_large.png", 4, 4, "UELXV;?aW9-;_0%KRpoet1WFxuRk~qWEIUfR")] + [InlineData("samples.ffmpeg.org/image-samples/money-2.bmp", 4, 4, "UT6a:iomawatW5fSfOfSokW6fSj^j|avfQfO")] + [InlineData("samples.ffmpeg.org/image-samples/test.png", 4, 4, "UVRysgof%Mayayayj[j[~qfQIUofxuofWBWB")] + [InlineData("samples.ffmpeg.org/image-samples/screen.png", 4, 4, "UTPQ8B4o4o00ohRkozt7E3axRjxuN1WAoeof")] + [InlineData("samples.ffmpeg.org/image-samples/money-256.bmp", 4, 4, "UcP6:N^+oLIU8^V?j]oy?HD$WCkD-;RjWBR+")] + [InlineData("samples.ffmpeg.org/image-samples/component.png", 4, 4, "UcNQ?E$m[4=P{FKHKHs;{FnmNrr^[NwikCnm")] + [InlineData("samples.ffmpeg.org/image-samples/money.jpg", 4, 4, "UcP6:N^+j[IU8^V?j]oz?HD$WCkD-;RjWBWC")] + [InlineData("samples.ffmpeg.org/image-samples/mail.png", 4, 4, "UeO43P%MpL-=~DozRnaeEmkDVqV?x_V@oatR")] + [InlineData("samples.ffmpeg.org/image-samples/14121200.jpg", 4, 4, "UTHLSTwb-;IT?dRPt8IUsiNGs:Rj9bM{xbof")] + [InlineData("samples.ffmpeg.org/image-samples/methods_large.png", 4, 4, "UHKUZ#-;avj_~nt7N0of%DIW-:xt%MRjofWD")] + [InlineData("samples.ffmpeg.org/image-samples/add_component_large.png", 4, 4, "UCKUZw-;~n%M~oxtD+Rj_0xtIWt6?aj]ogt7")] + [InlineData("samples.ffmpeg.org/image-samples/delete_component_small.png", 4, 4, "UEIrEhN3^~-=~oxs9HRj~n-:IVxs~oxuogt7")] + [InlineData("samples.ffmpeg.org/image-samples/money-256-rle.bmp", 4, 4, "UcP6:N^+oLIU8^V?j]oy?HD$WCkD-;RjWBR+")] + [InlineData("samples.ffmpeg.org/image-samples/dither.jpg", 4, 4, "U@KcPS}Uq]n4VonnogkCIcWTt6oexooMV]WC")] + [InlineData("samples.ffmpeg.org/image-samples/add_method_large.png", 4, 4, "UELN.F?bav-;_0%LWGWBoZWFxuM|~qa#IUj[")] + [InlineData("samples.ffmpeg.org/image-samples/opened_node.png", 4, 4, "UmQ;JC$m+k,LkWbFogbG:bnmKabF{rsDR%r_")] + [InlineData("samples.ffmpeg.org/image-samples/forsaken.jpg", 4, 4, "UCCYq6#79FI:00tRt7oyDiNZ?bog-=WB55NG")] + [InlineData("samples.ffmpeg.org/image-samples/sspline.BMP", 4, 4, "ULNv*+ystTJ-R8M{kVxapyQ,ROogF2xDi_Rk")] + [InlineData("samples.ffmpeg.org/image-samples/delete_component_large.png", 4, 4, "UDKd}W%M~m-;~ot6D+WB_0xtIWxt-;fSogt7")] + [InlineData("samples.ffmpeg.org/image-samples/logo.bmp", 4, 4, "UkTP*dmlh0mRujeTgheTgNe.e.gNwxeTf+g3")] + [InlineData("samples.ffmpeg.org/image-samples/authentica.jpg", 4, 4, "UJ9@S5M{WB%MRjj[WBj[00WBofj[xufQofj[")] + [InlineData("samples.ffmpeg.org/image-samples/off_l16.bmp", 4, 4, "UK6w=zm2i*gGl5iMe+gFakcDf9e=a,b_g3i_")] + [InlineData("samples.ffmpeg.org/image-samples/fujifilm-finepix40i.jpg", 4, 4, "ULF~BYR5oa01s:%24.kC9ZNHNHx]MxRjx]bI")] + [InlineData("samples.ffmpeg.org/image-samples/magick.png", 4, 4, "UPRC-@~q-;9F-oD%kD%M%3oJD%xv%NM{t7t7")] + [InlineData("samples.ffmpeg.org/image-samples/money-24bit.png", 4, 4, "UcP6:N^+oLIU8^V?j]oy?HD$WCkD-;RjWBR+")] + [InlineData("samples.ffmpeg.org/image-samples/change_label_small.png", 4, 4, "UJLXV:xvW8_2~p%KWFWFj;og-:WB%MWBxtRk")] + [InlineData("samples.ffmpeg.org/image-samples/logo.png", 4, 4, "UPDHzGs:5Onnsqj@WUa#0xag=#k9t6azj[ju")] + [InlineData("samples.ffmpeg.org/image-samples/run_large.png", 4, 4, "UcM$[P-6e9-8*{ayofayhgWCt6Rk[Uj@W=of")] + [InlineData("samples.ffmpeg.org/image-samples/run_small.png", 4, 4, "ULJb2M%ND,-;t7WBocj?02RjxrRi-,j?RlWA")] + [InlineData("samples.ffmpeg.org/image-samples/corbis.png", 4, 4, "UISFz|rBv|xu~pRjRjax?vT1SiWBWX%Lt7WB")] + [InlineData("samples.ffmpeg.org/image-samples/delete_member_large.png", 4, 4, "UHLXV;-;fMog?Zt6Rpt7Rij[xtt7~pt8M{fO")] + [InlineData("samples.ffmpeg.org/image-samples/right_triangle_option.png", 4, 4, "UHGTNmNN%M_N?vt7WBfk_2x@IURR%#ogt7kB")] + [InlineData("samples.ffmpeg.org/image-samples/appligent.jpg", 4, 4, "UKRD7jNZxuxu~WRjNGs;~DRjRjay4.xaxaa{")] + [InlineData("samples.ffmpeg.org/image-samples/save_large.png", 4, 4, "UTI=M:oeIWj^~pt7D%M_RjWB%Kt7D$Rj?axu")] + [InlineData("samples.ffmpeg.org/image-samples/examples.jpg", 4, 4, "UJN^h]IUxaVtoetRx]of~qRQbbko%2xujZRj")] + [InlineData("samples.ffmpeg.org/image-samples/open_small.png", 4, 4, "UKIrEg_2j;M~~n-:RpWExtM|xtt8xwoft2oc")] + [InlineData("samples.ffmpeg.org/image-samples/earth2.jpg", 4, 4, "UVE3I:M~IVIp0eNI%1xt=}t5NGax57t3Woog")] + [InlineData("samples.ffmpeg.org/image-samples/home.png", 4, 4, "UoMaRyxuTd-p~En+W;ozX.kCn4f8O;ofV?V@")] + [InlineData("samples.ffmpeg.org/image-samples/convert.png", 4, 4, "UOHLl6RkRjj[~qj[M{ay_2RkWBayt7azt7WB")] + [InlineData("samples.ffmpeg.org/image-samples/add_member_small.png", 4, 4, "UKJ[I|-;t7D;-.%LRlawM|ogobxs~o%LWFWC")] + [InlineData("samples.ffmpeg.org/image-samples/magick_small.png", 4, 4, "UWQvn3~px]D*-oD%ozxuxbs.D*xu%gM{xut7")] + [InlineData("samples.ffmpeg.org/image-samples/add_method_small.png", 4, 4, "UKJ*uQ?bM~j_?Z-:a$j=WAofxtax~pxtIWRk")] + [InlineData("samples.ffmpeg.org/image-samples/ellipsis.png", 4, 4, "UTL;me-;fQ-;~qt7fQt7RjayfQay%Mj[fQj[")] + [InlineData("samples.ffmpeg.org/image-samples/original.png", 4, 4, "U~SWcEnighnixGj[a|jtdBbbnObbW;fQjtfQ")] + [InlineData("samples.ffmpeg.org/image-samples/methods_small.png", 4, 4, "UIJRdr?aN0%N~nj^M}WCRifS?YxsxuRit7oe")] + [InlineData("samples.ffmpeg.org/image-samples/money-16.bmp", 4, 4, "UbP6:N?bflIU4mRiflt7?bD$WBog-;RjWBWB")] + [InlineData("samples.ffmpeg.org/image-samples/add_component_small.png", 4, 4, "UBIY6DWI~m%O~o-.4qIU~m_1IVoa~p-:t8a#")] + [InlineData("samples.ffmpeg.org/image-samples/add_node_large.png", 4, 4, "UBL#2=-=?X~o-;%KWFIW-+xtIbRl_3Rloct7")] + [InlineData("samples.ffmpeg.org/image-samples/make_small.png", 4, 4, "UIKni4-:~n?c-;j[t7of~nt7D+aw?cj[WBof")] + [InlineData("samples.ffmpeg.org/image-samples/delete_method_small.png", 4, 4, "UMK1%v-;M}t9_1-.a$t6obayxsoe~pt7IWWD")] + [InlineData("samples.ffmpeg.org/image-samples/save_small.png", 4, 4, "UIG+Umt34qM|~pt74o9F%MogW9Ri00IU-;%M")] + [InlineData("samples.ffmpeg.org/image-samples/TEST24rle.BMP", 4, 4, "UqFYHsjXKBXBI:j]M_adR%a#xDoINhjYs%f,")] + [InlineData("samples.ffmpeg.org/image-samples/deliverance800.jpg", 4, 4, "UB8=NK%iV].9oz%N%Nt7x^xvt7og?wogWAxv")] + [InlineData("samples.ffmpeg.org/image-samples/add_member_large.png", 4, 4, "UHLN.F-;oZRl?YxsRpofRioet7t7~pxuRjoc")] + [InlineData("samples.ffmpeg.org/image-samples/make_large.png", 4, 4, "UFL4y$%M~n?b?aj[WEj[~ot6D*WC-;j[j@of")] + [InlineData("samples.ffmpeg.org/image-samples/closed_node.png", 4, 4, "UnQ;bsTb+R{r,Kf6fkoM+Rr_KHS0,xjbjZoM")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/4_directions.gif", 4, 4, "UG9tIut409IZt4j@WDaz09WD-*j@IZazj@WD")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/top_title_green_frog.gif", 4, 4, "ULM*Ba~S4?^%~VNKxsR+D$M}xsIVD$IVxtWB")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/creatures_001.gif", 4, 4, "UA5=gf%b8*91xqt4RmM$s,n*WXbZy8kBR9V]")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/3d_book2.gif", 4, 4, "UYC?r^-;9FIUofRjM{t700IU%MxuWBxuxuRj")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/2_computers.gif", 4, 4, "U78z.9bF~ot6-?ayRkazIRayM^WU-waxxnj=")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/houses_002.gif", 4, 4, "UcM@7ixuTc?a$3oekrR+TJofwKWB.mWXZ#xZ")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/1.gif", 4, 4, "U~DKySj[fPj[?wj[f6j[-ffPfQfP$|fQfQfQ")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/7up.gif", 4, 4, "UwPjDU-;~qIUx]ofM{Rj-;RjIUtRxaRjoft7")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/2c-button_green_blue2.gif", 4, 4, "U31NdMVAQSpQQUp8Z}f3aJfgk8aepwZ@kYfS")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/8ball.gif", 4, 4, "UJAc#lt700M{EjfRWBWV4TWB_3xuRjayxuof")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/Accident.gif", 4, 4, "UFSs50D%IUM{~qj[ayf7%M%Mxuxut7WBWBay")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/truckbut.gif", 4, 4, "UA3w59baD-j@Dnf7%Ia|upe:NIfjYKf*oejZ")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/Astronaut1.gif", 4, 4, "UAB3._IU9F-;WBIUIUxu00t7tRRj8_xuxuRj")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/3D.gif", 4, 4, "UB6i:OR*1a$kS1oLspS11asp]:NZ$kS1NZ$j")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/2c_button-yellow_red.gif", 4, 4, "UDBeNgtg1Li}AFaNI?WDR,WWn*WW=uowxXfj")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/red_Dot.gif", 4, 4, "UwRxr^%2*J?bv#WBtlt7yqogm,jY?vt7nig3")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/bettie-gifs-3.gif", 4, 4, "UWI;YV4.0KS2E2oft7ae%LaeaeozE1WV%2j[")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/bank.gif", 4, 4, "UzPP+Qjt_Nay%gj[jZayofofayaeM{WAayoz")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/Big_counter.gif", 4, 4, "UFECwdRjM{t7~qayM{of?bj[%Mj[WBay%Mof")] + [InlineData("samples.ffmpeg.org/image-samples/GIF/cliff-gifs-trc-32c.gif", 4, 4, "UrKuXW}Ya0XAxxoNn%f5jDWAW=fRohj]jYju")] + [InlineData("samples.ffmpeg.org/image-samples/bmp-files/test1.bmp", 4, 4, "U47Y|Hipebi:p3eJeJeJebd+ebe1p2eJeJeJ")] + [InlineData("samples.ffmpeg.org/image-samples/bmp-files/8bpp-rle.bmp", 4, 4, "U64.9MI$V%i.RstKn#rqM;s$XKo#o7r?WHOs")] + [InlineData("samples.ffmpeg.org/image-samples/bmp-files/rmoney.bmp", 4, 4, "UcP6,G^+oLIU4mV?j]oz?HD$WCkD-;RjWBR+")] + [InlineData("samples.ffmpeg.org/image-samples/bmp-files/testcompress8.bmp", 4, 4, "UUCQ3=VU|_U;Un^QW~%#x]WTs.V|WGofX4bv")] + [InlineData("samples.ffmpeg.org/image-samples/bmp-files/test32.bmp", 4, 4, "UMGSM;ORNC+I_N$[+Hq.tlt2nOaRtRX5ayWG")] + [InlineData("samples.ffmpeg.org/image-samples/bmp-files/test24.bmp", 4, 4, "UMGSM;ORNC+I_N$[+Hq.tlt2nOaRtRX5ayWG")] + [InlineData("samples.ffmpeg.org/image-samples/bmp-files/test4.bmp", 4, 4, "UdE3PEr9{zh[Um^QW$%#x]R%s:WFW=baW.X8")] + [InlineData("samples.ffmpeg.org/image-samples/bmp-files/test8.bmp", 4, 4, "UUCQ3=VU|_U;Un^QW~%#x]WTs.V|WGofX4bv")] + [InlineData("samples.ffmpeg.org/image-samples/ART/6-BOATS.png", 4, 4, "UA8;Z6?wp0o#9GIptRxvxt-;j?WXtSWAj@ae")] + [InlineData("samples.ffmpeg.org/image-samples/ART/8257_p_t_477x480_image02.png", 4, 4, "UOJtSIN0Ipi]~q%Lad9FI[xtkBMx%MM{t7xZ")] + [InlineData("samples.ffmpeg.org/image-samples/ART/8266_p_t_640x480_image01.png", 4, 4, "UPF~:fI801Ri4mxv-;kC00WYs+ozMwayRloe")] + + public void Encode_Success(string relPath, int xComponent, int yComponent, string expectedResult) + { + string absPath = Path.Join(Environment.GetEnvironmentVariable("FFMPEG_SAMPLES_PATH"), relPath); + Assert.Equal(expectedResult, BlurHashEncoder.Encode(xComponent, yComponent, absPath)); + } +} diff --git a/tests/BlurHashSharp.ImageSharp.Tests/BlurHashSharp.ImageSharp.Tests.csproj b/tests/BlurHashSharp.ImageSharp.Tests/BlurHashSharp.ImageSharp.Tests.csproj new file mode 100644 index 0000000..65e0482 --- /dev/null +++ b/tests/BlurHashSharp.ImageSharp.Tests/BlurHashSharp.ImageSharp.Tests.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + false + true + + + + + + + + + + + + + + diff --git a/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashSharp.SkiaSharp.Tests.csproj b/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashSharp.SkiaSharp.Tests.csproj index 2a800df..b479a71 100644 --- a/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashSharp.SkiaSharp.Tests.csproj +++ b/tests/BlurHashSharp.SkiaSharp.Tests/BlurHashSharp.SkiaSharp.Tests.csproj @@ -2,7 +2,9 @@ net8.0 + enable false + true @@ -13,7 +15,6 @@ - diff --git a/tests/BlurHashSharp.Tests/BlurHashSharp.Tests.csproj b/tests/BlurHashSharp.Tests/BlurHashSharp.Tests.csproj index 8912e27..8874972 100644 --- a/tests/BlurHashSharp.Tests/BlurHashSharp.Tests.csproj +++ b/tests/BlurHashSharp.Tests/BlurHashSharp.Tests.csproj @@ -2,7 +2,9 @@ net8.0 + enable false + true diff --git a/tests/BlurHashSharp.Tests/CoreBlurHashEncoderTests.cs b/tests/BlurHashSharp.Tests/CoreBlurHashEncoderTests.cs index 4657612..5b17725 100644 --- a/tests/BlurHashSharp.Tests/CoreBlurHashEncoderTests.cs +++ b/tests/BlurHashSharp.Tests/CoreBlurHashEncoderTests.cs @@ -1,43 +1,42 @@ -using System.Collections; -using System.Collections.Generic; +using System; using Xunit; -namespace BlurHashSharp.Tests -{ - public class CoreBlurHashEncoderTests - { - public class LinearTosRGBRoundTripTestData : IEnumerable - { - public IEnumerator GetEnumerator() - { - foreach (float v in CoreBlurHashEncoder._sRGBToLinearLookup) - { - yield return new object[] { v }; - } - } +namespace BlurHashSharp.Tests; - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } +public sealed class CoreBlurHashEncoderTests +{ + public static TheoryData LookupTable + => new TheoryData(CoreBlurHashEncoder._sRGBToLinearLookup); - [Theory] - [ClassData(typeof(LinearTosRGBRoundTripTestData))] - public void LinearTosRGB_RoundTrip_Success(float value) - { - int i = CoreBlurHashEncoder.LinearTosRGB(value); - var roundtrip = CoreBlurHashEncoder._sRGBToLinearLookup[i]; - Assert.Equal((double)value, roundtrip, 9); - } + [Theory] + [MemberData(nameof(LookupTable))] + public void LinearTosRGB_RoundTrip_Success(float value) + { + int i = CoreBlurHashEncoder.LinearTosRGB(value); + var roundtrip = CoreBlurHashEncoder._sRGBToLinearLookup[i]; + Assert.Equal((double)value, roundtrip, 9); + } - [Theory] - [InlineData(float.Epsilon, float.Epsilon)] - [InlineData(0f, 0f)] - [InlineData(1f, 1f)] - [InlineData(-1f, -1f)] - [InlineData(3f, 1.732050808f)] - [InlineData(-3f, -1.732050808f)] - public void SignSqrtF_Success(float value, float excpected) - { - Assert.Equal((double)excpected, CoreBlurHashEncoder.SignSqrtF(value), 9); - } + [Theory] + [InlineData(float.Epsilon, float.Epsilon)] + [InlineData(0f, 0f)] + [InlineData(1f, 1f)] + [InlineData(-1f, -1f)] + [InlineData(3f, 1.732050808f)] + [InlineData(-3f, -1.732050808f)] + public void SignSqrtF_Success(float value, float excpected) + { + Assert.Equal((double)excpected, CoreBlurHashEncoder.SignSqrtF(value), 9); } + + [Theory] + [InlineData(0, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(1, 10)] + [InlineData(10, 1)] + [InlineData(-1, 1)] + [InlineData(1, -1)] + public void Ctor_InvalidNumberOfComponents_ThrowArgumentOutOfRangeException(int xComponents, int yComponents) + => Assert.Throws(() => CoreBlurHashEncoder.Encode(xComponents, yComponents, 128, 128, Array.Empty(), 3 * 128, PixelFormat.RGB888x)); }