First I want to thank Bitmiracle for this great lib. Even while creating very big files, the memory footprint is very low.
A few days ago I ran into a problem where I wanted to create a tiff file bigger than 4GB. I created the tiled tiff file successfully, but it seems that the color of the tiles created beyond 4GB are somehow inverted.
Here the code relevant code:
Usage:
WriteTiledTiff("bigtiff.tiff",BitmapSourceFromBrush(new RadialGradientBrush(Colors.Aqua,Colors.Red), 256));
Methods:
public static BitmapSource BitmapSourceFromBrush(Brush drawingBrush, int size = 32, int dpi = 96)
{
// RenderTargetBitmap = builds a bitmap rendering of a visual
var pixelFormat = PixelFormats.Pbgra32;
RenderTargetBitmap rtb = new RenderTargetBitmap(size, size, dpi, dpi, pixelFormat);
// Drawing visual allows us to compose graphic drawing parts into a visual to render
var drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
// Declaring drawing a rectangle using the input brush to fill up the visual
context.DrawRectangle(drawingBrush, null, new Rect(0, 0, size, size));
}
// Actually rendering the bitmap
rtb.Render(drawingVisual);
return rtb;
}
public static void WriteTiledTiff(string fileName, BitmapSource tile)
{
const int PIXEL_WIDTH = 48000;
const int PIXEL_HEIGHT = 48000;
int iTile_Width = tile.PixelWidth;
int iTile_Height = tile.PixelHeight;
using (Tiff tiff = Tiff.Open(fileName, "w"))
{
tiff.SetField(TiffTag.IMAGEWIDTH, PIXEL_WIDTH);
tiff.SetField(TiffTag.IMAGELENGTH, PIXEL_HEIGHT);
tiff.SetField(TiffTag.COMPRESSION, Compression.NONE);
tiff.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);
tiff.SetField(TiffTag.ROWSPERSTRIP, PIXEL_HEIGHT);
tiff.SetField(TiffTag.XRESOLUTION, 96);
tiff.SetField(TiffTag.YRESOLUTION, 96);
tiff.SetField(TiffTag.BITSPERSAMPLE, 8);
tiff.SetField(TiffTag.SAMPLESPERPIXEL, 3);
tiff.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
tiff.SetField(TiffTag.TILEWIDTH, iTile_Width);
tiff.SetField(TiffTag.TILELENGTH, iTile_Height);
int tileC = 0;
for (int row = 0; row < PIXEL_HEIGHT; row += iTile_Height)
{
for (int col = 0; col < PIXEL_WIDTH; col += iTile_Width)
{
if (tile.Format != PixelFormats.Rgb24) tile = new FormatConvertedBitmap(tile, PixelFormats.Rgb24, null, 0);
int stride = tile.PixelWidth * ((tile.Format.BitsPerPixel + 7) / 8);
byte[] pixels = new byte[tile.PixelHeight * stride];
tile.CopyPixels(pixels, stride, 0);
tiff.WriteEncodedTile(tileC++, pixels, pixels.Length);
}
}
tiff.WriteDirectory();
}
}
The resulted file will be 6,47GB in size. I viewed it with a small tool called "vliv" vilv download
All LibTiff.Net versions including 2.4.500.0 are based on 3.x branch of the original libtiff.
Support for BigTIFF was introduced in 4.x branch of the original libtiff. Thus, at this time there are no LibTiff.Net versions designed to handle BigTiff files / files over 4GB on disk.
EDIT:
LibTiff.Net 2.4.508 adds support for BigTiff.
Related
I'm developing a small C# tool that must be able to load a TIFF image, crop the image to a certain size, and save it as a PNG file.
I have large greyscale TIFF images of about 28000x256 pixels with 32-bit bit depth. When I try to process the images with my tool, it just outputs a blank white image.
Also, when I try to open the original TIFF images (not the ones processed with my tool) with the Windows Photo Viewer, it also shows a blank white image. Some other applications, e.g. ImageJ, display the image correctly. What is the problem here?
My code to load the images looks as follows:
Image image = Bitmap.FromFile(path.LocalPath);
int width = image.Width;
int height = image.Height;
Bitmap bmp = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bmp);
The problem is that C# (or better said the underlying API) can't handle Greyscale images with a Colordepth greater than 8bit.
I'd suggest using LibTiff.NET for Handling TIFF images.
When i faced such an problem, i loaded the TIFF image raw Data into an array
using (var inputImage = Tiff.Open(image, "r"))
{
width = inputImage.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
height = inputImage.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
inputImageData = new byte[width * height * bytePerPixel];
var offset = 0;
for (int i = 0; i < inputImage.NumberOfStrips(); i++)
{
offset += inputImage.ReadRawStrip(i, inputImageData, offset, (int)inputImage.RawStripSize(i));
}
}
The bytes then have to be converted into an array of uint (in my case, imagedata was only 16 bit, so i used ushort) Remember to take care of Endianness of the data!
// has to be done by hand to ensure endiannes is kept correctly.
var outputImageData = new ushort[inputImageData.Length / 2];
for (var i = 0; i < outputImageData.Length; i++)
{
outputImageData[i] = (ushort)((inputImageData[i * 2 + 1]) + (ushort)(inputImageData[i * 2] << 8));
}
You can then manipulate the image using normal Array Operations. I'd suggest you to use normal Array operations and not Lambda-Expressions, as they are much faster. (in My Scenario 100s vs 2s Runtime)
Finally you can save the image using LibTiff again
using (var output = Tiff.Open(imageout, "w"))
{
output.SetField(TiffTag.IMAGEWIDTH, width);
output.SetField(TiffTag.IMAGELENGTH, height);
output.SetField(TiffTag.SAMPLESPERPIXEL, 1);
output.SetField(TiffTag.BITSPERSAMPLE, 16);
output.SetField(TiffTag.ROWSPERSTRIP, height);
output.SetField(TiffTag.PHOTOMETRIC, Photometric.MINISBLACK);
output.SetField(TiffTag.FILLORDER, FillOrder.MSB2LSB);
// Transform to Byte-Array
var buffer = new byte[outputImageData.Length * sizeof(ushort)];
Buffer.BlockCopy(outputImageData, 0, buffer, 0, buffer.Length);
// Write it to Image
output.WriteRawStrip(0, buffer, buffer.Length);
}
I'm saving a bitmap to a file on my hard drive inside of a loop (All the jpeg files within a directory are being saved to a database). The save works fine the first pass through the loop, but then gives the subject error on the second pass. I thought perhaps the file was getting locked so I tried generating a unique file name for each pass, and I'm also using Dispose() on the bitmap after the file get saved. Any idea what is causing this error?
Here is my code:
private string fileReducedDimName = #"c:\temp\Photos\test\filePhotoRedDim";
...
foreach (string file in files)
{
int i = 0;
//if the file dimensions are big, scale the file down
Stream photoStream = File.OpenRead(file);
byte[] photoByte = new byte[photoStream.Length];
photoStream.Read(photoByte, 0, System.Convert.ToInt32(photoByte.Length));
Image image = Image.FromStream(new MemoryStream(photoByte));
Bitmap bm = ScaleImage(image);
bm.Save(fileReducedDimName + i.ToString() + ".jpg", ImageFormat.Jpeg);//error occurs here
Array.Clear(photoByte,0, photoByte.Length);
bm.Dispose();
i ++;
}
...
Thanks
Here's the scale image code: (this seems to be working ok)
protected Bitmap ScaleImage(System.Drawing.Image Image)
{
//reduce dimensions of image if appropriate
int destWidth;
int destHeight;
int sourceRes;//resolution of image
int maxDimPix;//largest dimension of image pixels
int maxDimInch;//largest dimension of image inches
Double redFactor;//factor to reduce dimensions by
if (Image.Width > Image.Height)
{
maxDimPix = Image.Width;
}
else
{
maxDimPix = Image.Height;
}
sourceRes = Convert.ToInt32(Image.HorizontalResolution);
maxDimInch = Convert.ToInt32(maxDimPix / sourceRes);
//Assign size red factor based on max dimension of image (inches)
if (maxDimInch >= 17)
{
redFactor = 0.45;
}
else if (maxDimInch < 17 && maxDimInch >= 11)
{
redFactor = 0.65;
}
else if (maxDimInch < 11 && maxDimInch >= 8)
{
redFactor = 0.85;
}
else//smaller than 8" dont reduce dimensions
{
redFactor = 1;
}
destWidth = Convert.ToInt32(Image.Width * redFactor);
destHeight = Convert.ToInt32(Image.Height * redFactor);
Bitmap bm = new Bitmap(destWidth, destHeight,
PixelFormat.Format24bppRgb);
bm.SetResolution(Image.HorizontalResolution, Image.VerticalResolution);
Graphics grPhoto = Graphics.FromImage(bm);
grPhoto.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
grPhoto.DrawImage(Image,
new Rectangle(0, 0, destWidth, destHeight),
new Rectangle(0, 0, Image.Width, Image.Height),
GraphicsUnit.Pixel);
grPhoto.Dispose();
return bm;
}
If I'm reading the code right, your i variable is zero every time through the loop.
It is hard to diagnose exactly what is wrong, I would recommend that you use using statements to ensure that your instances are getting disposed of properly, but it looks like they are.
I originally thought it might be an issue with the ScaleImage. So I tried a different resize function (C# GDI+ Image Resize Function) and it worked, but i is always set to zero at beginning of each loop. Once you move i's initialization outside of the loop your scale method works as well.
private void MethodName()
{
string fileReducedDimName = #"c:\pics";
int i = 0;
foreach (string file in Directory.GetFiles(fileReducedDimName, "*.jpg"))
{
//if the file dimensions are big, scale the file down
using (Image image = Image.FromFile(file))
{
using (Bitmap bm = ScaleImage(image))
{
bm.Save(fileReducedDimName + #"\" + i.ToString() + ".jpg", ImageFormat.Jpeg);//error occurs here
//this is all redundant code - do not need
//Array.Clear(photoByte, 0, photoByte.Length);
//bm.Dispose();
}
}
//ResizeImage(file, 50, 50, fileReducedDimName +#"\" + i.ToString()+".jpg");
i++;
}
}
Could some rewrite the following function to use any optimized mechanism? I'm pretty sure that this is not the way to proceed, copying pixel by pixel.
I have read about AlphaBlend, or BitBlt, but I'm not used to native code.
public static Bitmap GetAlphaBitmap(Bitmap srcBitmap)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
try
{
for (int y = 0; y <= srcData.Height - 1; y++)
{
for (int x = 0; x <= srcData.Width - 1; x++)
{
Color pixelColor = Color.FromArgb(
Marshal.ReadInt32(srcData.Scan0, (srcData.Stride * y) + (4 * x)));
result.SetPixel(x, y, pixelColor);
}
}
}
finally
{
srcBitmap.UnlockBits(srcData);
}
return result;
}
IMPORTANT NOTE: The source image has a wrong pixel format (Format32bppRgb), so I need to adjust the alpha channel. This is the only mechanism that works for me.
The reason why the src image has a wrong pixel format is explained here.
I tried the following options without luck:
Creating a new image and draw the src image using the Graphics.DrawImage from src. Did not preserve the alpha.
Creating a new image using the Scan0 form src. Works fine, but has a problem when the GC dispose the src image (explained in this other post);
This solution is the only that really works, but I know that is not optimal. I need to know how to do it using the WinAPI or other optimal mechanism.
Thank you very much!
Assuming the source image does infact have 32 bits per pixel, this should be a fast enough implementation using unsafe code and pointers. The same can be achieved using marshalling, though at a performance loss of around 10%-20% if I remember correctly.
Using native methods will most likely be faster but this should already be orders of magnitude faster than SetPixel.
public unsafe static Bitmap Clone32BPPBitmap(Bitmap srcBitmap)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);
int* srcScan0 = (int*)srcData.Scan0;
int* resScan0 = (int*)resData.Scan0;
int numPixels = srcData.Stride / 4 * srcData.Height;
try
{
for (int p = 0; p < numPixels; p++)
{
resScan0[p] = srcScan0[p];
}
}
finally
{
srcBitmap.UnlockBits(srcData);
result.UnlockBits(resData);
}
return result;
}
Here is the safe version of this method using marshalling:
public static Bitmap Copy32BPPBitmapSafe(Bitmap srcBitmap)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);
Int64 srcScan0 = srcData.Scan0.ToInt64();
Int64 resScan0 = resData.Scan0.ToInt64();
int srcStride = srcData.Stride;
int resStride = resData.Stride;
int rowLength = Math.Abs(srcData.Stride);
try
{
byte[] buffer = new byte[rowLength];
for (int y = 0; y < srcData.Height; y++)
{
Marshal.Copy(new IntPtr(srcScan0 + y * srcStride), buffer, 0, rowLength);
Marshal.Copy(buffer, 0, new IntPtr(resScan0 + y * resStride), rowLength);
}
}
finally
{
srcBitmap.UnlockBits(srcData);
result.UnlockBits(resData);
}
return result;
}
Edit: Your source image has a negative stride, which means the scanlines are stored upside-down in memory (only on the y axis, rows still go from left to right). This effectively means that .Scan0 returns the first pixel of the last row of the bitmap.
As such I modified the code to copy one row at a time.
notice: I've only modified the safe code. The unsafe code still assumes positive strides for both images!
Try the Bitmap Clone method.
A utility class in my Codeblocks library http://codeblocks.codeplex.com allows you to transform a source image to any other image using LINQ.
See this sample here: http://codeblocks.codeplex.com/wikipage?title=Linq%20Image%20Processing%20sample&referringTitle=Home
While the sample transforms the same image format between source and destination, you could change things around, as well.
Note that I have clocked this code and it is much faster than even unsafe code for large images because it uses cached full-row read ahead.
I've got to following function which is called to change the resolution of an image. I want to do this so uploaded image with for example 300dpi will be modified to 72dpi (for web). This question is related to another question here on SO where i'm working on.
I'm creation an extension method for this to be able to use this function on more places in my application, instead of only when uploading new files. (See above mentioned question)
public static byte[] SetDpiTo72(this byte[] imageToFit, string mimeType, Size newSize)
{
using (MemoryStream memoryStream = new MemoryStream(), newMemoryStream = new MemoryStream())
{
memoryStream.Write(imageToFit, 0, imageToFit.Length);
var originalImage = new Bitmap(memoryStream);
using (var canvas = Graphics.FromImage(originalImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage((Image)originalImage,0,0, newSize.Width, newSize.Height);
newBitmap.SetResolution(72, 72);
newBitmap.Save(newMemoryStream, ImageFunctions.GetEncoderInfo(mimeType), null);
}
return newMemoryStream.ToArray();
}
}
The mentioned extension methode is being called in a function similar to the situation below;
if (newSize.Width > originalImage.Width && newSize.Height > originalImage.Height)
{
newSize.Width = originalImage.Width;
newSize.Height = originalImage.Height;
uploadedFileBuffer = uploadedFileBuffer.SetDpiTo72(uploadedFile.ContentType, newSize);
return CreateFile(newSize, uploadedFile, uploadedFileBuffer);
}
The bytearray coming in is the file as an bytearray. It already has the correct size, but I want to change the resolution to 72dpi. However after exectution and saving the image the resolution is still the originale entered resolution, which is 300dpi. How can I do this?
UPDATE AFTER SEVERAL ANSWERS:
public static byte[] SetDpiTo72(this byte[] imageToFit, string mimeType, Size newSize)
{
using (MemoryStream memoryStream = new MemoryStream(), newMemoryStream = new MemoryStream())
{
memoryStream.Write(imageToFit, 0, imageToFit.Length);
var originalImage = new Bitmap(memoryStream);
using (var canvas = Graphics.FromImage(originalImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage((Image)originalImage,0,0, newSize.Width, newSize.Height);
originalImage.SetResolution(72, 72);
var epQuality = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75);
var epParameters = new EncoderParameters(1);
epParameters.Param[0] = epQuality;
Image newimg = Image.FromStream(memoryStream);
//Getting an GDI+ exception after the execution of this line.
newimg.Save("C:\\test1234.jpg", ImageFunctions.GetEncoderInfo(mimeType), epParameters);
originalImage.Save("test.jpg", ImageFormat.Jpeg);
//This line give me an Argumentexception - Parameter is not valid.
//originalImage.Save(newMemoryStream, ImageFunctions.GetEncoderInfo(mimeType), epParameters);
//newMemoryStream.Close();
}
return newMemoryStream.ToArray();
}
}
The stackstrace which comes with the exception is telling me the following;
at System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams)
at Extensions.ByteArrayExtensions.SetDpiTo72(Byte[] imageToFit, String mimeType, Size newSize) in C:\Website\Project\Extensions\ByteArrayExtensions.cs:line 356
at CMS.Presentation.FileFunctions.CreateFullsizeImage(HttpPostedFileBase uploadedFile, Size newSize, Byte[] uploadedFileBuffer) in C:\Website\Project\CMS.Presentation\FileFunctions.cs:line 197
at CMS.Presentation.FileFunctions.CreateFile(HttpPostedFileBase uploadedFile, INodeService nodeservice, Guid userId, Node parentNode) in C:\Website\Project\CMS.Presentation\FileFunctions.cs:line 53
In the mean time I've also developed another function (see below) resizing just a bitmap. And this seem to work correctly. I can't use this function with my current implementation though because it returns just an Bitmap. Or should i change everything to work with bitmaps?
private static Bitmap ResizeImage(Image image, int width, int height)
{
var frameCount = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
var newDimensions = ImageFunctions.GenerateImageDimensions(image.Width, image.Height, width, height);
Bitmap resizedImage;
if (frameCount > 1)
{
//we have a animated GIF
resizedImage = ResizeAnimatedGifImage(image, width, height);
}
else
{
resizedImage = (Bitmap)image.GetThumbnailImage(newDimensions.Width, newDimensions.Height, null, IntPtr.Zero);
}
resizedImage.SetResolution(72,72);
return resizedImage;
}
Ok, I tried it only on files on harddrive, but it should work with streams too.
Bitmap bitmap = new Bitmap(loadFrom);
Bitmap newBitmap = new Bitmap(bitmap);
newBitmap.SetResolution(72, 72);
newBitmap.Save(saveTo);
Took me a while, but I finally found the problem!
The problem lied in the ResizeImage function I used. In the 'GetThumbnailImage' to be specific. I ran into another problem with blurry images, which was explainable because GetThumbnailImage would stretch up the created ThumbNail to the desired size. And the resolution off the thumbnail never changes.
private static Bitmap ResizeImage(Image image, int width, int height)
{
var frameCount = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
var newDimensions = ImageFunctions.GenerateImageDimensions(image.Width, image.Height, width, height);
Bitmap resizedImage;
if (frameCount > 1)
{
//we have a animated GIF
resizedImage = ResizeAnimatedGifImage(image, width, height);
}
else
{
resizedImage = (Bitmap)image.GetThumbnailImage(newDimensions.Width, newDimensions.Height, null, IntPtr.Zero);
}
resizedImage.SetResolution(72,72);
return resizedImage;
}
By modifying the function above to the function below I was able to solve the problem using Graphics.DrawImage to redraw the new image before rendering it. Also the GenerateImageDimensions was slightly modified. This taken together the problem was solved.
private static Bitmap ResizeImage(Image image, int width, int height)
{
var frameCount = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
var newDimensions = ImageFunctions.GenerateImageDimensions(image.Width, image.Height, width, height);
var resizedImage = new Bitmap(newDimensions.Width, newDimensions.Height);
if (frameCount > 1)
{
//we have a animated GIF
resizedImage = ResizeAnimatedGifImage(image, width, height);
}
else
{
//we have a normal image
using (var gfx = Graphics.FromImage(resizedImage))
{
gfx.SmoothingMode = SmoothingMode.HighQuality;
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
var targRectangle = new Rectangle(0, 0, newDimensions.Width, newDimensions.Height);
var srcRectangle = new Rectangle(0, 0, image.Width, image.Height);
gfx.DrawImage(image, targRectangle, srcRectangle, GraphicsUnit.Pixel);
}
}
return resizedImage;
}
By "changing the resolution", do you actually mean you want to reduce the number of pixels in the image by 72/300? I.e. change a 4000x3000 image to 960x720?
If so, I can't see where your code actually does that. The overload of DrawImage() you're using does this:
Draws the specified image, using its original physical size, at the location specified by a coordinate pair.
Which is exactly what is happening.
Try one of the other overloads such as this one:
Draws the specified Image at the specified location and with the specified size.
for example:
// Create image.
Image newImage = Image.FromFile("SampImag.jpg");
// Create coordinates for upper-left corner of image and for size of image.
int x = 0;
int y = 0;
int width = 450;
int height = 150;
// Draw image to screen.
e.Graphics.DrawImage(newImage, x, y, width, height);
EDIT: per the comments, I understand the OP wants to reduce file size without reducing pixel count. Therefore the files must be recompressed.
I've borrowed some sample code from here:
ImageCodecInfo iciJpegCodec = null;
// This will specify the image quality to the encoder. Change the value of 75 from 0 to 100, where 100 is best quality, but highest file size.
EncoderParameter epQuality = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75);
// Get all image codecs that are available
ImageCodecInfo[] iciCodecs = ImageCodecInfo.GetImageEncoders();
// Store the quality parameter in the list of encoder parameters
EncoderParameters epParameters = new EncoderParameters(1);
epParameters.Param[0] = epQuality;
// Loop through all the image codecs
for (int i = 0; i < iciCodecs.Length; i++)
{
// Until the one that we are interested in is found, which is image/jpeg
if (iciCodecs[i].MimeType == "image/jpeg")
{
iciJpegCodec = iciCodecs[i];
break;
}
}
// Create a new Image object from the current file
Image newImage = Image.FromFile(strFile);
// Get the file information again, this time we want to find out the extension
FileInfo fiPicture = new FileInfo(strFile);
// Save the new file at the selected path with the specified encoder parameters, and reuse the same file name
newImage.Save(outputPath + "\\" + fiPicture.Name, iciJpegCodec, epParameters);
Rob, I believe that issue with your code is at saving the image - the actual digital image data would be certain number of dots/pixels i.e. (m x n) and setting resolution at bitmap wouldn't/shouldn't change the number dots (and hence physical byte size of image). The resolution information will be stored in the image header (to be used by programs while printing/editing images) - what happens if you store the new bitmap to file instead of mem stream
newBitmap.Save("c:\test.png", ImageFormat.Png);
Check dpi for above file from file -> properties -> summary (advanced). It should be 72 dpi.
I need a C# function that will take a Byte[] of an 8 bit grayscale TIFF, and return a Byte[] of a 1 bit (black & white) TIFF.
I'm fairly new to working with TIFFs, but the general idea is that we need to convert them from grayscale or color to black and white/monochrome/binary image format.
We receive the images via a WCF as a Byte[], then we need to make this conversion to black & white in order to send them to a component which does further processing. We do not plan at this point, to ever save them as files.
For reference, in our test client, this is how we create the Byte[]:
FileStream fs = new FileStream("test1.tif", FileMode.Open, FileAccess.Read);
this.image = new byte[fs.Length];
fs.Read(this.image, 0, System.Convert.ToInt32(fs.Length));
fs.Close();
--------update---------
I think there may be more than 1 good answer here, but we ended up using the code from the CodeProject site with the following method added to overload the convert function to accept Byte[] as well as bitmap:
public static Byte[] ConvertToBitonal(Byte[] original)
{
Bitmap bm = new Bitmap(new System.IO.MemoryStream(original, false));
bm = ConvertToBitonal(bm);
System.IO.MemoryStream s = new System.IO.MemoryStream();
bm.Save(s, System.Drawing.Imaging.ImageFormat.Tiff);
return s.ToArray();
}
There is an article on CodeProject here that describes what you need.
#neodymium has a good answer, but GetPixel/SetPixel will kill performance. Bob Powell has a great method.
C#:
private Bitmap convertTo1bpp(Bitmap img)
{
BitmapData bmdo = img.LockBits(new Rectangle(0, 0, img.Width, img.Height),
ImageLockMode.ReadOnly,
img.PixelFormat);
// and the new 1bpp bitmap
Bitmap bm = new Bitmap(img.Width, img.Height, PixelFormat.Format1bppIndexed);
BitmapData bmdn = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format1bppIndexed);
// scan through the pixels Y by X
for(int y = 0; y < img.Height; y++)
{
for(int x = 0; x < img.Width; x++)
{
// generate the address of the colour pixel
int index = y * bmdo.Stride + x * 4;
// check its brightness
if(Color.FromArgb(Marshal.ReadByte(bmdo.Scan0, index + 2),
Marshal.ReadByte(bmdo.Scan0, index + 1),
Marshal.ReadByte(bmdo.Scan0, index)).GetBrightness() > 0.5F)
{
setIndexedPixel(x, y, bmdn, true); // set it if its bright.
}
}
}
// tidy up
bm.UnlockBits(bmdn);
img.UnlockBits(bmdo);
return bm;
}
private void setIndexedPixel(int x, int y, BitmapData bmd, bool pixel)
{
int index = y * bmd.Stride + (x >> 3);
byte p = Marshal.ReadByte(bmd.Scan0, index);
byte mask = (byte)(0x80 >> (x & 0x7));
if (pixel)
{
p |= mask;
}
else
{
p &= (byte)(mask ^ 0xFF);
}
Marshal.WriteByte(bmd.Scan0, index, p);
}
might want to check out 'Craigs Utility Library' I believe he has that functionality in place.
Craig's Utility Library
My company's product, dotImage, will do this.
Given an image, you can convert from multi-bit to single bit using several methods including simple threshold, global threshold, local threshold, adaptive threshold, dithering (ordered and Floyd Steinberg), and dynamic threshold. The right choice depends on the type of the input image (document, image, graph).
The typical code looks like this:
AtalaImage image = new AtalaImage("path-to-tiff", null);
ImageCommand threshold = SomeFactoryToConstructAThresholdCommand();
AtalaImage finalImage = threshold.Apply(image).Image;
SomeFactoryToConstructAThresholdCommand() is a method that will return a new command that will process the image. It could be as simple as
return new DynamicThresholdCommand();
or
return new GlobalThresholdCommand();
And generally speaking, if you're looking to convert an entire multi-page tiff to black and white, you would do something like this:
// open a sequence of images
FileSystemImageSource source = new FileSystemImageSource("path-to-tiff", true);
using (FileStream outstm = new FileStream("outputpath", FileMode.Create)) {
// make an encoder and a threshold command
TiffEncoder encoder = new TiffEncoder(TiffCompression.Auto, true);
// dynamic is good for documents -- needs the DocumentImaging SDK
ImageCommand threshold = new DynamicThreshold();
while (source.HasMoreImages()) {
// get next image
AtalaImage image = source.AcquireNext();
AtalaImage final = threshold.Apply(image).Image;
try {
encoder.Save(outstm, final, null);
}
finally {
// free memory from current image
final.Dispose();
// release the source image back to the image source
source.Release(image);
}
}
}
First, you would need to know how an X,Y pixel location maps to an index value in you array.
This will depend upon how your Byte[] was constructed.
You need to know the details of your image format - for example, what is the stride?
I don't see 8 bit grayscale TIFF in the PixelFormat enumeration. If it was there, it would tell you what you need to know.
Then, iterate through each pixel and look at its color value.
You need to decide on a threshold value - if the color of the pixel is above the threshold, make the new color white; otherwise, make it black.
If you want to simulate grayscale shading with 1BPP, you could look at more advanced techniques, such as dithering.
Something like this might work, I haven't tested it. (Should be easy to C# it.)
Dim bmpGrayscale As Bitmap = Bitmap.FromFile("Grayscale.tif")
Dim bmpMonochrome As New Bitmap(bmpGrayscale.Width, bmpgrayscale.Height, Imaging.PixelFormat.Format1bppIndexed)
Using gfxMonochrome As Graphics = Graphics.FromImage(bmpMonochrome)
gfxMonochrome.Clear(Color.White)
End Using
For y As Integer = 0 To bmpGrayscale.Height - 1
For x As Integer = 0 To bmpGrayscale.Width - 1
If bmpGrayscale.GetPixel(x, y) <> Color.White Then
bmpMonochrome.SetPixel(x, y, Color.Black)
End If
Next
Next
bmpMonochrome.Save("Monochrome.tif")
This might be a better way still:
Using bmpGrayscale As Bitmap = Bitmap.FromFile("Grayscale.tif")
Using bmpMonochrome As New Bitmap(bmpGrayscale.Width, bmpgrayscale.Height, Imaging.PixelFormat.Format1bppIndexed)
Using gfxMonochrome As Graphics = Graphics.FromImage(bmpMonochrome)
gfxMonochrome.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
gfxMonochrome.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
gfxMonochrome.DrawImage(bmpGrayscale, new Rectangle(0, 0, bmpMonochrome.Width, bmpMonochrome.Height)
End Using
bmpMonochrome.Save("Monochrome.tif")
End Using
End Using
I believe the term you are looking for is "resampling".
pixel by pixel manipulation is extremly slow. 40 times slower than System.DrawImage.
System.Draw image is half solution, corrupts the picture (300dpi-->96dpi) and produces at 300dpi source 200-400kb large result files.
public static Image GetBlackAndWhiteImage(Image SourceImage)
{
Bitmap bmp = new Bitmap(SourceImage.Width, SourceImage.Height);
using (Graphics gr = Graphics.FromImage(bmp)) // SourceImage is a Bitmap object
{
var gray_matrix = new float[][] {
new float[] { 0.299f, 0.299f, 0.299f, 0, 0 },
new float[] { 0.587f, 0.587f, 0.587f, 0, 0 },
new float[] { 0.114f, 0.114f, 0.114f, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 0, 0, 0, 0, 1 }
};
var ia = new System.Drawing.Imaging.ImageAttributes();
ia.SetColorMatrix(new System.Drawing.Imaging.ColorMatrix(gray_matrix));
ia.SetThreshold(float.Parse(Settings.Default["Threshold"].ToString())); // Change this threshold as needed
var rc = new Rectangle(0, 0, SourceImage.Width, SourceImage.Height);
gr.DrawImage(SourceImage, rc, 0, 0, SourceImage.Width, SourceImage.Height, GraphicsUnit.Pixel, ia);
}
return bmp;
}
The perfect way is just simply convert to CCITT decoded tif, that contains only BW. Much more efficent method with 30-50kb result file, 300dpi also remains correct as well:
public void toCCITT(string tifURL)
{
byte[] imgBits = File.ReadAllBytes(tifURL);
using (MemoryStream ms = new MemoryStream(imgBits))
{
using (Image i = Image.FromStream(ms))
{
EncoderParameters parms = new EncoderParameters(1);
ImageCodecInfo codec = ImageCodecInfo.GetImageDecoders()
.FirstOrDefault(decoder => decoder.FormatID == ImageFormat.Tiff.Guid);
parms.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
i.Save(#"c:\test\result.tif", codec, parms);
}
}
}
Good Luck Bro,
I've tested this code and worked fine for me:
//You should use System.Linq for this to work
public static ImageCodecInfo TiffCodecInfo => ImageCodecInfo.GetImageDecoders().
FirstOrDefault(decoder => decoder.FormatID == ImageFormat.Tiff.Guid);
//Encapsulate this in a try catch block for possible exceptions
public static Bitmap ConvertToBitonal(Bitmap original)
{
EncoderParameters encoderParameters;
MemoryStream ms = new MemoryStream();
Bitmap result;
encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 1L);
original.Save(ms, TiffCodecInfo, encoderParameters);
result = new Bitmap(Image.FromStream(ms));
ms.Dispose();
return result;
}