commit 8678e0ad6794b7787945a34c6178b72e1bf72056
parent 4318dbe05142dd7229d4b44565b482ed50a7c381
Author: William Osler <william@oslers.us>
Date: Thu, 7 Dec 2017 21:09:20 -0800
add support for JPEG images
Diffstat:
6 files changed, 166 insertions(+), 4 deletions(-)
diff --git a/Makefile.am b/Makefile.am
@@ -22,7 +22,8 @@ i3lock_CFLAGS = \
$(XKBCOMMON_CFLAGS) \
$(CAIRO_CFLAGS) \
$(CODE_COVERAGE_CFLAGS) \
- $(X11_CFLAGS)
+ $(X11_CFLAGS) \
+ $(JPEG_CFLAGS)
i3lock_CPPFLAGS = \
$(AM_CPPFLAGS) \
@@ -35,7 +36,8 @@ i3lock_LDADD = \
$(XKBCOMMON_LIBS) \
$(CAIRO_LIBS) \
$(CODE_COVERAGE_LDFLAGS) \
- $(X11_LIBS)
+ $(X11_LIBS) \
+ $(JPEG_LIBS)
i3lock_SOURCES = \
cursors.h \
@@ -51,7 +53,9 @@ i3lock_SOURCES = \
tinyexpr.h \
blur_simd.c \
blur.c \
- blur.h
+ blur.h \
+ jpg.c \
+ jpg.h
EXTRA_DIST = \
diff --git a/README.md b/README.md
@@ -90,6 +90,7 @@ i3lock now uses GNU autotools for building; you'll need to do something like `au
- libx11-xcb-dev
- libxkbcommon >= 0.5.0
- libxkbcommon-x11 >= 0.5.0
+- libjpeg-turbo >= 1.4.90
(On centos/RHEL/etc, the packages tend to look like `cairo-devel` instead of `libcairo-dev`. Use `yum provides \*/<header.h>` to figure out what packages you need.)
@@ -97,6 +98,8 @@ i3lock now uses GNU autotools for building; you'll need to do something like `au
sudo apt-get install pkg-config libxcb1 libpam-dev libcairo-dev libxcb-composite0 libxcb-composite0-dev libxcb-xinerama0-dev libev-dev libx11-dev libx11-xcb-dev libxkbcommon0 libxkbcommon-x11-0 libxcb-dpms0-dev libxcb-image0-dev libxcb-util0-dev libxcb-xkb-dev libxkbcommon-x11-dev libxkbcommon-dev
+For JPEG lock image support, you'll also need `libjpeg-turbo8` version 1.4.90 or newer (Ubuntu 17.04 or later)
+
##### Aur Package
[Stable](https://aur.archlinux.org/packages/i3lock-color/)
diff --git a/configure.ac b/configure.ac
@@ -88,6 +88,7 @@ PKG_CHECK_MODULES([XCB_UTIL], [xcb-event xcb-util xcb-atom])
PKG_CHECK_MODULES([XKBCOMMON], [xkbcommon xkbcommon-x11])
PKG_CHECK_MODULES([CAIRO], [cairo])
PKG_CHECK_MODULES([X11], [x11])
+PKG_CHECK_MODULES([JPEG], [libjpeg])
# Checks for programs.
diff --git a/i3lock.c b/i3lock.c
@@ -20,6 +20,7 @@
#include <xcb/xcb.h>
#include <xcb/xkb.h>
#include <err.h>
+#include <errno.h>
#include <assert.h>
#ifdef __OpenBSD__
#include <bsd_auth.h>
@@ -50,6 +51,7 @@
#include "unlock_indicator.h"
#include "randr.h"
#include "blur.h"
+#include "jpg.h"
#define TSTAMP_N_SECS(n) (n * 1.0)
#define TSTAMP_N_MINS(n) (60 * TSTAMP_N_SECS(n))
@@ -988,6 +990,7 @@ int main(int argc, char *argv[]) {
struct passwd *pw;
char *username;
char *image_path = NULL;
+ JPEG_INFO jpg_info;
#ifndef __OpenBSD__
int ret;
struct pam_conv conv = {conv_callback, NULL};
@@ -1604,7 +1607,20 @@ int main(int argc, char *argv[]) {
if (image_path) {
/* Create a pixmap to render on, fill it with the background color */
- img = cairo_image_surface_create_from_png(image_path);
+ if (file_is_jpg(image_path)) {
+ if (debug_mode) {
+ fprintf(stderr, "Image looks like a jpeg, decoding\n");
+ }
+
+ unsigned char* jpg_data = read_JPEG_file(image_path, &jpg_info);
+ if (jpg_data != NULL) {
+ img = cairo_image_surface_create_for_data(jpg_data,
+ CAIRO_FORMAT_ARGB32, jpg_info.width, jpg_info.height,
+ jpg_info.stride);
+ }
+ } else {
+ img = cairo_image_surface_create_from_png(image_path);
+ }
/* In case loading failed, we just pretend no -i was specified. */
if (cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "Could not load image \"%s\": %s\n",
diff --git a/jpg.c b/jpg.c
@@ -0,0 +1,117 @@
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <err.h>
+#include <errno.h>
+#include <cairo.h>
+#include <jpeglib.h>
+
+#include "jpg.h"
+
+/*
+ * Checks if the file is a JPEG by looking for a valid JPEG header.
+ */
+bool file_is_jpg(char* file_path) {
+ FILE* image_file;
+ uint16_t file_header;
+ size_t read_count;
+ // TODO: Consider endianess on non-x86 platforms
+ uint16_t jpg_magick = 0xd8ff;
+
+ image_file = fopen(file_path, "rb");
+ if (image_file == NULL) {
+ int img_err = errno;
+ fprintf(stderr, "Could not open image file %s: %s\n",
+ file_path, strerror(img_err));
+ return false;
+ }
+
+ read_count = fread(&file_header, sizeof(file_header), 1, image_file);
+ fclose(image_file);
+
+ if (read_count < 1) {
+ fprintf(stderr, "Error searching for JPEG header in %s\n", file_path);
+ return false;
+ }
+
+ return file_header == jpg_magick;
+}
+
+/*
+ * Reads a JPEG from a file into memory, in a format that Cairo can create a
+ * surface from.
+ */
+void* read_JPEG_file(char *file_path, JPEG_INFO *jpg_info) {
+ int img_err;
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ FILE *infile; /* source file */
+ void *img; /* decompressed image data pointer */
+
+ if ((infile = fopen(file_path, "rb")) == NULL) {
+ img_err = errno;
+ fprintf(stderr, "Could not open image file %s: %s\n",
+ file_path, strerror(img_err));
+ return NULL;
+ }
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_stdio_src(&cinfo, infile);
+
+ (void) jpeg_read_header(&cinfo, TRUE);
+
+ // BGRA + endianness change = RGBA?
+ // TODO: Test this code on non-x86_64 platforms
+ cinfo.out_color_space = JCS_EXT_BGRA;
+
+ (void) jpeg_start_decompress(&cinfo);
+
+ jpg_info->height = cinfo.output_height;
+ jpg_info->width = cinfo.output_width;
+
+ /* Get the *cairo* stride rather than the stride from the image. This is
+ * the space needed in memory for each row for optimized Cairo rendering. */
+ int cairo_stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32,
+ cinfo.output_width);
+ jpg_info->stride = cairo_stride;
+ if (cairo_stride < jpg_info->width) {
+ /* This should never happen, but if it does then the following code
+ * will potentially write into unallocated memory */
+ fprintf(
+ stderr,
+ "WARNING: Cairo stride shorter than JPEG width. Aborting JPEG read."
+ );
+ return NULL;
+ }
+
+ // Allocate storage for the final, decompressed image.
+ img = calloc(cairo_stride, cinfo.output_height);
+ if (img == NULL) {
+ fprintf(stderr, "Could not allocate memory for JPEG decode\n");
+
+ (void) jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ fclose(infile);
+
+ return NULL;
+ }
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ /* Normally, you would allocate a buffer using libJPEG's memory
+ * management and write into it, but since we're reading one row at a
+ * time, we just write it directly into the image memory space */
+ unsigned char* pos = img + (cairo_stride * (cinfo.output_scanline));
+ (void) jpeg_read_scanlines(&cinfo, &pos, 1);
+ }
+
+ (void) jpeg_finish_decompress(&cinfo);
+
+ jpeg_destroy_decompress(&cinfo);
+ fclose(infile);
+
+ return img;
+}
diff --git a/jpg.h b/jpg.h
@@ -0,0 +1,21 @@
+#ifndef _JPG_H
+#define _JPG_H
+
+typedef struct {
+ uint height;
+ uint width;
+ uint stride; // The width of each row in memory, in bytes
+} JPEG_INFO;
+
+/*
+ * Checks if the file is a JPEG by looking for a valid JPEG header.
+ */
+bool file_is_jpg(char* file_path);
+
+/*
+ * Reads a JPEG from a file into memory, in a format that Cairo can create a
+ * surface from.
+ */
+void* read_JPEG_file(char *filename, JPEG_INFO *jpg_info);
+
+#endif