Big mosaic image (C#)

・Combine multiple images to create a larger image

・It takes quite a while

・Can be created with Visual Studio


Source code

Created as a C# console application (.NET Framework)

Replace the namespace part with your project name.


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace (Change here)
{
    internal class Program
    {
        public class ColorData
        {
            public string path;
            public byte r;
            public byte g;
            public byte b;
            public double sL = 0;
            public double sa = 0;
            public double sb = 0;
            public int use = 0;
            public ColorData(string path,byte r, byte g, byte b)
            {
                this.path = path;
                this.r = r;
                this.g = g;
                this.b = b;
            }
            public ColorData(string path, double sL, double sa, double sb)
            {
                this.path = path;
                this.sL = sL;
                this.sa = sa;
                this.sb = sb;
            }
        }
        static List colors = new List();
        static double Lin(double x)
        {
            return (x < 0.04045) ? x / 12.92 : Math.Pow((x + 0.055) / 1.055, 2.4);
        }
        static double[] XYZ(double r,double g,double b)
        {
            r = Lin(r / 255.0);
            g = Lin(g / 255.0);
            b = Lin(b / 255.0);
            return new double[] {(0.4124 * r + 0.3576 * g + 0.1805 * b) / 0.9505,
                               0.2126 * r + 0.7152 * g + 0.0722 * b,
                               (0.0193 * r + 0.1192 * g + 0.9505 * b) / 1.089};
        }
        static double[] Lab(double x, double y, double z)
        {
            double[] rt = { 0, 0, 0 };
            rt[0] = Labf(y);
            rt[1] = 4.3103 * (Labf(x) - Labf(y));
            rt[2] = 1.7241 * (Labf(y) - Labf(z));
            return rt;
        }
        static double Labf(double x)
        {
            return (x > 0.008856) ? 116 * Math.Pow(x, 0.333) - 16 : 903.296 * x;
        }

        static void Main(string[] args)
        {
            //Insert the code below here
            Console.ReadLine();
        }
        static void MakeData(string name, string path, int mult_read_pixel)
        {
            DirectoryInfo di = new DirectoryInfo(path);
            string[] ext = { "*.jpg", "*.jpeg", "*.png", "*.JPG", "*.JPEG", "*.PNG" };
            foreach (string ex in ext)
            {
                FileInfo[] fiAlls = di.GetFiles(ex, SearchOption.AllDirectories);
                for (int i = 0; i < fiAlls.Length; i++)
                {
                    Bitmap bitmap2 = new Bitmap(fiAlls[i].FullName);
                    Bitmap bitmap = new Bitmap(bitmap2, new Size((int)(1.0 * bitmap2.Width / mult_read_pixel), (int)(1.0 * bitmap2.Height / mult_read_pixel)));
                    bitmap2.Dispose();
                    Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
                    BitmapData bmpdata = bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
                    try
                    {
                        byte[] imgdata = new byte[bmpdata.Stride * bitmap.Height];
                        System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, imgdata, 0, imgdata.Length);
                        double[] _rgb = { 0, 0, 0 };
                        for (int y = 0; y < bitmap.Height; ++y)
                        {
                            for (int x = 0; x < bitmap.Width; ++x)
                            {
                                int pos = y * bmpdata.Stride + x * 4;
                                _rgb[2] += 1.0 * imgdata[pos] / (bitmap.Height * bitmap.Width);
                                _rgb[1] += 1.0 * imgdata[pos + 1] / (bitmap.Height * bitmap.Width);
                                _rgb[0] += 1.0 * imgdata[pos + 2] / (bitmap.Height * bitmap.Width);
                            }
                        }
                        colors.Add(new ColorData(fiAlls[i].FullName, (byte)_rgb[0], (byte)_rgb[1], (byte)_rgb[2]));
                    }
                    finally
                    {
                        bitmap.UnlockBits(bmpdata);
                    }
                    bitmap.Dispose();
                    Console.WriteLine(ex + ": " + i + "/" + (fiAlls.Length - 1));
                }
            }
            FileStream fs = new FileStream(name + ".dat", FileMode.Create);
            BinaryWriter writer = new BinaryWriter(fs);
            for (int i = 0; i < colors.Count; ++i)
            {
                byte[] data = System.Text.Encoding.UTF8.GetBytes(colors[i].path);
                byte[] rgb = { colors[i].r, colors[i].g, colors[i].b };
                writer.Write((byte)data.Length);
                writer.Write(data);
                writer.Write(rgb);
            }
            writer.Close();
            fs.Close();
            colors.Clear();
            Console.WriteLine("Finish");
        }
        static void MakePicture(string name,string pic_path,string data_path,int mult_base_p,int mult_use_p)
        {
            int[] wh = { -1, -1 };
            int[] rwh = { -1, -1 };
            Bitmap canvas = new Bitmap(1,1);
            Graphics g = Graphics.FromImage(canvas);
            using (var fs = File.OpenRead(data_path + ".dat"))
            {
                byte[] bytes = new byte[fs.Length];
                fs.Read(bytes, 0, (int)fs.Length);
                int k = 0;
                while (k < fs.Length)
                {
                    byte[] pb = new byte[bytes[k]];
                    k++;
                    for(int j = 0; j < pb.Length; j++)
                    {
                        pb[j] = bytes[k + j];
                    }
                    string fn =  System.Text.Encoding.UTF8.GetString(pb);
                    k += pb.Length;
                    double[] xyz = XYZ(bytes[k], bytes[k + 1], bytes[k + 2]);
                    double[] clab = Lab(xyz[0], xyz[1], xyz[2]);
                    colors.Add(new ColorData(fn, clab[0], clab[1], clab[2]));
                    k += 3;
                }
            }
            Image img = Image.FromFile(pic_path);
            Bitmap imgbitmap = new Bitmap(img);
            Bitmap bitmap = new Bitmap(imgbitmap, new Size((int)(1.0 * img.Width / mult_base_p), (int)(1.0 * img.Height / mult_base_p)));
            Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
            BitmapData bmpdata = bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            try
            {
                byte[] imgdata = new byte[bmpdata.Stride * bitmap.Height];
                System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, imgdata, 0, imgdata.Length);
                for (int y = 0; y < bitmap.Height; ++y)
                {
                    for (int x = 0; x < bitmap.Width; ++x)
                    {
                        int pos = y * bmpdata.Stride + x * 4;
                        int[] select = { 0, 999 };
                        double min_v = 9999999;
                        for (int i = 0; i < colors.Count; i++)
                        {
                            double[] xyz = XYZ(imgdata[pos + 2], imgdata[pos + 1], imgdata[pos]);
                            double[] lab = Lab(xyz[0], xyz[1], xyz[2]);
                            double[] delta = { lab[0] - colors[i].sL, lab[1] - colors[i].sa, lab[2] - colors[i].sb };
                            double tmp = delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];
                            if(tmp < min_v && colors[i].use < select[1])
                            {
                                select[0] = i;
                                min_v = tmp;
                                select[1] = colors[i].use;
                            }
                        }
                        Bitmap srcImage2 = new Bitmap(colors[select[0]].path);
                        if (rwh[0] == -1)
                        {
                            rwh[0] = (int)(1.0 * srcImage2.Width / mult_use_p);
                            rwh[1] = (int)(1.0 * srcImage2.Height / mult_use_p);
                        }
                        Bitmap srcImage3 = new Bitmap(srcImage2, new Size(rwh[0], rwh[1]));
                        Rectangle urect = srcImage3.Width > srcImage3.Height ? new Rectangle((srcImage3.Width - srcImage3.Height) / 2, 0, srcImage3.Height, srcImage3.Height) : new Rectangle((srcImage3.Height - srcImage3.Width) / 2, 0, srcImage3.Width, srcImage3.Width);
                        Bitmap srcImage = srcImage3.Clone(urect, srcImage3.PixelFormat);
                        srcImage2.Dispose();
                        srcImage3.Dispose();
                        if (wh[0] == -1)
                        {
                            wh[0] = bitmap.Width * srcImage.Width;
                            wh[1] = bitmap.Height * srcImage.Height;
                            canvas = new Bitmap(wh[0], wh[1]);
                            g = Graphics.FromImage(canvas);
                        }
                        g.DrawImage(srcImage, new Point(srcImage.Width * x,srcImage.Height * y));
                        colors[select[0]].use++;
                        srcImage.Dispose();
                        
                    }
                    Console.WriteLine(y + "/" + (bitmap.Height - 1));
                }
            }
            finally
            {
                bitmap.UnlockBits(bmpdata);
            }
            canvas.Save(AppDomain.CurrentDomain.BaseDirectory + name + ".png", System.Drawing.Imaging.ImageFormat.Png);
            colors.Clear();
            g.Dispose();
            imgbitmap.Dispose();
            bitmap.Dispose();
            Console.WriteLine("Finish");
        }
    }
}
			

Step 1: Creating a database

Since it is a hassle to calculate the average color of each image used for combination, we create a binary database with the following structure:

(Path length [1 byte] Path (UTF-8) [n byte] RGB [1×3 byte])×n


Enter the following code in the "//Insert the code below here" section and run it:


MakeData("Database name", @"Folder path", Reduce ratio);
			

※The larger the reduction ratio, the faster the creation speed.

Example:


MakeData("data", @"D:\data\BACKUP\Pic", 32);			
			


Wait until Finish is displayed. If successful, the dat file will be created.


Step 2: Image generation

Calculate the distance between the database information and each pixel of the image you want to create on the L*a*b* color space, and paste the image with the smallest distance among the least frequently used images.

Conversion order: sRGB → Linear RGB → XYZ → L*a*b


Replace the code below with the code in step 1 and execute it.


MakePicture("File name (without extension)", @"Path of the image you want to create", "Database name (without extension)", Reduction ratio of the completed image, Reduction ratio of the image to be used);		
			

※We recommend that you use a reduction ratio of 4 or more for the completed image (an error may occur).


Example:


MakePicture("Name", @"D:\data\img\xxx.png", "data", 4, 16);
			


Wait until Finish is displayed, and if successful, the dat file will be created (this may take several minutes depending on the arguments).


Completed!

The file size is so big(about 100MB) that it's a screenshot.

Big mosaic image entirety

When enlarged...

Big mosaic image detail

I get an error!

Did you reduce the scale of the finished image too much in "Step 2: Image Generation"?
→If you try to create an image that is too large to be created with the Bitmap class, it will fail.

Have you deleted any files referenced in the database?
→Restore the file or recreate the database.