diff --git a/Dockerfile b/Dockerfile index 4f3ef2b9..526bb2aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ WORKDIR /go/src/github.com/DarthSim/imgproxy RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \ && apk --no-cache upgrade \ && apk add --no-cache curl ca-certificates go gcc g++ make musl-dev fftw-dev glib-dev expat-dev \ - libjpeg-turbo-dev libpng-dev libwebp-dev giflib-dev libexif-dev lcms2-dev + libjpeg-turbo-dev libpng-dev libwebp-dev giflib-dev librsvg-dev libexif-dev lcms2-dev # Build ImageMagick RUN cd /root \ @@ -77,7 +77,7 @@ LABEL maintainer="Sergey Alexandrovich " RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \ && apk --no-cache upgrade \ && apk add --no-cache bash ca-certificates fftw glib expat libjpeg-turbo libpng \ - libwebp giflib libexif lcms2 \ + libwebp giflib librsvg libgsf libexif lcms2 \ && rm -rf /var/cache/apk* COPY --from=0 /usr/local/bin/imgproxy /usr/local/bin/ diff --git a/docs/image_formats_support.md b/docs/image_formats_support.md index 9942b3f4..a8112096 100644 --- a/docs/image_formats_support.md +++ b/docs/image_formats_support.md @@ -6,7 +6,8 @@ At the moment, imgproxy supports only the most popular Web image formats: * JPEG; * WebP; * GIF; -* ICO. +* ICO; +* SVG _(source only)_. ## GIF support diff --git a/image_types.h b/image_types.h index 98de8235..e21da1c2 100644 --- a/image_types.h +++ b/image_types.h @@ -4,5 +4,6 @@ enum types { PNG, WEBP, GIF, - ICO + ICO, + SVG }; diff --git a/process.go b/process.go index 0e9de323..a013b2d7 100644 --- a/process.go +++ b/process.go @@ -73,6 +73,9 @@ func initVips() { if int(C.vips_type_find_load_go(C.int(imageTypeGIF))) != 0 { vipsTypeSupportLoad[imageTypeGIF] = true } + if int(C.vips_type_find_load_go(C.int(imageTypeSVG))) != 0 { + vipsTypeSupportLoad[imageTypeSVG] = true + } // we load ICO with github.com/mat/besticon/ico and send decoded data to vips vipsTypeSupportLoad[imageTypeICO] = true @@ -140,7 +143,7 @@ func extractMeta(img *C.VipsImage) (int, int, int, bool) { return width, height, angle, flip } -func calcScale(width, height int, po *processingOptions) float64 { +func calcScale(width, height int, po *processingOptions, imgtype imageType) float64 { // If we're going only to crop, we need only to scale down to DPR. // Scaling up while cropping is not optimal on this stage, we'll do it later if needed. if po.Resize == resizeCrop { @@ -173,7 +176,7 @@ func calcScale(width, height int, po *processingOptions) float64 { scale = scale * po.Dpr - if !po.Enlarge && scale > 1 { + if !po.Enlarge && scale > 1 && imgtype != imageTypeSVG { return 1 } @@ -274,22 +277,31 @@ func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte, hasAlpha := vipsImageHasAlpha(*img) - if scale := calcScale(imgWidth, imgHeight, po); scale != 1 { - // Do some shrink-on-load - if scale < 1.0 && data != nil { - if shrink := calcShink(scale, imgtype); shrink != 1 { - scale = scale * float64(shrink) + if scale := calcScale(imgWidth, imgHeight, po, imgtype); scale != 1 { + if imgtype == imageTypeSVG && data != nil { + // Load SVG with desired scale + if tmp, err := vipsLoadImage(data, imgtype, 1, scale, false); err == nil { + C.swap_and_clear(img, tmp) + } else { + return err + } + } else { + // Do some shrink-on-load + if scale < 1.0 && data != nil { + if shrink := calcShink(scale, imgtype); shrink != 1 { + scale = scale * float64(shrink) - if tmp, err := vipsLoadImage(data, imgtype, shrink, false); err == nil { - C.swap_and_clear(img, tmp) - } else { - return err + if tmp, err := vipsLoadImage(data, imgtype, shrink, 1.0, false); err == nil { + C.swap_and_clear(img, tmp) + } else { + return err + } } } - } - if err = resizeImage(img, scale, hasAlpha); err != nil { - return err + if err = resizeImage(img, scale, hasAlpha); err != nil { + return err + } } // Update actual image size after resize @@ -506,7 +518,7 @@ func processImage(ctx context.Context) ([]byte, error) { } } - img, err := vipsLoadImage(data, imgtype, 1, po.Format == imageTypeGIF) + img, err := vipsLoadImage(data, imgtype, 1, 1.0, po.Format == imageTypeGIF) if err != nil { return nil, err } @@ -546,7 +558,7 @@ func vipsPrepareWatermark() error { return nil } - watermark, err = vipsLoadImage(data, imgtype, 1, false) + watermark, err = vipsLoadImage(data, imgtype, 1, 1.0, false) if err != nil { return err } @@ -593,7 +605,7 @@ func vipsPrepareWatermark() error { return nil } -func vipsLoadImage(data []byte, imgtype imageType, shrink int, allPages bool) (*C.struct__VipsImage, error) { +func vipsLoadImage(data []byte, imgtype imageType, shrink int, svgScale float64, allPages bool) (*C.struct__VipsImage, error) { var img *C.struct__VipsImage err := C.int(0) @@ -612,6 +624,8 @@ func vipsLoadImage(data []byte, imgtype imageType, shrink int, allPages bool) (* err = C.vips_webpload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(shrink), &img) case imageTypeGIF: err = C.vips_gifload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), pages, &img) + case imageTypeSVG: + err = C.vips_svgload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.double(svgScale), &img) case imageTypeICO: rawData, width, height, icoErr := icoData(data) if icoErr != nil { diff --git a/processing_options.go b/processing_options.go index bf284acf..919a0032 100644 --- a/processing_options.go +++ b/processing_options.go @@ -29,6 +29,7 @@ const ( imageTypeWEBP = imageType(C.WEBP) imageTypeGIF = imageType(C.GIF) imageTypeICO = imageType(C.ICO) + imageTypeSVG = imageType(C.SVG) ) type processingHeaders struct { @@ -45,6 +46,7 @@ var imageTypes = map[string]imageType{ "webp": imageTypeWEBP, "gif": imageTypeGIF, "ico": imageTypeICO, + "svg": imageTypeSVG, } type gravityType int diff --git a/svg.go b/svg.go new file mode 100644 index 00000000..2949d01b --- /dev/null +++ b/svg.go @@ -0,0 +1,22 @@ +package main + +import ( + "image" + goColor "image/color" + "io" +) + +func init() { + // Register fake svg decoder. Since we need this only for type detecting, we can + // return fake image sizes + image.RegisterFormat( + "svg", + " 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 3)) +#define VIPS_SUPPORT_SVG \ + (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 3)) + #define VIPS_SUPPORT_MAGICK \ (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 7)) @@ -52,6 +55,9 @@ vips_type_find_load_go(int imgtype) { if (imgtype == GIF) { return vips_type_find("VipsOperation", "gifload_buffer"); } + if (imgtype == SVG) { + return vips_type_find("VipsOperation", "svgload_buffer"); + } return 0; } @@ -106,6 +112,16 @@ vips_gifload_go(void *buf, size_t len, int pages, VipsImage **out) { #endif } +int +vips_svgload_go(void *buf, size_t len, double scale, VipsImage **out) { + #if VIPS_SUPPORT_SVG + return vips_svgload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, "scale", scale, NULL); + #else + vips_error("vips_svgload_go", "Loading SVG is not supported"); + return 1; + #endif +} + int vips_get_exif_orientation(VipsImage *image) { const char *orientation;