mirror of
https://github.com/labwc/labwc.git
synced 2025-11-14 06:59:54 -05:00
- fix that icons for normal/hovered/rounded buttons are not placed exactly the same position - fix blurry window button icons in scaled outputs This commit introduces lab_img and scaled_img_buffer and uses them for rendering icons in the window titlebar. Now the process of rendering button icons are split into 2 phases: loading with lab_img_load() and creating scene-nodes for them with scaled_img_buffer_create(). This might incur some additional overhead since we no longer preload icon textures, but the rendering of icon only happens for the first window as backing buffers are shared and the overhead won't be noticeable. This commit also simplifies the process of centering icon buffer in the button, by creating icon buffers in a fixed geometry via lab_img_render().
422 lines
9 KiB
C
422 lines
9 KiB
C
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
/*
|
|
* XPM image loader adapted from gdk-pixbuf
|
|
*
|
|
* Copyright (C) 1999 Mark Crichton
|
|
* Copyright (C) 1999 The Free Software Foundation
|
|
*
|
|
* Authors: Mark Crichton <crichton@gimp.org>
|
|
* Federico Mena-Quintero <federico@gimp.org>
|
|
*
|
|
* Adapted for labwc by John Lindgren, 2024
|
|
*/
|
|
|
|
#include <glib.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <wlr/util/log.h>
|
|
|
|
#include "buffer.h"
|
|
#include "common/buf.h"
|
|
#include "common/macros.h"
|
|
#include "common/mem.h"
|
|
#include "img/img-xpm.h"
|
|
|
|
#include "xpm-color-table.h"
|
|
|
|
enum buf_op { op_header, op_cmap, op_body };
|
|
|
|
struct xpm_color {
|
|
char *color_string;
|
|
uint32_t argb;
|
|
};
|
|
|
|
struct file_handle {
|
|
FILE *infile;
|
|
struct buf buf;
|
|
};
|
|
|
|
static inline uint32_t
|
|
make_argb(uint8_t a, uint8_t r, uint8_t g, uint8_t b)
|
|
{
|
|
return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
|
}
|
|
|
|
static int
|
|
compare_xcolor_entries(const void *a, const void *b)
|
|
{
|
|
return g_ascii_strcasecmp((const char *)a,
|
|
color_names + ((const struct xcolor_entry *)b)->name_offset);
|
|
}
|
|
|
|
static bool
|
|
lookup_named_color(const char *name, uint32_t *argb)
|
|
{
|
|
struct xcolor_entry *found = bsearch(name, xcolors, ARRAY_SIZE(xcolors),
|
|
sizeof(struct xcolor_entry), compare_xcolor_entries);
|
|
if (!found) {
|
|
return false;
|
|
}
|
|
|
|
*argb = make_argb(0xFF, found->red, found->green, found->blue);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
parse_color(const char *spec, uint32_t *argb)
|
|
{
|
|
if (spec[0] != '#') {
|
|
return lookup_named_color(spec, argb);
|
|
}
|
|
|
|
int red, green, blue;
|
|
switch (strlen(spec + 1)) {
|
|
case 3:
|
|
if (sscanf(spec + 1, "%1x%1x%1x", &red, &green, &blue) != 3) {
|
|
return false;
|
|
}
|
|
*argb = make_argb(255, (red * 255) / 15, (green * 255) / 15,
|
|
(blue * 255) / 15);
|
|
return true;
|
|
case 6:
|
|
if (sscanf(spec + 1, "%2x%2x%2x", &red, &green, &blue) != 3) {
|
|
return false;
|
|
}
|
|
*argb = make_argb(255, red, green, blue);
|
|
return true;
|
|
case 9:
|
|
if (sscanf(spec + 1, "%3x%3x%3x", &red, &green, &blue) != 3) {
|
|
return false;
|
|
}
|
|
*argb = make_argb(255, (red * 255) / 4095, (green * 255) / 4095,
|
|
(blue * 255) / 4095);
|
|
return true;
|
|
case 12:
|
|
if (sscanf(spec + 1, "%4x%4x%4x", &red, &green, &blue) != 3) {
|
|
return false;
|
|
}
|
|
*argb = make_argb(255, (red * 255) / 65535,
|
|
(green * 255) / 65535, (blue * 255) / 65535);
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
xpm_seek_string(FILE *infile, const char *str)
|
|
{
|
|
char instr[1024];
|
|
|
|
while (!feof(infile)) {
|
|
if (fscanf(infile, "%1023s", instr) < 0) {
|
|
return false;
|
|
}
|
|
if (strcmp(instr, str) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
xpm_seek_char(FILE *infile, char c)
|
|
{
|
|
int b, oldb;
|
|
|
|
while ((b = getc(infile)) != EOF) {
|
|
if (c != b && b == '/') {
|
|
b = getc(infile);
|
|
if (b == EOF) {
|
|
return false;
|
|
} else if (b == '*') { /* we have a comment */
|
|
b = -1;
|
|
do {
|
|
oldb = b;
|
|
b = getc(infile);
|
|
if (b == EOF) {
|
|
return false;
|
|
}
|
|
} while (!(oldb == '*' && b == '/'));
|
|
}
|
|
} else if (c == b) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
xpm_read_string(FILE *infile, struct buf *buf)
|
|
{
|
|
buf_clear(buf);
|
|
int c;
|
|
|
|
do {
|
|
c = getc(infile);
|
|
if (c == EOF) {
|
|
return false;
|
|
}
|
|
} while (c != '"');
|
|
|
|
while ((c = getc(infile)) != EOF) {
|
|
if (c == '"') {
|
|
return true;
|
|
}
|
|
buf_add_char(buf, c);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static uint32_t
|
|
xpm_extract_color(const char *buffer)
|
|
{
|
|
const char *p = buffer;
|
|
int new_key = 0;
|
|
int key = 0;
|
|
int current_key = 1;
|
|
char word[129], color[129], current_color[129];
|
|
char *r;
|
|
|
|
word[0] = '\0';
|
|
color[0] = '\0';
|
|
current_color[0] = '\0';
|
|
while (true) {
|
|
/* skip whitespace */
|
|
for (; *p != '\0' && g_ascii_isspace(*p); p++) {
|
|
/* nothing */
|
|
}
|
|
/* copy word */
|
|
for (r = word; *p != '\0' && !g_ascii_isspace(*p)
|
|
&& r - word < (int)sizeof(word) - 1;
|
|
p++, r++) {
|
|
*r = *p;
|
|
}
|
|
*r = '\0';
|
|
if (*word == '\0') {
|
|
if (color[0] == '\0') { /* incomplete colormap entry */
|
|
return 0;
|
|
} else { /* end of entry, still store the last color */
|
|
new_key = 1;
|
|
}
|
|
} else if (key > 0 && color[0] == '\0') {
|
|
/* next word must be a color name part */
|
|
new_key = 0;
|
|
} else {
|
|
if (strcmp(word, "c") == 0) {
|
|
new_key = 5;
|
|
} else if (strcmp(word, "g") == 0) {
|
|
new_key = 4;
|
|
} else if (strcmp(word, "g4") == 0) {
|
|
new_key = 3;
|
|
} else if (strcmp(word, "m") == 0) {
|
|
new_key = 2;
|
|
} else if (strcmp(word, "s") == 0) {
|
|
new_key = 1;
|
|
} else {
|
|
new_key = 0;
|
|
}
|
|
}
|
|
if (new_key == 0) { /* word is a color name part */
|
|
if (key == 0) { /* key expected */
|
|
return 0;
|
|
}
|
|
/* accumulate color name */
|
|
int len = strlen(color);
|
|
if (len && len < (int)sizeof(color) - 1) {
|
|
color[len++] = ' ';
|
|
}
|
|
g_strlcpy(color + len, word, sizeof(color) - len);
|
|
} else { /* word is a key */
|
|
if (key > current_key) {
|
|
current_key = key;
|
|
g_strlcpy(current_color, color, sizeof(current_color));
|
|
}
|
|
color[0] = '\0';
|
|
key = new_key;
|
|
if (*p == '\0') {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t argb;
|
|
if (current_key > 1 && (g_ascii_strcasecmp(current_color, "None") != 0)
|
|
&& parse_color(current_color, &argb)) {
|
|
return argb;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static const char *
|
|
file_buffer(enum buf_op op, struct file_handle *h)
|
|
{
|
|
switch (op) {
|
|
case op_header:
|
|
if (!xpm_seek_string(h->infile, "XPM")) {
|
|
break;
|
|
}
|
|
if (!xpm_seek_char(h->infile, '{')) {
|
|
break;
|
|
}
|
|
/* Fall through to the next xpm_seek_char. */
|
|
|
|
case op_cmap:
|
|
xpm_seek_char(h->infile, '"');
|
|
if (fseek(h->infile, -1, SEEK_CUR) != 0) {
|
|
return NULL;
|
|
}
|
|
/* Fall through to the xpm_read_string. */
|
|
|
|
case op_body:
|
|
if (!xpm_read_string(h->infile, &h->buf)) {
|
|
return NULL;
|
|
}
|
|
return h->buf.data;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static cairo_surface_t *
|
|
xpm_load_to_surface(struct file_handle *handle)
|
|
{
|
|
const char *buffer = file_buffer(op_header, handle);
|
|
if (!buffer) {
|
|
wlr_log(WLR_DEBUG, "No XPM header found");
|
|
return NULL;
|
|
}
|
|
|
|
int w, h, n_col, cpp, x_hot, y_hot;
|
|
int items = sscanf(buffer, "%d %d %d %d %d %d", &w, &h, &n_col, &cpp,
|
|
&x_hot, &y_hot);
|
|
|
|
if (items != 4 && items != 6) {
|
|
wlr_log(WLR_DEBUG, "Invalid XPM header");
|
|
return NULL;
|
|
}
|
|
|
|
if (w <= 0) {
|
|
wlr_log(WLR_DEBUG, "XPM file has image width <= 0");
|
|
return NULL;
|
|
}
|
|
if (h <= 0) {
|
|
wlr_log(WLR_DEBUG, "XPM file has image height <= 0");
|
|
return NULL;
|
|
}
|
|
/* Limits (width, height, colors) modified for labwc */
|
|
if (h > 1024 || w > 1024) {
|
|
wlr_log(WLR_DEBUG, "XPM file is larger than 1024x1024");
|
|
return NULL;
|
|
}
|
|
if (cpp <= 0 || cpp >= 32) {
|
|
wlr_log(WLR_DEBUG, "XPM has invalid number of chars per pixel");
|
|
return NULL;
|
|
}
|
|
if (n_col <= 0 || n_col > 1024) {
|
|
wlr_log(WLR_DEBUG, "XPM file has invalid number of colors");
|
|
return NULL;
|
|
}
|
|
|
|
/* The hash is used for fast lookups of color from chars */
|
|
GHashTable *color_hash = g_hash_table_new(g_str_hash, g_str_equal);
|
|
|
|
char *name_buf = xzalloc(n_col * (cpp + 1));
|
|
struct xpm_color *colors = znew_n(struct xpm_color, n_col);
|
|
cairo_surface_t *surface = NULL;
|
|
struct xpm_color *fallbackcolor = NULL;
|
|
char pixel_str[32]; /* cpp < 32 */
|
|
|
|
for (int cnt = 0; cnt < n_col; cnt++) {
|
|
buffer = file_buffer(op_cmap, handle);
|
|
if (!buffer) {
|
|
wlr_log(WLR_DEBUG, "Cannot read XPM colormap");
|
|
goto out;
|
|
}
|
|
|
|
struct xpm_color *color = &colors[cnt];
|
|
color->color_string = &name_buf[cnt * (cpp + 1)];
|
|
g_strlcpy(color->color_string, buffer, cpp + 1);
|
|
buffer += strlen(color->color_string);
|
|
|
|
color->argb = xpm_extract_color(buffer);
|
|
|
|
g_hash_table_insert(color_hash, color->color_string, color);
|
|
|
|
if (cnt == 0) {
|
|
fallbackcolor = color;
|
|
}
|
|
}
|
|
|
|
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
|
|
uint32_t *data = (uint32_t *)cairo_image_surface_get_data(surface);
|
|
int stride = cairo_image_surface_get_stride(surface) / sizeof(uint32_t);
|
|
|
|
for (int ycnt = 0; ycnt < h; ycnt++) {
|
|
uint32_t *pixtmp = data + stride * ycnt;
|
|
int wbytes = w * cpp;
|
|
|
|
buffer = file_buffer(op_body, handle);
|
|
if (!buffer || (strlen(buffer) < (size_t)wbytes)) {
|
|
/* Advertised width doesn't match pixels */
|
|
wlr_log(WLR_DEBUG, "Dimensions do not match data");
|
|
cairo_surface_destroy(surface);
|
|
surface = NULL;
|
|
goto out;
|
|
}
|
|
|
|
for (int n = 0, xcnt = 0; n < wbytes; n += cpp, xcnt++) {
|
|
g_strlcpy(pixel_str, &buffer[n], cpp + 1);
|
|
|
|
struct xpm_color *color =
|
|
g_hash_table_lookup(color_hash, pixel_str);
|
|
|
|
/* Bad XPM...punt */
|
|
if (!color) {
|
|
color = fallbackcolor;
|
|
}
|
|
|
|
*pixtmp++ = color->argb;
|
|
}
|
|
}
|
|
/* let cairo know pixel data has been modified */
|
|
cairo_surface_mark_dirty(surface);
|
|
|
|
out:
|
|
g_hash_table_destroy(color_hash);
|
|
free(colors);
|
|
free(name_buf);
|
|
return surface;
|
|
}
|
|
|
|
struct lab_data_buffer *
|
|
img_xpm_load(const char *filename)
|
|
{
|
|
struct file_handle h = {0};
|
|
h.infile = fopen(filename, "rb");
|
|
if (!h.infile) {
|
|
wlr_log(WLR_ERROR, "error opening '%s'", filename);
|
|
return NULL;
|
|
}
|
|
|
|
cairo_surface_t *surface = xpm_load_to_surface(&h);
|
|
struct lab_data_buffer *buffer = NULL;
|
|
if (surface) {
|
|
buffer = buffer_adopt_cairo_surface(surface);
|
|
} else {
|
|
wlr_log(WLR_ERROR, "error loading '%s'", filename);
|
|
}
|
|
|
|
fclose(h.infile);
|
|
buf_reset(&h.buf);
|
|
|
|
return buffer;
|
|
}
|