I am work on an ASP.NetCore 5 project that requires users to upload document using a normal file picker, so i have to check if the document the user is uploading is blurry before resampling it and saving it to a database.
I did some research and found out that imagemagick.net would help me accomplish all of that, but can't seem to find a way around it
I just googled a bit around and fiddled some Stackoverflow / Blog answers to this together:
public static class ImageExtensions
{
// as seen here: https://pyimagesearch.com/2015/09/07/blur-detection-with-opencv/
public static bool IsBlurry(this Image image, double threshold = 100.0)
{
var mat = GetMatFromSDImage(image);
var varianceOfLaplacian = VarianceOfLaplacian(mat);
return varianceOfLaplacian < threshold;
}
// as seen here: https://stackoverflow.com/questions/58005091/how-to-get-the-variance-of-laplacian-in-c-sharp
private static double VarianceOfLaplacian(Mat mat)
{
using var laplacian = new Mat();
CvInvoke.Laplacian(mat, laplacian, DepthType.Cv64F);
var mean = new MCvScalar();
var stddev = new MCvScalar();
CvInvoke.MeanStdDev(laplacian, ref mean, ref stddev);
return stddev.V0 * stddev.V0;
}
// as found here https://stackoverflow.com/questions/40384487/system-drawing-image-to-emgu-cv-mat
private static Mat GetMatFromSDImage(Image image)
{
int stride = 0;
Bitmap bmp = new Bitmap(image);
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
System.Drawing.Imaging.PixelFormat pf = bmp.PixelFormat;
if (pf == System.Drawing.Imaging.PixelFormat.Format32bppArgb)
{
stride = bmp.Width * 4;
}
else
{
stride = bmp.Width * 3;
}
Image<Bgra, byte> cvImage = new Image<Bgra, byte>(bmp.Width, bmp.Height, stride, (IntPtr)bmpData.Scan0);
bmp.UnlockBits(bmpData);
return cvImage.Mat;
}
}
I noted the origin of the snippets, as nothing has been implemented by me here ;) It does require the Emgu.CV package not imagemagick.
I hope this helps, it passed the few tests, i just did.
Related
We are using a camera that acquires up to 60 frames per second, providing Bitmaps for us to use in our codebase.
As our wpf-app requires, these bitmaps are scaled based on a scaling factor; That scaling-process is by far the most limiting factor when it comes to actually displaying 60 fps. I am aware of new Bitmap(Bitmap source, int width, int height) which is obviously the simplest way to resize a Bitmap;
Nevertheless, I am trying to implement a "manual" approach using BitmapData and pointers. I have come up with the following:
public static Bitmap /*myMoBetta*/ResizeBitmap(this Bitmap bmp, double scaleFactor)
{
int desiredWidth = (int)(bmp.Width * scaleFactor),
desiredHeight = (int)(bmp.Height * scaleFactor);
var scaled = new Bitmap(desiredWidth, desiredHeight, bmp.PixelFormat);
int formatSize = (int)Math.Ceiling(Image.GetPixelFormatSize(bmp.PixelFormat)/8.0);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
BitmapData scaledData = scaled.LockBits(new Rectangle(0, 0, scaled.Width, scaled.Height), ImageLockMode.WriteOnly, scaled.PixelFormat);
unsafe
{
var srcPtr = (byte*)bmpData.Scan0.ToPointer();
var destPtr = (byte*)scaledData.Scan0.ToPointer();
int scaledDataSize = scaledData.Stride * scaledData.Height;
int nextPixel = (int)(1 / scaleFactor)*formatSize;
Parallel.For(0, scaledDataSize - formatSize,
i =>
{
for (int j = 0; j < formatSize; j++)
{
destPtr[i + j] = srcPtr[i * nextPixel + j];
}
});
}
bmp.UnlockBits(bmpData);
bmp.Dispose();
scaled.UnlockBits(scaledData);
return scaled;
}
Given scalingFactor < 1.
Actually using this algorithm does not seem to work, though. How are the bits of each pixel arranged in memory, exactly? My guess was that calling Image.GetPixelFormatSize() and deviding its result by 8 returns the number of bytes per pixel; But continuing to copy only formatSize amout of bytes every 1 / scaleFactor * formatSize byte results in a corrupted image.
What am I missing?
After some more research I bumped into OpenCV that has it's own .NET implementation with Emgu.CV, containing relevant methods for faster resizing.
My ResizeBitmap()-function has shrinked significantly:
public static Bitmap ResizeBitmap(this Bitmap bmp, int width, int height)
{
var desiredSize = new Size(width, height);
var src = new Emgu.CV.Image<Rgb, byte>(bmp);
var dest = new Emgu.CV.Image<Rgb, byte>(desiredSize);
Emgu.CV.CvInvoke.Resize(src, dest, desiredSize);
bmp.Dispose();
src.Dispose();
return dest.ToBitmap();
}
I have not tested performance thouroughly, but while debugging, this implementation reduced executiontime from 22ms with new Bitmap(source, width, height) to about 7ms.
This question already has answers here:
A generic error occurred in GDI+, JPEG Image to MemoryStream
(36 answers)
Closed 3 years ago.
I'm resizing an image, what could be wrong with my code?
var newSize = ResizeImageFile(ConvertToBytes(myFile), 2048);
using (MemoryStream ms = new MemoryStream(newSize, 0, newSize.Length))
{
ms.Write(newSize, 0, newSize.Length);
using (Image image = Image.FromStream(ms, true))
{
image.Save(targetLocation, ImageFormat.Jpeg);
}
}
I have used this function to resize my image
public static byte[] ResizeImageFile(byte[] imageFile, int targetSize) // Set targetSize to 1024
{
using (Image oldImage = Image.FromStream(new MemoryStream(imageFile)))
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format24bppRgb))
{
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
}
I have been looking for answers for a long time, please help me.
Thank you
Since the both CalculateDimensions and ConvertToBytes methods are not shown, I tried to assume that the above methods are something like as follows:
// Calculate the Size at which the image width and height is lower than the specified value
// (Keep the aspect ratio)
private static Size CalculateDimensions(Size size, int targetSize)
{
double rate = Math.Max(size.Width * 1.0 / targetSize, size.Height * 1.0 / targetSize);
int w = (int)Math.Floor(size.Width / rate);
int h = (int)Math.Floor(size.Height / rate);
return new Size(w, h);
}
//Convert image file to byte array
private static byte[] ConvertToBytes(string fileName)
{
var result = File.ReadAllBytes(fileName);
return result;
}
If your code does not work well, then some problems must be in the above methods.
I'm working on an image processing project, and I've read that the fastest way to manipulate a bitmap image is to copy it from a byte array using Marshal.Copy(). However, for whatever reason, nothing is being copied from my byte array to my Bitmap, and there's not a clear reason why. This is the code I'm using to copy into my Bitmap:
public void UpdateImage()
{
var data = image.LockBits(
new Rectangle(Point.Empty, image.Size),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb);
Marshal.Copy(backBuffer, 0, data.Scan0, backBuffer.Length);
image.UnlockBits(data);
Console.WriteLine("UpdateImage");//For debugging purposes
}
I'm attempting to fill the image with complete black, and looking at the data of the backBuffer, it appears to be correct, and as expected, where as when I check any pixel of 'image' it is completely blank. I have no idea why nothing is happening. Any advice would be much appreciated!
Edit: I apologize, I'm a bit new around here, let me provide some more information. Specifically, I am working on some GPU accelerated image processing using Cloo/OpenCL. I wanted to fill the screen with black to make sure that I am doing things correctly, although I am evidently not. Here is the entire class file I'm using:
public class RenderTarget
{
public GraphicsDevice GraphicsDevice;
private byte[] backBuffer;
public Bitmap image;
private ComputeKernel fillKernel;
private ComputeProgram fillProgram;
public void UpdateImage()
{
var data = image.LockBits(
new Rectangle(Point.Empty, image.Size),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb);
Marshal.Copy(backBuffer, 0, data.Scan0, backBuffer.Length);
image.UnlockBits(data);
Console.WriteLine("UpdateImage");
}
//Test method ONLY
public void FillScreen(Color color)
{
if (fillProgram == null)//temporary, all kernels should be compiled on start up. In fact, these probably should be static
{
string fillText = #"
kernel void fillScreen(global uchar* data_out, int from, int to, uchar a, uchar r, uchar g, uchar b){
for (int i = from; i < to; i += 4){
data_out[i] = a;
data_out[i + 1] = r;
data_out[i + 2] = g;
data_out[i + 3] = b;
}
}";
fillProgram = new ComputeProgram(GraphicsDevice.context, fillText);
fillProgram.Build(null, null, null, IntPtr.Zero);
fillKernel = fillProgram.CreateKernel("fillScreen");
}
ComputeBuffer<byte> backBufferBuffer = new ComputeBuffer<byte>(GraphicsDevice.context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.UseHostPointer, backBuffer);
fillKernel.SetMemoryArgument(0, backBufferBuffer);
for (int i = 0; i < backBuffer.Length / 10000; i++)
{
fillKernel.SetValueArgument<int>(1, i * 10000);
fillKernel.SetValueArgument<int>(2, (i + 1) * 10000);
fillKernel.SetValueArgument<byte>(3, color.A);
fillKernel.SetValueArgument<byte>(4, color.R);
fillKernel.SetValueArgument<byte>(5, color.G);
fillKernel.SetValueArgument<byte>(6, color.B);
GraphicsDevice.queue.ExecuteTask(fillKernel, null);
}
GraphicsDevice.queue.ReadFromBuffer(backBufferBuffer, ref backBuffer, false, null);
GraphicsDevice.queue.Finish();
}
public RenderTarget(int Width, int Height, GraphicsDevice device)
{
image = new Bitmap(Width, Height);
backBuffer = new byte[4 * Width * Height];
GraphicsDevice = device;
//Fill the screen with black
FillScreen(Color.Black);
UpdateImage();
Console.WriteLine(image.GetPixel(0, 0).A);
}
}
I have checked to make absolutely sure that the backBuffer is correct. (The values I expected were 255, 0, 0, 0 for the first four elements of the backBuffer).
Okay, I figured out what was going wrong. I had the format in the backBuffer wrong. I was expecting it to be ARGB when it should be ordered RGBA. So, it was a problem with my code in 'fillText'.
I am creating placeholder images in certain sizes that will be used as Data URIs
Bitmap bitmap = new Bitmap(16, 10);
I have done some research, but can't find a good way of saving this bitmap as the smallest possible filesize, which is why I want an 8bit PNG.
My question is: How can I save this bitmap into a file/bytearray/stream as an 8bit PNG? Any good libraries?
You can do this with nQuant (which you can install with nuget, or see references below). The following example converts an image on disk and would be readily adapted to meet your needs.
public static bool TryNQuantify(string inputFilename, string outputFilename)
{
var quantizer = new nQuant.WuQuantizer();
var bitmap = new Bitmap(inputFilename);
if (bitmap.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppArgb)
{
ConvertTo32bppAndDisposeOriginal(ref bitmap);
}
try
{
using (var quantized = quantizer.QuantizeImage(bitmap))
{
quantized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
}
catch
{
return false;
}
finally
{
bitmap.Dispose();
}
return true;
}
private static void ConvertTo32bppAndDisposeOriginal(ref Bitmap img)
{
var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var gr = Graphics.FromImage(bmp))
gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));
img.Dispose();
img = bmp;
}
For more information see:
https://github.com/mcychan/nQuant.cs
https://code.msdn.microsoft.com/windowsdesktop/Convert-32-bit-PNGs-to-81ef8c81
I like the FreeImage project, it is light weight and easy to use. Below is an example of creating a transparent png. You could easily wrap this in a method and set the width and height and transparency value.
//create a new bit map
FIBITMAP dib = new FIBITMAP();
//allocate a 16x10 bitmap with 8bit depth
dib = FreeImage.Allocate(16, 10, 8);
//set the transpareny
byte[] Transparency = new byte[1];
Transparency[0] = 0x00;
FreeImage.SetTransparencyTable(dib, Transparency);
//save the bitmap
FreeImage.Save(FREE_IMAGE_FORMAT.FIF_PNG, dib, "C:\\temp\\tp.png", FREE_IMAGE_SAVE_FLAGS.DEFAULT);
If all you need is a small transparent image, why stop at 8 bit? You can go straight down to 1 bit! You only need one colour anyway, and it'll be even smaller.
In fact, you don't even need to do anything special for that. Since the pixels on a new indexed bitmap will all default to 0, meaning they reference its first palette colour, all you need to do is make a new 1bpp image, and set that first palette colour to transparent:
public static Bitmap MakePlaceholderImage(Int32 width, Int32 height)
{
Bitmap bm = new Bitmap(width, height, PixelFormat.Format1bppIndexed);
// This colour can't be assigned directly since the .Palette getter actually makes a copy of the palette.
ColorPalette pal = bm.Palette;
pal.Entries[0] = Color.Transparent;
bm.Palette = pal;
return bm;
}
I experimented a bit with this, and, saved as png, the end result seemed to consistently be 8 times smaller than the result of the same code executed with 8bpp. For a 5000x5000 image, the file size as png was barely over 3 KiB.
Google search led me to a 6 year old thread and I can't find my solution. It should've been possible 6 years as well, as it uses the System.Drawing Libraray of the .Net Framework.
This example code should help anyone who want to write an 8 bit indexed png.
static class SaveImages
{
public static void SaveGrayImage(Bitmap srcImg, string name)
{
Bitmap grayImg = new Bitmap(srcImg.Width, srcImg.Height, PixelFormat.Format8bppIndexed);
var pal = grayImg.Palette;
foreach (int i in Enumerable.Range(0, 256))
pal.Entries[i] = Color.FromArgb(255, i, i, i);
grayImg.Palette = pal;
var data = grayImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
var bytes = new byte[data.Stride * data.Height];
foreach (int y in Enumerable.Range(0, srcImg.Height))
{
foreach (int x in Enumerable.Range(0, srcImg.Width))
{
var colour = srcImg.GetPixel(x, y);
var c = (int)colour.R + (int)colour.G + (int)colour.B;
c /= 3;
bytes[data.Stride * y + x] = (byte)c;
}
}
Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
grayImg.Save(name, ImageFormat.Png);
}
}
Edit: To add transparency dabble with the color palette.
1) Create your bitmap with 8 bit per pixel format:
Bitmap bmp = new Bitmap(20, 20, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
See PixelFormat for more formats.
2) Draw to the bitmap ...
3) Then save it as PNG:
bmp.Save(#"c:\temp\xyz.png", System.Drawing.Imaging.ImageFormat.Png);
The created file will have the same pixel format as bmp (8 bit)
I need to convert bitonal (black and white) TIFF files into another format for display by a web browser, currently we're using JPGs, but the format isn't crucial. From reading around .NET doesn't seem to easily support writing bitonal images, so we're ending up with ~1MB files instead of ~100K ones. I'm considering using ImageMagick to do this, but ideally i'd like a solution which doesn't require this if possible.
Current code snippet (which also does some resizing on the image):
using (Image img = Image.FromFile(imageName))
{
using (Bitmap resized = new Bitmap(resizedWidth, resizedHeight)
{
using (Graphics g = Graphics.FromImage(resized))
{
g.DrawImage(img, new Rectangle(0, 0, resized.Width, resized.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel);
}
resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
Is there any way to achieve this?
Thanks.
I believe the problem can be solved by checking that resized bitmap is of PixelFormat.Format1bppIndexed. If it's not, you should convert it to 1bpp bitmap and after that you can save it as black and white png without problems.
In other words, you should use following code instead of resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Jpeg);
if (resized.PixelFormat != PixelFormat.Format1bppIndexed)
{
using (Bitmap bmp = convertToBitonal(resized))
bmp.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
else
{
resized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
I use following code for convertToBitonal :
private static Bitmap convertToBitonal(Bitmap original)
{
int sourceStride;
byte[] sourceBuffer = extractBytes(original, out sourceStride);
// Create destination bitmap
Bitmap destination = new Bitmap(original.Width, original.Height,
PixelFormat.Format1bppIndexed);
destination.SetResolution(original.HorizontalResolution, original.VerticalResolution);
// Lock destination bitmap in memory
BitmapData destinationData = destination.LockBits(
new Rectangle(0, 0, destination.Width, destination.Height),
ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
// Create buffer for destination bitmap bits
int imageSize = destinationData.Stride * destinationData.Height;
byte[] destinationBuffer = new byte[imageSize];
int sourceIndex = 0;
int destinationIndex = 0;
int pixelTotal = 0;
byte destinationValue = 0;
int pixelValue = 128;
int height = destination.Height;
int width = destination.Width;
int threshold = 500;
for (int y = 0; y < height; y++)
{
sourceIndex = y * sourceStride;
destinationIndex = y * destinationData.Stride;
destinationValue = 0;
pixelValue = 128;
for (int x = 0; x < width; x++)
{
// Compute pixel brightness (i.e. total of Red, Green, and Blue values)
pixelTotal = sourceBuffer[sourceIndex + 1] + sourceBuffer[sourceIndex + 2] +
sourceBuffer[sourceIndex + 3];
if (pixelTotal > threshold)
destinationValue += (byte)pixelValue;
if (pixelValue == 1)
{
destinationBuffer[destinationIndex] = destinationValue;
destinationIndex++;
destinationValue = 0;
pixelValue = 128;
}
else
{
pixelValue >>= 1;
}
sourceIndex += 4;
}
if (pixelValue != 128)
destinationBuffer[destinationIndex] = destinationValue;
}
Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, imageSize);
destination.UnlockBits(destinationData);
return destination;
}
private static byte[] extractBytes(Bitmap original, out int stride)
{
Bitmap source = null;
try
{
// If original bitmap is not already in 32 BPP, ARGB format, then convert
if (original.PixelFormat != PixelFormat.Format32bppArgb)
{
source = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppArgb);
source.SetResolution(original.HorizontalResolution, original.VerticalResolution);
using (Graphics g = Graphics.FromImage(source))
{
g.DrawImageUnscaled(original, 0, 0);
}
}
else
{
source = original;
}
// Lock source bitmap in memory
BitmapData sourceData = source.LockBits(
new Rectangle(0, 0, source.Width, source.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
// Copy image data to binary array
int imageSize = sourceData.Stride * sourceData.Height;
byte[] sourceBuffer = new byte[imageSize];
Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, imageSize);
// Unlock source bitmap
source.UnlockBits(sourceData);
stride = sourceData.Stride;
return sourceBuffer;
}
finally
{
if (source != original)
source.Dispose();
}
}
Have you tried saving using the Image.Save overload with Encoder parameters?
Like the Encoder.ColorDepth Parameter?
Trying jaroslav's suggestion for color depth doesn't work:
static void Main(string[] args)
{
var list = ImageCodecInfo.GetImageDecoders();
var jpegEncoder = list[1]; // i know this is the jpeg encoder by inspection
Bitmap bitmap = new Bitmap(500, 500);
Graphics g = Graphics.FromImage(bitmap);
g.DrawRectangle(new Pen(Color.Red), 10, 10, 300, 300);
var encoderParams = new EncoderParameters();
encoderParams.Param[0] = new EncoderParameter(Encoder.ColorDepth, 2);
bitmap.Save(#"c:\newbitmap.jpeg", jpegEncoder, encoderParams);
}
The jpeg is still a full color jpeg.
I don't think there is any support for grayscale jpeg in gdi plus. Have you tried looking in windows imaging component?
http://www.microsoft.com/downloads/details.aspx?FamilyID=8e011506-6307-445b-b950-215def45ddd8&displaylang=en
code example: http://www.codeproject.com/KB/GDI-plus/windows_imaging.aspx
wikipedia: http://en.wikipedia.org/wiki/Windows_Imaging_Component
This is an old thread. However, I'll add my 2 cents.
I use AForge.Net libraries (open source)
use these dlls. Aforge.dll, AForge.Imaging.dll
using AForge.Imaging.Filters;
private void ConvertBitmap()
{
markedBitmap = Grayscale.CommonAlgorithms.RMY.Apply(markedBitmap);
ApplyFilter(new FloydSteinbergDithering());
}
private void ApplyFilter(IFilter filter)
{
// apply filter
convertedBitmap = filter.Apply(markedBitmap);
}
Have you tried PNG with 1 bit color depth?
To achieve a size similar to a CCITT4 TIFF, I believe your image needs to use a 1-bit indexed pallette.
However, you can't use the Graphics object in .NET to draw on an indexed image.
You will probably have to use LockBits to manipulate each pixel.
See Bob Powell's excellent article.