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#