i3lock-color

The world's most popular non-default computer lockscreen.
Index Commits Files Refs README LICENSE
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:
MMakefile.am | 10+++++++---
MREADME.md | 3+++
Mconfigure.ac | 1+
Mi3lock.c | 18+++++++++++++++++-
Ajpg.c | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajpg.h | 21+++++++++++++++++++++
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