Skip to content

Commit

Permalink
Improvements over Image, Bitmap and Icon classes (#9)
Browse files Browse the repository at this point in the history
* fix test name

* redefine Image class based on SKBitmap and add missing props/methods; Image class will be the base class of Bitmap class

* make Bitmap inherit from Image class and add missing props/methods

* init HorizontalResolution/VerticalResolution props with default resolution

* add missing props/methods in Icon class according to new defintiion of Bitmap class

* take the largest icon by default (and not the lowest) as System.Drawing does; fix constructor by copy

* add test for Bitmap.MakeTransparent method

* fix Icon.ExtractAssociatedIcon to take the largest icon

* fix ExtractIcon to consider size prior to index

* add ExtractIcon test cases

* implement Image.GetBounds, add GraphicsUnit enum

* fix typo in Color.Transparent

* add ImageLockMode enum

* add PixelFormat enum; implement PixelFormat in Bitmap/Image classes

* add RotateFlipType enum, implement Image.RotateFlip method

* remove unused parameter from ToPixelFormat

* add ImageFlags enum, add Image.Flags basic implementation

* minor fix on PixelFormat property's summary

* mark Bitmap.SetResolution as not supported

* add Image.GetThumbnailImage

* add ColorPalette class, implement Image.Palette property

* minor improvement

* add image test cases

* fix Dispose pattern

* make Bitmap/Icon sealed

* Fix typo.

* Fix summary open tag declaration

* Rename properties to match naming convention.

* define Image as abstract

* remove Bitmap destructor (aleady defined in Image class)

* remove CreateInstance method (just use Bitmap constructor)

* add using directive for temp Image instances

* simplify SKRect to SKRectI

* Define GetResourceStream in Image class (instead of Bitmap class)

* fix Bitmap(Type,string) description

* define Svg class, remove SKBitmap references from Image class and use them on Bitmap class

* define SV as sealed

* Rename m_Image to InnerImage for property naming consistency.

---------

Co-authored-by: Fede Azzato <fedeazzato@gmail.com>
  • Loading branch information
damiansalvia and fedeazzato authored Jul 1, 2024
1 parent 2deea97 commit 065e57b
Show file tree
Hide file tree
Showing 13 changed files with 1,421 additions and 123 deletions.
314 changes: 264 additions & 50 deletions src/Common/Bitmap.cs

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions src/Common/GraphicsUnit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace GeneXus.Drawing;

/// <summary>
/// Specifies the unit of measure for the given data.
/// </summary>
public enum GraphicsUnit
{
/// <summary>
/// Specifies the world unit as the unit of measure.
/// </summary>
World = 0,

/// <summary>
/// Specifies 1/75 inch as the unit of measure.
/// </summary>
Display = 1,

/// <summary>
/// Specifies a device pixel as the unit of measure.
/// </summary>
Pixel = 2,

/// <summary>
/// Specifies a printer's point (1/72 inch) as the unit of measure.
/// </summary>
Point = 3,

/// <summary>
/// Specifies the inch as the unit of measure.
/// </summary>
Inch = 4,

/// <summary>
/// Specifies the document unit (1/300 inch) as the unit of measure.
/// </summary>
Document = 5,

/// <summary>
/// Specifies the millimeter as the unit of measure.
/// </summary>
Millimeter = 6
}
98 changes: 81 additions & 17 deletions src/Common/Icon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using GeneXus.Drawing.Imaging;
using SkiaSharp;

namespace GeneXus.Drawing;

[Serializable]
public class Icon : IDisposable, ICloneable
public sealed class Icon : IDisposable, ICloneable
{
internal readonly Bitmap m_bitmap;
internal readonly List<IconEntry> m_entries;
internal int m_index { get; private set; } = 0;
internal int EntryIndex { get; private set; }

private Icon(Bitmap bitmap, List<IconEntry> entries, float width, float height)
{
Expand All @@ -30,21 +31,21 @@ private Icon(Bitmap bitmap, List<IconEntry> entries, float width, float height)
if (diff >= pivot)
continue;
pivot = diff;
m_index = i;
EntryIndex = i;
}
}

/// <summary>
/// Initializes a new instance of the <see cref='Icon'/> class from a <see cref='Bitmap'/> instance.
/// </summary>
public Icon(Bitmap bitmap)
: this(bitmap, new List<IconEntry>().Append(new IconEntry { Width = (byte)bitmap.Width, Height = (byte)bitmap.Height }).ToList(), int.MinValue, int.MinValue) { }
: this(bitmap, new() { new IconEntry { Width = (byte)bitmap.Width, Height = (byte)bitmap.Height } }, -1, -1) { }

/// <summary>
/// Initializes a new instance of the <see cref='Icon'/> class from the specified file name.
/// </summary>
public Icon(string filename)
: this(filename, int.MinValue, int.MinValue) { }
: this(filename, byte.MaxValue, byte.MaxValue) { }

/// <summary>
/// Initializes a new instance of the <see cref='Icon'/> class of the specified <see cref='Size'/> from the specified file.
Expand All @@ -62,7 +63,7 @@ public Icon(string filename, float width, float height)
/// Initializes a new instance of the <see cref='Icon'/> class from the specified data stream.
/// </summary>
public Icon(Stream stream)
: this(stream, int.MinValue, int.MinValue) { }
: this(stream, byte.MaxValue, byte.MaxValue) { }

/// <summary>
/// Initializes a new instance of the <see cref='Icon'/> class of the specified size from the specified stream.
Expand All @@ -80,7 +81,7 @@ public Icon(Stream stream, float width, float height)
/// Initializes a new instance of the <see cref='Icon'/> class by copy.
/// </summary>
public Icon(Icon original)
: this(original, int.MinValue, int.MinValue) { }
: this(original, original.Width, original.Height) { }

/// <summary>
/// Initializes a new instance of the <see cref='Icon'/> class by copy and attempts to find a version of the
Expand All @@ -99,7 +100,7 @@ public Icon(Icon original, float width, float height)
/// <summary>
/// Cleans up resources for this <see cref='Icon'/>.
/// </summary>
~Icon() => Dispose();
~Icon() => Dispose(false);

/// <summary>
/// Creates a human-readable string that represents this <see cref='Icon'/>.
Expand All @@ -112,7 +113,13 @@ public Icon(Icon original, float width, float height)
/// <summary>
/// Cleans up resources for this <see cref='Icon'/>.
/// </summary>
public void Dispose() => m_bitmap.Dispose();
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}

private void Dispose(bool disposing) => m_bitmap.Dispose();

#endregion

Expand All @@ -134,34 +141,91 @@ public object Clone()
#region Properties

/// <summary>
/// Gets the width of this <see cref='Icon'/>.
/// Gets the Windows handle for this <see cref='Icon'/>. This is not a copy of the handle; do not free it.
/// </summary>
public int Width => m_entries[m_index].Width;
public IntPtr Handle => m_bitmap.Handle;

/// <summary>
/// Gets the height of this <see cref='Icon'/>.
/// </summary>
public int Height => m_entries[m_index].Height;
public int Height => m_entries[EntryIndex].Height;

/// <summary>
/// Gets the size of this <see cref='Icon'/>.
/// </summary>
public Size Size => new(Width, Height);

/// <summary>
/// Gets the width of this <see cref='Icon'/>.
/// </summary>
public int Width => m_entries[EntryIndex].Width;

#endregion


#region Factory

/// <summary>
/// Creates a GDI+ <see cref='Icon'/> from the specified Windows handle to an icon (HICON).
/// </summary>
public static Icon FromHandle(IntPtr handle)
{
var info = new SKImageInfo(100, 100);
var skBitmap = new SKBitmap();
skBitmap.InstallPixels(info, handle, info.RowBytes);
var bitmap = new Bitmap(skBitmap, ImageFormat.Ico);
return new Icon(bitmap);
}

#endregion


#region Methods

/// <summary>
/// Converts this <see cref='Icon'/> to a <see cref='Bitmap'/>.
/// Returns an icon representation of an image that is contained in the specified file.
/// </summary>
public static Icon ExtractAssociatedIcon(string filePath)
=> ExtractIcon(filePath, -1); // NOTE: https://stackoverflow.com/a/37419253

/// <summary>
/// Extracts a specified icon from the given <paramref name="filePath"/>.
/// </summary>
public Bitmap ToBitmap() => new(m_Resized);
public static Icon ExtractIcon(string filePath, int id, bool smallIcon = false)
=> ExtractIcon(filePath, id, smallIcon ? 8 : ushort.MaxValue);

/// <summary>
/// Extracts a specified icon from the given <paramref name="filePath"/> and specified size.
/// </summary>
public static Icon ExtractIcon(string filePath, int id, int size)
{
if (size is <= 0 or > ushort.MaxValue)
throw new ArgumentOutOfRangeException(nameof(size));

if (new[] { ".dll", ".exe" }.Contains(Path.GetExtension(filePath)))
throw new ArgumentException("portable executable (PE) file format is not supported.", nameof(filePath));

var icon = new Icon(filePath, size, size);
if (icon.Width == size && icon.Height == size)
id = int.MaxValue; // set to undefined

if (id >= 0 && id < icon.m_entries.Count)
icon.EntryIndex = id; // set defined index

return icon;
}

/// <summary>
/// Saves this <see cref='Icon'/> to the specified output <see cref='Stream'/>.
/// </summary>
public void Save(Stream stream) => new Bitmap(m_Resized).Save(stream, SKEncodedImageFormat.Png, 100);
public void Save(Stream stream)
=> ToBitmap().Save(stream, ImageFormat.Png, 100);

/// <summary>
/// Converts this <see cref='Icon'/> to a <see cref='Bitmap'/>.
/// </summary>
public Bitmap ToBitmap()
=> new(Resized, ImageFormat.Png);

#endregion

Expand Down Expand Up @@ -214,11 +278,11 @@ private static List<IconEntry> ReadIco(Stream stream)
return entries;
}

private SKBitmap m_Resized
private SKBitmap Resized
{
get
{
var original = m_bitmap.Clone() as Bitmap ?? throw new Exception("Cloning bitmap failed");
var original = m_bitmap.Clone() as Bitmap ?? throw new Exception("cloning bitmap failed.");
var resized = new SKBitmap(Width, Height);
original.m_bitmap.ScalePixels(resized, SKFilterQuality.High);
return resized;
Expand Down
Loading

0 comments on commit 065e57b

Please sign in to comment.