Extracting image subset (cropping) with SkiaSharp - c#

I am trying to re-purpose an example from GitHub that deals with cropping an image with SkiaSharp. Specifically, I have a 4096x4096 sprite sheet from which I'd like to extract a sub-image (a specific sprite, if you will). To do that, I use the following snippet (where spriteContent is a byte array of the PNG image - byte[]):
var gch = GCHandle.Alloc(spriteContent, GCHandleType.Pinned);
try
{
var addr = gch.AddrOfPinnedObject();
using var pixmap = new SkiaSharp.SKPixmap(info, addr);
SkiaSharp.SKRectI rectI = new SkiaSharp.SKRectI(0, 0, 256, 256);
var subset = pixmap.ExtractSubset(rectI);
using var data = subset.Encode(SkiaSharp.SKPngEncoderOptions.Default)
File.WriteAllBytes("test2.png", data.ToArray());
}
finally
{
gch.Free();
}
The output of this code is, however, this kind of image:
Seems like an odd output. I suspect that I am doing something funky with teh SKRectI declaration, where the true rectangle is never used. What I understand it to be doing is create a rectangle from point 0 on top, 0 on bottom, 256 pixels tall, 256 pixels wide (i.e., manage the selection). If I adjust this to, let's say:
SkiaSharp.SKRectI rectI = new SkiaSharp.SKRectI(256, 256, 256, 256);
I get a NullReferenceException and there is nothing in the subset, so I must be misinterpreting how the rectangle selector works.
Any thoughts on what I might be doing wrong here?

The noise you get is caused by the fact, that you reinterpret raw encoded png bytes as pixel data. You need to decode the image first:
using var skBitmap = SKBitmap.Decode(spriteContent);
using var pixmap = new SKPixmap(skBitmap.Info, skBitmap.GetPixels());
SkiaSharp.SKRectI rectI = new SkiaSharp.SKRectI(0, 0, 256, 256);
var subset = pixmap.ExtractSubset(rectI);
using var data = subset.Encode(SkiaSharp.SKPngEncoderOptions.Default);
File.WriteAllBytes(#"test2.png", data.ToArray());

Related

Direct2D (SharpDX) block compressed bitmaps not compressed in memory

I have a few (8-10) large texture atlases (around 8192x8192 saved as BC1 DDS) but they are rather small disk space wise, only 32MiB.
The problem is, that when I load those files using WIC, they lose their block compression and take up to 256MiB of RAM, which shouldn't happen according to the sources
Here is my C# SharpDX code which loads the file:
public Bitmap LoadBitmap(string path) {
Bitmap m;
using (BitmapDecoder d = new BitmapDecoder(imagingFactory, $"plugins/HDPatch/{path}", Guid.Empty, SharpDX.IO.NativeFileAccess.Read, DecodeOptions.CacheOnDemand)) {
DdsDecoder decoder = d.QueryInterface<DdsDecoder>();
decoder.GetFrame(0, 0, 0, out BitmapFrameDecode frame);
m = SharpDX.Direct2D1.Bitmap.FromWicBitmap(_target, frame);//_target.CreateBitmap(converter, new BitmapProperties());
decoder.Dispose();
frame.Dispose();
}
return m;
}
Do I need to change something, or should I use a different method of loading them than WIC?
I looked at the DdsFrameDecode.CopyBlocks (+ ID2D1Bitmap::CopyFromMemory) method but I do not know how that works.. (What exactly is my stride and where do I get that information from)
EDIT:
So the BitmapFrameDecode gives me a PixelFormat of 32bppPBGRA
If I query a DDSFrameDecode from that BitmapFrameDecode, I get a format of BC1_UNorm from the DdsFrameDecode. The DdsDecoder also reports a texture with DdsAlphaModePremultiplied, and a DxgiFormat of BC1_UNorm.
But when I force the pixel format of BC1_UNorm in the BitmapProperties I get the D2D DEBUG ERROR: "The pixel format passed to this API is not compatible with the pixel format of the IWICBitmapSource"
EDIT2:
I figured out how to use CopyBlocks
SharpDX.DataStream stream = new
SharpDX.DataStream(test2.FormatInfo.BytesPerBlock * test2.SizeInBlocks.Width * test2.SizeInBlocks.Height, true, true);
test2.CopyBlocks(null, test2.FormatInfo.BytesPerBlock * test2.SizeInBlocks.Width, stream);
m = new Bitmap(_target, new SharpDX.Size2(decoder.Parameters.Width, decoder.Parameters.Height), stream, test2.FormatInfo.BytesPerBlock * test2.FormatInfo.BlockWidth, new BitmapProperties(new PixelFormat(SharpDX.DXGI.Format.BC1_UNorm, AlphaMode.Premultiplied)));
But the problem still exists. It can successfully create the Bitmap but it still consumes to much memory (250MiB+ instead of the actual block compressed size of 32MiB)
Does it maybe have to do with the render target of?
I use a software B8G8R8A8_UNorm DeviceContext render target
EDIT3:
Example project: https://drive.google.com/file/d/1hCgQnEV9xcTevgdswAiPJNREk1oDgqDC/view?usp=sharing

Image after resize has a discolored line on the left side

I'm trying to resize images into thumbnails in a .NET Core C# application using SixLabors.ImageSharp (version 1.0.0-beta0007). I've noticed that for only certain images, the resized image has a white, red, or blue distorted border, like so:
My code for generating the thumbnail is as follows:
using (var imageToResize = Image.Load(inStream, out IImageFormat imageFormat))
{
var size = GetThumbnailSize(imageToResize.Size()); //max size 150,preserves aspect-ratio
imageToResize.Mutate(x => x.Resize(new ResizeOptions()
{
Size = size,
Mode = ResizeMode.Crop
}));
using (var memorystream = new MemoryStream())
{
imageToResize.Save(memorystream , imageFormat);
ms.Position = 0;
outputStream.UploadFromStreamAsync(memorystream);
}
}
These two images were captured from the same device, and both have the same dimension (3024x4032) and these are the only similarities I could notice as I am a novice to image processing. I've also played around with the resize modes and different Resamplers, but could not resolve this.
What is causing this issue? Is there any way to fix this by using the SixLabors.ImageSharp library?
The issue was resolved after I modified the code to load the ImageSharp Image from a byte array instead of a stream. As per #JamesSouth's comment on the question above, it might have been my input stream not providing the all the bytes.
Below is the updated code:
// convert inStream to a byte array "bytes"
using (var imageToResize = Image.Load(bytes, out IImageFormat imageFormat))
{
Size newSize = GetThumbnailSize(imageToResize.Size()); //max size 150, preserves aspect-ratio
imageToResize.Mutate(x => x.Resize(newSize));
...
}

Infrared stream from kinect (v1) c #

This is the kinect for xbox 360, using the kinect.dll library.
No problem with the RgbResolution1280x960Fps12 or RgbResolution640x480Fps30 color stream.
The problem occurs with the infrared stream (InfraredResolution640x480Fps30)
Below is the code used and the resulting image.
using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
{
byte[] colorData = null;
if (colorFrame == null) return;
if (colorData == null)
colorData = new byte[colorFrame.PixelDataLength];
colorFrame.CopyPixelDataTo(colorData);
Marshal.FreeHGlobal(colorPtr);
colorPtr = Marshal.AllocHGlobal(colorData.Length);
Marshal.Copy(colorData, 0, colorPtr, colorData.Length);
if (ir) //ir is true if there is infrared stream
{
kinectVideoBitmap = new Bitmap(
colorFrame.Width / 2, //stream in 16 bit
colorFrame.Height,
colorFrame.Width * colorFrame.BytesPerPixel,
System.Drawing.Imaging.PixelFormat.Format32bppRgb, //probable error
colorPtr);
}
else
{
kinectVideoBitmap = new Bitmap(
colorFrame.Width,
colorFrame.Height,
colorFrame.Width * colorFrame.BytesPerPixel,
System.Drawing.Imaging.PixelFormat.Format32bppRgb,
colorPtr);
}
pic.Image = kinectVideoBitmap; //pic is picturebox
byte[] pixelData = new byte[colorFrame.PixelDataLength];
colorFrame.CopyPixelDataTo(pixelData);
An error could be the handling of the PixelFormat, indicated in the code, but I don't think it depends only on that. Below are the image derived from my code, and the one that generates the example app "kinect explorer".
(both images are screenshots)
This is the image of the Kinect explorer example app, you can see how well defined it is in quality and noise, more importantly, even distant objects are well seen.
This is the image of my app, in addition to the various colors deriving from the wrong pixelformat, the noise is such that you cannot see any of the distant objects.
Do you have ideas on how to fix and get a clean image?
I thank everyone in advance.

Got Reversed Image from Byte Array when converting to Base64

I need to get the raw bitmap data only (no header, or other information). I used the following code to get the bitmap data:
using (Bitmap bitmap = svgDocument.Draw())
{
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
var length = Math.Abs(bitmapData.Stride) * bitmapData.Height;
byte[] bytes = new byte[length];
Marshal.Copy(bitmapData.Scan0, bytes, 0, length);
bitmap.UnlockBits(bitmapData);
MemoryStream memoryStream = new MemoryStream();
string filename = DateTime.Now.Ticks.ToString() + ".bmp"; // this works fine
bitmap.Save(filename, ImageFormat.Bmp);
string base64 = Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks); // the base64 is reversed.
}
When I save the bitmap, everything looks fine. The image is not reversed. However when I use the bytes only to convert the data to Base64, then the image is reversed.
Edit 1:
I think this has nothing to do with the Base64 conversion. It seems that the bytes are in reversed order.
When I save the image using the code, the image looks like this:
When I use the bytes, then I see this:
Solution:
I found the solution. Instead of creating a new bitmap, I just skipped the first 54 bytes of header information and then stored the byte array.
MemoryStream memoryStream = new MemoryStream();
bitmap.Save(memoryStream, ImageFormat.Bmp);
// Skip header
IEnumerable<byte> bytes = memoryStream.ToArray().Skip(54);
The standard BMP format allows to store bytes of the image either in classical top/down order or in reverse order.
The way to tell whether your image is stored this way is to check the value of the Height parameter in the BMP header:
If Height < 0, then the height of your image is abs(Height) and the pixels are stored in reverse (bottom / top) order.
If Height > 0, then you are in the case you expect, where pixels are in 'normal' order, top to bottom.
I would say that what happens in your case is that you are starting from an image stored with a negative Height header (the SVG object must do that for some reason). But you do not check it, so you store the pixels in bottom to top order.
When you store with the BMP object, it figures that out for you from the context. But when you export just the pixels, then the third party application that loads your image sees positive Height and thus shows your image upside down.
You can find details about this pixel ordering in the Wikipedia page for BMP file format.
Edit:
So, when you write a BMP file to your disk, you have to do the following:
Check whether your bytes are in top to bottom order (a) or in bottom to top order (b)
If (a): put the height of your image as a positive value in the BMP header
If (b): put - height as a negative value in the BMP header. So that the third party program that shows your image knows that it's reversed.
I found the solution. Instead of creating a new bitmap, I just skipped the first 54 bytes of header information and then stored the byte array.
MemoryStream memoryStream = new MemoryStream();
bitmap.Save(memoryStream, ImageFormat.Bmp);
// Skip header
IEnumerable<byte> bytes = memoryStream.ToArray().Skip(54);
I ran into the same problem but still needed a base 64 string (for HTML5 canvas). So I used the Image class to rotate the image, stored in a new stream, then convert to base64 string.
var image = System.Drawing.Image.FromStream(new MemoryStream(bytes));
//Rotate and save to new stream
image.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
MemoryStream streamOut = new MemoryStream();
image.Save(streamOut, System.Drawing.Imaging.ImageFormat.Jpeg);
//Convert to base64 string
string base64String = Convert.ToBase64String(streamOut.ToArray());

Exctract FlateDecode images using iTextSharp

I want to extract images from an PDF. I'm using iTextSharp right now.
Some images can be extracted correct, but most of them don't have the right colors and are distorted.
I did some experiments with different PixelFormats, but I didn't get a solution for my Problem...
This is the Code which separates the image-types:
if (filter == "/FlateDecode")
{
// ...
int w = int.Parse(width);
int h = int.Parse(height);
int bpp = tg.GetAsNumber(PdfName.BITSPERCOMPONENT).IntValue;
byte[] rawBytes = PdfReader.GetStreamBytesRaw((PRStream)tg);
byte[] decodedBytes = PdfReader.FlateDecode(rawBytes);
byte[] streamBytes = PdfReader.DecodePredictor(decodedBytes, tg.GetAsDict(PdfName.DECODEPARMS));
PixelFormat[] pixFormats = new PixelFormat[23] {
PixelFormat.Format24bppRgb,
// ... all Pixel Formats
};
for (int i = 0; i < pixFormats.Length; i++)
{
Program.ToPixelFormat(w, h, pixFormats[i], streamBytes, bpp, images));
}
}
This is the Code to save the Image in a MemoryStream. Saving the image in a folder is implemented later.
private static void ToPixelFormat(int width, int height, PixelFormat pixelformat, byte[] bytes, int bpp, IList<Image> images)
{
Bitmap bmp = new Bitmap(width, height, pixelformat);
BitmapData bmd = bmp.LockBits(new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly, pixelformat);
Marshal.Copy(bytes, 0, bmd.Scan0, bytes.Length);
bmp.UnlockBits(bmd);
using (var ms = new MemoryStream())
{
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Tiff);
bytes = ms.GetBuffer();
}
images.Add(bmp);
}
Please help me.
even you found solution for your problem, let me say suggestion to fix your code above.
I believe the distortion problem is caused because of mismatch in row data boundary. PdfReader returns data in a byte boundary. For example for grayscale image 20 pixel wide you will get 20 bytes of data for each image row. Bitmap class works with 32bit boundary. When creating bitmap with 20 pixels of width, Bitmap class will generate grayscale bitmap with stride(byte width)=32 bytes. It means you cannot simply copy the retrieved bytes from PdfReader into a new bitmap using Marshal.Copy() method as it is in your ToPixelFormat().
First pixel in source byte array is located as 21st byte but destination Bitmap needs it as 33rd byte becasue of the 32bit boundary of the Bitmap. To solve this issue I had to create byte array with size that considers the 32bit boundary for each data row.
Copy data row by row from bytes aray retrieved from PdfReader into new byte array with 32bit row boundary consideration. Now I had bytes of data with boundary that matched the Bitmap class boundary so I can copy it to the new Bitmap using Marshal.Copy().
I found an solution for my own problem.
To extract all Images on all Pages, it is not necessary to implement different filters.
iTextSharp has an Image Renderer, which saves all Images in their original image type.
Just do the following found here: http://kuujinbo.info/iTextSharp/CCITTFaxDecodeExtract.aspx
You don't need to implement HttpHandler...
PDF supports a pretty wide variety of image formats. I don't think I would take this approach you've chosen here. You need to determine the image format from the bytes in the stream itself. For example, JPEG will typically start with the ASCII bytes JFIF.
.NET (3.0+) does come with a method that will attempt to pick the right decoder: BitmapDecoder.Create. See http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapdecoder.aspx
If that doesn't work you may want to consider some third-party imaging libraries. I've used ImageMagick.NET and LeadTools (way overpriced).

Categories