Fix copyPixels with alpha behavior (close #639)

This commit is contained in:
Joshua Granick
2017-06-02 12:20:38 -07:00
parent 74dc959012
commit 23f215af27
3 changed files with 215 additions and 157 deletions

View File

@@ -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
sourceRect.width += destPoint.x;
sourceRect.x -= destPoint.x;
destPoint.x = 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
sourceRect.height += destPoint.y;
sourceRect.y -= destPoint.y;
destPoint.y = 0;
}
// TODO: Optimize
if (sourceImage == this) {
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);

View File

@@ -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;
}

View File

@@ -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 (sourceFormat == destFormat && sourcePremultiplied == destPremultiplied && sourceBytesPerPixel == destBytesPerPixel) {
if (!useAlphaImage) {
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;
}