Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_space_around_binary_operators = before_and_after
csharp_style_namespace_declarations = file_scoped

[*.{csproj,vbproj,fsproj,proj,targets,props}]
indent_style = space
Expand Down
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@
"internalConsoleOptions": "openOnSessionStart",
"justMyCode": false
},
{
"name": "Eto.Test.GirCore",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-gircore",
"program": "${workspaceFolder}/artifacts/test/Eto.Test.GirCore/${config:var.configuration}/net10.0/Eto.Test.GirCore.dll",
"args": [],
"osx": {
"env": {
"DYLD_FALLBACK_LIBRARY_PATH": "/opt/homebrew/lib"
}
},
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart",
"justMyCode": false,
},
{
"name": "Eto.Test.Gtk",
"type": "coreclr",
Expand Down
10 changes: 10 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@
"clear": true
}
},
{
"label": "build-gircore",
"command": "dotnet build ${config:var.buildProperties} /p:Configuration=${config:var.configuration} ${workspaceFolder}/test/Eto.Test.GirCore/Eto.Test.GirCore.csproj",
"type": "shell",
"group": "build",
"problemMatcher": "$msCompile",
"presentation": {
"clear": true
}
},
{
"label": "build-gtk",
"command": "dotnet build ${config:var.buildProperties} /p:Configuration=${config:var.configuration} ${workspaceFolder}/test/Eto.Test.Gtk/Eto.Test.Gtk.csproj",
Expand Down
187 changes: 187 additions & 0 deletions src/Eto.GirCore/Drawing/BitmapHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
using Eto.Shared.Drawing;
using System.Runtime.InteropServices;

namespace Eto.GirCore.Drawing
{
public class BitmapDataHandler : BaseBitmapData
{
public BitmapDataHandler(Image image, IntPtr data, int scanWidth, int bitsPerPixel, object controlObject, bool premultipliedAlpha)
: base(image, data, scanWidth, bitsPerPixel, controlObject, premultipliedAlpha)
{
}

public override int TranslateArgbToData(int argb)
{
return unchecked((int)(((uint)argb & 0xFF00FF00) | (((uint)argb & 0xFF) << 16) | (((uint)argb & 0xFF0000) >> 16)));
}

public override int TranslateDataToArgb(int bitmapData)
{
return unchecked((int)(((uint)bitmapData & 0xFF00FF00) | (((uint)bitmapData & 0xFF) << 16) | (((uint)bitmapData & 0xFF0000) >> 16)));
}
}

public class BitmapHandler : WidgetHandler<GdkPixbuf.Pixbuf, Bitmap>, Bitmap.IHandler, IGirImage
{
readonly Dictionary<Size, GdkPixbuf.Pixbuf> sizes = new();
Gdk.Texture? texture;

public BitmapHandler()
{
}

public BitmapHandler(GdkPixbuf.Pixbuf pixbuf)
{
Control = pixbuf;
}

public GdkPixbuf.Pixbuf Pixbuf => Control;

public Gdk.Texture Texture => texture ??= Gdk.Texture.NewForPixbuf(Control);

public Size Size => new(Control.GetWidth(), Control.GetHeight());

public void Create(string fileName)
{
Control = GdkPixbuf.Pixbuf.NewFromFile(fileName);

Check warning on line 46 in src/Eto.GirCore/Drawing/BitmapHandler.cs

View workflow job for this annotation

GitHub Actions / build-mac

Possible null reference assignment.
ClearCache();
}

public void Create(Stream stream)
{
using var memory = new MemoryStream();
stream.CopyTo(memory);
using var bytes = GLib.Bytes.New(memory.ToArray());
using var input = Gio.MemoryInputStream.NewFromBytes(bytes);
Control = GdkPixbuf.Pixbuf.NewFromStream(input, null);
ClearCache();
}

public void Create(int width, int height, PixelFormat pixelFormat)
{
var hasAlpha = pixelFormat != PixelFormat.Format24bppRgb;
Control = CreatePixbuf(width, height, hasAlpha);
if (pixelFormat == PixelFormat.Format32bppRgb)
FillOpaqueAlpha(Control);
ClearCache();
}

public void Create(int width, int height, Graphics graphics)
{
Create(width, height, PixelFormat.Format32bppRgba);
}

public void Create(Image image, int width, int height, ImageInterpolation interpolation)
{
Control = image.ToPixbuf().ScaleSimple(width, height, interpolation.ToPixbuf());
ClearCache();
}

public BitmapData Lock()
{
return new BitmapDataHandler(Widget, Control.Pixels, Control.Rowstride, Control.HasAlpha ? 32 : 24, null, false);
}

public void Unlock(BitmapData bitmapData)
{
ClearCache();
}

string ToPixbufFormat(ImageFormat format) => format switch
{
ImageFormat.Jpeg => "jpeg",
ImageFormat.Bitmap => "bmp",
ImageFormat.Tiff => "tiff",
ImageFormat.Gif => "gif",
_ => "png"
};

public void Save(string fileName, ImageFormat format)
{
Control.Savev(fileName, ToPixbufFormat(format), Array.Empty<string>(), Array.Empty<string>());
}

public void Save(Stream stream, ImageFormat format)
{
var fileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + "." + ToPixbufFormat(format));
try
{
Save(fileName, format);
using var fileStream = File.OpenRead(fileName);
fileStream.CopyTo(stream);
}
finally
{
if (File.Exists(fileName))
File.Delete(fileName);
}
}

public Bitmap Clone(Rectangle? rectangle = null)
{
if (rectangle == null)
return new Bitmap(new BitmapHandler(Control.Copy()));

var rect = rectangle.Value;
var pixbuf = Control.NewSubpixbuf(rect.X, rect.Y, rect.Width, rect.Height).Copy();
return new Bitmap(new BitmapHandler(pixbuf));
}

public Color GetPixel(int x, int y)
{
using var data = Lock();
return data.GetPixel(x, y);
}

public void SetPixel(int x, int y, Color color)
{
using var data = Lock();
data.SetPixel(x, y, color);
Unlock(data);
}

public GdkPixbuf.Pixbuf GetPixbuf(Size? size = null, ImageInterpolation interpolation = ImageInterpolation.Default)
{
if (size == null || size == Size)
return Control;

var target = size.Value;
if (!sizes.TryGetValue(target, out var pixbuf))
{
pixbuf = Control.ScaleSimple(target.Width, target.Height, interpolation.ToPixbuf());
sizes[target] = pixbuf;
}
return pixbuf;
}

void ClearCache()
{
sizes.Clear();
texture = null;
}

static GdkPixbuf.Pixbuf CreatePixbuf(int width, int height, bool hasAlpha)
{
var channels = hasAlpha ? 4 : 3;
var rowstride = width * channels;
var data = new byte[rowstride * height];
using var bytes = GLib.Bytes.New(data);
return GdkPixbuf.Pixbuf.NewFromBytes(bytes, GdkPixbuf.Colorspace.Rgb, hasAlpha, 8, width, height, rowstride);
}

static void FillOpaqueAlpha(GdkPixbuf.Pixbuf pixbuf)
{
if (!pixbuf.HasAlpha)
return;

var rowstride = pixbuf.Rowstride;
var width = pixbuf.Width;
var height = pixbuf.Height;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
Marshal.WriteByte(pixbuf.Pixels, (y * rowstride) + (x * 4) + 3, 0xFF);
}
}
}
}
15 changes: 15 additions & 0 deletions src/Eto.GirCore/Drawing/IconFrameHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Eto.GirCore.Drawing
{
public class IconFrameHandler : IconFrame.IHandler
{
public object Create(IconFrame frame, Stream stream) => new Bitmap(stream);

public object Create(IconFrame frame, Func<Stream> load) => new Bitmap(load());

public object Create(IconFrame frame, Bitmap bitmap) => bitmap;

public Bitmap GetBitmap(IconFrame frame) => (Bitmap)frame.ControlObject;

public Size GetPixelSize(IconFrame frame) => GetBitmap(frame).Size;
}
}
108 changes: 108 additions & 0 deletions src/Eto.GirCore/Drawing/IconHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
namespace Eto.GirCore.Drawing
{
public class IconHandler : WidgetHandler<object, Icon>, Icon.IHandler, IGirImage
{
readonly Dictionary<Size, GdkPixbuf.Pixbuf> sizes = new();
List<IconFrame> frames = new();
Gdk.Texture? texture;

public IconHandler()
{
Control = new object();
}

public GdkPixbuf.Pixbuf Pixbuf => GetPixbuf();

public Gdk.Texture Texture => texture ??= Gdk.Texture.NewForPixbuf(Pixbuf);

public Size Size => frames.Count > 0 ? frames[0].Bitmap.Size : Size.Empty;

public IEnumerable<IconFrame> Frames => frames;

public void Create(string fileName)
{
using var fs = File.OpenRead(fileName);
Create(fs);
}

public void Create(Stream stream)
{
using var ms = new MemoryStream();
stream.CopyTo(ms);
ms.Position = 0;
CreateFrames(ms);
ClearCache();
}

const int IconDirSize = 6;
const int IconDirEntrySize = 16;

void CreateFrames(MemoryStream input)
{
var source = input.ToArray();
var count = BitConverter.ToInt16(source, 4);
var result = new List<IconFrame>();

for (var i = 0; i < count; i++)
{
using var destStream = new MemoryStream();
using var writer = new BinaryWriter(destStream);

var pos = 0;
writer.Write(source, pos, IconDirSize - 2);
writer.Write((short)1);

pos += IconDirSize + (IconDirEntrySize * i);
writer.Write(source, pos, IconDirEntrySize - 4);
writer.Write(IconDirSize + IconDirEntrySize);
pos += 8;

var imageSize = BitConverter.ToInt32(source, pos);
pos += 4;
var imageOffset = BitConverter.ToInt32(source, pos);
if (imageOffset + imageSize > source.Length)
throw new InvalidDataException("Icon is not a valid format.");

writer.Write(source, imageOffset, imageSize);
writer.Flush();
destStream.Position = 0;
result.Add(new IconFrame(1f, new Bitmap(destStream)));
}

if (result.Count == 0)
{
input.Position = 0;
result.Add(new IconFrame(1f, new Bitmap(input)));
}

frames = result;
}

public void Create(IEnumerable<IconFrame> frames)
{
this.frames = new List<IconFrame>(frames);
ClearCache();
}

public GdkPixbuf.Pixbuf GetPixbuf(Size? size = null, ImageInterpolation interpolation = ImageInterpolation.Default)
{
var frame = size == null ? Widget.GetFrame(1) : Widget.GetFrame(1, size);
var pixbuf = frame.Bitmap.ToPixbuf();
if (size == null || pixbuf.GetWidth() == size.Value.Width && pixbuf.GetHeight() == size.Value.Height)
return pixbuf;

if (!sizes.TryGetValue(size.Value, out var scaled))
{
scaled = pixbuf.ScaleSimple(size.Value.Width, size.Value.Height, interpolation.ToPixbuf());
sizes[size.Value] = scaled;

Check warning on line 97 in src/Eto.GirCore/Drawing/IconHandler.cs

View workflow job for this annotation

GitHub Actions / build-mac

Possible null reference assignment.
}
return scaled;

Check warning on line 99 in src/Eto.GirCore/Drawing/IconHandler.cs

View workflow job for this annotation

GitHub Actions / build-mac

Possible null reference return.
}

void ClearCache()
{
sizes.Clear();
texture = null;
}
}
}
Loading
Loading