diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 1dd9a8c..fd2827a 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -6,7 +6,7 @@ on: [ push, pull_request ]
jobs:
analyze:
name: Analyze
- runs-on: windows-2019
+ runs-on: windows-2025
permissions:
actions: read
contents: read
@@ -36,6 +36,6 @@ jobs:
with:
configuration: Debug
build_options: '/p:UseSharedCompilation=false'
-
+
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/dotnet-release.yml b/.github/workflows/dotnet-release.yml
index 9088ff0..55bc7c6 100644
--- a/.github/workflows/dotnet-release.yml
+++ b/.github/workflows/dotnet-release.yml
@@ -9,7 +9,7 @@ on:
jobs:
portable-build:
- runs-on: windows-2019
+ runs-on: windows-2025
steps:
- uses: actions/checkout@v4
- uses: ./.github/build
@@ -19,7 +19,7 @@ jobs:
artifact: release_artifact_portable
portable-publish:
- runs-on: windows-2019
+ runs-on: windows-2025
needs: portable-build
steps:
- name: Collect artifact
@@ -41,7 +41,7 @@ jobs:
asset_content_type: application/zip
installer:
- runs-on: windows-2019
+ runs-on: windows-2025
steps:
- name: Checkout
uses: actions/checkout@v4
diff --git a/.github/workflows/dotnet-testbuild.yml b/.github/workflows/dotnet-testbuild.yml
index aa995b0..5c01ad3 100644
--- a/.github/workflows/dotnet-testbuild.yml
+++ b/.github/workflows/dotnet-testbuild.yml
@@ -6,7 +6,7 @@ on: [ push, pull_request ]
jobs:
build:
- runs-on: windows-2019
+ runs-on: windows-2025
strategy:
matrix:
flavor: [Installer, Portable]
@@ -19,7 +19,7 @@ jobs:
artifact: ${{ matrix.flavor == 'Portable' && 'PasteIntoFile_debug_portable' || '' }}
installer:
- runs-on: windows-2019
+ runs-on: windows-2025
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -39,10 +39,10 @@ jobs:
candle releaseFiles.wxs
candle PasteIntoFile.wxs
light -b ../${{steps.build.outputs.path}} releaseFiles.wixobj PasteIntoFile.wixobj -ext WixNetFxExtension -out Installer.msi
-
+
# test:
-# runs-on: windows-2019 # For a list of available runner types, refer to https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
+# runs-on: windows-2025 # For a list of available runner types, refer to https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
#
# steps:
# - name: Checkout
diff --git a/PasteIntoFile.sln b/PasteIntoFile.sln
index 07216d8..0f8dc74 100644
--- a/PasteIntoFile.sln
+++ b/PasteIntoFile.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.28729.10
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PasteIntoFile", "PasteIntoFile\PasteIntoFile.csproj", "{F6F4215C-6CD7-4279-9C4C-C2DA9A823D0C}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebP", "WebP\WebP.csproj", "{D0BAB316-8DF0-4955-8B5A-E4909402A906}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
{F6F4215C-6CD7-4279-9C4C-C2DA9A823D0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6F4215C-6CD7-4279-9C4C-C2DA9A823D0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6F4215C-6CD7-4279-9C4C-C2DA9A823D0C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D0BAB316-8DF0-4955-8B5A-E4909402A906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D0BAB316-8DF0-4955-8B5A-E4909402A906}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D0BAB316-8DF0-4955-8B5A-E4909402A906}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D0BAB316-8DF0-4955-8B5A-E4909402A906}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/PasteIntoFile/ClipboardContents.cs b/PasteIntoFile/ClipboardContents.cs
index 7d3b52a..a9763b3 100644
--- a/PasteIntoFile/ClipboardContents.cs
+++ b/PasteIntoFile/ClipboardContents.cs
@@ -8,6 +8,7 @@
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
@@ -15,6 +16,7 @@
using PasteIntoFile.Properties;
using PdfSharp.Drawing;
using PdfSharp.Pdf;
+using WebP;
namespace PasteIntoFile {
@@ -49,6 +51,57 @@ public TKey KeyOf(TValue value) {
}
}
+ ///
+ /// Class holding the preview of the clipboard contents, and the preview type as an enum
+ ///
+ public class PreviewHolder {
+ public Image Image = null;
+ public string Text = null;
+ public string Html = null;
+ public string Rtf = null;
+ public string[] List = null;
+
+ ///
+ /// A friendly description of the contents
+ ///
+ public string Description = Resources.str_preview;
+
+ private PreviewHolder(string description) {
+ Description = description ?? Resources.str_preview;
+ }
+ public static PreviewHolder ForImage(Image image, string description = null) {
+ if (description == null)
+ description = string.Format(Resources.str_preview_image, image.Width, image.Height);
+ var p = new PreviewHolder(description);
+ p.Image = image;
+ return p;
+ }
+ public static PreviewHolder ForText(string text, string description = null) {
+ if (description == null)
+ description = string.Format(Resources.str_preview_text, text.Length, text.Split('\n').Length);
+ var p = new PreviewHolder(description);
+ p.Text = text;
+ return p;
+ }
+ public static PreviewHolder ForHtml(string html, string description) {
+ var p = new PreviewHolder(description);
+ p.Html = html;
+ return p;
+ }
+ public static PreviewHolder ForRtf(string rtf, string description = null) {
+ if (description == null)
+ description = Resources.str_preview_rtf;
+ var p = new PreviewHolder(description);
+ p.Rtf = rtf;
+ return p;
+ }
+ public static PreviewHolder ForList(string[] list, string description) {
+ var p = new PreviewHolder(description);
+ p.List = list;
+ return p;
+ }
+ }
+
///
/// This is the base class to hold clipboard contents, metadata, and perform actions with it
@@ -62,9 +115,11 @@ public abstract class BaseContent {
public abstract string[] Extensions { get; }
///
- /// A friendly description of the contents
+ /// The preview of the contents
///
- public abstract string Description { get; }
+ /// File extension determining the format
+ ///
+ public abstract PreviewHolder Preview(string extension);
///
/// The actual data content
@@ -113,32 +168,30 @@ public static string NormalizeExtension(string extension) {
/// Holds image contents
///
public abstract class ImageLikeContent : BaseContent {
- ///
- /// Convert the image to the format used for saving it, so it can be used for a preview
- ///
- /// File extension determining the format
- /// Image in target format or null if no suitable format is found
- public abstract Image ImagePreview(string extension);
}
public class ImageContent : ImageLikeContent {
- public static readonly string[] EXTENSIONS = { "png", "bmp", "gif", "jpg", "pdf", "tif", "ico" };
+ public static readonly string[] EXTENSIONS = { "png", "webp", "jpg", "bmp", "gif", "pdf", "tif", "ico" };
public ImageContent(Image image) {
Data = image;
}
public Image Image => Data as Image;
public override string[] Extensions => EXTENSIONS;
- public override string Description => string.Format(Resources.str_preview_image, Image.Width, Image.Height);
public override void SaveAs(string path, string extension, bool append = false) {
if (append)
throw new AppendNotSupportedException();
- Image image = ImagePreview(extension);
+ var image = Preview(extension).Image;
if (image == null)
throw new FormatException(string.Format(Resources.str_error_cliboard_format_missmatch, extension));
+
switch (NormalizeExtension(extension)) {
+ case "webp":
+ var bytes = new WebPObject(image).GetWebPLossless();
+ File.WriteAllBytes(path, bytes);
+ return;
case "pdf":
// convert image to ximage
var stream = new MemoryStream();
@@ -174,19 +227,26 @@ public override void SaveAs(string path, string extension, bool append = false)
///
/// File extension determining the format
/// Image in target format or null if no suitable format is found
- public override Image ImagePreview(string extension) {
+ public override PreviewHolder Preview(string extension) {
extension = NormalizeExtension(extension);
// Special formats with intermediate conversion types
switch (extension) {
- case "pdf": extension = "png"; break;
- case "ico": return ImageAsIcon.ToBitmap();
+ case "pdf":
+ // Use png as intermediate format
+ extension = "png";
+ break;
+ case "webp":
+ // Lossless as-is
+ return PreviewHolder.ForImage(Image);
+ case "ico":
+ return PreviewHolder.ForImage(ImageAsIcon.ToBitmap());
}
// Find suitable codec and convert image
foreach (var encoder in ImageCodecInfo.GetImageEncoders()) {
if (encoder.FilenameExtension.ToLower().Contains(extension)) {
var stream = new MemoryStream();
Image.Save(stream, encoder, null);
- return Image.FromStream(stream);
+ return PreviewHolder.ForImage(Image.FromStream(stream));
}
}
// TODO: Support conversion to EMF, WMF
@@ -230,7 +290,7 @@ public Icon ImageAsIcon {
/// Like ImageContent, but only for formats which support alpha channel
///
public class TransparentImageContent : ImageContent {
- public static new readonly string[] EXTENSIONS = { "png", "gif", "pdf", "tif", "ico" };
+ public static new readonly string[] EXTENSIONS = { "png", "webp", "gif", "pdf", "tif", "ico" };
public TransparentImageContent(Image image) : base(image) { }
public override string[] Extensions => EXTENSIONS; // Note: gif has only alpha 100% or 0%
}
@@ -239,6 +299,7 @@ public TransparentImageContent(Image image) : base(image) { }
/// Like ImageContent, but only for formats which support animated frames
///
public class AnimatedImageContent : ImageContent {
+ // TODO: in principle "webp" can also support animated frames, but the library we use doesn't support it
public static new readonly string[] EXTENSIONS = { "gif" };
public AnimatedImageContent(Image image) : base(image) { }
public override string[] Extensions => EXTENSIONS;
@@ -255,7 +316,6 @@ public VectorImageContent(Metafile metafile) {
}
public Metafile Metafile => Data as Metafile;
public override string[] Extensions => EXTENSIONS;
- public override string Description => string.Format(Resources.str_preview_image_vector, Metafile.Width, Metafile.Height, Math.Round(Metafile.HorizontalResolution / 2 + Metafile.VerticalResolution / 2));
public override void SaveAs(string path, string extension, bool append = false) {
if (append)
@@ -283,13 +343,14 @@ public override void SaveAs(string path, string extension, bool append = false)
///
/// File extension determining the format
/// Image in target format or null if no suitable format is found
- public override Image ImagePreview(string extension) {
+ public override PreviewHolder Preview(string extension) {
switch (NormalizeExtension(extension)) {
case "emf":
- return Metafile;
+ var description = string.Format(Resources.str_preview_image_vector, Metafile.Width, Metafile.Height, Math.Round(Metafile.HorizontalResolution / 2 + Metafile.VerticalResolution / 2));
+ return PreviewHolder.ForImage(Metafile, description);
default: // fallback to save as raster image
- return new ImageContent(Metafile).ImagePreview(extension);
+ return new ImageContent(Metafile).Preview(extension);
}
}
public override void AddTo(IDataObject data) {
@@ -318,7 +379,6 @@ public string Xml {
}
public override string[] Extensions => EXTENSIONS;
- public override string Description => Resources.str_preview_svg;
public override void SaveAs(string path, string extension, bool append = false) {
if (append)
@@ -334,14 +394,15 @@ public override void SaveAs(string path, string extension, bool append = false)
public override void AddTo(IDataObject data) {
data.SetData("image/svg+xml", Stream);
}
- public override string TextPreview(string extension) {
- return Xml;
+ public override PreviewHolder Preview(string extension) {
+ return PreviewHolder.ForHtml(Xml, Resources.str_preview_svg);
}
}
public abstract class TextLikeContent : BaseContent {
+ public static new readonly string[] CLIP_FORMATS = { DataFormats.UnicodeText, DataFormats.Text };
public TextLikeContent(string text) {
Data = text;
}
@@ -360,25 +421,18 @@ protected static void Save(string path, string text, bool append = false) {
public static string EnsureNewline(string text) {
return text.TrimEnd('\n') + '\n';
}
-
- ///
- /// Return a string used for preview
- ///
- ///
- public abstract string TextPreview(string extension);
}
public class TextContent : TextLikeContent {
public TextContent(string text) : base(text) { }
public override string[] Extensions => new[] { "txt", "md", "log", "bat", "ps1", "java", "js", "cpp", "cs", "py", "css", "html", "php", "json", "csv" };
- public override string Description => string.Format(Resources.str_preview_text, Text.Length, Text.Split('\n').Length);
public override void AddTo(IDataObject data) {
data.SetData(DataFormats.Text, Text);
data.SetData(DataFormats.UnicodeText, Text);
}
- public override string TextPreview(string extension) {
- return Text;
+ public override PreviewHolder Preview(string extension) {
+ return PreviewHolder.ForText(Text);
}
}
@@ -386,7 +440,6 @@ public override string TextPreview(string extension) {
public class HtmlContent : TextLikeContent {
public HtmlContent(string text) : base(text) { }
public override string[] Extensions => new[] { "html", "htm", "xhtml" };
- public override string Description => Resources.str_preview_html;
public override void SaveAs(string path, string extension, bool append = false) {
var html = Text;
if (!append && !html.StartsWith(""))
@@ -409,8 +462,8 @@ public override void AddTo(IDataObject data) {
data.SetData(DataFormats.Html, header + Text);
}
- public override string TextPreview(string extension) {
- return Text;
+ public override PreviewHolder Preview(string extension) {
+ return PreviewHolder.ForHtml(Text, Resources.str_preview_html);
}
}
@@ -418,7 +471,6 @@ public override string TextPreview(string extension) {
public class CsvContent : TextLikeContent {
public CsvContent(string text) : base(text) { }
public override string[] Extensions => new[] { "csv", "tsv", "tab", "md" };
- public override string Description => Resources.str_preview_csv;
public override void AddTo(IDataObject data) {
data.SetData(DataFormats.CommaSeparatedValue, Text);
}
@@ -472,17 +524,65 @@ private string AsMarkdown() {
return header + markdown;
}
- public override string TextPreview(string extension) {
+ public override PreviewHolder Preview(string extension) {
switch (NormalizeExtension(extension)) {
case "md":
- return AsMarkdown();
+ return PreviewHolder.ForText(AsMarkdown(), Resources.str_preview_csv);
default:
- return Text;
+ return PreviewHolder.ForText(Text, Resources.str_preview_csv);
}
}
public override void SaveAs(string path, string extension, bool append = false) {
- Save(path, TextPreview(extension), append);
+ Save(path, Preview(extension).Text, append);
+ }
+ }
+
+
+ public class CalendarContent : TextLikeContent {
+ public CalendarContent(string text) : base(text) { }
+ public static new readonly string[] CLIP_FORMATS = TextLikeContent.CLIP_FORMATS.Concat(new[] { "text/calendar", "application/ics" }).ToArray();
+ public static new readonly string[] FILE_EXTENSIONS = { "ics" };
+ public override string[] Extensions => FILE_EXTENSIONS;
+ public static bool IsValidCalendar(string text) {
+ return text.StartsWith("BEGIN:VCALENDAR");
+ }
+ public Ical.Net.Calendar Calendar => Ical.Net.Calendar.Load(Text);
+ public override void AddTo(IDataObject data) {
+ foreach (var f in CLIP_FORMATS) {
+ data.SetData(f, Text);
+ }
+ }
+
+ public override PreviewHolder Preview(string extension) {
+ switch (extension) {
+ case "ics":
+ try {
+ return PreviewHolder.ForHtml(
+ "\n\n
\n\n\n\n"
+ + string.Join("\n", Calendar.Events.Select(
+ e => string.Format("{0}
{1}
", e.Start, e.Summary)
+ ))
+ + "\n\n\n",
+ Resources.str_preview_calendar
+ );
+ } catch (SerializationException e) {
+ return PreviewHolder.ForText(Text, Resources.str_preview_calendar);
+ }
+ default:
+ return PreviewHolder.ForText(Text, Resources.str_preview_calendar);
+ }
+ }
+ public override void SaveAs(string path, string extension, bool append = false) {
+ if (append)
+ throw new AppendNotSupportedException();
+ Save(path, Text);
+
+
}
}
@@ -494,12 +594,19 @@ public GenericTextContent(string format, string extension, string text) : base(t
Extensions = new[] { extension };
}
public override string[] Extensions { get; }
- public override string Description => Resources.str_preview;
public override void AddTo(IDataObject data) {
data.SetData(_format, Text);
}
- public override string TextPreview(string extension) {
- return Text;
+ public override PreviewHolder Preview(string extension) {
+ switch (extension) {
+ case "rtf":
+ return PreviewHolder.ForRtf(Text);
+ case "dif":
+ return PreviewHolder.ForText(Text, Resources.str_preview_dif);
+ default:
+ return PreviewHolder.ForText(Text);
+ }
+
}
}
@@ -508,7 +615,6 @@ public class UrlContent : TextLikeContent {
public static readonly string[] EXTENSIONS = { "url" };
public UrlContent(string text) : base(text) { }
public override string[] Extensions => EXTENSIONS;
- public override string Description => Resources.str_preview_url;
public override void SaveAs(string path, string extension, bool append = false) {
if (append)
throw new AppendNotSupportedException();
@@ -517,8 +623,8 @@ public override void SaveAs(string path, string extension, bool append = false)
public override void AddTo(IDataObject data) {
data.SetData(DataFormats.Text, Text);
}
- public override string TextPreview(string extension) {
- return Text;
+ public override PreviewHolder Preview(string extension) {
+ return PreviewHolder.ForText(Text, Resources.str_preview_url);
}
}
@@ -538,7 +644,6 @@ public List FileList {
public string FileListString => string.Join("\n", FileList);
public override string[] Extensions => new[] { "zip", "m3u", "files", "txt" };
- public override string Description => string.Format(Resources.str_preview_files, Files.Count);
public override void SaveAs(string path, string extension, bool append = false) {
switch (NormalizeExtension(extension)) {
case "zip":
@@ -567,12 +672,13 @@ public override void SaveAs(string path, string extension, bool append = false)
/// File extension determining the format
/// Preview as text string
///
- public string TextPreview(string extension) {
+ public override PreviewHolder Preview(string extension) {
+ var description = string.Format(Resources.str_preview_files, Files.Count);
switch (NormalizeExtension(extension)) {
case "zip":
- return null;
+ return PreviewHolder.ForList(FileList.ToArray(), description);
default:
- return FileListString;
+ return PreviewHolder.ForText(FileListString, description);
}
}
@@ -657,93 +763,107 @@ public static ClipboardContents FromClipboard() {
//
// Note: if multiple clipboard contents support to same extension, the first in Contents is used
+#if DEBUG
+ Console.WriteLine(">>> Clipboard contents as of " + container.Timestamp + " <<<");
+ var table = new List();
+ foreach (var format in Clipboard.GetDataObject().GetFormats(false)) {
+ var df = DataFormats.GetFormat(format);
+ table.Add(
+ df.Id.ToString().PadLeft(6) + " "
+ + (Enum.IsDefined(typeof(CF), (uint)df.Id) ? "CF_" + (CF)(uint)df.Id : "").PadRight(15) + " "
+ + format.PadRight(30)
+ );
+ }
+ table.Sort();
+ foreach (var row in table)
+ Console.WriteLine(row);
+ Console.WriteLine("");
+#endif
+
// Images
// ======
- var images = new Dict();
- var extensions = new HashSet(new[] {
- ImageContent.EXTENSIONS,
- TransparentImageContent.EXTENSIONS,
- AnimatedImageContent.EXTENSIONS,
- VectorImageContent.EXTENSIONS,
- }.SelectMany(i => i));
-
- // Native clipboard bitmap image
- if (Clipboard.GetData(DataFormats.Dib) is Image dib) // device independent bitmap
- images.Add("bmp", dib);
- else if (Clipboard.GetData(DataFormats.Bitmap) is Image bmp) // device specific bitmap
- images.Add("bmp", bmp);
- else if (Clipboard.GetImage() is Image converted) // anything converted to device specific bitmap
- images.Add("bmp", converted);
-
- // Native clipboard tiff image
- if (Clipboard.GetData(DataFormats.Tiff) is Image tif)
- images.Add("tif", tif);
+ // Collect images (preferred first)
+ var images = new Dict();
- // Native clipboard metafile (emf or wmf)
- if (ReadClipboardMetafile() is Metafile emf)
- images.Add("emf", emf);
+ // Generic image from file
+ if (Clipboard.ContainsFileDropList() && Clipboard.GetFileDropList() is { Count: 1 } files) {
+ var ext = BaseContent.NormalizeExtension(Path.GetExtension(files[0]).Trim('.'));
+ if (ImageContentFromBytes(ext, File.ReadAllBytes(files[0])) is { } imageContent)
+ images.Add(ext, imageContent);
+ }
// Mime and file extension formats
- var formats = extensions.SelectMany(ext => MimeForExtension(ext).Concat(new[] { ext }));
- foreach (var format in formats) { // case insensitive
- if (Clipboard.ContainsData(format) && Clipboard.GetData(format) is MemoryStream stream)
- if (Image.FromStream(stream) is Image img)
- images.Add(format, img);
+ foreach (var types in IMAGE_MIME_TYPES) {
+ var ext = BaseContent.NormalizeExtension(types.Key);
+ if (images.ContainsKey(ext))
+ continue;
+ foreach (var format in types.Value.Concat([ext])) {
+ if (!Clipboard.ContainsData(format) || Clipboard.GetData(format) is not MemoryStream stream)
+ continue;
+ if (ImageContentFromBytes(ext, stream.ToArray()) is { } imageContent)
+ images.Add(ext, imageContent);
+ }
}
- // Generic image from encoded data uri
- if (Clipboard.ContainsText() && ImageFromDataUri(Clipboard.GetText()) is Image uriImage)
- images.Add(uriImage.RawFormat.ToString().ToLower(), uriImage);
+ // Image from encoded data uri
+ if (Clipboard.ContainsText()) {
+ var (mime_ext, bytes) = BytesFromDataUri(Clipboard.GetText());
+ if (bytes != null && !images.ContainsKey(mime_ext))
+ if (ImageContentFromBytes(mime_ext, bytes) is { } imageContent)
+ images.Add(mime_ext, imageContent);
+ }
- // Generic image from file
- if (Clipboard.ContainsFileDropList() && Clipboard.GetFileDropList() is StringCollection files && files.Count == 1) {
- try {
- images.Add(Path.GetExtension(files[0]).Trim('.').ToLower(), Image.FromFile(files[0]));
- } catch { /* format not supported */ }
+ // Native clipboard bitmap image
+ if (!images.ContainsKey("bmp")) {
+ if (Clipboard.GetData(DataFormats.Dib) is Image dib) // device independent bitmap
+ images.Add("bmp", new ImageContent(dib));
+ else if (Clipboard.GetData(DataFormats.Bitmap) is Image bmp) // device specific bitmap
+ images.Add("bmp", new ImageContent(bmp));
+ else if (Clipboard.GetImage() is Image converted) // anything converted to device specific bitmap
+ images.Add("bmp", new ImageContent(converted));
}
+ // Native clipboard tiff image
+ if (!images.ContainsKey("tif") && Clipboard.GetData(DataFormats.Tiff) is Image tif)
+ images.Add("tif", new ImageContent(tif));
+
+ // Native clipboard metafile (emf or wmf)
+ if (!images.ContainsKey("emf") && ReadClipboardMetafile() is Metafile emf)
+ images.Add("emf", new VectorImageContent(emf));
+
+
// Since images can have features (transparency, animations) which are not supported by all file format,
// we handel images with such features separately (in order of priority):
- var remainingExtensions = new HashSet(extensions);
+ var remainingExtensions = new HashSet(new[] {
+ ImageContent.EXTENSIONS,
+ TransparentImageContent.EXTENSIONS,
+ AnimatedImageContent.EXTENSIONS,
+ VectorImageContent.EXTENSIONS,
+ }.SelectMany(i => i)); ;
// 0. Vector image (if any)
- foreach (var (ext, img) in images.Items) {
- if (img is Metafile mf) {
- container.Contents.Add(new VectorImageContent(mf));
- remainingExtensions.ExceptWith(VectorImageContent.EXTENSIONS);
- break;
- }
+ if (images.Values.FirstOrDefault(content => content is VectorImageContent) is { } vectorContent) {
+ container.Contents.Add(vectorContent);
+ remainingExtensions.ExceptWith(vectorContent.Extensions);
}
// 1. Animated image (if any)
- if (images.GetAll(AnimatedImageContent.EXTENSIONS).FirstOrDefault() is Image animated) {
- container.Contents.Add(new AnimatedImageContent(animated));
- remainingExtensions.ExceptWith(AnimatedImageContent.EXTENSIONS);
- } else {
- // no direct match, search for anything that looks like it's animated
- foreach (var (ext, img) in images.Items) {
- try {
- if (img.GetFrameCount(FrameDimension.Time) > 1) {
- container.Contents.Add(new AnimatedImageContent(img));
- remainingExtensions.ExceptWith(AnimatedImageContent.EXTENSIONS);
- break;
- }
- } catch { /* format does not support frames */
- }
- }
+ if (images.Values.FirstOrDefault(content => content is AnimatedImageContent) is { } animatedContent) {
+ container.Contents.Add(animatedContent);
+ remainingExtensions.ExceptWith(animatedContent.Extensions);
}
// 2. Transparent image (if any)
- if (images.GetAll(TransparentImageContent.EXTENSIONS).FirstOrDefault() is Image transparent) {
- container.Contents.Add(new TransparentImageContent(transparent));
- remainingExtensions.ExceptWith(TransparentImageContent.EXTENSIONS);
+ if (images.Values.FirstOrDefault(content => content is TransparentImageContent) is { } transparentContent) {
+ container.Contents.Add(transparentContent);
+ remainingExtensions.ExceptWith(transparentContent.Extensions);
} else {
- // no direct match, search for anything that looks like it's transparent
- foreach (var (ext, img) in images.Items) {
- if (((ImageFlags)img.Flags).HasFlag(ImageFlags.HasAlpha)) {
- container.Contents.Add(new TransparentImageContent(img));
+ // no direct match, search for anything that looks like it's transparent (e.g. transparent animated or vector image)
+ foreach (var cnt in images.Values) {
+ if (cnt is ImageContent imgCnt && ((ImageFlags)imgCnt.Image.Flags).HasFlag(ImageFlags.HasAlpha)) {
+ container.Contents.Add(new TransparentImageContent(imgCnt.Image));
remainingExtensions.ExceptWith(TransparentImageContent.EXTENSIONS);
break;
}
@@ -751,11 +871,11 @@ public static ClipboardContents FromClipboard() {
}
// 3. Remaining image with no special features (if any)
- if (images.GetAll(remainingExtensions).FirstOrDefault() is Image image) {
- container.Contents.Add(new ImageContent(image));
- } else if (images.Values.FirstOrDefault() is Image anything) {
+ if (images.GetAll(remainingExtensions).FirstOrDefault() is ImageContent imgContent) {
+ container.Contents.Add(new ImageContent(imgContent.Image)); // as generic ImageContent
+ } else if (images.Values.FirstOrDefault() is ImageContent anything) {
// no unique match, so accept anything (even if already used as special format)
- container.Contents.Add(new ImageContent(anything));
+ container.Contents.Add(new ImageContent(anything.Image)); // as generic ImageContent
}
@@ -785,6 +905,10 @@ public static ClipboardContents FromClipboard() {
if (Clipboard.ContainsText() && Uri.IsWellFormedUriString(Clipboard.GetText().Trim(), UriKind.Absolute))
container.Contents.Add(new UrlContent(Clipboard.GetText().Trim()));
+ if (ReadClipboardString(CalendarContent.CLIP_FORMATS)?.Trim() is string cal)
+ if (CalendarContent.IsValidCalendar(cal))
+ container.Contents.Add(new CalendarContent(cal));
+
// make sure text content comes last, so it does not overwrite extensions used by previous special formats...
if (ReadClipboardString(DataFormats.UnicodeText, DataFormats.Text, "text/plain") is string text)
container.Contents.Add(new TextContent(text));
@@ -793,21 +917,33 @@ public static ClipboardContents FromClipboard() {
if (Clipboard.ContainsFileDropList())
container.Contents.Add(new FilesContent(Clipboard.GetFileDropList()));
+#if DEBUG
+ // print a list of all contens in the container to the console
+ foreach (var content in container.Contents) {
+ Console.WriteLine("> " + content.GetType());
+ if (content.Preview(content.DefaultExtension).Text is string preview) {
+ preview = preview.Replace('\r', ' ').Replace('\n', ' ').Trim();
+ Console.WriteLine(" " + preview.Substring(0, preview.Length > 100 ? 100 : preview.Length));
+ }
+ }
+ Console.WriteLine();
+#endif
+
return container;
}
- private static IEnumerable MimeForExtension(string extension) {
- switch (BaseContent.NormalizeExtension(extension)) {
- case "jpg": return new[] { "image/jpeg" };
- case "bmp": return new[] { "image/bmp", "image/x-bmp", "image/x-ms-bmp" };
- case "tif": return new[] { "image/tiff", "image/tiff-fx" };
- case "ico": return new[] { "image/x-ico", "image/vnd.microsoft.icon" };
- case "emf": return new[] { "image/emf", "image/x-emf" };
- case "wmf": return new[] { "image/wmf", "image/x-wmf" };
- default: return new[] { "image/" + extension.ToLower() };
- }
- }
+ private static Dict IMAGE_MIME_TYPES = new() {
+ { "bmp", new[] { "image/bmp", "image/x-bmp", "image/x-ms-bmp" } },
+ { "emf", new[] { "image/emf", "image/x-emf" } },
+ { "gif", new[] { "image/gif" } },
+ { "ico", new[] { "image/x-ico", "image/vnd.microsoft.icon" } },
+ { "jpg", new[] { "image/jpeg" } },
+ { "png", new[] { "image/png" } },
+ { "tif", new[] { "image/tiff", "image/tiff-fx" } },
+ { "webp", new[] { "image/webp" } },
+ { "wmf", new[] { "image/wmf", "image/x-wmf" } },
+ };
private static string ReadClipboardHtml() {
if (Clipboard.ContainsData(DataFormats.Html)) {
@@ -865,16 +1001,9 @@ public static ClipboardContents FromFile(string path) {
// add the file itself
container.Contents.Add(new FilesContent(new StringCollection { path }));
- // if it's an image (try&catch instead of maintaining a list of supported extensions)
- try {
- var img = Image.FromFile(path);
- img = RotateFlipImageFromExif(img);
- if (img is Metafile mf) {
- container.Contents.Add(new VectorImageContent(mf));
- } else {
- container.Contents.Add(new ImageContent(img));
- }
- } catch { /* it's not */ }
+ // if it's an image
+ if (ImageContentFromBytes(ext, File.ReadAllBytes(path)) is BaseContent content)
+ container.Contents.Add(content);
// if it's text like (check for absence of zero byte)
@@ -899,10 +1028,14 @@ public static ClipboardContents FromFile(string path) {
container.Contents.Add(new CsvContent(contents));
if (ext == "dif")
container.Contents.Add(new GenericTextContent(DataFormats.Dif, ext, contents));
+ if (CalendarContent.FILE_EXTENSIONS.Contains(ext))
+ container.Contents.Add(new CalendarContent(contents));
if (ext == "rtf")
container.Contents.Add(new GenericTextContent(DataFormats.Rtf, ext, contents));
if (ext == "syk")
container.Contents.Add(new GenericTextContent(DataFormats.SymbolicLink, ext, contents));
+ if (ext == "url")
+ container.Contents.Add(new UrlContent(contents));
} else {
container.Contents.Add(new TextContent(path));
@@ -933,22 +1066,55 @@ private static bool LooksLikeBinaryFile(string filepath) {
///
/// The data URI, typically starting with data:image/
/// The image or null if the uri is not an image or conversion failed
- private static Image ImageFromDataUri(string uri) {
+ private static (string, byte[]) BytesFromDataUri(string uri) {
try {
- var match = Regex.Match(uri, @"^data:image/\w+(?;base64)?,(?.+)$");
+ var match = Regex.Match(uri, @"^data:image/(?;\w+)(?;base64)?,(?.+)$");
if (match.Success) {
+ var ext = BaseContent.NormalizeExtension(match.Groups["ext"].Value);
+ byte[] bytes;
if (match.Groups["base64"].Success) {
// Base64 encoded
- var bytes = Convert.FromBase64String(match.Groups["data"].Value);
- return Image.FromStream(new MemoryStream(bytes));
+ bytes = Convert.FromBase64String(match.Groups["data"].Value);
} else {
// URL encoded
- var bytes = Encoding.Default.GetBytes(match.Groups["data"].Value);
+ bytes = Encoding.Default.GetBytes(match.Groups["data"].Value);
bytes = WebUtility.UrlDecodeToBytes(bytes, 0, bytes.Length);
- return Image.FromStream(new MemoryStream(bytes));
}
+ return (ext, bytes);
}
} catch { /* data uri malformed or not supported */ }
+ return (null, null);
+ }
+
+ private static ImageLikeContent ImageContentFromBytes(string ext, byte[] bytes) {
+ try {
+ if (ext == "webp") {
+ var webp = new WebPObject(bytes);
+ var img = new Bitmap(webp.GetImage()); // create copy
+ if (webp.GetInfo().IsAnimated)
+ return new AnimatedImageContent(img);
+ if (webp.GetInfo().HasAlpha)
+ return new TransparentImageContent(img);
+ return new ImageContent(img);
+ }
+ } catch (Exception e) { /* not a webp, or an animated webp which we don't support yet */
+ Console.WriteLine(e);
+ }
+ try {
+ var img = Image.FromStream(new MemoryStream(bytes));
+ img = RotateFlipImageFromExif(img);
+ if (img is Metafile mf)
+ return new VectorImageContent(mf);
+ try {
+ if (img.GetFrameCount(FrameDimension.Time) > 1)
+ return new AnimatedImageContent(img);
+ } catch { /* not an animated image */ }
+ if (((ImageFlags)img.Flags).HasFlag(ImageFlags.HasAlpha))
+ return new TransparentImageContent(img);
+ return new ImageContent(img);
+ } catch (Exception e) { /* not an image? */
+ Console.WriteLine(e);
+ }
return null;
}
diff --git a/PasteIntoFile/Dialog.cs b/PasteIntoFile/Dialog.cs
index 2eb7698..603a4ab 100644
--- a/PasteIntoFile/Dialog.cs
+++ b/PasteIntoFile/Dialog.cs
@@ -213,9 +213,13 @@ public void updateFilename(string filenameTemplate = null) {
/// Extension
public static string determineExtension(ClipboardContents clipData) {
// Determines primary data in clipboard according to a custom prioritisation order
- var content = clipData.ForContentType(typeof(ImageLikeContent)) ??
- clipData.ForContentType(typeof(TextContent)) ??
- clipData.ForContentType(typeof(BaseContent));
+ var content =
+ clipData.ForContentType(typeof(CsvContent)) ??
+ clipData.ForContentType(typeof(ImageLikeContent)) ??
+ clipData.ForContentType(typeof(SvgContent)) ??
+ clipData.ForContentType(typeof(CalendarContent)) ??
+ clipData.ForContentType(typeof(TextContent)) ??
+ clipData.ForContentType(typeof(BaseContent));
// chose file extension based on user preference if available
switch (content) {
@@ -318,63 +322,51 @@ private void updateContentPreview() {
return;
}
- box.Text = content.Description;
-
- if (content is ImageLikeContent imageContent) {
- var img = imageContent.ImagePreview(ext);
- if (img != null) {
- imagePreview.Image = img;
-
- // Checkerboard background in case image is transparent
- Bitmap bg = new Bitmap(img.Width, img.Height, PixelFormat.Format32bppArgb);
- Graphics g = Graphics.FromImage(bg);
- Brush brush = new SolidBrush(Color.LightGray);
- float d = Math.Max(10, Math.Max(bg.Width, bg.Height) / 50f);
- for (int x = 0; x < bg.Width / d; x++) {
- for (int y = 0; y < bg.Height / d; y += 2) {
- g.FillRectangle(brush, x * d, d * (y + x % 2), d, d);
- }
- }
- imagePreview.BackgroundImage = bg;
+ var preview = content.Preview(ext);
+ box.Text = preview.Description;
- imagePreview.Show();
- } else {
- // conversion failed
- box.Text = String.Format(Resources.str_error_cliboard_format_missmatch, comExt.Text);
+ if (preview.Image is Image img) {
+ imagePreview.Image = img;
+
+ // Checkerboard background in case image is transparent
+ Bitmap bg = new Bitmap(img.Width, img.Height, PixelFormat.Format32bppArgb);
+ Graphics g = Graphics.FromImage(bg);
+ Brush brush = new SolidBrush(Color.LightGray);
+ float d = Math.Max(10, Math.Max(bg.Width, bg.Height) / 50f);
+ for (int x = 0; x < bg.Width / d; x++) {
+ for (int y = 0; y < bg.Height / d; y += 2) {
+ g.FillRectangle(brush, x * d, d * (y + x % 2), d, d);
+ }
}
+ imagePreview.BackgroundImage = bg;
+ imagePreview.Show();
- } else if (content is HtmlContent htmlContent) {
- htmlPreview.DocumentText = htmlContent.Text;
- htmlPreview.Show();
+ } else if (preview.Text is string text) {
+ textPreview.Text = text;
+ textPreview.Show();
- } else if (content is SvgContent svgContent) {
- htmlPreview.DocumentText = svgContent.Xml;
+ } else if (preview.Html is string html) {
+ htmlPreview.DocumentText = html;
htmlPreview.Show();
- } else if (content is TextLikeContent textLikeContent) {
- if (content.Extensions.FirstOrDefault() == "rtf")
- textPreview.Rtf = textLikeContent.TextPreview(ext);
- else
- textPreview.Text = textLikeContent.TextPreview(ext);
+ } else if (preview.Rtf is string rtf) {
+ textPreview.Rtf = rtf;
textPreview.Show();
- } else if (content is FilesContent filesContent) {
- if (filesContent.TextPreview(ext) is string preview) {
- textPreview.Text = preview;
- textPreview.Show();
- } else {
- treePreview.BeginUpdate();
- treePreview.Nodes.Clear();
- foreach (var file in filesContent.FileList) {
- treePreview.Nodes.Add(file);
- }
- treePreview.EndUpdate();
- treePreview.Show();
+ } else if (preview.List is string[] list) {
+ treePreview.BeginUpdate();
+ treePreview.Nodes.Clear();
+ foreach (var entry in list) {
+ treePreview.Nodes.Add(entry);
}
+ treePreview.EndUpdate();
+ treePreview.Show();
+ } else {
+ // conversion failed
+ box.Text = String.Format(Resources.str_error_cliboard_format_missmatch, comExt.Text);
}
-
}
diff --git a/PasteIntoFile/PasteIntoFile.csproj b/PasteIntoFile/PasteIntoFile.csproj
index 7e269eb..3e16780 100644
--- a/PasteIntoFile/PasteIntoFile.csproj
+++ b/PasteIntoFile/PasteIntoFile.csproj
@@ -12,6 +12,7 @@
PasteIntoFile.Program
app.manifest
Resources/icon.ico
+ 12
@@ -48,11 +49,18 @@
+
+
+
+ {20e1114b-c211-46e5-a2e0-10a598ff4a44}
+ WebP
+
+
diff --git a/PasteIntoFile/Properties/Resources.Designer.cs b/PasteIntoFile/Properties/Resources.Designer.cs
index 80070e2..9257208 100644
--- a/PasteIntoFile/Properties/Resources.Designer.cs
+++ b/PasteIntoFile/Properties/Resources.Designer.cs
@@ -425,6 +425,15 @@ internal static string str_preview {
}
}
+ ///
+ /// Looks up a localized string similar to Calendar event preview.
+ ///
+ internal static string str_preview_calendar {
+ get {
+ return ResourceManager.GetString("str_preview_calendar", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to CSV preview.
///
diff --git a/PasteIntoFile/Properties/Resources.de.resx b/PasteIntoFile/Properties/Resources.de.resx
index c8a1c32..f39c015 100644
--- a/PasteIntoFile/Properties/Resources.de.resx
+++ b/PasteIntoFile/Properties/Resources.de.resx
@@ -309,4 +309,7 @@ Allows to keep application window always on top (in foreground of other windows)
Unterordner-Vorlage
+
+ Termin Vorschau
+
diff --git a/PasteIntoFile/Properties/Resources.resx b/PasteIntoFile/Properties/Resources.resx
index 06b73d7..93dba8a 100644
--- a/PasteIntoFile/Properties/Resources.resx
+++ b/PasteIntoFile/Properties/Resources.resx
@@ -321,4 +321,7 @@ Allows to keep application window always on top (in foreground of other windows)
Subfolder template
+
+ Calendar event preview
+
diff --git a/WebP/Helpers/ThrowHelper.cs b/WebP/Helpers/ThrowHelper.cs
new file mode 100644
index 0000000..99ade4e
--- /dev/null
+++ b/WebP/Helpers/ThrowHelper.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Data;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace WebP.Helpers;
+
+internal static class ThrowHelper {
+ [Obsolete]
+ public static Exception Create(
+ Exception inner,
+ [CallerMemberName] string caller = "Unknown") {
+ return new Exception($"{inner.Message}\nIn {caller}", inner);
+ }
+
+ public static Exception UnknownPlatform() {
+ return new PlatformNotSupportedException("Unknown platform detected. Platform must be x86 or x64");
+ }
+
+ [Obsolete]
+ public static Exception ContainsNoData([CallerMemberName] string caller = "Unknown") {
+ return Create(new DataException("Bitmap contains no data"), caller);
+ }
+
+ [Obsolete]
+ public static Exception SizeTooBig([CallerMemberName] string caller = "Unknown") {
+ return
+ Create(new DataException($"Dimension of bitmap is too large. Max is {WebPObject.WebpMaxDimension}x{WebPObject.WebpMaxDimension} pixels"),
+ caller);
+ }
+
+ [Obsolete]
+ public static Exception CannotEncodeByUnknown([CallerMemberName] string caller = "Unknown") {
+ return Create(new Exception("Cannot encode by unknown cause"), caller);
+ }
+
+ [Obsolete]
+ public static Exception NullReferenced(string var, [CallerMemberName] string caller = "Unknown") {
+ return Create(new NullReferenceException($"{var} is null"), caller);
+ }
+
+ [Obsolete]
+ public static Exception QualityOutOfRange([CallerMemberName] string caller = "Unknown") {
+ return Create(new NullReferenceException("Quality must be between"), caller);
+ }
+}
diff --git a/WebP/LICENSE b/WebP/LICENSE
new file mode 100644
index 0000000..cede0dc
--- /dev/null
+++ b/WebP/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Sharp0802
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/WebP/Natives/Enums/Vp8StatusCode.cs b/WebP/Natives/Enums/Vp8StatusCode.cs
new file mode 100644
index 0000000..faa0fe4
--- /dev/null
+++ b/WebP/Natives/Enums/Vp8StatusCode.cs
@@ -0,0 +1,12 @@
+namespace WebP.Natives.Enums;
+
+public enum Vp8StatusCode {
+ Ok,
+ OutOfMemory,
+ InvalidParam,
+ BitstreamError,
+ UnsupportedFeature,
+ Suspended,
+ UserAbort,
+ NotEnoughData
+}
diff --git a/WebP/Natives/Enums/WebPCspMode.cs b/WebP/Natives/Enums/WebPCspMode.cs
new file mode 100644
index 0000000..778ae1a
--- /dev/null
+++ b/WebP/Natives/Enums/WebPCspMode.cs
@@ -0,0 +1,22 @@
+namespace WebP.Natives.Enums;
+
+public enum WebpCspMode {
+ ModeRgb,
+ ModeRgba,
+ ModeBgr,
+ ModeBgra,
+
+ ModeARGB,
+ ModeRGBA4444,
+ ModeRGB565,
+
+ ModeRgbA,
+ ModeBgrA,
+
+ ModeArgb,
+ ModeRgbA4444,
+ ModeYuv,
+
+ ModeYuva,
+ ModeLast
+}
diff --git a/WebP/Natives/Enums/WebPImageHint.cs b/WebP/Natives/Enums/WebPImageHint.cs
new file mode 100644
index 0000000..77e9abc
--- /dev/null
+++ b/WebP/Natives/Enums/WebPImageHint.cs
@@ -0,0 +1,9 @@
+namespace WebP.Natives.Enums;
+
+public enum WebPImageHint {
+ Default,
+ Picture,
+ Photo,
+ Graph,
+ Last
+}
diff --git a/WebP/Natives/Enums/WebPPreset.cs b/WebP/Natives/Enums/WebPPreset.cs
new file mode 100644
index 0000000..e873e43
--- /dev/null
+++ b/WebP/Natives/Enums/WebPPreset.cs
@@ -0,0 +1,10 @@
+namespace WebP.Natives.Enums;
+
+public enum WebPPreset {
+ Default,
+ Picture,
+ Photo,
+ Drawing,
+ Icon,
+ Text
+}
diff --git a/WebP/Natives/Native.cs b/WebP/Natives/Native.cs
new file mode 100644
index 0000000..d70d632
--- /dev/null
+++ b/WebP/Natives/Native.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Security;
+using WebP.Helpers;
+using WebP.Natives.Enums;
+using WebP.Natives.Structs;
+
+namespace WebP.Natives;
+
+using static Native86;
+using static Native64;
+
+[SuppressUnmanagedCodeSecurity]
+public static class Native {
+ private const int WebpDecoderAbiVersion = 0x0208;
+
+ public static int WebPConfigInit(ref WebPConfig config, WebPPreset preset, float quality) {
+ return IntPtr.Size switch {
+ 4 => WebPConfigInitInternal_x86(ref config, preset, quality, WebpDecoderAbiVersion),
+ 8 => WebPConfigInitInternal_x64(ref config, preset, quality, WebpDecoderAbiVersion),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static Vp8StatusCode WebPGetFeatures(IntPtr rawWebP, int dataSize, ref WebPBitstreamFeatures features) {
+ return IntPtr.Size switch {
+ 4 => WebPGetFeaturesInternal_x86(rawWebP, (UIntPtr)dataSize, ref features, WebpDecoderAbiVersion),
+ 8 => WebPGetFeaturesInternal_x64(rawWebP, (UIntPtr)dataSize, ref features, WebpDecoderAbiVersion),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPConfigLosslessPreset(ref WebPConfig config, int level) {
+ return IntPtr.Size switch {
+ 4 => WebPConfigLosslessPreset_x86(ref config, level),
+ 8 => WebPConfigLosslessPreset_x64(ref config, level),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPValidateConfig(ref WebPConfig config) {
+ return IntPtr.Size switch {
+ 4 => WebPValidateConfig_x86(ref config),
+ 8 => WebPValidateConfig_x64(ref config),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPPictureInitInternal(ref WebPPicture pic) {
+ return IntPtr.Size switch {
+ 4 => WebPPictureInitInternal_x86(ref pic, WebpDecoderAbiVersion),
+ 8 => WebPPictureInitInternal_x64(ref pic, WebpDecoderAbiVersion),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPPictureImportBgr(ref WebPPicture pic, IntPtr bgr, int stride) {
+ return IntPtr.Size switch {
+ 4 => WebPPictureImportBGR_x86(ref pic, bgr, stride),
+ 8 => WebPPictureImportBGR_x64(ref pic, bgr, stride),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPPictureImportBgra(ref WebPPicture pic, IntPtr bgra, int stride) {
+ return IntPtr.Size switch {
+ 4 => WebPPictureImportBGRA_x86(ref pic, bgra, stride),
+ 8 => WebPPictureImportBGRA_x64(ref pic, bgra, stride),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPPictureImportBgrx(ref WebPPicture pic, IntPtr bgr, int stride) {
+ return IntPtr.Size switch {
+ 4 => WebPPictureImportBGRX_x86(ref pic, bgr, stride),
+ 8 => WebPPictureImportBGRX_x64(ref pic, bgr, stride),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPEncode(ref WebPConfig config, ref WebPPicture picture) {
+ return IntPtr.Size switch {
+ 4 => WebPEncode_x86(ref config, ref picture),
+ 8 => WebPEncode_x64(ref config, ref picture),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static void WebPPictureFree(ref WebPPicture picture) {
+ switch (IntPtr.Size) {
+ case 4:
+ WebPPictureFree_x86(ref picture);
+ break;
+ case 8:
+ WebPPictureFree_x64(ref picture);
+ break;
+ default: throw ThrowHelper.UnknownPlatform();
+ }
+ }
+
+ public static int WebPGetInfo(IntPtr data, int dataSize, out int width, out int height) {
+ return IntPtr.Size switch {
+ 4 => WebPGetInfo_x86(data, (UIntPtr)dataSize, out width, out height),
+ 8 => WebPGetInfo_x64(data, (UIntPtr)dataSize, out width, out height),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPDecodeBgrInto(IntPtr data, int dataSize, IntPtr outputBuffer, int outputBufferSize,
+ int outputStride) {
+ return IntPtr.Size switch {
+ 4 => WebPDecodeBGRInto_x86(data, (UIntPtr)dataSize, outputBuffer, outputBufferSize, outputStride),
+ 8 => WebPDecodeBGRInto_x64(data, (UIntPtr)dataSize, outputBuffer, outputBufferSize, outputStride),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPDecodeBgraInto(IntPtr data, int dataSize, IntPtr outputBuffer, int outputBufferSize,
+ int outputStride) {
+ return IntPtr.Size switch {
+ 4 => WebPDecodeBGRAInto_x86(data, (UIntPtr)dataSize, outputBuffer, outputBufferSize, outputStride),
+ 8 => WebPDecodeBGRAInto_x64(data, (UIntPtr)dataSize, outputBuffer, outputBufferSize, outputStride),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPInitDecoderConfig(ref WebPDecoderConfig webPDecoderConfig) {
+ return IntPtr.Size switch {
+ 4 => WebPInitDecoderConfigInternal_x86(ref webPDecoderConfig, WebpDecoderAbiVersion),
+ 8 => WebPInitDecoderConfigInternal_x64(ref webPDecoderConfig, WebpDecoderAbiVersion),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static Vp8StatusCode WebPDecode(IntPtr data, int dataSize, ref WebPDecoderConfig webPDecoderConfig) {
+ return IntPtr.Size switch {
+ 4 => WebPDecode_x86(data, (UIntPtr)dataSize, ref webPDecoderConfig),
+ 8 => WebPDecode_x64(data, (UIntPtr)dataSize, ref webPDecoderConfig),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static void WebPFreeDecBuffer(ref WebPDecBuffer buffer) {
+ switch (IntPtr.Size) {
+ case 4:
+ WebPFreeDecBuffer_x86(ref buffer);
+ break;
+ case 8:
+ WebPFreeDecBuffer_x64(ref buffer);
+ break;
+ default: throw ThrowHelper.UnknownPlatform();
+ }
+ }
+
+ public static int WebPEncodeBgr(IntPtr bgr, int width, int height, int stride, float qualityFactor,
+ out IntPtr output) {
+ return IntPtr.Size switch {
+ 4 => WebPEncodeBGR_x86(bgr, width, height, stride, qualityFactor, out output),
+ 8 => WebPEncodeBGR_x64(bgr, width, height, stride, qualityFactor, out output),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPEncodeBgra(IntPtr bgra, int width, int height, int stride, float qualityFactor,
+ out IntPtr output) {
+ return IntPtr.Size switch {
+ 4 => WebPEncodeBGRA_x86(bgra, width, height, stride, qualityFactor, out output),
+ 8 => WebPEncodeBGRA_x64(bgra, width, height, stride, qualityFactor, out output),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPEncodeLosslessBgr(IntPtr bgr, int width, int height, int stride, out IntPtr output) {
+ return IntPtr.Size switch {
+ 4 => WebPEncodeLosslessBGR_x86(bgr, width, height, stride, out output),
+ 8 => WebPEncodeLosslessBGR_x64(bgr, width, height, stride, out output),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPEncodeLosslessBgra(IntPtr bgra, int width, int height, int stride, out IntPtr output) {
+ return IntPtr.Size switch {
+ 4 => WebPEncodeLosslessBGRA_x86(bgra, width, height, stride, out output),
+ 8 => WebPEncodeLosslessBGRA_x64(bgra, width, height, stride, out output),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static void WebPFree(IntPtr p) {
+ switch (IntPtr.Size) {
+ case 4:
+ WebPFree_x86(p);
+ break;
+ case 8:
+ WebPFree_x64(p);
+ break;
+ default: throw ThrowHelper.UnknownPlatform();
+ }
+ }
+
+ public static int WebPGetDecoderVersion() {
+ return IntPtr.Size switch {
+ 4 => WebPGetDecoderVersion_x86(),
+ 8 => WebPGetDecoderVersion_x64(),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+
+ public static int WebPPictureDistortion(ref WebPPicture srcPicture, ref WebPPicture refPicture, int metricType,
+ IntPtr pResult) {
+ return IntPtr.Size switch {
+ 4 => WebPPictureDistortion_x86(ref srcPicture, ref refPicture, metricType, pResult),
+ 8 => WebPPictureDistortion_x64(ref srcPicture, ref refPicture, metricType, pResult),
+ _ => throw ThrowHelper.UnknownPlatform()
+ };
+ }
+}
diff --git a/WebP/Natives/Native64.cs b/WebP/Natives/Native64.cs
new file mode 100644
index 0000000..a33eba9
--- /dev/null
+++ b/WebP/Natives/Native64.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+using WebP.Natives.Enums;
+using WebP.Natives.Structs;
+
+namespace WebP.Natives;
+
+[SuppressUnmanagedCodeSecurity]
+public static class Native64 {
+ private const string DllPath = "libwebp.x64.dll";
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPConfigInitInternal")]
+ public static extern int WebPConfigInitInternal_x64(ref WebPConfig config, WebPPreset preset, float quality,
+ int webpDecoderAbiVersion);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetFeaturesInternal")]
+ public static extern Vp8StatusCode WebPGetFeaturesInternal_x64([In] IntPtr rawWebP, UIntPtr dataSize,
+ ref WebPBitstreamFeatures features,
+ int webpDecoderAbiVersion);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPConfigLosslessPreset")]
+ public static extern int WebPConfigLosslessPreset_x64(ref WebPConfig config, int level);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPValidateConfig")]
+ public static extern int WebPValidateConfig_x64(ref WebPConfig config);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureInitInternal")]
+ public static extern int WebPPictureInitInternal_x64(ref WebPPicture pic, int webpDecoderAbiVersion);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGR")]
+ public static extern int WebPPictureImportBGR_x64(ref WebPPicture pic, IntPtr bgr, int stride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGRA")]
+ public static extern int WebPPictureImportBGRA_x64(ref WebPPicture pic, IntPtr bgra, int stride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGRX")]
+ public static extern int WebPPictureImportBGRX_x64(ref WebPPicture pic, IntPtr bgr, int stride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncode")]
+ public static extern int WebPEncode_x64(ref WebPConfig config, ref WebPPicture picture);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureFree")]
+ public static extern void WebPPictureFree_x64(ref WebPPicture pic);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetInfo")]
+ public static extern int WebPGetInfo_x64([In] IntPtr data, UIntPtr dataSize, out int width, out int height);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeBGRInto")]
+ public static extern int WebPDecodeBGRInto_x64([In] IntPtr data, UIntPtr dataSize, IntPtr outputBuffer,
+ int outputBufferSize, int outputStride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeBGRAInto")]
+ public static extern int WebPDecodeBGRAInto_x64([In] IntPtr data, UIntPtr dataSize, IntPtr outputBuffer,
+ int outputBufferSize, int outputStride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPInitDecoderConfigInternal")]
+ public static extern int WebPInitDecoderConfigInternal_x64(ref WebPDecoderConfig webPDecoderConfig,
+ int webpDecoderAbiVersion);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecode")]
+ public static extern Vp8StatusCode WebPDecode_x64(IntPtr data, UIntPtr dataSize, ref WebPDecoderConfig config);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPFreeDecBuffer")]
+ public static extern void WebPFreeDecBuffer_x64(ref WebPDecBuffer buffer);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeBGR")]
+ public static extern int WebPEncodeBGR_x64([In] IntPtr bgr, int width, int height, int stride, float qualityFactor,
+ out IntPtr output);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeBGRA")]
+ public static extern int WebPEncodeBGRA_x64([In] IntPtr bgra, int width, int height, int stride,
+ float qualityFactor, out IntPtr output);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeLosslessBGR")]
+ public static extern int WebPEncodeLosslessBGR_x64([In] IntPtr bgr, int width, int height, int stride,
+ out IntPtr output);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeLosslessBGRA")]
+ public static extern int WebPEncodeLosslessBGRA_x64([In] IntPtr bgra, int width, int height, int stride,
+ out IntPtr output);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPFree")]
+ public static extern void WebPFree_x64(IntPtr p);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetDecoderVersion")]
+ public static extern int WebPGetDecoderVersion_x64();
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureDistortion")]
+ public static extern int WebPPictureDistortion_x64(ref WebPPicture srcPicture, ref WebPPicture refPicture,
+ int metricType, IntPtr pResult);
+}
diff --git a/WebP/Natives/Native86.cs b/WebP/Natives/Native86.cs
new file mode 100644
index 0000000..00feeb0
--- /dev/null
+++ b/WebP/Natives/Native86.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+using WebP.Natives.Enums;
+using WebP.Natives.Structs;
+
+namespace WebP.Natives;
+
+[SuppressUnmanagedCodeSecurity]
+public static class Native86 {
+ private const string DllPath = "libwebp.x86.dll";
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPConfigInitInternal")]
+ public static extern int WebPConfigInitInternal_x86(ref WebPConfig config, WebPPreset preset, float quality,
+ int webpDecoderAbiVersion);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetFeaturesInternal")]
+ public static extern Vp8StatusCode WebPGetFeaturesInternal_x86([In] IntPtr rawWebP, UIntPtr dataSize,
+ ref WebPBitstreamFeatures features,
+ int webpDecoderAbiVersion);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPConfigLosslessPreset")]
+ public static extern int WebPConfigLosslessPreset_x86(ref WebPConfig config, int level);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPValidateConfig")]
+ public static extern int WebPValidateConfig_x86(ref WebPConfig config);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureInitInternal")]
+ public static extern int WebPPictureInitInternal_x86(ref WebPPicture pic, int webpDecoderAbiVersion);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGR")]
+ public static extern int WebPPictureImportBGR_x86(ref WebPPicture pic, IntPtr bgr, int stride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGRA")]
+ public static extern int WebPPictureImportBGRA_x86(ref WebPPicture pic, IntPtr bgra, int stride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureImportBGRX")]
+ public static extern int WebPPictureImportBGRX_x86(ref WebPPicture pic, IntPtr bgr, int stride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncode")]
+ public static extern int WebPEncode_x86(ref WebPConfig config, ref WebPPicture picture);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureFree")]
+ public static extern void WebPPictureFree_x86(ref WebPPicture pic);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetInfo")]
+ public static extern int WebPGetInfo_x86([In] IntPtr data, UIntPtr dataSize, out int width, out int height);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeBGRInto")]
+ public static extern int WebPDecodeBGRInto_x86([In] IntPtr data, UIntPtr dataSize, IntPtr outputBuffer,
+ int outputBufferSize, int outputStride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecodeBGRAInto")]
+ public static extern int WebPDecodeBGRAInto_x86([In] IntPtr data, UIntPtr dataSize, IntPtr outputBuffer,
+ int outputBufferSize, int outputStride);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPInitDecoderConfigInternal")]
+ public static extern int WebPInitDecoderConfigInternal_x86(ref WebPDecoderConfig webPDecoderConfig,
+ int webpDecoderAbiVersion);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPDecode")]
+ public static extern Vp8StatusCode WebPDecode_x86(IntPtr data, UIntPtr dataSize, ref WebPDecoderConfig config);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPFreeDecBuffer")]
+ public static extern void WebPFreeDecBuffer_x86(ref WebPDecBuffer buffer);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeBGR")]
+ public static extern int WebPEncodeBGR_x86([In] IntPtr bgr, int width, int height, int stride, float qualityFactor,
+ out IntPtr output);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeBGRA")]
+ public static extern int WebPEncodeBGRA_x86([In] IntPtr bgra, int width, int height, int stride,
+ float qualityFactor, out IntPtr output);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeLosslessBGR")]
+ public static extern int WebPEncodeLosslessBGR_x86([In] IntPtr bgr, int width, int height, int stride,
+ out IntPtr output);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPEncodeLosslessBGRA")]
+ public static extern int WebPEncodeLosslessBGRA_x86([In] IntPtr bgra, int width, int height, int stride,
+ out IntPtr output);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPFree")]
+ public static extern void WebPFree_x86(IntPtr p);
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPGetDecoderVersion")]
+ public static extern int WebPGetDecoderVersion_x86();
+
+ [DllImport(DllPath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPPictureDistortion")]
+ public static extern int WebPPictureDistortion_x86(ref WebPPicture srcPicture, ref WebPPicture refPicture,
+ int metricType, IntPtr pResult);
+}
diff --git a/WebP/Natives/Structs/RgbaYuvaBuffer.cs b/WebP/Natives/Structs/RgbaYuvaBuffer.cs
new file mode 100644
index 0000000..5553935
--- /dev/null
+++ b/WebP/Natives/Structs/RgbaYuvaBuffer.cs
@@ -0,0 +1,13 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Explicit),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct RgbaYuvaBuffer {
+ [FieldOffset(0)] public WebPRgbaBuffer Rgba;
+
+ [FieldOffset(0)] public WebPYuvaBuffer Yuva;
+}
diff --git a/WebP/Natives/Structs/WebPAuxStats.cs b/WebP/Natives/Structs/WebPAuxStats.cs
new file mode 100644
index 0000000..de22b30
--- /dev/null
+++ b/WebP/Natives/Structs/WebPAuxStats.cs
@@ -0,0 +1,58 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Sequential),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct WebPAuxStats {
+ public int coded_size;
+ public float PSNR_Y;
+ public float PSNR_U;
+ public float PSNR_V;
+ public float PSNR_ALL;
+ public float PSNRAlpha;
+ public int block_count_intra4;
+ public int block_count_intra16;
+ public int block_count_skipped;
+ public int header_bytes;
+ public int mode_partition_0;
+ public int residual_bytes_DC_segments0;
+ public int residual_bytes_AC_segments0;
+ public int residual_bytes_uv_segments0;
+ public int residual_bytes_DC_segments1;
+ public int residual_bytes_AC_segments1;
+ public int residual_bytes_uv_segments1;
+ public int residual_bytes_DC_segments2;
+ public int residual_bytes_AC_segments2;
+ public int residual_bytes_uv_segments2;
+ public int residual_bytes_DC_segments3;
+ public int residual_bytes_AC_segments3;
+ public int residual_bytes_uv_segments3;
+ public int segment_size_segments0;
+ public int segment_size_segments1;
+ public int segment_size_segments2;
+ public int segment_size_segments3;
+ public int segment_quant_segments0;
+ public int segment_quant_segments1;
+ public int segment_quant_segments2;
+ public int segment_quant_segments3;
+ public int segment_level_segments0;
+ public int segment_level_segments1;
+ public int segment_level_segments2;
+ public int segment_level_segments3;
+ public int alpha_data_size;
+ public int layer_data_size;
+ public int lossless_features;
+ public int histogram_bits;
+ public int transform_bits;
+ public int cache_bits;
+ public int palette_size;
+ public int lossless_size;
+ public int lossless_hdr_size;
+ public int lossless_data_size;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)]
+ private readonly uint[] pad;
+}
diff --git a/WebP/Natives/Structs/WebPBitstreamFeatures.cs b/WebP/Natives/Structs/WebPBitstreamFeatures.cs
new file mode 100644
index 0000000..c83361b
--- /dev/null
+++ b/WebP/Natives/Structs/WebPBitstreamFeatures.cs
@@ -0,0 +1,18 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Sequential),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct WebPBitstreamFeatures {
+ public int Width;
+ public int Height;
+ public int Has_alpha;
+ public int Has_animation;
+ public int Format;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.U4)]
+ private readonly uint[] pad;
+}
diff --git a/WebP/Natives/Structs/WebPConfig.cs b/WebP/Natives/Structs/WebPConfig.cs
new file mode 100644
index 0000000..663e746
--- /dev/null
+++ b/WebP/Natives/Structs/WebPConfig.cs
@@ -0,0 +1,40 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using WebP.Natives.Enums;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Sequential),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct WebPConfig {
+ public int lossless;
+ public float quality;
+ public int method;
+ public WebPImageHint image_hint;
+ public int target_size;
+ public float target_PSNR;
+ public int segments;
+ public int sns_strength;
+ public int filter_strength;
+ public int filter_sharpness;
+ public int filter_type;
+ public int auto_filter;
+ public int alpha_compression;
+ public int alpha_filtering;
+ public int alpha_quality;
+ public int pass;
+ public int show_compressed;
+ public int preprocessing;
+ public int partitions;
+ public int partition_limit;
+ public int emulate_jpeg_size;
+ public int thread_level;
+ public int low_memory;
+ public int near_lossless;
+ public int exact;
+ public int delta_palletization;
+ public int use_sharp_yuv;
+ private readonly int pad1;
+ private readonly int pad2;
+}
diff --git a/WebP/Natives/Structs/WebPDecBuffer.cs b/WebP/Natives/Structs/WebPDecBuffer.cs
new file mode 100644
index 0000000..486bd94
--- /dev/null
+++ b/WebP/Natives/Structs/WebPDecBuffer.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using WebP.Natives.Enums;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Sequential),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct WebPDecBuffer {
+ public WebpCspMode colorSpace;
+ public int width;
+ public int height;
+ public int isExternalMemory;
+ public RgbaYuvaBuffer u;
+ private readonly uint pad1;
+ private readonly uint pad2;
+ private readonly uint pad3;
+ private readonly uint pad4;
+ public IntPtr private_memory;
+}
diff --git a/WebP/Natives/Structs/WebPDecoderConfig.cs b/WebP/Natives/Structs/WebPDecoderConfig.cs
new file mode 100644
index 0000000..dba09b5
--- /dev/null
+++ b/WebP/Natives/Structs/WebPDecoderConfig.cs
@@ -0,0 +1,13 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Sequential),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct WebPDecoderConfig {
+ public WebPBitstreamFeatures input;
+ public WebPDecBuffer output;
+ public WebPDecoderOptions options;
+}
diff --git a/WebP/Natives/Structs/WebPDecoderOptions.cs b/WebP/Natives/Structs/WebPDecoderOptions.cs
new file mode 100644
index 0000000..fcc0de8
--- /dev/null
+++ b/WebP/Natives/Structs/WebPDecoderOptions.cs
@@ -0,0 +1,29 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Sequential),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct WebPDecoderOptions {
+ public int bypass_filtering;
+ public int no_fancy_upsampling;
+ public int use_cropping;
+ public int crop_left;
+ public int crop_top;
+ public int crop_width;
+ public int crop_height;
+ public int use_scaling;
+ public int scaled_width;
+ public int scaled_height;
+ public int use_threads;
+ public int dithering_strength;
+ public int flip;
+ public int alpha_dithering_strength;
+ private readonly uint pad1;
+ private readonly uint pad2;
+ private readonly uint pad3;
+ private readonly uint pad4;
+ private readonly uint pad5;
+}
diff --git a/WebP/Natives/Structs/WebPPicture.cs b/WebP/Natives/Structs/WebPPicture.cs
new file mode 100644
index 0000000..83a767b
--- /dev/null
+++ b/WebP/Natives/Structs/WebPPicture.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Sequential),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct WebPPicture : IDisposable {
+ public int use_argb;
+ public uint colorspace;
+ public int width;
+ public int height;
+ public IntPtr y;
+ public IntPtr u;
+ public IntPtr v;
+ public IntPtr a;
+ public int y_stride;
+ public int uv_stride;
+ public int a_stride;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)]
+ private readonly uint[] pad1;
+
+ public IntPtr argb;
+ public int argb_stride;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U4)]
+ private readonly uint[] pad2;
+
+ public IntPtr writer;
+ public IntPtr custom_ptr;
+ public int extra_info_type;
+ public IntPtr extra_info;
+ public IntPtr stats;
+ public uint error_code;
+ public IntPtr progress_hook;
+ public IntPtr user_data;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13, ArraySubType = UnmanagedType.U4)]
+ private readonly uint[] pad3;
+
+ private readonly IntPtr memory;
+ private readonly IntPtr memory_argb;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)]
+ private readonly uint[] pad4;
+
+ public void Dispose() {
+ }
+}
diff --git a/WebP/Natives/Structs/WebPRgbaBuffer.cs b/WebP/Natives/Structs/WebPRgbaBuffer.cs
new file mode 100644
index 0000000..84599b8
--- /dev/null
+++ b/WebP/Natives/Structs/WebPRgbaBuffer.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Sequential),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct WebPRgbaBuffer {
+ public IntPtr rgba;
+ public int stride;
+ public UIntPtr size;
+}
diff --git a/WebP/Natives/Structs/WebPYuvaBuffer.cs b/WebP/Natives/Structs/WebPYuvaBuffer.cs
new file mode 100644
index 0000000..d4f559e
--- /dev/null
+++ b/WebP/Natives/Structs/WebPYuvaBuffer.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace WebP.Natives.Structs;
+
+[StructLayout(LayoutKind.Sequential),
+ SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global"),
+ SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
+public struct WebPYuvaBuffer {
+ public IntPtr y;
+ public IntPtr u;
+ public IntPtr v;
+ public IntPtr a;
+ public int y_stride;
+ public int u_stride;
+ public int v_stride;
+ public int a_stride;
+ public UIntPtr y_size;
+ public UIntPtr u_size;
+ public UIntPtr v_size;
+ public UIntPtr a_size;
+}
diff --git a/WebP/Natives/libwebp.x64.dll b/WebP/Natives/libwebp.x64.dll
new file mode 100644
index 0000000..9fcabfd
Binary files /dev/null and b/WebP/Natives/libwebp.x64.dll differ
diff --git a/WebP/Natives/libwebp.x86.dll b/WebP/Natives/libwebp.x86.dll
new file mode 100644
index 0000000..6209467
Binary files /dev/null and b/WebP/Natives/libwebp.x86.dll differ
diff --git a/WebP/Properties/AssemblyInfo.cs b/WebP/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..d4e2095
--- /dev/null
+++ b/WebP/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("WebP")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("WebP")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("20E1114B-C211-46E5-A2E0-10A598FF4A44")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/WebP/README.md b/WebP/README.md
new file mode 100644
index 0000000..8a29bd5
--- /dev/null
+++ b/WebP/README.md
@@ -0,0 +1,74 @@
+# WebP.Net
+
+
+[](https://www.nuget.org/packages/WebP_Net/)
+
+## What is this?
+
+This library provides a simple encoder and decoder for webp.
+
+## How to use?
+
+### Install
+
+In your csproj :
+
+```xml
+
+```
+
+Or, if you using .net cli :
+
+```
+dotnet add package WebP_Net --version 1.1.1
+```
+
+### Encode / Decode
+
+```c#
+using System.Drawing;
+using WebP.Net;
+
+static byte[] EncodeLossy(Bitmap bitmap, float quality)
+{
+ using var webp = new WebPObject(bitmap);
+ return webp.GetWebPLossy(quality);
+}
+static byte[] EncodeLossless(Bitmap bitmap)
+{
+ using var webp = new WebPObject(bitmap);
+ return webp.GetLossless();
+}
+static Image DecodeWebP(byte[] webP)
+{
+ using var webp = new WebPObject(webP);
+ return webp.GetImage();
+}
+```
+
+### Get info
+
+```c#
+using WebP.Net;
+
+static WebPInfo GetInfo(byte[] webP)
+{
+ using var webp = new WebPObject(webP);
+ return webP.GetInfo();
+}
+```
+
+### Get version
+
+```c#
+using WebP.Net;
+
+static WebPVersion GetVersion()
+{
+ return WebPObject.GetVersion(); // get version of libwebp
+}
+static string GetVersionAsString()
+{
+ return WebPObject.GetVersion().ToString(); // Major.Minor.Revision
+}
+```
diff --git a/WebP/WebP.csproj b/WebP/WebP.csproj
new file mode 100644
index 0000000..6c8333c
--- /dev/null
+++ b/WebP/WebP.csproj
@@ -0,0 +1,86 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {20E1114B-C211-46E5-A2E0-10A598FF4A44}
+ Library
+ Properties
+ WebP
+ WebP
+ v4.8
+ 512
+ latest
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+ libwebp.x64.dll
+
+
+ Always
+ libwebp.x86.dll
+
+
+
+
+
+
+
+
diff --git a/WebP/WebPInfo.cs b/WebP/WebPInfo.cs
new file mode 100644
index 0000000..99702bf
--- /dev/null
+++ b/WebP/WebPInfo.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Runtime.InteropServices;
+using WebP.Helpers;
+using WebP.Natives;
+using WebP.Natives.Enums;
+using WebP.Natives.Structs;
+
+namespace WebP;
+
+public readonly struct WebPInfo {
+ [method: Obsolete("WebPInfo.GetFrom is obsolete. Use WebPObject instead of this.")]
+ public static WebPInfo GetFrom(byte[] webP) {
+ var handle = GCHandle.Alloc(webP, GCHandleType.Pinned);
+
+ try {
+ var features = new WebPBitstreamFeatures();
+ var status = Native.WebPGetFeatures(handle.AddrOfPinnedObject(), webP.Length, ref features);
+ if (status is not Vp8StatusCode.Ok)
+ throw new ExternalException(status.ToString());
+ return new WebPInfo(features);
+ } catch (Exception ex) {
+ throw ThrowHelper.Create(ex);
+ } finally {
+ if (handle.IsAllocated)
+ handle.Free();
+ }
+ }
+
+ internal WebPInfo(WebPBitstreamFeatures features) {
+ Width = features.Width;
+ Height = features.Height;
+ HasAlpha = features.Has_alpha is not 0;
+ IsAnimated = features.Has_animation is not 0;
+ }
+
+ public int Width { get; }
+ public int Height { get; }
+ public bool HasAlpha { get; }
+ public bool IsAnimated { get; }
+}
diff --git a/WebP/WebPObject.cs b/WebP/WebPObject.cs
new file mode 100644
index 0000000..9235d8d
--- /dev/null
+++ b/WebP/WebPObject.cs
@@ -0,0 +1,211 @@
+using System;
+using System.Data;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Net.Mime;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using WebP.Natives;
+using WebP.Natives.Enums;
+using WebP.Natives.Structs;
+
+namespace WebP;
+
+public sealed class WebPObject : IDisposable {
+ #region Ctors
+
+ public WebPObject(Image image) {
+ _initWithImage = true;
+ ImageCache = image;
+ }
+
+ public WebPObject(byte[] bytes) {
+ BytesCache = bytes;
+ }
+
+ #endregion
+
+ #region Fields
+
+ private readonly object _cacheLockHandle = new();
+
+ private byte[] _bytesCache;
+
+ private readonly bool _initWithImage;
+
+ internal const int WebpMaxDimension = 16383;
+
+ #endregion
+
+ #region Properties
+
+ private (IntPtr Pointer, int Size) DynamicArray { get; set; } = (IntPtr.Zero, 0);
+
+ private byte[] BytesCache {
+ get {
+ lock (_cacheLockHandle) {
+ if (_bytesCache is not null)
+ return _bytesCache;
+ if (DynamicArray.Pointer == IntPtr.Zero)
+ return null;
+ _bytesCache = new byte[DynamicArray.Size];
+ Marshal.Copy(DynamicArray.Pointer, _bytesCache, 0, DynamicArray.Size);
+ return _bytesCache;
+ }
+ }
+ set => _bytesCache = value;
+ }
+
+ private Image ImageCache { get; set; }
+
+ private WebPInfo? InfoCache { get; set; }
+
+ #endregion
+
+ #region Private methods
+
+ [method: MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void VerifyImage(Image image) {
+ switch (image) {
+ case null:
+ throw new ArgumentNullException(nameof(image));
+ case { Width: 0 } or { Height: 0 }:
+ throw new DataException("Image contains no data.");
+ case { Width: > WebpMaxDimension } or { Height: > WebpMaxDimension }:
+ throw new IOException($"Image too big. {WebpMaxDimension}x{WebpMaxDimension} is maximal size.");
+ }
+ }
+
+ [method: MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Bitmap ConvertTo32Argb(Image image) {
+ var bitmap = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb);
+ using var graphic = Graphics.FromImage(bitmap);
+ graphic.DrawImage(image, new Rectangle(0, 0, bitmap.Width, bitmap.Height));
+ return bitmap;
+ }
+
+ [method: MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static BitmapData GetBitmapData(Bitmap bitmap, ImageLockMode mode) {
+ return bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), mode, bitmap.PixelFormat);
+ }
+
+ [method: MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static WebPInfo GetInfoFrom(IntPtr pointer, int size) {
+ var features = new WebPBitstreamFeatures();
+ var status = Native.WebPGetFeatures(pointer, size, ref features);
+ if (status is not Vp8StatusCode.Ok)
+ throw new ExternalException(status.ToString());
+ return new WebPInfo(features);
+ }
+
+ [method: MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void StoreEncodedResult(Image image, bool lossy, float quality) {
+ VerifyImage(image);
+ using var bitmap = ConvertTo32Argb(image);
+ BitmapData data = null;
+ try {
+ data = GetBitmapData(bitmap, ImageLockMode.ReadOnly);
+ var size = lossy
+ ? Native.WebPEncodeBgra(data.Scan0, data.Width, data.Height, data.Stride, quality, out var ptr)
+ : Native.WebPEncodeLosslessBgra(data.Scan0, data.Width, data.Height, data.Stride, out ptr);
+ if (size is 0)
+ throw new IOException("Cannot encode image by unknown cause.");
+ DynamicArray = (ptr, size);
+ } finally {
+ if (data is not null) bitmap.UnlockBits(data);
+ }
+ }
+
+ private delegate int DecodeInto(IntPtr ptr, int size, IntPtr output, int outputSize, int outputStride);
+
+ [method: MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static Bitmap Decode(IntPtr pointer, int size) {
+ Bitmap bmp = null;
+ BitmapData data = null;
+ int length;
+
+ try {
+ var info = GetInfoFrom(pointer, size);
+ bmp = new Bitmap(info.Width, info.Height, info.HasAlpha
+ ? PixelFormat.Format32bppArgb
+ : PixelFormat.Format24bppRgb);
+ data = GetBitmapData(bmp, ImageLockMode.WriteOnly);
+ length = ((DecodeInto)(info.HasAlpha
+ ? Native.WebPDecodeBgraInto
+ : Native.WebPDecodeBgrInto))
+ .Invoke(pointer, size, data.Scan0, data.Stride * info.Height, data.Stride);
+ } finally {
+ if (data is not null) bmp.UnlockBits(data);
+ }
+
+ if (length is 0)
+ throw new IOException("Cannot decode image by unknown cause.");
+
+ return bmp;
+ }
+
+ #endregion
+
+ #region Public methods
+
+ public Image GetImage() {
+ if (DynamicArray.Pointer != IntPtr.Zero)
+ return ImageCache ??= Decode(DynamicArray.Pointer, DynamicArray.Size);
+
+ var cache = BytesCache;
+ var handle = GCHandle.Alloc(cache, GCHandleType.Pinned);
+ try {
+ return ImageCache ??= Decode(handle.AddrOfPinnedObject(), cache.Length);
+ } finally {
+ if (handle.IsAllocated) handle.Free();
+ }
+ }
+
+ public byte[] GetWebPLossy(float quality = 70, bool forceLossy = false) {
+ if (BytesCache is null)
+ StoreEncodedResult(ImageCache, true, quality);
+ else if (forceLossy)
+ StoreEncodedResult(GetImage(), true, quality);
+ return BytesCache;
+ }
+
+ public byte[] GetWebPLossless() {
+ if (BytesCache is null)
+ StoreEncodedResult(ImageCache, false, 0);
+ return BytesCache;
+ }
+
+ public WebPInfo GetInfo() {
+ if (InfoCache.HasValue)
+ return InfoCache.Value;
+ if (DynamicArray.Pointer != IntPtr.Zero)
+ return InfoCache ??= GetInfoFrom(DynamicArray.Pointer, DynamicArray.Size);
+
+ var cache = BytesCache;
+ var handle = GCHandle.Alloc(cache, GCHandleType.Pinned);
+ try {
+ return InfoCache ??= GetInfoFrom(handle.AddrOfPinnedObject(), cache.Length);
+ } finally {
+ if (handle.IsAllocated) handle.Free();
+ }
+ }
+
+ #endregion
+
+ #region Dtors
+
+ ~WebPObject() {
+ Dispose();
+ }
+
+ public void Dispose() {
+ if (!_initWithImage)
+ ImageCache?.Dispose();
+ if (DynamicArray.Pointer != IntPtr.Zero)
+ Native.WebPFree(DynamicArray.Pointer);
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+}