I have a bitmap sourceImage.bmp
locking it's bits:
BitmapData dataOriginal = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Do analysis, get a clone:
Bitmap originalClone = AForge.Imaging.Image.Clone(dataOriginal);
unlocking bits:
sourceImage.UnlockBits(dataOriginal);
is it possible to specify which part of "dataOriginal" to copy (x,y,w,h)? or to create new data from the dataOriginal, specifying X and Y coordinates as well as H and W?
The aim is to copy a small area from this image. This method might be faster than DrawImage, that's why I don't use the latter.
Edit:
So I took 29 Mb bitmap and did some hardcore testing! Full-size crop (basically a copy) + 100 iterations.
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using AForge;
using AForge.Imaging;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace testCropClone
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private unsafe Bitmap Clone(Bitmap bmp, int startX, int startY, int width, int height)
{
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int origByteCount = rawOriginal.Stride * rawOriginal.Height;
byte[] origBytes = new Byte[origByteCount];
Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount);
int BPP = 4; //4 Bpp = 32 bits, 3 = 24, etc.
byte[] croppedBytes = new Byte[width * height * BPP];
//Iterate the selected area of the original image, and the full area of the new image
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width * BPP; j += BPP)
{
int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j);
int croppedIndex = (i * width * BPP) + (j);
//copy data: once for each channel
for (int k = 0; k < BPP; k++)
{
croppedBytes[croppedIndex + k] = origBytes[origIndex + k];
}
}
}
//copy new data into a bitmap
Bitmap croppedBitmap = new Bitmap(width, height);
BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length);
bmp.UnlockBits(rawOriginal);
croppedBitmap.UnlockBits(croppedData);
return croppedBitmap;
}
private Bitmap cloneBitmap(Bitmap bmp, int startX, int startY, int width, int height)
{
Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height);
Bitmap cloneBitmap = bmp.Clone(srcRect, bmp.PixelFormat);
return cloneBitmap;
}
private Bitmap cloneRectangle(Bitmap bmp, int startX, int startY, int width, int height)
{
Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height);
Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height);
Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
using (Graphics graphics = Graphics.FromImage(dest))
{
graphics.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
}
return dest;
}
private Bitmap cloneAforge(Bitmap bmp, int startX, int startY, int width, int height)
{
BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Bitmap cloneBitmap = AForge.Imaging.Image.Clone(rawOriginal);
bmp.UnlockBits(rawOriginal);
return cloneBitmap;
}
private void button1_Click(object sender, EventArgs e)
{
Bitmap source = new Bitmap(#"C:\9\01.bmp");
Stopwatch s1 = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height);
Clone1.Dispose();
}
/*Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height);
Clone1.Save(#"C:\9\01_aforge.bmp");
Clone1.Dispose();*/
s1.Stop();
source.Dispose();
textBox1.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
}
private void button2_Click(object sender, EventArgs e)
{
Bitmap source = new Bitmap(#"C:\9\01.bmp");
Stopwatch s1 = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height);
Clone1.Dispose();
}
/*Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height);
Clone1.Save(#"C:\9\01_bitmap.bmp");
Clone1.Dispose();*/
s1.Stop();
source.Dispose();
textBox2.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
}
private void button3_Click(object sender, EventArgs e)
{
Bitmap source = new Bitmap(#"C:\9\01.bmp");
Stopwatch s1 = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height);
Clone1.Dispose();
}
/*Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height);
Clone1.Save(#"C:\9\01_bits.bmp");
Clone1.Dispose();*/
s1.Stop();
source.Dispose();
textBox3.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
}
private void button4_Click(object sender, EventArgs e)
{
Bitmap source = new Bitmap(#"C:\9\01.bmp");
Stopwatch s1 = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height);
Clone1.Dispose();
}
/*Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height);
Clone1.Save(#"C:\9\01_rect.bmp");
Clone1.Dispose();*/
s1.Stop();
source.Dispose();
textBox4.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
}
}
}
Edit2: (Aforge full-size Crop..) method Nr. 2
for (int i = 0; i < 100; i++)
{
Crop crop = new Crop(new Rectangle(0, 0, source.Width, source.Height));
var source2 = crop.Apply(source);
source2.Dispose();
}
Average = 62ms (40ms less that 1st Aforge approach)
Results:
BitmapClone (0 ms) ?? (cheating, isn't it?)
Aforge #2 (65 ms)
Aforge #1 (105 ms)
Rectangle (170 ms)
Lock Bits (803 ms) (waiting for fixes/new test results..)
I whipped up a quick (and admittedly rough) manual solution that demonstrates how to do this using locked bitmaps. It should be considerably faster than the alternative methods, but does involve a lot more code.
Bitmap bmp = new Bitmap(#"C:\original.jpg");
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int origByteCount = rawOriginal.Stride * rawOriginal.Height;
byte[] origBytes = new Byte[origByteCount];
Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount);
//I want to crop a 100x100 section starting at 15, 15.
int startX = 15;
int startY = 15;
int width = 100;
int height = 100;
int BPP = 4; //4 Bpp = 32 bits, 3 = 24, etc.
byte[] croppedBytes = new Byte[width * height * BPP];
//Iterate the selected area of the original image, and the full area of the new image
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width * BPP; j += BPP)
{
int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j);
int croppedIndex = (i * width * BPP) + (j);
//copy data: once for each channel
for (int k = 0; k < BPP; k++)
{
croppedBytes[croppedIndex + k] = origBytes[origIndex + k];
}
}
}
//copy new data into a bitmap
Bitmap croppedBitmap = new Bitmap(width, height);
BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length);
bmp.UnlockBits(rawOriginal);
croppedBitmap.UnlockBits(croppedData);
croppedBitmap.Save(#"C:\test.bmp");
I used this original image:
To output this image, cropped to 100x100 # 15,15:
Obviously if you use this code, you'll want to clean it up a bit and add error handling. If I understand your question correctly, doing things this way should eliminate the need to use AForge at all.
Fopedush's answer benefits greatly when we subsitute Marshal.copy with memcpy, because that way we don't have to copy it through a byte[] array. That way the memory gets copied only once, instead of three times!
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static unsafe extern int memcpy(byte* dest, byte* src, long count);
static public Bitmap cropBitmap(Bitmap sourceImage, Rectangle rectangle)
{
const int BPP = 4; //4 Bpp = 32 bits; argb
var sourceBitmapdata = sourceImage.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var croppedImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppArgb);
var croppedBitmapData = croppedImage.LockBits(new Rectangle(0, 0, rectangle.Width, rectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
croppedBitmapData.Stride = sourceBitmapdata.Stride;
byte* sourceImagePointer = (byte*)sourceBitmapdata.Scan0.ToPointer();
byte* croppedImagePointer = (byte*)croppedBitmapData.Scan0.ToPointer();
memcpy(croppedImagePointer, sourceImagePointer,
Math.Abs(croppedBitmapData.Stride) * rectangle.Height);
}
sourceImage.UnlockBits(sourceBitmapdata);
croppedImage.UnlockBits(croppedBitmapData);
return croppedImage;
}
My results are:
BitmapClone: 1823 ms
LockBits: 4857 ms
Rectangle: 1479 ms
My method: 559 ms
My method with LockBits on source image done only once (before loop): 160 ms
I don't have AForge so I haven't included that, but by looking on op's results it would be slower than this. I was testing cropping the image in half.
Please note, that if we would exchange memcpy with:
for (int k = 0; k < Math.Abs(croppedBitmapData.Stride) * rectangle.Height; k++)
*(croppedImagePointer++) = *(sourceImagePointer++);
it gets 10x slower!
You can try something like this:
public static Bitmap CropBitmap(Bitmap bitmap, int x, int y, int w, int h)
{
Rectangle rect = new Rectangle(x, y, w, h);
Bitmap cropped = bitmap.Clone(rect, bitmap.PixelFormat);
return cropped;
}
And do something like this in yout code (sample):
var croppedImagem = CropBitmap(dataOriginal, 0, 0, 100, 100);
I hope it helps!
I am a new user and can't vote yet, otherwise I would have upvoted Korwin80's answer as it provides the most efficient working solution, in my opinion. trakos' solution may execute faster but yields scrambled images, at least for me. Here is how I applied Korwin80's solution, with some minor improvements, in my own code:
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
private unsafe static extern int memcpy(byte* dest, byte* src, long count);
private unsafe Bitmap Crop(Bitmap srcImg, Rectangle rectangle)
{
if ((srcImg.Width == rectangle.Width) && (srcImg.Height == rectangle.Height))
return srcImg;
var srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
var bpp = srcImgBitmapData.Stride / srcImgBitmapData.Width; // 3 or 4
var srcPtr = (byte*)srcImgBitmapData.Scan0.ToPointer() + rectangle.Y * srcImgBitmapData.Stride + rectangle.X * bpp;
var srcStride = srcImgBitmapData.Stride;
var dstImg = new Bitmap(rectangle.Width, rectangle.Height, srcImg.PixelFormat);
var dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
var dstPtr = (byte*)dstImgBitmapData.Scan0.ToPointer();
var dstStride = dstImgBitmapData.Stride;
for (int y = 0; y < rectangle.Height; y++)
{
memcpy(dstPtr, srcPtr, dstStride);
srcPtr += srcStride;
dstPtr += dstStride;
}
srcImg.UnlockBits(srcImgBitmapData);
dstImg.UnlockBits(dstImgBitmapData);
return dstImg;
}
this class gets your bitmap obj . then lockbits. in ctor.
When you call crop method, it uses memcpy to copy the desired region to new bmp.
lockbits: tells the garbage collector to NOT move my bits anywhere, cuz im gonna modify it by pointers (scan0).
memcpy : fastest copy. can copy memory blocks. optimized by some experts.
why memcpy fast?
instead of copying byte by byte, (widthheight) times memory access .
memcpy does it block by block, much more less than wh times .
internal unsafe sealed class FastImageCroper : IDisposable
{
private readonly Bitmap _srcImg;
private readonly BitmapData _srcImgBitmapData;
private readonly int _bpp;
private readonly byte* _srtPrt;
public FastImageCroper(Bitmap srcImg)
{
_srcImg = srcImg;
_srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
_bpp = _srcImgBitmapData.Stride / _srcImgBitmapData.Width; // == 4
_srtPrt = (byte*)_srcImgBitmapData.Scan0.ToPointer();
}
public Bitmap Crop(Rectangle rectangle)
{
Bitmap dstImg = new Bitmap(rectangle.Width, rectangle.Height, _srcImg.PixelFormat);
BitmapData dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
byte* dstPrt = (byte*)dstImgBitmapData.Scan0.ToPointer();
byte* srcPrt = _srtPrt + rectangle.Y*_srcImgBitmapData.Stride + rectangle.X*_bpp;
for (int y = 0; y < rectangle.Height; y++)
{
int srcIndex = y * _srcImgBitmapData.Stride;
int croppedIndex = y * dstImgBitmapData.Stride;
memcpy(dstPrt + croppedIndex, srcPrt + srcIndex, dstImgBitmapData.Stride);
}
dstImg.UnlockBits(dstImgBitmapData);
return dstImg;
}
public void Dispose()
{
_srcImg.UnlockBits(_srcImgBitmapData);
}
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int memcpy(byte* dest, byte* src, long count);
}
Related
See: Save a 32-bit Bitmap as 1-bit .bmp file in C#
Listing #1
public static Bitmap BitmapTo1Bpp(Bitmap source)
{
int Width = source.Width;
int Height = source.Height;
Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
byte[] destBytes = new byte[(Width + 7) / 8];//19 bytes
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
Color c = source.GetPixel(x, y);
if (x % 8 == 0)
{
destBytes[x / 8] = 0;
}
if (c.GetBrightness() >= 0.5)
{
destBytes[x / 8] |= (byte)(0x80 >> (x % 8));
}
}
Marshal.Copy(destBytes, 0, (IntPtr)((long)destBmpData.Scan0 + destBmpData.Stride * y), destBytes.Length);
}
dest.UnlockBits(destBmpData);
return dest;
}
Listing #2
public static Bitmap BitmapTo1Bpp222(Bitmap source)
{
int Width = source.Width;
int Height = source.Height;
Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
int destStride = destBmpData.Stride;
int destSize = Math.Abs(destStride) * Height;
byte[] destBytes = new byte[destSize];
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
Color c = source.GetPixel(x, y);
if (x % 8 == 0)
{
destBytes[x*y / 8] = 0;
}
if (c.GetBrightness() >= 0.5)
{
destBytes[x*y / 8] |= (byte)(0x80 >> (x % 8));
}
}
}
Marshal.Copy(destBytes, 0, destBmpData.Scan0, destBytes.Length);
dest.UnlockBits(destBmpData);
return dest;
}
See the position of Marshal.Copy().
Why does the Listing #1 work, but Listing #2 doesn't?
What modification can make the Listing #2 work?
Both of these are overly complicated. LockBits can convert data to 1bpp. Just open the source as 1bpp, copy its data into the new 1bpp image, and you're done.
I'm also quite baffled by the combination of GetPixel and LockBits. Usually, using LockBits means you realized that GetPixel is a horribly slow waste of time that performs a LockBits internally on every call.
public static Bitmap BitmapTo1Bpp(Bitmap source)
{
Rectangle rect = new Rectangle(0, 0, source.Width, source.Height);
Bitmap dest = new Bitmap(rect.Width, rect.Height, PixelFormat.Format1bppIndexed);
dest.SetResolution(source.HorizontalResolution, source.VerticalResolution);
BitmapData sourceData = source.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
BitmapData targetData = dest.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
Int32 actualDataWidth = (rect.Width + 7) / 8;
Int32 h = source.Height;
Int32 origStride = sourceData.Stride;
Int32 targetStride = targetData.Stride;
// buffer for one line of image data.
Byte[] imageData = new Byte[actualDataWidth];
Int64 sourcePos = sourceData.Scan0.ToInt64();
Int64 destPos = targetData.Scan0.ToInt64();
// Copy line by line, skipping by stride but copying actual data width
for (Int32 y = 0; y < h; y++)
{
Marshal.Copy(new IntPtr(sourcePos), imageData, 0, actualDataWidth);
Marshal.Copy(imageData, 0, new IntPtr(destPos), actualDataWidth);
sourcePos += origStride;
destPos += targetStride;
}
dest.UnlockBits(targetData);
source.UnlockBits(sourceData);
return dest;
}
Do note that conversion of data to indexed formats should be avoided in cases where your result is not 1bpp for pure black and white. Indexed formats are paletted, and doing it this way will not do any kind of reduction to an optimised palette approaching the image colours; it will just change the colours on the image to their closest match on the standard palette for this bit depth. For 1bpp this is just black and white, which is perfect, but for 4bpp and 8bpp it will give pretty bad results.
Also note that for some reason you can't convert from a higher to a lower indexed pixel format; it will throw an exception. Since you can convert a bitmap to 32-bit using the new Bitmap(Bitmap) constructor, this problem can easily be avoided by calling the code like this:
public static Bitmap ConvertTo1Bpp(Bitmap source)
{
PixelFormat sourcePf = source.PixelFormat;
if ((sourcePf & PixelFormat.Indexed) == 0 || Image.GetPixelFormatSize(sourcePf) == 1)
return BitmapTo1Bpp(source);
using (Bitmap bm32 = new Bitmap(source))
return BitmapTo1Bpp(bm32);
}
I'm using libtiff.net to make a tiff from a jpeg.
The problem apears when I try to write, because the tiff.writeScanLine returns false, meaning the image isn't written in the tiff.
Why is this happening? And how can I figure out what's wrong?
Here's the code:
private bool creatingTiff()
{
using (Bitmap bmp = new Bitmap(targetFile))
{
using (Tiff tif = Tiff.Open("BitmapTo24BitColorTiff.tif", "w"))
{
byte[] raster = getImageRasterBytes(bmp, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
tif.SetField(TiffTag.IMAGEWIDTH, bmp.Width);
tif.SetField(TiffTag.IMAGELENGTH, bmp.Height);
tif.SetField(TiffTag.COMPRESSION, Compression.OJPEG);
tif.SetField(TiffTag.PHOTOMETRIC, Photometric.YCBCR);
tif.SetField(TiffTag.SUBFILETYPE, 0);
tif.SetField(TiffTag.ROWSPERSTRIP, bmp.Height);
tif.SetField(TiffTag.ORIENTATION, BitMiracle.LibTiff.Classic.Orientation.TOPLEFT);
tif.SetField(TiffTag.XRESOLUTION, bmp.HorizontalResolution);
tif.SetField(TiffTag.YRESOLUTION, bmp.VerticalResolution);
tif.SetField(TiffTag.RESOLUTIONUNIT, ResUnit.INCH);
tif.SetField(TiffTag.BITSPERSAMPLE, 8);
tif.SetField(TiffTag.SAMPLESPERPIXEL, 3);
tif.SetField(TiffTag.JPEGIFOFFSET, 768);
tif.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
for (int i = 0, offset = 0; i < bmp.Height; i++)
{
bool b = tif.WriteScanline(raster, offset, i, 0);
Console.WriteLine("write succes: " + b);
offset += stride;
}
}
System.Diagnostics.Process.Start("BitmapTo24BitColorTiff.tif");
return true;
}
}
private static byte[] getImageRasterBytes(Bitmap bmp, System.Drawing.Imaging.PixelFormat format)
{
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
byte[] bits = null;
try
{
// Lock the managed memory
BitmapData bmpdata = bmp.LockBits(rect, ImageLockMode.ReadWrite, format);
// Declare an array to hold the bytes of the bitmap.
bits = new byte[bmpdata.Stride * bmpdata.Height];
// Copy the values into the array.
System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, bits, 0, bits.Length);
// Release managed memory
bmp.UnlockBits(bmpdata);
}
catch
{
return null;
}
return bits;
}
private static void convertSamples(byte[] data, int width, int height)
{
int stride = data.Length / height;
const int samplesPerPixel = 3;
for (int y = 0; y < height; y++)
{
int offset = stride * y;
int strideEnd = offset + width * samplesPerPixel;
for (int i = offset; i < strideEnd; i += samplesPerPixel)
{
byte temp = data[i + 2];
data[i + 2] = data[i];
data[i] = temp;
}
}
}
The tif-tags are written, but the image itself isn't. Perhaps if someone can point me in the direction of the library developers blog (BitMiracle), I can direct my problem to them directly.
I think your code has the following errors:
You can not use Compression.OJPEG for new images. Old-JPEGs can only be de-compressed.
You probably should not specify TiffTag.JPEGIFOFFSET value by hand. The library will specify proper value itself.
You are trying to write the whole strip using WriteScanline method. You should use WriteEncodedStrip instead.
It also helps to review warnings emitted by the library (it emits them into console).
I am writing a function to get difference between two bitmap images in visual studio 2010. I have a function that takes two bitmap images as parameters, I use unlock bits to get data of each pixel,both the images are of equal resolution and dimensions.
When I use unlock bits for only one image it works well, but when I use it for both simultaneously in the same function it gives an exception
BITMAP REGION IS ALREADY LOCKED
code:
public Bitmap Invert(Bitmap b,Bitmap c)
{
BitmapData bmData =
b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
// for image 2
BitmapData data2 =
c.LockBits(new System.Drawing.Rectangle(0,
0,
c.Width,
c.Height),
ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
int stride1 = data2.Stride;
System.IntPtr Scan1 = data2.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
byte* q = (byte*)(void*)Scan1;
nOffset = stride - b.Width * 3;
nWidth = b.Width * 3;
for (y = 0; y < b.Height; ++y)
{
for (x = 0; x < nWidth; ++x)
{
p[0] = (byte)(p[0]-q[0]);
++p;
++q;
}
p += nOffset;
q += nOffset;
}
}
b.UnlockBits(bmData);
c.UnlockBits(data2);
return b;
}
Becuase you only need to compare images , I would suggesst opening them in read mode only i.e:
BitmapData data2 = c.LockBits(new System.Drawing.Rectangle(0,
0,
c.Width,
c.Height),
ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
I'm loading up an image into a BitmapImage and convert this into an array of PixelColor's. What I would like is to be able to manipulate some of these pixels to make them Lighter/Darker/Transparent but I can't seem to get it to work. It does work to set it to a certain color but the alpha channel is ignored.
It's a WPF app using C#4
Thanks!
And the code...
namespace BitmapTest {
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Image = System.Windows.Controls.Image;
public partial class MainWindow {
public MainWindow() {
InitializeComponent();
LoadImage();
}
[DllImport("gdi32")]
private static extern int DeleteObject(IntPtr o);
public static BitmapSource LoadBitmap(Bitmap source) {
var ip = source.GetHbitmap();
try {
return Imaging.CreateBitmapSourceFromHBitmap(ip,
IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
} finally {
DeleteObject(ip);
}
}
private void LoadImage() {
var i = new Image();
var src = new BitmapImage();
src.BeginInit();
src.UriSource = new Uri("road.jpg", UriKind.Relative);
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
var pixels = GetPixels(src);
for (var x = 0; x < Math.Min(50, pixels.GetLength(0)); x++) {
for (var y = 0; y < Math.Min(50, pixels.GetLength(1)); y++) {
pixels[x, y] = new PixelColor {Alpha = 100, Red = pixels[x, y].Red, Green = pixels[x, y].Green, Blue = pixels[x, y].Blue};
}
}
var bitmap = new WriteableBitmap(src.PixelWidth, src.PixelHeight, src.DpiX, src.DpiY, src.Format, src.Palette);
PutPixels(bitmap, pixels, 0, 0);
i.Source = bitmap;
i.Stretch = Stretch.Fill;
RootGrid.Children.Add(i);
}
public PixelColor[,] GetPixels(BitmapSource source) {
if (source.Format != PixelFormats.Bgra32)
source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
var pixels = source.CopyPixels();
return pixels;
}
public void PutPixels(WriteableBitmap bitmap, PixelColor[,] pixels, int x, int y) {
var width = pixels.GetLength(0);
var height = pixels.GetLength(1);
var sourceRect = new Int32Rect(0, 0, width, height);
bitmap.WritePixels(sourceRect, pixels, width*4, x, y);
}
[StructLayout(LayoutKind.Sequential)]
public struct PixelColor {
public byte Blue;
public byte Green;
public byte Red;
public byte Alpha;
}
}
public static class BitmapSourceHelper {
public static MainWindow.PixelColor[,] CopyPixels(this BitmapSource source) {
if (source.Format != PixelFormats.Bgra32)
source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
var pixels = new MainWindow.PixelColor[source.PixelWidth, source.PixelHeight];
var stride = source.PixelWidth * ((source.Format.BitsPerPixel + 7) / 8);
var pinnedPixels = GCHandle.Alloc(pixels, GCHandleType.Pinned);
source.CopyPixels(
new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight),
pinnedPixels.AddrOfPinnedObject(),
pixels.GetLength(0) * pixels.GetLength(1) * 4,
stride);
pinnedPixels.Free();
return pixels;
}
}
}
Here is an example of lightening and darkening an image in a windows forms app. The code here is based the Brightness filter section of an article on The Code Project site: Image Processing for Dummies with C# and GDI+ Part 1.
public Bitmap Lighten(Bitmap bitmap, int amount)
{
if (amount < -255 || amount > 255)
return bitmap;
// GDI+ still lies to us - the return format is BGR, NOT RGB.
BitmapData bmData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
int nVal = 0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nOffset = stride - bitmap.Width * 3;
int nWidth = bitmap.Width * 3;
for (int y = 0; y < bitmap.Height; ++y)
{
for (int x = 0; x < nWidth; ++x)
{
nVal = (int)(p[0] + amount);
if (nVal < 0) nVal = 0;
if (nVal > 255) nVal = 255;
p[0] = (byte)nVal;
++p;
}
p += nOffset;
}
}
bitmap.UnlockBits(bmData);
return bitmap;
}
private void btnLighten_Click(object sender, EventArgs e)
{
Bitmap image = pictureBox1.Image as Bitmap;
pictureBox1.Image = Lighten(image, 10);
}
private void btnDarken_Click(object sender, EventArgs e)
{
Bitmap image = pictureBox1.Image as Bitmap;
pictureBox1.Image = Lighten(image, -10);
}
This code would adjusts a given rectangle in the image, according to brightness, contrast, alpha, gamma:
Bitmap clonedImage = originalImage.Clone(rect, originalImage.PixelFormat);
float gamma = 1.0f; // no change in gamma
float adjustedBrightness = brightness - 1.0f;
// create matrix that will brighten and contrast the image
float[][] ptsArray ={
new float[] {contrast, 0, 0, 0, 0}, // scale red
new float[] {0, contrast, 0, 0, 0}, // scale green
new float[] {0, 0, contrast, 0, 0}, // scale blue
new float[] {0, 0, 0, alpha, 0},
new float[] {adjustedBrightness, adjustedBrightness, adjustedBrightness, 0, 1}};
var imageAttributes = new ImageAttributes();
imageAttributes.ClearColorMatrix();
imageAttributes.SetColorMatrix(new ColorMatrix(ptsArray), ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
imageAttributes.SetGamma(gamma, ColorAdjustType.Bitmap);
// Copy back to the original image from the cloned image
Graphics g = Graphics.FromImage(originalImage);
g.DrawImage(clonedImage, new Rectangle(0, 0, clonedImage.Width, clonedImage.Height)
, rect.left, rect.top, rect.Width, rect.Height,
GraphicsUnit.Pixel, imageAttributes);
g.Flush();
I was wondering if anyone could shed some light on improvements I can do in making this compositing algorithm faster. What is does is takes 3 images splits them up to get the 1st Images Red Channel, 2nd Images Green channel and the 3rd Images Blue channel and composites them together into 1 new image. Now it works but at an excruciatingly slow pace. The reason i think down to the pixel by pixel processing it has to do on all image components.
The process is to :
For all images:
Extract respective R G and B values -> composite into 1 image -> Save new Image.
foreach (Image[] QRE2ImgComp in QRE2IMGArray)
{
Globals.updProgress = "Processing frames: " + k + " of " + QRE2IMGArray.Count + " frames done.";
QRMProgressUpd(EventArgs.Empty);
Image RedLayer = GetRedImage(QRE2ImgComp[0]);
QRE2ImgComp[0] = RedLayer;
Image GreenLayer = GetGreenImage(QRE2ImgComp[1]);
QRE2ImgComp[1] = GreenLayer;
Image BlueLayer = GetBlueImage(QRE2ImgComp[2]);
QRE2ImgComp[2] = BlueLayer;
Bitmap composite = new Bitmap(QRE2ImgComp[0].Height, QRE2ImgComp[0].Width);
Color Rlayer,Glayer,Blayer;
byte R, G, B;
for (int y = 0; y < composite.Height; y++)
{
for (int x = 0; x < composite.Width; x++)
{
//pixelColorAlpha = composite.GetPixel(x, y);
Bitmap Rcomp = new Bitmap(QRE2ImgComp[0]);
Bitmap Gcomp = new Bitmap(QRE2ImgComp[1]);
Bitmap Bcomp = new Bitmap(QRE2ImgComp[2]);
Rlayer = Rcomp.GetPixel(x, y);
Glayer = Gcomp.GetPixel(x, y);
Blayer = Bcomp.GetPixel(x, y);
R = (byte)(Rlayer.R);
G = (byte)(Glayer.G);
B = (byte)(Blayer.B);
composite.SetPixel(x, y, Color.FromArgb((int)R, (int)G, (int)B));
}
}
Globals.updProgress = "Saving frame...";
QRMProgressUpd(EventArgs.Empty);
Image tosave = composite;
Globals.QRFrame = tosave;
tosave.Save("C:\\QRItest\\E" + k + ".png", ImageFormat.Png);
k++;
}
For reference here is the red channel filter method relatively the same for blue and green:
public Image GetRedImage(Image sourceImage)
{
Bitmap bmp = new Bitmap(sourceImage);
Bitmap redBmp = new Bitmap(sourceImage.Width, sourceImage.Height);
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
Color pxl = bmp.GetPixel(x, y);
Color redPxl = Color.FromArgb((int)pxl.R, 0, 0);
redBmp.SetPixel(x, y, redPxl);
}
}
Image tout = (Image)redBmp;
return tout;
}
Move these
Bitmap Rcomp = new Bitmap(QRE2ImgComp[0]);
Bitmap Gcomp = new Bitmap(QRE2ImgComp[1]);
Bitmap Bcomp = new Bitmap(QRE2ImgComp[2]);
outside the for-loops!
Other very important points:
avoid using GetPixel - it is VERY SLOW!
Checkout LockBits etc. - this is how pixel-level access is usually done in .NET
Consider using a 3rd-party library (free or commercial)... several have some optimized method built-in to do what you are trying to achieve...
I totally agree with the points Yahia listed in his answer to improve performance. I'd like to add one more point regarding performance. You could use the Parallel class of the .Net Framework to parallelize the execution of your for loops. The following example makes use of the LockBits method and the Parallel class to improve performance (assuming 32 bits per pixel (PixelFormat.Format32bppArgb)):
public unsafe static Bitmap GetBlueImagePerf(Image sourceImage)
{
int width = sourceImage.Width;
int height = sourceImage.Height;
Bitmap bmp = new Bitmap(sourceImage);
Bitmap redBmp = new Bitmap(width, height, bmp.PixelFormat);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
BitmapData bd2 = redBmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
byte* source = (byte*)bd.Scan0.ToPointer();
byte* target = (byte*)bd2.Scan0.ToPointer();
int stride = bd.Stride;
Parallel.For(0, height, (y1) =>
{
byte* s = source + (y1 * stride);
byte* t = target + (y1 * stride);
for (int x = 0; x < width; x++)
{
// use t[1], s[1] to access green channel
// use t[2], s[2] to access red channel
t[0] = s[0];
t += 4; // Add bytes per pixel to current position.
s += 4; // For other pixel formats this value is different.
}
});
bmp.UnlockBits(bd);
redBmp.UnlockBits(bd2);
return redBmp;
}
public unsafe static void DoImageConversion()
{
Bitmap RedLayer = GetRedImagePerf(Image.FromFile("image_path1"));
Bitmap GreenLayer = GetGreenImagePerf(Image.FromFile("image_path2"));
Bitmap BlueLayer = GetBlueImagePerf(Image.FromFile("image_path3"));
Bitmap composite =
new Bitmap(RedLayer.Width, RedLayer.Height, RedLayer.PixelFormat);
BitmapData bd = composite.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
byte* comp = (byte*)bd.Scan0.ToPointer();
BitmapData bdRed = RedLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bdGreen = GreenLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bdBlue = BlueLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* red = (byte*)bdRed.Scan0.ToPointer();
byte* green = (byte*)bdGreen.Scan0.ToPointer();
byte* blue = (byte*)bdBlue.Scan0.ToPointer();
int stride = bdRed.Stride;
Parallel.For(0, bdRed.Height, (y1) =>
{
byte* r = red + (y1 * stride);
byte* g = green + (y1 * stride);
byte* b = blue + (y1 * stride);
byte* c = comp + (y1 * stride);
for (int x = 0; x < bdRed.Width; x++)
{
c[0] = b[0];
c[1] = g[1];
c[2] = r[2];
r += 4; // Add bytes per pixel to current position.
g += 4; // For other pixel formats this value is different.
b += 4; // Use Image.GetPixelFormatSize to get number of bits per pixel
c += 4;
}
});
composite.Save("save_image_path", ImageFormat.Jpeg);
}
Hope, this answer gives you a starting point for improving your code.