Begin work on Quik.Media.Stb

This commit is contained in:
H. Utku Maden 2023-07-09 17:46:35 +03:00
parent 648f44f12d
commit c3a815171b
7 changed files with 333 additions and 4 deletions

@ -0,0 +1,37 @@
using System;
using System.IO;
using static StbTrueTypeSharp.StbTrueType;
using Quik.Media.Color;
namespace Quik.Media.Stb
{
public class QFontStbtt : QFont
{
public override FontInfo Info => throw new NotImplementedException();
public QFontStbtt(Stream source)
{
}
public override QGlyphMetrics[] GetMetricsForPage(int codepage)
{
throw new NotImplementedException();
}
public override QGlyphMetrics GetMetricsForRune(int rune)
{
throw new NotImplementedException();
}
public override bool HasRune(int rune)
{
throw new NotImplementedException();
}
public override QImage RenderPage(int codepage, float resolution, bool sdf)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1,93 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Quik.Media;
using Quik.Media.Color;
using Quik.Stb;
namespace Quik.Media.Stb
{
public unsafe class QImageStbi : QImage
{
private readonly StbImage image;
private ImageBuffer buffer;
public override int Width => image.Width;
public override int Height => image.Height;
public override int Depth => 1;
public override QImageFormat InternalFormat => Stb2QImageFormat(image.Format);
public QImageStbi(Stream source)
{
// According to the stbi documentation, only a specific type of PNG
// files are premultiplied out of the box (iPhone PNG). Take the
// precision loss L and move on.
StbImage.FlipVerticallyOnLoad = true;
StbImage.UnpremultiplyOnLoad = true;
image = StbImage.Load(source);
}
public static QImageFormat Stb2QImageFormat(StbiImageFormat src)
{
switch (src)
{
case StbiImageFormat.Grey: return QImageFormat.RedU8;
case StbiImageFormat.Rgb: return QImageFormat.RgbU8;
case StbiImageFormat.Rgba: return QImageFormat.RgbaU8;
case StbiImageFormat.GreyAlpha: return QImageFormat.RaU8;
default: return QImageFormat.Undefined;
}
}
public override void LockBits2d(out QImageLock imageLock, QImageLockOptions options)
{
if (options.MipLevel > 0) throw new Exception("This image has no mip levels.");
buffer?.Dispose();
buffer = new ImageBuffer(options.Format, Width, Height);
QImageLock dst = buffer.Lock();
byte *srcPtr = (byte*)image.ImagePointer;
QImageLock src = new QImageLock(InternalFormat, Width, Height, 1, (IntPtr)srcPtr);
FormatConvert.Convert(dst, src);
if (options.Premultiply)
{
FormatConvert.Premultiply(dst);
}
imageLock = dst;
}
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options)
{
LockBits2d(out imageLock, options);
}
public override void LockBits3d(out QImageLock imageLock, QImageLockOptions options, int depth)
{
if (depth != 1) throw new ArgumentOutOfRangeException(nameof(depth));
LockBits2d(out imageLock, options);
}
public override void UnlockBits()
{
buffer.Unlock();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
buffer?.Dispose();
image.Dispose();
}
}
}
}

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LanguageVersion>7.3</LanguageVersion>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StbImageSharp" Version="2.27.13" />
<PackageReference Include="StbTrueTypeSharp" Version="1.26.11" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Quik\Quik.csproj" />
<ProjectReference Include="..\Quik.StbImage\Quik.StbImage.csproj" />
<ProjectReference Include="..\Quik.StbTrueType\Quik.StbTrueType.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,152 @@
using System;
using System.Buffers;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using Quik.Media;
namespace Quik.Media.Stb
{
public class StbMediaLoader : MediaLoader<string>, MediaLoader<Uri>, MediaLoader<FileInfo>, MediaLoader<FontInfo>
{
public bool AllowRemoteTransfers { get; set; } = false;
private readonly ArrayPool<byte> ByteArrays = ArrayPool<byte>.Create();
public IDisposable GetMedia(object key, MediaHint hint)
{
Type t = key.GetType();
/**/ if (t == typeof(string))
{
return GetMedia((string)key, hint);
}
else if (t == typeof(Uri))
{
return GetMedia((Uri)key, hint);
}
else if (t == typeof(FileInfo))
{
return GetMedia((FileInfo)key, hint);
}
else if (t == typeof(FontInfo))
{
return GetMedia((FontInfo)key, hint);
}
else
{
return null;
}
}
public IDisposable GetMedia(Uri uri, MediaHint hint)
{
throw new NotImplementedException();
}
public IDisposable GetMedia(string str, MediaHint hint)
{
throw new NotImplementedException();
}
public IDisposable GetMedia(FileInfo file, MediaHint hint)
{
throw new NotImplementedException();
}
public IDisposable GetMedia(FontInfo key, MediaHint hint)
{
throw new NotImplementedException();
}
public Stream OpenResource(FileInfo file)
{
if (file.Exists)
{
return file.Open(FileMode.Open);
}
else
{
return null;
}
}
public Stream OpenResource(Uri uri)
{
switch (uri.Scheme)
{
case "http":
case "https":
if (!AllowRemoteTransfers) return null;
try
{
WebRequest request = HttpWebRequest.Create(uri);
WebResponse response = request.GetResponse();
MemoryStream stream = new MemoryStream();
response.GetResponseStream().CopyTo(stream);
response.Close();
stream.Position = 0;
return stream;
}
catch
{
return null;
}
case "file":
return OpenResource(new FileInfo(uri.AbsolutePath));
default:
return null;
}
}
public Stream OpenResource(string key)
{
if (File.Exists(key))
{
return File.Open(key, FileMode.Open);
}
else if (Uri.TryCreate(key, UriKind.RelativeOrAbsolute, out Uri uri))
{
return OpenResource(uri);
}
else
{
return null;
}
}
MediaHint InferMedia(Stream str, MediaHint hint)
{
if (hint != MediaHint.None)
{
return hint;
}
byte[] array = ByteArrays.Rent(4);
str.Read(array, 0, 4);
str.Position = 0;
foreach (var(type, seq) in MediaTypes)
{
if (seq.SequenceEqual(array))
return hint;
}
return MediaHint.None;
}
private readonly (MediaHint, byte[])[] MediaTypes = new (MediaHint, byte[])[] {
(MediaHint.Image, new byte[] { 0x42, 0x4d }), /* .bmp `BM` */
(MediaHint.Image, new byte[] { 0x47, 0x49, 0x46, 0x38 }), /* .gif `GIF8` */
(MediaHint.Image, new byte[] { 0xff, 0xd8, 0xff, 0xe0 }), /* .jpg (JFIF) */
(MediaHint.Image, new byte[] { 0xff, 0xd8, 0xff, 0xe1 }), /* .jpg (EXIF) */
(MediaHint.Image, new byte[] { 0x89, 0x50, 0x4e, 0x47 }), /* .png `.PNG `*/
(MediaHint.Image, new byte[] { 0x4d, 0x4d, 0x00, 0x2a }), /* .tif (motorola) */
(MediaHint.Image, new byte[] { 0x49, 0x49, 0x2a, 0x00 }), /* .tif (intel) */
(MediaHint.Font, new byte[] { 0x00, 0x01, 0x00, 0x00 }), /* .ttf */
(MediaHint.Font, new byte[] { 0x4F, 0x54, 0x54, 0x4F }), /* .otf */
};
}
}

@ -118,9 +118,9 @@ namespace Quik.Stb
/// <param name="stream">The stream to load from.</param> /// <param name="stream">The stream to load from.</param>
/// <param name="format">The desired image format.</param> /// <param name="format">The desired image format.</param>
/// <returns>The image object.</returns> /// <returns>The image object.</returns>
public static StbImage Load(Stream stream, StbiImageFormat format = StbiImageFormat.Default) public static StbImage Load(Stream stream, StbiImageFormat format = StbiImageFormat.Default, bool isFloat = false)
{ {
if (TryLoad(out StbImage image, stream, format)) if (TryLoad(out StbImage image, stream, format, isFloat))
{ {
return image; return image;
} }
@ -128,5 +128,17 @@ namespace Quik.Stb
string reason = Marshal.PtrToStringUTF8((IntPtr)Stbi.failure_reason()); string reason = Marshal.PtrToStringUTF8((IntPtr)Stbi.failure_reason());
throw new Exception($"Failed to load image: {reason}"); throw new Exception($"Failed to load image: {reason}");
} }
public bool IsLoadable(Stream stream)
{
int x, y, iFormat;
StbiStreamWrapper wrapper = new StbiStreamWrapper(stream, true);
wrapper.CreateCallbacks(out stbi_io_callbacks cb);
stream.Position = 0;
int result = Stbi.info_from_callbacks(&cb, null, &x, &y, &iFormat);
return result != 0;
}
} }
} }

@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{5E87AF9C
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quik.StbImage.Tests", "tests\Quik.StbImage.Tests\Quik.StbImage.Tests.csproj", "{AFF181CF-D51E-4E16-B3C6-38ED1E1FF615}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quik.StbImage.Tests", "tests\Quik.StbImage.Tests\Quik.StbImage.Tests.csproj", "{AFF181CF-D51E-4E16-B3C6-38ED1E1FF615}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quik.Media.Stb", "Quik.Media.Stb\Quik.Media.Stb.csproj", "{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -83,6 +85,18 @@ Global
{AFF181CF-D51E-4E16-B3C6-38ED1E1FF615}.Release|x64.Build.0 = Release|Any CPU {AFF181CF-D51E-4E16-B3C6-38ED1E1FF615}.Release|x64.Build.0 = Release|Any CPU
{AFF181CF-D51E-4E16-B3C6-38ED1E1FF615}.Release|x86.ActiveCfg = Release|Any CPU {AFF181CF-D51E-4E16-B3C6-38ED1E1FF615}.Release|x86.ActiveCfg = Release|Any CPU
{AFF181CF-D51E-4E16-B3C6-38ED1E1FF615}.Release|x86.Build.0 = Release|Any CPU {AFF181CF-D51E-4E16-B3C6-38ED1E1FF615}.Release|x86.Build.0 = Release|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Debug|x64.ActiveCfg = Debug|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Debug|x64.Build.0 = Debug|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Debug|x86.ActiveCfg = Debug|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Debug|x86.Build.0 = Debug|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Release|Any CPU.Build.0 = Release|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Release|x64.ActiveCfg = Release|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Release|x64.Build.0 = Release|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Release|x86.ActiveCfg = Release|Any CPU
{3D354BE0-42A7-45C4-AAEA-B0F8963A5745}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{AFF181CF-D51E-4E16-B3C6-38ED1E1FF615} = {5E87AF9C-AC12-4E48-99B1-CBEC0C97B624} {AFF181CF-D51E-4E16-B3C6-38ED1E1FF615} = {5E87AF9C-AC12-4E48-99B1-CBEC0C97B624}

@ -12,8 +12,8 @@ namespace Quik.StbImage.Tests
[TestMethod("Set Global Options")] [TestMethod("Set Global Options")]
public void SetGlobals() public void SetGlobals()
{ {
Stbi.set_flip_vertically_on_load(1); Quik.Stb.StbImage.FlipVerticallyOnLoad = true;
Stbi.set_unpremultiply_on_load(1); Quik.Stb.StbImage.UnpremultiplyOnLoad = true;
} }
private StbiStreamWrapper PrepareImage(string path, out stbi_io_callbacks cb) private StbiStreamWrapper PrepareImage(string path, out stbi_io_callbacks cb)