what is the reason of increasing image size in rotation? - c#

I have a c# application that contains an image gallery where I display some pictures.
This gallery have some features including left and right rotation.
everything is perfect but when I choose a picture from gallery and press rotation button (regardless left or right rotation), size of the picture increase significantly.
It should be mentioned that the picture's format is JPEG.
Size of picture before rotation : 278 kb
Size of picture after rotation : 780 kb
My code for rotation is like bellow :
public Image apply(Image img)
{
Image im = img;
if (rotate == 1) im.RotateFlip(RotateFlipType.Rotate90FlipNone);
if (rotate == 2) im.RotateFlip(RotateFlipType.Rotate180FlipNone);
if (rotate == 3) im.RotateFlip(RotateFlipType.Rotate270FlipNone);
//file size is increasing after RotateFlip method
if (brigh != DEFAULT_BRIGH ||
contr != DEFAULT_CONTR ||
gamma != DEFAULT_GAMMA)
{
using (Graphics g = Graphics.FromImage(im))
{
float b = _brigh;
float c = _contr;
ImageAttributes derp = new ImageAttributes();
derp.SetColorMatrix(new ColorMatrix(new float[][]{
new float[]{c, 0, 0, 0, 0},
new float[]{0, c, 0, 0, 0},
new float[]{0, 0, c, 0, 0},
new float[]{0, 0, 0, 1, 0},
new float[]{b, b, b, 0, 1}}));
derp.SetGamma(_gamma);
g.DrawImage(img, new Rectangle(Point.Empty, img.Size),
0, 0, img.Width, img.Height, GraphicsUnit.Pixel, derp);
}
}
return im;
}
What is the problem?

In your case applying RotateFlip on im is changing the ImageFormat from Jpeg to MemoryBmp.
By Default when you save the image it is going to make use of the default ImageFormat. This will be the format returned by im.RawFormat
if you check the GUID im.RawFormat.Guid
Before RotateFlip
{b96b3cae-0728-11d3-9d7b-0000f81ef32e}
which is same as ImageFormat.Jpeg.Guid
After RotateFlip
{b96b3caa-0728-11d3-9d7b-0000f81ef32e}
which is same as ImageFormat.MemoryBmp.Guid
At the time of saving the image pass the ImageFormat as the second parameter which will ensure that it uses the correct format. If not mentioned it is going to be the one in im.RawFormat
So If you want to save as jpeg at the time of saving call
im.Save("filename.jpg", ImageFormat.Jpeg);
This time the file size should be less than the original size.
Also note ImageFormat is in System.Drawing.Imaging namespace
NOTE
To control the quality of the jpeg make use of the overloaded Save method as mentioned in this MSDN Link
EDIT Based On Comment
OK assuming you are using SQL Server you must be having a image datatype column (it is recommended to use varbinary(max) instead of image as in future it is going to be obselete (Read MSDN Post)
Now to the steps
1) read the contents as a stream / byte[] array
2) convert this to Image
3) perform rotate operation on the Image
4) convert this Image back to stream / byte[] array
5) Update the database column with the new value

Two reasons:
The JPEG compression/encoding/sampling is not optimized as the original JPEG.
JPEG is not transparent. When an image is not rotated 90/180/270 degrees, the rectangular boundary of the image becomes larger.

You should to keep raw ImageFormat before change the image, and save to file by raw image format. Like bellow code :
using (Image image = Image.FromFile(filePath))
{
var rawFormat = image.RawFormat;
image.RotateFlip(angel);
image.Save(filePath, rawFormat);
}

Related

how to remove a section of image and move below part up in C#

I have a image in which i have the rectangle dimension of its which is needed to be removed from the image, how i can do that using the image processing library available in C#, or you have any kind of reference for this requirement please let me know and help me
i am attaching the image for the problem statement.
i have A image and i want B image after processing, how can i do that please help me.
thanks in advance
I would suggest to use the Graphics Object and The Bitmap class.
Load your source Bitmap into Memory.
Create a new Bitmap with the right height
Create Graphics instances from both of your Bitmaps https://msdn.microsoft.com/de-de/library/system.drawing.graphics.fromimage(v=vs.110).aspx
Draw the source Bitmap to the destination Bitmap https://msdn.microsoft.com/de-de/library/558kfzex(v=vs.110).aspx
Draw a filled Rectangle with Color.Transparent to the source Bitmap. It must be located at 0/0 and must have the size x4/y4. https://msdn.microsoft.com/de-de/library/c6ksfcek(v=vs.110).aspx
Draw the source bitmap to the destination picture again, but this time you locate it at 0/-(y3-y1)
Finally save the Bitmap to a file again or do whatever you want to do with it.
An important setting for this approch is the Graphics.ComposingMode. For the first draw to the destination and the source bitmap it must be set to SourceCopy. For the second draw to the destination it must be set to SourceOver. https://msdn.microsoft.com/de-de/library/system.drawing.drawing2d.compositingmode(v=vs.110).aspx
Additionally both bitmaps must have a pixelFormat with Alpha channel e.g. Format32bppArgb https://msdn.microsoft.com/de-de/library/system.drawing.imaging.pixelformat(v=vs.110).aspx
So what am I doing:
Im drawing the big picture into the small one. The bottom part is cropped.
I make the upper part transparent and draw the picture with a negative y coordinate to the destination. By this the bottom part of the picture is pasted over the remaining visible part from the first draw.
If you have any problem with this introduction, feel free to ask.
EDIT: I felt like trying myself... Here's the working code:
int cropFromY = 240;
int cropToY = 334;
// Change location if required...
using (FileStream fs = new FileStream(#"D:\Test\pic.png", FileMode.Open, FileAccess.Read))
{
// Step 1
// load Bitmap from File
var loadedBitmap = new Bitmap(fs);
// Step 1 (extended)
// As the loadedBitmap might have a wrong PixelFormat, we convert it to have an Alpha channel
var source = new Bitmap(loadedBitmap.Width, loadedBitmap.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics gSource = Graphics.FromImage(source))
{
gSource.DrawImageUnscaled(loadedBitmap, 0, 0);
}
// Step 2
// Create destination Picture
var destination = new Bitmap(source.Width, source.Height - (cropToY - cropFromY), System.Drawing.Imaging.PixelFormat.Format32bppArgb);
// Step 3
// Create the two graphics objects
using (Graphics gSource = Graphics.FromImage(source))
{
using (Graphics gDestination = Graphics.FromImage(destination))
{
// Step 4
// Copy picture source to destination => Crop Bottom
gDestination.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
gDestination.DrawImageUnscaled(source, 0, 0);
// Step 5
// Make the top part (which is already drawn to the destination) transparent
gSource.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
gSource.FillRectangle(Brushes.Transparent, new Rectangle(0, 0, source.Width, cropToY));
// Step 6
// Draw the source bitmap to the destination picture again, but this time you locate it at 0/-(y3-y1)
gDestination.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
gDestination.DrawImageUnscaled(source, 0, -(cropToY - cropFromY));
// Step 7
// Save new picture. Change location if required...
destination.Save(#"D:\Test\pic2.png");
}
}
}
Original:
Resulting picture:
Cheers Thomas

Insufficient buffer size using WriteableBitmap?

I am modifying the ColorBasic Kinect example in order to display an image overlaid to the video stream. So what I've done is to load an image with transparent background (now a GIF but it may change), and write to the displayed bitmap.
The error I'm getting is that the buffer I'm writing to is too small.
I cannot see what the actual error is (I'm a complete newbie in XAML/C#/Kinect), but the WriteableBitmap is 1920x1080, and the bitmap I want to copy is 200x200, so why am I getting this error? I cannot see how a transparent background could be of any harm, but I am beginning to suspect that...
Note that without the last WritePixels, the code works and I see the webcam's output. My code follows.
The overlay image:
public BitmapImage overlay = new BitmapImage(new Uri("C:\\users\\user\\desktop\\something.gif"));
The callback function that displays the Kinect's webcam (see the default example ColorBasic) with my very small modifications:
private void Reader_ColorFrameArrived(object sender, ColorFrameArrivedEventArgs e)
{
// ColorFrame is IDisposable
using (ColorFrame colorFrame = e.FrameReference.AcquireFrame())
{
if (colorFrame != null)
{
FrameDescription colorFrameDescription = colorFrame.FrameDescription;
using (KinectBuffer colorBuffer = colorFrame.LockRawImageBuffer())
{
this.colorBitmap.Lock();
// verify data and write the new color frame data to the display bitmap
if ((colorFrameDescription.Width == this.colorBitmap.PixelWidth) && (colorFrameDescription.Height == this.colorBitmap.PixelHeight))
{
colorFrame.CopyConvertedFrameDataToIntPtr(
this.colorBitmap.BackBuffer,
(uint)(colorFrameDescription.Width * colorFrameDescription.Height * 4),
ColorImageFormat.Bgra);
this.colorBitmap.AddDirtyRect(new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight));
}
if(this.overlay != null)
{
// Calculate stride of source
int stride = overlay.PixelWidth * (overlay.Format.BitsPerPixel / 8);
// Create data array to hold source pixel data
byte[] data = new byte[stride * overlay.PixelHeight];
// Copy source image pixels to the data array
overlay.CopyPixels(data, stride, 0);
this.colorBitmap.WritePixels(new Int32Rect(0, 0, overlay.PixelWidth, overlay.PixelHeight), data, stride, 0);
}
this.colorBitmap.Unlock();
}
}
}
}
Your overlay.Format.BitsPerPixel / 8 will be 1 (because it's a gif), but you're trying to copy it to something that is not a gif, probably BGRA (32 bit). Thus you got a huge difference in size (4x).
.WritePixels should take in the stride value of the destination buffer, but you past it the stride value of the overlay (this can cause weird problems as well).
And finally, even if it went 100% smooth your overlay will not actually "overlay" anything, it will replace -- since I don't see any alpha bending math in your code.
Switch your .gif to a .png (32bit) and see if that helps.
Also, if you're looking for an AlphaBltMerge type code: I wrote the entire thing here.. it's very easy to understand.
Merge 2 - 32bit Images with Alpha Channels

Graphics.DrawImage alternatives for large images

I am trying to draw a crosshair ("plus sign") with inverted colors over an image to show the location of a selected point within the image. This is how I do it:
private static void DrawInvertedCrosshair(Graphics g, Image img, PointF location, float length, float width)
{
float halfLength = length / 2f;
float halfWidth = width / 2f;
Rectangle absHorizRect = Rectangle.Round(new RectangleF(location.X - halfLength, location.Y - halfWidth, length, width));
Rectangle absVertRect = Rectangle.Round(new RectangleF(location.X - halfWidth, location.Y - halfLength, width, length));
ImageAttributes attributes = new ImageAttributes();
float[][] invertMatrix =
{
new float[] {-1, 0, 0, 0, 0 },
new float[] { 0, -1, 0, 0, 0 },
new float[] { 0, 0, -1, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 1, 1, 1, 0, 1 }
};
ColorMatrix matrix = new ColorMatrix(invertMatrix);
attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(img, absHorizRect, absHorizRect.X, absHorizRect.Y, absHorizRect.Width, absHorizRect.Height, GraphicsUnit.Pixel, attributes);
g.DrawImage(img, absVertRect, absVertRect.X, absVertRect.Y, absVertRect.Width, absVertRect.Height, GraphicsUnit.Pixel, attributes);
}
It works as expected, however, it is really slow. I want the user to be able to move the selected location around with their mouse by setting the location to the cursor's location whenever it moves. Unfortunately, on my computer, it can update only around once per second for big images.
So, I am looking for an alternative to using Graphics.DrawImage to invert a region of an image. Are there any ways to do this with speeds proportional to the selected region area rather than the entire image area?
Sounds to me you are focusing on the wrong problem. Painting the image is slow, not painting the "cross-hairs".
Large images can certainly be very expensive when you don't help. And System.Drawing makes it very easy to not help. Two basic things you want to do to make the image paint faster, getting it more than 20 times faster is quite achievable:
avoid forcing the image painting code to rescale the image. Instead do it just once so the image can be drawn directly one-to-one without any rescaling. Best time to do so is when you load the image. Possibly again in the control's Resize event handler.
pay attention to the pixel format of the image. The fastest one by a long shot is the pixel format that's directly compatible with the way the image needs to be stored in the video adapter. So the image data can be directly copied to video RAM without having to adjust each individual pixel. That format is PixelFormat.Format32bppPArgb on 99% of all modern machines. Makes a huge difference, it is ten times faster than all the other ones.
A simple helper method that accomplishes both without otherwise dealing with the aspect ratio:
private static Bitmap Resample(Image img, Size size) {
var bmp = new Bitmap(size.Width, size.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (var gr = Graphics.FromImage(bmp)) {
gr.DrawImage(img, new Rectangle(Point.Empty, size));
}
return bmp;
}
Draw the image once on Graphics g, then draw the crosshair on Graphics g directly instead of the image. You can optionally keep track of the places the user clicked so as to save them either in the image or elsewhere as needed.

Zxing weird image reverse

I am writing C# lib for very simple recognize image to use it in monodroid and also using zxing port to C#. But after I read image bytes from file I do such thing, same as in zxing barcode scanning.
binaryBitmap = new BinaryBitmap(new HybridBinarizer(new RGBLuminanceSource(rawRgb, width, height, format)));
But somehow it reverse image by vertical. I just saving binaryBitmap as bitmap to file by pixels.
Please help me understand why it's happen? What am I doing wrong?
#Michael am using Zxing.Net.Mobile port, from here https://github.com/Redth/ZXing.Net.Mobile. It's very weird for me it I am using PlanarYUVLuminanceSource - then I get such image http://i.imgur.com/OlwqC0I.png, but if I am using RGBLuminanceSource then I get full almost normal image, see example image. so now I have even 2 questions:
why planar take only part of image and have "layer on layer" effect? and
ok if I will use RGBLuminanceSource then, why it have some invertion of colors, I mean somewhere rectangles border is black and somewhere they are white. because it real image they all black?
UPDATE:
Here is how I get bytes from device and also as you see I set nv21 format, so it must be YUV, no? I wonder, what I am doing wrong that rgb source work(at list image is ok) and PLanarYUV not :((
BTW, original byte from preview frame have result and same file size.
Any suggestion?
public void OnPreviewFrame(byte[] bytes, Android.Hardware.Camera camera)
{
var img = new YuvImage(bytes, ImageFormatType.Nv21, cameraParameters.PreviewSize.Width, cameraParameters.PreviewSize.Height, null); string _fileName2 = "YUV_BYtes_"+ DateTime.Now.Ticks +".txt";
string pathToFile2 = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, _fileName2);
using (var fileStream = new FileStream(pathToFile2, FileMode.Append, FileAccess.Write, FileShare.None))
{
fileStream.Write(img.GetYuvData(), 0, img.GetYuvData().Length);
}
}
public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Format format, int width, int height)
{
if (camera == null)
return;
var parameters = camera.GetParameters();
width = parameters.PreviewSize.Width;
height = parameters.PreviewSize.Height;
parameters.PreviewFormat = ImageFormatType.Nv21;
//parameters.PreviewFrameRate = 15;
//this.height = size.height;
//this.width = size.width;
//camera.setParameters( params );
//parameters.PreviewFormat = ImageFormatType.;
camera.SetParameters(parameters);
camera.SetDisplayOrientation(90);
camera.StartPreview();
cameraResolution = new Size(parameters.PreviewSize.Width, parameters.PreviewSize.Height);
AutoFocus();
}
I think I know what you have done. The data looks like RGB565 bitmap data (or something similar). You can't put such a byte array into the PlanarYUVLuminanceSource. You have to make sure that the byte array which you use with the planar source is really a array with only yuv data, not RGB565.
The rules are easy:
if you use the following code snippet
new RGBLuminanceSource(rawRgb, width, height, format)
make sure that the value of format matches the layout and data of the parameter rawRgb.
if you use somethin glike the following
new PlanarYUVLuminanceSource(yuvBytes, 640, 960, 0, 0, 640, 960, false);
make sure that yuvBytes only contains real yuv data.
I can only give a better answer if you post a more complete code sample.

C#: Preserving Bits per Plane when resizing images

I'm developing a service in which users can upload images and save them in a library. I want to preserve the original file (so people can upload high resolution images), but also make a copy of the image to use as a thumbnail.
The problem I have is that the thumbnail "weights" much more than the original file, proportionally. When I check the basic properties of each file (in XnView), I can see that the original files are for instance saved with 32 bits per Plane, whereas the source file will have, for instance, only 24 bits per plane.
What would be the correct way to make a copy of the original file while still using compression? This is an excerpt of the code:
private void ResizeImage(string originalFile, string NewFile, int NewWidth, int MaxHeight, bool OnlyResizeIfWider, string directory)
{
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(originalFile);
// Prevent using images internal thumbnail
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
if (OnlyResizeIfWider)
{
if (FullsizeImage.Width <= NewWidth)
{
NewWidth = FullsizeImage.Width;
}
}
int NewHeight = FullsizeImage.Height * NewWidth / FullsizeImage.Width;
if (NewHeight > MaxHeight)
{
// Resize with height instead
NewWidth = FullsizeImage.Width * MaxHeight / FullsizeImage.Height;
NewHeight = MaxHeight;
}
System.Drawing.Image NewImage = FullsizeImage.GetThumbnailImage(NewWidth, NewHeight, null, IntPtr.Zero);
// Clear handle to original file so that we can overwrite it if necessary
FullsizeImage.Dispose();
// Save resized picture
NewImage.Save(directory + Path.DirectorySeparatorChar + NewFile);
}
I would suggest you save the image as a PNG or JPEG stream. That will be much more efficient. Considering that the thumbnail is not the original data, it is probably OK to apply strong compression on it (QF < 60). I once managed to get useful JPEG images in less than 1000 bytes. At that point, they're so small you can consider putting them in a database rather than on disk.
EDIT: (after reading the question fully :) )
And to answer the question, the result of GetThumbnailForImage(), is again an Image so you can call the Save method on it, one of the overloads allows you to specify which compression to use.
Rather than calling GetThumbnailImage, you can create a new image of the size you want and the PixelFormat that you want, and then scale the original image into that.
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(originalFile);
// Create a bitmap that's NewWidth x NewHeight
Bitmap bmp = new Bitmap(NewWidth, NewHeight, PixelFormat.Format24bppRgb);
// Get a Graphics object for that image and draw the original image into it
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawImage(FullsizeImage, 0, 0, NewWidth, NewHeight);
}
// you now have a 24bpp thumbnail image

Categories

Resources