diff --git a/web.go b/web.go
index cd1f33f4afccbaf479f26d09a70e6f03cca5666b..35e58a5e3a46182143d4a2144de5e40712950f42 100644
--- a/web.go
+++ b/web.go
@@ -6,17 +6,22 @@ import (
 	"flag"
 	"fmt"
 	"html/template"
+	"image"
+	_ "image/gif"
+	"image/jpeg"
+	_ "image/png"
 	"io"
 	"log"
 	"net/http"
 	"net/url"
-	"os/exec"
+	"os"
 	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
 
 	"github.com/gorilla/mux"
+	"github.com/nfnt/resize"
 )
 
 var (
@@ -261,7 +266,26 @@ func (s *uiServer) handleShowCover(book *Book, w http.ResponseWriter, req *http.
 	http.ServeContent(w, req, "", time.Time{}, f)
 }
 
-const thumbnailSize = "150x150"
+var thumbnailSize uint = 150
+
+func makeThumbnail(inputfile string) ([]byte, error) {
+	file, err := os.Open(inputfile)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+	img, _, err := image.Decode(file)
+	if err != nil {
+		return nil, err
+	}
+
+	thumb := resize.Thumbnail(thumbnailSize, thumbnailSize, img, resize.Lanczos2)
+	var buf bytes.Buffer
+	if err := jpeg.Encode(&buf, thumb, &jpeg.Options{Quality: 65}); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
 
 func (s *uiServer) handleShowThumbnail(book *Book, w http.ResponseWriter, req *http.Request) {
 	if book.CoverPath == "" {
@@ -287,8 +311,7 @@ func (s *uiServer) handleShowThumbnail(book *Book, w http.ResponseWriter, req *h
 	}
 	defer cachedFile.Close()
 
-	data, err := exec.Command("convert", book.CoverPath, "-geometry", thumbnailSize,
-		"-quality", "60", "jpeg:-").Output()
+	data, err := makeThumbnail(s.storage.Abs(book.CoverPath))
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return