How to set bit depth in Magick.NET Read - c#

How can I specify the bit depth for the MagickImage.Read() function when reading binary files?
I have a 1024x1024 image represented by 8-bit grayscale values (total file length = 1024x1024 = 1048576 bytes). Using ImageMagick v.7.0.8-7 Q16 x64, I can convert the file using
magick.exe -depth 8 -size 1024x1024 -format Gray Gray:filepath.bin convertedfile.png
When I try to convert the file using Magick.NET Q16-AnyCPU v7.5.0.1,
public MagickImage ReadNewMagickImageFromBinary(string fileName){
MagickReadSettings settings = new MagickReadSettings();
settings.Width = 1024;
settings.Height = 1024; //if I use settings.Height = 512; , I'm OK.
settings.Format = MagickFormat.Gray;
//settings.Depth = 8; //didn't work
//settings.SetDefine(MagickFormat.Gray, "depth", "8"); //also didn't work
MagickImage newImage = new MagickImage();
newImage.Depth = 8; //this appears to be ignored once the Read function is called
newImage.Read(fileName, settings);
return newImage;
}
I get the error
Message: ImageMagick.MagickCorruptImageErrorException : unexpected
end-of-file '': No such file or directory #
error/gray.c/ReadGRAYImage/241
Indicating that the program has read past the end of the file. I've confirmed that Magick.NET is reverting to a 16-bit depth instead of the 8-bit depth I want. I can read the file using settings.Height = 512 instead of 1024, which gives me a squashed version of my grayscale image.
I learned from Memory consumption in Magick.NET that Magick.NET Q16 stores pixels in memory with 16-bit precision; I'm fine with that but it doesn't seem that should preclude 8-bit reading capabilities.
How do I force Magick.NET Q16 to read pixels in with an 8-bit depth?

I just published Magick.NET 7.6.0.0 that now has better API for reading raw pixels. You should change your code to this:
public MagickImage ReadNewMagickImageFromBinary(string fileName)
{
var width = 1024;
var height = 1024;
var storageType = StorageType.Char;
var mapping = "R";
var pixelStorageSettings = new PixelStorageSettings(width, height, storageType, mapping);
return new MagickImage(fileName, pixelStorageSettings);
}

One workaround is to use the Magick.NET Q8 version instead of Magick.NET Q16. I successfully read the file using the program Magick.NET Q8-AnyCPU 7.5.0.1.
I'm still hoping there's a solution that allows me to still use Magick.NET Q16, but this works for now.

Related

C#/System.Drawing: Preserve COM marker in JPEG file (no EXIF)

I am doing a 90° rotation with minimal loss, using the following code:
System.Drawing.Image originalImage = System.Drawing.Image.FromFile("input.jpg");
ImageFormat sourceFormat = originalImage.RawFormat;
EncoderParameters encoderParams = null;
try
{
if (sourceFormat.Guid == ImageFormat.Jpeg.Guid)
{
encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Transformation,
(long)EncoderValue.TransformRotate90);
}
originalImage.Save("output.jpg", GetEncoder(sourceFormat), encoderParams);
}
finally
{
if (encoderParams != null)
encoderParams.Dispose();
}
However the Save function seems to create EXIF metadata from the original (pure, no EXIF) JPEG COM marker (0xFE). I do not want EXIF markers in the output JPEG. I also want to preserve the original COM marked. What C# API in my application can I use instead to save my rotated buffer ?
Using jpegdump (dicom3tools package):
$ jpegdump < input.jpg
[...]
Offset 0x0014 Marker 0xfffe COM Comment length variable 0x10
While:
$ jpegdump < output.jpg
[...]
Offset 0x0014 Marker 0xffe1 APP1 Reserved for Application Use length variable 0x5a
Turns out the only working solution I could come up with, was saving the JPEG to a MemoryStream and then post process this temporary Stream using the solution described at:
https://stackoverflow.com/a/1252097/136285
Pseudo code:
var jpegPatcher = new JpegPatcher();
FileStream outFile = new FileStream(fileName, FileMode.Open, FileAccess.Write);
jpegPatcher.PatchAwayExif(inStream, outFile);
I used the code from the blog, which does:
private void SkipAppHeaderSection(Stream inStream)
...
while (header[0] == 0xff && (header[1] >= 0xe0 && header[1] <= 0xef))
So the function name PatchAwayExif is a bit odd since it also remove the APP0 (aka JFIF) segment...but that was something I also needed.
I may be wrong but when the way you are rotating the image, you are not transforming the pixel matrix but simply changing the EXIF data to tell what the image orientation is.
So when you are saving it, it is just adding in the EXIF orientation flag to the original.
If you removed that EXIF data the image would lose its 90º rotation.
More info on rotation EXIF flag - here
Example of rotating an image by changing EXIF - here
If you do physically rotate the image using Image.RotateFlip you can remobve the exif data using originalImage.RemovePropertyItem(0x0112)

How to create 8 bpp BMP in Magick.NET?

Using Magick.NET-Q8-AnyCPU. I want to convert existing TIFF image to grayscale 8 bpp BMP image. I tried this:
byte[] input = <existing TIFF image>;
using (var image = new MagickImage(input))
{
image.Grayscale();
image.ColorType = ColorType.Palette;
image.Depth = 8;
image.Quantize(new QuantizeSettings() { Colors = 256, DitherMethod = DitherMethod.No });
byte[] result = image.ToByteArray(MagickFormat.Bmp);
return result;
}
In FastStone Viewer the image is reported as 8-bit, HOWEVER in file Properties > Details the image is reported as Bit depth: 32. I need it to be 8 here. I can convert this image in Paint.NET, and when I choose there Bit Depth: 8-bit then new image will properly show 8-bit depth in file properties.
So, Paint.NET creates proper 8-bit bitmap. How to do it with Magick.NET?
Windows Explorer displays all compressed BMP files as 32-bit contrary to their actual bit depths.
I don't know it's a bug or not but I'm a little bit closer to call it a bug.
Because; after creating a 8bpp BMP file with your code, when I open the file with a binary editor, in the bitmap header struct I saw that the bits per pixel field value (blocks 28-29) was 8 as it must be. Also, the next byte 01 (offset 30) means that the data compressed with Run-length encoding which is a straightforward lossless data compression algorithm.
Therefore, I can say that there's no problem with the image you produced with Magick.NET, it's certainly an 8bpp BMP image file, but compressed.
Unlike Magick.NET's defaults, it seems that Paint.NET produce uncompressed BMP files, that's why you see different bit depths due to weirdness of Windows Explorer.
To fix this, you can disable compression so the bit depth value displayed in the properties dialog will be the value you expect.
image.Settings.Compression = CompressionMethod.NoCompression;
byte[] result = image.ToByteArray(MagickFormat.Bmp);
It seems that's impossible. Neither image.Depth = 8 nor image.BitDepth(8) works.
May be the root is in:
// ImageMagick.MagickImage.NativeMethods.X{ver.}
[DllImport("Magick.Native-Q8-x{ver.}.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void MagickImage_SetBitDepth(IntPtr Instance, UIntPtr channels, UIntPtr value);
or
// ImageMagick.MagickImage.NativeMethods.X{ver.}
[DllImport("Magick.Native-Q8-x{ver.}.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void MagickImage_WriteStream(IntPtr Instance, IntPtr settings, ReadWriteStreamDelegate writer, SeekStreamDelegate seeker, TellStreamDelegate teller, ReadWriteStreamDelegate reader, out IntPtr exception);
Looks like it can not create 8 bits .bmp, though there is no problem with .png.
var original = #"D:\tmp\0.tif";
var copy = #"D:\tmp\0.bmp";
using (var image = new MagickImage(original))
{
image.Grayscale();
image.ColorType = ColorType.Palette;
image.Quantize(new QuantizeSettings() { Colors = 256, DitherMethod = DitherMethod.No });
byte[] result = image.ToByteArray(MagickFormat.Png8);
File.WriteAllBytes(copy, result);
}
Console.WriteLine("Press 'Enter'..."); // one have 8 bits .png here
Console.ReadLine();
using (var image = new MagickImage(copy))
{
byte[] result = image.ToByteArray(MagickFormat.Bmp3);
File.WriteAllBytes(copy, result);
} // but ends up with 32 bits .bmp again here
I also noticed that
image.Quantize(new QuantizeSettings() { Colors = 16, DitherMethod = DitherMethod.No });
produces 4 bits result. Gradual increasing gives 32 bits, but never 8 bits.

Converting to real 8bpp Jpeg

I already searched and tried all the suggestions given it at SO, AForge, FreeImage and couple more websites, but I'm unable to transform an image into a real 8bpp one. I always get the JPEG saved as 24bpp instead of 8.
After the grayscale conversion I have a MemoryBMP (according to myImage.RawFormat), so when I save it like this
myImage.Save("image.jpg");
I get a 3Mb+ image (so I assume it's saved in BMP) which Windows tells me is 8bpp (but I need it to be JPEG). But if I save it like this
myImage.Save("image_JPEG.jpg", ImageFormat.Jpeg);
I get a 400Kb image (so I assume it's saved in JPEG) but it's in 24bpp.
Any ideas what can be causing this?
EDIT
As JYelton mentioned this is a limitation of Image.Save() method, so I changed to saving the image with the FreeImage library: FreeImage.SaveBitmap() works like a charm.
UPDATE: The Image.Save() method does not support 8 bit per pixel for JPEG format. You may want to use the FreeImage library instead, as mentioned in the comments below.
If you want to reduce color depth to 8 bits per pixel, typically that is the same as converting from 24-bit color to grayscale, where each color channel has 8 bits per pixel to begin with. (In other words, reducing 3 channels of color information to 1.)
The default encoder when using Image.Save() and specifying ImageFormat.Jpeg is 24 bpp, so you'll need to specify an encoder and supply some parameters:
ImageCodecInfo[] availableCodecs = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo jpgCodec = availableCodecs.FirstOrDefault(codec => codec.MimeType == "image/jpeg");
if (jpgCodec == null)
throw new NotSupportedException("Encoder for JPEG not found.");
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8L);
myImage.Save("image_JPEG.jpg", jpgCodec, encoderParams);
This is a modified example from a longer explanation I found at aspnet-answers.com.
Here is one way to verify if ImageFormat.Jpeg supports Encoder.ColorDepth:
using System.Linq;
bool IsSupportedParameters(ImageFormat imageFormat, Encoder encoder)
{
var codec = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == imageFormat.Guid);
Bitmap bitmap = new Bitmap(1, 1);
EncoderParameters? paramList = bitmap.GetEncoderParameterList(codec.Clsid);
if (paramList != null)
{
return paramList.Param.Any(encParams => encParams.Encoder.Guid == encoder.Guid);
}
return false;
}
Lead to:
IsSupportedParameters(ImageFormat.Jpeg, Encoder.ColorDepth); // false
IsSupportedParameters(ImageFormat.Jpeg, Encoder.ChrominanceTable); // true
So you need to use another library to achieve what you want. For example see:
https://stackoverflow.com/a/31962503/136285
References:
How to: Determine the Parameters Supported by an Encoder
Encoder.ColorDepth Field

Fast way to compare 2 byte arrays

I am uploading jpeg images as fast as i can to a web service (it is the requirement I have been given).
I am using async call to the web service and I calling it within a timer.
I am trying to optimise as much as possible and tend to use an old laptop for testing. On a normal/reasonable build PC all is OK. On the laptop I get high RAM usage.
I know I will get a higher RAM usage using that old laptop but I want to know the lowest spec PC the app will work on.
As you can see in the code below I am converting the jpeg image into a byte array and then I upload the byte array.
If I can reduce/compress/zip the bye array then I am hoping this will be 1 of the ways of improving memory usage.
I know jpegs are already compressed but if I compare the current byte array with the previous byre array then uploading the difference between this byte arrays I could perhaps compress it even more on the basis that some of the byte values will be zero.
If I used a video encoder (which would do the trick) I would not be real time as much I would like.
Is there an optimum way of comparing 2 byte arrays and outputting to a 3rd byte array? I have looked around but could not find an answer that I liked.
This is my code on the client:
bool _uploaded = true;
private void tmrLiveFeed_Tick(object sender, EventArgs e)
{
try
{
if (_uploaded)
{
_uploaded = false;
_live.StreamerAsync(Shared.Alias, imageToByteArray((Bitmap)_frame.Clone()), Guid.NewGuid().ToString()); //web service being called here
}
}
catch (Exception _ex)
{
//do some thing but probably time out error here
}
}
//web service has finished the client invoke
void _live_StreamerCompleted(object sender, AsyncCompletedEventArgs e)
{
_uploaded = true; //we are now saying we start to upload the next byte array
}
private wsLive.Live _live = new wsLive.Live(); //web service
private byte[] imageToByteArray(Image imageIn)
{
MemoryStream ms = new MemoryStream();
imageIn.Save(ms,System.Drawing.Imaging.ImageFormat.Jpeg); //convert image to best image compression
imageIn.Dispose();
return ms.ToArray();
}
thanks...
As C.Evenhuis said - JPEG files are compressed, and changing even few pixels results in complettly differrent file. So - comparing resulting JPEG files is useless.
BUT you can compare your Image objects - quick search results in finding this:
unsafe Bitmap PixelDiff(Bitmap a, Bitmap b)
{
Bitmap output = new Bitmap(a.Width, a.Height, PixelFormat.Format32bppArgb);
Rectangle rect = new Rectangle(Point.Empty, a.Size);
using (var aData = a.LockBitsDisposable(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
using (var bData = b.LockBitsDisposable(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
using (var outputData = output.LockBitsDisposable(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb))
{
byte* aPtr = (byte*)aData.Scan0;
byte* bPtr = (byte*)bData.Scan0;
byte* outputPtr = (byte*)outputData.Scan0;
int len = aData.Stride * aData.Height;
for (int i = 0; i < len; i++)
{
// For alpha use the average of both images (otherwise pixels with the same alpha won't be visible)
if ((i + 1) % 4 == 0)
*outputPtr = (byte)((*aPtr + *bPtr) / 2);
else
*outputPtr = (byte)~(*aPtr ^ *bPtr);
outputPtr++;
aPtr++;
bPtr++;
}
}
return output;
}
If your goal is to find out whether two byte arrays contain exactly the same data, you can create an MD5 hash and compare these as others have suggested. However in your question you mention you want to upload the difference which means the result of the comparison must be more than a simple yes/no.
As JPEGs are already compressed, the smallest change to the image could lead to a large difference in the binary data. I don't think any two JPEGs contain binary data similar enough to easily compare.
For BMP files you may find that changing a single pixel affects only one or a few bytes, and more importantly, the data for the pixel at a certain offset in the image is located at the same position in both binary files (given that both images are of equal size and color depth). So for BMPs the difference in binary data directly relates to the difference in the images.
In short, I don't think obtaining the binary difference between JPEG files will improve the size of the data to be sent.

Read a tiff file's dimension and resolution without loading it first

How to read a tiff file's dimension (width and height) and resolution (horizontal and vertical) without first loading it into memory by using code like the following. It is too slow for big files and I don't need to manipulate them.
Image tif = Image.FromFile(#"C:\large_size.tif");
float width = tif.PhysicalDimension.Width;
float height = tif.PhysicalDimension.Height;
float hresolution = tif.HorizontalResolution;
float vresolution = tif.VerticalResolution;
tif.Dispose();
Edit:
Those tiff files are Bilevel and have a dimension of 30x42 inch. The file sizes are about 1~2 MB. So the method above works Ok but slow.
Ran into this myself and found the solution (possibly here). Image.FromStream with validateImageData = false allows you access to the information you're looking for, without loading the whole file.
using(FileStream stream = new FileStream(#"C:\large_size.tif", FileMode.Open, FileAccess.Read))
{
using(Image tif = Image.FromStream(stream, false, false))
{
float width = tif.PhysicalDimension.Width;
float height = tif.PhysicalDimension.Height;
float hresolution = tif.HorizontalResolution;
float vresolution = tif.VerticalResolution;
}
}
As far as I know, all classes from System.Drawing namespace load image data immediately when image is open.
I think LibTiff.Net can help you to read image properties without loading image data. It's free and open-source (BSD license, suitable for commercial applications).
Here is a sample for your task (error checks are omitted for brevity):
using BitMiracle.LibTiff.Classic;
namespace ReadTiffDimensions
{
class Program
{
static void Main(string[] args)
{
using (Tiff image = Tiff.Open(args[0], "r"))
{
FieldValue[] value = image.GetField(TiffTag.IMAGEWIDTH);
int width = value[0].ToInt();
value = image.GetField(TiffTag.IMAGELENGTH);
int height = value[0].ToInt();
value = image.GetField(TiffTag.XRESOLUTION);
float dpiX = value[0].ToFloat();
value = image.GetField(TiffTag.YRESOLUTION);
float dpiY = value[0].ToFloat();
}
}
}
}
Disclaimer: I am one of the maintainers of the library.
Try this, it seems to be what you are looking for. Just skip everything after:
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, ref w); //your width
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, ref h); //your height
TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, ref bits);
TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, ref samples);
Don't forget to close after you:
TIFFClose(tif);
The only way I can think of is reading the tiff binary header.
Here you can download the specification: http://partners.adobe.com/public/developer/tiff/index.html
Here is some code used to read Tiffs that you can use to learn:
http://www.koders.com/csharp/fidF6632006F25B8E5B3BCC62D13076B38D71847929.aspx?s=zoom
I created a library to read the tiff headers some time ago (with this two resources as base) but it was part of my employer code so I can't post my code here and I can say it is no really hard.
I Hope this helps.

Categories

Resources