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();

其他提示

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'.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top