0

I am trying to move my code from using System.Drawing.Image to using SkiaSharp as recommended here.

I trying to find similar operations for working with Tif files from a stream.

Currently, the following is what I have using System.Drawing.Image:

System.Drawing.Image MyImage = System.Drawing.Image.FromStream(inStream);
PdfDocument doc = new PdfDocument();

for (int PageIndex = 0; PageIndex < MyImage.GetFrameCount(FrameDimension.Page); PageIndex++)
{
    MyImage.SelectActiveFrame(FrameDimension.Page, PageIndex);
    XImage img = XImage.FromGdiPlusImage(MyImage);
    var page = new PdfPage();

    page.Width = MyImage.Width;
    page.Height = MyImage.Height;
    doc.Pages.Add(page);
    XGraphics xgr = XGraphics.FromPdfPage(doc.Pages[PageIndex]);
    xgr.DrawImage(img, 0, 0, page.Width, page.Height);
}

doc.Save(outStream);
MyImage.Dispose();

My current work with SkiaSharp is the following though it does not work correctly. Note: The following code has the following error and I am trying to figure out why in addition to how to select active frames: Unhandled exception. System.ObjectDisposedException: Cannot access a closed Stream. likely due to SKCodec codec = SkiaSharp.SKCodec.Create(inStream); but I am not sure why.

PdfDocument doc = new PdfDocument();
doc.Info.CreationDate = new DateTime();

using MemoryStream inStream = new MemoryStream(data);
using var imgStream = new SKManagedStream(inStream, false);
using var skData = SKData.Create(imgStream);
using SKCodec codec = SKCodec.Create(skData);

// TODO: codec is null!

for (int PageIndex = 0; PageIndex < codec.FrameCount; PageIndex++)
{
    SKImageInfo imageInfo = new SKImageInfo(codec.Info.Width, codec.Info.Height);

    // create bitmap stub
    using SKBitmap skBitmap = new SKBitmap(imageInfo);
    IntPtr pixelsPtr = skBitmap.GetPixels();

    // decode particular frame into the bitmap
    codec.GetPixels(imageInfo, pixelsPtr, new SKCodecOptions(PageIndex));

    // encode bitmap back
    using SKData encodedData = skBitmap.Encode(SKEncodedImageFormat.Png, 100);
    using Stream frameStream = encodedData.AsStream();

    XImage img = XImage.FromStream(frameStream);

    var page = new PdfPage();
    doc.Pages.Add(page);
    XGraphics xgr = XGraphics.FromPdfPage(doc.Pages[PageIndex]);
    xgr.DrawImage(img, 0, 0, page.Width, page.Height);
}
doc.Save(destination);

These are other things I have tried:

  • Using SKCodecOptions to set the FrameIndex but how would I use it since it used for GetPixels() not Decode().
  • There are no properties, I found, that control FrameIndex within a SKBitmap.
  • The additional conversion from Image to XImage is a bit confusing as from the source here, it appears to be a constructor that creates an XImage straight from an Image. I would need to create an XImage from a SkiaSharp.SKBitmap instead I believe thought I'm not quite sure how to do this at the moment such as use an existing method like FromStream() though I don't know the differences between the uses necesarily.
usagibear
  • 303
  • 3
  • 12
  • Post the Skiasharp code you've tried so-far. Have you reviewed Skia's documentation? What _speciifc_ difficulties are you having? ...or are you just expecting us to do it all for you? – Dai Nov 18 '22 at 00:23
  • Appreciate the feedback Dai. I have posted my efforts with the conversion. – usagibear Nov 18 '22 at 21:40

1 Answers1

3

You get the exception because decoding SKBitmap with

SkiaSharp.SKBitmap MyImage = SkiaSharp.SKBitmap.Decode(inStream);

disposes the inStream.

Anyway, to get the particular frame using SkiaSharp, you need to get pixels from the codec:

using MemoryStream inStream = new MemoryStream(data);
PdfDocument doc = new PdfDocument();
doc.Info.CreationDate = new DateTime();

using SKCodec codec = SkiaSharp.SKCodec.Create(inStream);
for (int PageIndex = 0; PageIndex < codec.FrameCount; PageIndex++)
{
    SKImageInfo imageInfo = new SKImageInfo(codec.Info.Width, codec.Info.Height);

    // create bitmap stub
    using SKBitmap skBitmap = new SKBitmap(imageInfo);
    IntPtr pixelsPtr = skBitmap.GetPixels();

    // decode particular frame into the bitmap
    codec.GetPixels(imageInfo, pixelsPtr, new SKCodecOptions(PageIndex));

    // encode bitmap back
    using SKData encodedData = skBitmap.Encode(SKEncodedImageFormat.Png, 100);
    using Stream frameStream = encodedData.AsStream();

    XImage img = XImage.FromStream(frameStream);

    var page = new PdfPage();

    doc.Pages.Add(page);
    XGraphics xgr = XGraphics.FromPdfPage(doc.Pages[PageIndex]);
    xgr.DrawImage(img, 0, 0, page.Width, page.Height);
}
doc.Save(destination);
Maku
  • 1,464
  • 11
  • 20
  • Thanks for the help Maku! I have made edits to my skiasharp solution but keep getting the segfaults despite implementing a workaround from https://github.com/mono/SkiaSharp/issues/1551 to resolve the seg fault/dispose issue. It appears that for whatever reason, the SKCodec.Create returns `null` due to some error. I'm not sure why and have tried drilling down in my debug but cannot in vscode. The `false` value for the `SKManagedStream` object should prevent `inStream` from being disposed I believe. Do you have any ideas? – usagibear Nov 29 '22 at 23:22
  • if codec is null, this often indicates the data is not a supported image format. I tested my code with some animated gif and that worked as intended. Could you provide the image you work on and get the errors? Also bug from that issue you mentioned appeared in 2.88.4 preview and was fixed around september, so I wouldn't expect the workaround to help. – Maku Nov 30 '22 at 07:26
  • That was the exact issue it turns out. The data I was trying to use was TIFF and that is not supported by SkiaSharp. I'll look for another solution then. Appreciate the help Maku. Your solution handles supported formats. – usagibear Dec 01 '22 at 21:45