mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-02-12 11:46:10 +02:00
GIF output support
This commit is contained in:
parent
fcdc4580a5
commit
2c0b538eb5
73
Dockerfile
73
Dockerfile
@ -5,24 +5,83 @@ ENV GOPATH /go
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
|
||||
ADD . /go/src/github.com/DarthSim/imgproxy
|
||||
WORKDIR /go/src/github.com/DarthSim/imgproxy
|
||||
|
||||
# Install dependencies
|
||||
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \
|
||||
&& apk --no-cache upgrade \
|
||||
&& apk add --no-cache --virtual .build-deps go gcc musl-dev fftw-dev vips-dev \
|
||||
&& cd /go/src/github.com/DarthSim/imgproxy \
|
||||
&& CGO_LDFLAGS_ALLOW="-s|-w" go build -v -o /usr/local/bin/imgproxy \
|
||||
&& apk del --purge .build-deps \
|
||||
&& rm -rf /var/cache/apk*
|
||||
&& 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
|
||||
|
||||
# Build ImageMagick
|
||||
RUN cd /root \
|
||||
&& mkdir ImageMagick \
|
||||
&& curl -Ls https://imagemagick.org/download/ImageMagick.tar.gz | tar -xz -C ImageMagick --strip-components 1 \
|
||||
&& cd ImageMagick \
|
||||
&& ./configure \
|
||||
--enable-silent-rules \
|
||||
--disable-static \
|
||||
--disable-openmp \
|
||||
--disable-deprecated \
|
||||
--disable-docs \
|
||||
--with-threads \
|
||||
--without-magick-plus-plus \
|
||||
--without-utilities \
|
||||
--without-perl \
|
||||
--without-bzlib \
|
||||
--without-dps \
|
||||
--without-freetype \
|
||||
--without-jbig \
|
||||
--without-jpeg \
|
||||
--without-lcms \
|
||||
--without-lzma \
|
||||
--without-png \
|
||||
--without-tiff \
|
||||
--without-wmf \
|
||||
--without-xml \
|
||||
--without-webp \
|
||||
&& make install-strip
|
||||
|
||||
# Build libvips
|
||||
RUN cd /root \
|
||||
&& export VIPS_VERSION=$(curl -s "https://api.github.com/repos/libvips/libvips/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') \
|
||||
&& echo "Vips version: $VIPS_VERSION" \
|
||||
&& curl -Ls https://github.com/libvips/libvips/releases/download/v$VIPS_VERSION/vips-$VIPS_VERSION.tar.gz | tar -xz \
|
||||
&& cd vips-$VIPS_VERSION \
|
||||
&& ./configure \
|
||||
--disable-magickload \
|
||||
--without-python \
|
||||
--without-tiff \
|
||||
--without-orc \
|
||||
--without-OpenEXR \
|
||||
--enable-debug=no \
|
||||
--disable-static \
|
||||
--enable-silent-rules \
|
||||
&& make install-strip
|
||||
|
||||
# Build imgproxy
|
||||
RUN cd /go/src/github.com/DarthSim/imgproxy \
|
||||
&& CGO_LDFLAGS_ALLOW="-s|-w" go build -v -o /usr/local/bin/imgproxy
|
||||
|
||||
# Copy compiled libs here to copy them to the final image
|
||||
RUN cd /root \
|
||||
&& mkdir libs \
|
||||
&& ldd /usr/local/bin/imgproxy | grep /usr/local/lib/ | awk '{print $3}' | xargs -I '{}' cp '{}' libs/
|
||||
|
||||
# ==================================================================================================
|
||||
# Final image
|
||||
|
||||
FROM alpine:edge
|
||||
LABEL maintainer="Sergey Alexandrovich <darthsim@gmail.com>"
|
||||
|
||||
RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \
|
||||
&& apk --no-cache upgrade \
|
||||
&& apk add --no-cache ca-certificates bash vips \
|
||||
&& apk add --no-cache bash ca-certificates fftw glib expat libjpeg-turbo libpng \
|
||||
libwebp giflib libexif lcms2 \
|
||||
&& rm -rf /var/cache/apk*
|
||||
|
||||
COPY --from=0 /usr/local/bin/imgproxy /usr/local/bin
|
||||
COPY --from=0 /usr/local/bin/imgproxy /usr/local/bin/
|
||||
COPY --from=0 /root/libs/* /usr/local/lib/
|
||||
|
||||
CMD ["imgproxy"]
|
||||
|
||||
|
9
Gopkg.lock
generated
9
Gopkg.lock
generated
@ -274,6 +274,14 @@
|
||||
pruneopts = "UT"
|
||||
revision = "9dcd33a902f40452422c2367fefcb95b54f9f8f8"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:b521f10a2d8fa85c04a8ef4e62f2d1e14d303599a55d64dabf9f5a02f84d35eb"
|
||||
name = "golang.org/x/sync"
|
||||
packages = ["errgroup"]
|
||||
pruneopts = "UT"
|
||||
revision = "42b317875d0fa942474b76e1b46a6060d720ae6e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fbacfb57e3d052810813bca2d48c22fbde916ecfc4a2bac08df30ed6e9e59759"
|
||||
@ -408,6 +416,7 @@
|
||||
"github.com/stretchr/testify/suite",
|
||||
"golang.org/x/image/webp",
|
||||
"golang.org/x/net/netutil",
|
||||
"golang.org/x/sync/errgroup",
|
||||
"google.golang.org/api/option",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
|
@ -79,7 +79,7 @@ Massive processing of remote images is a potentially dangerous thing, security-w
|
||||
9. [Serving files from Google Cloud Storage](./docs/serving_files_from_google_cloud_storage.md)
|
||||
10. [New Relic](./docs/new_relic.md)
|
||||
11. [Prometheus](./docs/prometheus.md)
|
||||
12. [Source image formats support](./docs/source_image_formats_support.md)
|
||||
12. [Image formats support](./docs/image_formats_support.md)
|
||||
13. [About processing pipeline](./docs/about_processing_pipeline.md)
|
||||
14. [Health check](./docs/healthcheck.md)
|
||||
|
||||
|
@ -271,7 +271,9 @@ When using encoded source URL, you can specify the [extension](#extension) after
|
||||
|
||||
#### Extension
|
||||
|
||||
Extension specifies the format of the resulting image. At the moment, imgproxy supports only `jpg`, `png` and `webp`, them being the most popular and useful image formats on the Web.
|
||||
Extension specifies the format of the resulting image. At the moment, imgproxy supports only `jpg`, `png`, `webp`, and `gif`, them being the most popular and useful image formats on the Web.
|
||||
|
||||
**Note:** Read about GIF support [here](./image_formats_support.md#gif-support).
|
||||
|
||||
The extension part can be omitted. In this case, if the format is not defined by processing options, imgproxy will use `jpg` by default. You can also [enable WebP support detection](./configuration.md#webp-support-detection) to use it as default resulting format when possible.
|
||||
|
||||
|
@ -87,7 +87,9 @@ When using encoded source URL, you can specify the [extension](#extension) after
|
||||
|
||||
#### Extension
|
||||
|
||||
Extension specifies the format of the resulting image. At the moment, imgproxy supports only `jpg`, `png` and `webp`, them being the most popular and useful image formats on the Web.
|
||||
Extension specifies the format of the resulting image. At the moment, imgproxy supports only `jpg`, `png`, `webp`, and `gif`, them being the most popular and useful image formats on the Web.
|
||||
|
||||
**Note:** Read about GIF support [here](./image_formats_support.md#gif-support).
|
||||
|
||||
The extension part can be omitted. In this case, imgproxy will use `jpg` by default. You also can [enable WebP support detection](./configuration.md#webp-support-detection) to use it as default resulting format when possible.
|
||||
|
||||
|
12
docs/image_formats_support.md
Normal file
12
docs/image_formats_support.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Image formats support
|
||||
|
||||
At the moment, imgproxy supports only the most popular Web image formats:
|
||||
|
||||
* PNG;
|
||||
* JPEG;
|
||||
* WebP;
|
||||
* GIF.
|
||||
|
||||
## GIF support
|
||||
|
||||
imgproxy supports GIF output only when using libvips 8.7.0+ compiled with ImageMagick support. Official imgproxy Docker image supports GIF out of the box.
|
@ -1,8 +0,0 @@
|
||||
# Source image formats support
|
||||
|
||||
At the moment, imgproxy supports only the most popular Web image formats:
|
||||
|
||||
* PNG;
|
||||
* JPEG;
|
||||
* GIF;
|
||||
* WebP.
|
26
download.go
26
download.go
@ -63,23 +63,33 @@ func initDownloading() {
|
||||
}
|
||||
}
|
||||
|
||||
func checkDimensions(width, height int) error {
|
||||
if width > conf.MaxSrcDimension || height > conf.MaxSrcDimension {
|
||||
return errSourceDimensionsTooBig
|
||||
}
|
||||
|
||||
if width*height > conf.MaxSrcResolution {
|
||||
return errSourceResolutionTooBig
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTypeAndDimensions(r io.Reader) (imageType, error) {
|
||||
imgconf, imgtypeStr, err := image.DecodeConfig(r)
|
||||
imgtype, imgtypeOk := imageTypes[imgtypeStr]
|
||||
|
||||
if err != nil {
|
||||
return imageTypeUnknown, err
|
||||
}
|
||||
if imgconf.Width > conf.MaxSrcDimension || imgconf.Height > conf.MaxSrcDimension {
|
||||
return imageTypeUnknown, errSourceDimensionsTooBig
|
||||
}
|
||||
if imgconf.Width*imgconf.Height > conf.MaxSrcResolution {
|
||||
return imageTypeUnknown, errSourceResolutionTooBig
|
||||
}
|
||||
|
||||
imgtype, imgtypeOk := imageTypes[imgtypeStr]
|
||||
if !imgtypeOk || !vipsTypeSupportLoad[imgtype] {
|
||||
return imageTypeUnknown, errSourceImageTypeNotSupported
|
||||
}
|
||||
|
||||
if err = checkDimensions(imgconf.Width, imgconf.Height); err != nil {
|
||||
return imageTypeUnknown, err
|
||||
}
|
||||
|
||||
return imgtype, nil
|
||||
}
|
||||
|
||||
|
322
process.go
322
process.go
@ -15,6 +15,8 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -81,6 +83,9 @@ func initVips() {
|
||||
if int(C.vips_type_find_save_go(C.int(imageTypeWEBP))) != 0 {
|
||||
vipsTypeSupportSave[imageTypeWEBP] = true
|
||||
}
|
||||
if int(C.vips_type_find_save_go(C.int(imageTypeGIF))) != 0 {
|
||||
vipsTypeSupportSave[imageTypeGIF] = true
|
||||
}
|
||||
|
||||
if conf.JpegProgressive {
|
||||
cConf.JpegProgressive = C.int(1)
|
||||
@ -216,35 +221,10 @@ func calcCrop(width, height int, po *processingOptions) (left, top int) {
|
||||
return
|
||||
}
|
||||
|
||||
func processImage(ctx context.Context) ([]byte, error) {
|
||||
if newRelicEnabled {
|
||||
newRelicCancel := startNewRelicSegment(ctx, "Processing image")
|
||||
defer newRelicCancel()
|
||||
}
|
||||
func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte, po *processingOptions, imgtype imageType) error {
|
||||
var err error
|
||||
|
||||
if prometheusEnabled {
|
||||
defer startPrometheusDuration(prometheusProcessingDuration)()
|
||||
}
|
||||
|
||||
defer C.vips_cleanup()
|
||||
|
||||
data := getImageData(ctx).Bytes()
|
||||
po := getProcessingOptions(ctx)
|
||||
imgtype := getImageType(ctx)
|
||||
|
||||
if po.Gravity.Type == gravitySmart && !vipsSupportSmartcrop {
|
||||
return nil, errSmartCropNotSupported
|
||||
}
|
||||
|
||||
img, err := vipsLoadImage(data, imgtype, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer C.clear_image(&img)
|
||||
|
||||
checkTimeout(ctx)
|
||||
|
||||
imgWidth, imgHeight, angle, flip := extractMeta(img)
|
||||
imgWidth, imgHeight, angle, flip := extractMeta(*img)
|
||||
|
||||
// Ensure we won't crop out of bounds
|
||||
if !po.Enlarge || po.Resize == resizeCrop {
|
||||
@ -257,20 +237,20 @@ func processImage(ctx context.Context) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
hasAlpha := vipsImageHasAlpha(img)
|
||||
hasAlpha := vipsImageHasAlpha(*img)
|
||||
|
||||
if needToScale(imgWidth, imgHeight, po) {
|
||||
scale := calcScale(imgWidth, imgHeight, po)
|
||||
|
||||
// Do some shrink-on-load
|
||||
if scale < 1.0 {
|
||||
if scale < 1.0 && data != nil {
|
||||
if shrink := calcShink(scale, imgtype); shrink != 1 {
|
||||
scale = scale * float64(shrink)
|
||||
|
||||
if tmp, e := vipsLoadImage(data, imgtype, shrink); e == nil {
|
||||
C.swap_and_clear(&img, tmp)
|
||||
if tmp, err := vipsLoadImage(data, imgtype, shrink, false); err == nil {
|
||||
C.swap_and_clear(img, tmp)
|
||||
} else {
|
||||
return nil, e
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -279,50 +259,46 @@ func processImage(ctx context.Context) ([]byte, error) {
|
||||
var bandFormat C.VipsBandFormat
|
||||
|
||||
if hasAlpha {
|
||||
if bandFormat, err = vipsPremultiply(&img); err != nil {
|
||||
return nil, err
|
||||
if bandFormat, err = vipsPremultiply(img); err != nil {
|
||||
return err
|
||||
}
|
||||
premultiplied = true
|
||||
}
|
||||
|
||||
if err = vipsResize(&img, scale); err != nil {
|
||||
return nil, err
|
||||
if err = vipsResize(img, scale); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update actual image size after resize
|
||||
imgWidth, imgHeight, _, _ = extractMeta(img)
|
||||
imgWidth, imgHeight, _, _ = extractMeta(*img)
|
||||
|
||||
if premultiplied {
|
||||
if err = vipsUnpremultiply(&img, bandFormat); err != nil {
|
||||
return nil, err
|
||||
if err = vipsUnpremultiply(img, bandFormat); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = vipsImportColourProfile(&img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = vipsFixColourspace(&img); err != nil {
|
||||
return nil, err
|
||||
if err = vipsImportColourProfile(img); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkTimeout(ctx)
|
||||
|
||||
if angle != C.VIPS_ANGLE_D0 || flip {
|
||||
if err = vipsImageCopyMemory(&img); err != nil {
|
||||
return nil, err
|
||||
if err = vipsImageCopyMemory(img); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if angle != C.VIPS_ANGLE_D0 {
|
||||
if err = vipsRotate(&img, angle); err != nil {
|
||||
return nil, err
|
||||
if err = vipsRotate(img, angle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if flip {
|
||||
if err = vipsFlip(&img); err != nil {
|
||||
return nil, err
|
||||
if err = vipsFlip(img); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,21 +315,21 @@ func processImage(ctx context.Context) ([]byte, error) {
|
||||
|
||||
if po.Width < imgWidth || po.Height < imgHeight {
|
||||
if po.Gravity.Type == gravitySmart {
|
||||
if err = vipsImageCopyMemory(&img); err != nil {
|
||||
return nil, err
|
||||
if err = vipsImageCopyMemory(img); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = vipsSmartCrop(&img, po.Width, po.Height); err != nil {
|
||||
return nil, err
|
||||
if err = vipsSmartCrop(img, po.Width, po.Height); err != nil {
|
||||
return err
|
||||
}
|
||||
// Applying additional modifications after smart crop causes SIGSEGV on Alpine
|
||||
// so we have to copy memory after it
|
||||
if err = vipsImageCopyMemory(&img); err != nil {
|
||||
return nil, err
|
||||
if err = vipsImageCopyMemory(img); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
left, top := calcCrop(imgWidth, imgHeight, po)
|
||||
if err = vipsCrop(&img, left, top, po.Width, po.Height); err != nil {
|
||||
return nil, err
|
||||
if err = vipsCrop(img, left, top, po.Width, po.Height); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,29 +337,153 @@ func processImage(ctx context.Context) ([]byte, error) {
|
||||
}
|
||||
|
||||
if hasAlpha && po.Flatten {
|
||||
if err = vipsFlatten(&img, po.Background); err != nil {
|
||||
return nil, err
|
||||
if err = vipsFlatten(img, po.Background); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if po.Blur > 0 {
|
||||
if err = vipsBlur(&img, po.Blur); err != nil {
|
||||
return nil, err
|
||||
if err = vipsBlur(img, po.Blur); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if po.Sharpen > 0 {
|
||||
if err = vipsSharpen(&img, po.Sharpen); err != nil {
|
||||
return nil, err
|
||||
if err = vipsSharpen(img, po.Sharpen); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
checkTimeout(ctx)
|
||||
|
||||
if po.Watermark.Enabled {
|
||||
if err = vipsApplyWatermark(&img, &po.Watermark); err != nil {
|
||||
if err = vipsApplyWatermark(img, &po.Watermark); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = vipsFixColourspace(img); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func transformGif(ctx context.Context, img **C.struct__VipsImage, po *processingOptions) error {
|
||||
imgWidth := int((*img).Xsize)
|
||||
imgHeight := int((*img).Ysize)
|
||||
|
||||
// Double check dimensions because gif may have many frames
|
||||
if err := checkDimensions(imgWidth, imgHeight); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
frameHeight, err := vipsGetInt(*img, "page-height")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delay, err := vipsGetInt(*img, "gif-delay")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loop, err := vipsGetInt(*img, "gif-loop")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
framesCount := imgHeight / frameHeight
|
||||
|
||||
frames := make([]*C.struct__VipsImage, framesCount)
|
||||
defer func() {
|
||||
for _, frame := range frames {
|
||||
C.clear_image(&frame)
|
||||
}
|
||||
}()
|
||||
|
||||
var errg errgroup.Group
|
||||
|
||||
for i := 0; i < framesCount; i++ {
|
||||
ind := i
|
||||
errg.Go(func() error {
|
||||
var frame *C.struct__VipsImage
|
||||
|
||||
if err := vipsExtract(*img, &frame, 0, ind*frameHeight, imgWidth, frameHeight); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := transformImage(ctx, &frame, nil, po, imageTypeGIF); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
frames[ind] = frame
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := errg.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checkTimeout(ctx)
|
||||
|
||||
if err := vipsArrayjoin(frames, img); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vipsSetInt(*img, "page-height", int(frames[0].Ysize))
|
||||
vipsSetInt(*img, "gif-delay", delay)
|
||||
vipsSetInt(*img, "gif-loop", loop)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processImage(ctx context.Context) ([]byte, error) {
|
||||
if newRelicEnabled {
|
||||
newRelicCancel := startNewRelicSegment(ctx, "Processing image")
|
||||
defer newRelicCancel()
|
||||
}
|
||||
|
||||
if prometheusEnabled {
|
||||
defer startPrometheusDuration(prometheusProcessingDuration)()
|
||||
}
|
||||
|
||||
po := getProcessingOptions(ctx)
|
||||
|
||||
defer C.vips_cleanup()
|
||||
|
||||
data := getImageData(ctx).Bytes()
|
||||
imgtype := getImageType(ctx)
|
||||
|
||||
if po.Gravity.Type == gravitySmart && !vipsSupportSmartcrop {
|
||||
return nil, errSmartCropNotSupported
|
||||
}
|
||||
|
||||
img, err := vipsLoadImage(data, imgtype, 1, po.Format == imageTypeGIF)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer C.clear_image(&img)
|
||||
|
||||
if imgtype == imageTypeGIF && po.Format == imageTypeGIF {
|
||||
if err := transformGif(ctx, &img, po); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := transformImage(ctx, &img, data, po, imgtype); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
checkTimeout(ctx)
|
||||
|
||||
if po.Format == imageTypeGIF {
|
||||
if err := vipsCastUchar(&img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
checkTimeout(ctx)
|
||||
}
|
||||
|
||||
return vipsSaveImage(img, po.Format, po.Quality)
|
||||
@ -401,8 +501,9 @@ func vipsPrepareWatermark() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if C.vips_load_buffer(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(imgtype), 1, &watermark) != 0 {
|
||||
return vipsError()
|
||||
watermark, err = vipsLoadImage(data, imgtype, 1, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tmp *C.struct__VipsImage
|
||||
@ -447,11 +548,30 @@ func vipsPrepareWatermark() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func vipsLoadImage(data []byte, imgtype imageType, shrink int) (*C.struct__VipsImage, error) {
|
||||
func vipsLoadImage(data []byte, imgtype imageType, shrink int, allPages bool) (*C.struct__VipsImage, error) {
|
||||
var img *C.struct__VipsImage
|
||||
if C.vips_load_buffer(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(imgtype), C.int(shrink), &img) != 0 {
|
||||
|
||||
err := C.int(0)
|
||||
|
||||
pages := C.int(1)
|
||||
if allPages {
|
||||
pages = -1
|
||||
}
|
||||
|
||||
switch imgtype {
|
||||
case imageTypeJPEG:
|
||||
err = C.vips_jpegload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(shrink), &img)
|
||||
case imageTypePNG:
|
||||
err = C.vips_pngload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &img)
|
||||
case imageTypeWEBP:
|
||||
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)
|
||||
}
|
||||
if err != 0 {
|
||||
return nil, vipsError()
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
@ -470,6 +590,8 @@ func vipsSaveImage(img *C.struct__VipsImage, imgtype imageType, quality int) ([]
|
||||
err = C.vips_pngsave_go(img, &ptr, &imgsize, cConf.PngInterlaced)
|
||||
case imageTypeWEBP:
|
||||
err = C.vips_webpsave_go(img, &ptr, &imgsize, 1, C.int(quality))
|
||||
case imageTypeGIF:
|
||||
err = C.vips_gifsave_go(img, &ptr, &imgsize)
|
||||
}
|
||||
if err != 0 {
|
||||
return nil, vipsError()
|
||||
@ -478,10 +600,33 @@ func vipsSaveImage(img *C.struct__VipsImage, imgtype imageType, quality int) ([]
|
||||
return C.GoBytes(ptr, C.int(imgsize)), nil
|
||||
}
|
||||
|
||||
func vipsArrayjoin(in []*C.struct__VipsImage, out **C.struct__VipsImage) error {
|
||||
var tmp *C.struct__VipsImage
|
||||
|
||||
if C.vips_arrayjoin_go(&in[0], &tmp, C.int(len(in))) != 0 {
|
||||
return vipsError()
|
||||
}
|
||||
|
||||
C.swap_and_clear(out, tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func vipsImageHasAlpha(img *C.struct__VipsImage) bool {
|
||||
return C.vips_image_hasalpha_go(img) > 0
|
||||
}
|
||||
|
||||
func vipsGetInt(img *C.struct__VipsImage, name string) (int, error) {
|
||||
var i C.int
|
||||
if C.vips_image_get_int(img, C.CString(name), &i) != 0 {
|
||||
return 0, vipsError()
|
||||
}
|
||||
return int(i), nil
|
||||
}
|
||||
|
||||
func vipsSetInt(img *C.struct__VipsImage, name string, value int) {
|
||||
C.vips_image_set_int(img, C.CString(name), C.int(value))
|
||||
}
|
||||
|
||||
func vipsPremultiply(img **C.struct__VipsImage) (C.VipsBandFormat, error) {
|
||||
var tmp *C.struct__VipsImage
|
||||
|
||||
@ -511,6 +656,19 @@ func vipsUnpremultiply(img **C.struct__VipsImage, format C.VipsBandFormat) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func vipsCastUchar(img **C.struct__VipsImage) error {
|
||||
var tmp *C.struct__VipsImage
|
||||
|
||||
if C.vips_image_get_format(*img) != C.VIPS_FORMAT_UCHAR {
|
||||
if C.vips_cast_go(*img, &tmp, C.VIPS_FORMAT_UCHAR) != 0 {
|
||||
return vipsError()
|
||||
}
|
||||
C.swap_and_clear(img, tmp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func vipsResize(img **C.struct__VipsImage, scale float64) error {
|
||||
var tmp *C.struct__VipsImage
|
||||
|
||||
@ -555,6 +713,13 @@ func vipsCrop(img **C.struct__VipsImage, left, top, width, height int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func vipsExtract(in *C.struct__VipsImage, out **C.struct__VipsImage, left, top, width, height int) error {
|
||||
if C.vips_extract_area_go(in, out, C.int(left), C.int(top), C.int(width), C.int(height)) != 0 {
|
||||
return vipsError()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func vipsSmartCrop(img **C.struct__VipsImage, width, height int) error {
|
||||
var tmp *C.struct__VipsImage
|
||||
|
||||
@ -770,8 +935,10 @@ func vipsApplyWatermark(img **C.struct__VipsImage, opts *watermarkOptions) error
|
||||
}
|
||||
C.swap_and_clear(&wm, tmp)
|
||||
|
||||
if C.vips_image_guess_interpretation(*img) != C.vips_image_guess_interpretation(wm) {
|
||||
if C.vips_colourspace_go(wm, &tmp, C.vips_image_guess_interpretation(*img)) != 0 {
|
||||
imgInterpolation := C.vips_image_guess_interpretation(*img)
|
||||
|
||||
if imgInterpolation != C.vips_image_guess_interpretation(wm) {
|
||||
if C.vips_colourspace_go(wm, &tmp, imgInterpolation) != 0 {
|
||||
return vipsError()
|
||||
}
|
||||
C.swap_and_clear(&wm, tmp)
|
||||
@ -784,6 +951,8 @@ func vipsApplyWatermark(img **C.struct__VipsImage, opts *watermarkOptions) error
|
||||
C.swap_and_clear(&wmAlpha, tmp)
|
||||
}
|
||||
|
||||
imgFormat := C.vips_image_get_format(*img)
|
||||
|
||||
var imgAlpha *C.struct__VipsImage
|
||||
defer C.clear_image(&imgAlpha)
|
||||
|
||||
@ -812,6 +981,13 @@ func vipsApplyWatermark(img **C.struct__VipsImage, opts *watermarkOptions) error
|
||||
C.swap_and_clear(img, tmp)
|
||||
}
|
||||
|
||||
if imgFormat != C.vips_image_get_format(*img) {
|
||||
if C.vips_cast_go(*img, &tmp, imgFormat) != 0 {
|
||||
return vipsError()
|
||||
}
|
||||
C.swap_and_clear(img, tmp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -24,12 +24,14 @@ var (
|
||||
imageTypeJPEG: "image/jpeg",
|
||||
imageTypePNG: "image/png",
|
||||
imageTypeWEBP: "image/webp",
|
||||
imageTypeGIF: "image/gif",
|
||||
}
|
||||
|
||||
contentDispositions = map[imageType]string{
|
||||
imageTypeJPEG: "inline; filename=\"image.jpg\"",
|
||||
imageTypePNG: "inline; filename=\"image.png\"",
|
||||
imageTypeWEBP: "inline; filename=\"image.webp\"",
|
||||
imageTypeGIF: "inline; filename=\"image.gif\"",
|
||||
}
|
||||
|
||||
authHeaderMust []byte
|
||||
|
3
vendor/golang.org/x/sync/AUTHORS
generated
vendored
Normal file
3
vendor/golang.org/x/sync/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
3
vendor/golang.org/x/sync/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/golang.org/x/sync/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
27
vendor/golang.org/x/sync/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/sync/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/sync/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/sync/PATENTS
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
66
vendor/golang.org/x/sync/errgroup/errgroup.go
generated
vendored
Normal file
66
vendor/golang.org/x/sync/errgroup/errgroup.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package errgroup provides synchronization, error propagation, and Context
|
||||
// cancelation for groups of goroutines working on subtasks of a common task.
|
||||
package errgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Group is a collection of goroutines working on subtasks that are part of
|
||||
// the same overall task.
|
||||
//
|
||||
// A zero Group is valid and does not cancel on error.
|
||||
type Group struct {
|
||||
cancel func()
|
||||
|
||||
wg sync.WaitGroup
|
||||
|
||||
errOnce sync.Once
|
||||
err error
|
||||
}
|
||||
|
||||
// WithContext returns a new Group and an associated Context derived from ctx.
|
||||
//
|
||||
// The derived Context is canceled the first time a function passed to Go
|
||||
// returns a non-nil error or the first time Wait returns, whichever occurs
|
||||
// first.
|
||||
func WithContext(ctx context.Context) (*Group, context.Context) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &Group{cancel: cancel}, ctx
|
||||
}
|
||||
|
||||
// Wait blocks until all function calls from the Go method have returned, then
|
||||
// returns the first non-nil error (if any) from them.
|
||||
func (g *Group) Wait() error {
|
||||
g.wg.Wait()
|
||||
if g.cancel != nil {
|
||||
g.cancel()
|
||||
}
|
||||
return g.err
|
||||
}
|
||||
|
||||
// Go calls the given function in a new goroutine.
|
||||
//
|
||||
// The first call to return a non-nil error cancels the group; its error will be
|
||||
// returned by Wait.
|
||||
func (g *Group) Go(f func() error) {
|
||||
g.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer g.wg.Done()
|
||||
|
||||
if err := f(); err != nil {
|
||||
g.errOnce.Do(func() {
|
||||
g.err = err
|
||||
if g.cancel != nil {
|
||||
g.cancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
75
vips.h
75
vips.h
@ -10,7 +10,10 @@
|
||||
(VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 5))
|
||||
|
||||
#define VIPS_SUPPORT_GIF \
|
||||
VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 3)
|
||||
(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))
|
||||
|
||||
#define EXIF_ORIENTATION "exif-ifd0-Orientation"
|
||||
|
||||
@ -38,16 +41,16 @@ swap_and_clear(VipsImage **in, VipsImage *out) {
|
||||
int
|
||||
vips_type_find_load_go(int imgtype) {
|
||||
if (imgtype == JPEG) {
|
||||
return vips_type_find("VipsOperation", "jpegload");
|
||||
return vips_type_find("VipsOperation", "jpegload_buffer");
|
||||
}
|
||||
if (imgtype == PNG) {
|
||||
return vips_type_find("VipsOperation", "pngload");
|
||||
return vips_type_find("VipsOperation", "pngload_buffer");
|
||||
}
|
||||
if (imgtype == WEBP) {
|
||||
return vips_type_find("VipsOperation", "webpload");
|
||||
return vips_type_find("VipsOperation", "webpload_buffer");
|
||||
}
|
||||
if (imgtype == GIF) {
|
||||
return vips_type_find("VipsOperation", "gifload");
|
||||
return vips_type_find("VipsOperation", "gifload_buffer");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -63,30 +66,40 @@ vips_type_find_save_go(int imgtype) {
|
||||
if (imgtype == WEBP) {
|
||||
return vips_type_find("VipsOperation", "webpsave_buffer");
|
||||
}
|
||||
if (imgtype == GIF) {
|
||||
return vips_type_find("VipsOperation", "magicksave_buffer");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
vips_load_buffer(void *buf, size_t len, int imgtype, int shrink, VipsImage **out) {
|
||||
switch (imgtype) {
|
||||
case JPEG:
|
||||
if (shrink > 1) {
|
||||
return vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, "shrink", shrink, NULL);
|
||||
}
|
||||
return vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||
case PNG:
|
||||
return vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||
case WEBP:
|
||||
if (shrink > 1) {
|
||||
return vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, "shrink", shrink, NULL);
|
||||
}
|
||||
return vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||
#if VIPS_SUPPORT_GIF
|
||||
case GIF:
|
||||
return vips_gifload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||
#endif
|
||||
vips_jpegload_go(void *buf, size_t len, int shrink, VipsImage **out) {
|
||||
if (shrink > 1) {
|
||||
return vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, "shrink", shrink, NULL);
|
||||
}
|
||||
return 1;
|
||||
return vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
vips_pngload_go(void *buf, size_t len, VipsImage **out) {
|
||||
return vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
vips_webpload_go(void *buf, size_t len, int shrink, VipsImage **out) {
|
||||
if (shrink > 1) {
|
||||
return vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, "shrink", shrink, NULL);
|
||||
}
|
||||
return vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
vips_gifload_go(void *buf, size_t len, int pages, VipsImage **out) {
|
||||
#if VIPS_SUPPORT_GIF
|
||||
return vips_gifload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, "n", pages, NULL);
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
@ -238,6 +251,11 @@ vips_ifthenelse_go(VipsImage *cond, VipsImage *in1, VipsImage *in2, VipsImage **
|
||||
return vips_ifthenelse(cond, in1, in2, out, "blend", TRUE, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
vips_arrayjoin_go(VipsImage **in, VipsImage **out, int n) {
|
||||
return vips_arrayjoin(in, out, n, "across", 1, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
vips_jpegsave_go(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace) {
|
||||
return vips_jpegsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL);
|
||||
@ -253,6 +271,15 @@ vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int strip, int quality)
|
||||
return vips_webpsave_buffer(in, buf, len, "strip", strip, "Q", quality, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
vips_gifsave_go(VipsImage *in, void **buf, size_t *len) {
|
||||
#if VIPS_SUPPORT_MAGICK
|
||||
return vips_magicksave_buffer(in, buf, len, "format", "gif", NULL);
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
vips_cleanup() {
|
||||
vips_thread_shutdown();
|
||||
|
Loading…
x
Reference in New Issue
Block a user