C# GIF encoder/decoder - c#

I’m working on a program that loads a animated GIF file and alter some of the pixels, then spits it back out as a new animated GIF file. The outputted GIF animates just fine in Windows, browsers and Photoshop.
However, if I try to load the GIF with my program, it will not animated. Also, I cannot get the correct palette. The loading code is exactly the same code that I use to load the original GIF with in the first place.
Here’s my saving code:
public int saveAsGif(string absPath, byte[] mark)
{
BitmapPalette palette = new BitmapPalette(getPaletteAsMediaColorList(m_Pal));
int width = m_CanvasWidth;
int height = m_CanvasHeight;
using (FileStream fs = new FileStream(absPath, FileMode.Create))
{
GifBitmapEncoder encoder = new GifBitmapEncoder();
for (int f = 0; f < m_NumberOfFrames; f++)
{
FrameData frame = m_Frames[f];
byte[] pixels = frame.pixels;
BitmapSource image = BitmapSource.Create(
width,
height,
96,
96,
System.Windows.Media.PixelFormats.Indexed8,
palette,
pixels,
width);
encoder.Frames.Add(BitmapFrame.Create(image));
}
encoder.Save(fs);
fs.Close();
}
return RESULT_SUCCESFUL;
}
To make sure it's not my loading code, I created a plain new project with the following code:
private void Form1_Load(object sender, EventArgs e)
{
Bitmap test1 = new Bitmap(#"F:\original.gif");
pictureBox1.Image = test1;
Bitmap test2 = new Bitmap(#"F:\exported.gif");
pictureBox2.Image = test2;
}
original.gif will load and play perfectly, exported.gif will only show a still frame. However, in Windows/Browser/Photoshop exported.gif will play.

I have found a relative simple solution. It’s not perfect, but it worked for me. It works with GifBitmapEncoder only, no additional libraries are needed.
All that you need to do;
write the GIF data from encoder to a memory stream.
Binary write the first 13 bytes (GIF89a header and stuff).
Binary write the ‘ApplicationExtention’ block.
Binary write the rest of the GIF data.
The ApplicationExtention block allows for animations. If no delay times are set, the default time of 100ms is used. If you do want custom delays for each frame, you will need to add a few more bytes in between (see GIF file format doc).
Code:
public void saveAsGif(string absPath)
{
int width = m_CanvasWidth;
int height = m_CanvasHeight;
GifBitmapEncoder encoder = new GifBitmapEncoder();
BitmapPalette palette = new BitmapPalette(getPaletteAsMediaColorList(m_Pal));
for (int f = 0; f < m_NumberOfFrames; f++)
{
FrameData frame = m_Frames[f];
byte[] pixels = frame.pixels;
BitmapSource image = BitmapSource.Create(
width,
height,
96,
96,
System.Windows.Media.PixelFormats.Indexed8,
palette,
pixels,
width);
encoder.Frames.Add(BitmapFrame.Create(image));
}
byte[] GifData;
using (MemoryStream ms = new MemoryStream())
{
encoder.Save(ms);
GifData = ms.ToArray();
}
byte[] ApplicationExtention = { 33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0 };
using (FileStream fs = new FileStream(absPath, FileMode.Create))
{
fs.Write(GifData, 0, 13);
fs.Write(ApplicationExtention, 0, ApplicationExtention.Length);
fs.Write(GifData, 13, GifData.Length - 13);
}
}

Related

Font size of graphics.DrawString on an image does not same with result image

I'm working on a task that draw a string on an image.
but the result image is not same with source code that I wrote down.
following image shows the font size difference.
red text is written in NanumSquare font 18px in window paint.
and the black text date below red text is also NanumSquare font 18px. It is written with C# source code.
following is my source code. C#.
static void Main(string[] args)
{
DrawTextToImageSave("webPrint_back.png");
}
public static void DrawTextToImageSave(string path)
{
//png to bitmap
Image Dummy = Image.FromFile(path);
using (Bitmap bitmap = (Bitmap)Dummy)
{//load the image file
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
var titleFont = new Font("NanumSquareOTF ExtraBold", 25);
var bodyFont = new Font("NanumSquareOTF Regular", 25);
graphics.DrawString("DATE", titleFont, System.Drawing.Brushes.Black, new PointF(401.5f, 863.5f)); //comment 1
graphics.DrawString(DateTime.Now.ToString("yyyy.MM.dd"), bodyFont, System.Drawing.Brushes.Black, new PointF(345, 885f));
graphics.DrawString("LOCATION", titleFont, System.Drawing.Brushes.Black, new PointF(344, 919.5f));
graphics.DrawString(System.DateTime.Now.ToString("yyyyMMddHHmmss") , bodyFont, System.Drawing.Brushes.Black, new PointF(267f, 946f));
WriteableBitmap bitmapimg = Generator128Code("STACKOVERFLOW", 110, 110);
bitmapimg = resize_image(bitmapimg, 1.4); //comment 2
var stream = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapimg));
encoder.Save(stream);
byte[] buffer = stream.GetBuffer();
var qrBitmap = new System.Drawing.Bitmap(new MemoryStream(buffer));
graphics.DrawImage(qrBitmap, 485f, 855f);
}
bitmap.Save( "output_WebPrintBack.png", ImageFormat.Png);
}
}
see comment 1. I expect it draws exactly 18px font. but It does not.
I have also same problem on drawing qrcode with zxing.
without comment2 code I get a ~90 px qr code size.
public static WriteableBitmap Generator128Code(string contents, int width, int height)
{
if (string.IsNullOrEmpty(contents))
{
return null;
}
EncodingOptions options = null;
BarcodeWriter writer = null;
options = new QrCodeEncodingOptions
{
CharacterSet = "UTF-8",
Width = width,
Height = height,
ErrorCorrection = ErrorCorrectionLevel.H,
Margin = 0
};
writer = new BarcodeWriter
{
Format = BarcodeFormat.QR_CODE,
Options = options
};
WriteableBitmap bitmap = writer.Write(contents);
return bitmap;
}
static WriteableBitmap resize_image(WriteableBitmap img, double scale)
{
BitmapSource source = img;
var s = new ScaleTransform(scale, scale);
var res = new TransformedBitmap(img, s);
return convert_BitmapSource_to_WriteableBitmap(res);
}
static WriteableBitmap convert_BitmapSource_to_WriteableBitmap(BitmapSource source)
{
// Calculate stride of source
int stride = source.PixelWidth * (source.Format.BitsPerPixel / 8);
// Create data array to hold source pixel data
byte[] data = new byte[stride * source.PixelHeight];
// Copy source image pixels to the data array
source.CopyPixels(data, stride, 0);
// Create WriteableBitmap to copy the pixel data to.
WriteableBitmap target = new WriteableBitmap(source.PixelWidth
, source.PixelHeight, source.DpiX, source.DpiY
, source.Format, null);
// Write the pixel data to the WriteableBitmap.
target.WritePixels(new Int32Rect(0, 0
, source.PixelWidth, source.PixelHeight)
, data, stride, 0);
return target;
}
with multiplying x 1.4 with its size, I can get similar result that I want.
why this difference has occured?
size of origin image is also 638px * 1010px. following image is origin image.
thank you for reading. and I apologize my poor English skill.
Edit
following source is executable with Console .net framework.
I retry with this source code but the result was same. :( ...
following source code is full source code.
you need png file that named "webPrint_back.png". and size 638x1010 . https://dummyimage.com/
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ZXing;
using ZXing.Common;
using ZXing.QrCode;
using ZXing.QrCode.Internal;
using BarcodeWriter = ZXing.Presentation.BarcodeWriter;
namespace bitmapTest
{
class Program
{
static void Main(string[] args)
{
DrawTextToImageSave("webPrint_back.png");
}
public static void DrawTextToImageSave(string path)
{
//png to bitmap
Image Dummy = Image.FromFile(path);
using (Bitmap bitmap = (Bitmap)Dummy)
{//load the image file
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
var titleFont = new Font("NanumSquareOTF ExtraBold", 18);
var bodyFont = new Font("NanumSquareOTF Regular", 18);
graphics.DrawString("DATE", titleFont, System.Drawing.Brushes.Black, new PointF(401.5f, 863.5f)); //comment 1
graphics.DrawString(DateTime.Now.ToString("yyyy.MM.dd"), bodyFont, System.Drawing.Brushes.Black, new PointF(345, 885f));
graphics.DrawString("LOCATION", titleFont, System.Drawing.Brushes.Black, new PointF(344, 919.5f));
graphics.DrawString(System.DateTime.Now.ToString("yyyyMMddHHmmss") , bodyFont, System.Drawing.Brushes.Black, new PointF(267f, 946f));
WriteableBitmap bitmapimg = Generator128Code("STACKOVERFLOW", 110, 110);
bitmapimg = resize_image(bitmapimg, 1.4); //comment 2
var stream = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapimg));
encoder.Save(stream);
byte[] buffer = stream.GetBuffer();
var qrBitmap = new System.Drawing.Bitmap(new MemoryStream(buffer));
graphics.DrawImage(qrBitmap, 485f, 855f);
}
bitmap.Save( "output_WebPrintBack.png", ImageFormat.Png);
}
}
public static WriteableBitmap Generator128Code(string contents, int width, int height)
{
if (string.IsNullOrEmpty(contents))
{
return null;
}
EncodingOptions options = null;
BarcodeWriter writer = null;
options = new QrCodeEncodingOptions
{
CharacterSet = "UTF-8",
Width = width,
Height = height,
ErrorCorrection = ErrorCorrectionLevel.H,
Margin = 0
};
writer = new BarcodeWriter
{
Format = BarcodeFormat.QR_CODE,
Options = options
};
WriteableBitmap bitmap = writer.Write(contents);
return bitmap;
}
static WriteableBitmap resize_image(WriteableBitmap img, double scale)
{
BitmapSource source = img;
var s = new ScaleTransform(scale, scale);
var res = new TransformedBitmap(img, s);
return convert_BitmapSource_to_WriteableBitmap(res);
}
static WriteableBitmap convert_BitmapSource_to_WriteableBitmap(BitmapSource source)
{
// Calculate stride of source
int stride = source.PixelWidth * (source.Format.BitsPerPixel / 8);
// Create data array to hold source pixel data
byte[] data = new byte[stride * source.PixelHeight];
// Copy source image pixels to the data array
source.CopyPixels(data, stride, 0);
// Create WriteableBitmap to copy the pixel data to.
WriteableBitmap target = new WriteableBitmap(source.PixelWidth
, source.PixelHeight, source.DpiX, source.DpiY
, source.Format, null);
// Write the pixel data to the WriteableBitmap.
target.WritePixels(new Int32Rect(0, 0
, source.PixelWidth, source.PixelHeight)
, data, stride, 0);
return target;
}
}
}
I found the factor that makes font size is smaller than I expected.
First, source code that I posted works bad with PNG File with "24 bit depth"(without transparent background).
but PNG file with "32 bit depth"(with transparent background), Fontsize works well.
I don't know why It happened.
Second, the barcode made with zxing.net nuget. internally has padding with its border. the problem is that padding size depends on string length. longer string length makes more smaller barcode size and more bigger padding.
following source is my solution for barcode zxing.net nuget
WriteableBitmap bitmapimg = Generator128Code(StaticCommon.localConfigModel.cardBack_QRText, 110, 110);
var stream = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapimg));
encoder.Save(stream);
byte[] buffer = stream.GetBuffer();
var qrBitmap = new System.Drawing.Bitmap(new MemoryStream(buffer));
RectangleF recF = new RectangleF(new PointF(477f, 852f), new SizeF(130, 130));
//ZXING PADDING value, padding size depends on QR encoded string length, so I divide with integer 30 and use remainder
int len = StaticCommon.localConfigModel.cardBack_QRText.Length;
int pad = (int)len / 30;
if (len % 30 > 0) pad++;
RectangleF srecF = new RectangleF(pad * 6f, pad * 6f, 110f - pad * 12f, 110 - pad * 12f);
graphics.DrawImage(qrBitmap, recF, srecF, GraphicsUnit.Pixel);
I solved my problem with engineering way, but I hope someone solve this problem with theoretical way. so I remain this question unsolved.

ZXing <FNC> Separator for GS1 bar code

I am trying to place in <FNC1> separators in my ZXing barcode, I am not sure how these are supposed to be formatted in a string value. I have
BarcodeWriterPixelData writer = new BarcodeWriterPixelData()
{
Format = BarcodeFormat.CODE_128,
Options = new ZXing.Common.EncodingOptions
{
Width = 554,
Height = 120
}
};
var pixelData = writer.Write("<FNC1>42011111<FNC1>92612123456789000000155015");
using (var bitmap = new Bitmap(pixelData.Width, pixelData.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb))
{
using (var ms = new System.IO.MemoryStream())
{
var bitmapData = bitmap.LockBits(new Rectangle(0, 0, pixelData.Width, pixelData.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
try
{
// we assume that the row stride of the bitmap is aligned to 4 byte multiplied by the width of the image
System.Runtime.InteropServices.Marshal.Copy(pixelData.Pixels, 0, bitmapData.Scan0, pixelData.Pixels.Length);
}
finally
{
bitmap.UnlockBits(bitmapData);
}
// PNG or JPEG or whatever you want
bitmap.SetResolution(300, 300);
bitmap.Save(#"C:\Users\user\Desktop\newBarcode.jpeg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
This code generates this image
Which is exactly <FNC1>42011111<FNC1>92612123456789000000155015how can I interpolate and insert the FNC1 values? I tried using "]C1" which I found in this solution:
https://github.com/zxing/zxing/issues/475
But it did not work. Any help would be greatly appreciated.
The unicode character '\u00f1' instead of "<FNC1>" should work.

Imrpove Barcode Image Quality PNG

I am currently using ZXing.Net to generate a CODE_128 (which in the documentation says is compliant with GS1-128 barcodes). The code I am using is down below:
BarcodeWriterPixelData writer = new BarcodeWriterPixelData()
{
Format = BarcodeFormat.CODE_128,
Options = new ZXing.Common.EncodingOptions
{
Width = 280,
Height = 80
}
};
var pixelData = writer.Write(packageModel.TrackingId.PrintValue);
using (var bitmap = new Bitmap(pixelData.Width, pixelData.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb))
{
using (var ms = new System.IO.MemoryStream())
{
var bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, pixelData.Width, pixelData.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
try
{
// we assume that the row stride of the bitmap is aligned to 4 byte multiplied by the width of the image
System.Runtime.InteropServices.Marshal.Copy(pixelData.Pixels, 0, bitmapData.Scan0, pixelData.Pixels.Length);
}
finally
{
bitmap.UnlockBits(bitmapData);
}
// PNG or JPEG or whatever you want
bitmap.SetResolution(100F, 100F);
drawing.DrawImage(bitmap, new Rectangle(10, 255, 280, 80));
}
}
This creates the image below
For some reason my barcode is coming out blurry and with grey shadows on it. I am even setting the barcode resolution to be 100DPI x 100DPI to make it printer friendly.
My question:
What can I add to my code to make the barcode look crisp and blur free?

How to add image to pdf with using bitmap, itext, and chart and rendering more detail (pixels or dpi)?

Goal
I need to increase the quality of the bitmap that is being added to a pdf. I have a 300dpi image, but looks really bad when itextsharp adds it to pdf.
Description
I am working on a project which takes a bitmap and writes it to a pdf using itextSharp. The problem is that the project makes a graph, saves it as a bitmap, writes it to the pdf, and the graph appears to be low quality. I am unable to attach the picture here of what is being graphed, but will do my best to describe what is going on. Here is the code below:
//Declare chart object
private static System.Windows.Forms.DataVisualization.Charting.Chart chart_runs;
//Start the PDF Document
Document pdfDocument = new Document(PageSize.LETTER);
private static System.Drawing.Bitmap GetChartBitmap()
{
chart_runs.Width = 2269;
chart_runs.Height = 1406;
System.Drawing.Bitmap bitmap1 = new System.Drawing.Bitmap(2269, 1406);
bitmap1.SetResolution(300, 300);
chart_runs.DrawToBitmap(bitmap1, new System.Drawing.Rectangle(0, 0, 2269, 1406));
string path = System.IO.Path.GetTempPath();
bitmap1.Save(path + #"\Image.png");
return bitmap1;
}
Here is how the bitmap and image is added to the pdf.
System.Drawing.Bitmap img = GetChartBitmap();
img.Dispose();
string path = System.IO.Path.GetTempPath();
iTextSharp.text.Image imag
= iTextSharp.text.Image.GetInstance(path + #"\Image.bmp");
//imag.ScaleAbsolute(chart_runs.Width, chart_runs.Height);
//imag.ScaleToFit(550, 330);
imag.ScaleAbsolute(550, 350);
//imag.ScaleAbsolute(650, 450);
//imag.ScaleAbsolute(100f, 100f);
pdfDocument.Add(imag);
pdfDocument.Close();
pdfDocument.Dispose();
Attempts
I have spent considerable time trying to fix this, here are some of the highlights.
At first, it was believed this was a DPI issue. However, when I made
the bitmap (and itext image) 300DPI, no quality difference was
noticed.
I then added more pixels by enlarging the targetBounds object above. This had the weird effect of shrinking my graph on my PDF? Why?
I have attempted many manipulations with itext.scale functions as I found here.
Another justification why I am trying to make my bitmap larger is on here.
As I said above, I seem to be making my image even smaller with the current approach. I am just trying to increase the quality of the bitmap so when people print it off or zoom in on the pdf it doesn't look terrible. I appreciate any help.
Not an answer yet
Here's a very simple image generating function just for testing purposes, it should be fairly self-explanatory:
private static byte[] CreateImage(string text, int fontSize, int width, int height) {
using (var b = new Bitmap(width, height)) {
using (var g = Graphics.FromImage(b)) {
using(var br = new SolidBrush(System.Drawing.Color.Red)){
using (var f = new System.Drawing.Font("Arial Unicode MS", fontSize)) {
g.DrawString(text, f, br, 10, 10);
using (var ms = new System.IO.MemoryStream()) {
b.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
return ms.ToArray();
}
}
}
}
}
}
If you examine the PDF generated from the below code that uses the function above you'll see each image in the PDF gets progressively "better" and you can zoom in further before seeing jagged lines. Ultimately you're working with raster/bitmap files, however, so some pixelation will almost always eventually occur. The DPI listed is the "effective DPI" which is based on the assumption that the PDF will map 72 units into 1 inch of space (which is the PDF standard's default) and that it is printed at 100% resolution.
var testFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "test.pdf");
using (var fs = new FileStream(testFile, FileMode.Create, FileAccess.Write, FileShare.None)) {
using (var doc = new Document()) {
using (var writer = PdfWriter.GetInstance(doc, fs)) {
doc.SetMargins(0, 0, 0, 0);
doc.Open();
var img1 = iTextSharp.text.Image.GetInstance(CreateImage("Hello", 24, 600, 50));
doc.Add(img1); //72 DPI
var img2 = iTextSharp.text.Image.GetInstance(CreateImage("Hello", 48, 1200, 100));
img2.ScaleAbsolute(600, 50);
doc.Add(img2); //144 DPI
var img3 = iTextSharp.text.Image.GetInstance(CreateImage("Hello", 96, 2400, 200));
img3.ScaleAbsolute(600, 50);
doc.Add(img3); //288 DPI
var img4 = iTextSharp.text.Image.GetInstance(CreateImage("Hello", 192, 4800, 400));
img4.ScaleAbsolute(600, 50);
doc.Add(img4); //576 DPI
doc.Close();
}
}
}

Need C# function to convert grayscale TIFF to black & white (monochrome/1BPP) TIFF

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;
}

Categories

Resources