diff --git a/src/textures.c b/src/textures.c index 1ad5ef527..aa74eddaf 100644 --- a/src/textures.c +++ b/src/textures.c @@ -2493,128 +2493,241 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) || (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; - // Security checks to avoid size and rectangle issues (out of bounds) - // Check that srcRec is inside src image - if (srcRec.x < 0) srcRec.x = 0; - if (srcRec.y < 0) srcRec.y = 0; - - if ((srcRec.x + srcRec.width) > src.width) + if (dst->mipmaps > 1) TRACELOG(LOG_WARNING, "Image drawing only applied to base mipmap level"); + if (dst->format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image drawing not supported for compressed formats"); + else { - srcRec.width = src.width - srcRec.x; - TRACELOG(LOG_WARNING, "IMAGE: Source rectangle width out of bounds, rescaled width: %i", srcRec.width); - } + // Despite all my efforts for optimization, original implementation is faster... + // I left here other implementations for future reference +#define IMAGEDRAW_METHOD01 +#if defined(IMAGEDRAW_METHOD01) + // Security checks to avoid size and rectangle issues (out of bounds) + // Check that srcRec is inside src image + if (srcRec.x < 0) srcRec.x = 0; + if (srcRec.y < 0) srcRec.y = 0; + + if ((srcRec.x + srcRec.width) > src.width)srcRec.width = src.width - srcRec.x; + if ((srcRec.y + srcRec.height) > src.height) srcRec.height = src.height - srcRec.y; + + Image srcMod = ImageCopy(src); // Make a copy of source image to work with it - if ((srcRec.y + srcRec.height) > src.height) - { - srcRec.height = src.height - srcRec.y; - TRACELOG(LOG_WARNING, "IMAGE: Source rectangle height out of bounds, rescaled height: %i", srcRec.height); - } - - // TODO: OPTIMIZATION: Avoid ImageCrop(), not required, it should be enough to know the src/dst rectangles to copy data + // Crop source image to desired source rectangle (if required) + if ((src.width != (int)srcRec.width) && (src.height != (int)srcRec.height)) + { + ImageCrop(&srcMod, srcRec); + } - Image srcCopy = ImageCopy(src); // Make a copy of source image to work with it + // Scale source image in case destination rec size is different than source rec size + if (((int)dstRec.width != (int)srcRec.width) || ((int)dstRec.height != (int)srcRec.height)) + { + ImageResize(&srcMod, (int)dstRec.width, (int)dstRec.height); + } - // Crop source image to desired source rectangle (if required) - if ((src.width != (int)srcRec.width) && (src.height != (int)srcRec.height)) - { - ImageCrop(&srcCopy, srcRec); - } + // Check that dstRec is inside dst image + // Allow negative position within destination with cropping + if (dstRec.x < 0) + { + ImageCrop(&srcMod, (Rectangle) { -dstRec.x, 0, dstRec.width + dstRec.x, dstRec.height }); + dstRec.width = dstRec.width + dstRec.x; + dstRec.x = 0; + } - // Scale source image in case destination rec size is different than source rec size - if (((int)dstRec.width != (int)srcRec.width) || ((int)dstRec.height != (int)srcRec.height)) - { - ImageResize(&srcCopy, (int)dstRec.width, (int)dstRec.height); - } + if ((dstRec.x + dstRec.width) > dst->width) + { + ImageCrop(&srcMod, (Rectangle) { 0, 0, dst->width - dstRec.x, dstRec.height }); + dstRec.width = dst->width - dstRec.x; + } - // Check that dstRec is inside dst image - // Allow negative position within destination with cropping - if (dstRec.x < 0) - { - ImageCrop(&srcCopy, (Rectangle) { -dstRec.x, 0, dstRec.width + dstRec.x, dstRec.height }); - dstRec.width = dstRec.width + dstRec.x; - dstRec.x = 0; - } + if (dstRec.y < 0) + { + ImageCrop(&srcMod, (Rectangle) { 0, -dstRec.y, dstRec.width, dstRec.height + dstRec.y }); + dstRec.height = dstRec.height + dstRec.y; + dstRec.y = 0; + } - if ((dstRec.x + dstRec.width) > dst->width) - { - ImageCrop(&srcCopy, (Rectangle) { 0, 0, dst->width - dstRec.x, dstRec.height }); - dstRec.width = dst->width - dstRec.x; - } + if ((dstRec.y + dstRec.height) > dst->height) + { + ImageCrop(&srcMod, (Rectangle) { 0, 0, dstRec.width, dst->height - dstRec.y }); + dstRec.height = dst->height - dstRec.y; + } - if (dstRec.y < 0) - { - ImageCrop(&srcCopy, (Rectangle) { 0, -dstRec.y, dstRec.width, dstRec.height + dstRec.y }); - dstRec.height = dstRec.height + dstRec.y; - dstRec.y = 0; - } + // Get image data as Color pixels array to work with it + Color *dstPixels = GetImageData(*dst); + Color *srcPixels = GetImageData(srcMod); - if ((dstRec.y + dstRec.height) > dst->height) - { - ImageCrop(&srcCopy, (Rectangle) { 0, 0, dstRec.width, dst->height - dstRec.y }); - dstRec.height = dst->height - dstRec.y; - } + UnloadImage(srcMod); // Source copy not required any more - // Get image data as Color pixels array to work with it - Color *dstPixels = GetImageData(*dst); - Color *srcPixels = GetImageData(srcCopy); + Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) + Vector4 ftint = ColorNormalize(tint); // Normalized color tint - UnloadImage(srcCopy); // Source copy not required any more + // Blit pixels, copy source image into destination + for (int j = (int)dstRec.y; j < (int)(dstRec.y + dstRec.height); j++) + { + for (int i = (int)dstRec.x; i < (int)(dstRec.x + dstRec.width); i++) + { + // Alpha blending (https://en.wikipedia.org/wiki/Alpha_compositing) - Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) - Vector4 ftint = ColorNormalize(tint); // Normalized color tint + fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]); + fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); - // Blit pixels, copy source image into destination - for (int j = (int)dstRec.y; j < (int)(dstRec.y + dstRec.height); j++) - { - for (int i = (int)dstRec.x; i < (int)(dstRec.x + dstRec.width); i++) - { - // Alpha blending (https://en.wikipedia.org/wiki/Alpha_compositing) + // Apply color tint to source image + fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; - fdst = ColorNormalize(dstPixels[j*(int)dst->width + i]); - fsrc = ColorNormalize(srcPixels[(j - (int)dstRec.y)*(int)dstRec.width + (i - (int)dstRec.x)]); + fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); - // Apply color tint to source image - fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; + if (fout.w <= 0.0f) + { + fout.x = 0.0f; + fout.y = 0.0f; + fout.z = 0.0f; + } + else + { + fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; + fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; + fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; + } - fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); + dstPixels[j*(int)dst->width + i] = (Color){ (unsigned char)(fout.x*255.0f), + (unsigned char)(fout.y*255.0f), + (unsigned char)(fout.z*255.0f), + (unsigned char)(fout.w*255.0f) }; - if (fout.w <= 0.0f) - { - fout.x = 0.0f; - fout.y = 0.0f; - fout.z = 0.0f; - } - else - { - fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; - fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; - fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; + // TODO: Support other blending options } + } - dstPixels[j*(int)dst->width + i] = (Color){ (unsigned char)(fout.x*255.0f), - (unsigned char)(fout.y*255.0f), - (unsigned char)(fout.z*255.0f), - (unsigned char)(fout.w*255.0f) }; + Image final = { + .data = dstPixels, + .width = dst->width, + .height = dst->height, + .format = UNCOMPRESSED_R8G8B8A8, + .mipmaps = 1 + }; + + // NOTE: dstPixels are free() inside ImageFormat() + ImageFormat(&final, dst->format); + + UnloadImage(*dst); + *dst = final; + + RL_FREE(srcPixels); +#elif defined(IMAGEDRAW_METHOD02) + Image srcMod = ImageCopy(src); // Make a copy of source image to work with it + ImageFormat(&srcMod, UNCOMPRESSED_R8G8B8A8); // Convert to R8G8B8A8 to help on blending + + // Source rectangle out-of-bounds security checks + if (srcRec.x < 0) { srcRec.width -= srcRec.x; srcRec.x = 0; } + if (srcRec.y < 0) { srcRec.height -= srcRec.y; srcRec.y = 0; } + if ((srcRec.x + srcRec.width) > src.width) srcRec.width = src.width - srcRec.x; + if ((srcRec.y + srcRec.height) > src.height) srcRec.height = src.height - srcRec.y; + + // Check if source rectangle needs to be resized to destination rectangle + // In that case, we make a copy of source and we apply all required transform + if ((srcRec.width != fabs(dstRec.width - dstRec.x)) || (srcRec.height != fabs(dstRec.height - dstRec.y))) + { + ImageCrop(&srcMod, srcRec); // Crop to source rectangle + ImageResize(&srcMod, (int)dstRec.width, (int)dstRec.height); // Resize to destination rectangle + srcRec = (Rectangle){ 0, 0, srcMod.width, srcMod.height }; + } + + // Check if destination format is different than source format and no source copy created yet + if (dst->format != src.format) + { + ImageCrop(&srcMod, srcRec); // Crop to source rectangle + srcRec = (Rectangle){ 0, 0, srcMod.width, srcMod.height }; + } - // TODO: Support other blending options + // Destination rectangle out-of-bounds security checks + if (dstRec.x < 0) + { + srcRec.x = -dstRec.x; + srcRec.width += dstRec.x; + dstRec.x = 0; } - } + else if ((dstRec.x + srcMod.width) > dst->width) srcRec.width = dst->width - dstRec.x; - Image final = { - .data = dstPixels, - .width = dst->width, - .height = dst->height, - .format = UNCOMPRESSED_R8G8B8A8, - .mipmaps = 1 - }; + if (dstRec.y < 0) + { + srcRec.y = -dstRec.y; + srcRec.height += dstRec.y; + dstRec.y = 0; + } + else if ((dstRec.y + srcMod.height) > dst->height) srcRec.height = dst->height - dstRec.y; - // NOTE: dstPixels are free() inside ImageFormat() - ImageFormat(&final, dst->format); - - UnloadImage(*dst); - *dst = final; + if (dst->width < srcRec.width) srcRec.width = dst->width; + if (dst->height < srcRec.height) srcRec.height = dst->height; + + #if defined(IMAGEDRAW_NO_BLENDING) + // This method is very fast but no pixels blending is considered + int dataSize = GetPixelDataSize(dst->width, dst->height, dst->format); + int bytesPerPixel = dataSize/(dst->width*dst->height); + + // Image blitting src -> destination, line by line + for (int y = 0; y < (int)srcRec.height; y++) + { + memcpy((unsigned char *)dst->data + ((int)dstRec.y*dst->width + (int)dstRec.x + y*dst->width)*bytesPerPixel, + (unsigned char *)srcMod.data + ((y + (int)srcRec.y)*srcMod.width + (int)srcRec.x)*bytesPerPixel, + (int)srcRec.width*bytesPerPixel); + } + #else + // This method is very slow considering alpha blending... + + // Convert destination to R8G8B8A8 for blending calculation + int dstFormat = dst->format; + ImageFormat(dst, UNCOMPRESSED_R8G8B8A8); // Force 4 bytes per pixel with alpha for blending + + Vector4 fsrc, fdst, fout; // Normalized pixel data (ready for operation) + Vector4 ftint = ColorNormalize(tint); // Normalized color tint + unsigned char srcAlpha = 0; + + for (int y = 0; y < (int)srcRec.height; y++) + { + for (int x = 0; x < (int)srcRec.width; x++) + { + srcAlpha = ((Color *)srcMod.data)[((y + (int)srcRec.y)*srcMod.width + (int)srcRec.x) + x].a; + + if (srcAlpha == 255) + { + ((Color *)dst->data)[((int)dstRec.y*dst->width + (int)dstRec.x) + y*(dst->width) + x] = ((Color *)srcMod.data)[((y + (int)srcRec.y)*srcMod.width + (int)srcRec.x) + x]; + } + else if (srcAlpha > 0) + { + // Alpha blending (https://en.wikipedia.org/wiki/Alpha_compositing) - RL_FREE(srcPixels); + fdst = ColorNormalize(((Color *)dst->data)[((int)dstRec.y*dst->width + (int)dstRec.x) + y*(dst->width) + x]); + fsrc = ColorNormalize(((Color *)srcMod.data)[((y + (int)srcRec.y)*srcMod.width + (int)srcRec.x) + x]); + + // Apply color tint to source image + fsrc.x *= ftint.x; fsrc.y *= ftint.y; fsrc.z *= ftint.z; fsrc.w *= ftint.w; + + fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); + + if (fout.w <= 0.0f) + { + fout.x = 0.0f; + fout.y = 0.0f; + fout.z = 0.0f; + } + else + { + fout.x = (fsrc.x*fsrc.w + fdst.x*fdst.w*(1 - fsrc.w))/fout.w; + fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; + fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; + } + + ((Color *)dst->data)[((int)dstRec.y*dst->width + (int)dstRec.x) + y*(dst->width) + x] = (Color){ (unsigned char)(fout.x*255.0f), (unsigned char)(fout.y*255.0f), (unsigned char)(fout.z*255.0f), (unsigned char)(fout.w*255.0f) }; + + // TODO: Support other blending options + } + } + } + + ImageFormat(dst, dstFormat); // Restore original image format after drawing with blending + UnloadImage(srcMod); // Unload source modified image + #endif +#endif + } } // Draw text (default font) within an image (destination)