Fix copyPixels with alpha behavior (close #639)
This commit is contained in:
@@ -304,45 +304,49 @@ class Image {
|
||||
|
||||
public function copyPixels (sourceImage:Image, sourceRect:Rectangle, destPoint:Vector2, alphaImage:Image = null, alphaPoint:Vector2 = null, mergeAlpha:Bool = false):Void {
|
||||
|
||||
//fast fails -- if source or destination is null or of 0 dimensions, do nothing
|
||||
if (buffer == null || sourceImage == null) return;
|
||||
if (sourceRect.width <= 0 || sourceRect.height <= 0) return;
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
//source rect expands too far right or too far below source image boundaries
|
||||
if (sourceRect.x + sourceRect.width > sourceImage.width) sourceRect.width = sourceImage.width - sourceRect.x;
|
||||
if (sourceRect.y + sourceRect.height > sourceImage.height) sourceRect.height = sourceImage.height - sourceRect.y;
|
||||
|
||||
//source rect starts too far left or too far above source image boundaries
|
||||
if (sourceRect.x < 0) {
|
||||
sourceRect.width += sourceRect.x; //shrink width by amount off canvas
|
||||
sourceRect.x = 0; //clamp rect to 0
|
||||
}
|
||||
if (sourceRect.y < 0) {
|
||||
sourceRect.height += sourceRect.y; //shrink height by amount off canvas
|
||||
sourceRect.y = 0; //clamp rect to 0
|
||||
|
||||
sourceRect.width += sourceRect.x;
|
||||
sourceRect.x = 0;
|
||||
|
||||
}
|
||||
|
||||
if (sourceRect.y < 0) {
|
||||
|
||||
sourceRect.height += sourceRect.y;
|
||||
sourceRect.y = 0;
|
||||
|
||||
}
|
||||
|
||||
//draw area expands too far right or too far below destination image boundaries
|
||||
if (destPoint.x + sourceRect.width > width) sourceRect.width = width - destPoint.x;
|
||||
if (destPoint.y + sourceRect.height > height) sourceRect.height = height - destPoint.y;
|
||||
|
||||
//draw area starts too far left or too far above destination image boundaries
|
||||
if (destPoint.x < 0) {
|
||||
sourceRect.width += destPoint.x; //shrink width by amount off canvas
|
||||
sourceRect.x -= destPoint.x; //adjust source rect to effective starting point
|
||||
destPoint.x = 0; //clamp destination point to 0
|
||||
}
|
||||
if (destPoint.y < 0) {
|
||||
sourceRect.height += destPoint.y; //shrink height by amount off canvas
|
||||
sourceRect.y -= destPoint.y; //adjust source rect to effective starting point
|
||||
destPoint.y = 0; //clamp destination point to 0
|
||||
}
|
||||
|
||||
// TODO: Optimize
|
||||
|
||||
if (sourceImage == this) {
|
||||
|
||||
sourceRect.width += destPoint.x;
|
||||
sourceRect.x -= destPoint.x;
|
||||
destPoint.x = 0;
|
||||
|
||||
}
|
||||
|
||||
if (destPoint.y < 0) {
|
||||
|
||||
sourceRect.height += destPoint.y;
|
||||
sourceRect.y -= destPoint.y;
|
||||
destPoint.y = 0;
|
||||
|
||||
}
|
||||
|
||||
if (sourceImage == this && destPoint.x < sourceRect.right && destPoint.y < sourceRect.bottom) {
|
||||
|
||||
// TODO: Optimize further?
|
||||
sourceImage = clone ();
|
||||
|
||||
}
|
||||
@@ -359,6 +363,7 @@ class Image {
|
||||
#if (js && html5)
|
||||
ImageCanvasUtil.convertToData (this);
|
||||
ImageCanvasUtil.convertToData (sourceImage);
|
||||
if (alphaImage != null) ImageCanvasUtil.convertToData (alphaImage);
|
||||
#end
|
||||
|
||||
ImageDataUtil.copyPixels (this, sourceImage, sourceRect, destPoint, alphaImage, alphaPoint, mergeAlpha);
|
||||
|
||||
@@ -181,61 +181,27 @@ class ImageDataUtil {
|
||||
if (sourceData == null || destData == null) return;
|
||||
|
||||
var sourceView = new ImageDataView (sourceImage, sourceRect);
|
||||
var destView = new ImageDataView (image, new Rectangle (destPoint.x, destPoint.y, sourceView.width, sourceView.height));
|
||||
var destRect = new Rectangle (destPoint.x, destPoint.y, sourceView.width, sourceView.height);
|
||||
var destView = new ImageDataView (image, destRect);
|
||||
|
||||
var sourceFormat = sourceImage.buffer.format;
|
||||
var destFormat = image.buffer.format;
|
||||
|
||||
var sourcePosition, destPosition;
|
||||
var sourceAlpha, destAlpha, oneMinusSourceAlpha, blendAlpha;
|
||||
var sourcePixel:RGBA, destPixel:RGBA;
|
||||
|
||||
var sourcePremultiplied = sourceImage.buffer.premultiplied;
|
||||
var destPremultiplied = image.buffer.premultiplied;
|
||||
var sourceBytesPerPixel = Std.int (sourceImage.buffer.bitsPerPixel / 8);
|
||||
var destBytesPerPixel = Std.int (image.buffer.bitsPerPixel / 8);
|
||||
|
||||
var sourcePosition, destPosition, sourcePixel:RGBA;
|
||||
var useAlphaImage = (alphaImage != null && alphaImage.transparent);
|
||||
var blend = (mergeAlpha || (useAlphaImage && !image.transparent));
|
||||
|
||||
if (!mergeAlpha || !sourceImage.transparent) {
|
||||
if (!useAlphaImage) {
|
||||
|
||||
if (sourceFormat == destFormat && sourcePremultiplied == destPremultiplied && sourceBytesPerPixel == destBytesPerPixel) {
|
||||
|
||||
for (y in 0...destView.height) {
|
||||
|
||||
sourcePosition = sourceView.row (y);
|
||||
destPosition = destView.row (y);
|
||||
|
||||
#if js
|
||||
destData.set (sourceData.subarray (sourcePosition, sourcePosition + destView.width * destBytesPerPixel), destPosition);
|
||||
#else
|
||||
destData.buffer.blit (destPosition, sourceData.buffer, sourcePosition, destView.width * destBytesPerPixel);
|
||||
#end
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (y in 0...destView.height) {
|
||||
|
||||
sourcePosition = sourceView.row (y);
|
||||
destPosition = destView.row (y);
|
||||
|
||||
for (x in 0...destView.width) {
|
||||
|
||||
sourcePixel.readUInt8 (sourceData, sourcePosition, sourceFormat, sourcePremultiplied);
|
||||
sourcePixel.writeUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
sourcePosition += 4;
|
||||
destPosition += 4;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
var sourceAlpha, destAlpha, oneMinusSourceAlpha, blendAlpha;
|
||||
var destPixel:RGBA;
|
||||
|
||||
if (alphaImage == null) {
|
||||
if (blend) {
|
||||
|
||||
for (y in 0...destView.height) {
|
||||
|
||||
@@ -274,51 +240,112 @@ class ImageDataUtil {
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
} else if (sourceFormat == destFormat && sourcePremultiplied == destPremultiplied && sourceBytesPerPixel == destBytesPerPixel) {
|
||||
|
||||
if (alphaPoint == null) alphaPoint = new Vector2 ();
|
||||
|
||||
var alphaData = alphaImage.buffer.data;
|
||||
var alphaFormat = alphaImage.buffer.format;
|
||||
var alphaPremultiplied = alphaImage.buffer.premultiplied;
|
||||
|
||||
var alphaView = new ImageDataView (alphaImage, new Rectangle (alphaPoint.x, alphaPoint.y, destView.width, destView.height));
|
||||
var alphaPosition, alphaPixel:RGBA;
|
||||
|
||||
for (y in 0...alphaView.height) {
|
||||
for (y in 0...destView.height) {
|
||||
|
||||
sourcePosition = sourceView.row (y);
|
||||
destPosition = destView.row (y);
|
||||
alphaPosition = alphaView.row (y);
|
||||
|
||||
for (x in 0...alphaView.width) {
|
||||
#if js
|
||||
// TODO: Is this faster on HTML5 than the normal copy method?
|
||||
destData.set (sourceData.subarray (sourcePosition, sourcePosition + destView.width * destBytesPerPixel), destPosition);
|
||||
#else
|
||||
destData.buffer.blit (destPosition, sourceData.buffer, sourcePosition, destView.width * destBytesPerPixel);
|
||||
#end
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (y in 0...destView.height) {
|
||||
|
||||
sourcePosition = sourceView.row (y);
|
||||
destPosition = destView.row (y);
|
||||
|
||||
for (x in 0...destView.width) {
|
||||
|
||||
sourcePixel.readUInt8 (sourceData, sourcePosition, sourceFormat, sourcePremultiplied);
|
||||
sourcePixel.writeUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
sourcePosition += 4;
|
||||
destPosition += 4;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (alphaPoint == null) alphaPoint = new Vector2 ();
|
||||
|
||||
var alphaData = alphaImage.buffer.data;
|
||||
var alphaFormat = alphaImage.buffer.format;
|
||||
|
||||
var alphaView = new ImageDataView (alphaImage, new Rectangle (alphaPoint.x, alphaPoint.y, destView.width, destView.height));
|
||||
var alphaPosition, alphaPixel:RGBA;
|
||||
var alphaOffsetY = alphaView.y - destView.y;
|
||||
|
||||
if (blend) {
|
||||
|
||||
for (y in 0...destView.height) {
|
||||
|
||||
sourcePosition = sourceView.row (y);
|
||||
destPosition = destView.row (y);
|
||||
alphaPosition = alphaView.row (y + alphaOffsetY);
|
||||
|
||||
for (x in 0...destView.width) {
|
||||
|
||||
sourcePixel.readUInt8 (sourceData, sourcePosition, sourceFormat, sourcePremultiplied);
|
||||
destPixel.readUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
alphaPixel.readUInt8 (alphaData, alphaPosition, alphaFormat, alphaPremultiplied);
|
||||
alphaPixel.readUInt8 (alphaData, alphaPosition, alphaFormat, false);
|
||||
|
||||
sourceAlpha = alphaPixel.a / 0xFF;
|
||||
destAlpha = destPixel.a / 0xFF;
|
||||
oneMinusSourceAlpha = 1 - sourceAlpha;
|
||||
blendAlpha = sourceAlpha + (destAlpha * oneMinusSourceAlpha);
|
||||
sourceAlpha = (alphaPixel.a / 255.0) * (sourcePixel.a / 255.0);
|
||||
|
||||
if (blendAlpha == 0) {
|
||||
if (sourceAlpha > 0) {
|
||||
|
||||
destPixel = 0;
|
||||
|
||||
} else {
|
||||
destAlpha = destPixel.a / 255.0;
|
||||
oneMinusSourceAlpha = 1 - sourceAlpha;
|
||||
blendAlpha = sourceAlpha + (destAlpha * oneMinusSourceAlpha);
|
||||
|
||||
destPixel.r = RGBA.__clamp[Math.round ((sourcePixel.r * sourceAlpha + destPixel.r * destAlpha * oneMinusSourceAlpha) / blendAlpha)];
|
||||
destPixel.g = RGBA.__clamp[Math.round ((sourcePixel.g * sourceAlpha + destPixel.g * destAlpha * oneMinusSourceAlpha) / blendAlpha)];
|
||||
destPixel.b = RGBA.__clamp[Math.round ((sourcePixel.b * sourceAlpha + destPixel.b * destAlpha * oneMinusSourceAlpha) / blendAlpha)];
|
||||
destPixel.a = RGBA.__clamp[Math.round (blendAlpha * 255.0)];
|
||||
|
||||
destPixel.writeUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
}
|
||||
|
||||
destPixel.writeUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
sourcePosition += 4;
|
||||
destPosition += 4;
|
||||
alphaPosition += 4;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (y in 0...destView.height) {
|
||||
|
||||
sourcePosition = sourceView.row (y);
|
||||
destPosition = destView.row (y);
|
||||
alphaPosition = alphaView.row (y + alphaOffsetY);
|
||||
|
||||
for (x in 0...destView.width) {
|
||||
|
||||
sourcePixel.readUInt8 (sourceData, sourcePosition, sourceFormat, sourcePremultiplied);
|
||||
alphaPixel.readUInt8 (alphaData, alphaPosition, alphaFormat, false);
|
||||
|
||||
sourcePixel.a = Math.round (sourcePixel.a * (alphaPixel.a / 0xFF));
|
||||
sourcePixel.writeUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
sourcePosition += 4;
|
||||
destPosition += 4;
|
||||
alphaPosition += 4;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@ namespace lime {
|
||||
uint8_t* sourceData = (uint8_t*)sourceImage->buffer->data->Data ();
|
||||
uint8_t* destData = (uint8_t*)image->buffer->data->Data ();
|
||||
|
||||
if (!sourceData || !destData) return;
|
||||
|
||||
ImageDataView sourceView = ImageDataView (sourceImage, sourceRect);
|
||||
Rectangle destRect = Rectangle (destPoint->x, destPoint->y, sourceView.width, sourceView.height);
|
||||
ImageDataView destView = ImageDataView (image, &destRect);
|
||||
@@ -120,53 +122,20 @@ namespace lime {
|
||||
PixelFormat destFormat = image->buffer->format;
|
||||
|
||||
int sourcePosition, destPosition;
|
||||
RGBA sourcePixel;
|
||||
float sourceAlpha, destAlpha, oneMinusSourceAlpha, blendAlpha;
|
||||
RGBA sourcePixel, destPixel;
|
||||
|
||||
bool sourcePremultiplied = sourceImage->buffer->premultiplied;
|
||||
bool destPremultiplied = image->buffer->premultiplied;
|
||||
int sourceBytesPerPixel = sourceImage->buffer->bitsPerPixel / 8;
|
||||
int destBytesPerPixel = image->buffer->bitsPerPixel / 8;
|
||||
|
||||
if (!mergeAlpha || !sourceImage->buffer->transparent) {
|
||||
bool useAlphaImage = (alphaImage && alphaImage->buffer->transparent);
|
||||
bool blend = (mergeAlpha || (useAlphaImage && !image->buffer->transparent));
|
||||
|
||||
if (!useAlphaImage) {
|
||||
|
||||
if (sourceFormat == destFormat && sourcePremultiplied == destPremultiplied && sourceBytesPerPixel == destBytesPerPixel) {
|
||||
|
||||
for (int y = 0; y < destView.height; y++) {
|
||||
|
||||
sourcePosition = sourceView.Row (y);
|
||||
destPosition = destView.Row (y);
|
||||
|
||||
memcpy (&destData[destPosition], &sourceData[sourcePosition], destView.width * destBytesPerPixel);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (int y = 0; y < destView.height; y++) {
|
||||
|
||||
sourcePosition = sourceView.Row (y);
|
||||
destPosition = destView.Row (y);
|
||||
|
||||
for (int x = 0; x < destView.width; x++) {
|
||||
|
||||
sourcePixel.ReadUInt8 (sourceData, sourcePosition, sourceFormat, sourcePremultiplied);
|
||||
sourcePixel.WriteUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
sourcePosition += 4;
|
||||
destPosition += 4;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
float sourceAlpha, destAlpha, oneMinusSourceAlpha, blendAlpha;
|
||||
RGBA destPixel;
|
||||
|
||||
if (alphaImage == 0) {
|
||||
if (blend) {
|
||||
|
||||
for (int y = 0; y < destView.height; y++) {
|
||||
|
||||
@@ -205,51 +174,108 @@ namespace lime {
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
} else if (sourceFormat == destFormat && sourcePremultiplied == destPremultiplied && sourceBytesPerPixel == destBytesPerPixel) {
|
||||
|
||||
uint8_t* alphaData = (uint8_t*)alphaImage->buffer->data->Data ();
|
||||
PixelFormat alphaFormat = alphaImage->buffer->format;
|
||||
bool alphaPremultiplied = alphaImage->buffer->premultiplied;
|
||||
|
||||
Rectangle alphaRect = Rectangle (alphaPoint->x, alphaPoint->y, destView.width, destView.height);
|
||||
ImageDataView alphaView = ImageDataView (alphaImage, &alphaRect);
|
||||
int alphaPosition;
|
||||
RGBA alphaPixel;
|
||||
|
||||
for (int y = 0; y < alphaView.height; y++) {
|
||||
for (int y = 0; y < destView.height; y++) {
|
||||
|
||||
sourcePosition = sourceView.Row (y);
|
||||
destPosition = destView.Row (y);
|
||||
alphaPosition = alphaView.Row (y);
|
||||
|
||||
for (int x = 0; x < alphaView.width; x++) {
|
||||
memcpy (&destData[destPosition], &sourceData[sourcePosition], destView.width * destBytesPerPixel);
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (int y = 0; y < destView.height; y++) {
|
||||
|
||||
sourcePosition = sourceView.Row (y);
|
||||
destPosition = destView.Row (y);
|
||||
|
||||
for (int x = 0; x < destView.width; x++) {
|
||||
|
||||
sourcePixel.ReadUInt8 (sourceData, sourcePosition, sourceFormat, sourcePremultiplied);
|
||||
sourcePixel.WriteUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
sourcePosition += 4;
|
||||
destPosition += 4;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
uint8_t* alphaData = (uint8_t*)alphaImage->buffer->data->Data ();
|
||||
PixelFormat alphaFormat = alphaImage->buffer->format;
|
||||
bool alphaPremultiplied = alphaImage->buffer->premultiplied;
|
||||
|
||||
Rectangle alphaRect = Rectangle (alphaPoint->x, alphaPoint->y, destView.width, destView.height);
|
||||
ImageDataView alphaView = ImageDataView (alphaImage, &alphaRect);
|
||||
int alphaPosition;
|
||||
RGBA alphaPixel;
|
||||
int alphaOffsetY = alphaView.y - destView.y;
|
||||
|
||||
if (blend) {
|
||||
|
||||
for (int y = 0; y < destView.height; y++) {
|
||||
|
||||
sourcePosition = sourceView.Row (y);
|
||||
destPosition = destView.Row (y);
|
||||
alphaPosition = alphaView.Row (y + alphaOffsetY);
|
||||
|
||||
for (int x = 0; x < destView.width; x++) {
|
||||
|
||||
sourcePixel.ReadUInt8 (sourceData, sourcePosition, sourceFormat, sourcePremultiplied);
|
||||
destPixel.ReadUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
alphaPixel.ReadUInt8 (alphaData, alphaPosition, alphaFormat, alphaPremultiplied);
|
||||
alphaPixel.ReadUInt8 (alphaData, alphaPosition, alphaFormat, false);
|
||||
|
||||
sourceAlpha = alphaPixel.a / 0xFF;
|
||||
destAlpha = destPixel.a / 0xFF;
|
||||
oneMinusSourceAlpha = 1 - sourceAlpha;
|
||||
blendAlpha = sourceAlpha + (destAlpha * oneMinusSourceAlpha);
|
||||
sourceAlpha = (alphaPixel.a / 255.0) * (sourcePixel.a / 255.0);
|
||||
|
||||
if (blendAlpha == 0) {
|
||||
if (sourceAlpha > 0) {
|
||||
|
||||
destPixel.Set (0, 0, 0, 0);
|
||||
|
||||
} else {
|
||||
destAlpha = destPixel.a / 255.0;
|
||||
oneMinusSourceAlpha = 1 - sourceAlpha;
|
||||
blendAlpha = sourceAlpha + (destAlpha * oneMinusSourceAlpha);
|
||||
|
||||
destPixel.r = __clamp[int (0.5 + (sourcePixel.r * sourceAlpha + destPixel.r * destAlpha * oneMinusSourceAlpha) / blendAlpha)];
|
||||
destPixel.g = __clamp[int (0.5 + (sourcePixel.g * sourceAlpha + destPixel.g * destAlpha * oneMinusSourceAlpha) / blendAlpha)];
|
||||
destPixel.b = __clamp[int (0.5 + (sourcePixel.b * sourceAlpha + destPixel.b * destAlpha * oneMinusSourceAlpha) / blendAlpha)];
|
||||
destPixel.a = __clamp[int (0.5 + blendAlpha * 255.0)];
|
||||
|
||||
destPixel.WriteUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
}
|
||||
|
||||
destPixel.WriteUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
sourcePosition += 4;
|
||||
destPosition += 4;
|
||||
alphaPosition += 4;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
for (int y = 0; y < destView.height; y++) {
|
||||
|
||||
sourcePosition = sourceView.Row (y);
|
||||
destPosition = destView.Row (y);
|
||||
alphaPosition = alphaView.Row (y + alphaOffsetY);
|
||||
|
||||
for (int x = 0; x < destView.width; x++) {
|
||||
|
||||
sourcePixel.ReadUInt8 (sourceData, sourcePosition, sourceFormat, sourcePremultiplied);
|
||||
alphaPixel.ReadUInt8 (alphaData, alphaPosition, alphaFormat, false);
|
||||
|
||||
sourcePixel.a = int (0.5 + (sourcePixel.a * (alphaPixel.a / 255.0)));
|
||||
sourcePixel.WriteUInt8 (destData, destPosition, destFormat, destPremultiplied);
|
||||
|
||||
sourcePosition += 4;
|
||||
destPosition += 4;
|
||||
alphaPosition += 4;
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user