pictw_t * simg_load_icon(session_t *ps, Window wid, int desired_size) { pictw_t *pictw = NULL; bool processed = false; { // _NET_WM_ICON int best_width = 0, best_height = 0; float best_scale = 1.0f, best_area = 0.0f; const unsigned char *best_data = NULL; winprop_t prop = wid_get_prop_adv(ps, wid, _NET_WM_ICON, 0, ICON_PROP_MAXLEN, XA_CARDINAL, 32); if (prop.nitems) { int width = 0, height = 0; const long *end = prop.data32 + prop.nitems; // Format: WIDTH HEIGHT DATA (32-bit) int wanted_bytes = 0; for (const long *p = prop.data32; p < end; p += wanted_bytes) { if (p + 2 >= end) { printfef("(%#010lx): %d trailing byte(s).", wid, (int) (end - p)); break; } width = p[0]; height = p[1]; if (width <= 0 || height <= 0) { printfef("(%#010lx): (offset %d, width %d, height %d) Invalid width/height.", wid, (int) (p - prop.data32), width, height); break; } wanted_bytes = 2 + width * height; if ((end - p) < wanted_bytes) { printfef("(%#010lx): (offset %d, width %d, height %d) Not enough bytes (%d/%d).", wid, (int) (p - prop.data32), width, height, (int) (end - p), wanted_bytes); break; } // Prefer larger ones if possible if (best_width >= desired_size && best_height >= desired_size && (width < desired_size || height < desired_size)) continue; float scale = MAX(1.0f, MIN((float) best_height / height, (float) best_width / width)); float area = width * height * scale * scale; if (area > best_area) { best_width = width; best_height = height; best_scale = scale; best_area = area; best_data = (const unsigned char *) (p + 2); } } } if (best_data) { { unsigned char *converted_data = simg_data32_from_long( (const long *) best_data, best_width * best_height); simg_data32_premultiply(converted_data, best_width * best_height); pictw = simg_data_to_pictw(ps, best_width, best_height, 32, converted_data, 0); if (converted_data != best_data) free(converted_data); } if (!pictw) printfef("(%#010lx): Failed to create picture.", wid); /* if (pictw) printfdf("(%#010lx): (offset %d, width %d, height %d) Loaded.", wid, (int) (best_data - prop.data8), pictw->width, pictw->height); */ } free_winprop(&prop); } if (pictw) goto simg_load_icon_end; // WM_HINTS // Our method probably fills 1-8 bit pixmaps as black instead of using // "suitable background/foreground". I hope it isn't a problem, though. { XWMHints *h = XGetWMHints(ps->dpy, wid); if (h && (IconPixmapHint & h->flags) && h->icon_pixmap) pictw = simg_pixmap_to_pictw(ps, 0, 0, 0, h->icon_pixmap, h->icon_mask); sxfree(h); } if (pictw) goto simg_load_icon_end; // KWM_WIN_ICON // Same issue as above. { winprop_t prop = wid_get_prop_adv(ps, wid, KWM_WIN_ICON, 0, 2, KWM_WIN_ICON, 32); if (prop.nitems) { Pixmap pxmap = prop.data32[0], mask = (prop.nitems >= 2 ? prop.data32[1]: None); if (pxmap) pictw = simg_pixmap_to_pictw(ps, 0, 0, 0, pxmap, mask); } free_winprop(&prop); } if (pictw) goto simg_load_icon_end; simg_load_icon_end: // Post-processing if (pictw && !processed) { pictw = simg_postprocess(ps, pictw, PICTPOSP_SCALEK, desired_size, desired_size, ALIGN_MID, ALIGN_MID, NULL); /* printfdf("(%#010lx): (width %d, height %d) Processed.", wid, pictw->width, pictw->height); */ } return pictw; }
pictw_t * sgif_read(session_t *ps, const char *path) { assert(path); pictw_t *pictw = NULL; GifPixelType *data = NULL; unsigned char *tdata = NULL; GifRecordType rectype; int ret = 0, err = 0; const char *errstr = NULL; #ifdef SGIF_THREADSAFE GifFileType *f = DGifOpenFileName(path, &err); #else GifFileType *f = DGifOpenFileName(path); #endif if (unlikely(!f)) { #ifdef SGIF_HAS_ERROR err = GifError(); errstr = GifErrorString(); #endif #ifdef SGIF_THREADSAFE errstr = GifErrorString(err); #endif printfef("(\"%s\"): Failed to open file: %d (%s)", path, err, errstr); goto sgif_read_end; } int width = 0, height = 0, transp = -1; { int i = 0; while (GIF_OK == (ret = DGifGetRecordType(f, &rectype)) && TERMINATE_RECORD_TYPE != rectype) { ++i; switch (rectype) { case UNDEFINED_RECORD_TYPE: printfef("(\"%s\"): %d: Encountered a record of unknown type.", path, i); break; case SCREEN_DESC_RECORD_TYPE: printfef("(\"%s\"): %d: Encountered a record of " "ScreenDescRecordType. This shouldn't happen!", path, i); break; case IMAGE_DESC_RECORD_TYPE: if (data) { printfef("(\"%s\"): %d: Extra image section ignored.", path, i); break; } if (GIF_ERROR == DGifGetImageDesc(f)) { printfef("(\"%s\"): %d: Failed to read GIF image info.", path, i); break; } width = f->Image.Width; height = f->Image.Height; if (width <= 0 || height <= 0) { printfef("(\"%s\"): %d: Width/height invalid.", path, i); break; } assert(!data); data = allocchk(malloc(width * height * sizeof(GifPixelType))); // FIXME: Interlace images may need special treatments for (int j = 0; j < height; ++j) if (GIF_OK != DGifGetLine(f, &data[j * width], width)) { printfef("(\"%s\"): %d: Failed to read line %d.", path, i, j); goto sgif_read_end; } break; case EXTENSION_RECORD_TYPE: { int code = 0; GifByteType *pbytes = NULL; if (GIF_OK != DGifGetExtension(f, &code, &pbytes) || !pbytes) { printfef("(\"%s\"): %d: Failed to read extension block.", path, i); break; } do { // Transparency if (0xf9 == code && (pbytes[1] & 1)) transp = pbytes[4]; } while (GIF_OK == DGifGetExtensionNext(f, &pbytes) && pbytes); } break; case TERMINATE_RECORD_TYPE: assert(0); break; } } if (unlikely(!data)) { printfef("(\"%s\"): No valid data found.", path); goto sgif_read_end; } } // Colormap translation int depth = 32; { ColorMapObject *cmap = f->Image.ColorMap; if (!cmap) cmap = f->SColorMap; if (unlikely(!cmap)) { printfef("(\"%s\"): No colormap found.", path); goto sgif_read_end; } tdata = allocchk(malloc(width * height * depth / 8)); { GifPixelType *pd = data; unsigned char *end = tdata + width * height * depth / 8; for (unsigned char *p = tdata; p < end; p += depth / 8, ++pd) { // When the alpha is 0, X seemingly wants all color channels // to be 0 as well. if (transp >= 0 && transp == *pd) { p[0] = p[1] = p[2] = 0; if (32 == depth) p[3] = 0; continue; } p[0] = cmap->Colors[*pd].Blue; p[1] = cmap->Colors[*pd].Green; p[2] = cmap->Colors[*pd].Red; p[3] = 0xff; } } } if (unlikely(!(pictw = simg_data_to_pictw(ps, width, height, depth, tdata, 0)))) { printfef("(\"%s\"): Failed to create Picture.", path); goto sgif_read_end; } sgif_read_end: if (data) free(data); if (likely(f)) DGifCloseFile(f); return pictw; }
pictw_t * sjpg_read(session_t *ps, const char *path) { pictw_t *pictw = NULL; JSAMPLE *data = NULL; bool need_abort = false; struct jpeg_error_mgr jerr; struct jpeg_decompress_struct cinfo = { .err = jpeg_std_error(&jerr), }; jpeg_create_decompress(&cinfo); FILE *fp = fopen(path, "rb"); if (unlikely(!fp)) { printfef("(\"%s\"): Failed to open file.", path); goto sjpg_read_end; } jpeg_stdio_src(&cinfo, fp); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); need_abort = true; int width = 0, height = 0, depth = 24; { const int comps = 4; data = allocchk(malloc(cinfo.output_width * cinfo.output_height * comps * sizeof(JSAMPLE))); JSAMPROW rowptrs[cinfo.output_height]; for (int i = 0; i < cinfo.output_height; ++i) rowptrs[i] = data + i * cinfo.output_width * comps; width = cinfo.output_width; height = cinfo.output_height; // libjpeg enforces a loop to read scanlines while (cinfo.output_scanline < cinfo.output_height) { if (unlikely(!jpeg_read_scanlines(&cinfo, &rowptrs[cinfo.output_scanline], cinfo.output_height - cinfo.output_scanline))) { printfef("(\"%s\"): Failed to read scanline %d.", path, cinfo.output_scanline); goto sjpg_read_end; } } // Expand greyscale value to RGBA if (1 == cinfo.output_components) { for (int i = 0; i < height; ++i) for (int j = width - 1; j >= 0; --j) { unsigned char a = rowptrs[i][j]; rowptrs[i][j * comps + 0] = a; rowptrs[i][j * comps + 1] = a; rowptrs[i][j * comps + 2] = a; rowptrs[i][j * comps + 3] = 0xff; } } else if (3 == cinfo.output_components) { for (int i = 0; i < height; ++i) { simg_data24_fillalpha(&data[i * 4 * width], width); simg_data24_tobgr(&data[i * 4 * width], width); } } } if (unlikely(!jpeg_finish_decompress(&cinfo))) { printfef("(\"%s\"): Failed to finish decompression.", path); goto sjpg_read_end; } need_abort = false; pictw = simg_data_to_pictw(ps, width, height, depth, data, 0); free(data); if (unlikely(!pictw)) { printfef("(\"%s\"): Failed to create Picture.", path); goto sjpg_read_end; } sjpg_read_end: /* if (unlikely(data && !pictw)) free(data); */ if (unlikely(need_abort)) jpeg_abort_decompress(&cinfo); if (likely(fp)) fclose(fp); jpeg_destroy_decompress(&cinfo); return pictw; }
pictw_t * spng_read(session_t *ps, const char *path) { assert(path); char sig[SPNG_SIGBYTES] = ""; pictw_t *pictw = NULL; png_structp png_ptr = NULL; png_infop info_ptr = NULL; FILE *fp = fopen(path, "rb"); bool need_premultiply = false; if (unlikely(!fp)) { printfef("(\"%s\"): Failed to open file.", path); goto spng_read_end; } if (unlikely(SPNG_SIGBYTES != fread(&sig, 1, SPNG_SIGBYTES, fp))) { printfef("(\"%s\"): Failed to read %d-byte signature.", path, SPNG_SIGBYTES); goto spng_read_end; } if (unlikely(png_sig_cmp((png_bytep) sig, 0, SPNG_SIGBYTES))) { printfef("(\"%s\"): PNG signature invalid.", path); goto spng_read_end; } png_ptr = allocchk(png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)); info_ptr = allocchk(png_create_info_struct(png_ptr)); if (setjmp(png_jmpbuf(png_ptr))) goto spng_read_end; png_init_io(png_ptr, fp); png_set_sig_bytes(png_ptr, SPNG_SIGBYTES); png_read_info(png_ptr, info_ptr); png_uint_32 width = 0, height = 0; // Set transformations int bit_depth = 0, color_type = 0; { int interlace_type = 0; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); // Scale or strip 16-bit colors if (bit_depth == 16) { printfdf("(\"%s\"): Scaling 16-bit colors.", path); #if PNG_LIBPNG_VER >= 10504 png_set_scale_16(png_ptr); #else png_set_strip_16(png_ptr); #endif bit_depth = 8; } /* if (bit_depth < 8) png_set_packing(png_ptr); */ // No idea why this is needed... png_set_bgr(png_ptr); // Convert palette to RGB if (color_type == PNG_COLOR_TYPE_PALETTE) { printfdf("(\"%s\"): Converting palette PNG to RGB.", path); png_set_palette_to_rgb(png_ptr); color_type = PNG_COLOR_TYPE_RGB; } if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { printfdf("(\"%s\"): Converting rDNS to full alpha.", path); png_set_tRNS_to_alpha(png_ptr); } if (color_type == PNG_COLOR_TYPE_GRAY || PNG_COLOR_TYPE_GRAY_ALPHA == color_type) { printfdf("(\"%s\"): Converting gray (+ alpha) PNG to RGB.", path); png_set_gray_to_rgb(png_ptr); if (PNG_COLOR_TYPE_GRAY == color_type) color_type = PNG_COLOR_TYPE_RGB; else color_type = PNG_COLOR_TYPE_RGB_ALPHA; } /* if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { printfdf("(\"%s\"): Converting 1/2/4 bit gray PNG to 8-bit.", path); #if PNG_LIBPNG_VER >= 10209 png_set_expand_gray_1_2_4_to_8(png_ptr); #else png_set_gray_1_2_4_to_8(png_ptr); #endif bit_depth = 8; } */ // Somehow XImage requires 24-bit visual to use 32 bits per pixel if (color_type == PNG_COLOR_TYPE_RGB) { printfdf("(\"%s\"): Appending filler alpha values.", path); png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER); } // Premultiply alpha if (PNG_COLOR_TYPE_RGB_ALPHA == color_type) { #if PNG_LIBPNG_VER >= 10504 png_set_alpha_mode(png_ptr, PNG_ALPHA_STANDARD, 1.0); #else need_premultiply = true; #endif } /* int number_passes = 1; #ifdef PNG_READ_INTERLACING_SUPPORTED number_passes = png_set_interlace_handling(png_ptr); #endif */ if (PNG_INTERLACE_NONE != interlace_type) png_set_interlace_handling(png_ptr); } png_read_update_info(png_ptr, info_ptr); int depth = 0; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); switch (color_type) { case PNG_COLOR_TYPE_GRAY: depth = 1 * bit_depth; break; case PNG_COLOR_TYPE_RGB: depth = 3 * bit_depth; break; case PNG_COLOR_TYPE_RGB_ALPHA: depth = 4 * bit_depth; break; default: assert(0); break; } // Read data and fill to Picture { int rowbytes = png_get_rowbytes(png_ptr, info_ptr); png_bytep row_pointers[height]; memset(row_pointers, 0, sizeof(row_pointers)); row_pointers[0] = png_malloc(png_ptr, rowbytes * height); for (int row = 1; row < height; row++) row_pointers[row] = row_pointers[row - 1] + rowbytes; png_read_image(png_ptr, row_pointers); if (need_premultiply) for (int row = 0; row < height; row++) simg_data32_premultiply(row_pointers[row], width); if (unlikely(!(pictw = simg_data_to_pictw(ps, width, height, depth, row_pointers[0], rowbytes)))) { printfef("(\"%s\"): Failed to create Picture.", path); goto spng_read_end; } } spng_read_end: if (png_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if (fp) fclose(fp); return pictw; }