Merging multiple Images - c#

I'm trying to merge multiple Images into one image. Problem is that most libraries with such functionality are not available in a Windows 8.1 App. I'd prefer to not have to use external libraries such as WriteableBitmapEx
This is my current code which unfortunately doesn't work:
int count = 4;
int size = 150;
WriteableBitmap destination = new WriteableBitmap(300, 300);
BitmapFrame frame = await (await BitmapDecoder.CreateAsync(randomAccessStream)).GetFrameAsync(0);
PixelDataProvider pixelData = await frame.GetPixelDataAsync();
byte[] test = pixelData.DetachPixelData();
MemoryStream mem = new MemoryStream();
for (int row = 0; row < frame.PixelHeight; row++) {
for (int i = 0; i < count; i++)
{
mem.Write(test, row * (int)frame.PixelWidth * 4, (int)frame.PixelWidth * 4);
}
}
mem.Seek(0, SeekOrigin.Begin);
BitmapImage bmp = new BitmapImage();
bmp.SetSourceAsync(mem.AsRandomAccessStream());
If I set the bmp as the source of an Image UIElement nothing happens.
My Idea was to get the Pixeldata as a byte array and to write it line by line (pixel row of each image, so they'd be next to each other) to a memory stream which is then used as the source of the BitmapImage.
Solved
Thanks to Aditya and Romasz I could solve this.
The problem was that I had to encode the pixel data back to an image.
If anyone has the same Problem the following class merges the pixel data of multiple images and returns a BitmapImage:
public class ImageMerger
{
public static async Task<BitmapImage> MergeImages(int singleWidth, int singleHeight, params byte[][] pixelData)
{
int perRow = (int) Math.Ceiling(Math.Sqrt(pixelData.Length));
byte[] mergedImageBytes = new byte[singleHeight * singleWidth * perRow * perRow * 4];
for (int i = 0; i < pixelData.Length; i++ )
{
LoadPixelBytesAt(ref mergedImageBytes, pixelData[i], (i % perRow) * singleWidth, (i / perRow) * singleHeight, perRow * singleWidth, singleWidth, singleHeight);
}
InMemoryRandomAccessStream mem = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, mem);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, (uint)(singleHeight * perRow), (uint)(singleWidth * perRow), 91, 91, mergedImageBytes);
await encoder.FlushAsync();
BitmapImage bmp = new BitmapImage();
bmp.SetSourceAsync(mem);
return bmp;
}
private static void LoadPixelBytesAt(ref byte[] dest, byte[] src, int destX, int destY, int destW, int srcW, int srcH)
{
for (int i = 0; i < srcH; i++)
{
for (int j = 0; j < srcW; j++)
{
if (src.Length < ((i * srcW + j + 1) * 4)) return;
for (int p = 0; p < 4; p++)
dest[((destY + i) * destW + destX + j) * 4 + p] = src[(i * srcW + j) * 4 + p];
}
}
}
}
This takes any number of images and puts them next to each other with around as many images from left to right as from top to bottom.
I.e. for 4 images it would return an image with them aligned like this:
1 2
3 4
Works for all of my images but one. There is one image that looks pretty weird after getting merged with others. Didn't figure out why yet.

This should do it :
byte[] PutOnCanvas(byte[] Canvas,byte[] Image,uint x,uint y,uint imageheight,uint imagewidth,uint CanvasWidth)
{
for (uint row = y; row < y+imageheight; row++)
for (uint col = x; col < x+imagewidth; col++)
for (int i = 0; i < 4; i++)
Canvas[(row * CanvasWidth + col) * 4 + i] = Image[((row-y) * imagewidth + (col - x)) * 4 + i];
return Canvas;
}
Now say I want to put two images (pixelbytes in Image1 and Image2) of 30x30 side by side and have a vertical margin of 10px in between them. I would call the function in the following way:
byte[] Canvas = new byte[30 * 70 * 4];
Canvas=PutOnCanvas(Canvas,Image1,0,0,30,30,70);
Canvas=PutOnCanvas(Canvas,Image2,40,0,30,30,70);
Then convert pixel bytes to BMP and you should be done!
Edit:
And this is the correct way to convert pixel bytes to image:
memStream.Size = 0;
var encoder = await BitmapEncoder.CreateAsync(Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId, memStream);
encoder.SetPixelData(
Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8,
Windows.Graphics.Imaging.BitmapAlphaMode.Straight,
CanvasWidth, // pixel width
CanvasHeight, // pixel height
96, // horizontal DPI
96, // vertical DPI
PixelData);
try { await encoder.FlushAsync(); }
catch { }
memStream.Dispose();

Tried this method awhile ago and it worked for me.
http://www.codeproject.com/Articles/502249/Combineplusseveralplusimagesplustoplusformplusaplu

One option is to draw them in a Canvas like you normally would and then render that Canvas out. The only problem with this is that they must all be on the screen at the same time.
Unfortunately, that's about it as far as simple solutions without something like WriteableBitmapEx goes. Their BitmapContext class abstracts away a lot of the more complex math that goes on when changing an image's width. You can check out WinRTXamlToolkit's blit implementation here, but it has the limitation that the source and destination files must be the same width (due to the annoying math).
One option may be to try and up the size of the images without scaling, hopefully creating some whitespace in the proper spot, then layering them together using a facsimile of that blit implementation, but this seems like it will be a lot of trouble as well.
Your best bet, IMO, is to cut out the chunks of WriteableBitmapEx that you need, specifically their BitmapContext and the Blit Extensions that they provide, then create a blank image and overlay each image onto the destination image (as you are attempting to do now).
This is not legal advice.
WriteableBitmapEx is Microsoft License, which is very permissive, so you should be okay to do this.
Anyway, it'd likely be easier to just add the reference, but if it's necessary that you don't, you can still cut out the parts that you need (in this case) and use them 'a la carte'.

Related

Convert 32bpp to 8bpp UWP

I'm trying to draw InkCanvas to an 8bpp image but when I try to do so the image convert itself to 32bpp, the lower I got was 24bpp but not 8bpp. Anyone can help me out? The image I am giving as input is an 8bpp BMP image created with paint.
Image imgToEdit;
InkCanvas inkCanvas;
file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(Ambiente.imgBlankFirma);
await file.CopyAsync(photoFolder, NomeFile, NameCollisionOption.ReplaceExisting);
file = await photoFolder.GetFileAsync(NomeFile);
imgToEdit = imgFirma;
inkCanvas = inkCanvasFirma;
if (inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Count <= 0)
{
errore = true;
return;
}
var randomAccessStream = await file.OpenReadAsync();
CanvasDevice device = CanvasDevice.GetSharedDevice();
CanvasRenderTarget renderTarget = new CanvasRenderTarget(device, (int)inkCanvas.ActualWidth, (int)inkCanvas.ActualHeight, 96); //inkCanvas.ActualWidth inkCanvas.ActualHeight
using (var ds = renderTarget.CreateDrawingSession())
{
var image = await CanvasBitmap.LoadAsync(device, randomAccessStream);
// draw your image first
ds.DrawImage(image);
// then draw contents of your ink canvas over it
ds.DrawInk(inkCanvas.InkPresenter.StrokeContainer.GetStrokes());
}
randomAccessStream.Dispose();
// save results
using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await renderTarget.SaveAsync(fileStream, CanvasBitmapFileFormat.Tiff, 1f);
}
This shows how to do the conversion by hand. It requires direct pointer access to the image data. So just create a new 8bpp (i.e. Format8bppIndexed) bitmap with the correct size for the conversion target. So converting the data should look something like this:
public static unsafe void Bgr24ToMono8(byte* source, byte* target, int sourceStride, int targetStride, int width, int height)
{
for(var y = 0; y < height; y++)
{
var sourceRow = source + y * sourceStride;
var targetRow = y * targetStride;
for (int x = 0; x < width; x++)
{
var sourceIndex = (sourceRow + x * 3);
var value = (byte)(sourceIndex[0] * 0.11f + sourceIndex[1] * 0.59f + sourceIndex[2] * 0.3f);
target[targetRow + x] = value;
}
}
}
if you have 32bpp data it should just be a issue of changing x * 3 to x * 4. Note that there is some confusion regarding Bgr vs Rbg, different contexts uses different terms for the same thing.
Note that this converts the bitmap to 8bpp grayscale. If you need 8bpp color indexed this will be much more work since you would ideally need to find a optimal color-map, find the closest colors in said map, and apply dithering to avoid banding. For this I would recommend some image processing library. I do not think there is any built in functions for this, and it is way to much work to demonstrate here.

c# - how to merge images in vertical?

I have this code but doesn't work.I'm trying to extract a image from a site that contains a captcha.
var width = Images.First().Image.Width; //all images in list have the same width so i take the first
var height = 0;
for (int i = 104; i < 140; i++) //the list has 300 images. I have to get 36 that contains the captcha separated into pieces
{
height += Images[i].Image.Height;
}
var bitmap2 = new Bitmap(width, height);
var g = Graphics.FromImage(bitmap2);
height = 0;
for (int i = 104; i < 140; i++)
{
Image image = Images[i].Image;
g.DrawImage(image, 0, height);
height += image.Height;
}
bitmap2.Save(#"C:\Users\user\Desktop\test\test.png", ImageFormat.Png);
With this code i get this result:
image
I don't know why it is of poor quality. I think it is repeating the images that are recorded in the result bitmap
I can see a few suboptimal things in the code, but, to be honest, not a single thing that can give that result. The only way you get problems like that is if you go messing with the raw back-end and perform operations that mess up how the data is interpreted as image.
The only two specific things that need fixing in the code seem to be:
Setting the resolution of all images to the same values. This affects how large they are drawn, and thus can mess up positioning
Closing the Graphics object after you're done with it, so all changes are confirmed to be finished before you attempt to save anything.
Note that in my adjusted code, images is just a List<Bitmap>, and the for-loop just goes over them all. You never specified what type your Images collection was, and this was much easier for me to test.
Int32 width = Images.First().Width;
Int32 height = 0;
for (Int32 i = 0; i < Images.Count; i++)
{
height += Images[i].Height;
}
Bitmap bitmap2 = new Bitmap(width, height);
bitmap2.SetResolution(72, 72); // <-- Set explicit resolution on bitmap2
// Always put Graphics objects in a 'using' block.
using (Graphics g = Graphics.FromImage(bitmap2))
{
height = 0;
for (Int32 i = 0; i < Images.Count; i++)
{
Bitmap image = Images[i];
image.SetResolution(72, 72); // <-- Set resolution equal to bitmap2
g.DrawImage(image, 0, height);
height += image.Height;
}
}
bitmap2.Save(#"C:\Users\user\Desktop\test\test.png", ImageFormat.Png);

Reading RGB values from TIFF Image in C#

I have a tiff Image and I am reading the RGB values of each pixel with following code. the Image Height :16198 width :12900.
But the code is taking much longer time (more than 20 minutes). tried various way like converting it to bitmap, but non of them leads to better performance. any suggestions
using (Tiff tiffreader = Tiff.Open(imgpath, "r"))
{
img.Width = tiffreader.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
img.Height = tiffreader.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
img.DipX = tiffreader.GetField(TiffTag.XRESOLUTION)[0].ToInt();
img.DipY = tiffreader.GetField(TiffTag.YRESOLUTION)[0].ToInt();
// Reading RGB values
int height = (int)img.Height;
int width = (int)img.Width;
int[] raster = new int[height*width];
var b = tiffreader.ReadRGBAImage(width, height, raster);
img.Pixels = new PColor[height, width];
img.Pixels = Utility.ConvertToRGB(height, width, raster);
}
internal static PColor[,] ConvertToRGB(int height, int width, int[] raster)
{
PColor[,] pcolor = new PColor[height, width];
try
{
for (int i = 0; i < height; ++i)
for (int j = 0; j < width; ++j)
{
int offset = (height - j - 1) * width + i;
PColor color = new PColor();
color.R = Tiff.GetR(raster[offset]);
color.G = Tiff.GetG(raster[offset]);
color.B = Tiff.GetB(raster[offset]);
pcolor[i, j] = color;
}
}
catch(Exception exp)
{
throw exp;
}
return pcolor;
}
It looks like you are doing unnecessary processing of the file. The whole loop that calls ReadScanline seems to be pointless since you just read the whole file later with a call to ReadRGBAImage. I'm not sure what class backs the img variable, but it looks like it has duplicated data in it (The Pixels property and the bytearray variable). I would see if you can refactor your code to remove one of these items.
It would also be good to change your loops to convert to RGB to have i (the height) be the inside loop so that you are accessing the raster array in order (which saves a lot of memory fetches).
Also note that 2-dimensional array access is significantly slower than 1d because of double bounds checking. In my own experience it's actually faster to calculate the offset of a 1d array manually than to use a C# 2d array. It might be worth refactoring Pixels to be 1d.
EDIT: If you can't refactor the code to remove Pixels or bytearray, then I would suggest creating the bytearray variable contents from the contents of raster instead of re-reading the file again.

How to convert from System.Drawing.Bitmap to grayscale, then to array of doubles (Double[,]), then back to grayscale Bitmap?

I need to perform some mathematical operations in photographs, and for that I need the floating point grayscale version of an image (which might come from JPG, PNG or BMP files with various colordepths).
I used to do that in Python using PIL and scipy.ndimage, and it was very straightforward to convert to grayscale with PIL and then to an array of floating-point numbers with numpy, but now I need to do something similar in C#, and I'm confused how to do so.
I have read this very nice tutorial, that seems to be a recurring reference, but that only covers the "convert to grayscale" part, I am not sure how to get an array of doubles from a Bitmap, and then (at some moment) to convert it back to System.Drawing.Bitmap for viewing.
I'm sure there are loads of optimal ways to do this.
As #Groo points out perfectly in the comments section, one could use for instance the LockBits method to write and read pixel colors to and from a Bitmap instance.
Going even further, one could use the graphics card of the computer to do the actual computations.
Furthermore, the method Color ToGrayscaleColor(Color color) which turns a color into its
grayscale version is not optically correct. There is a set of ratios which actually need to be applied to the color component strengths. I just used 1, 1, 1 ratios. That's accceptable for me and probably horrible for an artist or a scientist.
In the comments section, #plinth was very nice to point out to this question you should look at, if you want to make an anatomically correct conversion: Converting RGB to grayscale/intensity
Just wanted to share this really easy to understand and implement solution:
First a little helper to turn a Color into it's grayscale version:
public static Color ToGrayscaleColor(Color color) {
var level = (byte)((color.R + color.G + color.B) / 3);
var result = Color.FromArgb(level, level, level);
return result;
}
Then for the color bitmap to grayscale bitmap conversion:
public static Bitmap ToGrayscale(Bitmap bitmap) {
var result = new Bitmap(bitmap.Width, bitmap.Height);
for (int x = 0; x < bitmap.Width; x++)
for (int y = 0; y < bitmap.Height; y++) {
var grayColor = ToGrayscaleColor(bitmap.GetPixel(x, y));
result.SetPixel(x, y, grayColor);
}
return result;
}
The doubles part is quite easy. The Bitmap object is a memory representation of the actual image which you can use in various operations. The colordepth and image format details are only the concern of loading and saving instances of Bitmap onto streams or files. We needn't care about those at this point:
public static double[,] FromGrayscaleToDoubles(Bitmap bitmap) {
var result = new double[bitmap.Width, bitmap.Height];
for (int x = 0; x < bitmap.Width; x++)
for (int y = 0; y < bitmap.Height; y++)
result[x, y] = (double)bitmap.GetPixel(x, y).R / 255;
return result;
}
And turning a double array back into a grayscale image:
public static Bitmap FromDoublesToGrayscal(double[,] doubles) {
var result = new Bitmap(doubles.GetLength(0), doubles.GetLength(1));
for (int x = 0; x < result.Width; x++)
for (int y = 0; y < result.Height; y++) {
int level = (int)Math.Round(doubles[x, y] * 255);
if (level > 255) level = 255; // just to be sure
if (level < 0) level = 0; // just to be sure
result.SetPixel(x, y, Color.FromArgb(level, level, level));
}
return result;
}
The following lines:
if (level > 255) level = 255; // just to be sure
level < 0) level = 0; // just to be sure
are really there in case you operate on the doubles and you want to allow room for little mistakes.
The final code, based mostly in tips taken from the comments, specifically the LockBits part (blog post here) and the perceptual balancing between R, G and B values (not paramount here, but something to know about):
private double[,] TransformaImagemEmArray(System.Drawing.Bitmap imagem) {
// Transforma a imagem de entrada em um array de doubles
// com os valores grayscale da imagem
BitmapData bitmap_data = imagem.LockBits(new System.Drawing.Rectangle(0,0,_foto_franjas_original.Width,_foto_franjas_original.Height),
ImageLockMode.ReadOnly, _foto_franjas_original.PixelFormat);
int pixelsize = System.Drawing.Image.GetPixelFormatSize(bitmap_data.PixelFormat)/8;
IntPtr pointer = bitmap_data.Scan0;
int nbytes = bitmap_data.Height * bitmap_data.Stride;
byte[] imagebytes = new byte[nbytes];
System.Runtime.InteropServices.Marshal.Copy(pointer, imagebytes, 0, nbytes);
double red;
double green;
double blue;
double gray;
var _grayscale_array = new Double[bitmap_data.Height, bitmap_data.Width];
if (pixelsize >= 3 ) {
for (int I = 0; I < bitmap_data.Height; I++) {
for (int J = 0; J < bitmap_data.Width; J++ ) {
int position = (I * bitmap_data.Stride) + (J * pixelsize);
blue = imagebytes[position];
green = imagebytes[position + 1];
red = imagebytes[position + 2];
gray = 0.299 * red + 0.587 * green + 0.114 * blue;
_grayscale_array[I,J] = gray;
}
}
}
_foto_franjas_original.UnlockBits(bitmap_data);
return _grayscale_array;
}

Converting iTextSharp.text.Image back to System.Drawing.Image

I am trying to convert object of type iTextSharp.text.Image back to System.Drawing.Image.
Here is a chunk of code that is not working:
System.Drawing.Image img = System.Drawing.Image.FromStream(new MemoryStream(itextImg.RawData));
I could be going about this all wrong, but I won't know unless I consult the experts, and after two hours of fruitless searching online, I am finally posting it myself as a question.
I'm pretty sure that will work occasionally, but will fail in the general case... it depends on what compression filters the image is using.
I believe JPEG image streams are exactly what you'd see in a .jpeg file... but for most (all?) other compression types, the image information (height, width, bits per component, number of components, etc) is vital.
So it'll be possible, but Not Like That.
PS: There's at least one image format that iText cannot decompress, CITTFAXDecode (JBIG2, probably others). In those cases, you'll need some Other Software that will get the raw pixel data out so you can wrap it in a Drawing.Image.
Yes, I found the solution by rewriting BarcodeQRCode class of ITextSharp and GetImageMethod() as described below:
using System;
using System.Collections.Generic;
using System.Drawing;
using iTextSharp.text;
using iTextSharp.text.pdf.qrcode;
using iTextSharp.text.pdf.codec;
/*
Class rewritted to Convert ByteMatrix to BMP image by rewritting GetImage method
from ITextSharp
author: Luis Claudio Souza
*/
namespace iTextSharp.text.pdf{
/**
* A QRCode implementation based on the zxing code.
* #author Paulo Soares
* #since 5.0.2
*/
public class BarcodeQRCode {
ByteMatrix bm;
/**
* Creates the QR barcode. The barcode is always created with the smallest possible size and is then stretched
* to the width and height given. Set the width and height to 1 to get an unscaled barcode.
* #param content the text to be encoded
* #param width the barcode width
* #param height the barcode height
* #param hints modifiers to change the way the barcode is create. They can be EncodeHintType.ERROR_CORRECTION
* and EncodeHintType.CHARACTER_SET. For EncodeHintType.ERROR_CORRECTION the values can be ErrorCorrectionLevel.L, M, Q, H.
* For EncodeHintType.CHARACTER_SET the values are strings and can be Cp437, Shift_JIS and ISO-8859-1 to ISO-8859-16. The default value is
* ISO-8859-1.
* #throws WriterException
*/
public BarcodeQRCode(String content, int width, int height, IDictionary<EncodeHintType, Object> hints) {
QRCodeWriter qc = new QRCodeWriter();
bm = qc.Encode(content, width, height, hints);
}
private byte[] GetBitMatrix() {
int width = bm.GetWidth();
int height = bm.GetHeight();
int stride = (width + 7) / 8;
byte[] b = new byte[stride * height];
sbyte[][] mt = bm.GetArray();
for (int y = 0; y < height; ++y) {
sbyte[] line = mt[y];
for (int x = 0; x < width; ++x) {
if (line[x] != 0) {
int offset = stride * y + x / 8;
b[offset] |= (byte)(0x80 >> (x % 8));
}
}
}
return b;
}
/** Gets an <CODE>Image</CODE> with the barcode.
* #return the barcode <CODE>Image</CODE>
* #throws BadElementException on error
*/
public void GetImage()
{
sbyte[][] imgNew = bm.GetArray();
Bitmap bmp1 = new Bitmap(bm.GetWidth(), bm.GetHeight());
Graphics g1 = Graphics.FromImage(bmp1);
g1.Clear(Color.White);
for (int i = 0; i <= imgNew.Length - 1; i++)
{
for (int j = 0; j <= imgNew[i].Length - 1; j++)
{
if (imgNew[j][i] == 0)
{
g1.FillRectangle(Brushes.Black, i, j, 1, 1);
}
else
{
g1.FillRectangle(Brushes.White, i, j, 1, 1);
}
}
}
bmp1.Save("D:\\QREncode.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
After that, I used this code to call the method:
var hints = new Dictionary<EncodeHintType, object>();
hints.Add(EncodeHintType.ERROR_CORRECTION, iTextSharp.text.pdf.qrcode.ErrorCorrectionLevel.H);
BarcodeQRCode code = new BarcodeQRCode("98134979213479523874952873", 100, 100, hints);
code.GetImage();
In new GetImage method, you can choose what to do with the bmp class. In this case, it saves a JPEG image file but the method can either return a memorystream to be used by the caller.

Categories