SDL_image: Added IMG_GetClipboardImage() and showclipboard example program

From a2e17f346595775f22a7c689fcf256228713ca9b Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 10 Oct 2025 17:31:15 -0700
Subject: [PATCH] Added IMG_GetClipboardImage() and showclipboard example
 program

---
 CMakeLists.txt                 |   1 +
 examples/showclipboard.c       | 160 +++++++++++++++++++++++++++++++++
 include/SDL3_image/SDL_image.h |  12 +++
 src/IMG.c                      |  26 ++++++
 src/SDL_image.sym              |   1 +
 5 files changed, 200 insertions(+)
 create mode 100644 examples/showclipboard.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ff74b40a..69db9866 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1283,6 +1283,7 @@ if(SDLIMAGE_SAMPLES)
     endfunction()
     add_sdl_image_example_executable(showanim examples/showanim.c)
     add_sdl_image_example_executable(showimage examples/showimage.c)
+    add_sdl_image_example_executable(showclipboard examples/showclipboard.c)
 endif()
 
 if(SDLIMAGE_TESTS)
diff --git a/examples/showclipboard.c b/examples/showclipboard.c
new file mode 100644
index 00000000..e2686a4d
--- /dev/null
+++ b/examples/showclipboard.c
@@ -0,0 +1,160 @@
+/*
+  showclipboard:  A test application for the SDL image loading library.
+  Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include <SDL3/SDL.h>
+#include <SDL3/SDL_main.h>
+#include <SDL3_image/SDL_image.h>
+
+
+static SDL_Texture *load_clipboard(SDL_Window *window, SDL_Renderer *renderer)
+{
+    SDL_Texture *texture = NULL;
+    SDL_Surface *surface = IMG_GetClipboardImage();
+    if (surface) {
+        char *text = SDL_GetClipboardText();
+        if (text && *text) {
+            SDL_SetWindowTitle(window, text);
+        } else {
+            SDL_SetWindowTitle(window, "Copy an image and click here");
+        }
+        SDL_free(text);
+
+        texture = SDL_CreateTextureFromSurface(renderer, surface);
+
+        SDL_SetWindowTitle(window, SDL_GetClipboardText());
+        SDL_SetWindowSize(window, surface->w, surface->h);
+        SDL_SetRenderLogicalPresentation(renderer, surface->w, surface->h, SDL_LOGICAL_PRESENTATION_LETTERBOX);
+
+        SDL_DestroySurface(surface);
+    }
+    return texture;
+}
+
+/* Draw a Gimpish background pattern to show transparency in the image */
+static void draw_background(SDL_Renderer *renderer)
+{
+    const SDL_Color col[2] = {
+        { 0x66, 0x66, 0x66, 0xff },
+        { 0x99, 0x99, 0x99, 0xff }
+    };
+    const int dx = 8, dy = 8;
+    SDL_FRect rect;
+    int i, x, y, w, h;
+
+    SDL_GetCurrentRenderOutputSize(renderer, &w, &h);
+
+    rect.w = (float)dx;
+    rect.h = (float)dy;
+    for (y = 0; y < h; y += dy) {
+        for (x = 0; x < w; x += dx) {
+            /* use an 8x8 checkerboard pattern */
+            i = (((x ^ y) >> 3) & 1);
+            SDL_SetRenderDrawColor(renderer, col[i].r, col[i].g, col[i].b, col[i].a);
+
+            rect.x = (float)x;
+            rect.y = (float)y;
+            SDL_RenderFillRect(renderer, &rect);
+        }
+    }
+}
+
+int main(int argc, char *argv[])
+{
+    SDL_Window *window = NULL;
+    SDL_Renderer *renderer = NULL;
+    SDL_Texture *texture = NULL;
+    Uint32 flags = 0;
+    int i;
+    bool done = false;
+    SDL_Event event;
+    int result = 0;
+
+    (void)argc;
+
+    /* Check command line usage */
+    for ( i=1; argv[i]; ++i ) {
+        if (SDL_strcmp(argv[i], "-fullscreen") == 0) {
+            SDL_HideCursor();
+            flags |= SDL_WINDOW_FULLSCREEN;
+        } else {
+            SDL_Log("Usage: %s [-fullscreen]\n", argv[0]);
+            result = 1;
+            goto done;
+        }
+    }
+
+    if (!SDL_Init(SDL_INIT_VIDEO)) {
+        SDL_Log("SDL_Init(SDL_INIT_VIDEO) failed: %s\n", SDL_GetError());
+        result = 2;
+        goto done;
+    }
+
+    if (!SDL_CreateWindowAndRenderer("", 640, 480, flags, &window, &renderer)) {
+        SDL_Log("SDL_CreateWindowAndRenderer() failed: %s\n", SDL_GetError());
+        result = 2;
+        goto done;
+    }
+
+    texture = load_clipboard(window, renderer);
+
+    while ( !done ) {
+        while (SDL_PollEvent(&event)) {
+            switch (event.type) {
+            case SDL_EVENT_CLIPBOARD_UPDATE:
+                texture = load_clipboard(window, renderer);
+                break;
+            case SDL_EVENT_KEY_UP:
+                switch (event.key.key) {
+                case SDLK_ESCAPE:
+                case SDLK_Q:
+                    done = 1;
+                    break;
+                }
+                break;
+            case SDL_EVENT_QUIT:
+                done = 1;
+                break;
+            default:
+                break;
+            }
+        }
+
+        /* Draw a background pattern in case the image has transparency */
+        draw_background(renderer);
+
+        /* Display the image */
+        if (texture) {
+            SDL_RenderTexture(renderer, texture, NULL, NULL);
+        }
+        SDL_RenderPresent(renderer);
+
+        SDL_Delay(100);
+    }
+
+    if (texture) {
+        SDL_DestroyTexture(texture);
+    }
+
+    /* We're done! */
+done:
+    SDL_Quit();
+    return result;
+}
diff --git a/include/SDL3_image/SDL_image.h b/include/SDL3_image/SDL_image.h
index 4f2a61ec..0254b2b9 100644
--- a/include/SDL3_image/SDL_image.h
+++ b/include/SDL3_image/SDL_image.h
@@ -363,6 +363,18 @@ extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTexture_IO(SDL_Renderer *rende
  */
 extern SDL_DECLSPEC SDL_Texture * SDLCALL IMG_LoadTextureTyped_IO(SDL_Renderer *renderer, SDL_IOStream *src, bool closeio, const char *type);
 
+/**
+ * Get the image currently in the clipboard.
+ *
+ * When done with the returned surface, the app should dispose of it with a
+ * call to SDL_DestroySurface().
+ *
+ * \returns a new SDL surface, or NULL if no supported image is available.
+ *
+ * \since This function is available since SDL_image 3.4.0.
+ */
+extern SDL_DECLSPEC SDL_Surface * SDLCALL IMG_GetClipboardImage(void);
+
 /**
  * Detect AVIF image data on a readable/seekable SDL_IOStream.
  *
diff --git a/src/IMG.c b/src/IMG.c
index 0aec229c..8572fcd9 100644
--- a/src/IMG.c
+++ b/src/IMG.c
@@ -411,6 +411,32 @@ bool IMG_SaveTyped_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, con
     return result;
 }
 
+SDL_Surface *IMG_GetClipboardImage(void)
+{
+    SDL_Surface *surface = NULL;
+
+    char **mime_types = SDL_GetClipboardMimeTypes(NULL);
+    if (mime_types) {
+        for (int i = 0; !surface && mime_types[i]; ++i) {
+            if (SDL_strncmp(mime_types[i], "image/", 6) == 0) {
+                size_t size = 0;
+                void *data = SDL_GetClipboardData(mime_types[i], &size);
+                if (data) {
+                    SDL_IOStream *src = SDL_IOFromConstMem(data, size);
+                    if (src) {
+                        surface = IMG_Load_IO(src, true);
+                    }
+                    SDL_free(data);
+                }
+            }
+        }
+    }
+    if (!surface) {
+        SDL_SetError("No clipboard image available");
+    }
+    return surface;
+}
+
 Uint64 IMG_TimebaseDuration(Uint64 pts, Uint64 duration, Uint64 src_numerator, Uint64 src_denominator, Uint64 dst_numerator, Uint64 dst_denominator)
 {
     Uint64 a = ( ( ( ( pts + duration ) * 2 ) + 1 ) * src_numerator * dst_denominator ) / ( 2 * src_denominator * dst_numerator );
diff --git a/src/SDL_image.sym b/src/SDL_image.sym
index eb570463..96234f4e 100644
--- a/src/SDL_image.sym
+++ b/src/SDL_image.sym
@@ -84,5 +84,6 @@ SDL3_image_0.0.0 {
     IMG_CloseAnimationDecoder;
     IMG_GetAnimationDecoderProperties;
     IMG_GetAnimationDecoderStatus;
+    IMG_GetClipboardImage;
   local: *;
 };