From 81a175a7db65d1dc76b5b307ccb8ea0bf065bac5 Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 24 Jun 2014 23:32:06 +0100 Subject: [PATCH] Adding transparent webp support Former-commit-id: b58d521dd552879f628fa155468b4f47d8e23ad1 --- .../HttpModules/ImageProcessingModule.cs | 5 +- .../Imaging/Formats/WebPFormat.cs | 71 +++++++++++++----- src/ImageProcessorConsole/Program.cs | 27 +++++-- .../images/input/circle.png | Bin 0 -> 6957 bytes .../images/input/rotate.jpg.REMOVED.git-id | 1 + .../images/output/circle.webp | Bin 0 -> 1228 bytes .../images/output/rotate.webp | Bin 0 -> 1812 bytes 7 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 src/ImageProcessorConsole/images/input/circle.png create mode 100644 src/ImageProcessorConsole/images/input/rotate.jpg.REMOVED.git-id create mode 100644 src/ImageProcessorConsole/images/output/circle.webp create mode 100644 src/ImageProcessorConsole/images/output/rotate.webp diff --git a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs index 91ec40aab..f2bd71ad3 100644 --- a/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs +++ b/src/ImageProcessor.Web/NET45/HttpModules/ImageProcessingModule.cs @@ -359,13 +359,14 @@ namespace ImageProcessor.Web.HttpModules IPrincipal user = context.User ?? new GenericPrincipal(new GenericIdentity(string.Empty, string.Empty), new string[0]); // Do we have permission to call UrlAuthorizationModule.CheckUrlAccessForPrincipal? - PermissionSet permission = new PermissionSet(PermissionState.None); + PermissionSet permission = new PermissionSet(PermissionState.Unrestricted); permission.AddPermission(new AspNetHostingPermission(AspNetHostingPermissionLevel.Unrestricted)); bool hasPermission = permission.IsSubsetOf(AppDomain.CurrentDomain.PermissionSet); bool isAllowed = true; - // Run the rewritten path past the auth system again, using the result as the default "AllowAccess" value + // Run the rewritten path past the authorization system again. + // We can then use the result as the default "AllowAccess" value if (hasPermission && !context.SkipAuthorization) { isAllowed = UrlAuthorizationModule.CheckUrlAccessForPrincipal(virtualCachedPath, user, "GET"); diff --git a/src/ImageProcessor/Imaging/Formats/WebPFormat.cs b/src/ImageProcessor/Imaging/Formats/WebPFormat.cs index 5253983e3..15e4e4313 100644 --- a/src/ImageProcessor/Imaging/Formats/WebPFormat.cs +++ b/src/ImageProcessor/Imaging/Formats/WebPFormat.cs @@ -13,6 +13,7 @@ namespace ImageProcessor.Imaging.Formats { using System; + using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; @@ -20,6 +21,8 @@ namespace ImageProcessor.Imaging.Formats using System.Runtime.InteropServices; using System.Text; + using ImageProcessor.Core.Common.Exceptions; + /// /// Provides the necessary information to support webp images. /// Adapted from @@ -72,6 +75,35 @@ namespace ImageProcessor.Imaging.Formats } } + /// + /// Applies the given processor the current image. + /// + /// The processor delegate. + /// The . + public override void ApplyProcessor(Func processor, ImageFactory factory) + { + base.ApplyProcessor(processor, factory); + + // Set the property item information from any Exif metadata. + // We do this here so that they can be changed between processor methods. + if (factory.PreserveExifData) + { + foreach (KeyValuePair propertItem in factory.ExifPropertyItems) + { + try + { + factory.Image.SetPropertyItem(propertItem.Value); + } + // ReSharper disable once EmptyGeneralCatchClause + catch + { + // Do nothing. The image format does not handle EXIF data. + // TODO: empty catch is fierce code smell. + } + } + } + } + /// /// Decodes the image to process. /// @@ -151,49 +183,48 @@ namespace ImageProcessor.Imaging.Formats IntPtr ptrData = pinnedWebP.AddrOfPinnedObject(); uint dataSize = (uint)webpData.Length; - int imgWidth; - int imgHeight; + int width; + int height; - if (WebPGetInfo(ptrData, dataSize, out imgWidth, out imgHeight) != 1) + if (WebPGetInfo(ptrData, dataSize, out width, out height) != 1) { - // TODO: Throw error? - return null; + throw new ImageFormatException("WebP image is corrupted."); } // Create a BitmapData and Lock all pixels to be written - Bitmap bmp = new Bitmap(imgWidth, imgHeight, PixelFormat.Format24bppRgb); - BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat); + Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb); + BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat); // Allocate memory for uncompress image - int outputBufferSize = bmpData.Stride * imgHeight; + int outputBufferSize = bitmapData.Stride * height; IntPtr outputBuffer = Marshal.AllocHGlobal(outputBufferSize); // Uncompress the image - outputBuffer = WebPDecodeBGRInto(ptrData, dataSize, outputBuffer, outputBufferSize, bmpData.Stride); + outputBuffer = WebPDecodeBGRAInto(ptrData, dataSize, outputBuffer, outputBufferSize, bitmapData.Stride); // Write image to bitmap using Marshal byte[] buffer = new byte[outputBufferSize]; Marshal.Copy(outputBuffer, buffer, 0, outputBufferSize); - Marshal.Copy(buffer, 0, bmpData.Scan0, outputBufferSize); + Marshal.Copy(buffer, 0, bitmapData.Scan0, outputBufferSize); // Unlock the pixels - bmp.UnlockBits(bmpData); + bitmap.UnlockBits(bitmapData); // Free memory pinnedWebP.Free(); Marshal.FreeHGlobal(outputBuffer); - return bmp; + return bitmap; } /// - /// Lossly encodes the image in bitmap. + /// Lossy encodes the image in bitmap. /// /// /// Bitmap with the image /// /// - /// Quality. 0 = minimum ... 100 = maximimun quality + /// Quality. 0 = minimum ... 100 = maximum quality /// /// /// The byte array containing the encoded image data. @@ -207,9 +238,9 @@ namespace ImageProcessor.Imaging.Formats try { - BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); + BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); IntPtr unmanagedData; - int size = WebPEncodeBGR(bmpData.Scan0, bitmap.Width, bitmap.Height, bmpData.Stride, quality, out unmanagedData); + int size = WebPEncodeBGRA(bmpData.Scan0, bitmap.Width, bitmap.Height, bmpData.Stride, quality, out unmanagedData); // Copy image compress data to output array webpData = new byte[size]; @@ -266,16 +297,16 @@ namespace ImageProcessor.Imaging.Formats /// Size of allocated buffer /// /// - /// Specifies the distance between scanlines + /// Specifies the distance between scan-lines /// /// /// output_buffer if function succeeds; NULL otherwise /// [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr WebPDecodeBGRInto(IntPtr data, uint dataSize, IntPtr outputBuffer, int outputBufferSize, int outputStride); + private static extern IntPtr WebPDecodeBGRAInto(IntPtr data, uint dataSize, IntPtr outputBuffer, int outputBufferSize, int outputStride); /// - /// Lossless encoding images pointed to by *data in WebP format + /// Lossy encoding images pointed to by *data in WebP format /// /// /// Pointer to RGB image data @@ -299,7 +330,7 @@ namespace ImageProcessor.Imaging.Formats /// Size of WebP Image /// [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)] - private static extern int WebPEncodeBGR(IntPtr rgb, int width, int height, int stride, float qualityFactor, out IntPtr output); + private static extern int WebPEncodeBGRA(IntPtr rgb, int width, int height, int stride, float qualityFactor, out IntPtr output); /// /// Frees the unmanaged memory. diff --git a/src/ImageProcessorConsole/Program.cs b/src/ImageProcessorConsole/Program.cs index cb33aef79..20e92e803 100644 --- a/src/ImageProcessorConsole/Program.cs +++ b/src/ImageProcessorConsole/Program.cs @@ -10,8 +10,11 @@ namespace ImageProcessorConsole { using System; + using System.Collections.Generic; using System.Drawing; using System.IO; + using System.Linq; + using ImageProcessor; using ImageProcessor.Imaging.Formats; @@ -37,8 +40,10 @@ namespace ImageProcessorConsole di.Create(); } - //FileInfo[] files = di.GetFiles("*.gif"); - FileInfo[] files = di.GetFiles(); + FileInfo[] files = di.GetFiles("*.jpg"); + //FileInfo[] files = di.GetFiles(); + //var files = GetFilesByExtensions(di, ".gif", ".webp"); + foreach (FileInfo fileInfo in files) { @@ -47,20 +52,30 @@ namespace ImageProcessorConsole // ImageProcessor using (MemoryStream inStream = new MemoryStream(photoBytes)) { - using (ImageFactory imageFactory = new ImageFactory()) + using (ImageFactory imageFactory = new ImageFactory(true)) { Size size = new Size(200, 200); // Load, resize, set the format and quality and save an image. imageFactory.Load(inStream) + .AutoRotate() .Constrain(size) - //.Format(new JpegFormat()) + .Format(new WebPFormat()) + .Quality(5) // ReSharper disable once AssignNullToNotNullAttribute - // .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".jpg"))); - .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name))); + .Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".webp"))); + //.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name))); } } } } + + public static IEnumerable GetFilesByExtensions(DirectoryInfo dir, params string[] extensions) + { + if (extensions == null) + throw new ArgumentNullException("extensions"); + IEnumerable files = dir.EnumerateFiles(); + return files.Where(f => extensions.Contains(f.Extension, StringComparer.OrdinalIgnoreCase)); + } } } diff --git a/src/ImageProcessorConsole/images/input/circle.png b/src/ImageProcessorConsole/images/input/circle.png new file mode 100644 index 0000000000000000000000000000000000000000..029091dbc72bf71881302caf881721fa18a235ad GIT binary patch literal 6957 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 literal 0 HcmV?d00001 diff --git a/src/ImageProcessorConsole/images/input/rotate.jpg.REMOVED.git-id b/src/ImageProcessorConsole/images/input/rotate.jpg.REMOVED.git-id new file mode 100644 index 000000000..bf0538b24 --- /dev/null +++ b/src/ImageProcessorConsole/images/input/rotate.jpg.REMOVED.git-id @@ -0,0 +1 @@ +406a6a7916628c0c0bea8243565a7162ebd5a505 \ No newline at end of file diff --git a/src/ImageProcessorConsole/images/output/circle.webp b/src/ImageProcessorConsole/images/output/circle.webp new file mode 100644 index 0000000000000000000000000000000000000000..ef0281087a99eab7de16954d2dfa362028c4127a GIT binary patch literal 1228 zcmV;-1T*_mNk&G*1ONb6MM6+kP&il$0000G0002T0074T06|PpNZA7b00FnApluuJ zlJV!=%M>kRI3)5i{4Ayzc?jA9AQ_Ua zIJRxuwr$(CZQHhO+qi2Ze&~89qFy(m|B3AXv!gB9r0?WayN+GBarfS>E2j>upEsm! zl{^S+(Uz0;-u<1J`o3Yr(^eOPfBtopxZ4EowQf-)b}YPm|Devs+%TS!Mx}ElVY`ZAkE+2l=XT1y ztq;eXdnx$2A^dXeq2!l(u-in@x0PYGn6ghwz-knQ&*yv$?}fKS z+XyDJDF3JcESe-ze+?Y++@V2QTNuow#p8UCuNqB{Igmd{lc>rNZ=Ft;9T2}no0JBS z?nkiJZ#l!g#)lS!xD5I#by*vgQto_Cs_F?S7Vk zWPuO#8vx0+G~5lzWi)(W0E#zgn9&@9Wuxgh6@oozc@%;RX!#-+^iI$+sRrb7KBH$h z$W@4?=M2cTrs+1w^{43>$jzYX(;QG+L(|Viptgsmf0TpTahfL7fZ9cxX4Hq;O}aLL z+FiP~fZ9E}wt(6#x;BH_Rk}8U+8LUr)PmYUnnqWG+9sNQFA23pG<}^1Y9nZR4RW1n zx*u|NQt7!2as@xrb1?L-(K53s^fu7)dr{~Or{xt0)=i<~N(knCMaSMy+(W~tGEf{s z!wZlsA49+C&^$}KsSWK)2`@ zIt_;MY#Kew3*}XYHpCzIMo;Io9%&x*n5D24y152vDcD4W&;Ugcg=btK$sf1>6L_%-}W z#kp{7^pSdV;92uI#WE(swd{3D#SVgR!F?3^&BFwvfphU*XT(B>DhyrhV zAqd01GSb^hArKXIq%o2Yx+5U%E-_-?rsYFe2Hs^v{#a2C!O1=N2DAL@(h3MumqRhk z%KMW_BV5&H-pypzeA(VHCj!@e@y$5?-*-Fv6hjcJ4&8b;n*TB7&GG3i3L>QCJ4{%2 z;^D{N@frLxIqKWAy^C* zFuBMUSY7ZNV#olQj71_P%e@SX^TZ1*D&RS5jkJsPVmKGF6U00VE9#KY@1_7^gu3sVvM8B8y)i qMWUyzLkvUQ?iiyX>+LP4G&gQGqev8-o4qC3!qqv0UQ?*30000XdQ!pw literal 0 HcmV?d00001 diff --git a/src/ImageProcessorConsole/images/output/rotate.webp b/src/ImageProcessorConsole/images/output/rotate.webp new file mode 100644 index 0000000000000000000000000000000000000000..ca786888a0deb517cac3db1fb29b03564a0fec4c GIT binary patch literal 1812 zcmV+v2kZD!Nk&Et2LJ$9MM6+kP&gm}2LJ$YHvpXhD#!qq06&?+*JU{{Dyb*6D7?ZXOJ~Jb@>@|KXOp0)kn(YZMY@XYT;)oYvHGCn zBmNPiq9qO5PqjX6WPi#}wR>w-(GvdQ_tN(ftl;WgB|{KzN_Tdc%RPkyK&u%ZQpxS= z+mj1Ae%9A0ooFtoVmIcM(?ebPkS<21zi`Q6O(h5T$u7hCWKr~?4wgKx5L^-{o2#$0 zu#bHwtOeQdwi-`UbASZ+(&uht6K?0$*aXKw+2?ZnXAPaH=1PQxO6XWWwl-iEk(I9; zwVG(^8x=ely=UppHR2$_-}P(k$pAsI@dc}uE4S*i+O`3VnDjAVUH#KND#PyAPKdSK z+2a`UX(|57^XYTYE~U|)Gt!4_d(l16=HTq>lRVI!|CKPiNn$Uefk%;Med0o3N ze(Ed2WlAyVH>ofH{@Oo9^9FYkV=X+JbsaR`wUb(b1QvP?F)iS6&Tw%qhW*cHB@@S| zdp=gkhGe{Bf#0;TBF4g&*n1**HcVI`8XbkloPPdgu@_}DdqAWpO0CR2JdB7x6DeAi zFGMg&VRyktJC{HBX7PPqBwU1W-Kf0a5P*B&>cX(7!>~UWoH1oEaL{i1{jdg=be-SK z_k~ar8_zfn?7kUK=RH&WoN4CB#Xt3LNg?aaf`HwlxP2bkB9Z)4Ez7+)P}U7f zPbMqM2vfxHsiDhd;KDYT#eoG&Ubq zkkAqnn)@#~#+uI;mLurWCNv1te+g05UsP}&6NT=zUGP^ULHc3PTOp50=y=f~` zY9N6W1`Qlj`h(Kz*!*|279Pp)lY1Nb5&J8n{aYtWHl6t>g*?YQj&`$+YSKrJ%?}75 z&HEfes6VvB$ZIaFl zON->0oBPA420(KdA%HFPtH)La@Nv*1DOU#&Cf})aP#9oV%E4HYFv53?i!C-narKpT z2pTJhVmRQX9yMR+q42$j_*N#)I6@)oM{FEVwyB@R!H%pMj`h25%v6|)#O_yifg&6y zNCy{2RCQeL|5LQ#G&A#eIidLmUr}@=6trBDhMPnBn88b?3azH=O8irEpzR6XtchUV zG3gn*LeD16Yt&kngei6I1P3P4Gyu$9j{A#8sV2oH+sXNmZ>tv=6N~QmZ2h zIgaF_{}_L4q4ZFzWe2Rc_GV}ZsjmKLW6Ne8+eKA zc7Y@^YFo+C%La$f@n_4=Bo_uF@I1X)+5DzD&laaTJ1eX{N*zI--k9IeNNzX@sam&b zhET9}=_GA9{w`5UaE<{0*5Mt3Fz$y)dHXk+h>L_w&+ounrmM#DhfV(<{xIxvAON`0PWdwPXOr_OsD)afKc$7<6X9bu|77Jltyi)bz`F#SWZ*f zq-!Oh*9SXGpka*!(LqfM<#?Br{EvCkZ3SXwHMFB zXe%=ZfjbB&bqI}S;)}dr?`>;`>xl>gnLH?(wV5%7fa{7)a#s_4ow zV}tSF+IwcI<55GH@~G@teR=r9I4fiUPPp7@tUO7Nt%U8&!g(0&lb#DnSw z4FL^p`m+dfGvlUu&cC@2Oj6<@Ojw-Zxs2Z%yGSVBI`x80001L Cv3s)s literal 0 HcmV?d00001