#region Header
//
// Project: WriteableBitmapEx - WriteableBitmap extensions
// Description: Collection of blit (copy) extension methods for the WriteableBitmap class.
//
// Changed by: $Author: unknown $
// Changed on: $Date: 2015-03-23 10:25:30 +0100 (Mo, 23 Mrz 2015) $
// Changed in: $Revision: 113508 $
// Project: $URL: https://writeablebitmapex.svn.codeplex.com/svn/trunk/Source/WriteableBitmapEx/WriteableBitmapBlitExtensions.cs $
// Id: $Id: WriteableBitmapBlitExtensions.cs 113508 2015-03-23 09:25:30Z unknown $
//
//
// Copyright © 2009-2015 Bill Reiss, Rene Schulte and WriteableBitmapEx Contributors
//
// This code is open source. Please read the License.txt for details. No worries, we won't sue you! ;)
//
#endregion
using System;
#if NETFX_CORE
using Windows.Foundation;
namespace Windows.UI.Xaml.Media.Imaging
#else
namespace System.Windows.Media.Imaging
#endif
{
///
/// Collection of blit (copy) extension methods for the WriteableBitmap class.
///
internal
#if WPF
unsafe
#endif
static partial class WriteableBitmapExtensions
{
private const int WhiteR = 255;
private const int WhiteG = 255;
private const int WhiteB = 255;
#region Enum
///
/// The blending mode.
///
internal enum BlendMode
{
///
/// Alpha blending uses the alpha channel to combine the source and destination.
///
Alpha,
///
/// Additive blending adds the colors of the source and the destination.
///
Additive,
///
/// Subtractive blending subtracts the source color from the destination.
///
Subtractive,
///
/// Uses the source color as a mask.
///
Mask,
///
/// Multiplies the source color with the destination color.
///
Multiply,
///
/// Ignores the specified Color
///
ColorKeying,
///
/// No blending just copies the pixels from the source.
///
None
}
#endregion
#region Methods
///
/// Copies (blits) the pixels from the WriteableBitmap source to the destination WriteableBitmap (this).
///
/// The destination WriteableBitmap.
/// The rectangle that defines the destination region.
/// The source WriteableBitmap.
/// The rectangle that will be copied from the source to the destination.
/// The blending mode .
internal static void Blit(this WriteableBitmap bmp, Rect destRect, WriteableBitmap source, Rect sourceRect, BlendMode blendMode)
{
Blit(bmp, destRect, source, sourceRect, Colors.White, blendMode);
}
///
/// Copies (blits) the pixels from the WriteableBitmap source to the destination WriteableBitmap (this).
///
/// The destination WriteableBitmap.
/// The rectangle that defines the destination region.
/// The source WriteableBitmap.
/// The rectangle that will be copied from the source to the destination.
internal static void Blit(this WriteableBitmap bmp, Rect destRect, WriteableBitmap source, Rect sourceRect)
{
Blit(bmp, destRect, source, sourceRect, Colors.White, BlendMode.Alpha);
}
///
/// Copies (blits) the pixels from the WriteableBitmap source to the destination WriteableBitmap (this).
///
/// The destination WriteableBitmap.
/// The destination position in the destination bitmap.
/// The source WriteableBitmap.
/// The rectangle that will be copied from the source to the destination.
/// If not Colors.White, will tint the source image. A partially transparent color and the image will be drawn partially transparent.
/// The blending mode .
internal static void Blit(this WriteableBitmap bmp, Point destPosition, WriteableBitmap source, Rect sourceRect, Color color, BlendMode blendMode)
{
var destRect = new Rect(destPosition, new Size(sourceRect.Width, sourceRect.Height));
Blit(bmp, destRect, source, sourceRect, color, blendMode);
}
///
/// Copies (blits) the pixels from the WriteableBitmap source to the destination WriteableBitmap (this).
///
/// The destination WriteableBitmap.
/// The rectangle that defines the destination region.
/// The source WriteableBitmap.
/// The rectangle that will be copied from the source to the destination.
/// If not Colors.White, will tint the source image. A partially transparent color and the image will be drawn partially transparent. If the BlendMode is ColorKeying, this color will be used as color key to mask all pixels with this value out.
/// The blending mode .
internal static void Blit(this WriteableBitmap bmp, Rect destRect, WriteableBitmap source, Rect sourceRect, Color color, BlendMode blendMode)
{
if (color.A == 0)
{
return;
}
#if WPF
var isPrgba = source.Format == PixelFormats.Pbgra32 || source.Format == PixelFormats.Prgba64 || source.Format == PixelFormats.Prgba128Float;
#endif
var dw = (int)destRect.Width;
var dh = (int)destRect.Height;
using (var srcContext = source.GetBitmapContext(ReadWriteMode.ReadOnly))
{
using (var destContext = bmp.GetBitmapContext())
{
var sourceWidth = srcContext.Width;
var dpw = destContext.Width;
var dph = destContext.Height;
var intersect = new Rect(0, 0, dpw, dph);
intersect.Intersect(destRect);
if (intersect.IsEmpty)
{
return;
}
var sourcePixels = srcContext.Pixels;
var destPixels = destContext.Pixels;
var sourceLength = srcContext.Length;
int sourceIdx = -1;
int px = (int)destRect.X;
int py = (int)destRect.Y;
int x;
int y;
int idx;
double ii;
double jj;
int sr = 0;
int sg = 0;
int sb = 0;
int dr, dg, db;
int sourcePixel;
int sa = 0;
int da;
int ca = color.A;
int cr = color.R;
int cg = color.G;
int cb = color.B;
bool tinted = color != Colors.White;
var sw = (int)sourceRect.Width;
var sdx = sourceRect.Width / destRect.Width;
var sdy = sourceRect.Height / destRect.Height;
int sourceStartX = (int)sourceRect.X;
int sourceStartY = (int)sourceRect.Y;
int lastii, lastjj;
lastii = -1;
lastjj = -1;
jj = sourceStartY;
y = py;
for (int j = 0; j < dh; j++)
{
if (y >= 0 && y < dph)
{
ii = sourceStartX;
idx = px + y * dpw;
x = px;
sourcePixel = sourcePixels[0];
// Scanline BlockCopy is much faster (3.5x) if no tinting and blending is needed,
// even for smaller sprites like the 32x32 particles.
if (blendMode == BlendMode.None && !tinted)
{
sourceIdx = (int)ii + (int)jj * sourceWidth;
var offset = x < 0 ? -x : 0;
var xx = x + offset;
var wx = sourceWidth - offset;
var len = xx + wx < dpw ? wx : dpw - xx;
if (len > sw) len = sw;
if (len > dw) len = dw;
BitmapContext.BlockCopy(srcContext, (sourceIdx + offset) * 4, destContext, (idx + offset) * 4, len * 4);
}
// Pixel by pixel copying
else
{
for (int i = 0; i < dw; i++)
{
if (x >= 0 && x < dpw)
{
if ((int)ii != lastii || (int)jj != lastjj)
{
sourceIdx = (int)ii + (int)jj * sourceWidth;
if (sourceIdx >= 0 && sourceIdx < sourceLength)
{
sourcePixel = sourcePixels[sourceIdx];
sa = ((sourcePixel >> 24) & 0xff);
sr = ((sourcePixel >> 16) & 0xff);
sg = ((sourcePixel >> 8) & 0xff);
sb = ((sourcePixel) & 0xff);
if (tinted && sa != 0)
{
sa = (((sa * ca) * 0x8081) >> 23);
sr = ((((((sr * cr) * 0x8081) >> 23) * ca) * 0x8081) >> 23);
sg = ((((((sg * cg) * 0x8081) >> 23) * ca) * 0x8081) >> 23);
sb = ((((((sb * cb) * 0x8081) >> 23) * ca) * 0x8081) >> 23);
sourcePixel = (sa << 24) | (sr << 16) | (sg << 8) | sb;
}
}
else
{
sa = 0;
}
}
if (blendMode == BlendMode.None)
{
destPixels[idx] = sourcePixel;
}
else if (blendMode == BlendMode.ColorKeying)
{
sr = ((sourcePixel >> 16) & 0xff);
sg = ((sourcePixel >> 8) & 0xff);
sb = ((sourcePixel) & 0xff);
if (sr != color.R || sg != color.G || sb != color.B)
{
destPixels[idx] = sourcePixel;
}
}
else if (blendMode == BlendMode.Mask)
{
int destPixel = destPixels[idx];
da = ((destPixel >> 24) & 0xff);
dr = ((destPixel >> 16) & 0xff);
dg = ((destPixel >> 8) & 0xff);
db = ((destPixel) & 0xff);
destPixel = ((((da * sa) * 0x8081) >> 23) << 24) |
((((dr * sa) * 0x8081) >> 23) << 16) |
((((dg * sa) * 0x8081) >> 23) << 8) |
((((db * sa) * 0x8081) >> 23));
destPixels[idx] = destPixel;
}
else if (sa > 0)
{
int destPixel = destPixels[idx];
da = ((destPixel >> 24) & 0xff);
if ((sa == 255 || da == 0) &&
blendMode != BlendMode.Additive
&& blendMode != BlendMode.Subtractive
&& blendMode != BlendMode.Multiply
)
{
destPixels[idx] = sourcePixel;
}
else
{
dr = ((destPixel >> 16) & 0xff);
dg = ((destPixel >> 8) & 0xff);
db = ((destPixel) & 0xff);
if (blendMode == BlendMode.Alpha)
{
var isa = 255 - sa;
#if NETFX_CORE
// Special case for WinRT since it does not use pARGB (pre-multiplied alpha)
destPixel = ((da & 0xff) << 24) |
((((sr * sa + isa * dr) >> 8) & 0xff) << 16) |
((((sg * sa + isa * dg) >> 8) & 0xff) << 8) |
(((sb * sa + isa * db) >> 8) & 0xff);
#elif WPF
if (isPrgba)
{
destPixel = ((da & 0xff) << 24) |
(((((sr << 8) + isa * dr) >> 8) & 0xff) << 16) |
(((((sg << 8) + isa * dg) >> 8) & 0xff) << 8) |
((((sb << 8) + isa * db) >> 8) & 0xff);
}
else
{
destPixel = ((da & 0xff) << 24) |
(((((sr * sa) + isa * dr) >> 8) & 0xff) << 16) |
(((((sg * sa) + isa * dg) >> 8) & 0xff) << 8) |
((((sb * sa) + isa * db) >> 8) & 0xff);
}
#else
destPixel = ((da & 0xff) << 24) |
(((((sr << 8) + isa * dr) >> 8) & 0xff) << 16) |
(((((sg << 8) + isa * dg) >> 8) & 0xff) << 8) |
((((sb << 8) + isa * db) >> 8) & 0xff);
#endif
}
else if (blendMode == BlendMode.Additive)
{
int a = (255 <= sa + da) ? 255 : (sa + da);
destPixel = (a << 24) |
(((a <= sr + dr) ? a : (sr + dr)) << 16) |
(((a <= sg + dg) ? a : (sg + dg)) << 8) |
(((a <= sb + db) ? a : (sb + db)));
}
else if (blendMode == BlendMode.Subtractive)
{
int a = da;
destPixel = (a << 24) |
(((sr >= dr) ? 0 : (sr - dr)) << 16) |
(((sg >= dg) ? 0 : (sg - dg)) << 8) |
(((sb >= db) ? 0 : (sb - db)));
}
else if (blendMode == BlendMode.Multiply)
{
// Faster than a division like (s * d) / 255 are 2 shifts and 2 adds
int ta = (sa * da) + 128;
int tr = (sr * dr) + 128;
int tg = (sg * dg) + 128;
int tb = (sb * db) + 128;
int ba = ((ta >> 8) + ta) >> 8;
int br = ((tr >> 8) + tr) >> 8;
int bg = ((tg >> 8) + tg) >> 8;
int bb = ((tb >> 8) + tb) >> 8;
destPixel = (ba << 24) |
((ba <= br ? ba : br) << 16) |
((ba <= bg ? ba : bg) << 8) |
((ba <= bb ? ba : bb));
}
destPixels[idx] = destPixel;
}
}
}
x++;
idx++;
ii += sdx;
}
}
}
jj += sdy;
y++;
}
}
}
}
internal static void Blit(BitmapContext destContext, int dpw, int dph, Rect destRect, BitmapContext srcContext, Rect sourceRect, int sourceWidth)
{
const BlendMode blendMode = BlendMode.Alpha;
int dw = (int)destRect.Width;
int dh = (int)destRect.Height;
Rect intersect = new Rect(0, 0, dpw, dph);
intersect.Intersect(destRect);
if (intersect.IsEmpty)
{
return;
}
#if WPF
var isPrgba = srcContext.Format == PixelFormats.Pbgra32 || srcContext.Format == PixelFormats.Prgba64 || srcContext.Format == PixelFormats.Prgba128Float;
#endif
var sourcePixels = srcContext.Pixels;
var destPixels = destContext.Pixels;
int sourceLength = srcContext.Length;
int sourceIdx = -1;
int px = (int)destRect.X;
int py = (int)destRect.Y;
int x;
int y;
int idx;
double ii;
double jj;
int sr = 0;
int sg = 0;
int sb = 0;
int dr, dg, db;
int sourcePixel;
int sa = 0;
int da;
var sw = (int)sourceRect.Width;
var sdx = sourceRect.Width / destRect.Width;
var sdy = sourceRect.Height / destRect.Height;
int sourceStartX = (int)sourceRect.X;
int sourceStartY = (int)sourceRect.Y;
int lastii, lastjj;
lastii = -1;
lastjj = -1;
jj = sourceStartY;
y = py;
for (var j = 0; j < dh; j++)
{
if (y >= 0 && y < dph)
{
ii = sourceStartX;
idx = px + y * dpw;
x = px;
sourcePixel = sourcePixels[0];
// Pixel by pixel copying
for (var i = 0; i < dw; i++)
{
if (x >= 0 && x < dpw)
{
if ((int)ii != lastii || (int)jj != lastjj)
{
sourceIdx = (int)ii + (int)jj * sourceWidth;
if (sourceIdx >= 0 && sourceIdx < sourceLength)
{
sourcePixel = sourcePixels[sourceIdx];
sa = ((sourcePixel >> 24) & 0xff);
sr = ((sourcePixel >> 16) & 0xff);
sg = ((sourcePixel >> 8) & 0xff);
sb = ((sourcePixel) & 0xff);
}
else
{
sa = 0;
}
}
if (sa > 0)
{
int destPixel = destPixels[idx];
da = ((destPixel >> 24) & 0xff);
if ((sa == 255 || da == 0))
{
destPixels[idx] = sourcePixel;
}
else
{
dr = ((destPixel >> 16) & 0xff);
dg = ((destPixel >> 8) & 0xff);
db = ((destPixel) & 0xff);
var isa = 255 - sa;
#if NETFX_CORE
// Special case for WinRT since it does not use pARGB (pre-multiplied alpha)
destPixel = ((da & 0xff) << 24) |
((((sr * sa + isa * dr) >> 8) & 0xff) << 16) |
((((sg * sa + isa * dg) >> 8) & 0xff) << 8) |
(((sb * sa + isa * db) >> 8) & 0xff);
#elif WPF
if (isPrgba)
{
destPixel = ((da & 0xff) << 24) |
(((((sr << 8) + isa * dr) >> 8) & 0xff) << 16) |
(((((sg << 8) + isa * dg) >> 8) & 0xff) << 8) |
((((sb << 8) + isa * db) >> 8) & 0xff);
}
else
{
destPixel = ((da & 0xff) << 24) |
(((((sr * sa) + isa * dr) >> 8) & 0xff) << 16) |
(((((sg * sa) + isa * dg) >> 8) & 0xff) << 8) |
((((sb * sa) + isa * db) >> 8) & 0xff);
}
#else
destPixel = ((da & 0xff) << 24) |
(((((sr << 8) + isa * dr) >> 8) & 0xff) << 16) |
(((((sg << 8) + isa * dg) >> 8) & 0xff) << 8) |
((((sb << 8) + isa * db) >> 8) & 0xff);
#endif
destPixels[idx] = destPixel;
}
}
}
x++;
idx++;
ii += sdx;
}
}
jj += sdy;
y++;
}
}
///
/// Renders a bitmap using any affine transformation and transparency into this bitmap
/// Unlike Silverlight's Render() method, this one uses 2-3 times less memory, and is the same or better quality
/// The algorithm is simple dx/dy (bresenham-like) step by step painting, optimized with fixed point and fast bilinear filtering
/// It's used in Fantasia Painter for drawing stickers and 3D objects on screen
///
/// Destination bitmap.
/// The source WriteableBitmap.
/// If true, the the destination bitmap will be set to all clear (0) before rendering.
/// opacity of the source bitmap to render, between 0 and 1 inclusive
/// Transformation to apply
internal static void BlitRender(this WriteableBitmap bmp, WriteableBitmap source, bool shouldClear = true, float opacity = 1f, GeneralTransform transform = null)
{
const int PRECISION_SHIFT = 10;
const int PRECISION_VALUE = (1 << PRECISION_SHIFT);
const int PRECISION_MASK = PRECISION_VALUE - 1;
using (BitmapContext destContext = bmp.GetBitmapContext())
{
if (transform == null) transform = new MatrixTransform();
var destPixels = destContext.Pixels;
int destWidth = destContext.Width;
int destHeight = destContext.Height;
var inverse = transform.Inverse;
if(shouldClear) destContext.Clear();
using (BitmapContext sourceContext = source.GetBitmapContext())
{
var sourcePixels = sourceContext.Pixels;
int sourceWidth = sourceContext.Width;
int sourceHeight = sourceContext.Height;
Rect sourceRect = new Rect(0, 0, sourceWidth, sourceHeight);
Rect destRect = new Rect(0, 0, destWidth, destHeight);
Rect bounds = transform.TransformBounds(sourceRect);
bounds.Intersect(destRect);
int startX = (int)bounds.Left;
int startY = (int)bounds.Top;
int endX = (int)bounds.Right;
int endY = (int)bounds.Bottom;
#if NETFX_CORE
Point zeroZero = inverse.TransformPoint(new Point(startX, startY));
Point oneZero = inverse.TransformPoint(new Point(startX + 1, startY));
Point zeroOne = inverse.TransformPoint(new Point(startX, startY + 1));
#else
Point zeroZero = inverse.Transform(new Point(startX, startY));
Point oneZero = inverse.Transform(new Point(startX + 1, startY));
Point zeroOne = inverse.Transform(new Point(startX, startY + 1));
#endif
float sourceXf = ((float)zeroZero.X);
float sourceYf = ((float)zeroZero.Y);
int dxDx = (int)((((float)oneZero.X) - sourceXf) * PRECISION_VALUE); // for 1 unit in X coord, how much does X change in source texture?
int dxDy = (int)((((float)oneZero.Y) - sourceYf) * PRECISION_VALUE); // for 1 unit in X coord, how much does Y change in source texture?
int dyDx = (int)((((float)zeroOne.X) - sourceXf) * PRECISION_VALUE); // for 1 unit in Y coord, how much does X change in source texture?
int dyDy = (int)((((float)zeroOne.Y) - sourceYf) * PRECISION_VALUE); // for 1 unit in Y coord, how much does Y change in source texture?
int sourceX = (int)(((float)zeroZero.X) * PRECISION_VALUE);
int sourceY = (int)(((float)zeroZero.Y) * PRECISION_VALUE);
int sourceWidthFixed = sourceWidth << PRECISION_SHIFT;
int sourceHeightFixed = sourceHeight << PRECISION_SHIFT;
int opacityInt = (int)(opacity * 255);
int index = 0;
for (int destY = startY; destY < endY; destY++)
{
index = destY * destWidth + startX;
int savedSourceX = sourceX;
int savedSourceY = sourceY;
for (int destX = startX; destX < endX; destX++)
{
if ((sourceX >= 0) && (sourceX < sourceWidthFixed) && (sourceY >= 0) && (sourceY < sourceHeightFixed))
{
// bilinear filtering
int xFloor = sourceX >> PRECISION_SHIFT;
int yFloor = sourceY >> PRECISION_SHIFT;
if (xFloor < 0) xFloor = 0;
if (yFloor < 0) yFloor = 0;
int xCeil = xFloor + 1;
int yCeil = yFloor + 1;
if (xCeil >= sourceWidth)
{
xFloor = sourceWidth - 1;
xCeil = 0;
}
else
{
xCeil = 1;
}
if (yCeil >= sourceHeight)
{
yFloor = sourceHeight - 1;
yCeil = 0;
}
else
{
yCeil = sourceWidth;
}
int i1 = yFloor * sourceWidth + xFloor;
int p1 = sourcePixels[i1];
int p2 = sourcePixels[i1 + xCeil];
int p3 = sourcePixels[i1 + yCeil];
int p4 = sourcePixels[i1 + yCeil + xCeil];
int xFrac = sourceX & PRECISION_MASK;
int yFrac = sourceY & PRECISION_MASK;
// alpha
byte a1 = (byte)(p1 >> 24);
byte a2 = (byte)(p2 >> 24);
byte a3 = (byte)(p3 >> 24);
byte a4 = (byte)(p4 >> 24);
int comp1, comp2;
byte a;
if ((a1 == a2) && (a1 == a3) && (a1 == a4))
{
if (a1 == 0)
{
destPixels[index] = 0;
sourceX += dxDx;
sourceY += dxDy;
index++;
continue;
}
a = a1;
}
else
{
comp1 = a1 + ((xFrac * (a2 - a1)) >> PRECISION_SHIFT);
comp2 = a3 + ((xFrac * (a4 - a3)) >> PRECISION_SHIFT);
a = (byte)(comp1 + ((yFrac * (comp2 - comp1)) >> PRECISION_SHIFT));
}
// red
comp1 = ((byte)(p1 >> 16)) + ((xFrac * (((byte)(p2 >> 16)) - ((byte)(p1 >> 16)))) >> PRECISION_SHIFT);
comp2 = ((byte)(p3 >> 16)) + ((xFrac * (((byte)(p4 >> 16)) - ((byte)(p3 >> 16)))) >> PRECISION_SHIFT);
byte r = (byte)(comp1 + ((yFrac * (comp2 - comp1)) >> PRECISION_SHIFT));
// green
comp1 = ((byte)(p1 >> 8)) + ((xFrac * (((byte)(p2 >> 8)) - ((byte)(p1 >> 8)))) >> PRECISION_SHIFT);
comp2 = ((byte)(p3 >> 8)) + ((xFrac * (((byte)(p4 >> 8)) - ((byte)(p3 >> 8)))) >> PRECISION_SHIFT);
byte g = (byte)(comp1 + ((yFrac * (comp2 - comp1)) >> PRECISION_SHIFT));
// blue
comp1 = ((byte)p1) + ((xFrac * (((byte)p2) - ((byte)p1))) >> PRECISION_SHIFT);
comp2 = ((byte)p3) + ((xFrac * (((byte)p4) - ((byte)p3))) >> PRECISION_SHIFT);
byte b = (byte)(comp1 + ((yFrac * (comp2 - comp1)) >> PRECISION_SHIFT));
// save updated pixel
if (opacityInt != 255)
{
a = (byte)((a * opacityInt) >> 8);
r = (byte)((r * opacityInt) >> 8);
g = (byte)((g * opacityInt) >> 8);
b = (byte)((b * opacityInt) >> 8);
}
destPixels[index] = (a << 24) | (r << 16) | (g << 8) | b;
}
sourceX += dxDx;
sourceY += dxDy;
index++;
}
sourceX = savedSourceX + dyDx;
sourceY = savedSourceY + dyDy;
}
}
}
}
#endregion
}
}