SkiaSharp bit eşlem piksel bitlerine erişme
SkiaSharp bit eşlemlerini dosyalara kaydetme makalesinde gördüğünüz gibi bit eşlemler genellikle dosyalarda JPEG veya PNG gibi sıkıştırılmış bir biçimde depolanır. Constrast'ta, bellekte depolanan bir SkiaSharp bit eşlemi sıkıştırılmaz. Sıralı bir piksel serisi olarak depolanır. Bu sıkıştırılmamış biçim bit eşlemlerin bir görüntü yüzeyine aktarılmasını kolaylaştırır.
Bir SkiaSharp bit eşleminin kapladığı bellek bloğu çok basit bir şekilde düzenlenir: Soldan sağa ilk piksel satırıyla başlar ve ikinci satırla devam eder. Tam renkli bit eşlemler için her piksel dört bayttır; bu da bit eşlem için gereken toplam bellek alanının genişliğinin ve yüksekliğinin dört katı olduğu anlamına gelir.
Bu makalede, bir uygulamanın bit eşlemin piksel bellek bloğuna doğrudan erişerek veya dolaylı olarak bu piksellere nasıl erişebileceği açıklanmaktadır. Bazı durumlarda, bir program görüntünün piksellerini analiz etmek ve bir tür histogram oluşturmak isteyebilir. Daha yaygın olarak uygulamalar, bit eşlemi oluşturan pikselleri algoritmalı olarak oluşturarak benzersiz görüntüler oluşturabilir:
Teknikler
SkiaSharp, bit eşlem piksel bitlerine erişmek için çeşitli teknikler sağlar. Hangisini seçtiğiniz genellikle kodlama kolaylığı (bakım ve hata ayıklama kolaylığı ile ilgilidir) ile performans arasında bir uzlaşmadır. Çoğu durumda bit eşlem piksellerine erişmek için aşağıdaki yöntemlerden ve özelliklerinden SKBitmap
birini kullanacaksınız:
GetPixel
veSetPixel
yöntemleri, tek bir pikselin rengini almanıza veya ayarlamanıza olanak sağlar.- özelliği tüm
Pixels
bit eşlem için bir piksel renkleri dizisi alır veya renk dizisini ayarlar. GetPixels
bit eşlem tarafından kullanılan piksel belleğinin adresini döndürür.SetPixels
bit eşlem tarafından kullanılan piksel belleğinin adresini değiştirir.
İlk iki tekniği "yüksek düzey" ve ikinci ikisini de "düşük düzey" olarak düşünebilirsiniz. Kullanabileceğiniz başka yöntemler ve özellikler de vardır, ancak bunlar en değerli olanlardır.
Bu teknikler arasındaki performans farklarını görmenize olanak sağlamak için örnek uygulama, gradyan oluşturmak için kırmızı ve mavi tonlarını birleştiren piksellerle bit eşlem oluşturan Gradyan Bit Eşlem adlı bir sayfa içerir. Program, bit eşlem piksellerini ayarlamak için farklı teknikler kullanan bu bit eşlemin sekiz farklı kopyasını oluşturur. Bu sekiz bit eşlemin her biri, tekniğin kısa bir metin açıklamasını ayarlayan ve tüm pikselleri ayarlamak için gereken süreyi hesaplayan ayrı bir yöntemde oluşturulur. Her yöntem, performansın daha iyi bir tahminini elde etmek için piksel ayarlama mantığını 100 kez döngüye alır.
SetPixel yöntemi
Yalnızca birkaç piksel SetPixel
ayarlamanız veya almanız gerekiyorsa ve GetPixel
yöntemleri idealdir. Bu iki yöntemin her biri için tamsayı sütununu ve satırını belirtirsiniz. Piksel biçiminden bağımsız olarak, bu iki yöntem pikseli bir değer olarak SKColor
almanıza veya ayarlamanıza olanak sağlar:
bitmap.SetPixel(col, row, color);
SKColor color = bitmap.GetPixel(col, row);
col
Bağımsız değişken bit eşlem özelliğinden 0 ile bir küçük Width
arasında ve row
0 ile özelliğinden bir küçük Height
arasında olmalıdır.
Gradyan Bit Eşlem'de yöntemini kullanarak bit eşlem içeriğini ayarlayan yöntem aşağıdadırSetPixel
. Bit eşlem 256 x 256 pikseldir ve for
döngüler değer aralığıyla sabit kodlanır:
public class GradientBitmapPage : ContentPage
{
const int REPS = 100;
Stopwatch stopwatch = new Stopwatch();
···
SKBitmap FillBitmapSetPixel(out string description, out int milliseconds)
{
description = "SetPixel";
SKBitmap bitmap = new SKBitmap(256, 256);
stopwatch.Restart();
for (int rep = 0; rep < REPS; rep++)
for (int row = 0; row < 256; row++)
for (int col = 0; col < 256; col++)
{
bitmap.SetPixel(col, row, new SKColor((byte)col, 0, (byte)row));
}
milliseconds = (int)stopwatch.ElapsedMilliseconds;
return bitmap;
}
···
}
Her piksel için renk kümesi bit eşlem sütununa eşit kırmızı bir bileşene ve satıra eşit mavi bir bileşene sahiptir. Sonuçta elde edilen bit eşlem sol üstte siyah, sağ üstte kırmızı, sol altta mavi ve diğer yerlerde gradyanlarla birlikte sağ altta eflatundur.
SetPixel
yöntemi 65.536 kez çağrılır ve bu yöntemin ne kadar verimli olabileceğinden bağımsız olarak, bir alternatif varsa bu kadar çok API çağrısı yapmak genellikle iyi bir fikir değildir. Neyse ki, birkaç alternatif vardır.
Piksel özelliği
SKBitmap
Pixels
tüm bit eşlem için bir değer dizisi SKColor
döndüren bir özellik tanımlar. Bit eşlem için renk değerleri dizisi ayarlamak için de kullanabilirsiniz Pixels
:
SKColor[] pixels = bitmap.Pixels;
bitmap.Pixels = pixels;
Pikseller dizide ilk satırdan başlayarak soldan sağa, ikinci satırdan başlayarak vb. düzenlenir. Dizideki toplam renk sayısı, bit eşlem genişliği ve yüksekliğinin çarpımlarına eşittir.
Bu özellik verimli gibi görünse de piksellerin bit eşlemden diziye, diziden bit eşlem içine kopyalandığını ve piksellerin de değerlerden ve değerlerine SKColor
dönüştürüldüğünü unutmayın.
Burada, özelliğini kullanarak bit eşlemi ayarlayan sınıfındaki yöntemi GradientBitmapPage
gösterilmiştir Pixels
. yöntemi gerekli boyutta bir SKColor
dizi ayırır, ancak bu diziyi Pixels
oluşturmak için özelliğini kullanmış olabilir:
SKBitmap FillBitmapPixelsProp(out string description, out int milliseconds)
{
description = "Pixels property";
SKBitmap bitmap = new SKBitmap(256, 256);
stopwatch.Restart();
SKColor[] pixels = new SKColor[256 * 256];
for (int rep = 0; rep < REPS; rep++)
for (int row = 0; row < 256; row++)
for (int col = 0; col < 256; col++)
{
pixels[256 * row + col] = new SKColor((byte)col, 0, (byte)row);
}
bitmap.Pixels = pixels;
milliseconds = (int)stopwatch.ElapsedMilliseconds;
return bitmap;
}
Dizinin dizininin pixels
ve col
değişkenlerinden row
hesaplanması gerektiğini unutmayın. Satır, her satırdaki piksel sayısıyla (bu örnekte 256) çarpılır ve sütun eklenir.
SKBitmap
tüm bit eşlem için bayt dizisi döndüren benzer Bytes
bir özellik de tanımlar, ancak tam renkli bit eşlemler için daha hantaldır.
GetPixels işaretçisi
Bit eşlem piksellerine erişmek için en güçlü teknik, yöntemi veya Pixels
özelliğiyle GetPixel
karıştırılmamasıdırGetPixels
. C# programlamasında çok yaygın olmayan bir şey döndürmesiyle ilgili bir farkı GetPixels
hemen fark edeceksiniz:
IntPtr pixelsAddr = bitmap.GetPixels();
.NET IntPtr
türü bir işaretçiyi temsil eder. Genellikle 32 bit veya 64 bit uzunluğunda olmak üzere programın çalıştırıldığı makinenin yerel işlemcisindeki bir tamsayı uzunluğu olduğundan çağrılır IntPtr
. IntPtr
DöndürenGetPixels
, bit eşlem nesnesinin piksellerini depolamak için kullandığı gerçek bellek bloğunun adresidir.
yöntemini kullanarak öğesini IntPtr
bir C# işaretçi türüne ToPointer
dönüştürebilirsiniz. C# işaretçisi söz dizimi C ve C++ile aynıdır:
byte* ptr = (byte*)pixelsAddr.ToPointer();
Değişken ptr
bayt işaretçisi türündedir. Bu ptr
değişken bit eşlem piksellerini depolamak için kullanılan tek tek bellek baytlarına erişmenizi sağlar. Bu bellekten bayt okumak veya belleğe bayt yazmak için aşağıdaki gibi bir kod kullanırsınız:
byte pixelComponent = *ptr;
*ptr = pixelComponent;
Bu bağlamda yıldız işareti C# dolaylı işlecidir ve tarafından ptr
işaret edilen belleğin içeriğine başvurmak için kullanılır. Başlangıçta, ptr
bit eşlemin ilk satırının ilk pikselinin ilk baytını işaret eder, ancak bit eşlem içindeki diğer konumlara taşımak için değişken üzerinde ptr
aritmetik gerçekleştirebilirsiniz.
Bir dezavantajı, bu ptr
değişkeni yalnızca anahtar sözcüğüyle işaretlenmiş bir kod bloğunda unsafe
kullanabilmenizdir. Ayrıca, derleme güvenli olmayan bloklara izin verecek şekilde işaretlenmelidir. Bu, projenin özelliklerinde yapılır.
C# dilinde işaretçileri kullanmak çok güçlüdür, ancak aynı zamanda çok tehlikelidir. İşaretçinin başvurması gereken bellek dışında belleğe erişmemeye dikkat etmeniz gerekir. bu nedenle işaretçi kullanımı "güvenli değil" sözcüğüyle ilişkilendirilir.
Sınıfındaki GradientBitmapPage
yöntemini kullanan yöntem aşağıdadır GetPixels
. Bayt işaretçisini unsafe
kullanarak tüm kodu kapsayan bloğuna dikkat edin:
SKBitmap FillBitmapBytePtr(out string description, out int milliseconds)
{
description = "GetPixels byte ptr";
SKBitmap bitmap = new SKBitmap(256, 256);
stopwatch.Restart();
IntPtr pixelsAddr = bitmap.GetPixels();
unsafe
{
for (int rep = 0; rep < REPS; rep++)
{
byte* ptr = (byte*)pixelsAddr.ToPointer();
for (int row = 0; row < 256; row++)
for (int col = 0; col < 256; col++)
{
*ptr++ = (byte)(col); // red
*ptr++ = 0; // green
*ptr++ = (byte)(row); // blue
*ptr++ = 0xFF; // alpha
}
}
}
milliseconds = (int)stopwatch.ElapsedMilliseconds;
return bitmap;
}
ptr
değişkeni yönteminden ToPointer
ilk kez elde edildiğinde, bit eşlemin ilk satırının en soldaki pikselinin ilk baytını gösterir. ve döngülerirow
, her pikselin ++
her baytı ayarlandıktan sonra işleçle artırılacak şekilde ayarlanırptr
.col
for
Diğer 99 piksel döngüleri için bit ptr
eşlem başlangıcına geri ayarlanmalıdır.
Her piksel dört bayt bellek olduğundan her bayt ayrı olarak ayarlanmalıdır. Buradaki kod, baytların renk türüyle tutarlı olan kırmızı, yeşil, mavi ve alfa sıralarında SKColorType.Rgba8888
olduğunu varsayar. Bunun iOS ve Android için varsayılan renk türü olduğunu ancak Evrensel Windows Platformu için olmadığını hatırlayabilirsiniz. Varsayılan olarak, UWP renk türüyle SKColorType.Bgra8888
bit eşlemler oluşturur. Bu nedenle, bu platformda bazı farklı sonuçlar görmeyi bekleyebilirsiniz!
döndürülen değeri ToPointer
işaretçi yerine byte
bir uint
işaretçiye vermek mümkündür. Bu, bir pikselin tamamına tek bir deyimde erişilmesine olanak tanır. ++
İşleci bu işaretçiye uygulamak, sonraki pikseli işaret etmek için bunu dört bayt artırır:
public class GradientBitmapPage : ContentPage
{
···
SKBitmap FillBitmapUintPtr(out string description, out int milliseconds)
{
description = "GetPixels uint ptr";
SKBitmap bitmap = new SKBitmap(256, 256);
stopwatch.Restart();
IntPtr pixelsAddr = bitmap.GetPixels();
unsafe
{
for (int rep = 0; rep < REPS; rep++)
{
uint* ptr = (uint*)pixelsAddr.ToPointer();
for (int row = 0; row < 256; row++)
for (int col = 0; col < 256; col++)
{
*ptr++ = MakePixel((byte)col, 0, (byte)row, 0xFF);
}
}
}
milliseconds = (int)stopwatch.ElapsedMilliseconds;
return bitmap;
}
···
[MethodImpl(MethodImplOptions.AggressiveInlining)]
uint MakePixel(byte red, byte green, byte blue, byte alpha) =>
(uint)((alpha << 24) | (blue << 16) | (green << 8) | red);
···
}
Piksel, kırmızı, yeşil, mavi ve alfa bileşenlerinden bir tamsayı pikseli oluşturan yöntemi kullanılarak MakePixel
ayarlanır. Biçimin SKColorType.Rgba8888
aşağıdaki gibi bir piksel bayt sıralamasına sahip olduğunu unutmayın:
RR GG BB AA
Ancak bu baytlara karşılık gelen tamsayı:
AABBGGRR
Tamsayının en az önemli baytı, little-endian mimarisine uygun olarak ilk olarak depolanır. Bu MakePixel
yöntem, renk türüne Bgra8888
sahip bit eşlemler için düzgün çalışmaz.
MakePixel
yöntemine, derleyiciyi MethodImplOptions.AggressiveInlining
bunu ayrı bir yöntem yapmaktan kaçınmaya teşvik etme, bunun yerine yönteminin çağrıldığı kodu derleme seçeneğiyle bayrak eklenir. Bu, performansı geliştirmelidir.
İlginç bir şekilde, SKColor
yapı öğesinden SKColor
işaretsiz bir tamsayıya açık bir dönüştürme tanımlar; bu da bir SKColor
değerin oluşturulabileceği ve yerine kullanılacak MakePixel
bir dönüştürmenin uint
kullanılabildiği anlamına gelir:
SKBitmap FillBitmapUintPtrColor(out string description, out int milliseconds)
{
description = "GetPixels SKColor";
SKBitmap bitmap = new SKBitmap(256, 256);
stopwatch.Restart();
IntPtr pixelsAddr = bitmap.GetPixels();
unsafe
{
for (int rep = 0; rep < REPS; rep++)
{
uint* ptr = (uint*)pixelsAddr.ToPointer();
for (int row = 0; row < 256; row++)
for (int col = 0; col < 256; col++)
{
*ptr++ = (uint)new SKColor((byte)col, 0, (byte)row);
}
}
}
milliseconds = (int)stopwatch.ElapsedMilliseconds;
return bitmap;
}
Tek soru şudur: Değerin SKColor
tamsayı biçimi renk türü veya renk türü sırasına SKColorType.Rgba8888
SKColorType.Bgra8888
göre mi yoksa tamamen başka bir şey mi? Bu sorunun cevabı kısa süre sonra ortaya çıkar.
SetPixels Yöntemi
SKBitmap
ayrıca, aşağıdaki gibi çağırdığınız adlı SetPixels
bir yöntemi tanımlar:
bitmap.SetPixels(intPtr);
GetPixels
Bit eşlem tarafından piksellerini depolamak için kullanılan bellek bloğuna başvuran bir IntPtr
alan olduğunu hatırlayın. Çağrısı, SetPixels
bu bellek bloğunu, belirtilen tarafından bağımsız değişken olarak başvuruda bulunan IntPtr
bellek bloğuyla SetPixels
değiştirir. Bit eşlem daha önce kullandığı bellek bloğunu boşaltıyor. Bir sonraki çağrıda GetPixels
ile SetPixels
ayarlanan bellek bloğunu alır.
İlk başta, daha az kullanışlı olduğunuzdan GetPixels
daha fazla güç ve performans vermiyor gibi SetPixels
görünüyor. Bit GetPixels
eşlem bellek bloğunu elde ederek buna erişin. Biraz SetPixels
bellek ayırıp erişerek bunu bit eşlem bellek bloğu olarak ayarlayın.
Ancak kullanmak SetPixels
benzersiz bir bozulma avantajı sunar: Bit eşlem piksel bitlerine bir dizi kullanarak erişmenizi sağlar. Bu tekniği gösteren yöntemi GradientBitmapPage
aşağıda bulabilirsiniz. yöntemi ilk olarak bit eşlem piksellerinin baytlarına karşılık gelen çok boyutlu bir bayt dizisi tanımlar. İlk boyut satırdır, ikinci boyut sütundur ve üçüncü boyut her pikselin dört bileşeniyle ilişkilendirilir:
SKBitmap FillBitmapByteBuffer(out string description, out int milliseconds)
{
description = "SetPixels byte buffer";
SKBitmap bitmap = new SKBitmap(256, 256);
stopwatch.Restart();
byte[,,] buffer = new byte[256, 256, 4];
for (int rep = 0; rep < REPS; rep++)
for (int row = 0; row < 256; row++)
for (int col = 0; col < 256; col++)
{
buffer[row, col, 0] = (byte)col; // red
buffer[row, col, 1] = 0; // green
buffer[row, col, 2] = (byte)row; // blue
buffer[row, col, 3] = 0xFF; // alpha
}
unsafe
{
fixed (byte* ptr = buffer)
{
bitmap.SetPixels((IntPtr)ptr);
}
}
milliseconds = (int)stopwatch.ElapsedMilliseconds;
return bitmap;
}
Ardından, dizi piksellerle doldurulduktan sonra, bu diziyi işaret eden bir unsafe
fixed
bayt işaretçisi elde etmek için bir blok ve bir deyim kullanılır. Bu bayt işaretçisi daha sonra öğesine geçirmek SetPixels
için öğesine IntPtr
yayınlanabilir.
Oluşturduğunuz dizinin bayt dizisi olması gerekmez. Satır ve sütun için yalnızca iki boyutu olan bir tamsayı dizisi olabilir:
SKBitmap FillBitmapUintBuffer(out string description, out int milliseconds)
{
description = "SetPixels uint buffer";
SKBitmap bitmap = new SKBitmap(256, 256);
stopwatch.Restart();
uint[,] buffer = new uint[256, 256];
for (int rep = 0; rep < REPS; rep++)
for (int row = 0; row < 256; row++)
for (int col = 0; col < 256; col++)
{
buffer[row, col] = MakePixel((byte)col, 0, (byte)row, 0xFF);
}
unsafe
{
fixed (uint* ptr = buffer)
{
bitmap.SetPixels((IntPtr)ptr);
}
}
milliseconds = (int)stopwatch.ElapsedMilliseconds;
return bitmap;
}
MakePixel
yöntemi, renk bileşenlerini 32 bit pikselde birleştirmek için yeniden kullanılır.
Tamlık için aynı kod şu şekildedir; ancak işaretsiz bir SKColor
tamsayıya değer atanmalıdır:
SKBitmap FillBitmapUintBufferColor(out string description, out int milliseconds)
{
description = "SetPixels SKColor";
SKBitmap bitmap = new SKBitmap(256, 256);
stopwatch.Restart();
uint[,] buffer = new uint[256, 256];
for (int rep = 0; rep < REPS; rep++)
for (int row = 0; row < 256; row++)
for (int col = 0; col < 256; col++)
{
buffer[row, col] = (uint)new SKColor((byte)col, 0, (byte)row);
}
unsafe
{
fixed (uint* ptr = buffer)
{
bitmap.SetPixels((IntPtr)ptr);
}
}
milliseconds = (int)stopwatch.ElapsedMilliseconds;
return bitmap;
}
Teknikleri karşılaştırma
Gradyan Rengi sayfasının oluşturucusunun yukarıda gösterilen sekiz yöntemin tümünü çağırması ve sonuçları kaydetmesi:
public class GradientBitmapPage : ContentPage
{
···
string[] descriptions = new string[8];
SKBitmap[] bitmaps = new SKBitmap[8];
int[] elapsedTimes = new int[8];
SKCanvasView canvasView;
public GradientBitmapPage ()
{
Title = "Gradient Bitmap";
bitmaps[0] = FillBitmapSetPixel(out descriptions[0], out elapsedTimes[0]);
bitmaps[1] = FillBitmapPixelsProp(out descriptions[1], out elapsedTimes[1]);
bitmaps[2] = FillBitmapBytePtr(out descriptions[2], out elapsedTimes[2]);
bitmaps[4] = FillBitmapUintPtr(out descriptions[4], out elapsedTimes[4]);
bitmaps[6] = FillBitmapUintPtrColor(out descriptions[6], out elapsedTimes[6]);
bitmaps[3] = FillBitmapByteBuffer(out descriptions[3], out elapsedTimes[3]);
bitmaps[5] = FillBitmapUintBuffer(out descriptions[5], out elapsedTimes[5]);
bitmaps[7] = FillBitmapUintBufferColor(out descriptions[7], out elapsedTimes[7]);
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
···
}
Oluşturucu, sonuçta elde edilen bit eşlemleri görüntülemek için bir SKCanvasView
oluşturarak sonuçlanır. İşleyici, PaintSurface
yüzeyini sekiz dikdörtgene böler ve her birini görüntülemek için çağırır Display
:
public class GradientBitmapPage : ContentPage
{
···
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
int width = info.Width;
int height = info.Height;
canvas.Clear();
Display(canvas, 0, new SKRect(0, 0, width / 2, height / 4));
Display(canvas, 1, new SKRect(width / 2, 0, width, height / 4));
Display(canvas, 2, new SKRect(0, height / 4, width / 2, 2 * height / 4));
Display(canvas, 3, new SKRect(width / 2, height / 4, width, 2 * height / 4));
Display(canvas, 4, new SKRect(0, 2 * height / 4, width / 2, 3 * height / 4));
Display(canvas, 5, new SKRect(width / 2, 2 * height / 4, width, 3 * height / 4));
Display(canvas, 6, new SKRect(0, 3 * height / 4, width / 2, height));
Display(canvas, 7, new SKRect(width / 2, 3 * height / 4, width, height));
}
void Display(SKCanvas canvas, int index, SKRect rect)
{
string text = String.Format("{0}: {1:F1} msec", descriptions[index],
(double)elapsedTimes[index] / REPS);
SKRect bounds = new SKRect();
using (SKPaint textPaint = new SKPaint())
{
textPaint.TextSize = (float)(12 * canvasView.CanvasSize.Width / canvasView.Width);
textPaint.TextAlign = SKTextAlign.Center;
textPaint.MeasureText("Tly", ref bounds);
canvas.DrawText(text, new SKPoint(rect.MidX, rect.Bottom - bounds.Bottom), textPaint);
rect.Bottom -= bounds.Height;
canvas.DrawBitmap(bitmaps[index], rect, BitmapStretch.Uniform);
}
}
}
Derleyicinin kodu iyileştirmesine izin vermek için bu sayfa Yayın modunda çalıştırıldı. MacBook Pro, Nexus 5 Android telefon ve Windows 10 çalıştıran Surface Pro 3'te i Telefon 8 simülatöründe çalışan sayfa aşağıdadır. Donanım farklılıkları nedeniyle, cihazlar arasındaki performans sürelerini karşılaştırmaktan kaçının, ancak bunun yerine her cihazdaki göreli sürelere bakın:
Yürütme sürelerini milisaniye cinsinden bir araya getiren bir tablo aşağıdadır:
API | Veri türü | iOS | Android | UWP |
---|---|---|---|---|
SetPixel | 3.17 | 10.77 | 3.49 | |
Piksel | 0.32 | 1.23 | 0.07 | |
GetPixels | byte | 0.09 | 0.24 | 0.10 |
uint | 0.06 | 0,26 | 0.05 | |
SKColor | 0,29 | 0.99 | 0.07 | |
SetPixels | byte | 1,33 | 6.78 | 0,11 |
uint | 0,14 | 0.69 | 0.06 | |
SKColor | 0,35 | 1.93 | 0.10 |
Beklendiği gibi 65.536 kez çağrılması SetPixel
, bit eşlem piksellerini ayarlamanın en düşük verimli yoludur. Bir SKColor
diziyi Pixels
doldurmak ve özelliği ayarlamak çok daha iyidir ve hatta bazı GetPixels
ve SetPixels
teknikleriyle karşılaştırır. Piksel değerleriyle uint
çalışmak genellikle ayrı byte
bileşenler ayarlamaktan daha hızlıdır ve değeri işaretsiz bir tamsayıya dönüştürmek SKColor
işleme biraz ek yük getirir.
Çeşitli gradyanları karşılaştırmak da ilginçtir: Her platformun en üst satırları aynıdır ve gradyan amaçlandığı gibi gösterilir. Bu, yöntemin SetPixel
ve özelliğin Pixels
temel piksel biçiminden bağımsız olarak renklerden pikselleri doğru şekilde oluşturduğu anlamına gelir.
iOS ve Android ekran görüntülerinin sonraki iki satırı da aynıdır ve bu da küçük MakePixel
yöntemin bu platformlar için varsayılan Rgba8888
piksel biçimi için doğru tanımlandığını onaylar.
iOS ve Android ekran görüntülerinin alt satırı geriye doğrudur ve bu, bir SKColor
değer atanarak elde edilen imzasız tamsayının formda olduğunu gösterir:
AARRGGBB
Baytlar şu sıradadır:
BB GG RR AA
Bu, Bgra8888
sıralama yerine sıralamadır Rgba8888
. Biçim Brga8888
, Evrensel Windows platformu için varsayılan değerdir; bu nedenle bu ekran görüntüsünün son satırındaki gradyanlar ilk satırla aynıdır. Ancak bu bit eşlemleri oluşturan kod bir Rgba8888
sıralama varsaydığından ortadaki iki satır yanlıştır.
Her platformda piksel bitlerine erişmek için aynı kodu kullanmak istiyorsanız, veya Bgra8888
biçimini kullanarak Rgba8888
açıkça bir SKBitmap
oluşturabilirsiniz. Değerleri bit eşlem piksellerine değiştirmek SKColor
istiyorsanız kullanın Bgra8888
.
Piksellerin rastgele erişimi
FillBitmapBytePtr
Gradyan Bit Eşlem sayfasındaki ve FillBitmapUintPtr
yöntemleri, bit eşlemi sıralı olarak, üst satırdan alt satıra ve her satırda soldan sağa doldurmak için tasarlanmış döngülerden yararlandıfor
. Piksel, işaretçiyi artıran aynı deyimle ayarlanabilir.
Bazen piksellere sırayla değil rastgele erişmek gerekir. Yaklaşımı kullanıyorsanız GetPixels
, işaretçileri satır ve sütuna göre hesaplamanız gerekir. Bu, gökkuşağını sinüs eğrisinin bir döngüsü biçiminde gösteren bir bit eşlem oluşturan Gökkuşağı Sinüsü sayfasında gösterilmiştir.
Gökkuşağının renkleri, HSL (ton, doygunluk, parlaklık) renk modeli kullanılarak en kolay şekilde oluşturulur. yöntemi, SKColor.FromHsl
0 ile 360 arasında (bir dairenin açıları gibi, kırmızıdan yeşile ve maviye ve yeniden kırmızıya) ve 0 ile 100 arasında doygunluk ve parlaklık değerleri kullanarak bir SKColor
değer oluşturur. Gökkuşağının renkleri için doygunluk en fazla 100'e, parlaklık ise 50 orta noktasına ayarlanmalıdır.
Gökkuşağı Sinüsü , bit eşlem satırlarında döngü yaparak ve ardından 360 ton değeri arasında döngü yaparak bu görüntüyü oluşturur. Her ton değerinden, sinüs değerini de temel alan bir bit eşlem sütununu hesaplar:
public class RainbowSinePage : ContentPage
{
SKBitmap bitmap;
public RainbowSinePage()
{
Title = "Rainbow Sine";
bitmap = new SKBitmap(360 * 3, 1024, SKColorType.Bgra8888, SKAlphaType.Unpremul);
unsafe
{
// Pointer to first pixel of bitmap
uint* basePtr = (uint*)bitmap.GetPixels().ToPointer();
// Loop through the rows
for (int row = 0; row < bitmap.Height; row++)
{
// Calculate the sine curve angle and the sine value
double angle = 2 * Math.PI * row / bitmap.Height;
double sine = Math.Sin(angle);
// Loop through the hues
for (int hue = 0; hue < 360; hue++)
{
// Calculate the column
int col = (int)(360 + 360 * sine + hue);
// Calculate the address
uint* ptr = basePtr + bitmap.Width * row + col;
// Store the color value
*ptr = (uint)SKColor.FromHsl(hue, 100, 50);
}
}
}
// Create the SKCanvasView
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(bitmap, info.Rect);
}
}
Oluşturucunun bit eşlemi biçimine göre oluşturduğuna SKColorType.Bgra8888
dikkat edin:
bitmap = new SKBitmap(360 * 3, 1024, SKColorType.Bgra8888, SKAlphaType.Unpremul);
Bu, programın endişelenmeden değerleri piksellere uint
dönüştürmesini SKColor
sağlar. Bu programda bir rol oynamasa da, pikselleri ayarlamak için dönüştürmeyi SKColor
her kullandığınızda, renk bileşenlerini alfa değerine göre önceden yüklemediğinden de belirtmeniz SKColor
SKAlphaType.Unpremul
gerekir.
Oluşturucu daha sonra yöntemini kullanarak GetPixels
bit eşlemin ilk pikseline bir işaretçi alır:
uint* basePtr = (uint*)bitmap.GetPixels().ToPointer();
Belirli bir satır ve sütun için, öğesine basePtr
bir uzaklık değeri eklenmelidir. Bu uzaklık, satırın bit eşlem genişliğinin yanı sıra sütunun çarpı olmasıdır:
uint* ptr = basePtr + bitmap.Width * row + col;
Bu SKColor
işaretçi kullanılarak değer bellekte depolanır:
*ptr = (uint)SKColor.FromHsl(hue, 100, 50);
PaintSurface
işleyicisinde SKCanvasView
bit eşlem, görüntü alanını dolduracak şekilde genişletilir:
Bir bit eşlemden diğerine
Çok sayıda görüntü işleme görevi, pikselleri bir bit eşlemden diğerine aktarılırken değiştirmeyi içerir. Bu teknik, Renk Ayarlaması sayfasında gösterilmiştir. Sayfa bit eşlem kaynaklarından birini yükler ve ardından üç Slider
görünüm kullanarak görüntüyü değiştirmenize olanak tanır:
Her piksel rengi için ilk Slider
değer tona 0 ile 360 arasında bir değer ekler, ancak ardından sonucu 0 ile 360 arasında tutmak için modül işlecini kullanarak renkleri spektrum boyunca etkili bir şekilde kaydırır (UWP ekran görüntüsünde gösterildiği gibi). İkincisi Slider
, doygunluğa uygulamak için 0,5 ile 2 arasında bir çarpımsal faktör seçmenize olanak tanır ve üçüncüsü Slider
, Android ekran görüntüsünde gösterildiği gibi parlaklık için aynı işlemi yapar.
Program, adlı özgün kaynak bit eşlem ve adlı srcBitmap
ayarlanmış hedef bit eşlem olan dstBitmap
iki bit eşlem tutar. bir Slider
her taşındığında, program içindeki dstBitmap
tüm yeni pikselleri hesaplar. Elbette, kullanıcılar görünümleri çok hızlı bir şekilde taşıyarak Slider
denemeler yapacaktır, böylece yönetebileceğiniz en iyi performansı istiyorsunuz. Bu, hem kaynak hem de hedef bit eşlemler için yöntemini içerir GetPixels
.
Renk Ayarlama sayfası, kaynak ve hedef bit eşlemlerin renk biçimini denetlemez. Bunun yerine ve biçimleri için SKColorType.Rgba8888
SKColorType.Bgra8888
biraz farklı mantık içerir. Kaynak ve hedef farklı biçimlerde olabilir ve program çalışmaya devam eder.
Kaynağın piksellerini hedefe aktaran kritik TransferPixels
yöntem dışındaki program aşağıdadır. Oluşturucu, değerine srcBitmap
eşit ayarlardstBitmap
. İşleyici PaintSurface
görüntüler dstBitmap
:
public partial class ColorAdjustmentPage : ContentPage
{
SKBitmap srcBitmap =
BitmapExtensions.LoadBitmapResource(typeof(FillRectanglePage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
SKBitmap dstBitmap;
public ColorAdjustmentPage()
{
InitializeComponent();
dstBitmap = new SKBitmap(srcBitmap.Width, srcBitmap.Height);
OnSliderValueChanged(null, null);
}
void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
float hueAdjust = (float)hueSlider.Value;
hueLabel.Text = $"Hue Adjustment: {hueAdjust:F0}";
float saturationAdjust = (float)Math.Pow(2, saturationSlider.Value);
saturationLabel.Text = $"Saturation Adjustment: {saturationAdjust:F2}";
float luminosityAdjust = (float)Math.Pow(2, luminositySlider.Value);
luminosityLabel.Text = $"Luminosity Adjustment: {luminosityAdjust:F2}";
TransferPixels(hueAdjust, saturationAdjust, luminosityAdjust);
canvasView.InvalidateSurface();
}
···
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(dstBitmap, info.Rect, BitmapStretch.Uniform);
}
}
Görünümlerin ValueChanged
Slider
işleyicisi ayarlama değerlerini hesaplar ve çağırır TransferPixels
.
Yöntemin tamamı TransferPixels
olarak unsafe
işaretlenir. Her iki bit eşlemlerin piksel bitlerine bayt işaretçileri alarak başlar ve ardından tüm satırlar ve sütunlar arasında döngüler oluşturur. Yöntem, kaynak bit eşleminden her piksel için dört bayt alır. Bunlar veya Bgra8888
sırasına göre Rgba8888
olabilir. Renk türünü denetlemek, bir SKColor
değerin oluşturulmasına olanak tanır. HSL bileşenleri daha sonra ayıklanır, ayarlanır ve değeri yeniden oluşturmak SKColor
için kullanılır. Hedef bit eşleminin Rgba8888
veya Bgra8888
olmasına bağlı olarak baytlar hedef bit damgasında depolanır:
public partial class ColorAdjustmentPage : ContentPage
{
···
unsafe void TransferPixels(float hueAdjust, float saturationAdjust, float luminosityAdjust)
{
byte* srcPtr = (byte*)srcBitmap.GetPixels().ToPointer();
byte* dstPtr = (byte*)dstBitmap.GetPixels().ToPointer();
int width = srcBitmap.Width; // same for both bitmaps
int height = srcBitmap.Height;
SKColorType typeOrg = srcBitmap.ColorType;
SKColorType typeAdj = dstBitmap.ColorType;
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
// Get color from original bitmap
byte byte1 = *srcPtr++; // red or blue
byte byte2 = *srcPtr++; // green
byte byte3 = *srcPtr++; // blue or red
byte byte4 = *srcPtr++; // alpha
SKColor color = new SKColor();
if (typeOrg == SKColorType.Rgba8888)
{
color = new SKColor(byte1, byte2, byte3, byte4);
}
else if (typeOrg == SKColorType.Bgra8888)
{
color = new SKColor(byte3, byte2, byte1, byte4);
}
// Get HSL components
color.ToHsl(out float hue, out float saturation, out float luminosity);
// Adjust HSL components based on adjustments
hue = (hue + hueAdjust) % 360;
saturation = Math.Max(0, Math.Min(100, saturationAdjust * saturation));
luminosity = Math.Max(0, Math.Min(100, luminosityAdjust * luminosity));
// Recreate color from HSL components
color = SKColor.FromHsl(hue, saturation, luminosity);
// Store the bytes in the adjusted bitmap
if (typeAdj == SKColorType.Rgba8888)
{
*dstPtr++ = color.Red;
*dstPtr++ = color.Green;
*dstPtr++ = color.Blue;
*dstPtr++ = color.Alpha;
}
else if (typeAdj == SKColorType.Bgra8888)
{
*dstPtr++ = color.Blue;
*dstPtr++ = color.Green;
*dstPtr++ = color.Red;
*dstPtr++ = color.Alpha;
}
}
}
}
···
}
Bu yöntemin performansı, kaynak ve hedef bit eşlemlerin renk türlerinin çeşitli bileşimleri için ayrı yöntemler oluşturularak ve her piksel için türü denetlemekten kaçınılarak daha da iyileştirilebilir. Bir diğer seçenek de, renk türüne göre değişken için col
birden çok for
döngüye sahip olmaktır.
Posterleştirme
Piksel bitlerine erişmeyi içeren bir diğer yaygın iş de posterleştirmedir. Bit eşlem piksellerinde kodlanmış renkler azaltılır ve sonuç sınırlı renk paleti kullanılarak elle çizilmiş postere benzer.
Posterize sayfası bu işlemi maymun görüntülerinden birinde gerçekleştirir:
public class PosterizePage : ContentPage
{
SKBitmap bitmap =
BitmapExtensions.LoadBitmapResource(typeof(FillRectanglePage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
public PosterizePage()
{
Title = "Posterize";
unsafe
{
uint* ptr = (uint*)bitmap.GetPixels().ToPointer();
int pixelCount = bitmap.Width * bitmap.Height;
for (int i = 0; i < pixelCount; i++)
{
*ptr++ &= 0xE0E0E0FF;
}
}
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform;
}
}
Oluşturucudaki kod her piksele erişir, 0xE0E0E0FF değeriyle bit düzeyinde AND işlemi gerçekleştirir ve ardından sonucu bit eşlem içinde yeniden depolar. 0xE0E0E0FF değerler her renk bileşeninin yüksek 3 bitini tutar ve alt 5 biti 0 olarak ayarlar. Bit eşlem 2 24 veya 16.777.216 renk yerine 29 veya 512 renge indirilir: