diff --git a/lime/graphics/Image.hx b/lime/graphics/Image.hx index 801a2830f..0be333aa9 100644 --- a/lime/graphics/Image.hx +++ b/lime/graphics/Image.hx @@ -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); diff --git a/lime/graphics/utils/ImageDataUtil.hx b/lime/graphics/utils/ImageDataUtil.hx index aa721a386..dc838cd26 100644 --- a/lime/graphics/utils/ImageDataUtil.hx +++ b/lime/graphics/utils/ImageDataUtil.hx @@ -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; } diff --git a/project/src/graphics/utils/ImageDataUtil.cpp b/project/src/graphics/utils/ImageDataUtil.cpp index 3c55bbfa3..4a18bcb36 100644 --- a/project/src/graphics/utils/ImageDataUtil.cpp +++ b/project/src/graphics/utils/ImageDataUtil.cpp @@ -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; }