Skip to content

Commit

Permalink
HEIF/AVIF Encoding & Deconding support (x64 only)
Browse files Browse the repository at this point in the history
  • Loading branch information
maforget committed Aug 1, 2024
1 parent 91fe4f9 commit 3e3192f
Show file tree
Hide file tree
Showing 12 changed files with 375 additions and 25 deletions.
30 changes: 30 additions & 0 deletions ComicRack.Engine/ComicRack.Engine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,34 @@
<ItemGroup>
<AppDesigner Include="Properties\" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="DLL\aom.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>Resources\aom.dll</TargetPath>
</ContentWithTargetPath>
<None Include="DLL\aom.dll" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="DLL\libde265.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>Resources\libde265.dll</TargetPath>
</ContentWithTargetPath>
<None Include="DLL\libde265.dll" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="DLL\libheif.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>Resources\libheif.dll</TargetPath>
</ContentWithTargetPath>
<None Include="DLL\libheif.dll" />
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="DLL\libx265.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>Resources\libx265.dll</TargetPath>
</ContentWithTargetPath>
<None Include="DLL\libx265.dll" />
</ItemGroup>
<ItemGroup>
<Compile Update="Controls\ComicPageContainerControl.cs">
<SubType>Component</SubType>
Expand All @@ -53,6 +81,8 @@
<PackageReference Include="bblanchon.PDFium.Win32">
<Version>128.0.6611</Version>
</PackageReference>
<PackageReference Include="LibHeifSharp" Version="3.2.0" />
<PackageReference Include="Microsoft.Bcl.Numerics" Version="8.0.0" />
<PackageReference Include="MySqlConnector">
<Version>2.3.7</Version>
</PackageReference>
Expand Down
Binary file added ComicRack.Engine/DLL/aom.dll
Binary file not shown.
Binary file added ComicRack.Engine/DLL/libde265.dll
Binary file not shown.
Binary file added ComicRack.Engine/DLL/libheif.dll
Binary file not shown.
Binary file added ComicRack.Engine/DLL/libx265.dll
Binary file not shown.
306 changes: 306 additions & 0 deletions ComicRack.Engine/IO/Provider/HeifAvifImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LibHeifSharp;
using cYo.Common.Drawing;
using cYo.Common.ComponentModel;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;

namespace cYo.Projects.ComicRack.Engine.IO.Provider
{
public static class HeifAvifImage
{
public static class NativeMethods
{
public enum heif_filetype_result
{
heif_filetype_no,
heif_filetype_yes_supported, // it is heif and can be read by libheif
heif_filetype_yes_unsupported, // it is heif, but cannot be read by libheif
heif_filetype_maybe // not sure whether it is an heif, try detection with more input data
};

[DllImport("libheif", CallingConvention = CallingConvention.Cdecl)]
public static extern heif_filetype_result heif_check_filetype(IntPtr data, int len);
}

public static byte[] ConvertToJpeg(byte[] data)
{
if (!IsSupported(data))
{
return data;
}
try
{
using (Bitmap image = DecodeFromBytes(data))
{
return image.ImageToJpegBytes();
}
}
catch (Exception)
{
return data;
}
}

public static byte[] ConvertToHeif(Bitmap bmp, int quality = 40, bool avif = false)
{
if (bmp == null || !Environment.Is64BitProcess)
{
return null;
}
Bitmap bitmap = null;
try
{
using (MemoryStream memoryStream = new MemoryStream())
{
bitmap = ((bmp.PixelFormat == PixelFormat.Format24bppRgb) ? bmp : bmp.CreateCopy(PixelFormat.Format24bppRgb));
Encode(bitmap, memoryStream, quality, avif);
return memoryStream.ToArray();
}
}
finally
{
if (bmp != bitmap)
{
bitmap.SafeDispose();
}
}
}

#region Checking file header for identification
private static bool IsSupported(byte[] data)
{
if (data.Length < 12 || !Environment.Is64BitProcess)
return false;

GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
IntPtr dataPtr = handle.AddrOfPinnedObject();

try
{
NativeMethods.heif_filetype_result result = NativeMethods.heif_check_filetype(dataPtr, data.Length);

if (result == NativeMethods.heif_filetype_result.heif_filetype_yes_supported)
return true;

return false;
}
finally
{
handle.Free();
}
}
#endregion

#region HEIF // AVIF Decoding
public static Bitmap DecodeFromBytes(byte[] data)
{
try
{
var decodingOptions = new HeifDecodingOptions
{
ConvertHdrToEightBit = true,
Strict = false,
DecoderId = null
};

using HeifContext context = new HeifContext(data);
using HeifImageHandle primaryImage = context.GetPrimaryImageHandle();

bool hasAlpha = primaryImage.HasAlphaChannel;
HeifChroma chroma = hasAlpha ? HeifChroma.InterleavedRgba32 : HeifChroma.InterleavedRgb24;

using HeifImage image = primaryImage.Decode(HeifColorspace.Rgb, chroma, decodingOptions);
return ConvertToBitmap(image);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
return null;
}

private static unsafe Bitmap ConvertToBitmap(HeifImage image)
{
Bitmap bmp = null;
BitmapData bmpData = null;

try
{
int width = image.Width;
int height = image.Height;

bool hasAlpha = image.HasAlphaChannel;
PixelFormat pixelFormat = PixelFormat.Format32bppArgb;
bmp = new Bitmap(width, height, pixelFormat);
bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat);

HeifPlaneData heifPlaneData = image.GetPlane(HeifChannel.Interleaved);
byte* srcScan0 = (byte*)heifPlaneData.Scan0;
byte* dstScan0 = (byte*)bmpData.Scan0;

if (image.IsPremultipliedAlpha)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int srcPos = y * heifPlaneData.Stride + x * 4;
int dstPos = y * bmpData.Stride + x * 4;

byte alpha = srcScan0[srcPos + 3];

if (alpha == 0)
{
dstScan0[dstPos + 0] = 0;
dstScan0[dstPos + 1] = 0;
dstScan0[dstPos + 2] = 0;
dstScan0[dstPos + 3] = 0;
}
else if (alpha == 0xff)
{
dstScan0[dstPos + 0] = srcScan0[srcPos + 2];//B
dstScan0[dstPos + 1] = srcScan0[srcPos + 1];//G
dstScan0[dstPos + 2] = srcScan0[srcPos + 0];//R
dstScan0[dstPos + 3] = 0xff;// srcScan0[pos + 0];//A
}
else
{
dstScan0[dstPos + 0] = (byte)Math.Min(MathF.Round(srcScan0[srcPos + 2] * 255f / alpha), 255);
dstScan0[dstPos + 1] = (byte)Math.Min(MathF.Round(srcScan0[srcPos + 1] * 255f / alpha), 255);
dstScan0[dstPos + 2] = (byte)Math.Min(MathF.Round(srcScan0[srcPos + 0] * 255f / alpha), 255);
dstScan0[dstPos + 3] = alpha;
}
}
}
}
else
{
CopyBits(width, height, heifPlaneData.Stride, bmpData.Stride, srcScan0, dstScan0, hasAlpha);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
if (bmp != null && bmpData != null)
{
bmp.UnlockBits(bmpData);
}
}
return bmp;
}
#endregion

#region HEIF // ABIF Encoding
public static void Encode(Bitmap bmp, MemoryStream stream, int quality, bool avif = false)
{
var format = avif ? HeifCompressionFormat.Av1 : HeifCompressionFormat.Hevc;

var encodingOptions = new HeifEncodingOptions
{
SaveAlphaChannel = true,
WriteTwoColorProfiles = false
};

using HeifContext context = new HeifContext();
HeifEncoderDescriptor encoderDescriptor = context.GetEncoderDescriptors(format).FirstOrDefault();
using HeifEncoder encoder = context.GetEncoder(encoderDescriptor);
encoder.SetLossyQuality(quality);

using HeifImage heifImage = CreateHeifImage(bmp);
context.EncodeImage(heifImage, encoder, encodingOptions);
context.WriteToStream(stream);
}

private static HeifImage CreateHeifImage(Bitmap image)
{
HeifColorspace colorspace = HeifColorspace.Rgb;

HeifImage heifImage = null;
HeifImage temp = null;

try
{
HeifChroma chroma = HeifChroma.InterleavedRgba32;//It seems all output are 32bit anyway, and the CopyRgba assumes destination is 32bit

temp = new HeifImage(image.Width, image.Height, colorspace, chroma);
temp.AddPlane(HeifChannel.Interleaved, image.Width, image.Height, 8);

CopyRgba(image, temp);

heifImage = temp;
temp = null;
}
finally
{
temp?.Dispose();
}

return heifImage;
}

private static unsafe void CopyRgba(Bitmap inputBitmap, HeifImage heifImage)
{
BitmapData bmpData = null;

try
{
int width = inputBitmap.Width;
int height = inputBitmap.Height;

bool inputHasAlpha = ((ImageFlags)inputBitmap.Flags & ImageFlags.HasAlpha) != 0;
PixelFormat pixelFormat = inputHasAlpha ? PixelFormat.Format32bppArgb : PixelFormat.Format24bppRgb;
bmpData = inputBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat);

HeifPlaneData heifPlaneData = heifImage.GetPlane(HeifChannel.Interleaved);
byte* srcScan0 = (byte*)bmpData.Scan0;
byte* dstScan0 = (byte*)heifPlaneData.Scan0;

CopyBits(width, height, bmpData.Stride, heifPlaneData.Stride, srcScan0, dstScan0, inputHasAlpha);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
if (inputBitmap != null && bmpData != null)
{
inputBitmap.UnlockBits(bmpData);
}
}
}
#endregion

#region Common
private static unsafe void CopyBits(int width, int height, int srcStride, int dstStride, byte* srcScan0, byte* dstScan0, bool inputHasAlpha)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int srcBitDepth = inputHasAlpha ? 4 : 3;//Input bit depth
int srcPos = y * srcStride + x * srcBitDepth;
int dstPos = y * dstStride + x * 4;//Change to 3 if output isnt 32bit

dstScan0[dstPos + 0] = srcScan0[srcPos + 2];//B
dstScan0[dstPos + 1] = srcScan0[srcPos + 1];//G
dstScan0[dstPos + 2] = srcScan0[srcPos + 0];//R
dstScan0[dstPos + 3] = inputHasAlpha ? srcScan0[srcPos + 3] : (byte)0xff;//A //Remove if output isn't 32bit
}
}
}
#endregion
}
}
3 changes: 2 additions & 1 deletion ComicRack.Engine/IO/Provider/ImageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,8 @@ private byte[] RetrieveSourceByteImage(int n, bool keepSourceFormat = false)
{
array = DjVuImage.ConvertToJpeg(array);
array = WebpImage.ConvertToJpeg(array);
}
array = HeifAvifImage.ConvertToJpeg(array);
}
return array;
}
catch (Exception)
Expand Down
8 changes: 6 additions & 2 deletions ComicRack.Engine/IO/Provider/Readers/ComicProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace cYo.Projects.ComicRack.Engine.IO.Provider.Readers
{
public abstract class ComicProvider : ImageProvider, IInfoStorage
{
private static readonly string[] supportedTypes = new string[11]
private static readonly string[] supportedTypes = new string[]
{
"jpg",
"jpeg",
Expand All @@ -20,7 +20,11 @@ public abstract class ComicProvider : ImageProvider, IInfoStorage
"tiff",
"bmp",
"djvu",
"webp"
"webp",
"heic",
"heif",
"avif",
//"jxl"
};

public bool UpdateEnabled => GetType().GetAttributes<FileFormatAttribute>().FirstOrDefault((FileFormatAttribute f) => f.Format.Supports(base.Source))?.EnableUpdate ?? false;
Expand Down
Loading

0 comments on commit 3e3192f

Please sign in to comment.