Zauważyłem, że w C# operacje na pikselach w bitmapie są dużo wolniejsze niż w Java. Zastosowałem znaleziony niegdyś sposób, który jest znacznie szybszy niż SetPixel i GetPixel, ale przez przypadek odkryłem, że to samo w Java jest znacznie szybsze.

W celach testowych napisałem identyczny program w C# i w Javie, który generuje dużą bitmapę, maluje na niej piksel po pikselu różne kolory i zapisuje do pliku. Test polega na pomiarze czasu pomalowania bitmapy, bez uwzględniania czasu tworzenia obiektu i zapisu do pliku.

Zauważyłem, że w C# potrzebne jest zablokowanie i odblokowanie bitów i sama ta czynność też zabiera trochę czasu.

W C# uzyskuję taki mniej więcej czas w milisekundach:
Po zablokowaniu: 360
Przed odblokowaniem: 630
Łącznie: 970
Czyli samo pomalowanie trwa ok. 270ms.

W Java na tym samym komputerze uzyskuję czas ok. 100 milisekund.

Załączam kod programu testowego w obu językach. Czy sposób operowania na pikselach w C# jest poprawny? Może powinienem to inaczej robić i wtedy uzyskam podobną wydajność tej czynności? Przecież w jednym i w drugim to tak naprawdę zapisywanie liczb do tablicy pikseli bitmapy, nic więcej.

Wersja w C#:

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;

namespace TestThr2
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            int BmpW = 30 * 256;
            int BmpH = 20 * 256;
            Bitmap TestBmp = new Bitmap(BmpW, BmpH, PixelFormat.Format24bppRgb);

            Stopwatch SW = new Stopwatch();
            SW.Start();
            Console.WriteLine("Start");

            GraphicsUnit GUP = GraphicsUnit.Pixel;
            BitmapData TestBmp__ = TestBmp.LockBits(Rectangle.Round(TestBmp.GetBounds(ref GUP)), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            Console.WriteLine("Po zablokowaniu: " + SW.ElapsedMilliseconds);
            unsafe
            {
                byte* TestBmp_ = (byte*)TestBmp__.Scan0;
                int TestBmp__Stride = TestBmp__.Stride;

                int Ptr = 0;

                for (int YY = 0; YY < 10; YY++)
                {
                    for (int XX = 0; XX < 10; XX++)
                    {
                        int PtrOffset = (TestBmp__Stride * 512 * YY) + (768 * 3 * XX);

                        Ptr = PtrOffset;
                        for (int Y = 0; Y < 256; Y++)
                        {
                            for (int X = 0; X < 256; X++)
                            {
                                TestBmp_[Ptr + 0] = (byte)(0);
                                TestBmp_[Ptr + 1] = (byte)(Y);
                                TestBmp_[Ptr + 2] = (byte)(X);
                                Ptr += 3;
                            }
                            Ptr = Ptr + TestBmp__Stride - (3 * 256);
                        }

                        Ptr = PtrOffset + (3 * 256);
                        for (int Y = 0; Y < 256; Y++)
                        {
                            for (int X = 0; X < 256; X++)
                            {
                                TestBmp_[Ptr + 0] = (byte)(Y);
                                TestBmp_[Ptr + 1] = (byte)(X);
                                TestBmp_[Ptr + 2] = (byte)(0);
                                Ptr += 3;
                            }
                            Ptr = Ptr + TestBmp__Stride - (3 * 256);
                        }

                        Ptr = PtrOffset + 3 * 512;
                        for (int Y = 0; Y < 256; Y++)
                        {
                            for (int X = 0; X < 256; X++)
                            {
                                TestBmp_[Ptr + 0] = (byte)(X);
                                TestBmp_[Ptr + 1] = (byte)(0);
                                TestBmp_[Ptr + 2] = (byte)(Y);
                                Ptr += 3;
                            }
                            Ptr = Ptr + TestBmp__Stride - (3 * 256);
                        }

                        Ptr = PtrOffset + (TestBmp__Stride * 256);
                        for (int Y = 0; Y < 256; Y++)
                        {
                            for (int X = 0; X < 256; X++)
                            {
                                TestBmp_[Ptr + 0] = (byte)(255);
                                TestBmp_[Ptr + 1] = (byte)(Y);
                                TestBmp_[Ptr + 2] = (byte)(X);
                                Ptr += 3;
                            }
                            Ptr = Ptr + TestBmp__Stride - (3 * 256);
                        }

                        Ptr = PtrOffset + (TestBmp__Stride * 256) + (3 * 256);
                        for (int Y = 0; Y < 256; Y++)
                        {
                            for (int X = 0; X < 256; X++)
                            {
                                TestBmp_[Ptr + 0] = (byte)(Y);
                                TestBmp_[Ptr + 1] = (byte)(X);
                                TestBmp_[Ptr + 2] = (byte)(255);
                                Ptr += 3;
                            }
                            Ptr = Ptr + TestBmp__Stride - (3 * 256);
                        }

                        Ptr = PtrOffset + (TestBmp__Stride * 256) + (3 * 512);
                        for (int Y = 0; Y < 256; Y++)
                        {
                            for (int X = 0; X < 256; X++)
                            {
                                TestBmp_[Ptr + 0] = (byte)(X);
                                TestBmp_[Ptr + 1] = (byte)(255);
                                TestBmp_[Ptr + 2] = (byte)(Y);
                                Ptr += 3;
                            }
                            Ptr = Ptr + TestBmp__Stride - (3 * 256);
                        }
                    }
                }
            }
            Console.WriteLine("Przed odblokowaniem: " + SW.ElapsedMilliseconds);
            TestBmp.UnlockBits(TestBmp__);

            Console.WriteLine("Stop");
            Console.WriteLine(SW.ElapsedMilliseconds);

            try
            {
                TestBmp.Save("/home/xxx/TestNet.png", ImageFormat.Png);
            }
            catch (Exception E)
            {
            }
        }
    }
}

Wersja w Java:

package testjava2;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.time.Duration;
import java.time.Instant;
import javax.imageio.ImageIO;

public class TestJava2
{
    public static byte IntToByte(int Val)
    {
        if (Val > 127)
        {
            Val = Val - 256;
        }
        return (byte)Val;
    }    
    
    public static void main(String[] args)
    {
        int BmpW = 30 * 256;
        int BmpH = 20 * 256;
        BufferedImage TestBmp = new BufferedImage(BmpW, BmpH, BufferedImage.TYPE_3BYTE_BGR);
        
        Instant SW = Instant.now();
        
        System.out.println("Start");
        
        byte[] TestBmp__ = ((DataBufferByte)TestBmp.getRaster().getDataBuffer()).getData();
        int TestBmp__Stride = BmpW * 3;
        int Ptr = 0;
        
        for (int YY = 0; YY < 10; YY++)
        {
            for (int XX = 0; XX < 10; XX++)
            {
                int PtrOffset = (TestBmp__Stride * 512 * YY) + (768 * 3 * XX);
                
                Ptr = PtrOffset;
                for (int Y = 0; Y < 256; Y++)
                {
                    for (int X = 0; X < 256; X++)
                    {
                        TestBmp__[Ptr + 0] = IntToByte(0);
                        TestBmp__[Ptr + 1] = IntToByte(Y);
                        TestBmp__[Ptr + 2] = IntToByte(X);
                        Ptr += 3;
                    }
                    Ptr = Ptr + TestBmp__Stride - (3 * 256);
                }

                Ptr = PtrOffset + (3 * 256);
                for (int Y = 0; Y < 256; Y++)
                {
                    for (int X = 0; X < 256; X++)
                    {
                        TestBmp__[Ptr + 0] = IntToByte(Y);
                        TestBmp__[Ptr + 1] = IntToByte(X);
                        TestBmp__[Ptr + 2] = IntToByte(0);
                        Ptr += 3;
                    }
                    Ptr = Ptr + TestBmp__Stride - (3 * 256);
                }

                Ptr = PtrOffset + (3 * 512);
                for (int Y = 0; Y < 256; Y++)
                {
                    for (int X = 0; X < 256; X++)
                    {
                        TestBmp__[Ptr + 0] = IntToByte(X);
                        TestBmp__[Ptr + 1] = IntToByte(0);
                        TestBmp__[Ptr + 2] = IntToByte(Y);
                        Ptr += 3;
                    }
                    Ptr = Ptr + TestBmp__Stride - (3 * 256);
                }
                
                Ptr = PtrOffset + (TestBmp__Stride * 256);
                for (int Y = 0; Y < 256; Y++)
                {
                    for (int X = 0; X < 256; X++)
                    {
                        TestBmp__[Ptr + 0] = IntToByte(255);
                        TestBmp__[Ptr + 1] = IntToByte(Y);
                        TestBmp__[Ptr + 2] = IntToByte(X);
                        Ptr += 3;
                    }
                    Ptr = Ptr + TestBmp__Stride - (3 * 256);
                }

                Ptr = PtrOffset + (TestBmp__Stride * 256) + (3 * 256);
                for (int Y = 0; Y < 256; Y++)
                {
                    for (int X = 0; X < 256; X++)
                    {
                        TestBmp__[Ptr + 0] = IntToByte(Y);
                        TestBmp__[Ptr + 1] = IntToByte(X);
                        TestBmp__[Ptr + 2] = IntToByte(255);
                        Ptr += 3;
                    }
                    Ptr = Ptr + TestBmp__Stride - (3 * 256);
                }

                Ptr = PtrOffset + (TestBmp__Stride * 256) + (3 * 512);
                for (int Y = 0; Y < 256; Y++)
                {
                    for (int X = 0; X < 256; X++)
                    {
                        TestBmp__[Ptr + 0] = IntToByte(X);
                        TestBmp__[Ptr + 1] = IntToByte(255);
                        TestBmp__[Ptr + 2] = IntToByte(Y);
                        Ptr += 3;
                    }
                    Ptr = Ptr + TestBmp__Stride - (3 * 256);
                }
                
            }
        }
        
        System.out.println("Stop ");
        System.out.println(Duration.between(SW, Instant.now()).toMillis());
        
        try
        {
            ImageIO.write(TestBmp, "png", new File("/home/xxx/TestJava.png"));
        }
        catch (Exception E)
        {
        }
    }
}

Sprawdziłem tez na Ideone (potrzebna była drobna modyfikacja kodu) i dla Java mam ok 140ms, dla C# mam odpowiednio czasy 470,570,1060 (po zablokowaniu, przed odblokowaniem, łącznie):
Java C#