I have a console application that manages images. Now i need something like a preview of the Images within the console application. Is there a way to display them in the console?
Here is a comparison of the current character based answers:
Input:
Output:
Though showing an image in a console is not the intended usage of the console, you can surely hack the things, as the console window is just a window, like any other windows.
Actually, once I have started to develop a text controls library for console applications with graphics support. I have never finished that, though I have a working proof-of-concept demo:
And if you obtain the console font size, you can place the image very precisely.
This is how you can do it:
static void Main(string[] args)
{
Console.WriteLine("Graphics in console window!");
Point location = new Point(10, 10);
Size imageSize = new Size(20, 10); // desired image size in characters
// draw some placeholders
Console.SetCursorPosition(location.X - 1, location.Y);
Console.Write(">");
Console.SetCursorPosition(location.X + imageSize.Width, location.Y);
Console.Write("<");
Console.SetCursorPosition(location.X - 1, location.Y + imageSize.Height - 1);
Console.Write(">");
Console.SetCursorPosition(location.X + imageSize.Width, location.Y + imageSize.Height - 1);
Console.WriteLine("<");
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures), #"Sample Pictures\tulips.jpg");
using (Graphics g = Graphics.FromHwnd(GetConsoleWindow()))
{
using (Image image = Image.FromFile(path))
{
Size fontSize = GetConsoleFontSize();
// translating the character positions to pixels
Rectangle imageRect = new Rectangle(
location.X * fontSize.Width,
location.Y * fontSize.Height,
imageSize.Width * fontSize.Width,
imageSize.Height * fontSize.Height);
g.DrawImage(image, imageRect);
}
}
}
Here is how you can obtain the current console font size:
private static Size GetConsoleFontSize()
{
// getting the console out buffer handle
IntPtr outHandle = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
0,
IntPtr.Zero);
int errorCode = Marshal.GetLastWin32Error();
if (outHandle.ToInt32() == INVALID_HANDLE_VALUE)
{
throw new IOException("Unable to open CONOUT$", errorCode);
}
ConsoleFontInfo cfi = new ConsoleFontInfo();
if (!GetCurrentConsoleFont(outHandle, false, cfi))
{
throw new InvalidOperationException("Unable to get font information.");
}
return new Size(cfi.dwFontSize.X, cfi.dwFontSize.Y);
}
And the required additional WinApi calls, constants and types:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetConsoleWindow();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(
string lpFileName,
int dwDesiredAccess,
int dwShareMode,
IntPtr lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetCurrentConsoleFont(
IntPtr hConsoleOutput,
bool bMaximumWindow,
[Out][MarshalAs(UnmanagedType.LPStruct)]ConsoleFontInfo lpConsoleCurrentFont);
[StructLayout(LayoutKind.Sequential)]
internal class ConsoleFontInfo
{
internal int nFont;
internal Coord dwFontSize;
}
[StructLayout(LayoutKind.Explicit)]
internal struct Coord
{
[FieldOffset(0)]
internal short X;
[FieldOffset(2)]
internal short Y;
}
private const int GENERIC_READ = unchecked((int)0x80000000);
private const int GENERIC_WRITE = 0x40000000;
private const int FILE_SHARE_READ = 1;
private const int FILE_SHARE_WRITE = 2;
private const int INVALID_HANDLE_VALUE = -1;
private const int OPEN_EXISTING = 3;
And the result:
[
I further played with code from #DieterMeemken. I halved vertical resolution and added dithering via ░▒▓. On the left is Dieter Meemken result, on the right my. On the bottom is original picture resized to rougly match the output.
While Malwyns conversion function is impressive, it does not use all gray colors, what is pity.
static int[] cColors = { 0x000000, 0x000080, 0x008000, 0x008080, 0x800000, 0x800080, 0x808000, 0xC0C0C0, 0x808080, 0x0000FF, 0x00FF00, 0x00FFFF, 0xFF0000, 0xFF00FF, 0xFFFF00, 0xFFFFFF };
public static void ConsoleWritePixel(Color cValue)
{
Color[] cTable = cColors.Select(x => Color.FromArgb(x)).ToArray();
char[] rList = new char[] { (char)9617, (char)9618, (char)9619, (char)9608 }; // 1/4, 2/4, 3/4, 4/4
int[] bestHit = new int[] { 0, 0, 4, int.MaxValue }; //ForeColor, BackColor, Symbol, Score
for (int rChar = rList.Length; rChar > 0; rChar--)
{
for (int cFore = 0; cFore < cTable.Length; cFore++)
{
for (int cBack = 0; cBack < cTable.Length; cBack++)
{
int R = (cTable[cFore].R * rChar + cTable[cBack].R * (rList.Length - rChar)) / rList.Length;
int G = (cTable[cFore].G * rChar + cTable[cBack].G * (rList.Length - rChar)) / rList.Length;
int B = (cTable[cFore].B * rChar + cTable[cBack].B * (rList.Length - rChar)) / rList.Length;
int iScore = (cValue.R - R) * (cValue.R - R) + (cValue.G - G) * (cValue.G - G) + (cValue.B - B) * (cValue.B - B);
if (!(rChar > 1 && rChar < 4 && iScore > 50000)) // rule out too weird combinations
{
if (iScore < bestHit[3])
{
bestHit[3] = iScore; //Score
bestHit[0] = cFore; //ForeColor
bestHit[1] = cBack; //BackColor
bestHit[2] = rChar; //Symbol
}
}
}
}
}
Console.ForegroundColor = (ConsoleColor)bestHit[0];
Console.BackgroundColor = (ConsoleColor)bestHit[1];
Console.Write(rList[bestHit[2] - 1]);
}
public static void ConsoleWriteImage(Bitmap source)
{
int sMax = 39;
decimal percent = Math.Min(decimal.Divide(sMax, source.Width), decimal.Divide(sMax, source.Height));
Size dSize = new Size((int)(source.Width * percent), (int)(source.Height * percent));
Bitmap bmpMax = new Bitmap(source, dSize.Width * 2, dSize.Height);
for (int i = 0; i < dSize.Height; i++)
{
for (int j = 0; j < dSize.Width; j++)
{
ConsoleWritePixel(bmpMax.GetPixel(j * 2, i));
ConsoleWritePixel(bmpMax.GetPixel(j * 2 + 1, i));
}
System.Console.WriteLine();
}
Console.ResetColor();
}
usage:
Bitmap bmpSrc = new Bitmap(#"HuwnC.gif", true);
ConsoleWriteImage(bmpSrc);
EDIT
Color distance is complex topic (here, here and links on those pages...). I tried to calculate distance in YUV and results were rather worse than in RGB. They could be better with Lab and DeltaE, but I did not try that. Distance in RGB seems to be good enough. In fact results are very simmilar for both euclidean and manhattan distance in RGB color space, so I suspect there are just too few colors to choose from.
The rest is just brute force compare of color against all combinations of colors and patterns (=symbols). I stated fill ratio for ░▒▓█ to be 1/4, 2/4, 3/4 and 4/4. In that case the third symbol is in fact redundant to the first. But if ratios were not such uniform (depends on font), results could change, so I left it there for future improvements. Average color of symbol is calculated as weighed average of foregroudColor and backgroundColor according to fill ratio. It assumes linear colors, what is also big simplification. So there is still room for improvement.
If you use ASCII 219 ( █ ) twice, you have something like a pixel ( ██ ).
Now you are restricted by the amount of pixels and the number of colors in your console application.
if you keep default settings you have about 39x39 pixel, if you want more you can resize your console with Console.WindowHeight = resSize.Height + 1;and Console.WindowWidth = resultSize.Width * 2;
you have to keep the image's aspect-ratio as far as possible, so you won't have 39x39 in the most cases
Malwyn posted a totally underrated method to convert System.Drawing.Color to System.ConsoleColor
so my approach would be
using System.Drawing;
public static int ToConsoleColor(System.Drawing.Color c)
{
int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0;
index |= (c.R > 64) ? 4 : 0;
index |= (c.G > 64) ? 2 : 0;
index |= (c.B > 64) ? 1 : 0;
return index;
}
public static void ConsoleWriteImage(Bitmap src)
{
int min = 39;
decimal pct = Math.Min(decimal.Divide(min, src.Width), decimal.Divide(min, src.Height));
Size res = new Size((int)(src.Width * pct), (int)(src.Height * pct));
Bitmap bmpMin = new Bitmap(src, res);
for (int i = 0; i < res.Height; i++)
{
for (int j = 0; j < res.Width; j++)
{
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i));
Console.Write("██");
}
System.Console.WriteLine();
}
}
so you can
ConsoleWriteImage(new Bitmap(#"C:\image.gif"));
sample input:
sample output:
that was fun.
Thanks fubo, i tried your solution and was able to increase the resolution of the preview by 4 (2x2).
I found, that you can set the background color for each individual char. So, instead of using two ASCII 219 ( █ ) chars, i used ASCII 223 ( ▀ ) two times with different foreground and background Colors. That divides the big Pixel ( ██ ) in 4 subpixels like this ( ▀▄ ).
In this example i put both images next to each other, so you can see the difference easily:
Here is the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace ConsoleWithImage
{
class Program
{
public static void ConsoleWriteImage(Bitmap bmpSrc)
{
int sMax = 39;
decimal percent = Math.Min(decimal.Divide(sMax, bmpSrc.Width), decimal.Divide(sMax, bmpSrc.Height));
Size resSize = new Size((int)(bmpSrc.Width * percent), (int)(bmpSrc.Height * percent));
Func<System.Drawing.Color, int> ToConsoleColor = c =>
{
int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0;
index |= (c.R > 64) ? 4 : 0;
index |= (c.G > 64) ? 2 : 0;
index |= (c.B > 64) ? 1 : 0;
return index;
};
Bitmap bmpMin = new Bitmap(bmpSrc, resSize.Width, resSize.Height);
Bitmap bmpMax = new Bitmap(bmpSrc, resSize.Width * 2, resSize.Height * 2);
for (int i = 0; i < resSize.Height; i++)
{
for (int j = 0; j < resSize.Width; j++)
{
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i));
Console.Write("██");
}
Console.BackgroundColor = ConsoleColor.Black;
Console.Write(" ");
for (int j = 0; j < resSize.Width; j++)
{
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2));
Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2 + 1));
Console.Write("▀");
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2));
Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2 + 1));
Console.Write("▀");
}
System.Console.WriteLine();
}
}
static void Main(string[] args)
{
System.Console.WindowWidth = 170;
System.Console.WindowHeight = 40;
Bitmap bmpSrc = new Bitmap(#"image.bmp", true);
ConsoleWriteImage(bmpSrc);
System.Console.ReadLine();
}
}
}
To run the example, the bitmap "image.bmp" has to be in the same directory as the executable. I increased the size of the console, the size of the preview is still 39 and can be changed at int sMax = 39;.
The solution from taffer is also very cool.
You two have my upvote...
I was reading about color spaces and LAB space appears to be a good option for you (see this questions: Finding an accurate “distance” between colors and Algorithm to check similarity of colors)
Quoting Wikipedia CIELAB page, the advantages of this color space are:
Unlike the RGB and CMYK color models, Lab color is designed to approximate human vision. It aspires to perceptual uniformity, and its L component closely matches human perception of lightness. Thus, it can be used to make accurate color balance corrections by modifying output curves in the a and b components.
To measure the distance between colors you can use Delta E distance.
With this you can approximate better from Color to ConsoleColor:
Firstly, you can define an CieLab class to represent colors in this space:
public class CieLab
{
public double L { get; set; }
public double A { get; set; }
public double B { get; set; }
public static double DeltaE(CieLab l1, CieLab l2)
{
return Math.Pow(l1.L - l2.L, 2) + Math.Pow(l1.A - l2.A, 2) + Math.Pow(l1.B - l2.B, 2);
}
public static CieLab Combine(CieLab l1, CieLab l2, double amount)
{
var l = l1.L * amount + l2.L * (1 - amount);
var a = l1.A * amount + l2.A * (1 - amount);
var b = l1.B * amount + l2.B * (1 - amount);
return new CieLab { L = l, A = a, B = b };
}
}
There are two static methods, one to measure the distance using Delta E (DeltaE) and other to combine two colors specifying how much of each color (Combine).
And for transform from RGB to LAB you can use the following method (from here):
public static CieLab RGBtoLab(int red, int green, int blue)
{
var rLinear = red / 255.0;
var gLinear = green / 255.0;
var bLinear = blue / 255.0;
double r = rLinear > 0.04045 ? Math.Pow((rLinear + 0.055) / (1 + 0.055), 2.2) : (rLinear / 12.92);
double g = gLinear > 0.04045 ? Math.Pow((gLinear + 0.055) / (1 + 0.055), 2.2) : (gLinear / 12.92);
double b = bLinear > 0.04045 ? Math.Pow((bLinear + 0.055) / (1 + 0.055), 2.2) : (bLinear / 12.92);
var x = r * 0.4124 + g * 0.3576 + b * 0.1805;
var y = r * 0.2126 + g * 0.7152 + b * 0.0722;
var z = r * 0.0193 + g * 0.1192 + b * 0.9505;
Func<double, double> Fxyz = t => ((t > 0.008856) ? Math.Pow(t, (1.0 / 3.0)) : (7.787 * t + 16.0 / 116.0));
return new CieLab
{
L = 116.0 * Fxyz(y / 1.0) - 16,
A = 500.0 * (Fxyz(x / 0.9505) - Fxyz(y / 1.0)),
B = 200.0 * (Fxyz(y / 1.0) - Fxyz(z / 1.0890))
};
}
The idea is use shade characters like #AntoninLejsek do ('█', '▓', '▒', '░'), this allows you to get more than 16 colors combining the console colors (using Combine method).
Here, we can do some improvements by pre-computing the colors to use:
class ConsolePixel
{
public char Char { get; set; }
public ConsoleColor Forecolor { get; set; }
public ConsoleColor Backcolor { get; set; }
public CieLab Lab { get; set; }
}
static List<ConsolePixel> pixels;
private static void ComputeColors()
{
pixels = new List<ConsolePixel>();
char[] chars = { '█', '▓', '▒', '░' };
int[] rs = { 0, 0, 0, 0, 128, 128, 128, 192, 128, 0, 0, 0, 255, 255, 255, 255 };
int[] gs = { 0, 0, 128, 128, 0, 0, 128, 192, 128, 0, 255, 255, 0, 0, 255, 255 };
int[] bs = { 0, 128, 0, 128, 0, 128, 0, 192, 128, 255, 0, 255, 0, 255, 0, 255 };
for (int i = 0; i < 16; i++)
for (int j = i + 1; j < 16; j++)
{
var l1 = RGBtoLab(rs[i], gs[i], bs[i]);
var l2 = RGBtoLab(rs[j], gs[j], bs[j]);
for (int k = 0; k < 4; k++)
{
var l = CieLab.Combine(l1, l2, (4 - k) / 4.0);
pixels.Add(new ConsolePixel
{
Char = chars[k],
Forecolor = (ConsoleColor)i,
Backcolor = (ConsoleColor)j,
Lab = l
});
}
}
}
Another improvement could be access directly to the image data using LockBits instead of using GetPixel.
UPDATE: If the image have parts with the same color you can speed up considerably the process drawing chunk of characters having the same colors, instead of individuals chars:
public static void DrawImage(Bitmap source)
{
int width = Console.WindowWidth - 1;
int height = (int)(width * source.Height / 2.0 / source.Width);
using (var bmp = new Bitmap(source, width, height))
{
var unit = GraphicsUnit.Pixel;
using (var src = bmp.Clone(bmp.GetBounds(ref unit), PixelFormat.Format24bppRgb))
{
var bits = src.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, src.PixelFormat);
byte[] data = new byte[bits.Stride * bits.Height];
Marshal.Copy(bits.Scan0, data, 0, data.Length);
for (int j = 0; j < height; j++)
{
StringBuilder builder = new StringBuilder();
var fore = ConsoleColor.White;
var back = ConsoleColor.Black;
for (int i = 0; i < width; i++)
{
int idx = j * bits.Stride + i * 3;
var pixel = DrawPixel(data[idx + 2], data[idx + 1], data[idx + 0]);
if (pixel.Forecolor != fore || pixel.Backcolor != back)
{
Console.ForegroundColor = fore;
Console.BackgroundColor = back;
Console.Write(builder);
builder.Clear();
}
fore = pixel.Forecolor;
back = pixel.Backcolor;
builder.Append(pixel.Char);
}
Console.ForegroundColor = fore;
Console.BackgroundColor = back;
Console.WriteLine(builder);
}
Console.ResetColor();
}
}
}
private static ConsolePixel DrawPixel(int r, int g, int b)
{
var l = RGBtoLab(r, g, b);
double diff = double.MaxValue;
var pixel = pixels[0];
foreach (var item in pixels)
{
var delta = CieLab.DeltaE(l, item.Lab);
if (delta < diff)
{
diff = delta;
pixel = item;
}
}
return pixel;
}
Finally, call DrawImage like so:
static void Main(string[] args)
{
ComputeColors();
Bitmap image = new Bitmap("image.jpg", true);
DrawImage(image);
}
Result Images:
The following solutions aren't based on chars but provides full detailed images
You can draw over any window using its handler to create a Graphics object. To get the handler of a console application you can do it importing GetConsoleWindow:
[DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow", SetLastError = true)]
private static extern IntPtr GetConsoleHandle();
Then, create a graphics with the handler (using Graphics.FromHwnd) and draw the image using the methods in Graphics object, for example:
static void Main(string[] args)
{
var handler = GetConsoleHandle();
using (var graphics = Graphics.FromHwnd(handler))
using (var image = Image.FromFile("img101.png"))
graphics.DrawImage(image, 50, 50, 250, 200);
}
This looks fine but if the console is resized or scrolled, the image disappears because the windows is refreshed (maybe implementing some kind of mechanism to redraw the image is possible in your case).
Another solution is embedding a window (Form) into the console application. To do this you have to import SetParent (and MoveWindow to relocate the window inside the console):
[DllImport("user32.dll")]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
Then you just need to create a Form and set BackgroundImage property to the desired image (do it on a Thread or Task to avoid blocking the console):
static void Main(string[] args)
{
Task.Factory.StartNew(ShowImage);
Console.ReadLine();
}
static void ShowImage()
{
var form = new Form
{
BackgroundImage = Image.FromFile("img101.png"),
BackgroundImageLayout = ImageLayout.Stretch
};
var parent = GetConsoleHandle();
var child = form.Handle;
SetParent(child, parent);
MoveWindow(child, 50, 50, 250, 200, true);
Application.Run(form);
}
Of course you can set FormBorderStyle = FormBorderStyle.None to hide windows borders (right image)
In this case you can resize the console and the image/window still be there.
One benefit with this approach is that you can locate the window where you want and change the image at any time by just changing BackgroundImage property.
There's no direct way. But you may try to use an image-to-ascii-art converter like this one
Yes, you can do it, if you stretch the question a little by opening a Form from within the Console application.
Here is how you can have you console application open a form and display an image:
include these two references in your project: System.Drawing and System.Windows.Forms
include the two namespaces as well:
using System.Windows.Forms;
using System.Drawing;
See this post on how to do that!
Now all you need it to add something like this:
Form form1 = new Form();
form1.BackgroundImage = bmp;
form1.ShowDialog();
Of course you can also use a PictureBox..
And you can use form1.Show(); to keep the console alive while the preview shows..
Original post: Of course you can't properly display an image inside a 25x80 window; even if you use a larger window and block graphics it wouldn't be a preview but a mess!
Update: Looks like you can after all GDI-draw an image onto the Console Form; see taffer's answer!
I need to fill a rectangle with a black to white (transparent) gradient. However, I could only find a GradientBrush class and all examples I found showed smooth transition and I want sharp bars. That's what I need:
You need to average the colors between your start color and your end color. Here is a routine that does all that, using an averaging formula found here: Generate Color Gradient in C#
private void PaintGradientBars(Graphics g, Rectangle r,
Color startColor, Color endColor, int numBars) {
int rMin = startColor.R;
int gMin = startColor.G;
int bMin = startColor.B;
int rMax = endColor.R;
int gMax = endColor.G;
int bMax = endColor.B;
int left = 0;
for (int i = 0; i < numBars; i++) {
int rAvg = rMin + (int)((rMax - rMin) * i / numBars);
int gAvg = gMin + (int)((gMax - gMin) * i / numBars);
int bAvg = bMin + (int)((bMax - bMin) * i / numBars);
Color useColor = Color.FromArgb(rAvg, gAvg, bAvg);
int width = (r.Width - left) / (numBars - i);
using (SolidBrush br = new SolidBrush(useColor)) {
g.FillRectangle(br, new Rectangle(left, 0, width, r.Height));
}
left += width;
}
}
Then you make a simple call:
private void panel1_Paint(object sender, PaintEventArgs e) {
PaintGradientBars(e.Graphics, panel1.ClientRectangle,
Color.Blue, Color.Green, 5);
}
Resulting in:
in this code i use picturebox, play with 'k' and 'i'
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
int k = 20;
Color mycolor = new Color();
for (int i = 0; i < 10; i++)
{
mycolor = Color.FromArgb(i * k, i * k, i * k);
SolidBrush mybrash = new SolidBrush(mycolor);
e.Graphics.FillRectangle((Brush)mybrash, 0 + i * k, 0, k, k);
}
}
Good luck!
I'm looking for a way to improve the performance of some drawing I am doing. Currently it is a 32x32 grid of tiles that I am drawing. Using the following code to draw onto the drawing context
for (int x = startX; x < endX; x++)
{
for (int y = startY; y < endY; y++)
{
dg.Children.Add(
new ImageDrawing(_mapTiles[GameWorldObject.GameMap[x, y].GraphicsTile.TileStartPoint],
new Rect(CountX * 8, CountY * 8, 8, 8)
));
dg.Children.Add(
new GeometryDrawing(
null,
new Pen(
new SolidColorBrush(
Color.FromRgb(255, 0, 20)), .3),
new RectangleGeometry(
new Rect(CountX * 8, CountY * 8, 8, 8)
)
)
);
CountY++;
}
CountY = 0;
CountX++;
}
dc.DrawDrawing(dg);
The Image I am drawing is a CachedBitmap. Even using a CachedBitmap, I still have a delay of about a half second each time I need to redraw the Canvas.
Not sure if there is a more performant way to handle drawing to this grid. Eventually I want to expand control to function as a mini-map, so I need to keep that in mind.
Also, I tried previously to just draw each bitmap directly to the drawing context but that seems a bit slower.
I added DrawingGroup.Freeze() before drawing, and it seemed to help with the performance.
If it's mostly static minimap draw it into an Image, and draw that image. Or you can do a big image, where you draw the whole map into it, and just draw the current visible part of it.
Edit: And maybe this blog post worth a check, whether you are drawing it with software or hardware acceleration on.
Here's a example using WriteableBitmap, the performance of this is mainly related to the size of the whole map whereas your original method is more dependent on the amount of tiles. You could alter it to have an alpha-blended border between the tiles but leaving a gap between them would be easier and more performant. You won't need the code randomising the tiles but you should have some dirty flag so you only redraw the bitmap when its changed.
You may also want to look my answer and the others to this question. That said you don't have as many items and 32x32 using your method wasn't slow for me.
<local:Map x:Name="map" />
<RepeatButton Click="Button_Click" Content="Change" />
private void Button_Click(object sender, RoutedEventArgs e)
{
map.seed++;
map.InvalidateVisual();
}
public class Map : FrameworkElement
{
private int[][] _mapTiles;
public Map()
{
_mapTiles = Directory.GetFiles(#"C:\Users\Public\Pictures\Sample Pictures", "*.jpg").Select(x =>
{
var b = new BitmapImage(new Uri(x));
var transform = new TransformedBitmap(b, new ScaleTransform((1.0 / b.PixelWidth)*tileSize,(1.0 / b.PixelHeight)*tileSize));
var conv = new FormatConvertedBitmap(transform, PixelFormats.Pbgra32, null, 0);
int[] data = new int[tileSize * tileSize];
conv.CopyPixels(data, tileSize * 4, 0);
return data;
}).ToArray();
bmp = new WriteableBitmap(w * tileSize, h * tileSize, 96, 96, PixelFormats.Pbgra32, null);
destData = new int[bmp.PixelWidth * bmp.PixelHeight];
}
const int w = 64, h = 64, tileSize = 8;
public int seed = 72141;
private int oldSeed = -1;
private WriteableBitmap bmp;
int[] destData;
protected override void OnRender(DrawingContext dc)
{
if(seed != oldSeed)
{
oldSeed = seed;
int startX = 0, endX = w;
int startY = 0, endY = h;
Random rnd = new Random(seed);
for(int x = startX; x < endX; x++)
{
for(int y = startY; y < endY; y++)
{
var tile = _mapTiles[rnd.Next(_mapTiles.Length)];
var rect = new Int32Rect(x * tileSize, y * tileSize, tileSize, tileSize);
for(int sourceY = 0; sourceY < tileSize; sourceY++)
{
int destY = ((rect.Y + sourceY) * (w * tileSize)) + rect.X;
Array.Copy(tile, sourceY * tileSize, destData, destY, tileSize);
}
}
}
bmp.WritePixels(new Int32Rect(0, 0, w * tileSize, h * tileSize), destData, w * tileSize * 4, 0);
}
dc.DrawImage(bmp,new Rect(0,0,w*tileSize,h*tileSize));
}
}