// widgetImage.c — Image widget (displays bitmap, responds to clicks) #include "widgetInternal.h" #include "../thirdparty/stb_image.h" // ============================================================ // wgtImage // ============================================================ // // Create an image widget from raw pixel data already in display format. // Takes ownership of the data buffer (freed on destroy). WidgetT *wgtImage(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch) { WidgetT *wgt = widgetAlloc(parent, WidgetImageE); if (wgt) { wgt->as.image.data = data; wgt->as.image.imgW = w; wgt->as.image.imgH = h; wgt->as.image.imgPitch = pitch; wgt->as.image.pressed = false; } return wgt; } // ============================================================ // wgtImageFromFile // ============================================================ // // Load an image from a file (BMP, PNG, JPEG, GIF), convert to // display pixel format, and create an image widget. WidgetT *wgtImageFromFile(WidgetT *parent, const char *path) { if (!parent || !path) { return NULL; } // Find the AppContextT to get display format WidgetT *root = parent; while (root->parent) { root = root->parent; } AppContextT *ctx = (AppContextT *)root->userData; if (!ctx) { return NULL; } const DisplayT *d = &ctx->display; // Load image via stb_image (force RGB) int imgW; int imgH; int channels; uint8_t *rgb = stbi_load(path, &imgW, &imgH, &channels, 3); if (!rgb) { return NULL; } // Convert RGB to display pixel format int32_t bpp = d->format.bytesPerPixel; int32_t pitch = imgW * bpp; uint8_t *buf = (uint8_t *)malloc(pitch * imgH); if (!buf) { stbi_image_free(rgb); return NULL; } for (int32_t y = 0; y < imgH; y++) { for (int32_t x = 0; x < imgW; x++) { const uint8_t *src = rgb + (y * imgW + x) * 3; uint32_t color = packColor(d, src[0], src[1], src[2]); uint8_t *dst = buf + y * pitch + x * bpp; if (bpp == 1) { *dst = (uint8_t)color; } else if (bpp == 2) { *(uint16_t *)dst = (uint16_t)color; } else { *(uint32_t *)dst = color; } } } stbi_image_free(rgb); return wgtImage(parent, buf, imgW, imgH, pitch); } // ============================================================ // wgtImageSetData // ============================================================ void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) { if (!w || w->type != WidgetImageE) { return; } free(w->as.image.data); w->as.image.data = data; w->as.image.imgW = imgW; w->as.image.imgH = imgH; w->as.image.imgPitch = pitch; } // ============================================================ // widgetImageCalcMinSize // ============================================================ void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) { (void)font; w->calcMinW = w->as.image.imgW; w->calcMinH = w->as.image.imgH; } // ============================================================ // widgetImageOnMouse // ============================================================ void widgetImageOnMouse(WidgetT *hit) { hit->as.image.pressed = true; wgtInvalidate(hit); if (hit->onClick) { hit->onClick(hit); } hit->as.image.pressed = false; } // ============================================================ // widgetImagePaint // ============================================================ void widgetImagePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { (void)font; (void)colors; if (!w->as.image.data) { return; } // Center the image within the widget bounds int32_t imgW = w->as.image.imgW; int32_t imgH = w->as.image.imgH; int32_t dx = w->x + (w->w - imgW) / 2; int32_t dy = w->y + (w->h - imgH) / 2; // Offset by 1px when pressed (button-press effect) if (w->as.image.pressed) { dx++; dy++; } rectCopy(d, ops, dx, dy, w->as.image.data, w->as.image.imgPitch, 0, 0, imgW, imgH); }