Copy tiles from one tiff to another (4-band) - c#

I'm trying to simply use an offset to select tiles from one tiff image and write them to another image. I am using libtiff.net. Instead of each pixel having an ARGB value, there is one pixel that is alpha, then the pixel to the right of that is a red pixel, the one to the right of that is green and of course the one to the right of that is blue. I need to store those 4 pixels in one and something is happening along the way. Can you see anything wrong with this logic?
using (Tiff output = Tiff.Open(#"C:\base.tif", "w"))
{
if (output == null)
{
System.Console.Error.WriteLine("Could not open outgoing image");
return;
}
// We need to know the width and the height before we can malloc
int[] raster = new int[tileWidth * tileHeight];
// Write the tiff tags to the file
output.SetField(TiffTag.IMAGEWIDTH, 1024);
output.SetField(TiffTag.IMAGELENGTH, 1024);
output.SetField(TiffTag.TILEWIDTH, 256);
output.SetField(TiffTag.TILELENGTH, 256);
output.SetField(TiffTag.COMPRESSION, Compression.DEFLATE);
output.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
output.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);
output.SetField(TiffTag.BITSPERSAMPLE, 8);
output.SetField(TiffTag.SAMPLESPERPIXEL, numOfBands);
byte[] inputBuffer = new byte[tileWidth * tileHeight * numOfBands];
// loop through every tile column
for (int x = 0; x < 1024 / tileWidth; x++)
{
// loop through every tile row in the current column
for (int y = 0; y < 1024 / tileHeight; y++)
{
image.ReadRGBATile((x + xOffset) * tileWidth, (y + yOffset) * tileHeight, raster);
Tiff.IntsToByteArray(raster, 0, raster.Length, inputBuffer, 0);
output.WriteEncodedTile(getTileIndex(x, y, 1024 / tileWidth), inputBuffer, 0, raster.Length);
}
}
}

Related

C# RGB[,] to picturebox

I have a data that is (2448*2048) 5Mpixel image data, but the picturebox only has (816*683) about 500,000 pixels, so I lowered the pixels and I only need a black and white image, so I used the G value to create the image, but The image I output is shown in the following figure. Which part of my mistake?
public int[,] lowered(int[,] greenar)
{
int[,] Sy = new int[816, 683];
int x = 0;
int y = 0;
for (int i = 1; i < 2448; i += 3)
{
for (int j = 1; j < 2048; j += 3)
{
Sy[x, y] = greenar[i, j];
y++;
}
y = 0;
x++;
}
return Sy;
}
static Bitmap Create(int[,] R, int[,] G, int[,] B)
{
int iWidth = G.GetLength(1);
int iHeight = G.GetLength(0);
Bitmap Result = new Bitmap(iWidth, iHeight,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Rectangle rect = new Rectangle(0, 0, iWidth, iHeight);
System.Drawing.Imaging.BitmapData bmpData = Result.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
IntPtr iPtr = bmpData.Scan0;
int iStride = bmpData.Stride;
int iBytes = iWidth * iHeight * 3;
byte[] PixelValues = new byte[iBytes];
int iPoint = 0;
for (int i = 0; i < iHeight; i++)
{
for (int j = 0; j < iWidth; j++)
{
int iG = G[i, j];
int iB = G[i, j];
int iR = G[i, j];
PixelValues[iPoint] = Convert.ToByte(iB);
PixelValues[iPoint + 1] = Convert.ToByte(iG);
PixelValues[iPoint + 2] = Convert.ToByte(iR);
iPoint += 3;
}
}
System.Runtime.InteropServices.Marshal.Copy(PixelValues, 0, iPtr, iBytes);
Result.UnlockBits(bmpData);
return Result;
}
https://upload.cc/i1/2018/04/26/WHOXTJ.png
You don't need to downsample your image, you can do it in this way. Set picturebox property BackgroundImageLayout as either zoom or stretch and assign it as:
picturebox.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom;
picturebox.BackgroundImage = bitmap;
System.Windows.Forms.ImageLayout.Zoom will automatically adjust your bitmap to the size of picturebox.
You seem to be constantly mixing up your x and y offsets, which can easily be avoided simply by actually calling your loop variables x and y whenever you loop through image data. Also, image data is generally saved line by line, so your outer loop should be the Y loop going over the height, and the inner loop should process the X coordinates on one line, and should thus loop over the width.
Also, I'm not sure where your original data comes from, but in most of the cases I've seen where the image data is in multidimensional arrays like this, the Y is actually the first index in the array. Your actual image building function also assumes this, since it uses G.GetLength(0) to get the height of the image. But your channel resize function doesn't; it makes a multidimensional array as new int[816, 683], which would be a 683*816 image, not 816*683 as you said. So that certainly seems wrong.
Since you confirmed it to be [x,y], I adapted this solution to use it like that.
That aside, you hardcoded a lot of values in your functions, which is very bad practice. If you know you will reduce the image to 1/3rd by taking only one in three pixels, just give that 3 as parameter.
The reduction code:
public static Int32[,] ResizeChannel(Int32[,] origChannel, Int32 lossfactor)
{
Int32 newWidth = origChannel.GetLength(0) / lossfactor;
Int32 newHeight = origChannel.GetLength(1) / lossfactor;
// to avoid rounding errors
Int32 origHeight = newHeight * lossfactor;
Int32 origWidth = newWidth *lossfactor;
Int32[,] newChannel = new Int32[newWidth, newHeight];
Int32 newX = 0;
Int32 newY = 0;
for (Int32 y = 1; y < origHeight; y += lossfactor)
{
newX = 0;
for (Int32 x = 1; x < origWidth; x += lossfactor)
{
newChannel[newX, newY] = origChannel[x, y];
newX++;
}
newY++;
}
return newChannel;
}
The actual build code, as was remarked by GSerg in the comments, is wrong because you don't take the stride into account. The stride is the actual byte length of each line of pixels, and this is not just width * BytesPerPixel, since it gets rounded up to the next multiple of 4 bytes.
So you need to initialize your array as height * stride, not as height * width * 3, and you need to skip your write offset to the next multiple of the stride whenever you go to a lower Y line, rather than assuming it will just get there automatically because your X processing adds 3 for each pixel. Because it will not get there automatically, unless, by pure coincidence, your image width happens to be a multiple of 4 pixels.
Also, if you only use one channel for this, there is no reason to give it all three channels. Just give a single one.
public static Bitmap CreateGreyImage(Int32[,] greyChannel)
{
Int32 width = greyChannel.GetLength(0);
Int32 height = greyChannel.GetLength(1);
Bitmap result = new Bitmap(width, height, PixelFormat.Format24bppRgb);
Rectangle rect = new Rectangle(0, 0, width, height);
BitmapData bmpData = result.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
Int32 stride = bmpData.Stride;
// stride is the actual line width in bytes.
Int32 bytes = stride * height;
Byte[] pixelValues = new Byte[bytes];
Int32 offset = 0;
for (Int32 y = 0; y < height; y++)
{
Int32 workOffset = offset;
for (Int32 x = 0; x < width; x++)
{
pixelValues[workOffset + 0] = (Byte)greyChannel[x, y];
pixelValues[workOffset + 1] = (Byte)greyChannel[x, y];
pixelValues[workOffset + 2] = (Byte)greyChannel[x, y];
workOffset += 3;
}
// Add stride to get the start offset of the next line
offset += stride;
}
Marshal.Copy(pixelValues, 0, bmpData.Scan0, bytes);
result.UnlockBits(bmpData);
return result;
}
Now, this works as expected if your R, G and B channels are indeed identical, But if they are not, you have to realize there is a difference between reducing the image to grayscale and just building a grey image from the green channel. On a colour image, you will get totally different results if you take the blue or red channel instead.
This was the code I executed for this:
Int32[,] greyar = ResizeChannel(greenar, 3);
Bitmap newbm = CreateGreyImage(greyar);

Converting 32bit TIFF to Bitmap

I have a 32bit .tif file which is displayed in the image below by first ImageJ, and secondly my program. As you can guess, the way ImageJ displays the picture is correct.
I am converting the file to a Bitmap like this:
private Bitmap TiffToBmp()
{
Bitmap bmp;
int width;
int height;
float[] scanline32Bit;
float[] buffer;
using (Tiff original = Tiff.Open(file, "r"))
{
width = original.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
height = original.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
byte[] scanline = new byte[original.ScanlineSize()];
scanline32Bit = new float[original.ScanlineSize() / 4];
buffer = new float[width * height];
for (int i = 0; i < height; i++) //loading the data into a buffer
{
original.ReadScanline(scanline, i);
Buffer.BlockCopy(scanline, 0, scanline32Bit, 0, scanline.Length);
for (int j = 0; j < width; j++)
{
buffer[i * width + j] = scanline32Bit[j];
}
}
}
bmp = new Bitmap(width, height);
BitmapData data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat);
byte[] bytes = new byte[data.Height * data.Stride];
for (int y = 0; y < data.Height; y++) //creating Bitmap from buffer
{
for (int x = 0; x < data.Stride; x += 4)
{
bytes[y * data.Stride + x] = (byte)buffer[(y * data.Stride + x) / 4];
bytes[y * data.Stride + x + 1] = (byte)buffer[(y * data.Stride + x) / 4];
bytes[y * data.Stride + x + 2] = (byte)buffer[(y * data.Stride + x) / 4];
bytes[y * data.Stride + x + 3] = 255;
}
}
Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
bmp.UnlockBits(data);
return bmp;
}
Using a RGBA Bitmap might seem a bit naive but it's necessary in further steps of the program. Also worth mentioning: XnView displays the file similar to mine, just the negative (from white to black instead of black to white). When opening the file it notices me that it converts the image to RGB with 8bits per channel (the same thing I'm doing) and falsely claims the .tif is 16bit instead of 32bit.
Has someone an idea of what I am doing wrong?
It seems to me that the error comes purely from some kind of misuse of structs or wrongly converting between them (from float to byte and so on).
I am using BitMiracle.LibTiff.Classic from the the .NET version of original libtiff library
edit: after some research I found out that original.ReadScanline(scanline, i) returns seemingly weird values and converts them via Buffer.BlockCopy(...) to the stripes appearing in the image. For example the 4 bytes (read from scanline) of the pixel (x = 0, y = 0) are 0, 0, 200, 6, the corresponding 8bit pixel value (read from scanline32Bit) turns out to be 110. Of Pixel (x = width, y = 0) the 4 bytes are 0, 128, 111, 68 and the 8bit value displayed is 958 corresponding to 190. So now I think there's something wrong with one of those steps but I actually have no idea what's going on.
Assuming you don't mind using existing code, there is this: https://www.codeproject.com/Articles/8279/ImageConverter-Converts-images-to-a-specific-image
The author (not me, BTW) provides both the C# source and an executable. I used the executable to convert a TIF (created using paint.net) to a BMP, so I presume the source will be useful to you. I was able to open the author's solution using VS 2017 (VS upgraded both the solution and project to work in the current VS environment). The conversion is based on the System.Drawing.Imaging.ImageFormat class

Rotate raw pixel data of an image 180 degrees

I'm trying to rotate raw pixel data from a DICOM file by 180 degrees (or flipped). I've successfully flipped the image correctly, however, upon writing the pixel data back to the file (in this case it's a DICOM file) and displaying it. The final output of the image is not correct.
Below is the sample a sample of the image I'm trying to flip 180 /mirror.
Here's the code I'm using to perform the flipping:
string file = #"adicomfile.dcm";
DicomFile df = new DicomFile();
df.Load(file);
// Get the amount of bits per pixel from the DICOM header.
int bitsPerPixel = df.DataSet[DicomTags.BitsAllocated].GetInt32(0, 0);
// Get the raw pixel data from the DICOM file.
byte[] bytes = df.DataSet[DicomTags.PixelData].Values as byte[];
// Get the width and height of the image.
int width = df.DataSet[DicomTags.Columns].GetInt32(0, 0);
int height = df.DataSet[DicomTags.Rows].GetInt32(0, 0);
byte[] original = bytes;
byte[] mirroredPixels = new byte[width * height * (bitsPerPixel / 8)];
width *= (bitsPerPixel / 8);
// The mirroring / image flipping.
for (int i = 0; i < original.Length; i++)
{
int mod = i % width;
int x = ((width - mod - 1) + i) - mod;
mirroredPixels[i] = original[x];
}
df.DataSet[DicomTags.PixelData].Values = mirroredPixels;
df.Save(#"flippedicom.dcm", DicomWriteOptions.Default);
And here's my output (incorrect). The white and distortion is not the desired output.
I'm using ClearCanvas DICOM library, however this shouldn't matter as I'm only trying to manipulate the raw pixel data contained within the file itself.
The desired output would preferably look like the original, but flipped 180 / mirrored.
Some assistance would be greatly appreciated. I've tried my best searching SO, but to no avail.
It took a while, but I ended up solving my problem by using a method from a Java library. You can see the class here.
string file = #"adicomfile.dcm";
DicomFile df = new DicomFile();
df.Load(file);
// Get the amount of bits per pixel from the DICOM header.
int bitsPerPixel = df.DataSet[DicomTags.BitsAllocated].GetInt32(0, 0);
// Get the raw pixel data from the DICOM file.
byte[] bytes = df.DataSet[DicomTags.PixelData].Values as byte[];
// Get the width and height of the image.
int width = df.DataSet[DicomTags.Columns].GetInt32(0, 0);
int height = df.DataSet[DicomTags.Rows].GetInt32(0, 0);
byte[] newBytes = new byte[height * width * (bitsPerPixel / 8)];
int stride = bitsPerPixel / 8;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width * stride; x++)
{
newBytes[((height - y - 1) * (width * stride)) + x] = bytes[(y * (width * stride)) + x];
}
}
// Set patient orientation.
df.DataSet[DicomTags.PatientOrientation].Values = #"A\L";
// The pixel data of the DICOM file to the flipped/mirrored data.
df.DataSet[DicomTags.PixelData].Values = mirroredPixels;
// Save the DICOM file.
df.Save(#"flippedicom.dcm", DicomWriteOptions.Default);
The output was correct and I was able to continue other modifications to the raw pixel data.
Thank you all for the pointers.

Y coordinate is out of range in a loop

I am working on constructing and saving a bitmap, and i have a loop that sets the pixels in the bitmap to their proper values. However it crashes after a short period of ime with an IndexOutOfRange exception at the noted point in the code.
//data is an array of bytes of size (image width * image height) * 2;
Bitmap b = new Bitmap(width, height, PixelFormat.Format32bppArgb);
for (int i = 0; i < data.Length; i += 2)
{
int luminance = ((int)data[i] << 8) | (int)data[i + 1];
Color c = Color.FromArgb(luminance,luminance,luminance,luminance);
int x = i / 2;
int y = x / width;
x %= width;
b.SetPixel(x, y, c);//crashes here when Y is at 513, should only go to 512
}
b.Save(Path.GetFileNameWithoutExtension(fileName) + ".bmp");
I'm stumped as to why this happens.Why does this happen and how can i fix it?
(a note ot all of those that reommend unsafe code: I am going for a working program then a fast one. I'll be sure to write up 3 questions on the subject when i start! ;) )
When Length is odd, then at some point i+1 == Length will be true.
for (int i = 0; i < data.Length; i += 2)
{
int luminance = ((int)data[i] << 8) | (int)data[i + 1];
int x = (i + 1) / 2;
}
I would suggest replacing
//data is an array of bytes of size (image width * image height) * 2;
with
System.Diagnostics.Debug.Assert(data.Length == width * height * 2);
System.Diagnostics.Debug.Assert((data.Length % 2) == 0);
It's hard to tell what might be wrong without knowing what your data actually is. I suspect that it might be organised into rows like a bitmap, but sometimes bitmap format data requires that rows be a multiple of 4 bytes in length (with unused padding at the end, see BMP file format). If this is the case, your y value might become larger than you expect. You may need to take such padding into account.

Converting an array of Pixels to an image in C#

I have an array of int pixels in my C# program and I want to convert it into an image. The problem is I am converting Java source code for a program into equivalent C# code. In java the line reads which displays the array of int pixels into image:
Image output = createImage(new MemoryImageSource(width, height, orig, 0, width));
can someone tell me the C# equivalent?
Here orig is the array of int pixels. I searched the Bitmap class and there is a method called SetPixel but the problem is it takes a x,y coordinate number. But what I have in my code is an array of int pixels. Another weird thing is my orig array has negative number and they are way far away from 255. In Java this is the same case (meaning both the array in C# and Java have equivalent value) and the values is working fine in Java.
But I can't get that line translated into C#. Please help.
Using WPF, you can create a bitmap (image) directly from your array. You can then encode this image or display it or play with it:
int width = 200;
int height = 200;
//
// Here is the pixel format of your data, set it to the proper value for your data
//
PixelFormat pf = PixelFormats.Bgr32;
int rawStride = (width * pf.BitsPerPixel + 7) / 8;
//
// Here is your raw data
//
int[] rawImage = new int[rawStride * height / 4];
//
// Create the BitmapSource
//
BitmapSource bitmap = BitmapSource.Create(
width, height,
96, 96, pf, null,
rawImage, rawStride);
You can use Bitmap.LockBits to obtain the bitmap data that you can then manipulate directly, rather than via SetPixel. (How to use LockBits)
I like the WPF option already presented, but here it is using LockBits and Bitmap:
// get the raw image data
int width, height;
int[] data = GetData(out width, out height);
// create a bitmap and manipulate it
Bitmap bmp = new Bitmap(width,height, PixelFormat.Format32bppArgb);
BitmapData bits = bmp.LockBits(new Rectangle(0, 0, width, height),
ImageLockMode.ReadWrite, bmp.PixelFormat);
unsafe
{
for (int y = 0; y < height; y++)
{
int* row = (int*)((byte*)bits.Scan0 + (y * bits.Stride));
for (int x = 0; x < width; x++)
{
row[x] = data[y * width + x];
}
}
}
bmp.UnlockBits(bits);
With (as test data):
public static int[] GetData(out int width, out int height)
{
// diagonal gradient over a rectangle
width = 127;
height = 128;
int[] data = new int[width * height];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
int val = x + y;
data[y * width + x] = 0xFF << 24 | (val << 16) | (val << 8) | val;
}
}
return data;
}
Well, I'm assuming each int is the composite ARGB value? If there isn't an easy option, then LockBits might be worth looking at - it'll be a lot quicker than SetPixel, but is more complex. You'll also have to make sure you know how the int is composed (ARGB? RGBA?). I'll try to see if there is a more obvious option...
MemoryImageSource's constructor's 3rd argument is an array of ints composed of argb values in that order
The example in that page creates such an array by;
pix[index++] = (255 << 24) | (red << 16) | blue;
You need to decompose that integer array to a byte array (shift operator would be useful), but it should be in bgr order, for LockBits method to work.
I would recommend using LockBits but a slower SetPixel based algorithm might look something like
// width - how many int's per row
// array - array of integers
Bitmap createImage(int width, int[] array)
{
int height = array.Length / width;
Bitmap bmp = new Bitmap(width, height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < array.Length; x += width)
{
bmp.SetPixel(x, y, Color.FromArgb(array[i]));
}
}
return bmp;
}

Categories