浏览代码

REVIEWED: ImageDraw(), optimizations test #1218

Despite all the effort put on function optimization, dealing with alpha blending is complex, considering src and dst could have different pixel format...
raysan5 5 年之前
父节点
当前提交
15bfe44e73
共有 1 个文件被更改,包括 211 次插入98 次删除
  1. 211 98
      src/textures.c

+ 211 - 98
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) ||
     if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0) ||
         (src.data == NULL) || (src.width == 0) || (src.height == 0)) return;
         (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)
 // Draw text (default font) within an image (destination)