1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-03-17 20:17:48 +02:00

Drop bimb dependency

This commit is contained in:
DarthSim 2017-09-27 14:42:49 +06:00
parent 1e2ee1df22
commit 51a57d43d0
103 changed files with 1352 additions and 7197 deletions

View File

@ -210,10 +210,6 @@ You can find helpful code snippets in the `examples` folder.
imgproxy supports only the most popular image formats of the moment: PNG, JPEG, GIF and WebP.
## Special thanks
Special thanks to [h2non](https://github.com/h2non) and all authors and contributors of [bimg](https://github.com/h2non/bimg).
## Author
Sergey "DarthSim" Aleksandrovich

View File

@ -167,4 +167,6 @@ func init() {
} else if conf.GZipCompression > 9 {
log.Fatalf("GZip compression can't be greater than 9, now - %d\n", conf.GZipCompression)
}
initVips()
}

View File

@ -57,41 +57,50 @@ func (r *netReader) GrowBuf(s int) {
r.buf.Grow(s)
}
func checkTypeAndDimensions(r io.Reader) error {
imgconf, _, err := image.DecodeConfig(r)
func checkTypeAndDimensions(r io.Reader) (imageType, error) {
imgconf, imgtypeStr, err := image.DecodeConfig(r)
imgtype, imgtypeOk := imageTypes[imgtypeStr]
if err != nil {
return err
return UNKNOWN, err
}
if imgconf.Width > conf.MaxSrcDimension || imgconf.Height > conf.MaxSrcDimension {
return errors.New("File is too big")
return UNKNOWN, errors.New("File is too big")
}
return nil
if !imgtypeOk || !vipsTypeSupportedLoad(imgtype) {
return UNKNOWN, errors.New("Source image type not supported")
}
return imgtype, nil
}
func readAndCheckImage(res *http.Response) ([]byte, error) {
func readAndCheckImage(res *http.Response) ([]byte, imageType, error) {
nr := newNetReader(res.Body)
if err := checkTypeAndDimensions(nr); err != nil {
return nil, err
imgtype, err := checkTypeAndDimensions(nr)
if err != nil {
return nil, UNKNOWN, err
}
if res.ContentLength > 0 {
nr.GrowBuf(int(res.ContentLength))
}
return nr.ReadAll()
b, err := nr.ReadAll()
return b, imgtype, err
}
func downloadImage(url string) ([]byte, error) {
func downloadImage(url string) ([]byte, imageType, error) {
res, err := downloadClient.Get(url)
if err != nil {
return nil, err
return nil, UNKNOWN, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
body, _ := ioutil.ReadAll(res.Body)
return nil, fmt.Errorf("Can't download image; Status: %d; %s", res.StatusCode, string(body))
return nil, UNKNOWN, fmt.Errorf("Can't download image; Status: %d; %s", res.StatusCode, string(body))
}
return readAndCheckImage(res)

12
glide.lock generated
View File

@ -1,19 +1,15 @@
hash: 54db290c0c5bcd14e69e3faa4a1a23040d9117283134d96d6d8ed0df3544283e
updated: 2017-09-15T13:31:21.075317146+03:00
hash: 0ef57dd15f41e8f5181f43ff93b7c420e63c8bb2174a8c879ffd56dd98bed659
updated: 2017-09-27T13:53:20.123821976+06:00
imports:
- name: github.com/h2non/bimg
version: 276e0541892a9c518de3cf332536cd30ebef25de
- name: github.com/tj/go-debug
version: ff4a55a20a86994118644bbddc6a216da193cc13
- name: golang.org/x/image
version: e20db36d77bd0cb36cea8fe49d5c37d82d21591f
version: 334384d9e19178a0488c9360d94d183c1ef0f711
subpackages:
- riff
- vp8
- vp8l
- webp
- name: golang.org/x/net
version: 859d1a86bb617c0c20d154590c3c5d3fcb670b07
version: 0a9397675ba34b2845f758fe3cd68828369c6517
subpackages:
- netutil
- name: gopkg.in/yaml.v2

View File

@ -1,7 +1,5 @@
package: github.com/DarthSim/imgproxy
import:
- package: github.com/h2non/bimg
version: ~1.0.9
- package: gopkg.in/yaml.v2
- package: golang.org/x/net
subpackages:

View File

@ -16,7 +16,7 @@ func main() {
}
s := &http.Server{
Handler: newHttpHandler(),
Handler: newHTTPHandler(),
ReadTimeout: time.Duration(conf.ReadTimeout) * time.Second,
WriteTimeout: time.Duration(conf.WriteTimeout) * time.Second,
MaxHeaderBytes: 1 << 20,

View File

@ -1,91 +1,269 @@
package main
import (
"math"
/*
#cgo pkg-config: vips
#include "vips.h"
*/
import "C"
"github.com/h2non/bimg"
import (
"errors"
"log"
"math"
"runtime"
"unsafe"
)
type imageType int
const (
UNKNOWN imageType = iota
JPEG
PNG
WEBP
GIF
)
var imageTypes = map[string]imageType{
"jpeg": JPEG,
"jpg": JPEG,
"png": PNG,
"webp": WEBP,
"gif": GIF,
}
type gravityType int
const (
CENTER gravityType = iota
NORTH
EAST
SOUTH
WEST
SMART
)
var gravityTypes = map[string]gravityType{
"ce": CENTER,
"no": NORTH,
"ea": EAST,
"so": SOUTH,
"we": WEST,
"sm": SMART,
}
type resizeType int
const (
FIT resizeType = iota
FILL
CROP
)
var resizeTypes = map[string]resizeType{
"fit": FIT,
"fill": FILL,
"crop": CROP,
}
type processingOptions struct {
resize string
resize resizeType
width int
height int
gravity bimg.Gravity
gravity gravityType
enlarge bool
format bimg.ImageType
format imageType
}
var imageTypes = map[string]bimg.ImageType{
"jpeg": bimg.JPEG,
"jpg": bimg.JPEG,
"png": bimg.PNG,
"webp": bimg.WEBP,
func initVips() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if err := C.vips_initialize(); err != 0 {
C.vips_shutdown()
log.Fatalln("unable to start vips!")
}
C.vips_concurrency_set(1)
C.vips_cache_set_max_mem(100 * 1024 * 1024) // 100Mb
C.vips_cache_set_max(500)
}
var gravityTypes = map[string]bimg.Gravity{
"ce": bimg.GravityCentre,
"no": bimg.GravityNorth,
"ea": bimg.GravityEast,
"so": bimg.GravitySouth,
"we": bimg.GravityWest,
"sm": bimg.GravitySmart,
func vipsTypeSupportedLoad(imgtype imageType) bool {
switch imgtype {
case JPEG:
return int(C.vips_type_find_load_go(C.JPEG)) != 0
case PNG:
return int(C.vips_type_find_load_go(C.PNG)) != 0
case WEBP:
return int(C.vips_type_find_load_go(C.WEBP)) != 0
case GIF:
return int(C.vips_type_find_load_go(C.GIF)) != 0
}
return false
}
func vipsTypeSupportedSave(imgtype imageType) bool {
switch imgtype {
case JPEG:
return int(C.vips_type_find_save_go(C.JPEG)) != 0
case PNG:
return int(C.vips_type_find_save_go(C.PNG)) != 0
case WEBP:
return int(C.vips_type_find_save_go(C.WEBP)) != 0
}
return false
}
func round(f float64) int {
return int(f + .5)
}
func calcSize(size bimg.ImageSize, po processingOptions) (int, int) {
if (po.width == size.Width && po.height == size.Height) || (po.resize != "fill" && po.resize != "fit") {
return po.width, po.height
func calcScale(width, height int, po processingOptions) float64 {
if (po.width == width && po.height == height) || (po.resize != FILL && po.resize != FIT) {
return 1
}
fsw, fsh, fow, foh := float64(size.Width), float64(size.Height), float64(po.width), float64(po.height)
fsw, fsh, fow, foh := float64(width), float64(height), float64(po.width), float64(po.height)
wr := fow / fsw
hr := foh / fsh
var rate float64
if po.resize == "fit" {
rate = math.Min(wr, hr)
if po.resize == FIT {
return math.Min(wr, hr)
}
return math.Max(wr, hr)
}
func calcCrop(width, height int, po processingOptions) (left, top int) {
left = (width - po.width + 1) / 2
top = (height - po.height + 1) / 2
if po.gravity == NORTH {
top = 0
}
if po.gravity == EAST {
left = width - po.width
}
if po.gravity == SOUTH {
top = height - po.height
}
if po.gravity == WEST {
left = 0
}
return
}
func processImage(data []byte, imgtype imageType, po processingOptions) ([]byte, error) {
defer runtime.KeepAlive(data)
err := C.int(0)
var img, tmpImg *C.struct__VipsImage
// Cleanup after all
defer func() {
C.vips_thread_shutdown()
C.vips_error_clear()
}()
// Load the image
switch imgtype {
case JPEG:
err = C.vips_jpegload_buffer_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &img)
case PNG:
err = C.vips_pngload_buffer_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &img)
case GIF:
err = C.vips_gifload_buffer_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &img)
case WEBP:
err = C.vips_webpload_buffer_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &img)
}
if err != 0 {
return nil, vipsError()
}
imgWidth := int(img.Xsize)
imgHeight := int(img.Ysize)
// Ensure we won't crop out of bounds
if !po.enlarge || po.resize == CROP {
if imgWidth < po.width {
po.width = imgWidth
}
if imgHeight < po.height {
po.height = imgHeight
}
}
if po.width != imgWidth || po.height != imgHeight {
// Resize image for "fill" and "fit"
if po.resize == FILL || po.resize == FIT {
scale := calcScale(imgWidth, imgHeight, po)
err = C.vips_resize_go(img, &tmpImg, C.double(scale))
C.g_object_unref(C.gpointer(img))
img = tmpImg
if err != 0 {
return nil, vipsError()
}
}
// Crop image for "fill" and "crop"
if po.resize == FILL || po.resize == CROP {
if po.gravity == SMART && C.vips_support_smartcrop() == 1 {
err = C.vips_smartcrop_go(img, &tmpImg, C.int(po.width), C.int(po.height))
C.g_object_unref(C.gpointer(img))
img = tmpImg
if err != 0 {
return nil, vipsError()
}
} else {
rate = math.Max(wr, hr)
left, top := calcCrop(int(img.Xsize), int(img.Ysize), po)
err = C.vips_extract_area_go(img, &tmpImg, C.int(left), C.int(top), C.int(po.width), C.int(po.height))
C.g_object_unref(C.gpointer(img))
img = tmpImg
if err != 0 {
return nil, vipsError()
}
}
}
}
return round(math.Min(fsw*rate, fow)), round(math.Min(fsh*rate, foh))
// Convert to sRGB colour space
err = C.vips_colourspace_go(img, &tmpImg, C.VIPS_INTERPRETATION_sRGB)
C.g_object_unref(C.gpointer(img))
img = tmpImg
if err != 0 {
return nil, vipsError()
}
// Finally, save
imgsize := C.size_t(0)
var ptr unsafe.Pointer
switch po.format {
case JPEG:
err = C.vips_jpegsave_go(img, &ptr, &imgsize, 1, C.int(conf.Quality), 0)
case PNG:
err = C.vips_pngsave_go(img, &ptr, &imgsize)
case WEBP:
err = C.vips_webpsave_go(img, &ptr, &imgsize, 1, C.int(conf.Quality))
}
if err != 0 {
return nil, vipsError()
}
C.g_object_unref(C.gpointer(img))
buf := C.GoBytes(ptr, C.int(imgsize))
C.g_free(C.gpointer(ptr))
return buf, nil
}
func processImage(p []byte, po processingOptions) ([]byte, error) {
var err error
img := bimg.NewImage(p)
size, err := img.Size()
if err != nil {
return nil, err
}
// Default options
opts := bimg.Options{
Interpolator: bimg.Bicubic,
Quality: conf.Quality,
Gravity: po.gravity,
Enlarge: po.enlarge,
Type: po.format,
}
opts.Width, opts.Height = calcSize(size, po)
switch po.resize {
case "fit":
opts.Embed = true
case "fill":
opts.Embed = true
opts.Crop = true
case "crop":
opts.Crop = true
default:
opts.Force = true
}
return img.Process(opts)
func vipsError() error {
return errors.New(C.GoString(C.vips_error_buffer()))
}

View File

@ -12,21 +12,19 @@ import (
"strconv"
"strings"
"time"
"github.com/h2non/bimg"
)
var mimes = map[bimg.ImageType]string{
bimg.JPEG: "image/jpeg",
bimg.PNG: "image/png",
bimg.WEBP: "image/webp",
var mimes = map[imageType]string{
JPEG: "image/jpeg",
PNG: "image/png",
WEBP: "image/webp",
}
type httpHandler struct {
sem chan struct{}
}
func newHttpHandler() httpHandler {
func newHTTPHandler() httpHandler {
return httpHandler{make(chan struct{}, conf.Concurrency)}
}
@ -47,7 +45,11 @@ func parsePath(r *http.Request) (string, processingOptions, error) {
return "", po, err
}
po.resize = parts[1]
if r, ok := resizeTypes[parts[1]]; ok {
po.resize = r
} else {
return "", po, fmt.Errorf("Invalid resize type: %s", parts[1])
}
if po.width, err = strconv.Atoi(parts[2]); err != nil {
return "", po, fmt.Errorf("Invalid width: %s", parts[2])
@ -75,6 +77,10 @@ func parsePath(r *http.Request) (string, processingOptions, error) {
return "", po, fmt.Errorf("Invalid image format: %s", filenameParts[1])
}
if !vipsTypeSupportedSave(po.format) {
return "", po, errors.New("Resulting image type not supported")
}
filename, err := base64.RawURLEncoding.DecodeString(filenameParts[0])
if err != nil {
return "", po, errors.New("Invalid filename encoding")
@ -173,13 +179,13 @@ func (h httpHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
return
}
b, err := downloadImage(imgURL)
b, imgtype, err := downloadImage(imgURL)
if err != nil {
respondWithError(rw, 404, err, "Image is unreachable")
return
}
b, err = processImage(b, procOpt)
b, err = processImage(b, imgtype, procOpt)
if err != nil {
respondWithError(rw, 500, err, "Error occurred while processing image")
return

View File

@ -1,12 +0,0 @@
root = true
[*]
indent_style = tab
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@ -1,9 +0,0 @@
/bimg
/bundle
bin
/*.jpg
/*.png
/*.webp
/fixtures/*_out.*
/.idea/
fixtures/test_vertical_*.jpg

View File

@ -1,101 +0,0 @@
language: go
dist: trusty
sudo: false
go:
- 1.6
- 1.7
- 1.8
- 1.9
- tip
env:
- LIBVIPS=7.42.3
- LIBVIPS=8.2.3
- LIBVIPS=8.3.3
- LIBVIPS=8.4.6
- LIBVIPS=8.5.8
- LIBVIPS=8.6.0-alpha4
- LIBVIPS=master
matrix:
allow_failures:
- env: LIBVIPS=7.42.3
- env: LIBVIPS=8.2.3
- env: LIBVIPS=8.3.3
- env: LIBVIPS=8.6.0-alpha4
cache:
apt:
directories:
- $HOME/libvips
addons:
apt:
packages:
- gobject-introspection
- gtk-doc-tools
- libcfitsio3-dev
- libfftw3-dev
- libgif-dev
- libgs-dev
- libgsf-1-dev
- libmatio-dev
- libopenslide-dev
- liborc-0.4-dev
- libpango1.0-dev
- libpoppler-glib-dev
- libwebp-dev
# VIPS 8.3.3 requires Poppler 0.30 which is not released on Trusty.
before_install:
- >
test "$LIBVIPS" != "master" -a "$LIBVIPS" \< "8.4" \
&& wget http://www.vips.ecs.soton.ac.uk/supported/${LIBVIPS%.*}/vips-${LIBVIPS}.tar.gz -O vips.tgz \
|| echo ":-)"
- >
test "$LIBVIPS" != "master" -a "$LIBVIPS" \> "8.4" \
&& wget https://github.com/jcupitt/libvips/archive/v${LIBVIPS}.tar.gz -O vips.tgz \
|| echo ":-)"
- >
test $LIBVIPS == "master" \
&& wget https://github.com/jcupitt/libvips/archive/${LIBVIPS}.tar.gz -O vips.tgz \
|| echo ":-)"
- mkdir libvips
- tar xf vips.tgz -C libvips --strip-components 1
- cd libvips
- test -f autogen.sh && ./autogen.sh || ./bootstrap.sh
- >
CXXFLAGS=-D_GLIBCXX_USE_CXX11_ABI=0
./configure
--disable-debug
--disable-dependency-tracking
--disable-introspection
--disable-static
--enable-gtk-doc-html=no
--enable-gtk-doc=no
--enable-pyvips8=no
--without-orc
--without-python
--prefix=$HOME/libvips
$1
- make
- make install
- cd ..
- export PATH=$PATH:$HOME/libvips/bin
- export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$HOME/libvips/lib/pkgconfig
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/libvips/lib
- vips --vips-version
before_script:
- go get -u github.com/golang/lint/golint
script:
- diff -u <(echo -n) <(gofmt -s -d ./)
- diff -u <(echo -n) <(go vet ./)
- diff -u <(echo -n) <(golint ./)
- go test -v -race -covermode=atomic -coverprofile=coverage.out
after_success:
- goveralls -coverprofile=coverage.out -service=travis-ci

View File

@ -1,139 +0,0 @@
## v1.0.14 / 2017-09-12
* Merge pull request #192 from greut/trim
* Adding trim operation.
* Merge pull request #191 from greut/alpha4
* Update 8.6 to alpha4.
## v1.0.13 / 2017-09-11
* Merge pull request #190 from greut/typos
* Fix typo and small cleanup.
## v1.0.12 / 2017-09-10
* Merge branch '99designs-vips-reduce'
* fix(reduce): resolve conflicts with master
* Use vips reduce when downscaling
## v1.0.11 / 2017-09-10
* feat(#189): allow strip image metadata via bimg.Options.StripMetadata = bool
* fix(resize): code format issue
* refactor(resize): add Go version comment
* refactor(tests): fix minor code formatting issues
* fix(#162): garbage collection fix. split Resize() implementation for Go runtime specific
* feat(travis): add go 1.9
* Merge pull request #183 from greut/autorotate
* Proper handling of the EXIF cases.
* Merge pull request #184 from greut/libvips858
* Merge branch 'master' into libvips858
* Merge pull request #185 from greut/libvips860
* Add libvips 8.6 pre-release
* Update to libvips 8.5.8
* fix(resize): runtime.KeepAlive is only Go
* fix(#159): prevent buf to be freed by the GC before resize function exits
* Merge pull request #171 from greut/fix-170
* Check the length before jumping into buffer.
* Merge pull request #168 from Traum-Ferienwohnungen/icc_transform
* Add option to convert embedded ICC profiles
* Merge pull request #166 from danjou-a/patch-1
* Fix Resize verification value
* Merge pull request #165 from greut/libvips846
* Testing using libvips8.4.6 from Github.
## v1.0.10 / 2017-06-25
* Merge pull request #164 from greut/length
* Add Image.Length()
* Merge pull request #163 from greut/libvips856
* Run libvips 8.5.6 on Travis.
* Merge pull request #161 from henry-blip/master
* Expose vips cache memory management functions.
* feat(docs): add watermark image note in features
## v1.0.9 / 2017-05-25
* Merge pull request #156 from Dynom/SmartCropToGravity
* Adding a test, verifying both ways of enabling SmartCrop work
* Merge pull request #149 from waldophotos/master
* Replacing SmartCrop with a Gravity option
* refactor(docs): v8.4
* Change for older LIBVIPS versions. `vips_bandjoin_const1` is added in libvips 8.2.
* Second try, watermarking memory issue fix
## v1.0.8 / 2017-05-18
* Merge pull request #145 from greut/smartcrop
* Merge pull request #155 from greut/libvips8.5.5
* Update libvips to 8.5.5.
* Adding basic smartcrop support.
* Merge pull request #153 from abracadaber/master
* Added Linux Mint 17.3+ distro names
* feat(docs): add new maintainer notice (thanks to @kirillDanshin)
* Merge pull request #152 from greut/libvips85
* Download latest version of libvips from github.
* Merge pull request #147 from h2non/revert-143-master
* Revert "Fix for memory issue when watermarking images"
* Merge pull request #146 from greut/minor-major
* Merge pull request #143 from waldophotos/master
* Merge pull request #144 from greut/go18
* Fix tests where minor/major were mixed up
* Enabled go 1.8 builds.
* Fix the unref of images, when image isn't transparent
* Fix for memory issue when watermarking images
* feat(docs): add maintainers sections
* Merge pull request #132 from jaume-pinyol/WATERMARK_SUPPORT
* Add support for image watermarks
* Merge pull request #131 from greut/versions
* Running tests on more specific versions.
* refactor(preinstall.sh): remove deprecation notice
* Update preinstall.sh
* fix(requirements): required libvips 7.42
* fix(History): typo
* chore(History): add breaking change note
## v1.0.7 / 13-01-2017
- fix(#128): crop image calculation for missing width or height axis.
- feat: add TIFF save output format (**note**: this introduces a minor interface breaking change in `bimg.IsImageTypeSupportedByVips` auxiliary function).
## v1.0.6 / 12-11-2016
- feat(#118): handle 16-bit PNGs.
- feat(#119): adds JPEG2000 file for the type tests.
- feat(#121): test bimg against multiple libvips versions.
## v1.0.5 / 01-10-2016
- feat(#92): support Extend param with optional background.
- fix(#106): allow image area extraction without explicit x/y axis.
- feat(api): add Extend type with `libvips` enum alias.
## v1.0.4 / 29-09-2016
- fix(#111): safe check of magick image type support.
## v1.0.3 / 28-09-2016
- fix(#95): better image type inference and support check.
- fix(background): pass proper background RGB color for PNG image conversion.
- feat(types): validate supported image types by current `libvips` compilation.
- feat(types): consistent SVG image checking.
- feat(api): add public functions `VipsIsTypeSupported()`, `IsImageTypeSupportedByVips()` and `IsSVGImage()`.
## v1.0.2 / 27-09-2016
- feat(#95): support GIF, SVG and PDF formats.
- fix(#108): auto-width and height calculations now round instead of floor.
## v1.0.1 / 22-06-2016
- fix(#90): Do not not dereference the original image a second time.
## v1.0.0 / 21-04-2016
- refactor(api): breaking changes: normalize public members to follow Go naming idioms.
- feat(version): bump to major version. API contract won't be compromised in `v1`.
- feat(docs): add missing inline godoc documentation.

24
vendor/github.com/h2non/bimg/LICENSE generated vendored
View File

@ -1,24 +0,0 @@
The MIT License
Copyright (c) Tomas Aparicio and contributors
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,348 +0,0 @@
# bimg [![Build Status](https://travis-ci.org/h2non/bimg.svg)](https://travis-ci.org/h2non/bimg) [![GoDoc](https://godoc.org/github.com/h2non/bimg?status.svg)](https://godoc.org/github.com/h2non/bimg) [![Go Report Card](http://goreportcard.com/badge/h2non/bimg)](http://goreportcard.com/report/h2non/bimg) [![Coverage Status](https://coveralls.io/repos/github/h2non/bimg/badge.svg?branch=master)](https://coveralls.io/github/h2non/bimg?branch=master) ![License](https://img.shields.io/badge/license-MIT-blue.svg)
Small [Go](http://golang.org) package for fast high-level image processing using [libvips](https://github.com/jcupitt/libvips) via C bindings, providing a simple, elegant and fluent [programmatic API](#examples).
bimg was designed to be a small and efficient library supporting a common set of [image operations](#supported-image-operations) such as crop, resize, rotate, zoom or watermark. It can read JPEG, PNG, WEBP natively, and optionally TIFF, PDF, GIF and SVG formats if `libvips@8.3+` is compiled with proper library bindings.
bimg is able to output images as JPEG, PNG and WEBP formats, including transparent conversion across them.
bimg uses internally libvips, a powerful library written in C for image processing which requires a [low memory footprint](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use)
and it's typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings or Go native `image` package, and in some cases it's even 8x faster processing JPEG images.
If you're looking for an HTTP based image processing solution, see [imaginary](https://github.com/h2non/imaginary).
bimg was heavily inspired in [sharp](https://github.com/lovell/sharp), its homologous package built for [node.js](http://nodejs.org). bimg is used in production environments processing thousands of images per day.
**v1 notice**: `bimg` introduces some minor breaking changes in `v1` release.
If you're using `gopkg.in`, you can still rely in the `v0` without worrying about API breaking changes.
`bimg` is currently maintained by [Kirill Danshin](https://github.com/kirillDanshin).
## Contents
- [Supported image operations](#supported-image-operations)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Performance](#performance)
- [Benchmark](#benchmark)
- [Examples](#examples)
- [Debugging](#debugging)
- [API](#api)
- [Authors](#authors)
- [Credits](#credits)
## Supported image operations
- Resize
- Enlarge
- Crop (including smart crop support, libvips 8.5+)
- Rotate (with auto-rotate based on EXIF orientation)
- Flip (with auto-flip based on EXIF metadata)
- Flop
- Zoom
- Thumbnail
- Extract area
- Watermark (using text or image)
- Gaussian blur effect
- Custom output color space (RGB, grayscale...)
- Format conversion (with additional quality/compression settings)
- EXIF metadata (size, alpha channel, profile, orientation...)
- Trim (libvips 8.6+)
## Prerequisites
- [libvips](https://github.com/jcupitt/libvips) 7.42+ or 8+ (8.4+ recommended)
- C compatible compiler such as gcc 4.6+ or clang 3.0+
- Go 1.3+
**Note**: `libvips` v8.3+ is required for GIF, PDF and SVG support.
## Installation
```bash
go get -u gopkg.in/h2non/bimg.v1
```
### libvips
Run the following script as `sudo` (supports OSX, Debian/Ubuntu, Redhat, Fedora, Amazon Linux):
```bash
curl -s https://raw.githubusercontent.com/h2non/bimg/master/preinstall.sh | sudo bash -
```
If you wanna take the advantage of [OpenSlide](http://openslide.org/), simply add `--with-openslide` to enable it:
```bash
curl -s https://raw.githubusercontent.com/h2non/bimg/master/preinstall.sh | sudo bash -s --with-openslide
```
The [install script](https://github.com/h2non/bimg/blob/master/preinstall.sh) requires `curl` and `pkg-config`.
## Performance
libvips is probably the faster open source solution for image processing.
Here you can see some performance test comparisons for multiple scenarios:
- [libvips speed and memory usage](http://www.vips.ecs.soton.ac.uk/index.php?title=Speed_and_Memory_Use)
## Benchmark
Tested using Go 1.5.1 and libvips-7.42.3 in OSX i7 2.7Ghz
```
BenchmarkRotateJpeg-8 20 64686945 ns/op
BenchmarkResizeLargeJpeg-8 20 63390416 ns/op
BenchmarkResizePng-8 100 18147294 ns/op
BenchmarkResizeWebP-8 100 20836741 ns/op
BenchmarkConvertToJpeg-8 100 12831812 ns/op
BenchmarkConvertToPng-8 10 128901422 ns/op
BenchmarkConvertToWebp-8 10 204027990 ns/op
BenchmarkCropJpeg-8 30 59068572 ns/op
BenchmarkCropPng-8 10 117303259 ns/op
BenchmarkCropWebP-8 10 107060659 ns/op
BenchmarkExtractJpeg-8 50 30708919 ns/op
BenchmarkExtractPng-8 3000 595546 ns/op
BenchmarkExtractWebp-8 3000 386379 ns/op
BenchmarkZoomJpeg-8 10 160005424 ns/op
BenchmarkZoomPng-8 30 44561047 ns/op
BenchmarkZoomWebp-8 10 126732678 ns/op
BenchmarkWatermarkJpeg-8 20 79006133 ns/op
BenchmarkWatermarPng-8 200 8197291 ns/op
BenchmarkWatermarWebp-8 30 49360369 ns/op
```
## Examples
```go
import (
"fmt"
"os"
"gopkg.in/h2non/bimg.v1"
)
```
#### Resize
```go
buffer, err := bimg.Read("image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
newImage, err := bimg.NewImage(buffer).Resize(800, 600)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
size, err := bimg.NewImage(newImage).Size()
if size.Width == 800 && size.Height == 600 {
fmt.Println("The image size is valid")
}
bimg.Write("new.jpg", newImage)
```
#### Rotate
```go
buffer, err := bimg.Read("image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
newImage, err := bimg.NewImage(buffer).Rotate(90)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
bimg.Write("new.jpg", newImage)
```
#### Convert
```go
buffer, err := bimg.Read("image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
newImage, err := bimg.NewImage(buffer).Convert(bimg.PNG)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
if bimg.NewImage(newImage).Type() == "png" {
fmt.Fprintln(os.Stderr, "The image was converted into png")
}
```
#### Force resize
Force resize operation without perserving the aspect ratio:
```go
buffer, err := bimg.Read("image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
newImage, err := bimg.NewImage(buffer).ForceResize(1000, 500)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
size := bimg.Size(newImage)
if size.Width != 1000 || size.Height != 500 {
fmt.Fprintln(os.Stderr, "Incorrect image size")
}
```
#### Custom colour space (black & white)
```go
buffer, err := bimg.Read("image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
newImage, err := bimg.NewImage(buffer).Colourspace(bimg.INTERPRETATION_B_W)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
colourSpace, _ := bimg.ImageInterpretation(newImage)
if colourSpace != bimg.INTERPRETATION_B_W {
fmt.Fprintln(os.Stderr, "Invalid colour space")
}
```
#### Custom options
See [Options](https://godoc.org/github.com/h2non/bimg#Options) struct to discover all the available fields
```go
options := bimg.Options{
Width: 800,
Height: 600,
Crop: true,
Quality: 95,
Rotate: 180,
Interlace: true,
}
buffer, err := bimg.Read("image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
newImage, err := bimg.NewImage(buffer).Process(options)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
bimg.Write("new.jpg", newImage)
```
#### Watermark
```go
buffer, err := bimg.Read("image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
watermark := bimg.Watermark{
Text: "Chuck Norris (c) 2315",
Opacity: 0.25,
Width: 200,
DPI: 100,
Margin: 150,
Font: "sans bold 12",
Background: bimg.Color{255, 255, 255},
}
newImage, err := bimg.NewImage(buffer).Watermark(watermark)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
bimg.Write("new.jpg", newImage)
```
#### Fluent interface
```go
buffer, err := bimg.Read("image.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
image := bimg.NewImage(buffer)
// first crop image
_, err := image.CropByWidth(300)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
// then flip it
newImage, err := image.Flip()
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
// save the cropped and flipped image
bimg.Write("new.jpg", newImage)
```
## Debugging
Run the process passing the `DEBUG` environment variable
```
DEBUG=bimg ./app
```
Enable libvips traces (note that a lot of data will be written in stdout):
```
VIPS_TRACE=1 ./app
```
You can also dump a core on failure, as [John Cuppit](https://github.com/jcupitt) said:
```c
g_log_set_always_fatal(
G_LOG_FLAG_RECURSION |
G_LOG_FLAG_FATAL |
G_LOG_LEVEL_ERROR |
G_LOG_LEVEL_CRITICAL |
G_LOG_LEVEL_WARNING );
```
Or set the G_DEBUG environment variable:
```
export G_DEBUG=fatal-warnings,fatal-criticals
```
## API
See [godoc reference](https://godoc.org/github.com/h2non/bimg) for detailed API documentation.
## Authors
- [Tomás Aparicio](https://github.com/h2non) - Original author and architect.
- [Kirill Danshin](https://github.com/kirillDanshin) - Maintainer since April 2017.
## Credits
People who recurrently contributed to improve `bimg` in some way.
- [John Cupitt](https://github.com/jcupitt)
- [Yoan Blanc](https://github.com/greut)
- [Christophe Eblé](https://github.com/chreble)
- [Brant Fitzsimmons](https://github.com/bfitzsimmons)
- [Thomas Meson](https://github.com/zllak)
Thank you!
## License
MIT - Tomas Aparicio
[![views](https://sourcegraph.com/api/repos/github.com/h2non/bimg/.counters/views.svg)](https://sourcegraph.com/github.com/h2non/bimg)

15
vendor/github.com/h2non/bimg/file.go generated vendored
View File

@ -1,15 +0,0 @@
package bimg
import "io/ioutil"
// Read reads all the content of the given file path
// and returns it as byte buffer.
func Read(path string) ([]byte, error) {
return ioutil.ReadFile(path)
}
// Write writes the given byte buffer into disk
// to the given file path.
func Write(path string, buf []byte) error {
return ioutil.WriteFile(path, buf, 0644)
}

View File

@ -1,38 +0,0 @@
package bimg
import (
"testing"
)
func TestRead(t *testing.T) {
buf, err := Read("fixtures/test.jpg")
if err != nil {
t.Errorf("Cannot read the image: %#v", err)
}
if len(buf) == 0 {
t.Fatal("Empty buffer")
}
if DetermineImageType(buf) != JPEG {
t.Fatal("Image is not jpeg")
}
}
func TestWrite(t *testing.T) {
buf, err := Read("fixtures/test.jpg")
if err != nil {
t.Errorf("Cannot read the image: %#v", err)
}
if len(buf) == 0 {
t.Fatal("Empty buffer")
}
err = Write("fixtures/test_write_out.jpg", buf)
if err != nil {
t.Fatalf("Cannot write the file: %#v", err)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 KiB

View File

@ -1,725 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg id="svg2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 900" version="1.1">
<g id="g4" fill="none" transform="matrix(1.7656463,0,0,1.7656463,324.90716,255.00942)">
<g id="g6" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path8" d="m-122.3,84.285s0.1,1.894-0.73,1.875c-0.82-0.019-17.27-48.094-37.8-45.851,0,0,17.78-7.353,38.53,43.976z"/>
</g>
<g id="g10" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path12" d="m-118.77,81.262s-0.55,1.816-1.32,1.517c-0.77-0.298,0.11-51.104-19.95-55.978,0,0,19.22-0.864,21.27,54.461z"/>
</g>
<g id="g14" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path16" d="m-91.284,123.59s1.636,0.96,1.166,1.64c-0.471,0.67-49.642-12.13-59.102,6.23,0,0,3.68-18.89,57.936-7.87z"/>
</g>
<g id="g18" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path20" d="m-94.093,133.8s1.856,0.4,1.622,1.19c-0.233,0.79-50.939,4.13-54.129,24.53,0,0-2.46-19.08,52.507-25.72z"/>
</g>
<g id="g22" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path24" d="m-98.304,128.28s1.778,0.66,1.432,1.41-50.998-3.34-57.128,16.37c0,0,0.35-19.24,55.696-17.78z"/>
</g>
<g id="g26" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path28" d="m-109.01,110.07s1.31,1.38,0.67,1.9-44.38-25.336-58.53-10.29c0,0,8.74-17.147,57.86,8.39z"/>
</g>
<g id="g30" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path32" d="m-116.55,114.26s1.45,1.22,0.88,1.81c-0.58,0.59-46.97-20.148-59.32-3.6,0,0,6.74-18.023,58.44,1.79z"/>
</g>
<g id="g34" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path36" d="m-119.15,118.34s1.6,1,1.11,1.67c-0.49,0.66-49.27-13.56-59.25,4.51,0,0,4.22-18.77,58.14-6.18z"/>
</g>
<g id="g38" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path40" d="m-108.42,118.95s1.12,1.53,0.42,1.97c-0.7,0.43-40.77-30.818-56.73-17.71,0,0,10.87-15.884,56.31,15.74z"/>
</g>
<g id="g42" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path44" d="m-128.2,90s0.6,1.8-0.2,2-29.4-41.8-48.6-34.2c0,0,15.2-11.8,48.8,32.2z"/>
</g>
<g id="g46" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path48" d="m-127.5,96.979s0.97,1.629,0.23,1.996c-0.74,0.368-37.72-34.476-54.83-22.914,0,0,12.3-14.8,54.6,20.918z"/>
</g>
<g id="g50" stroke-width="0.17200001" stroke="#000" fill="#FFF">
<path id="path52" d="m-127.62,101.35s1.12,1.53,0.42,1.97c-0.7,0.43-40.77-30.818-56.73-17.713,0,0,10.87-15.881,56.31,15.743z"/>
</g>
<g id="g54" stroke="#000" fill="#FFF">
<path id="path56" d="m-129.83,103.06c0.5,6.05,1.49,12.62,3.23,15.74,0,0-3.6,12.4,5.2,25.6,0,0-0.4,7.2,1.2,10.4,0,0,4,8.4,8.8,9.2,3.88,0.65,12.607,3.72,22.468,5.12,0,0,17.132,14.08,13.932,26.88,0,0-0.4,16.4-4,18,0,0,11.6-11.2,2,5.6l-4.4,18.8s25.6-21.6,10-3.2l-10,26s19.6-18.4,12.4-10l-3.2,8.8s43.2-27.2,12.4,2.4c0,0,8-3.6,12.4-0.8,0,0,6.8-1.2,6,0.4,0,0-20.8,10.4-24.4,28.8,0,0,8.4-10,5.2,0.8l0.4,11.6s4-21.6,3.6,16c0,0,19.2-18,7.6,2.8v16.8s15.2-16.4,8.8-3.6c0,0,10-8.8,6,6.4,0,0-0.8,10.4,3.6-0.8,0,0,16-30.6,10-4.4,0,0-0.8,19.2,4,4.4,0,0,0.4,10.4,9.6,17.6,0,0-1.2-50.8,11.6-14.8l4,16.4s2.8-9.2,2.4-14.4l8,8s15.2-22.8,12-9.6c0,0-7.6,16-6,20.8,0,0,16.8-34.8,18-36.4,0,0-2,42.4,8.8,6.4,0,0,5.6,12,2.8,16.4,0,0,8-8,7.2-11.2,0,0,4.6-8.2,7.4,5.4,0,0,1.8,9.4,3.4,6.2,0,0,4,24,5.2,1.2,0,0,1.6-13.6-5.6-25.2,0,0,0.8-3.2-2-7.2,0,0,13.6,21.6,6.4-7.2,0,0,11.201,8,12.401,8,0,0-13.601-23.2-4.801-18.4,0,0-5.2-10.4,12.801,1.6,0,0-16.001-16,1.6-6.4,0,0,7.999,6.4,0.4-3.6,0,0-14.401-16,7.599,2,0,0,11.6,16.4,12.4,19.2,0,0-10-29.2-14.4-32,0,0,8.4-36.4,49.6-20.8,0,0,6.8,17.2,11.2-1.2,0,0,12.8-6.4,24,21.2,0,0,4-13.6,3.2-16.4,0,0,6.8,1.2,6,0,0,0,13.2,4.4,14.4,3.6,0,0,6.8,6.8,7.2,3.2,0,0,9.2,2.8,7.2-0.8,0,0,8.8,15.6,9.2,19.2l2.4-14,2,2.8s1.6-7.6,0.8-8.8,20,6.8,24.8,27.6l2,8.4s6-14.8,4.4-18.8c0,0,5.2,0.8,5.6,5.2,0,0,4-23.2-0.8-29.2,0,0,4.4-0.8,5.6,2.8v-7.2s7.2,0.8,7.2-1.6c0,0,4.4-4,6.4,0.8,0,0-12.4-35.2,6-16,0,0,7.2,10.8,3.6-8s-7.6-20.4-2.8-20.8c0,0,0.8-3.6-1.2-5.2s1.2,0,1.2,0,4.8,4-0.4-18c0,0,6.4,1.6-5.6-27.6,0,0,2.8-2.4-1.2-10.8,0,0,8,4.4,10.8,2.8,0,0-0.4-1.6-3.6-5.6,0,0-21.6-54.8-1.2-32.8,0,0,11.85,13.55,5.45-9.25,0,0-9.11-24.009-8.33-28.305l-429.55,23.015z"/>
</g>
<g id="g58" stroke="#000" fill="#cc7226">
<path id="path60" d="m299.72,80.245c0.62,0.181,2.83,1.305,4.08,2.955,0,0,6.8,10.8,1.6-7.6,0,0-9.2-28.8-0.4-17.6,0,0,6,7.2,2.8-6.4-3.86-16.427-6.4-22.8-6.4-22.8s11.6,4.8-15.2-34.8l8.8,3.6s-19.6-39.6-41.2-44.8l-8-6s38.4-38,25.6-74.8c0,0-6.8-5.2-16.4,4,0,0-6.4,4.8-12.4,3.2,0,0-30.8,1.2-32.8,1.2s-36.8-37.2-102.4-19.6c0,0-5.2,2-9.599,0.8,0,0-18.401-16-67.201,6.8,0,0-10,2-11.6,2s-4.4,0-12.4,6.4-8.4,7.2-10.4,8.8c0,0-16.4,11.2-21.2,12,0,0-11.6,6.4-16,16.4l-3.6,1.2s-1.6,7.2-2,8.4c0,0-4.8,3.6-5.6,9.2,0,0-8.8,6-8.4,10.4,0,0-1.6,5.2-2.4,10,0,0-7.2,4.8-6.4,7.6,0,0-7.6,14-6.4,20.8,0,0-6.4-0.4-9.2,2,0,0-0.8,4.8-2.4,5.2,0,0-2.8,1.2-0.4,5.2,0,0-1.6,2.8-2,4.4,0,0,0.8,2.8-3.6,8.4,0,0-6.4,18.8-4.4,24,0,0,0.4,4.8-2.4,6.4,0,0-3.6-0.4,4.8,11.6,0,0,0.8,1.2-2.4,3.6,0,0-17.2,3.6-19.6,20,0,0-13.6,14.8-13.6,20,0,2.305,0.27,5.452,0.97,10.06,0,0-0.57,8.34,27.03,9.14s402.72-31.355,402.72-31.355z"/>
</g>
<g id="g62" fill="#cc7226">
<path id="path64" d="m-115.6,102.6c-25-39.4-10.6,17-10.6,17,8.8,34.4,138.4-3.2,138.4-3.2s168.8-30.4,180-34.4,106.4,2.4,106.4,2.4l-5.6-16.8c-64.8-46.4-84-23.2-97.6-27.2s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2-31.74-22.951-16.8,8.8c16,34-58.4,39.2-75.2,28s7.2,18.4,7.2,18.4c18.4,20-16,3.2-16,3.2-34.4-12.8-58.4,12.8-61.6,13.6s-8,4-8.8-2.4-8.31-23.101-40,3.2c-20,16.6-33.8-5.4-33.8-5.4l-2.8,11.6z"/>
</g>
<g id="g66" fill="#e87f3a">
<path id="path68" d="m133.51,25.346c-6.4,0.8-31.77-22.939-16.8,8.8,16.6,35.2-58.4,39.2-75.2,28-16.801-11.2,7.2,18.4,7.2,18.4,18.4,20.004-16.001,3.2-16.001,3.2-34.4-12.8-58.4,12.8-61.6,13.6s-8,4.004-8.8-2.4c-0.8-6.4-8.179-22.934-40,3.2-21.236,17.344-34.729-4.109-34.729-4.109l-3.2,10.113c-25-39.804-9.93,18.51-9.93,18.51,8.81,34.4,139.06-4.51,139.06-4.51s168.8-30.404,180-34.404,105.53,2.327,105.53,2.327l-5.53-17.309c-64.8-46.4-83.2-22.618-96.8-26.618s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/>
</g>
<g id="g70" fill="#ea8c4d">
<path id="path72" d="m134.82,27.091c-6.4,0.8-31.14-23.229-16.8,8.8,16.2,36.201-58.401,39.201-75.201,28.001s7.2,18.4,7.2,18.4c18.4,19.998-16,3.2-16,3.2-34.4-12.8-58.401,12.8-61.601,13.6s-8,3.998-8.8-2.4c-0.8-6.4-8.048-22.767-40,3.2-22.473,18.088-35.658-2.818-35.658-2.818l-3.6,8.616c-23.8-38.998-9.25,20.02-9.25,20.02,8.8,34.4,139.71-5.82,139.71-5.82s168.8-30.398,180-34.398,104.65,2.254,104.65,2.254l-5.45-17.818c-64.8-46.4-82.4-22.037-96-26.037s-11.2,5.6-14.4,6.401c-3.2,0.8-42.4-24.001-48.8-23.201z"/>
</g>
<g id="g74" fill="#ec9961">
<path id="path76" d="m136.13,28.837c-6.4,0.8-31.13-23.232-16.8,8.8,16.8,37.556-58.936,38.845-75.202,28-16.8-11.2,7.2,18.4,7.2,18.4,18.4,20.003-16,3.2-16,3.2-34.4-12.8-58.4,12.803-61.6,13.603s-8,4-8.8-2.403c-0.8-6.4-7.917-22.598-40.001,3.203-23.709,18.83-36.587-1.53-36.587-1.53l-4,7.13c-21.8-36.803-8.58,21.52-8.58,21.52,8.8,34.4,140.37-7.12,140.37-7.12s168.8-30.403,180-34.403,103.78,2.182,103.78,2.182l-5.38-18.327c-64.8-46.401-81.6-21.455-95.2-25.455s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/>
</g>
<g id="g78" fill="#eea575">
<path id="path80" d="m137.44,30.583c-6.4,0.8-30.63-23.454-16.8,8.8,16.8,39.2-58.403,39.2-75.203,28s7.2,18.4,7.2,18.4c18.4,19.997-16,3.2-16,3.2-34.4-12.8-58.4,12.797-61.6,13.597s-8,4-8.8-2.4c-0.8-6.397-7.785-22.428-40,3.2-24.946,19.58-37.507-0.23-37.507-0.23l-4.4,5.63c-19.8-34.798-7.91,23.04-7.91,23.04,8.8,34.4,141.02-8.44,141.02-8.44s168.8-30.397,180-34.397,102.91,2.109,102.91,2.109l-5.31-18.837c-64.8-46.4-80.8-20.872-94.4-24.872s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/>
</g>
<g id="g82" fill="#f1b288">
<path id="path84" d="m138.75,32.328c-6.4,0.8-32.37-22.651-16.8,8.8,19.2,38.8-58.404,39.2-75.204,28s7.2,18.4,7.2,18.4c18.4,20.002-16,3.2-16,3.2-34.4-12.8-58.4,12.802-61.6,13.602s-8,4-8.8-2.4c-0.8-6.402-7.654-22.265-40,3.2-26.182,20.33-38.436,1.05-38.436,1.05l-4.8,4.15c-18-33.202-7.24,24.54-7.24,24.54,8.8,34.4,141.68-9.74,141.68-9.74s168.8-30.402,180-34.402,102.03,2.036,102.03,2.036l-5.23-19.345c-64.8-46.4-80-20.291-93.6-24.291s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/>
</g>
<g id="g86" fill="#f3bf9c">
<path id="path88" d="m140.06,34.073c-6.4,0.8-32.75-22.46-16.8,8.8,20.4,40.001-58.405,39.201-75.205,28.001s7.2,18.4,7.2,18.4c18.4,19.996-16,3.2-16,3.2-34.4-12.8-58.4,12.796-61.6,13.596s-8,4-8.8-2.4c-0.8-6.396-7.523-22.092-40,3.2-27.419,21.08-39.365,2.35-39.365,2.35l-5.2,2.65c-16-30.196-6.56,26.06-6.56,26.06,8.8,34.4,142.32-11.06,142.32-11.06s168.8-30.396,180-34.396,101.16,1.963,101.16,1.963l-5.16-19.854c-64.8-46.4-79.2-19.709-92.8-23.709-13.6-4.001-11.2,5.6-14.4,6.4s-42.4-24.001-48.8-23.201z"/>
</g>
<g id="g90" fill="#f5ccb0">
<path id="path92" d="m141.36,35.819c-6.4,0.8-33.84-21.875-16.8,8.8,22,39.6-58.396,39.2-75.196,28s7.2,18.4,7.2,18.4c18.4,20.001-16,3.2-16,3.2-34.4-12.8-58.4,12.801-61.6,13.601s-8,4-8.8-2.4c-0.8-6.401-7.391-21.928-40,3.2-28.655,21.82-40.294,3.64-40.294,3.64l-5.6,1.16c-14.4-28.401-5.89,27.56-5.89,27.56,8.8,34.4,142.98-12.36,142.98-12.36s168.8-30.401,180-34.401,100.3,1.891,100.3,1.891l-5.1-20.364c-64.8-46.4-78.4-19.127-92-23.127s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/>
</g>
<g id="g94" fill="#f8d8c4">
<path id="path96" d="m142.67,37.565c-6.4,0.8-33.84-21.876-16.8,8.8,22,39.6-58.396,39.2-75.196,28s7.2,18.4,7.2,18.4c18.4,19.995-16,3.2-16,3.2-34.401-12.8-58.401,12.795-61.601,13.595s-8,4-8.8-2.4-7.259-21.755-40,3.2c-29.891,22.57-41.213,4.93-41.213,4.93l-6-0.33c-13.61-26.396-5.22,29.08-5.22,29.08,8.8,34.4,143.63-13.68,143.63-13.68s168.8-30.395,180-34.395,99.42,1.818,99.42,1.818l-5.01-20.873c-64.81-46.4-77.61-18.545-91.21-22.545s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/>
</g>
<g id="g98" fill="#fae5d7">
<path id="path100" d="m143.98,39.31c-6.4,0.8-33.45-22.087-16.8,8.8,22,40.8-58.397,39.2-75.197,28s7.2,18.4,7.2,18.4c18.4,20-16,3.2-16,3.2-34.4-12.8-58.4,12.8-61.6,13.6-3.201,0.8-8.001,4-8.801-2.4s-7.128-21.592-40,3.2c-31.127,23.31-42.142,6.22-42.142,6.22l-6.4-1.82c-13-24-4.55,30.58-4.55,30.58,8.8,34.4,144.29-14.98,144.29-14.98s168.8-30.4,180-34.4,98.55,1.746,98.55,1.746l-4.95-21.382c-64.8-46.401-76.8-17.964-90.4-21.964s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2z"/>
</g>
<g id="g102" fill="#fcf2eb">
<path id="path104" d="m145.29,41.055c-6.4,0.8-32.37-22.644-16.8,8.8,21.2,42.801-58.398,39.201-75.198,28.001s7.2,18.4,7.2,18.4c18.4,20.004-16,3.2-16,3.2-34.4-12.8-58.4,12.804-61.6,13.604s-8,4-8.8-2.4-6.997-21.428-40,3.2c-32.365,24.05-43.072,7.5-43.072,7.5l-6.8-3.3c-12.8-23.204-3.87,32.09-3.87,32.09,8.8,34.4,144.94-16.29,144.94-16.29s168.8-30.4,180-34.404c11.2-4,97.67,1.674,97.67,1.674l-4.87-21.893c-64.8-46.4-76-17.381-89.6-21.381-13.6-4.001-11.2,5.6-14.4,6.4s-42.4-24.001-48.8-23.201z"/>
</g>
<g id="g106" fill="#FFF">
<path id="path108" d="m-115.8,119.6c-12.8-22-3.2,33.6-3.2,33.6,8.8,34.4,145.6-17.6,145.6-17.6s168.8-30.4,180-34.4,96.8,1.6,96.8,1.6l-4.8-22.4c-64.8-46.4-75.2-16.8-88.8-20.8s-11.2,5.6-14.4,6.4-42.4-24-48.8-23.2-31.62-23.007-16.8,8.8c22.23,47.707-60.759,37.627-75.2,28-16.8-11.2,7.2,18.4,7.2,18.4,18.4,20-16,3.2-16,3.2-34.4-12.8-58.4,12.8-61.6,13.6s-8,4-8.8-2.4-6.865-21.256-40,3.2c-33.6,24.8-44,8.8-44,8.8l-7.2-4.8z"/>
</g>
<g id="g110" fill="#000">
<path id="path112" d="m-74.2,149.6s-7.2,11.6,13.6,24.8c0,0,1.4,1.4-16.6-2.8,0,0-6.2-2-7.8-12.4,0,0-4.8-4.4-9.6-10s20.4,0.4,20.4,0.4z"/>
</g>
<g id="g114" fill="#CCC">
<path id="path116" d="m65.8,102s17.698,26.82,17.1,31.6c-1.3,10.4-1.5,20,1.7,24,3.201,4,12.001,37.2,12.001,37.2s-0.4,1.2,11.999-36.8c0,0,11.6-16-8.4-34.4,0,0-35.2-28.8-34.4-21.6z"/>
</g>
<g id="g118" fill="#000">
<path id="path120" d="m-54.2,176.4s11.2,7.2-3.2,38.4l6.4-2.4s-0.8,11.2-4,13.6l7.2-3.2s4.8,8,0.8,12.8c0,0,16.8,8,16,14.4,0,0,6.4-8,2.4-14.4s-11.2-2.4-10.4-20.8l-8.8,3.2s5.6-8.8,5.6-15.2l-8,2.4s15.469-26.58,4.8-28c-6-0.8-8.8-0.8-8.8-0.8z"/>
</g>
<g id="g122" fill="#CCC">
<path id="path124" d="m-21.8,193.2s2.8-4.4,0-3.6-34,15.6-40,25.2c0,0,34.4-24.4,40-21.6z"/>
</g>
<g id="g126" fill="#CCC">
<path id="path128" d="m-11.4,201.2s2.8-4.4,0-3.6-34,15.6-40,25.2c0,0,34.4-24.4,40-21.6z"/>
</g>
<g id="g130" fill="#CCC">
<path id="path132" d="m1.8,186s2.8-4.4,0-3.6-34,15.6-40,25.2c0,0,34.4-24.4,40-21.6z"/>
</g>
<g id="g134" fill="#CCC">
<path id="path136" d="m-21.4,229.6s0-6-2.8-5.2-38.8,18.4-44.8,28c0,0,42-25.6,47.6-22.8z"/>
</g>
<g id="g138" fill="#CCC">
<path id="path140" d="m-20.2,218.8s1.2-4.8-1.6-4c-2,0-28.4,11.6-34.4,21.2,0,0,29.6-21.6,36-17.2z"/>
</g>
<g id="g142" fill="#CCC">
<path id="path144" d="m-34.6,266.4-10,7.6s10.4-7.6,14-6.4c0,0-6.8,11.2-7.6,16.4,0,0,10.4-12.8,16-12.4,0,0,7.6,0.4,7.6,11.2,0,0,5.6-10.4,8.8-10,0,0,1.2,6.4,0,13.2,0,0,4-7.6,8-6,0,0,6.4-2,5.6,9.6,0,0,0,10.4-0.8,13.2,0,0,5.6-26.4,8-26.8,0,0,8-1.2,12.8,7.6,0,0-4-7.6,0.8-5.6,0,0,10.8,1.6,14,8.4,0,0-6.8-12-1.2-8.8l8,6.4s8.4,21.2,10.4,22.8c0,0-7.6-21.6-6-21.6,0,0-2-12,3.2,2.8,0,0-3.2-14,2.4-13.2s10,10.8,18.4,8.4c0,0,9.601,5.6,11.601-63.6l-124,46.8z"/>
</g>
<g id="g146" fill="#000">
<path id="path148" d="m-29.8,173.6s14.8-6,54.8,0c0,0,7.2,0.4,14-8.4s33.6-16,40-14l9.601,6.4,0.8,1.2s12.399,10.4,12.799,18-14.399,55.6-24,71.6c-9.6,16-19.2,28.4-38.4,26,0,0-20.8-4-46.4,0,0,0-29.2-1.6-32-9.6s11.2-23.2,11.2-23.2,4.4-8.4,3.2-22.8-0.8-42.4-5.6-45.2z"/>
</g>
<g id="g150" fill="#e5668c">
<path id="path152" d="M-7.8,175.6c8.4,18.4-21.2,83.6-21.2,83.6-2,1.6,12.66,7.65,22.8,5.2,10.946-2.64,51.2,1.6,51.2,1.6,23.6-15.6,36.4-60,36.4-60s10.401-24-7.2-27.2c-17.6-3.2-82-3.2-82-3.2z"/>
</g>
<g id="g154" fill="#b23259">
<path id="path156" d="m-9.831,206.5c3.326-12.79,4.91-24.59,2.031-30.9,0,0,62.4,6.4,73.6-14.4,4.241-7.87,19.001,22.8,18.6,32.4,0,0-63,14.4-77.8,3.2l-16.431,9.7z"/>
</g>
<g id="g158" fill="#a5264c">
<path id="path160" d="m-5.4,222.8s2,7.2-0.4,11.2c0,0-1.6,0.8-2.8,1.2,0,0,1.2,3.6,7.2,5.2,0,0,2,4.4,4.4,4.8s7.2,6,11.2,4.8,15.2-5.2,15.2-5.2,5.6-3.2,14.4,0.4c0,0,2.375-0.8,2.8-4.8,0.5-4.7,3.6-8.4,5.6-10.4s11.6-14.8,10.4-15.2-68,8-68,8z"/>
</g>
<g id="g162" stroke="#000" fill="#ff727f">
<path id="path164" d="m-9.8,174.4s-2.8,22.4,0.4,30.8,2.4,10.4,1.6,14.4,3.6,14,9.2,20l12,1.6s15.2-3.6,24.4-0.8c0,0,8.994,1.34,12.4-13.6,0,0,4.8-6.4,12-9.2s14.4-44.4,10.4-52.4-18.4-12.4-34.4,3.2-18-1.2-48,6z"/>
</g>
<g id="g166" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path168" d="m-8.2,249.2s-0.8-2-5.2-2.4c0,0-22.4-3.6-30.8-16,0,0-6.8-5.6-2.4,6,0,0,10.4,20.4,17.2,23.2,0,0,16.4,4,21.2-10.8z"/>
</g>
<g id="g170" fill="#cc3f4c">
<path id="path172" d="m71.742,185.23c0.659-7.91,2.612-16.52,0.858-20.03-6.446-12.89-23.419-7.5-34.4,3.2-16,15.6-18-1.2-48,6,0,0-1.745,13.96-0.905,23.98,0,0,37.305-11.58,38.105-5.98,0,0,1.6-3.2,10.8-3.2s31.942-1.17,33.542-3.97z"/>
</g>
<g id="g174" stroke-width="2" stroke="#a51926">
<path id="path176" d="m28.6,175.2s4.8,4.8,1.2,14.4c0,0-14.4,16-12.4,30"/>
</g>
<g id="g178" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path180" d="m-19.4,260s-4.4-12.8,4.4-6l3.6,3.6c-1.2,1.6-6.8,5.6-8,2.4z"/>
</g>
<g id="g182" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path184" d="m-14.36,261.2s-3.52-10.24,3.52-4.8l2.88,2.88c-4.56,1.28,0,3.84-6.4,1.92z"/>
</g>
<g id="g186" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path188" d="m-9.56,261.2s-3.52-10.24,3.52-4.8l2.88,2.88c-3.36,1.28,0,3.84-6.4,1.92z"/>
</g>
<g id="g190" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path192" d="m-2.96,261.4s-3.52-10.24,3.52-4.8c0,0,4.383,2.33,2.881,2.88-2.961,1.08,0,3.84-6.401,1.92z"/>
</g>
<g id="g194" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path196" d="m3.52,261.32s-3.52-10.24,3.521-4.8l2.88,2.88c-0.96,1.28,0,3.84-6.401,1.92z"/>
</g>
<g id="g198" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path200" d="m10.2,262s-4.8-12.4,4.4-6l3.6,3.6c-1.2,1.6,0,4.8-8,2.4z"/>
</g>
<g id="g202" stroke-width="2" stroke="#a5264c">
<path id="path204" d="m-18.2,244.8s13.2-2.8,19.2,0.4c0,0,6,1.2,7.2,0.8s4.4-0.8,4.4-0.8"/>
</g>
<g id="g206" stroke-width="2" stroke="#a5264c">
<path id="path208" d="m15.8,253.6s12-13.6,24-9.2c7.016,2.57,6-0.8,6.8-3.6s1-7,6-10"/>
</g>
<g id="g210" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path212" d="m33,237.6s-4-10.8-6.8,2-6,16.4-7.6,19.2c0,0,0,5.2,8.4,4.8,0,0,10.8-0.4,11.2-3.2s-1.2-14.4-5.2-22.8z"/>
</g>
<g id="g214" stroke-width="2" stroke="#a5264c">
<path id="path216" d="m47,244.8s3.6-2.4,6-1.2"/>
</g>
<g id="g218" stroke-width="2" stroke="#a5264c">
<path id="path220" d="m53.5,228.4s2.9-4.9,7.7-5.7"/>
</g>
<g id="g222" fill="#b2b2b2">
<path id="path224" d="m-25.8,265.2s18,3.2,22.4,1.6l0.4,2-20.8-1.2s-11.6-5.6-2-2.4z"/>
</g>
<g id="g226" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path228" d="m-11.8,172,19.6,0.8s7.2,30.8,3.6,38.4c0,0-1.2,2.8-4-2.8,0,0-18.4-32.8-21.6-34.8s1.2-1.6,2.4-1.6z"/>
</g>
<g id="g230" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path232" d="m-88.9,169.3s8.9,1.7,21.5,4.3c0,0,4.8,22.4,8,27.2s-0.4,4.8-4,2-18.4-16.8-20.4-21.2-5.1-12.3-5.1-12.3z"/>
</g>
<g id="g234" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path236" d="m-67.039,173.82s5.8,1.55,6.809,3.76c1.008,2.22-1.202,5.51-1.202,5.51s-1,3.31-2.202,1.15c-1.202-2.17-4.074-9.83-3.405-10.42z"/>
</g>
<g id="g238" fill="#000">
<path id="path240" d="m-67,173.6s3.6,5.2,7.2,5.2,3.982-0.41,6.8,0.2c4.6,1,4.2-1,10.8,0.2,2.64,0.48,5.2-0.4,8,0.8s6,0.4,7.2-1.6,6-6.2,6-6.2-12.8,1.8-15.6,2.6c0,0-22.4,1.2-30.4-1.2z"/>
</g>
<g id="g242" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path244" d="m-22.4,173.8s-6.45,3.5-6.85,5.9,5.25,6.1,5.25,6.1,2.75,4.6,3.35,2.2-0.95-13.8-1.75-14.2z"/>
</g>
<g id="g246" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path248" d="m-59.885,179.26s7.007,11.19,7.224-0.02c0,0,0.557-1.26-1.203-1.28-6.075-0.07-4.554-4.18-6.021,1.3z"/>
</g>
<g id="g250" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path252" d="m-52.707,179.51s7.921,11.19,7.285-0.09c0,0,0.007-0.33-1.746-0.48-4.747-0.42-4.402-4.94-5.539,0.57z"/>
</g>
<g id="g254" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path256" d="m-45.494,179.52s7.96,10.63,7.291,0.96c0,0,0.119-1.23-1.535-1.53-3.892-0.71-4.103-3.95-5.756,0.57z"/>
</g>
<g id="g258" stroke-width="0.5" stroke="#000" fill="#FFC">
<path id="path260" d="m-38.618,179.6s7.9,11.56,8.248,1.78c0,0,1.644-1.38-0.102-1.6-5.818-0.74-5.02-5.19-8.146-0.18z"/>
</g>
<g id="g262" fill="#e5e5b2">
<path id="path264" d="m-74.792,183.13-7.658-1.53c-2.6-5-4.7-11.15-4.7-11.15s6.35,1,18.85,3.8c0,0,0.876,3.32,2.348,9.11l-8.84-0.23z"/>
</g>
<g id="g266" fill="#e5e5b2">
<path id="path268" d="m-9.724,178.47c-1.666-2.51-2.983-4.26-3.633-4.67-3.013-1.88,1.13-1.51,2.259-1.51l18.454,0.76s0.524,2.24,1.208,5.63c0,0-10.088-2.01-18.288-0.21z"/>
</g>
<g id="g270" fill="#cc7226">
<path id="path272" d="m43.88,40.321c27.721,3.96,53.241-31.68,55.001-41.361,1.759-9.68-8.36-21.56-8.36-21.56,1.32-3.08-3.52-17.16-8.8-26.4s-21.181-8.266-38.721-9.24c-15.84-0.88-34.32,22.44-35.64,24.2s4.84,40.041,6.16,45.761-1.32,32.12-1.32,32.12c34.24-9.1,3.96-7.48,31.68-3.52z"/>
</g>
<g id="g274" fill="#ea8e51">
<path id="path276" d="m8.088-33.392c-1.296,1.728,4.752,39.313,6.048,44.929s-1.296,31.536-1.296,31.536c32.672-8.88,3.888-7.344,31.104-3.456,27.217,3.888,52.273-31.104,54.001-40.609,1.728-9.504-8.208-21.168-8.208-21.168,1.296-3.024-3.456-16.848-8.64-25.92s-20.795-8.115-38.017-9.072c-15.552-0.864-33.696,22.032-34.992,23.76z"/>
</g>
<g id="g278" fill="#efaa7c">
<path id="path280" d="m8.816-32.744c-1.272,1.696,4.664,38.585,5.936,44.097s-1.272,30.952-1.272,30.952c31.404-9.16,3.816-7.208,30.528-3.392,26.713,3.816,51.305-30.528,53.001-39.857,1.696-9.328-8.056-20.776-8.056-20.776,1.272-2.968-3.392-16.536-8.48-25.44s-20.41-7.965-37.313-8.904c-15.264-0.848-33.072,21.624-34.344,23.32z"/>
</g>
<g id="g282" fill="#f4c6a8">
<path id="path284" d="m9.544-32.096c-1.248,1.664,4.576,37.857,5.824,43.265s-1.248,30.368-1.248,30.368c29.436-9.04,3.744-7.072,29.952-3.328,26.209,3.744,50.337-29.952,52.001-39.104,1.664-9.153-7.904-20.385-7.904-20.385,1.248-2.912-3.328-16.224-8.32-24.96s-20.025-7.815-36.609-8.736c-14.976-0.832-32.448,21.216-33.696,22.88z"/>
</g>
<g id="g286" fill="#f9e2d3">
<path id="path288" d="m10.272-31.448c-1.224,1.632,4.488,37.129,5.712,42.433s-1.224,29.784-1.224,29.784c27.868-8.92,3.672-6.936,29.376-3.264,25.705,3.672,49.369-29.376,51.001-38.353,1.632-8.976-7.752-19.992-7.752-19.992,1.224-2.856-3.264-15.912-8.16-24.48s-19.64-7.665-35.905-8.568c-14.688-0.816-31.824,20.808-33.048,22.44z"/>
</g>
<g id="g290" fill="#FFF">
<path id="path292" d="M44.2,36.8c25.2,3.6,48.401-28.8,50.001-37.6s-7.6-19.6-7.6-19.6c1.2-2.8-3.201-15.6-8.001-24s-19.254-7.514-35.2-8.4c-14.4-0.8-31.2,20.4-32.4,22s4.4,36.4,5.6,41.6-1.2,29.2-1.2,29.2c25.5-8.6,3.6-6.8,28.8-3.2z"/>
</g>
<g id="g294" fill="#CCC">
<path id="path296" d="m90.601,2.8s-27.801,7.6-39.401,6c0,0-15.8-6.6-24.6,15.2,0,0-3.6,7.2-5.6,9.2s69.601-30.4,69.601-30.4z"/>
</g>
<g id="g298" fill="#000">
<path id="path300" d="m94.401,0.6s-29.001,12.2-39.001,11.8c0,0-16.4-4.6-24.8,10,0,0-8.4,9.2-11.6,10.8,0,0-0.4,1.6,6-2.4l10.4,5.2s14.8,9.6,24.4-6.4c0,0,4-11.2,4-13.2s21.2-7.6,22.801-8c1.6-0.4,8.2-4.6,7.8-7.8z"/>
</g>
<g id="g302" fill="#99cc32">
<path id="path304" d="m47,36.514c-6.872,0-15.245-3.865-15.245-10.114,0-6.248,8.373-12.513,15.245-12.513,6.874,0,12.446,5.065,12.446,11.313,0,6.249-5.572,11.314-12.446,11.314z"/>
</g>
<g id="g306" fill="#659900">
<path id="path308" d="m43.377,19.83c-4.846,0.722-9.935,2.225-9.863,2.009,1.54-4.619,7.901-7.952,13.486-7.952,4.296,0,8.084,1.978,10.32,4.988,0,0-5.316-0.33-13.943,0.955z"/>
</g>
<g id="g310" fill="#FFF">
<path id="path312" d="m55.4,19.6s-4.4-3.2-4.4-1c0,0,3.6,4.4,4.4,1z"/>
</g>
<g id="g314" fill="#000">
<path id="path316" d="m45.4,27.726c-2.499,0-4.525-2.026-4.525-4.526,0-2.499,2.026-4.525,4.525-4.525,2.5,0,4.526,2.026,4.526,4.525,0,2.5-2.026,4.526-4.526,4.526z"/>
</g>
<g id="g318" fill="#cc7226">
<path id="path320" d="m-58.6,14.4s-3.2-21.2-0.8-25.6c0,0,10.8-10,10.4-13.6,0,0-0.4-18-1.6-18.8s-8.8-6.8-14.8-0.4c0,0-10.4,18-9.6,24.4v2s-7.6-0.4-9.2,1.6c0,0-1.2,5.2-2.4,5.6,0,0-2.8,2.4-0.8,5.2,0,0-2,2.4-1.6,6.4l7.6,4s2,14.4,12.8,19.6c4.836,2.329,8-4.4,10-10.4z"/>
</g>
<g id="g322" fill="#FFF">
<path id="path324" d="m-59.6,12.56s-2.88-19.08-0.72-23.04c0,0,9.72-9,9.36-12.24,0,0-0.36-16.2-1.44-16.92s-7.92-6.12-13.32-0.36c0,0-9.36,16.2-8.64,21.96v1.8s-6.84-0.36-8.28,1.44c0,0-1.08,4.68-2.16,5.04,0,0-2.52,2.16-0.72,4.68,0,0-1.8,2.16-1.44,5.76l6.84,3.6s1.8,12.96,11.52,17.64c4.352,2.095,7.2-3.96,9-9.36z"/>
</g>
<g id="g326" fill="#eb955c">
<path id="path328" d="m-51.05-42.61c-1.09-0.86-8.58-6.63-14.43-0.39,0,0-10.14,17.55-9.36,23.79v1.95s-7.41-0.39-8.97,1.56c0,0-1.17,5.07-2.34,5.46,0,0-2.73,2.34-0.78,5.07,0,0-1.95,2.34-1.56,6.24l7.41,3.9s1.95,14.04,12.48,19.11c4.714,2.27,7.8-4.29,9.75-10.14,0,0-3.12-20.67-0.78-24.96,0,0,10.53-9.75,10.14-13.26,0,0-0.39-17.55-1.56-18.33z"/>
</g>
<g id="g330" fill="#f2b892">
<path id="path332" d="m-51.5-41.62c-0.98-0.92-8.36-6.46-14.06-0.38,0,0-9.88,17.1-9.12,23.18v1.9s-7.22-0.38-8.74,1.52c0,0-1.14,4.94-2.28,5.32,0,0-2.66,2.28-0.76,4.94,0,0-1.9,2.28-1.52,6.08l7.22,3.8s1.9,13.68,12.16,18.62c4.594,2.212,7.6-4.18,9.5-9.88,0,0-3.04-20.14-0.76-24.32,0,0,10.26-9.5,9.88-12.92,0,0-0.38-17.1-1.52-17.86z"/>
</g>
<g id="g334" fill="#f8dcc8">
<path id="path336" d="m-51.95-40.63c-0.87-0.98-8.14-6.29-13.69-0.37,0,0-9.62,16.65-8.88,22.57v1.85s-7.03-0.37-8.51,1.48c0,0-1.11,4.81-2.22,5.18,0,0-2.59,2.22-0.74,4.81,0,0-1.85,2.22-1.48,5.92l7.03,3.7s1.85,13.32,11.84,18.13c4.473,2.154,7.4-4.07,9.25-9.62,0,0-2.96-19.61-0.74-23.68,0,0,9.99-9.25,9.62-12.58,0,0-0.37-16.65-1.48-17.39z"/>
</g>
<g id="g338" fill="#FFF">
<path id="path340" d="m-59.6,12.46s-2.88-18.98-0.72-22.94c0,0,9.72-9,9.36-12.24,0,0-0.36-16.2-1.44-16.92-0.76-1.04-7.92-6.12-13.32-0.36,0,0-9.36,16.2-8.64,21.96v1.8s-6.84-0.36-8.28,1.44c0,0-1.08,4.68-2.16,5.04,0,0-2.52,2.16-0.72,4.68,0,0-1.8,2.16-1.44,5.76l6.84,3.6s1.8,12.96,11.52,17.64c4.352,2.095,7.2-4.06,9-9.46z"/>
</g>
<g id="g342" fill="#CCC">
<path id="path344" d="m-62.7,6.2s-21.6-10.2-22.5-11c0,0,9.1,8.2,9.9,8.2s12.6,2.8,12.6,2.8z"/>
</g>
<g id="g346" fill="#000">
<path id="path348" d="m-79.8,0s18.4,3.6,18.4,8c0,2.912-0.243,16.331-5.6,14.8-8.4-2.4-4.8-16.8-12.8-22.8z"/>
</g>
<g id="g350" fill="#99cc32">
<path id="path352" d="m-71.4,3.8s8.978,1.474,10,4.2c0.6,1.6,1.263,9.908-4.2,11-4.552,0.911-6.782-9.31-5.8-15.2z"/>
</g>
<g id="g354" fill="#000">
<path id="path356" d="m14.595,46.349c-0.497-1.742,0.814-1.611,2.605-2.149,2-0.6,14.2-4.4,15-7s14,1.8,14,1.8c1.8,0.8,6.2,3.4,6.2,3.4,4.8,1.2,11.4,1.6,11.4,1.6,2.4,1,5.8,3.8,5.8,3.8,14.6,10.2,27.001,3,27.001,3,19.999-6.6,13.999-23.8,13.999-23.8-3-9,0.2-12.4,0.2-12.4,0.2-3.8,7.4,2.6,7.4,2.6,2.6,4.2,3.4,9.2,3.4,9.2,8,11.2,4.6-6.6,4.6-6.6,0.2-1-2.6-4.6-2.6-5.8s-1.8-4.6-1.8-4.6c-3-3.4-0.6-10.4-0.6-10.4,1.8-13.8-0.4-12-0.4-12-1.2-1.8-10.4,8.2-10.4,8.2-2.2,3.4-8.2,5-8.2,5-2.799,1.8-6.199,0.4-6.199,0.4-2.6-0.4-8.2,6.6-8.2,6.6,2.8-0.2,5.2,4.2,7.6,4.4s4.2-2.4,5.799-3c1.6-0.6,4.4,5.2,4.4,5.2,0.4,2.6-5.2,7.4-5.2,7.4-0.4,4.6-1.999,3-1.999,3-3-0.6-4.2,3.2-5.2,7.8s-5.2,5-5.2,5c-1.6,7.4-2.801,4.4-2.801,4.4-0.2-5.6-6.2,0.2-6.2,0.2-1.2,2-5.8-0.2-5.8-0.2-6.8-2-4.4-4-4.4-4,1.8-2.2,13,0,13,0,2.2-1.6-5.8-5.6-5.8-5.6-0.6-1.8,0.4-6.2,0.4-6.2,1.2-3.2,8-8.8,8-8.8,9.401-1.2,6.601-2.8,6.601-2.8-6.2-5.2-12.001,2.4-12.001,2.4-2.2,6.2-19.6,21.2-19.6,21.2-4.8,3.4-2.2-3.4-6.2,0s-24.6-5.6-24.6-5.6c-11.562-1.193-14.294,14.549-17.823,11.429,0,0,5.418,8.52,3.818,2.92z"/>
</g>
<g id="g358" fill="#000">
<path id="path360" d="m209.4-120s-25.6,8-28.4,26.8c0,0-2.4,22.8,18,40.4,0,0,0.4,6.4,2.4,9.6,0,0-1.6,4.8,17.2-2.8l27.2-8.4s6.4-2.4,11.6-11.2,20.4-27.6,16.8-52.8c0,0,1.2-11.2-4.8-11.6,0,0-8.4-1.6-15.6,6,0,0-6.8,3.2-9.2,2.8l-35.2,1.2z"/>
</g>
<g id="g362" fill="#000">
<path id="path364" d="m264.02-120.99s2.1-8.93-2.74-4.09c0,0-7.04,5.72-14.52,5.72,0,0-14.52,2.2-18.92,15.4,0,0-3.96,26.84,3.96,32.56,0,0,4.84,7.48,11.88,0.88s22.54-36.83,20.34-50.47z"/>
</g>
<g id="g366" fill="#323232">
<path id="path368" d="m263.65-120.63s2.09-8.75-2.66-3.99c0,0-6.92,5.61-14.26,5.61,0,0-14.26,2.16-18.58,15.12,0,0-3.89,26.354,3.89,31.97,0,0,4.75,7.344,11.66,0.864,6.92-6.48,22.11-36.184,19.95-49.574z"/>
</g>
<g id="g370" fill="#666">
<path id="path372" d="m263.27-120.27s2.08-8.56-2.58-3.9c0,0-6.78,5.51-13.99,5.51,0,0-14,2.12-18.24,14.84,0,0-3.81,25.868,3.82,31.38,0,0,4.66,7.208,11.45,0.848,6.78-6.36,21.66-35.538,19.54-48.678z"/>
</g>
<g id="g374" fill="#999">
<path id="path376" d="m262.9-119.92s2.07-8.37-2.51-3.79c0,0-6.65,5.41-13.73,5.41,0,0-13.72,2.08-17.88,14.56,0,0-3.75,25.372,3.74,30.78,0,0,4.58,7.072,11.23,0.832,6.66-6.24,21.23-34.892,19.15-47.792z"/>
</g>
<g id="g378" fill="#CCC">
<path id="path380" d="m262.53-119.56s2.06-8.18-2.43-3.7c0,0-6.53,5.31-13.47,5.31,0,0-13.46,2.04-17.54,14.28,0,0-3.67,24.886,3.67,30.19,0,0,4.49,6.936,11.02,0.816,6.52-6.12,20.79-34.246,18.75-46.896z"/>
</g>
<g id="g382" fill="#FFF">
<path id="path384" d="m262.15-119.2s2.05-8-2.35-3.6c0,0-6.4,5.2-13.2,5.2,0,0-13.2,2-17.2,14,0,0-3.6,24.4,3.6,29.6,0,0,4.4,6.8,10.8,0.8s20.35-33.6,18.35-46z"/>
</g>
<g id="g386" fill="#992600">
<path id="path388" d="m50.6,84s-20.4-19.2-28.4-20c0,0-34.4-4-49.2,14,0,0,17.6-20.4,45.2-14.8,0,0-21.6-4.4-34-1.2l-26.4,14-2.8,4.8s4-14.8,22.4-20.8c0,0,22.8-4.8,33.6,0,0,0-21.6-6.8-31.6-4.8,0,0-30.4-2.4-43.2,24,0,0,4-14.4,18.8-21.6,0,0,13.6-8.8,34-6,0,0,14.4,3.2,19.6,5.6s4-0.4-4.4-5.2c0,0-5.6-10-19.6-9.6,0,0-42.8,3.6-53.2,15.6,0,0,13.6-11.2,24-14,0,0,22.4-8,30.8-7.2,0,0,24.8,1,32.4-3,0,0-11.2,5-8,8.2s10,10.8,10,12,24.2,23.3,27.8,27.7l2.2,2.3z"/>
</g>
<g id="g390" fill="#CCC">
<path id="path392" d="m189,278s-15.5-36.5-28-46c0,0,26,16,29.5,34,0,0,0,10-1.5,12z"/>
</g>
<g id="g394" fill="#CCC">
<path id="path396" d="m236,285.5s-26.5-55-45-79c0,0,43.5,37.5,48.5,64l0.5,5.5-3-2.5s-0.5,9-1,12z"/>
</g>
<g id="g398" fill="#CCC">
<path id="path400" d="m292.5,237s-62.5-59.5-64-62c0,0,60.5,66,63.5,73.5,0,0-2-9,0.5-11.5z"/>
</g>
<g id="g402" fill="#CCC">
<path id="path404" d="m104,280.5s19.5-52,38.5-29.5c0,0,15,10,14.5,13,0,0-4-6.5-22-6,0,0-19-3-31,22.5z"/>
</g>
<g id="g406" fill="#CCC">
<path id="path408" d="m294.5,153s-45-28.5-52.5-30c-11.81-2.36,49.5,29,54.5,39.5,0,0,2-2.5-2-9.5z"/>
</g>
<g id="g410" fill="#000">
<path id="path412" d="m143.8,259.6s20.4-2,27.2-8.8l4.4,3.6,17.6-38.4,3.6,5.2s14.4-14.8,13.6-22.8,12.8,6,12.8,6-0.8-11.6,6.4-4.8c0,0-2.4-15.6,6-7.6,0,0-10.54-30.16,12-4.4,5.6,6.4,1.2-0.4,1.2-0.4s-26-48-4.4-33.6c0,0,2-22.8,0.8-27.2s-3.2-26.8-8-32,0.4-6.8,6-1.6c0,0-11.2-24,2-12,0,0-3.6-15.2-8-18,0,0-5.6-17.2,9.6-6.4,0,0-4.4-12.4-7.6-15.6,0,0-11.6-27.6-4.4-22.8l4.4,3.6s-6.8-14-0.4-9.6,6.4,4,6.4,4-21.2-33.2-0.8-15.6c0,0-8.16-13.918-11.6-20.8,0,0-18.8-20.4-4.4-14l4.8,1.6s-8.8-10-16.8-11.6,2.4-8,8.8-6,22,9.6,22,9.6,12.8,18.8,16.8,19.2c0,0-20-7.6-14,0.4,0,0,14.4,14,7.2,13.6,0,0-6,7.2-1.2,16,0,0-18.46-18.391-3.6,7.2l6.8,16.4s-24.4-24.8-13.2-2.8c0,0,17.2,23.6,19.2,24s6.4,9.2,6.4,9.2l-4.4-2,5.2,8.8s-11.2-12-5.2,1.2l5.6,14.4s-20.4-22-6.8,7.6c0,0-16.4-5.2-7.6,12,0,0-1.6,16-1.2,21.2s1.6,33.6-2.8,41.6,6,27.2,8,31.2,5.6,14.8-3.2,5.6-4.4-3.6-2.4,5.2,8,24.4,7.2,30c0,0-1.2,1.2-4.4-2.4,0,0-14.8-22.8-13.2-8.4,0,0-1.2,8-4.4,16.8,0,0-3.2,10.8-3.2,2,0,0-3.2-16.8-6-9.2s-6.4,13.6-9.2,16-8-20.4-9.2-10c0,0-12-12.4-16.8,4l-11.6,16.4s-0.4-12.4-1.6-6.4c0,0-30,6-40.4,1.6z"/>
</g>
<g id="g414" fill="#000">
<path id="path416" d="m109.4-97.2s-11.599-8-15.599-7.6,27.599-8.8,68.799,18.8c0,0,4.8,2.8,8.4,2.4,0,0,3.2,2.4,0.4,6,0,0-8.8,9.6,2.4,20.8,0,0,18.4,6.8,12.8-2,0,0,10.8,4,13.2,8s1.2,0,1.2,0l-12.4-12.4s-5.2-2-8-10.4-5.2-18.4-0.8-21.6c0,0-4,4.4-3.2,0.4s4.4-7.6,6-8,18-16.2,24.8-16.6c0,0-9.2,1.4-12.2,0.4s-29.6-12.4-35.6-13.6c0,0-16.8-6.6-4.8-4.6,0,0,35.8,3.8,54,17,0,0-7.2-8.4-25.6-15.4,0,0-22.2-12.6-57.4-7.6,0,0-17.8,3.2-25.6,5,0,0-2.599-0.6-3.199-1s-12.401-9.4-40.001-2.4c0,0-17,4.6-25.6,9.4,0,0-15.2,1.2-18.8,4.4,0,0-18.6,14.6-20.6,15.4s-13.4,8.4-14.2,8.8c0,0,24.6-6.6,27-9s19.8-5,22.2-3.6,10.8,0.8,1.2,1.4c0,0,75.6,14.8,76.4,16.8s4.8,0.8,4.8,0.8z"/>
</g>
<g id="g418" fill="#cc7226">
<path id="path420" d="m180.8-106.4s-10.2-7.4-12.2-7.4-14.4-10.2-18.6-9.8-16.4-9.6-43.8-1.4c0,0-0.6-2,3-2.8,0,0,6.4-2.2,6.8-2.8,0,0,20.2-4.2,27.4-0.6,0,0,9.2,2.6,15.4,8.8,0,0,11.2,3.2,14.4,2.2,0,0,8.8,2.2,9.2,4,0,0,5.8,3,4,5.6,0,0,0.4,1.6-5.6,4.2z"/>
</g>
<g id="g422" fill="#cc7226">
<path id="path424" d="m168.33-108.51c0.81,0.63,1.83,0.73,2.43,1.54,0.24,0.31-0.05,0.64-0.37,0.74-1.04,0.31-2.1-0.26-3.24,0.33-0.4,0.21-1.04,0.03-1.6-0.12-1.63-0.44-3.46-0.47-5.15,0.22-1.98-1.13-4.34-0.54-6.42-1.55-0.06-0.02-0.28,0.32-0.36,0.3-3.04-1.15-6.79-0.87-9.22-3.15-2.43-0.41-4.78-0.87-7.21-1.55-1.82-0.51-3.23-1.5-4.85-2.33-1.38-0.71-2.83-1.23-4.37-1.61-1.86-0.45-3.69-0.34-5.58-0.86-0.1-0.02-0.29,0.32-0.37,0.3-0.32-0.11-0.62-0.69-0.79-0.64-1.68,0.52-3.17-0.45-4.83-0.11-1.18-1.22-2.9-0.98-4.45-1.42-2.97-0.85-6.12,0.42-9.15-0.58,4.11-1.84,8.8-0.61,12.86-2.68,2.33-1.18,4.99-0.08,7.56-0.84,0.49-0.15,1.18-0.35,1.58,0.32,0.14-0.14,0.32-0.37,0.38-0.35,2.44,1.16,4.76,2.43,7.24,3.5,0.34,0.15,0.88-0.09,1.13,0.12,1.52,1.21,3.46,1.11,4.85,2.33,1.7-0.5,3.49-0.12,5.22-0.75,0.08-0.02,0.31,0.32,0.34,0.3,1.14-0.75,2.29-0.48,3.18-0.18,0.34,0.12,1,0.37,1.31,0.44,1.12,0.27,1.98,0.75,3.16,0.94,0.11,0.02,0.3-0.32,0.37-0.3,1.12,0.44,2.16,0.39,2.82,1.55,0.14-0.14,0.3-0.37,0.38-0.35,1.03,0.34,1.68,1.1,2.78,1.34,0.48,0.1,1.1,0.73,1.67,0.91,2.39,0.73,4.24,2.26,6.43,3.15,0.76,0.31,1.64,0.55,2.27,1.04z"/>
</g>
<g id="g426" fill="#cc7226">
<path id="path428" d="m91.696-122.74c-2.518-1.72-4.886-2.83-7.328-4.62-0.181-0.13-0.541,0.04-0.743-0.08-1.007-0.61-1.895-1.19-2.877-1.89-0.539-0.38-1.36-0.37-1.868-0.63-2.544-1.29-5.173-1.85-7.68-3.04,0.682-0.64,1.804-0.39,2.4-1.2,0.195,0.28,0.433,0.56,0.786,0.37,1.678-0.9,3.528-1.05,5.204-0.96,1.704,0.09,3.424,0.39,5.199,0.67,0.307,0.04,0.506,0.56,0.829,0.66,2.228,0.66,4.617,0.14,6.736,0.98,1.591,0.63,3.161,1.45,4.4,2.72,0.252,0.26-0.073,0.57-0.353,0.76,0.388-0.11,0.661,0.1,0.772,0.41,0.084,0.24,0.084,0.54,0,0.78-0.112,0.31-0.391,0.41-0.765,0.46-1.407,0.19,0.365-1.19-0.335-0.74-1.273,0.82-0.527,2.22-1.272,3.49-0.28-0.19-0.51-0.41-0.4-0.8,0.234,0.52-0.368,0.81-0.536,1.13-0.385,0.72-1.284,2.14-2.169,1.53z"/>
</g>
<g id="g430" fill="#cc7226">
<path id="path432" d="m59.198-115.39c-3.154-0.79-6.204-0.68-9.22-1.96-0.067-0.02-0.29,0.32-0.354,0.3-1.366-0.6-2.284-1.56-3.36-2.61-0.913-0.89-2.571-0.5-3.845-0.99-0.324-0.12-0.527-0.63-0.828-0.67-1.219-0.16-2.146-1.11-3.191-1.68,2.336-0.8,4.747-0.76,7.209-1.15,0.113-0.02,0.258,0.31,0.391,0.31,0.136,0,0.266-0.23,0.4-0.36,0.195,0.28,0.497,0.61,0.754,0.35,0.548-0.54,1.104-0.35,1.644-0.31,0.144,0.01,0.269,0.32,0.402,0.32,0.136,0,0.267-0.32,0.4-0.32,0.136,0,0.267,0.32,0.4,0.32,0.136,0,0.266-0.23,0.4-0.36,0.692,0.78,1.577,0.23,2.399,0.41,1.038,0.22,1.305,1.37,2.379,1.67,4.715,1.3,8.852,3.45,13.215,5.54,0.307,0.14,0.517,0.39,0.407,0.78,0.267,0,0.58-0.09,0.77,0.04,1.058,0.74,2.099,1.28,2.796,2.38,0.216,0.34-0.113,0.75-0.346,0.7-4.429-1-8.435-1.61-12.822-2.71z"/>
</g>
<g id="g434" fill="#cc7226">
<path id="path436" d="m45.338-71.179c-1.592-1.219-2.176-3.25-3.304-5.042-0.214-0.34,0.06-0.654,0.377-0.743,0.56-0.159,1.103,0.319,1.512,0.521,1.745,0.862,3.28,2.104,5.277,2.243,1.99,2.234,6.25,2.619,6.257,6,0.001,0.859-1.427-0.059-1.857,0.8-2.451-1.003-4.84-0.9-7.22-2.367-0.617-0.381-0.287-0.834-1.042-1.412z"/>
</g>
<g id="g438" fill="#cc7226">
<path id="path440" d="m17.8-123.76c0.135,0,7.166,0.24,7.149,0.35-0.045,0.31-7.775,1.36-8.139,1.19-0.164-0.08-7.676,2.35-7.81,2.22,0.268-0.14,8.534-3.76,8.8-3.76z"/>
</g>
<g id="g442" fill="#000">
<path id="path444" d="m33.2-114s-14.8,1.8-19.2,3-23,8.8-26,10.8c0,0-13.4,5.4-30.4,25.4,0,0,7.6-3.4,9.8-6.2,0,0,13.6-12.6,13.4-10,0,0,12.2-8.6,11.6-6.4,0,0,24.4-11.2,22.4-8,0,0,21.6-4.6,20.6-2.6,0,0,18.8,4.4,16,4.6,0,0-5.8,1.2,0.6,4.8,0,0-3.4,4.4-8.8,0.4s-2.4-1.8-7.4-0.8c0,0-2.6,0.8-7.2-3.2,0,0-5.6-4.6-14.4-1,0,0-30.6,12.6-32.6,13.2,0,0-3.6,2.8-6,6.4,0,0-5.8,4.4-8.8,5.8,0,0-12.8,11.6-14,13,0,0-3.4,5.2-4.2,5.6,0,0,6.4-3.8,8.4-5.8,0,0,14-10,19.4-10.8,0,0,4.4-3,5.2-4.4,0,0,14.4-9.2,18.6-9.2,0,0,9.2,5.2,11.6-1.8,0,0,5.8-1.8,11.4-0.6,0,0,3.2-2.6,2.4-4.8,0,0,1.6-1.8,2.6,2,0,0,3.4,3.6,8.2,1.6,0,0,4-0.2,2,2.2,0,0-4.4,3.8-16.2,4,0,0-12.4,0.6-28.8,8.2,0,0-29.8,10.4-39,20.8,0,0-6.4,8.8-11.8,10,0,0-5.8,0.8-11.8,8.2,0,0,9.8-5.8,18.8-5.8,0,0,4-2.4,0.2,1.2,0,0-3.6,7.6-2,13,0,0-0.6,5.2-1.4,6.8,0,0-7.8,12.8-7.8,15.2s1.2,12.2,1.6,12.8-1-1.6,2.8,0.8,6.6,4,7.4,6.8-2-5.4-2.2-7.2-4.4-9-3.6-11.4c0,0,1,1,1.8,2.4,0,0-0.6-0.6,0-4.2,0,0,0.8-5.2,2.2-8.4s3.4-7,3.8-7.8,0.4-6.6,1.8-4l3.4,2.6s-2.8-2.6-0.6-4.8c0,0-1-5.6,0.8-8.2,0,0,7-8.4,8.6-9.4s0.2-0.6,0.2-0.6,6-4.2,0.2-2.6c0,0-4,1.6-7,1.6,0,0-7.6,2-3.6-2.2s14-9.6,17.8-9.4l0.8,1.6,11.2-2.4-1.2,0.8s-0.2-0.2,4-0.6,10,1,11.4-0.8,4.8-2.8,4.4-1.4-0.6,3.4-0.6,3.4,5-5.8,4.4-3.6-8.8,7.4-10.2,13.6l10.4-8.2,3.6-3s3.6,2.2,3.8,0.6,4.8-7.4,6-7.2,3.2-2.6,3,0,7.4,8,7.4,8,3.2-1.8,4.6-0.4,5.6-19.8,5.6-19.8l25-10.6,43.6-3.4-16.999-6.8-61.001-11.4z"/>
</g>
<g id="g446" stroke-width="2" stroke="#4c0000">
<path id="path448" d="m51.4,85s-15-16.8-23.4-19.4c0,0-13.4-6.8-38,1"/>
</g>
<g id="g450" stroke-width="2" stroke="#4c0000">
<path id="path452" d="m24.8,64.2s-25.2-8-40.6-3.8c0,0-18.4,2-26.8,15.8"/>
</g>
<g id="g454" stroke-width="2" stroke="#4c0000">
<path id="path456" d="m21.2,63s-17-7.2-31.8-9.4c0,0-16.6-2.6-33.2,4.6,0,0-12.2,6-17.6,16.2"/>
</g>
<g id="g458" stroke-width="2" stroke="#4c0000">
<path id="path460" d="m22.2,63.4s-15.4-11-16.4-12.4c0,0-7-11-20-11.4,0,0-21.4,0.8-38.6,8.8"/>
</g>
<g id="g462" fill="#000">
<path id="path464" d="M20.895,54.407c1.542,1.463,28.505,30.393,28.505,30.393,35.2,36.6,7.2,2.4,7.2,2.4-7.6-4.8-16.8-23.6-16.8-23.6-1.2-2.8,14,7.2,14,7.2,4,0.8,17.6,20,17.6,20-6.8-2.4-2,4.8-2,4.8,2.8,2,23.201,17.6,23.201,17.6,3.6,4,7.599,5.6,7.599,5.6,14-5.2,7.6,8,7.6,8,2.4,6.8,8-4.8,8-4.8,11.2-16.8-5.2-14.4-5.2-14.4-30,2.8-36.8-13.2-36.8-13.2-2.4-2.4,6.4,0,6.4,0,8.401,2-7.2-12.4-7.2-12.4,2.4,0,11.6,6.8,11.6,6.8,10.401,9.2,12.401,7.2,12.401,7.2,17.999-8.8,28.399-1.2,28.399-1.2,2,1.6-3.6,8.4-2,13.6s6.4,17.6,6.4,17.6c-2.4,1.6-2,12.4-2,12.4,16.8,23.2,7.2,21.2,7.2,21.2-15.6-0.4-0.8,7.2-0.8,7.2,3.2,2,12,9.2,12,9.2-2.8-1.2-4.4,4-4.4,4,4.8,4,2,8.8,2,8.8-6,1.2-7.2,5.2-7.2,5.2,6.8,8-3.2,8.4-3.2,8.4,3.6,4.4-1.2,16.4-1.2,16.4-4.8,0-11.2,5.6-11.2,5.6,2.4,4.8-8,10.4-8,10.4-8.4,1.6-5.6,8.4-5.6,8.4-7.999,6-10.399,22-10.399,22-0.8,10.4-3.2,13.6,2,11.6,5.199-2,4.399-14.4,4.399-14.4-4.799-15.6,38-31.6,38-31.6,4-1.6,4.8-6.8,4.8-6.8,2,0.4,10.8,8,10.8,8,7.6,11.2,8,2,8,2,1.2-3.6-0.4-9.6-0.4-9.6,6-21.6-8-28-8-28-10-33.6,4-25.2,4-25.2,2.8,5.6,13.6,10.8,13.6,10.8l3.6-2.4c-1.6-4.8,6.8-10.8,6.8-10.8,2.8,6.4,8.8-1.6,8.8-1.6,3.6-24.4,16-10,16-10,4,1.2,5.2-5.6,5.2-5.6,3.6-10.4,0-24,0-24,3.6-0.4,13.2,5.6,13.2,5.6,2.8-3.6-6.4-20.4-2.4-18s8.4,4,8.4,4c0.8-2-9.2-14.4-9.2-14.4-4.4-2.8-9.6-23.2-9.6-23.2,7.2,3.6-2.8-11.6-2.8-11.6,0-3.2,6-14.4,6-14.4-0.8-6.8,0-6.4,0-6.4,2.8,1.2,10.8,2.8,4-3.6s0.8-11.2,0.8-11.2c4.4-2.8-9.2-2.4-9.2-2.4-5.2-4.4-4.8-8.4-4.8-8.4,8,2-6.4-12.4-8.8-16s7.2-8.8,7.2-8.8c13.2-3.6,1.6-6.8,1.6-6.8-19.6,0.4-8.8-10.4-8.8-10.4,6,0.4,4.4-2,4.4-2-5.2-1.2-14.8-7.6-14.8-7.6-4-3.6-0.4-2.8-0.4-2.8,16.8,1.2-12-10-12-10,8,0-10-10.4-10-10.4-2-1.6-5.2-9.2-5.2-9.2-6-5.2-10.8-12-10.8-12-0.4-4.4-5.2-9.2-5.2-9.2-11.6-13.6-17.2-13.2-17.2-13.2-14.8-3.6-20-2.8-20-2.8l-52.8,4.4c-26.4,12.8-18.6,33.8-18.6,33.8,6.4,8.4,15.6,4.6,15.6,4.6,4.6-6.2,16.2-4,16.2-4,20.401,3.2,17.801-0.4,17.801-0.4-2.4-4.6-18.601-10.8-18.801-11.4s-9-4-9-4c-3-1.2-7.4-10.4-7.4-10.4-3.2-3.4,12.6,2.4,12.6,2.4-1.2,1,6.2,5,6.2,5,17.401-1,28.001,9.8,28.001,9.8,10.799,16.6,10.999,8.4,10.999,8.4,2.8-9.4-9-30.6-9-30.6,0.4-2,8.6,4.6,8.6,4.6,1.4-2,2.2,3.8,2.2,3.8,0.2,2.4,4,10.4,4,10.4,2.8,13,6.4,5.6,6.4,5.6l4.6,9.4c1.4,2.6-4.6,10.2-4.6,10.2-0.2,2.8,0.6,2.6-5,10.2s-2.2,12-2.2,12c-1.4,6.6,7.4,6.2,7.4,6.2,2.6,2.2,6,2.2,6,2.2,1.8,2,4.2,1.4,4.2,1.4,1.6-3.8,7.8-1.8,7.8-1.8,1.4-2.4,9.6-2.8,9.6-2.8,1-2.6,1.4-4.2,4.8-4.8s-21.2-43.6-21.2-43.6c6.4-0.8-1.8-13.2-1.8-13.2-2.2-6.6,9.2,8,11.4,9.4s3.2,3.6,1.6,3.4-3.4,2-2,2.2,14.4,15.2,17.8,25.4,9.4,14.2,15.6,20.2,5.4,30.2,5.4,30.2c-0.4,8.8,5.6,19.4,5.6,19.4,2,3.8-2.2,22-2.2,22-2,2.2-0.6,3-0.6,3,1,1.2,7.8,14.4,7.8,14.4-1.8-0.2,1.8,3.4,1.8,3.4,5.2,6-1.2,3-1.2,3-6-1.6,1,8.2,1,8.2,1.2,1.8-7.8-2.8-7.8-2.8-9.2-0.6,2.4,6.6,2.4,6.6,8.6,7.2-2.8,2.8-2.8,2.8-4.6-1.8-1.4,5-1.4,5,3.2,1.6,20.4,8.6,20.4,8.6,0.4,3.8-2.6,8.8-2.6,8.8,0.4,4-1.8,7.4-1.8,7.4-1.2,8.2-1.8,9-1.8,9-4.2,0.2-11.6,14-11.6,14-1.8,2.6-12,14.6-12,14.6-2,7-20-0.2-20-0.2-6.6,3.4-4.6,0-4.6,0-0.4-2.2,4.4-8.2,4.4-8.2,7-2.6,4.4-13.4,4.4-13.4,4-1.4-7.2-4.2-7-5.4s6-2.6,6-2.6c8-2,3.6-4.4,3.6-4.4-0.6-4,2.4-9.6,2.4-9.6,11.6-0.8,0-17,0-17-10.8-7.6-11.8-13.4-11.8-13.4,12.6-8.2,4.4-20.6,4.6-24.2s1.4-25.2,1.4-25.2c-2-6.2-5-19.8-5-19.8,2.2-5.2,9.6-17.8,9.6-17.8,2.8-4.2,11.6-9,9.4-12s-10-1.2-10-1.2c-7.8-1.4-7.2,3.8-7.2,3.8-1.6,1-2.4,6-2.4,6-0.72,7.933-9.6,14.2-9.6,14.2-11.2,6.2-2,10.2-2,10.2,6,6.6-3.8,6.8-3.8,6.8-11-1.8-2.8,8.4-2.8,8.4,10.8,12.8,7.8,15.6,7.8,15.6-10.2,1,2.4,10.2,2.4,10.2s-0.8-2-0.6-0.2,3.2,6,4,8-3.2,2.2-3.2,2.2c0.6,9.6-14.8,5.4-14.8,5.4l-1.6,0.2c-1.6,0.2-12.8-0.6-18.6-2.8s-12.599-2.2-12.599-2.2-4,1.8-11.601,1.6c-7.6-0.2-15.6,2.6-15.6,2.6-4.4-0.4,4.2-4.8,4.4-4.6s5.8-5.4-2.2-4.8c-21.797,1.635-32.6-8.6-32.6-8.6-2-1.4-4.6-4.2-4.6-4.2-10-2,1.4,12.4,1.4,12.4,1.2,1.4-0.2,2.4-0.2,2.4-0.8-1.6-8.6-7-8.6-7-2.811-0.973-4.174-2.307-6.505-4.793z"/>
</g>
<g id="g466" fill="#4c0000">
<path id="path468" d="m-3,42.8s11.6,5.6,14.2,8.4,16.6,14.2,16.6,14.2-5.4-2-8-3.8-13.4-10-13.4-10-3.8-6-9.4-8.8z"/>
</g>
<g id="g470" fill="#99cc32">
<path id="path472" d="M-61.009,11.603c0.337-0.148-0.187-2.86-0.391-3.403-1.022-2.726-10-4.2-10-4.2-0.227,1.365-0.282,2.961-0.176,4.599,0,0,4.868,5.519,10.567,3.004z"/>
</g>
<g id="g474" fill="#659900">
<path id="path476" d="M-61.009,11.403c-0.449,0.158-0.015-2.734-0.191-3.203-1.022-2.726-10.2-4.3-10.2-4.3-0.227,1.365-0.282,2.961-0.176,4.599,0,0,4.268,5.119,10.567,2.904z"/>
</g>
<g id="g478" fill="#000">
<path id="path480" d="m-65.4,11.546c-0.625,0-1.131-1.14-1.131-2.546,0-1.405,0.506-2.545,1.131-2.545s1.132,1.14,1.132,2.545c0,1.406-0.507,2.546-1.132,2.546z"/>
</g>
<g id="g482" fill="#000">
<path id="path484" d="M-65.4,9z"/>
</g>
<g id="g486" fill="#000">
<path id="path488" d="m-111,109.6s-5.6,10,19.2,4c0,0,14-1.2,16.4-3.6,1.2,0.8,9.566,3.73,12.4,4.4,6.8,1.6,15.2-8.4,15.2-8.4s4.6-10.5,7.4-10.5-0.4,1.6-0.4,1.6-6.6,10.1-6.2,11.7c0,0-5.2,20-21.2,20.8,0,0-16.15,0.95-14.8,6.8,0,0,8.8-2.4,11.2,0,0,0,10.8-0.4,2.8,6l-6.8,11.6s0.14,3.92-10,0.4c-9.8-3.4-20.1-16.3-20.1-16.3s-15.95-14.55-5.1-28.5z"/>
</g>
<g id="g490" fill="#e59999">
<path id="path492" d="m-112.2,113.6s-2,9.6,34.8-0.8l6.8,0.8c2.4,0.8,14.4,3.6,16.4,2.4,0,0-7.2,13.6-18.8,12,0,0-13.2,1.6-12.8,6.4,0,0,4,7.2,8.8,9.6,0,0,2.8,2.4,2.4,5.6s-3.2,4.8-5.2,5.6-5.2-2.4-6.8-2.4-10-6.4-14.4-11.2-12.8-16.8-12.4-19.6,1.2-8.4,1.2-8.4z"/>
</g>
<g id="g494" fill="#b26565">
<path id="path496" d="m-109,131.05c2.6,3.95,5.8,8.15,8,10.55,4.4,4.8,12.8,11.2,14.4,11.2s4.8,3.2,6.8,2.4,4.8-2.4,5.2-5.6-2.4-5.6-2.4-5.6c-3.066-1.53-5.806-5.02-7.385-7.35,0,0,0.185,2.55-5.015,1.75s-10.4-3.6-12-6.8-4-5.6-2.4-2,4,7.2,5.6,7.6,1.2,1.6-1.2,1.2-5.2-0.8-9.6-6z"/>
</g>
<g id="g498" fill="#992600">
<path id="path500" d="m-111.6,110s1.8-13.6,3-17.6c0,0-0.8-6.8,1.6-11s4.4-10.4,7.4-15.8,3.2-9.4,7.2-11,10-10.2,12.8-11.2,2.6-0.2,2.6-0.2,6.8-14.8,20.4-10.8c0,0-16.2-2.8-0.4-12.2,0,0-4.8,1.1-1.5-5.9,2.201-4.668,1.7,2.1-9.3,13.9,0,0-5,8.6-10.2,11.6s-17.2,10-18.4,13.8-4.4,9.6-6.4,11.2-4.8,5.8-5.2,9.2c0,0-1.2,4-2.6,5.2s-1.6,4.4-1.6,6.4-2,4.8-1.8,7.2c0,0,0.8,19,0.4,21l2-3.8z"/>
</g>
<g id="g502" fill="#FFF">
<path id="path504" d="m-120.2,114.6s-2-1.4-6.4,4.6c0,0,7.3,33,7.3,34.4,0,0,1.1-2.1-0.2-9.3s-2.2-19.9-2.2-19.9l1.5-9.8z"/>
</g>
<g id="g506" fill="#992600">
<path id="path508" d="m-98.6,54s-17.6,3.2-17.2,32.4l-0.8,24.8s-1.2-25.6-2.4-27.2,2.8-12.8-0.4-6.8c0,0-14,14-6,35.2,0,0,1.5,3.3-1.5-1.3,0,0-4.6-12.6-3.5-19,0,0,0.2-2.2,2.1-5,0,0,8.6-11.7,11.3-14,0,0,1.8-14.4,17.2-19.6,0,0,5.7-2.3,1.2,0.5z"/>
</g>
<g id="g510" fill="#000">
<path id="path512" d="m40.8-12.2c0.66-0.354,0.651-1.324,1.231-1.497,1.149-0.344,1.313-1.411,1.831-2.195,0.873-1.319,1.066-2.852,1.648-4.343,0.272-0.7,0.299-1.655-0.014-2.315-1.174-2.481-1.876-4.93-3.318-7.356-0.268-0.45-0.53-1.244-0.731-1.842-0.463-1.384-1.72-2.375-2.58-3.695-0.288-0.441,0.237-1.366-0.479-1.45-0.897-0.105-2.346-0.685-2.579,0.341-0.588,2.587,0.423,5.11,1.391,7.552-0.782,0.692-0.448,1.613-0.296,2.38,0.71,3.606-0.488,6.958-1.249,10.432-0.023,0.104,0.319,0.302,0.291,0.364-1.222,2.686-2.674,5.131-4.493,7.512-0.758,0.992-1.63,1.908-2.127,2.971-0.368,0.787-0.776,1.753-0.526,2.741-3.435,2.78-5.685,6.625-8.296,10.471-0.462,0.68-0.171,1.889,0.38,2.158,0.813,0.398,1.769-0.626,2.239-1.472,0.389-0.698,0.742-1.348,1.233-1.991,0.133-0.175-0.046-0.594,0.089-0.715,2.633-2.347,4.302-5.283,6.755-7.651,1.95-0.329,3.487-1.327,5.235-2.34,0.308-0.179,0.832,0.07,1.122-0.125,1.753-1.177,1.751-3.213,1.857-5.123,0.05-0.884,0.246-2.201,1.386-2.812z"/>
</g>
<g id="g514" fill="#000">
<path id="path516" d="m31.959-16.666c0.124-0.077-0.031-0.5,0.078-0.716,0.162-0.324,0.565-0.512,0.727-0.836,0.109-0.216-0.054-0.596,0.082-0.738,2.333-2.447,2.59-5.471,1.554-8.444,1.024-0.62,1.085-1.882,0.66-2.729-0.853-1.7-1.046-3.626-2.021-5.169-0.802-1.269-2.38-2.513-3.751-1.21-0.421,0.4-0.742,1.187-0.464,1.899,0.064,0.163,0.349,0.309,0.322,0.391-0.107,0.324-0.653,0.548-0.659,0.82-0.03,1.496-0.984,3.007-0.354,4.336,0.772,1.629,1.591,3.486,2.267,5.262-1.234,2.116-0.201,4.565-1.954,6.442-0.136,0.146-0.127,0.532-0.005,0.734,0.292,0.486,0.698,0.892,1.184,1.184,0.202,0.121,0.55,0.123,0.75-0.001,0.578-0.362,0.976-0.849,1.584-1.225z"/>
</g>
<g id="g518" fill="#000">
<path id="path520" d="m94.771-26.977c1.389,1.792,1.679,4.587-0.37,5.977,0.55,3.309,3.901,1.33,5.999,0.8-0.11-0.388,0.12-0.732,0.4-0.737,1.06-0.015,1.74-1.047,2.8-0.863,0.44-1.557,2.07-2.259,2.72-3.639,1.72-3.695,1.13-7.968-1.45-11.214-0.2-0.254,0.01-0.771-0.11-1.133-0.76-2.211-2.82-2.526-4.76-3.214-1.176-3.875-1.837-7.906-3.599-11.6-1.614-0.25-2.312-1.989-3.649-2.709-1.333-0.719-1.901,0.86-1.86,1.906,0.007,0.205,0.459,0.429,0.289,0.794-0.076,0.164-0.336,0.275-0.336,0.409,0.001,0.135,0.222,0.266,0.356,0.4-0.918,0.82-2.341,1.297-2.636,2.442-0.954,3.71,1.619,6.835,3.287,10.036,0.591,1.135-0.145,2.406-0.905,3.614-0.438,0.695-0.33,1.822-0.054,2.678,0.752,2.331,2.343,4.07,3.878,6.053z"/>
</g>
<g id="g522" fill="#000">
<path id="path524" d="m57.611-8.591c-1.487,1.851-4.899,4.42-1.982,6.348,0.194,0.129,0.564,0.133,0.737-0.001,2.021-1.565,4.024-2.468,6.46-3.05,0.124-0.029,0.398,0.438,0.767,0.277,1.613-0.703,3.623-0.645,4.807-1.983,3.767,0.224,7.332-0.892,10.723-2.2,1.161-0.448,2.431-1.007,3.632-1.509,1.376-0.576,2.58-1.504,3.692-2.645,0.133-0.136,0.487-0.046,0.754-0.046-0.04-0.863,0.922-0.99,1.169-1.612,0.092-0.232-0.058-0.628,0.075-0.73,2.138-1.63,3.058-3.648,1.889-6.025-0.285-0.578-0.534-1.196-1.1-1.672-1.085-0.911-2.187-0.057-3.234-0.361-0.159,0.628-0.888,0.456-1.274,0.654-0.859,0.439-2.192-0.146-3.051,0.292-1.362,0.695-2.603,0.864-4.025,1.241-0.312,0.082-1.09-0.014-1.25,0.613-0.134-0.134-0.282-0.368-0.388-0.346-1.908,0.396-3.168,0.61-4.469,2.302-0.103,0.133-0.545-0.046-0.704,0.089-0.957,0.808-1.362,2.042-2.463,2.714-0.201,0.123-0.553-0.045-0.747,0.084-0.646,0.431-1.013,1.072-1.655,1.519-0.329,0.229-0.729-0.096-0.697-0.352,0.245-1.947,0.898-3.734,0.323-5.61,2.077-2.52,4.594-4.469,6.4-7.2,0.015-2.166,0.707-4.312,0.594-6.389-0.01-0.193-0.298-0.926-0.424-1.273-0.312-0.854,0.594-1.92-0.25-2.644-1.404-1.203-2.696-0.327-3.52,1.106-1.838,0.39-3.904,1.083-5.482-0.151-1.007-0.787-1.585-1.693-2.384-2.749-0.985-1.302-0.65-2.738-0.58-4.302,0.006-0.128-0.309-0.264-0.309-0.398,0.001-0.135,0.221-0.266,0.355-0.4-0.706-0.626-0.981-1.684-2-2,0.305-1.092-0.371-1.976-1.242-2.278-1.995-0.691-3.672,1.221-5.564,1.294-0.514,0.019-0.981-1.019-1.63-1.344-0.432-0.216-1.136-0.249-1.498,0.017-0.688,0.504-1.277,0.618-2.035,0.823-1.617,0.436-2.895,1.53-4.375,2.385-1.485,0.857-2.44,2.294-3.52,3.614-0.941,1.152-1.077,3.566,0.343,4.066,1.843,0.65,3.147-2.053,5.113-1.727,0.312,0.051,0.518,0.362,0.408,0.75,0.389,0.109,0.607-0.12,0.8-0.4,0.858,1.019,2.022,1.356,2.96,2.229,0.97,0.904,2.716,0.486,3.731,1.483,1.529,1.502,0.97,4.183,2.909,5.488-0.586,1.313-1.193,2.59-1.528,4.017-0.282,1.206,0.712,2.403,1.923,2.312,1.258-0.094,1.52-0.853,2.005-1.929,0.267,0.267,0.736,0.564,0.695,0.78-0.457,2.387-1.484,4.38-1.942,6.811-0.059,0.317-0.364,0.519-0.753,0.409-0.468,4.149-4.52,6.543-7.065,9.708-0.403,0.502-0.407,1.751,0.002,2.154,1.403,1.387,3.363-0.159,5.063-0.662,0.213-1.206,1.072-2.148,2.404-2.092,0.256,0.01,0.491-0.532,0.815-0.662,0.348-0.138,0.85,0.086,1.136-0.112,1.729-1.195,3.137-2.301,4.875-3.49,0.192-0.131,0.536,0.028,0.752-0.08,0.325-0.162,0.512-0.549,0.835-0.734,0.348-0.2,0.59,0.09,0.783,0.37-0.646,0.349-0.65,1.306-1.232,1.508-0.775,0.268-1.336,0.781-2.01,1.228-0.292,0.193-0.951-0.055-1.055,0.124-0.598,1.028-1.782,1.466-2.492,2.349z"/>
</g>
<g id="g526" fill="#000">
<path id="path528" d="m2.2-58s-9.238-2.872-20.4,22.8c0,0-2.4,5.2-4.8,7.2s-13.6,5.6-15.6,9.6l-10.4,16s14.8-16,18-18.4c0,0,8-8.4,4.8-1.6,0,0-14,10.8-12.8,20,0,0-5.6,14.4-6.4,16.4,0,0,16-32,18.4-33.2s3.6-1.2,2.4,2.4-1.6,20-4.4,22c0,0,8-20.4,7.2-23.6,0,0,3.2-3.6,5.6,1.6l-1.2,16,4.4,12s-2.4-11.2-0.8-26.8c0,0-2-10.4,2-4.8s13.6,11.6,13.6,16.4c0,0-5.2-17.6-14.4-22.4l-4,6-1.2-2s-3.6-0.8,0.8-7.6,4-7.6,4-7.6,6.4,7.2,8,7.2c0,0,13.2-7.6,14.4,16.8,0,0,6.8-14.4-2.4-21.2,0,0-14.8-2-13.6-7.2l7.2-12.4c3.6-5.2,2-2.4,2-2.4z"/>
</g>
<g id="g530" fill="#000">
<path id="path532" d="m-17.8-41.6-16,5.2-7.2,9.6s17.2-10,21.2-11.2,2-3.6,2-3.6z"/>
</g>
<g id="g534" fill="#000">
<path id="path536" d="m-57.8-35.2s-2,1.2-2.4,4-2.8,3.2-2,6,2.8,5.2,2.8,1.2,1.6-6,2.4-7.2,2.4-5.6-0.8-4z"/>
</g>
<g id="g538" fill="#000">
<path id="path540" d="m-66.6,26s-8.4-4-11.6-7.6-2.748,1.566-7.6,1.2c-5.847-0.441-4.8-16.4-4.8-16.4l-4,7.6s-1.2,14.4,6.8,12c3.907-1.172,5.2,0.4,3.6,1.2s5.6,1.2,2.8,2.8,11.6-3.6,9.2,6.8l5.6-7.6z"/>
</g>
<g id="g542" fill="#000">
<path id="path544" d="m-79.2,40.4s-15.4,4.4-19-5.2c0,0-4.8,2.4-2.6,5.4s3.4,3.4,3.4,3.4,5.4,1.2,4.8,2-3,4.2-3,4.2,10.2-6,16.4-9.8z"/>
</g>
<g id="g546" fill="#FFF">
<path id="path548" d="m149.2,118.6c-0.43,2.14-2.1,2.94-4,3.6-1.92-0.96-4.51-4.06-6.4-2-0.47-0.48-1.25-0.54-1.6-1.2-0.46-0.9-0.19-1.94-0.53-2.74-0.55-1.28-1.25-2.64-1.07-4.06,1.81-0.71,2.4-2.62,1.93-4.38-0.07-0.26-0.5-0.45-0.3-0.8,0.19-0.33,0.5-0.55,0.77-0.82-0.13,0.14-0.28,0.37-0.39,0.35-0.61-0.11-0.49-0.75-0.36-1.13,0.59-1.75,2.6-2.01,3.95-0.82,0.26-0.56,0.77-0.37,1.2-0.4-0.05-0.58,0.36-1.11,0.56-1.53,0.52-1.09,2.14,0.01,2.94-0.6,1.08-0.83,2.14-1.52,3.22-0.92,1.81,1.01,3.52,2.22,4.72,3.97,0.57,0.83,0.81,2.11,0.75,3.07-0.04,0.65-1.42,0.29-1.76,1.22-0.65,1.75,1.19,2.27,1.94,3.61,0.2,0.35-0.06,0.65-0.38,0.75-0.41,0.13-1.19-0.06-1.06,0.39,0.98,3.19-1.78,3.87-4.13,4.44z"/>
</g>
<g id="g550" fill="#FFF">
<path id="path552" d="m139.6,138.2c-0.01-1.74-1.61-3.49-0.4-5.2,0.14,0.14,0.27,0.36,0.4,0.36,0.14,0,0.27-0.22,0.4-0.36,1.5,2.22,5.15,3.14,5.01,5.99-0.03,0.45-1.11,1.37-0.21,2.01-1.81,1.35-1.87,3.72-2.8,5.6-1.24-0.28-2.45-0.65-3.6-1.2,0.35-1.48,0.24-3.17,1.06-4.49,0.43-0.7,0.14-1.78,0.14-2.71z"/>
</g>
<g id="g554" fill="#CCC">
<path id="path556" d="m-26.6,129.2s-16.858,10.14-2.8-5.2c8.8-9.6,18.8-15.2,18.8-15.2s10.4-4.4,14-5.6,18.8-6.4,22-6.8,12.8-4.4,19.6-0.4,14.8,8.4,14.8,8.4-16.4-8.4-20-6-10.8,2-16.8,5.2c0,0-14.8,4.4-18,6.4s-13.6,13.6-15.2,12.8,0.4-1.2,1.6-4-0.8-4.4-8.8,2-9.2,8.4-9.2,8.4z"/>
</g>
<g id="g558" fill="#000">
<path id="path560" d="m-19.195,123.23s1.41-13.04,9.888-11.37c0,0,8.226-4.17,10.948-6.14,0,0,8.139-1.7,9.449-2.32,18.479-8.698,33.198-4.179,33.745-5.299,0.546-1.119,20.171,5.999,23.78,10.079,0.391,0.45-10.231-5.59-19.929-7.48-8.273-1.617-29.875,0.24-40.781,5.78-2.973,1.51-11.918,7.29-14.449,7.18s-12.651,9.57-12.651,9.57z"/>
</g>
<g id="g562" fill="#CCC">
<path id="path564" d="m-23,148.8s-15.2-2.4,1.6-4c0,0,18-2,22-7.2,0,0,13.6-9.2,16.4-9.6s32.8-7.6,33.2-10,6-2.4,7.6-1.6,0.8,2-2,2.8-34,17.2-40.4,18.4-18,8.8-22.8,10-15.6,1.2-15.6,1.2z"/>
</g>
<g id="g566" fill="#000">
<path id="path568" d="m-3.48,141.4s-8.582-0.83,0.019-1.64c0,0,8.816-3.43,10.864-6.09,0,0,6.964-4.71,8.397-4.92,1.434-0.2,15.394-3.89,15.599-5.12s34.271-13.81,38.691-10.62c2.911,2.1-6.99,0.43-16.624,4.84-1.355,0.62-35.208,15.2-38.485,15.82-3.277,0.61-9.216,4.5-11.674,5.12-2.457,0.61-6.787,2.61-6.787,2.61z"/>
</g>
<g id="g570" fill="#000">
<path id="path572" d="m-11.4,143.6s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g574" fill="#000">
<path id="path576" d="m-18.6,145.2s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g578" fill="#000">
<path id="path580" d="m-29,146.8s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g582" fill="#000">
<path id="path584" d="m-36.6,147.6s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g586" fill="#000">
<path id="path588" d="m1.8,108,3.2,1.6c-1.2,1.6-4.4,1.2-4.4,1.2l1.2-2.8z"/>
</g>
<g id="g590" fill="#000">
<path id="path592" d="m-8.2,113.6s6.506-2.14,4,1.2c-1.2,1.6-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g594" fill="#000">
<path id="path596" d="m-19.4,118.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g598" fill="#000">
<path id="path600" d="m-27,124.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g602" fill="#000">
<path id="path604" d="m-33.8,129.2s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g606" fill="#000">
<path id="path608" d="m5.282,135.6s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/>
</g>
<g id="g610" fill="#000">
<path id="path612" d="m15.682,130.8s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/>
</g>
<g id="g614" fill="#000">
<path id="path616" d="m26.482,126.4s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/>
</g>
<g id="g618" fill="#000">
<path id="path620" d="m36.882,121.6s6.921-0.53,5.324,1.6c-1.597,2.12-4.792,1.06-4.792,1.06l-0.532-2.66z"/>
</g>
<g id="g622" fill="#000">
<path id="path624" d="m9.282,103.6s6.921-0.53,5.324,1.6c-1.597,2.12-5.592,1.86-5.592,1.86l0.268-3.46z"/>
</g>
<g id="g626" fill="#000">
<path id="path628" d="m19.282,100.4s6.921-0.534,5.324,1.6c-1.597,2.12-5.992,1.86-5.992,1.86l0.668-3.46z"/>
</g>
<g id="g630" fill="#000">
<path id="path632" d="m-3.4,140.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g634" fill="#992600">
<path id="path636" d="m-76.6,41.2s-4.4,8.8-4.8,12c0,0,0.8-8.8,2-10.8s2.8-1.2,2.8-1.2z"/>
</g>
<g id="g638" fill="#992600">
<path id="path640" d="m-95,55.2s-3.2,14.4-2.8,17.2c0,0-1.2-11.6-0.8-12.8s3.6-4.4,3.6-4.4z"/>
</g>
<g id="g642" fill="#CCC">
<path id="path644" d="m-74.2-19.4-0.2,3.2-2.2,0.2s14.2,12.6,14.8,20.2c0,0,0.8-8.2-12.4-23.6z"/>
</g>
<g id="g646" fill="#000">
<path id="path648" d="m-70.216-18.135c-0.431-0.416-0.212-1.161-0.62-1.421-0.809-0.516,1.298-0.573,1.07-1.289-0.383-1.206-0.196-1.227-0.318-2.503-0.057-0.598,0.531-2.138,0.916-2.578,1.446-1.652,0.122-4.584,1.762-6.135,0.304-0.289,0.68-0.841,0.965-1.259,0.659-0.963,1.843-1.451,2.793-2.279,0.318-0.276,0.117-1.103,0.686-1.011,0.714,0.115,1.955-0.015,1.91,0.826-0.113,2.12-1.442,3.84-2.722,5.508,0.451,0.704-0.007,1.339-0.291,1.896-1.335,2.62-1.146,5.461-1.32,8.301-0.005,0.085-0.312,0.163-0.304,0.216,0.353,2.335,0.937,4.534,1.816,6.763,0.366,0.93,0.837,1.825,0.987,2.752,0.111,0.686,0.214,1.519-0.194,2.224,2.035,2.89,0.726,5.541,1.895,9.072,0.207,0.625,1.899,2.539,1.436,2.378-2.513-0.871-2.625-1.269-2.802-2.022-0.146-0.623-0.476-2-0.713-2.602-0.064-0.164-0.235-2.048-0.313-2.17-1.513-2.382-0.155-2.206-1.525-4.564-1.428-0.68-2.394-1.784-3.517-2.946-0.198-0.204,0.945-0.928,0.764-1.141-1.092-1.289-2.245-2.056-1.909-3.549,0.155-0.69,0.292-1.747-0.452-2.467z"/>
</g>
<g id="g650" fill="#000">
<path id="path652" d="m-73.8-16.4s0.4,6.8,2.8,8.4,1.2,0.8-2-0.4-2-2-2-2-2.8,0.4-0.4,2.4,6,4.4,4.4,4.4-9.2-4-9.2-6.8-1-6.9-1-6.9,1.1-0.8,5.9-0.7c0,0,1.4,0.7,1.5,1.6z"/>
</g>
<g id="g654" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path656" d="m-74.6,2.2s-8.52-2.791-27,0.6c0,0,9.031-2.078,27.8,0.2,10.3,1.25-0.8-0.8-0.8-0.8z"/>
</g>
<g id="g658" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path660" d="m-72.502,2.129s-8.246-3.518-26.951-1.737c0,0,9.178-1.289,27.679,2.603,10.154,2.136-0.728-0.866-0.728-0.866z"/>
</g>
<g id="g662" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path664" d="m-70.714,2.222s-7.962-4.121-26.747-3.736c0,0,9.248-0.604,27.409,4.654,9.966,2.885-0.662-0.918-0.662-0.918z"/>
</g>
<g id="g666" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path668" d="m-69.444,2.445s-6.824-4.307-23.698-5.405c0,0,8.339,0.17,24.22,6.279,8.716,3.353-0.522-0.874-0.522-0.874z"/>
</g>
<g id="g670" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path672" d="m45.84,12.961s-0.93,0.644-0.716-0.537c0.215-1.181,28.423-14.351,32.037-14.101,0,0-30.248,13.206-31.321,14.638z"/>
</g>
<g id="g674" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path676" d="m42.446,13.6s-0.876,0.715-0.755-0.479,27.208-16.539,30.83-16.573c0,0-29.117,15.541-30.075,17.052z"/>
</g>
<g id="g678" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path680" d="m39.16,14.975s-0.828,0.772-0.786-0.428c0.042-1.199,19.859-16.696,29.671-18.57,0,0-18.03,8.127-28.885,18.998z"/>
</g>
<g id="g682" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path684" d="m36.284,16.838s-0.745,0.694-0.707-0.385c0.038-1.08,17.872-15.027,26.703-16.713,0,0-16.226,7.314-25.996,17.098z"/>
</g>
<g id="g686" fill="#CCC">
<path id="path688" d="m4.6,164.8s-15.2-2.4,1.6-4c0,0,18-2,22-7.2,0,0,13.6-9.2,16.4-9.6s19.2-4,19.6-6.4,6.4-4.8,8-4,1.6,10-1.2,10.8-21.6,8-28,9.2-18,8.8-22.8,10-15.6,1.2-15.6,1.2z"/>
</g>
<g id="g690" fill="#000">
<path id="path692" d="m77.6,127.4s-3,1.6-4.2,4.2c0,0-6.4,10.6-20.6,13.8,0,0-23,9-30.8,11,0,0-13.4,5-20.8,4.2,0,0-7,0.2-0.8,1.8,0,0,20.2-2,23.6-3.8,0,0,15.6-5.2,18.6-7.8s21.2-7.6,23.4-9.6,12-10.4,11.6-13.8z"/>
</g>
<g id="g694" fill="#000">
<path id="path696" d="m18.882,158.91s5.229-0.23,4.076,1.32-3.601,0.68-3.601,0.68l-0.475-2z"/>
</g>
<g id="g698" fill="#000">
<path id="path700" d="m11.68,160.26s5.228-0.22,4.076,1.33c-1.153,1.55-3.601,0.67-3.601,0.67l-0.475-2z"/>
</g>
<g id="g702" fill="#000">
<path id="path704" d="m1.251,161.51s5.229-0.23,4.076,1.32-3.601,0.68-3.601,0.68l-0.475-2z"/>
</g>
<g id="g706" fill="#000">
<path id="path708" d="m-6.383,162.06s5.229-0.23,4.076,1.32-3.601,0.67-3.601,0.67l-0.475-1.99z"/>
</g>
<g id="g710" fill="#000">
<path id="path712" d="m35.415,151.51s6.96-0.3,5.425,1.76c-1.534,2.07-4.793,0.9-4.793,0.9l-0.632-2.66z"/>
</g>
<g id="g714" fill="#000">
<path id="path716" d="m45.73,147.09s5.959-3.3,5.425,1.76c-0.27,2.55-4.793,0.9-4.793,0.9l-0.632-2.66z"/>
</g>
<g id="g718" fill="#000">
<path id="path720" d="m54.862,144.27s7.159-3.7,5.425,1.77c-0.778,2.44-4.794,0.9-4.794,0.9l-0.631-2.67z"/>
</g>
<g id="g722" fill="#000">
<path id="path724" d="m64.376,139.45s4.359-4.9,5.425,1.76c0.406,2.54-4.793,0.9-4.793,0.9l-0.632-2.66z"/>
</g>
<g id="g726" fill="#000">
<path id="path728" d="m26.834,156s5.228-0.23,4.076,1.32c-1.153,1.55-3.602,0.68-3.602,0.68l-0.474-2z"/>
</g>
<g id="g730" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path732" d="m62.434,34.603s-0.726,0.665-0.727-0.406c0-1.07,17.484-14.334,26.327-15.718,0,0-16.099,6.729-25.6,16.124z"/>
</g>
<g id="g734" fill="#000">
<path id="path736" d="m65.4,98.4s22.001,22.4,31.201,26c0,0,9.199,11.2,5.199,37.2,0,0-3.199,7.6-6.399-13.2,0,0,3.2-25.2-8-9.2,0,0-8.401-9.9-2.001-9.6,0,0,3.201,2,3.601,0.4s-7.601-15.2-24.801-29.6,1.2-2,1.2-2z"/>
</g>
<g id="g738" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path740" d="m7,137.2s-0.2-1.8,1.6-1,96,7,127.6,31c0,0-45.199-23.2-129.2-30z"/>
</g>
<g id="g742" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path744" d="m17.4,132.8s-0.2-1.8,1.6-1,138.4-0.2,162,32.2c0,0-22-25.2-163.6-31.2z"/>
</g>
<g id="g746" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path748" d="m29,128.8s-0.2-1.8,1.6-1,175.2-12.2,198.8,20.2c0,0-9.6-25.6-200.4-19.2z"/>
</g>
<g id="g750" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path752" d="m39,124s-0.2-1.8,1.6-1,124-37.8,147.6-5.4c0,0-13.4-24.6-149.2,6.4z"/>
</g>
<g id="g754" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path756" d="m-19,146.8s-0.2-1.8,1.6-1,19.6,3,21.6,41.8c0,0-7.2-42-23.2-40.8z"/>
</g>
<g id="g758" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path760" d="m-27.8,148.4s-0.2-1.8,1.6-1,16-3.8,13.2,35c0,0,1.2-35.2-14.8-34z"/>
</g>
<g id="g762" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path764" d="m-35.8,148.8s-0.2-1.8,1.6-1,17.2,1.4,4.8,23.8c0,0,9.6-24-6.4-22.8z"/>
</g>
<g id="g766" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path768" d="m11.526,104.46s-0.444,2,1.105,0.79c16.068-12.628,48.51-71.53,104.2-77.164,0,0-38.312-12.11-105.3,76.374z"/>
</g>
<g id="g770" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path772" d="m22.726,102.66s-1.363-1.19,0.505-1.81c1.868-0.63,114.31-73.13,153.6-65.164,0,0-27.11-7.51-154.1,66.974z"/>
</g>
<g id="g774" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path776" d="m1.885,108.77s-0.509,1.6,1.202,0.62c8.975-5.12,12.59-62.331,56.167-63.586,0,0-32.411-14.714-57.369,62.966z"/>
</g>
<g id="g778" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path780" d="m-18.038,119.79s-1.077,1.29,0.876,1.03c10.246-1.33,31.651-42.598,76.09-37.519,0,0-31.966-14.346-76.966,36.489z"/>
</g>
<g id="g782" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path784" d="m-6.8,113.67s-0.811,1.47,1.058,0.84c9.799-3.27,22.883-47.885,67.471-51.432,0,0-34.126-7.943-68.529,50.592z"/>
</g>
<g id="g786" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path788" d="m-25.078,124.91s-0.873,1.04,0.709,0.84c8.299-1.08,25.637-34.51,61.633-30.396,0,0-25.893-11.62-62.342,29.556z"/>
</g>
<g id="g790" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path792" d="m-32.677,130.82s-1.005,1.05,0.586,0.93c4.168-0.31,34.806-33.39,53.274-17.89,0,0-12.015-18.721-53.86,16.96z"/>
</g>
<g id="g794" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path796" d="m36.855,98.898s-1.201-1.355,0.731-1.74c1.932-0.384,122.63-58.097,160.59-45.231,0,0-25.94-10.874-161.32,46.971z"/>
</g>
<g id="g798" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path800" d="m3.4,163.2s-0.2-1.8,1.6-1,17.2,1.4,4.8,23.8c0,0,9.6-24-6.4-22.8z"/>
</g>
<g id="g802" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path804" d="m13.8,161.6s-0.2-1.8,1.6-1,19.6,3,21.6,41.8c0,0-7.2-42-23.2-40.8z"/>
</g>
<g id="g806" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path808" d="m20.6,160s-0.2-1.8,1.6-1,26.4,4.2,50,36.6c0,0-35.6-36.8-51.6-35.6z"/>
</g>
<g id="g810" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path812" d="m28.225,157.97s-0.437-1.76,1.453-1.2c1.89,0.55,22.324-1.35,60.421,32.83,0,0-46.175-34.94-61.874-31.63z"/>
</g>
<g id="g814" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path816" d="m38.625,153.57s-0.437-1.76,1.453-1.2c1.89,0.55,36.724,5.05,88.422,40.03,0,0-74.176-42.14-89.875-38.83z"/>
</g>
<g id="g818" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path820" d="m-1.8,142s-0.2-1.8,1.6-1,55.2,3.4,85.6,30.2c0,0-34.901-24.77-87.2-29.2z"/>
</g>
<g id="g822" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path824" d="m-11.8,146s-0.2-1.8,1.6-1,26.4,4.2,50,36.6c0,0-35.6-36.8-51.6-35.6z"/>
</g>
<g id="g826" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path828" d="m49.503,148.96s-0.565-1.72,1.361-1.3c1.926,0.41,36.996,2.34,91.116,33.44,0,0-77.663-34.4-92.477-32.14z"/>
</g>
<g id="g830" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path832" d="m57.903,146.56s-0.565-1.72,1.361-1.3c1.926,0.41,36.996,2.34,91.116,33.44,0,0-77.063-34.8-92.477-32.14z"/>
</g>
<g id="g834" stroke-width="0.1" stroke="#000" fill="#FFF">
<path id="path836" d="m67.503,141.56s-0.565-1.72,1.361-1.3c1.926,0.41,44.996,4.74,134.72,39.04,0,0-120.66-40.4-136.08-37.74z"/>
</g>
<g id="g838" fill="#000">
<path id="path840" d="m-43.8,148.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g842" fill="#000">
<path id="path844" d="m-13,162.4s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g846" fill="#000">
<path id="path848" d="m-21.8,162s5.2-0.4,4,1.2-3.6,0.8-3.6,0.8l-0.4-2z"/>
</g>
<g id="g850" fill="#000">
<path id="path852" d="m-117.17,150.18s5.05,1.32,3.39,2.44-3.67-0.42-3.67-0.42l0.28-2.02z"/>
</g>
<g id="g854" fill="#000">
<path id="path856" d="m-115.17,140.58s5.05,1.32,3.39,2.44-3.67-0.42-3.67-0.42l0.28-2.02z"/>
</g>
<g id="g858" fill="#000">
<path id="path860" d="m-122.37,136.18s5.05,1.32,3.39,2.44-3.67-0.42-3.67-0.42l0.28-2.02z"/>
</g>
<g id="g862" fill="#CCC">
<path id="path864" d="m-42.6,211.2-5.6,2c-2,0-13.2,3.6-18.8,13.6,0,0,12.4-9.6,24.4-15.6z"/>
</g>
<g id="g866" fill="#CCC">
<path id="path868" d="m45.116,303.85c0.141,0.25,0.196,0.67,0.488,0.69,0.658,0.04,1.891,0.34,1.766-0.29-0.848-4.31-1.722-9.25-5.855-11.05-0.639-0.28-2.081,0.13-2.155,1.02-0.127,1.52-0.244,2.87,0.065,4.33,0.3,1.43,2.458,1.43,3.375,0.05,0.936,1.67,1.368,3.52,2.316,5.25z"/>
</g>
<g id="g870" fill="#CCC">
<path id="path872" d="m34.038,308.58c0.748,1.41,0.621,3.27,2.036,3.84,0.74,0.29,2.59-0.68,2.172-1.76-0.802-2.06-1.19-4.3-2.579-6.11-0.2-0.26,0.04-0.79-0.12-1.12-0.594-1.22-1.739-1.96-3.147-1.63-1.115,2.2,0.033,4.33,1.555,6.04,0.136,0.15-0.03,0.53,0.083,0.74z"/>
</g>
<g id="g874" fill="#CCC">
<path id="path876" d="m-5.564,303.39c-0.108-0.38-0.146-0.84,0.019-1.16,0.531-1.03,1.324-2.15,0.987-3.18-0.348-1.05-1.464-0.87-2.114-0.3-1.135,0.99-1.184,2.82-1.875,4.18-0.196,0.38-0.145,0.96-0.586,1.35-0.474,0.42-0.914,1.94-0.818,2.51,0.053,0.32-0.13,10.22,0.092,9.96,0.619-0.73,3.669-10.47,3.738-11.36,0.057-0.73,0.789-1.19,0.557-2z"/>
</g>
<g id="g878" fill="#CCC">
<path id="path880" d="m-31.202,296.6c2.634-2.5,5.424-5.46,4.982-9.17-0.116-0.98-1.891-0.45-2.078,0.39-0.802,3.63-2.841,6.29-5.409,8.68-2.196,2.05-4.058,8.39-4.293,8.9,3.697-5.26,5.954-8,6.798-8.8z"/>
</g>
<g id="g882" fill="#CCC">
<path id="path884" d="m-44.776,290.64c0.523-0.38,0.221-0.87,0.438-1.2,0.953-1.46,2.254-2.7,2.272-4.44,0.003-0.28-0.375-0.59-0.71-0.36-0.277,0.18-0.619,0.31-0.727,0.44-2.03,2.45-3.43,5.12-4.873,7.93-0.183,0.36-1.327,4.85-1.014,4.96,0.239,0.09,1.959-4.09,2.169-4.21,1.263-0.68,1.275-2.3,2.445-3.12z"/>
</g>
<g id="g886" fill="#CCC">
<path id="path888" d="m-28.043,310.18c0.444-0.87,2.02-2.07,1.907-2.96-0.118-0.93,0.35-2.37-0.562-1.68-1.257,0.94-4.706,2.29-4.976,8.1-0.026,0.57,2.948-2.12,3.631-3.46z"/>
</g>
<g id="g890" fill="#CCC">
<path id="path892" d="m-13.6,293c0.4-0.67,1.108-0.19,1.567-0.46,0.648-0.37,1.259-0.93,1.551-1.58,0.97-2.14,2.739-3.96,2.882-6.36-1.491-1.4-2.17,0.64-2.8,1.6-1.323-1.65-2.322,0.23-3.622,0.75-0.07,0.03-0.283-0.32-0.358-0.29-1.177,0.44-1.857,1.52-2.855,2.3-0.171,0.13-0.576-0.05-0.723,0.09-0.652,0.6-1.625,0.93-1.905,1.61-1.11,2.7-4.25,4.8-6.137,12.34,0.381,0.91,4.512-6.64,4.999-7.34,0.836-1.2,0.954,1.66,2.23,1,0.051-0.03,0.237,0.21,0.371,0.34,0.194-0.28,0.412-0.51,0.8-0.4,0-0.4-0.134-0.96,0.067-1.11,1.237-0.98,1.153-2.05,1.933-3.29,0.458,0.79,1.519,0.07,2,0.8z"/>
</g>
<g id="g894" fill="#CCC">
<path id="path896" d="m46.2,347.4s7.4-20.4,3-31.6c0,0,11.4,21.6,6.8,32.8,0,0-0.4-10.4-4.4-15.4,0,0-4,12.8-5.4,14.2z"/>
</g>
<g id="g898" fill="#CCC">
<path id="path900" d="m31.4,344.8s5.4-8.8-2.6-27.2c0,0-0.8,20.4-7.6,31.4,0,0,14.2-20.2,10.2-4.2z"/>
</g>
<g id="g902" fill="#CCC">
<path id="path904" d="m21.4,342.8s-0.2-20,0.2-23c0,0-3.8,16.6-14,26.2,0,0,14.4-12,13.8-3.2z"/>
</g>
<g id="g906" fill="#CCC">
<path id="path908" d="m11.8,310.8s6,13.6-4,32c0,0,6.4-12.2,1.6-19.2,0,0,2.6-3.4,2.4-12.8z"/>
</g>
<g id="g910" fill="#CCC">
<path id="path912" d="m-7.4,342.4s-1-15.6,0.8-17.8c0,0,0.2-6.4-0.2-7.4,0,0,4-6.2,4.2,1.2,0,0,1.4,7.8,4.2,12.4,0,0,3.6,5.4,3.4,11.8,0,0-10-30.2-12.4-0.2z"/>
</g>
<g id="g914" fill="#CCC">
<path id="path916" d="m-11,314.8s-6.6,10.8-8.4,29.8c0,0-1.4-6.2,2.4-20.6,0,0,4.2-15.4,6-9.2z"/>
</g>
<g id="g918" fill="#CCC">
<path id="path920" d="m-32.8,334.6s5-5.4,6.4-10.4c0,0,3.6-15.8-2.8-7.2,0,0,0.2,8-8,15.4,0,0,4.8-2.4,4.4,2.2z"/>
</g>
<g id="g922" fill="#CCC">
<path id="path924" d="m-38.6,329.6s3.4-17.4,4.2-18.2c0,0,1.8-3.4-1-0.2,0,0-8.8,19.2-12.8,25.8,0,0,8-9.2,9.6-7.4z"/>
</g>
<g id="g926" fill="#CCC">
<path id="path928" d="m-44.4,313s11.6-22.4-10.2,3.4c0,0,11-9.8,10.2-3.4z"/>
</g>
<g id="g930" fill="#CCC">
<path id="path932" d="m-59.8,298.4s4.8-18.8,7.4-18.6l1.6,1.6s-6,9.6-5.4,19.4c0,0-0.6-9.6-3.6-2.4z"/>
</g>
<g id="g934" fill="#CCC">
<path id="path936" d="m270.5,287s-12-10-14.5-13.5c0,0,13.5,18.5,13.5,25.5,0,0,2.5-7.5,1-12z"/>
</g>
<g id="g938" fill="#CCC">
<path id="path940" d="m276,265s-21-15-24.5-22.5c0,0,26.5,29.5,26.5,34,0,0,0.5-9-2-11.5z"/>
</g>
<g id="g942" fill="#CCC">
<path id="path944" d="m293,111s-12-8-13.5-6c0,0,10.5,6.5,13,15,0,0-1.5-9,0.5-9z"/>
</g>
<g id="g946" fill="#CCC">
<path id="path948" d="m301.5,191.5-17.5-12s19,17,19.5,21l-2-9z"/>
</g>
<g id="g950" stroke="#000">
<path id="path952" d="m-89.25,169,22,4.75"/>
</g>
<g id="g954" stroke="#000">
<path id="path956" d="m-39,331s-0.5-3.5-9.5,7"/>
</g>
<g id="g958" stroke="#000">
<path id="path960" d="m-33.5,336s2-6.5-4.5-2"/>
</g>
<g id="g962" stroke="#000">
<path id="path964" d="m20.5,344.5s1.5-11-10,2"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

235
vendor/github.com/h2non/bimg/image.go generated vendored
View File

@ -1,235 +0,0 @@
package bimg
// Image provides a simple method DSL to transform a given image as byte buffer.
type Image struct {
buffer []byte
}
// NewImage creates a new Image struct with method DSL.
func NewImage(buf []byte) *Image {
return &Image{buf}
}
// Resize resizes the image to fixed width and height.
func (i *Image) Resize(width, height int) ([]byte, error) {
options := Options{
Width: width,
Height: height,
Embed: true,
}
return i.Process(options)
}
// ForceResize resizes with custom size (aspect ratio won't be maintained).
func (i *Image) ForceResize(width, height int) ([]byte, error) {
options := Options{
Width: width,
Height: height,
Force: true,
}
return i.Process(options)
}
// ResizeAndCrop resizes the image to fixed width and height with additional crop transformation.
func (i *Image) ResizeAndCrop(width, height int) ([]byte, error) {
options := Options{
Width: width,
Height: height,
Embed: true,
Crop: true,
}
return i.Process(options)
}
// SmartCrop produces a thumbnail aiming at focus on the interesting part.
func (i *Image) SmartCrop(width, height int) ([]byte, error) {
options := Options{
Width: width,
Height: height,
Crop: true,
Gravity: GravitySmart,
}
return i.Process(options)
}
// Extract area from the by X/Y axis in the current image.
func (i *Image) Extract(top, left, width, height int) ([]byte, error) {
options := Options{
Top: top,
Left: left,
AreaWidth: width,
AreaHeight: height,
}
if top == 0 && left == 0 {
options.Top = -1
}
return i.Process(options)
}
// Enlarge enlarges the image by width and height. Aspect ratio is maintained.
func (i *Image) Enlarge(width, height int) ([]byte, error) {
options := Options{
Width: width,
Height: height,
Enlarge: true,
}
return i.Process(options)
}
// EnlargeAndCrop enlarges the image by width and height with additional crop transformation.
func (i *Image) EnlargeAndCrop(width, height int) ([]byte, error) {
options := Options{
Width: width,
Height: height,
Enlarge: true,
Crop: true,
}
return i.Process(options)
}
// Crop crops the image to the exact size specified.
func (i *Image) Crop(width, height int, gravity Gravity) ([]byte, error) {
options := Options{
Width: width,
Height: height,
Gravity: gravity,
Crop: true,
}
return i.Process(options)
}
// CropByWidth crops an image by width only param (auto height).
func (i *Image) CropByWidth(width int) ([]byte, error) {
options := Options{
Width: width,
Crop: true,
}
return i.Process(options)
}
// CropByHeight crops an image by height (auto width).
func (i *Image) CropByHeight(height int) ([]byte, error) {
options := Options{
Height: height,
Crop: true,
}
return i.Process(options)
}
// Thumbnail creates a thumbnail of the image by the a given width by aspect ratio 4:4.
func (i *Image) Thumbnail(pixels int) ([]byte, error) {
options := Options{
Width: pixels,
Height: pixels,
Crop: true,
Quality: 95,
}
return i.Process(options)
}
// Watermark adds text as watermark on the given image.
func (i *Image) Watermark(w Watermark) ([]byte, error) {
options := Options{Watermark: w}
return i.Process(options)
}
// WatermarkImage adds image as watermark on the given image.
func (i *Image) WatermarkImage(w WatermarkImage) ([]byte, error) {
options := Options{WatermarkImage: w}
return i.Process(options)
}
// Zoom zooms the image by the given factor.
// You should probably call Extract() before.
func (i *Image) Zoom(factor int) ([]byte, error) {
options := Options{Zoom: factor}
return i.Process(options)
}
// Rotate rotates the image by given angle degrees (0, 90, 180 or 270).
func (i *Image) Rotate(a Angle) ([]byte, error) {
options := Options{Rotate: a}
return i.Process(options)
}
// Flip flips the image about the vertical Y axis.
func (i *Image) Flip() ([]byte, error) {
options := Options{Flip: true}
return i.Process(options)
}
// Flop flops the image about the horizontal X axis.
func (i *Image) Flop() ([]byte, error) {
options := Options{Flop: true}
return i.Process(options)
}
// Convert converts image to another format.
func (i *Image) Convert(t ImageType) ([]byte, error) {
options := Options{Type: t}
return i.Process(options)
}
// Colourspace performs a color space conversion bsaed on the given interpretation.
func (i *Image) Colourspace(c Interpretation) ([]byte, error) {
options := Options{Interpretation: c}
return i.Process(options)
}
// Trim removes the background from the picture. It can result in a 0x0 output
// if the image is all background.
func (i *Image) Trim() ([]byte, error) {
options := Options{Trim: true}
return i.Process(options)
}
// Process processes the image based on the given transformation options,
// talking with libvips bindings accordingly and returning the resultant
// image buffer.
func (i *Image) Process(o Options) ([]byte, error) {
image, err := Resize(i.buffer, o)
if err != nil {
return nil, err
}
i.buffer = image
return image, nil
}
// Metadata returns the image metadata (size, alpha channel, profile, EXIF rotation).
func (i *Image) Metadata() (ImageMetadata, error) {
return Metadata(i.buffer)
}
// Interpretation gets the image interpretation type.
// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation
func (i *Image) Interpretation() (Interpretation, error) {
return ImageInterpretation(i.buffer)
}
// ColourspaceIsSupported checks if the current image
// color space is supported.
func (i *Image) ColourspaceIsSupported() (bool, error) {
return ColourspaceIsSupported(i.buffer)
}
// Type returns the image type format (jpeg, png, webp, tiff).
func (i *Image) Type() string {
return DetermineImageTypeName(i.buffer)
}
// Size returns the image size as form of width and height pixels.
func (i *Image) Size() (ImageSize, error) {
return Size(i.buffer)
}
// Image returns the current resultant image buffer.
func (i *Image) Image() []byte {
return i.buffer
}
// Length returns the size in bytes of the image buffer.
func (i *Image) Length() int {
return len(i.buffer)
}

View File

@ -1,527 +0,0 @@
package bimg
import (
"fmt"
"path"
"testing"
)
func TestImageResize(t *testing.T) {
buf, err := initImage("test.jpg").Resize(300, 240)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
err = assertSize(buf, 300, 240)
if err != nil {
t.Error(err)
}
Write("fixtures/test_resize_out.jpg", buf)
}
func TestImageGifResize(t *testing.T) {
_, err := initImage("test.gif").Resize(300, 240)
if err == nil {
t.Errorf("GIF shouldn't be saved within VIPS")
}
}
func TestImagePdfResize(t *testing.T) {
_, err := initImage("test.pdf").Resize(300, 240)
if err == nil {
t.Errorf("PDF cannot be saved within VIPS")
}
}
func TestImageSvgResize(t *testing.T) {
_, err := initImage("test.svg").Resize(300, 240)
if err == nil {
t.Errorf("SVG cannot be saved within VIPS")
}
}
func TestImageGifToJpeg(t *testing.T) {
if VipsMajorVersion >= 8 && VipsMinorVersion > 2 {
i := initImage("test.gif")
options := Options{
Type: JPEG,
}
buf, err := i.Process(options)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_gif.jpg", buf)
}
}
func TestImagePdfToJpeg(t *testing.T) {
if VipsMajorVersion >= 8 && VipsMinorVersion > 2 {
i := initImage("test.pdf")
options := Options{
Type: JPEG,
}
buf, err := i.Process(options)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_pdf.jpg", buf)
}
}
func TestImageSvgToJpeg(t *testing.T) {
if VipsMajorVersion >= 8 && VipsMinorVersion > 2 {
i := initImage("test.svg")
options := Options{
Type: JPEG,
}
buf, err := i.Process(options)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_svg.jpg", buf)
}
}
func TestImageResizeAndCrop(t *testing.T) {
buf, err := initImage("test.jpg").ResizeAndCrop(300, 200)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
err = assertSize(buf, 300, 200)
if err != nil {
t.Error(err)
}
Write("fixtures/test_resize_crop_out.jpg", buf)
}
func TestImageExtract(t *testing.T) {
buf, err := initImage("test.jpg").Extract(100, 100, 300, 200)
if err != nil {
t.Errorf("Cannot process the image: %s", err)
}
err = assertSize(buf, 300, 200)
if err != nil {
t.Error(err)
}
Write("fixtures/test_extract_out.jpg", buf)
}
func TestImageExtractZero(t *testing.T) {
buf, err := initImage("test.jpg").Extract(0, 0, 300, 200)
if err != nil {
t.Errorf("Cannot process the image: %s", err)
}
err = assertSize(buf, 300, 200)
if err != nil {
t.Error(err)
}
Write("fixtures/test_extract_zero_out.jpg", buf)
}
func TestImageEnlarge(t *testing.T) {
buf, err := initImage("test.png").Enlarge(500, 375)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
err = assertSize(buf, 500, 375)
if err != nil {
t.Error(err)
}
Write("fixtures/test_enlarge_out.jpg", buf)
}
func TestImageEnlargeAndCrop(t *testing.T) {
buf, err := initImage("test.png").EnlargeAndCrop(800, 480)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
err = assertSize(buf, 800, 480)
if err != nil {
t.Error(err)
}
Write("fixtures/test_enlarge_crop_out.jpg", buf)
}
func TestImageCrop(t *testing.T) {
buf, err := initImage("test.jpg").Crop(800, 600, GravityNorth)
if err != nil {
t.Errorf("Cannot process the image: %s", err)
}
err = assertSize(buf, 800, 600)
if err != nil {
t.Error(err)
}
Write("fixtures/test_crop_out.jpg", buf)
}
func TestImageCropByWidth(t *testing.T) {
buf, err := initImage("test.jpg").CropByWidth(600)
if err != nil {
t.Errorf("Cannot process the image: %s", err)
}
err = assertSize(buf, 600, 1050)
if err != nil {
t.Error(err)
}
Write("fixtures/test_crop_width_out.jpg", buf)
}
func TestImageCropByHeight(t *testing.T) {
buf, err := initImage("test.jpg").CropByHeight(300)
if err != nil {
t.Errorf("Cannot process the image: %s", err)
}
err = assertSize(buf, 1680, 300)
if err != nil {
t.Error(err)
}
Write("fixtures/test_crop_height_out.jpg", buf)
}
func TestImageThumbnail(t *testing.T) {
buf, err := initImage("test.jpg").Thumbnail(100)
if err != nil {
t.Errorf("Cannot process the image: %s", err)
}
err = assertSize(buf, 100, 100)
if err != nil {
t.Error(err)
}
Write("fixtures/test_thumbnail_out.jpg", buf)
}
func TestImageWatermark(t *testing.T) {
image := initImage("test.jpg")
_, err := image.Crop(800, 600, GravityNorth)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
buf, err := image.Watermark(Watermark{
Text: "Copy me if you can",
Opacity: 0.5,
Width: 200,
DPI: 100,
Background: Color{255, 255, 255},
})
if err != nil {
t.Error(err)
}
err = assertSize(buf, 800, 600)
if err != nil {
t.Error(err)
}
if DetermineImageType(buf) != JPEG {
t.Fatal("Image is not jpeg")
}
Write("fixtures/test_watermark_text_out.jpg", buf)
}
func TestImageWatermarkWithImage(t *testing.T) {
image := initImage("test.jpg")
watermark, _ := imageBuf("transparent.png")
_, err := image.Crop(800, 600, GravityNorth)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
buf, err := image.WatermarkImage(WatermarkImage{Left: 100, Top: 100, Buf: watermark})
if err != nil {
t.Error(err)
}
err = assertSize(buf, 800, 600)
if err != nil {
t.Error(err)
}
if DetermineImageType(buf) != JPEG {
t.Fatal("Image is not jpeg")
}
Write("fixtures/test_watermark_image_out.jpg", buf)
}
func TestImageWatermarkNoReplicate(t *testing.T) {
image := initImage("test.jpg")
_, err := image.Crop(800, 600, GravityNorth)
if err != nil {
t.Errorf("Cannot process the image: %s", err)
}
buf, err := image.Watermark(Watermark{
Text: "Copy me if you can",
Opacity: 0.5,
Width: 200,
DPI: 100,
NoReplicate: true,
Background: Color{255, 255, 255},
})
if err != nil {
t.Error(err)
}
err = assertSize(buf, 800, 600)
if err != nil {
t.Error(err)
}
if DetermineImageType(buf) != JPEG {
t.Fatal("Image is not jpeg")
}
Write("fixtures/test_watermark_replicate_out.jpg", buf)
}
func TestImageZoom(t *testing.T) {
image := initImage("test.jpg")
_, err := image.Extract(100, 100, 400, 300)
if err != nil {
t.Errorf("Cannot extract the image: %s", err)
}
buf, err := image.Zoom(1)
if err != nil {
t.Errorf("Cannot process the image: %s", err)
}
err = assertSize(buf, 800, 600)
if err != nil {
t.Error(err)
}
Write("fixtures/test_zoom_out.jpg", buf)
}
func TestImageFlip(t *testing.T) {
buf, err := initImage("test.jpg").Flip()
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_flip_out.jpg", buf)
}
func TestImageFlop(t *testing.T) {
buf, err := initImage("test.jpg").Flop()
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_flop_out.jpg", buf)
}
func TestImageRotate(t *testing.T) {
buf, err := initImage("test_flip_out.jpg").Rotate(90)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_image_rotate_out.jpg", buf)
}
func TestImageConvert(t *testing.T) {
buf, err := initImage("test.jpg").Convert(PNG)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_image_convert_out.png", buf)
}
func TestTransparentImageConvert(t *testing.T) {
image := initImage("transparent.png")
options := Options{
Type: JPEG,
Background: Color{255, 255, 255},
}
buf, err := image.Process(options)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
Write("fixtures/test_transparent_image_convert_out.jpg", buf)
}
func TestImageMetadata(t *testing.T) {
data, err := initImage("test.png").Metadata()
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
if data.Alpha != true {
t.Fatal("Invalid alpha channel")
}
if data.Size.Width != 400 {
t.Fatal("Invalid width size")
}
if data.Type != "png" {
t.Fatal("Invalid image type")
}
}
func TestInterpretation(t *testing.T) {
interpretation, err := initImage("test.jpg").Interpretation()
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
if interpretation != InterpretationSRGB {
t.Errorf("Invalid interpretation: %d", interpretation)
}
}
func TestImageColourspace(t *testing.T) {
tests := []struct {
file string
interpretation Interpretation
}{
{"test.jpg", InterpretationSRGB},
{"test.jpg", InterpretationBW},
}
for _, test := range tests {
buf, err := initImage(test.file).Colourspace(test.interpretation)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
interpretation, err := ImageInterpretation(buf)
if interpretation != test.interpretation {
t.Errorf("Invalid colourspace")
}
}
}
func TestImageColourspaceIsSupported(t *testing.T) {
supported, err := initImage("test.jpg").ColourspaceIsSupported()
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
if supported != true {
t.Errorf("Non-supported colourspace")
}
}
func TestFluentInterface(t *testing.T) {
image := initImage("test.jpg")
_, err := image.CropByWidth(300)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
_, err = image.Flip()
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
_, err = image.Convert(PNG)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
data, _ := image.Metadata()
if data.Alpha != false {
t.Fatal("Invalid alpha channel")
}
if data.Size.Width != 300 {
t.Fatal("Invalid width size")
}
if data.Type != "png" {
t.Fatal("Invalid image type")
}
Write("fixtures/test_image_fluent_out.png", image.Image())
}
func TestImageSmartCrop(t *testing.T) {
if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 5) {
t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.5", VipsVersion)
}
i := initImage("northern_cardinal_bird.jpg")
buf, err := i.SmartCrop(300, 300)
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
err = assertSize(buf, 300, 300)
if err != nil {
t.Error(err)
}
Write("fixtures/test_smart_crop.jpg", buf)
}
func TestImageTrim(t *testing.T) {
if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 6) {
t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.6", VipsVersion)
}
i := initImage("transparent.png")
buf, err := i.Trim()
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
err = assertSize(buf, 250, 208)
if err != nil {
t.Errorf("The image wasn't trimmed.")
}
Write("fixtures/transparent_trim.png", buf)
}
func TestImageLength(t *testing.T) {
i := initImage("test.jpg")
actual := i.Length()
expected := 53653
if expected != actual {
t.Errorf("Size in Bytes of the image doesn't correspond. %d != %d", expected, actual)
}
}
func initImage(file string) *Image {
buf, _ := imageBuf(file)
return NewImage(buf)
}
func imageBuf(file string) ([]byte, error) {
return Read(path.Join("fixtures", file))
}
func assertSize(buf []byte, width, height int) error {
size, err := NewImage(buf).Size()
if err != nil {
return err
}
if size.Width != width || size.Height != height {
return fmt.Errorf("Invalid image size: %dx%d", size.Width, size.Height)
}
return nil
}

View File

@ -1,77 +0,0 @@
package bimg
/*
#cgo pkg-config: vips
#include "vips/vips.h"
*/
import "C"
// ImageSize represents the image width and height values
type ImageSize struct {
Width int
Height int
}
// ImageMetadata represents the basic metadata fields
type ImageMetadata struct {
Orientation int
Channels int
Alpha bool
Profile bool
Type string
Space string
Colourspace string
Size ImageSize
}
// Size returns the image size by width and height pixels.
func Size(buf []byte) (ImageSize, error) {
metadata, err := Metadata(buf)
if err != nil {
return ImageSize{}, err
}
return ImageSize{
Width: int(metadata.Size.Width),
Height: int(metadata.Size.Height),
}, nil
}
// ColourspaceIsSupported checks if the image colourspace is supported by libvips.
func ColourspaceIsSupported(buf []byte) (bool, error) {
return vipsColourspaceIsSupportedBuffer(buf)
}
// ImageInterpretation returns the image interpretation type.
// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation
func ImageInterpretation(buf []byte) (Interpretation, error) {
return vipsInterpretationBuffer(buf)
}
// Metadata returns the image metadata (size, type, alpha channel, profile, EXIF orientation...).
func Metadata(buf []byte) (ImageMetadata, error) {
defer C.vips_thread_shutdown()
image, imageType, err := vipsRead(buf)
if err != nil {
return ImageMetadata{}, err
}
defer C.g_object_unref(C.gpointer(image))
size := ImageSize{
Width: int(image.Xsize),
Height: int(image.Ysize),
}
metadata := ImageMetadata{
Size: size,
Channels: int(image.Bands),
Orientation: vipsExifOrientation(image),
Alpha: vipsHasAlpha(image),
Profile: vipsHasProfile(image),
Space: vipsSpace(image),
Type: ImageTypeName(imageType),
}
return metadata, nil
}

View File

@ -1,124 +0,0 @@
package bimg
import (
"io/ioutil"
"os"
"path"
"testing"
)
func TestSize(t *testing.T) {
files := []struct {
name string
width int
height int
}{
{"test.jpg", 1680, 1050},
{"test.png", 400, 300},
{"test.webp", 550, 368},
}
for _, file := range files {
size, err := Size(readFile(file.name))
if err != nil {
t.Fatalf("Cannot read the image: %#v", err)
}
if size.Width != file.width || size.Height != file.height {
t.Fatalf("Unexpected image size: %dx%d", size.Width, size.Height)
}
}
}
func TestMetadata(t *testing.T) {
files := []struct {
name string
format string
orientation int
alpha bool
profile bool
space string
}{
{"test.jpg", "jpeg", 0, false, false, "srgb"},
{"test_icc_prophoto.jpg", "jpeg", 0, false, true, "srgb"},
{"test.png", "png", 0, true, false, "srgb"},
{"test.webp", "webp", 0, false, false, "srgb"},
}
for _, file := range files {
metadata, err := Metadata(readFile(file.name))
if err != nil {
t.Fatalf("Cannot read the image: %s -> %s", file.name, err)
}
if metadata.Type != file.format {
t.Fatalf("Unexpected image format: %s", file.format)
}
if metadata.Orientation != file.orientation {
t.Fatalf("Unexpected image orientation: %d != %d", metadata.Orientation, file.orientation)
}
if metadata.Alpha != file.alpha {
t.Fatalf("Unexpected image alpha: %t != %t", metadata.Alpha, file.alpha)
}
if metadata.Profile != file.profile {
t.Fatalf("Unexpected image profile: %t != %t", metadata.Profile, file.profile)
}
if metadata.Space != file.space {
t.Fatalf("Unexpected image profile: %t != %t", metadata.Profile, file.profile)
}
}
}
func TestImageInterpretation(t *testing.T) {
files := []struct {
name string
interpretation Interpretation
}{
{"test.jpg", InterpretationSRGB},
{"test.png", InterpretationSRGB},
{"test.webp", InterpretationSRGB},
}
for _, file := range files {
interpretation, err := ImageInterpretation(readFile(file.name))
if err != nil {
t.Fatalf("Cannot read the image: %s -> %s", file.name, err)
}
if interpretation != file.interpretation {
t.Fatalf("Unexpected image interpretation")
}
}
}
func TestColourspaceIsSupported(t *testing.T) {
files := []struct {
name string
}{
{"test.jpg"},
{"test.png"},
{"test.webp"},
}
for _, file := range files {
supported, err := ColourspaceIsSupported(readFile(file.name))
if err != nil {
t.Fatalf("Cannot read the image: %s -> %s", file.name, err)
}
if supported != true {
t.Fatalf("Unsupported image colourspace")
}
}
supported, err := initImage("test.jpg").ColourspaceIsSupported()
if err != nil {
t.Errorf("Cannot process the image: %#v", err)
}
if supported != true {
t.Errorf("Non-supported colourspace")
}
}
func readFile(file string) []byte {
data, _ := os.Open(path.Join("fixtures", file))
buf, _ := ioutil.ReadAll(data)
return buf
}

View File

@ -1,221 +0,0 @@
package bimg
/*
#cgo pkg-config: vips
#include "vips/vips.h"
*/
import "C"
const (
// Quality defines the default JPEG quality to be used.
Quality = 80
// MaxSize defines the maximum pixels width or height supported.
MaxSize = 16383
)
// Gravity represents the image gravity value.
type Gravity int
const (
// GravityCentre represents the centre value used for image gravity orientation.
GravityCentre Gravity = iota
// GravityNorth represents the north value used for image gravity orientation.
GravityNorth
// GravityEast represents the east value used for image gravity orientation.
GravityEast
// GravitySouth represents the south value used for image gravity orientation.
GravitySouth
// GravityWest represents the west value used for image gravity orientation.
GravityWest
// GravitySmart enables libvips Smart Crop algorithm for image gravity orientation.
GravitySmart
)
// Interpolator represents the image interpolation value.
type Interpolator int
const (
// Bicubic interpolation value.
Bicubic Interpolator = iota
// Bilinear interpolation value.
Bilinear
// Nohalo interpolation value.
Nohalo
)
var interpolations = map[Interpolator]string{
Bicubic: "bicubic",
Bilinear: "bilinear",
Nohalo: "nohalo",
}
func (i Interpolator) String() string {
return interpolations[i]
}
// Angle represents the image rotation angle value.
type Angle int
const (
// D0 represents the rotation angle 0 degrees.
D0 Angle = 0
// D45 represents the rotation angle 90 degrees.
D45 Angle = 45
// D90 represents the rotation angle 90 degrees.
D90 Angle = 90
// D135 represents the rotation angle 90 degrees.
D135 Angle = 135
// D180 represents the rotation angle 180 degrees.
D180 Angle = 180
// D235 represents the rotation angle 235 degrees.
D235 Angle = 235
// D270 represents the rotation angle 270 degrees.
D270 Angle = 270
// D315 represents the rotation angle 180 degrees.
D315 Angle = 315
)
// Direction represents the image direction value.
type Direction int
const (
// Horizontal represents the orizontal image direction value.
Horizontal Direction = C.VIPS_DIRECTION_HORIZONTAL
// Vertical represents the vertical image direction value.
Vertical Direction = C.VIPS_DIRECTION_VERTICAL
)
// Interpretation represents the image interpretation type.
// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation
type Interpretation int
const (
// InterpretationError points to the libvips interpretation error type.
InterpretationError Interpretation = C.VIPS_INTERPRETATION_ERROR
// InterpretationMultiband points to its libvips interpretation equivalent type.
InterpretationMultiband Interpretation = C.VIPS_INTERPRETATION_MULTIBAND
// InterpretationBW points to its libvips interpretation equivalent type.
InterpretationBW Interpretation = C.VIPS_INTERPRETATION_B_W
// InterpretationCMYK points to its libvips interpretation equivalent type.
InterpretationCMYK Interpretation = C.VIPS_INTERPRETATION_CMYK
// InterpretationRGB points to its libvips interpretation equivalent type.
InterpretationRGB Interpretation = C.VIPS_INTERPRETATION_RGB
// InterpretationSRGB points to its libvips interpretation equivalent type.
InterpretationSRGB Interpretation = C.VIPS_INTERPRETATION_sRGB
// InterpretationRGB16 points to its libvips interpretation equivalent type.
InterpretationRGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16
// InterpretationGREY16 points to its libvips interpretation equivalent type.
InterpretationGREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16
// InterpretationScRGB points to its libvips interpretation equivalent type.
InterpretationScRGB Interpretation = C.VIPS_INTERPRETATION_scRGB
// InterpretationLAB points to its libvips interpretation equivalent type.
InterpretationLAB Interpretation = C.VIPS_INTERPRETATION_LAB
// InterpretationXYZ points to its libvips interpretation equivalent type.
InterpretationXYZ Interpretation = C.VIPS_INTERPRETATION_XYZ
)
// Extend represents the image extend mode, used when the edges
// of an image are extended, you can specify how you want the extension done.
// See: http://www.vips.ecs.soton.ac.uk/supported/8.4/doc/html/libvips/libvips-conversion.html#VIPS-EXTEND-BACKGROUND:CAPS
type Extend int
const (
// ExtendBlack extend with black (all 0) pixels mode.
ExtendBlack Extend = C.VIPS_EXTEND_BLACK
// ExtendCopy copy the image edges.
ExtendCopy Extend = C.VIPS_EXTEND_COPY
// ExtendRepeat repeat the whole image.
ExtendRepeat Extend = C.VIPS_EXTEND_REPEAT
// ExtendMirror mirror the whole image.
ExtendMirror Extend = C.VIPS_EXTEND_MIRROR
// ExtendWhite extend with white (all bits set) pixels.
ExtendWhite Extend = C.VIPS_EXTEND_WHITE
// ExtendBackground with colour from the background property.
ExtendBackground Extend = C.VIPS_EXTEND_BACKGROUND
// ExtendLast extend with last pixel.
ExtendLast Extend = C.VIPS_EXTEND_LAST
)
// WatermarkFont defines the default watermark font to be used.
var WatermarkFont = "sans 10"
// Color represents a traditional RGB color scheme.
type Color struct {
R, G, B uint8
}
// ColorBlack is a shortcut to black RGB color representation.
var ColorBlack = Color{0, 0, 0}
// Watermark represents the text-based watermark supported options.
type Watermark struct {
Width int
DPI int
Margin int
Opacity float32
NoReplicate bool
Text string
Font string
Background Color
}
// WatermarkImage represents the image-based watermark supported options.
type WatermarkImage struct {
Left int
Top int
Buf []byte
Opacity float32
}
// GaussianBlur represents the gaussian image transformation values.
type GaussianBlur struct {
Sigma float64
MinAmpl float64
}
// Sharpen represents the image sharp transformation options.
type Sharpen struct {
Radius int
X1 float64
Y2 float64
Y3 float64
M1 float64
M2 float64
}
// Options represents the supported image transformation options.
type Options struct {
Height int
Width int
AreaHeight int
AreaWidth int
Top int
Left int
Quality int
Compression int
Zoom int
Crop bool
SmartCrop bool // Deprecated
Enlarge bool
Embed bool
Flip bool
Flop bool
Force bool
NoAutoRotate bool
NoProfile bool
Interlace bool
StripMetadata bool
Trim bool
Extend Extend
Rotate Angle
Background Color
Gravity Gravity
Watermark Watermark
WatermarkImage WatermarkImage
Type ImageType
Interpolator Interpolator
Interpretation Interpretation
GaussianBlur GaussianBlur
Sharpen Sharpen
OutputICC string
}

View File

@ -1,302 +0,0 @@
#!/bin/bash
vips_version_minimum=8.4.2
vips_version_latest_major_minor=8.4
vips_version_latest_patch=2
openslide_version_minimum=3.4.0
openslide_version_latest_major_minor=3.4
openslide_version_latest_patch=1
install_libvips_from_source() {
echo "Compiling libvips $vips_version_latest_major_minor.$vips_version_latest_patch from source"
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major_minor/vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch
CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" ./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1
make
make install
cd ..
rm -rf vips-$vips_version_latest_major_minor.$vips_version_latest_patch
rm vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
ldconfig
echo "Installed libvips $(PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig pkg-config --modversion vips)"
}
install_libopenslide_from_source() {
echo "Compiling openslide $openslide_version_latest_major_minor.$openslide_version_latest_patch from source"
curl -O -L https://github.com/openslide/openslide/releases/download/v$openslide_version_latest_major_minor.$openslide_version_latest_patch/openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
tar xzvf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
cd openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch
PKG_CONFIG_PATH=$pkg_config_path ./configure $1
make
make install
cd ..
rm -rf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch
rm openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
ldconfig
echo "Installed libopenslide $openslide_version_latest_major_minor.$openslide_version_latest_patch"
}
sorry() {
echo "Sorry, I don't yet know how to install lib$1 on $2"
exit 1
}
pkg_config_path="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig"
check_if_library_exists() {
PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists $1
if [ $? -eq 0 ]; then
version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion $1)
PKG_CONFIG_PATH=$pkg_config_path pkg-config --atleast-version=$2 $1
if [ $? -eq 0 ]; then
# Found suitable version of libvips
echo "Found lib$1 $version_found"
return 1
fi
echo "Found lib$1 $version_found but require $2"
else
echo "Could not find lib$1 using a PKG_CONFIG_PATH of '$pkg_config_path'"
fi
return 0
}
enable_openslide=0
# Is libvips already installed, and is it at least the minimum required version?
if [ $# -eq 1 ]; then
if [ "$1" = "--with-openslide" ]; then
echo "Installing vips with openslide support"
enable_openslide=1
else
echo "Sorry, $1 is not supported. Did you mean --with-openslide?"
exit 1
fi
fi
if ! type pkg-config >/dev/null; then
sorry "vips" "a system without pkg-config"
fi
openslide_exists=0
if [ $enable_openslide -eq 1 ]; then
check_if_library_exists "openslide" "$openslide_version_minimum"
openslide_exists=$?
fi
check_if_library_exists "vips" "$vips_version_minimum"
vips_exists=$?
if [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 1 ]; then
if [ $openslide_exists -eq 1 ]; then
# Check if vips compiled with openslide support
vips_with_openslide=`vips list classes | grep -i opensli`
if [ -z $vips_with_openslide ]; then
echo "Vips compiled without openslide support."
else
exit 0
fi
fi
elif [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 0 ]; then
exit 0
fi
# Verify root/sudo access
if [ "$(id -u)" -ne "0" ]; then
echo "Sorry, I need root/sudo access to continue"
exit 1
fi
# Deprecation warning
if [ "$(arch)" == "x86_64" ]; then
echo "This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+"
fi
# OS-specific installations of libopenslide follows
# Either openslide does not exist, or vips is installed without openslide support
if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_exists -eq 0 ]; then
if [ -f /etc/debian_version ]; then
# Debian Linux
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid|wily|xenial)
# Debian 8, Ubuntu 15
echo "Installing libopenslide via apt-get"
apt-get install -y libopenslide-dev
;;
trusty|utopic|qiana|rebecca|rafaela|freya|rosa|sarah|serena)
# Ubuntu 14, Mint 17+
echo "Installing libopenslide dependencies via apt-get"
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
install_libopenslide_from_source
;;
precise|wheezy|maya)
# Debian 7, Ubuntu 12.04, Mint 13
echo "Installing libopenslide dependencies via apt-get"
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
install_libopenslide_from_source
;;
*)
# Unsupported Debian-based OS
sorry "openslide" "Debian-based $DISTRO"
;;
esac
elif [ -f /etc/redhat-release ]; then
# Red Hat Linux
RELEASE=$(cat /etc/redhat-release)
echo "Detected Red Hat Linux '$RELEASE'"
case $RELEASE in
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
# RHEL/CentOS 7
echo "Installing libopenslide dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
install_libopenslide_from_source "--prefix=/usr"
;;
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
# RHEL/CentOS 6
echo "Installing libopenslide dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
install_libopenslide_from_source "--prefix=/usr"
;;
"Fedora release 21 "*|"Fedora release 22 "*)
# Fedora 21, 22
echo "Installing libopenslide via yum"
yum install -y openslide-devel
;;
*)
# Unsupported RHEL-based OS
sorry "openslide" "$RELEASE"
;;
esac
elif [ -f /etc/os-release ]; then
RELEASE=$(cat /etc/os-release | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.2"*)
echo "Installing libopenslide via zypper"
zypper --gpg-auto-import-keys install -y libopenslide-devel
;;
esac
elif [ -f /etc/SuSE-brand ]; then
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.1")
echo "Installing libopenslide dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl libpng16-devel libjpeg-turbo libjpeg8-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel libgdk_pixbuf-2_0-0 sqlite3-devel cairo-devel glib2-devel
install_libopenslide_from_source
;;
esac
else
# Unsupported OS
sorry "openslide" "$(uname -a)"
fi
fi
# OS-specific installations of libvips follows
if [ -f /etc/debian_version ]; then
# Debian Linux
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|trusty|utopic|vivid|wily|xenial|qiana|rebecca|rafaela|freya|rosa|sarah|serena)
# Debian 8, Ubuntu 14.04+, Mint 17+
echo "Installing libvips dependencies via apt-get"
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
install_libvips_from_source
;;
precise|wheezy|maya)
# Debian 7, Ubuntu 12.04, Mint 13
echo "Installing libvips dependencies via apt-get"
add-apt-repository -y ppa:lyrasis/precise-backports
apt-get update
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
install_libvips_from_source
;;
*)
# Unsupported Debian-based OS
sorry "vips" "Debian-based $DISTRO"
;;
esac
elif [ -f /etc/redhat-release ]; then
# Red Hat Linux
RELEASE=$(cat /etc/redhat-release)
echo "Detected Red Hat Linux '$RELEASE'"
case $RELEASE in
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
# RHEL/CentOS 7
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source "--prefix=/usr"
;;
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
# RHEL/CentOS 6
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel
yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
yum install -y --enablerepo=nux-dextop gobject-introspection-devel
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
yum install -y --enablerepo=remi libwebp-devel
install_libvips_from_source "--prefix=/usr"
;;
"Fedora"*)
# Fedora 21, 22, 23
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gcc-c++ gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
install_libvips_from_source "--prefix=/usr"
;;
*)
# Unsupported RHEL-based OS
sorry "vips" "$RELEASE"
;;
esac
elif [ -f /etc/system-release ]; then
# Probably Amazon Linux
RELEASE=$(cat /etc/system-release)
case $RELEASE in
"Amazon Linux AMI release 2015.03"|"Amazon Linux AMI release 2015.09")
# Amazon Linux
echo "Detected '$RELEASE'"
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
install_libvips_from_source "--prefix=/usr"
;;
*)
# Unsupported Amazon Linux version
sorry "vips" "$RELEASE"
;;
esac
elif [ -f /etc/os-release ]; then
RELEASE=$(cat /etc/os-release | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.2"*)
echo "Installing libvips dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source
;;
esac
elif [ -f /etc/SuSE-brand ]; then
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.1")
echo "Installing libvips dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source
;;
esac
else
# Unsupported OS
sorry "vips" "$(uname -a)"
fi

View File

@ -1,16 +0,0 @@
// +build go17
package bimg
import (
"runtime"
)
// Resize is used to transform a given image as byte buffer
// with the passed options.
func Resize(buf []byte, o Options) ([]byte, error) {
// Required in order to prevent premature garbage collection. See:
// https://github.com/h2non/bimg/pull/162
defer runtime.KeepAlive(buf)
return resizer(buf, o)
}

View File

@ -1,10 +0,0 @@
// +build !go17
package bimg
// Resize is used to transform a given image as byte buffer
// with the passed options.
// Used as proxy to resizer() only in Go <= 1.6 versions
func Resize(buf []byte, o Options) ([]byte, error) {
return resizer(buf, o)
}

View File

@ -1,572 +0,0 @@
package bimg
/*
#cgo pkg-config: vips
#include "vips/vips.h"
*/
import "C"
import (
"errors"
"math"
)
// resizer is used to transform a given image as byte buffer
// with the passed options.
func resizer(buf []byte, o Options) ([]byte, error) {
defer C.vips_thread_shutdown()
image, imageType, err := loadImage(buf)
if err != nil {
return nil, err
}
// Clone and define default options
o = applyDefaults(o, imageType)
if !IsTypeSupported(o.Type) {
return nil, errors.New("Unsupported image output type")
}
debug("Options: %#v", o)
// Auto rotate image based on EXIF orientation header
image, rotated, err := rotateAndFlipImage(image, o)
if err != nil {
return nil, err
}
// If JPEG image, retrieve the buffer
if rotated && imageType == JPEG && !o.NoAutoRotate {
buf, err = getImageBuffer(image)
if err != nil {
return nil, err
}
}
inWidth := int(image.Xsize)
inHeight := int(image.Ysize)
// Infer the required operation based on the in/out image sizes for a coherent transformation
normalizeOperation(&o, inWidth, inHeight)
// image calculations
factor := imageCalculations(&o, inWidth, inHeight)
shrink := calculateShrink(factor, o.Interpolator)
residual := calculateResidual(factor, shrink)
// Do not enlarge the output if the input width or height
// are already less than the required dimensions
if !o.Enlarge && !o.Force {
if inWidth < o.Width && inHeight < o.Height {
factor = 1.0
shrink = 1
residual = 0
o.Width = inWidth
o.Height = inHeight
}
}
// Try to use libjpeg shrink-on-load
if imageType == JPEG && shrink >= 2 {
tmpImage, factor, err := shrinkJpegImage(buf, image, factor, shrink)
if err != nil {
return nil, err
}
image = tmpImage
factor = math.Max(factor, 1.0)
shrink = int(math.Floor(factor))
residual = float64(shrink) / factor
}
// Zoom image, if necessary
image, err = zoomImage(image, o.Zoom)
if err != nil {
return nil, err
}
// Transform image, if necessary
if shouldTransformImage(o, inWidth, inHeight) {
image, err = transformImage(image, o, shrink, residual)
if err != nil {
return nil, err
}
}
// Apply effects, if necessary
if shouldApplyEffects(o) {
image, err = applyEffects(image, o)
if err != nil {
return nil, err
}
}
// Add watermark, if necessary
image, err = watermarkImageWithText(image, o.Watermark)
if err != nil {
return nil, err
}
// Add watermark, if necessary
image, err = watermarkImageWithAnotherImage(image, o.WatermarkImage)
if err != nil {
return nil, err
}
// Flatten image on a background, if necessary
image, err = imageFlatten(image, imageType, o)
if err != nil {
return nil, err
}
return saveImage(image, o)
}
func loadImage(buf []byte) (*C.VipsImage, ImageType, error) {
if len(buf) == 0 {
return nil, JPEG, errors.New("Image buffer is empty")
}
image, imageType, err := vipsRead(buf)
if err != nil {
return nil, JPEG, err
}
return image, imageType, nil
}
func applyDefaults(o Options, imageType ImageType) Options {
if o.Quality == 0 {
o.Quality = Quality
}
if o.Compression == 0 {
o.Compression = 6
}
if o.Type == 0 {
o.Type = imageType
}
if o.Interpretation == 0 {
o.Interpretation = InterpretationSRGB
}
return o
}
func saveImage(image *C.VipsImage, o Options) ([]byte, error) {
saveOptions := vipsSaveOptions{
Quality: o.Quality,
Type: o.Type,
Compression: o.Compression,
Interlace: o.Interlace,
NoProfile: o.NoProfile,
Interpretation: o.Interpretation,
OutputICC: o.OutputICC,
StripMetadata: o.StripMetadata,
}
// Finally get the resultant buffer
return vipsSave(image, saveOptions)
}
func normalizeOperation(o *Options, inWidth, inHeight int) {
if !o.Force && !o.Crop && !o.Embed && !o.Enlarge && o.Rotate == 0 && (o.Width > 0 || o.Height > 0) {
o.Force = true
}
}
func shouldTransformImage(o Options, inWidth, inHeight int) bool {
return o.Force || (o.Width > 0 && o.Width != inWidth) ||
(o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0 ||
o.Trim
}
func shouldApplyEffects(o Options) bool {
return o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 || o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0
}
func transformImage(image *C.VipsImage, o Options, shrink int, residual float64) (*C.VipsImage, error) {
var err error
// Use vips_shrink with the integral reduction
if shrink > 1 {
image, residual, err = shrinkImage(image, o, residual, shrink)
if err != nil {
return nil, err
}
}
residualx, residualy := residual, residual
if o.Force {
residualx = float64(o.Width) / float64(image.Xsize)
residualy = float64(o.Height) / float64(image.Ysize)
}
if o.Force || residual != 0 {
if residualx < 1 && residualy < 1 {
image, err = vipsReduce(image, 1/residualx, 1/residualy)
} else {
image, err = vipsAffine(image, residualx, residualy, o.Interpolator)
}
if err != nil {
return nil, err
}
}
if o.Force {
o.Crop = false
o.Embed = false
}
image, err = extractOrEmbedImage(image, o)
if err != nil {
return nil, err
}
debug("Transform: shrink=%v, residual=%v, interpolator=%v",
shrink, residual, o.Interpolator.String())
return image, nil
}
func applyEffects(image *C.VipsImage, o Options) (*C.VipsImage, error) {
var err error
if o.GaussianBlur.Sigma > 0 || o.GaussianBlur.MinAmpl > 0 {
image, err = vipsGaussianBlur(image, o.GaussianBlur)
if err != nil {
return nil, err
}
}
if o.Sharpen.Radius > 0 && o.Sharpen.Y2 > 0 || o.Sharpen.Y3 > 0 {
image, err = vipsSharpen(image, o.Sharpen)
if err != nil {
return nil, err
}
}
debug("Effects: gaussSigma=%v, gaussMinAmpl=%v, sharpenRadius=%v",
o.GaussianBlur.Sigma, o.GaussianBlur.MinAmpl, o.Sharpen.Radius)
return image, nil
}
func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) {
var err error
inWidth := int(image.Xsize)
inHeight := int(image.Ysize)
switch {
case o.Gravity == GravitySmart, o.SmartCrop:
image, err = vipsSmartCrop(image, o.Width, o.Height)
break
case o.Crop:
width := int(math.Min(float64(inWidth), float64(o.Width)))
height := int(math.Min(float64(inHeight), float64(o.Height)))
left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity)
left, top = int(math.Max(float64(left), 0)), int(math.Max(float64(top), 0))
image, err = vipsExtract(image, left, top, width, height)
break
case o.Embed:
left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2
image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend, o.Background)
break
case o.Trim:
left, top, width, height, err := vipsTrim(image)
if err == nil {
image, err = vipsExtract(image, left, top, width, height)
}
break
case o.Top != 0 || o.Left != 0 || o.AreaWidth != 0 || o.AreaHeight != 0:
if o.AreaWidth == 0 {
o.AreaHeight = o.Width
}
if o.AreaHeight == 0 {
o.AreaHeight = o.Height
}
if o.AreaWidth == 0 || o.AreaHeight == 0 {
return nil, errors.New("Extract area width/height params are required")
}
image, err = vipsExtract(image, o.Left, o.Top, o.AreaWidth, o.AreaHeight)
break
}
return image, err
}
func rotateAndFlipImage(image *C.VipsImage, o Options) (*C.VipsImage, bool, error) {
var err error
var rotated bool
var direction Direction = -1
if o.NoAutoRotate == false {
rotation, flip := calculateRotationAndFlip(image, o.Rotate)
if flip {
o.Flip = flip
}
if rotation > 0 && o.Rotate == 0 {
o.Rotate = rotation
}
}
if o.Rotate > 0 {
rotated = true
image, err = vipsRotate(image, getAngle(o.Rotate))
}
if o.Flip {
direction = Horizontal
} else if o.Flop {
direction = Vertical
}
if direction != -1 {
rotated = true
image, err = vipsFlip(image, direction)
}
return image, rotated, err
}
func watermarkImageWithText(image *C.VipsImage, w Watermark) (*C.VipsImage, error) {
if w.Text == "" {
return image, nil
}
// Defaults
if w.Font == "" {
w.Font = WatermarkFont
}
if w.Width == 0 {
w.Width = int(math.Floor(float64(image.Xsize / 6)))
}
if w.DPI == 0 {
w.DPI = 150
}
if w.Margin == 0 {
w.Margin = w.Width
}
if w.Opacity == 0 {
w.Opacity = 0.25
} else if w.Opacity > 1 {
w.Opacity = 1
}
image, err := vipsWatermark(image, w)
if err != nil {
return nil, err
}
return image, nil
}
func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) {
if len(w.Buf) == 0 {
return image, nil
}
if w.Opacity == 0.0 {
w.Opacity = 1.0
}
image, err := vipsDrawWatermark(image, w)
if err != nil {
return nil, err
}
return image, nil
}
func imageFlatten(image *C.VipsImage, imageType ImageType, o Options) (*C.VipsImage, error) {
// Only PNG images are supported for now
if imageType != PNG || o.Background == ColorBlack {
return image, nil
}
return vipsFlattenBackground(image, o.Background)
}
func zoomImage(image *C.VipsImage, zoom int) (*C.VipsImage, error) {
if zoom == 0 {
return image, nil
}
return vipsZoom(image, zoom+1)
}
func shrinkImage(image *C.VipsImage, o Options, residual float64, shrink int) (*C.VipsImage, float64, error) {
// Use vips_shrink with the integral reduction
image, err := vipsShrink(image, shrink)
if err != nil {
return nil, 0, err
}
// Recalculate residual float based on dimensions of required vs shrunk images
residualx := float64(o.Width) / float64(image.Xsize)
residualy := float64(o.Height) / float64(image.Ysize)
if o.Crop {
residual = math.Max(residualx, residualy)
} else {
residual = math.Min(residualx, residualy)
}
return image, residual, nil
}
func shrinkJpegImage(buf []byte, input *C.VipsImage, factor float64, shrink int) (*C.VipsImage, float64, error) {
var image *C.VipsImage
var err error
shrinkOnLoad := 1
// Recalculate integral shrink and double residual
switch {
case shrink >= 8:
factor = factor / 8
shrinkOnLoad = 8
case shrink >= 4:
factor = factor / 4
shrinkOnLoad = 4
case shrink >= 2:
factor = factor / 2
shrinkOnLoad = 2
}
// Reload input using shrink-on-load
if shrinkOnLoad > 1 {
image, err = vipsShrinkJpeg(buf, input, shrinkOnLoad)
}
return image, factor, err
}
func imageCalculations(o *Options, inWidth, inHeight int) float64 {
factor := 1.0
xfactor := float64(inWidth) / float64(o.Width)
yfactor := float64(inHeight) / float64(o.Height)
switch {
// Fixed width and height
case o.Width > 0 && o.Height > 0:
if o.Crop {
factor = math.Min(xfactor, yfactor)
} else {
factor = math.Max(xfactor, yfactor)
}
// Fixed width, auto height
case o.Width > 0:
if o.Crop {
o.Height = inHeight
} else {
factor = xfactor
o.Height = roundFloat(float64(inHeight) / factor)
}
// Fixed height, auto width
case o.Height > 0:
if o.Crop {
o.Width = inWidth
} else {
factor = yfactor
o.Width = roundFloat(float64(inWidth) / factor)
}
// Identity transform
default:
o.Width = inWidth
o.Height = inHeight
break
}
return factor
}
func roundFloat(f float64) int {
if f < 0 {
return int(math.Ceil(f - 0.5))
}
return int(math.Floor(f + 0.5))
}
func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) (int, int) {
left, top := 0, 0
switch gravity {
case GravityNorth:
left = (inWidth - outWidth + 1) / 2
case GravityEast:
left = inWidth - outWidth
top = (inHeight - outHeight + 1) / 2
case GravitySouth:
left = (inWidth - outWidth + 1) / 2
top = inHeight - outHeight
case GravityWest:
top = (inHeight - outHeight + 1) / 2
default:
left = (inWidth - outWidth + 1) / 2
top = (inHeight - outHeight + 1) / 2
}
return left, top
}
func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) {
rotate := D0
flip := false
if angle > 0 {
return rotate, flip
}
switch vipsExifOrientation(image) {
case 6:
rotate = D90
break
case 3:
rotate = D180
break
case 8:
rotate = D270
break
case 2:
flip = true
break // flip 1
case 7:
flip = true
rotate = D270
break // flip 6
case 4:
flip = true
rotate = D180
break // flip 3
case 5:
flip = true
rotate = D90
break // flip 8
}
return rotate, flip
}
func calculateShrink(factor float64, i Interpolator) int {
var shrink float64
// Calculate integral box shrink
windowSize := vipsWindowSize(i.String())
if factor >= 2 && windowSize > 3 {
// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
shrink = float64(math.Floor(factor * 3.0 / windowSize))
} else {
shrink = math.Floor(factor)
}
return int(math.Max(shrink, 1))
}
func calculateResidual(factor float64, shrink int) float64 {
return float64(shrink) / factor
}
func getAngle(angle Angle) Angle {
divisor := angle % 90
if divisor != 0 {
angle = angle - divisor
}
return Angle(math.Min(float64(angle), 270))
}

View File

@ -1,704 +0,0 @@
package bimg
import (
"bytes"
"crypto/md5"
"fmt"
"image"
"image/jpeg"
"io/ioutil"
"os"
"path"
"strconv"
"testing"
)
func TestResize(t *testing.T) {
options := Options{Width: 800, Height: 600}
buf, _ := Read("fixtures/test.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
if DetermineImageType(newImg) != JPEG {
t.Fatal("Image is not jpeg")
}
size, _ := Size(newImg)
if size.Height != options.Height || size.Width != options.Width {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_out.jpg", newImg)
}
func TestResizeVerticalImage(t *testing.T) {
tests := []struct {
format ImageType
options Options
}{
{JPEG, Options{Width: 800, Height: 600}},
{JPEG, Options{Width: 1000, Height: 1000}},
{JPEG, Options{Width: 1000, Height: 1500}},
{JPEG, Options{Width: 1000}},
{JPEG, Options{Height: 1500}},
{JPEG, Options{Width: 100, Height: 50}},
{JPEG, Options{Width: 2000, Height: 2000}},
{JPEG, Options{Width: 500, Height: 1000}},
{JPEG, Options{Width: 500}},
{JPEG, Options{Height: 500}},
{JPEG, Options{Crop: true, Width: 500, Height: 1000}},
{JPEG, Options{Crop: true, Enlarge: true, Width: 2000, Height: 1400}},
{JPEG, Options{Enlarge: true, Force: true, Width: 2000, Height: 2000}},
{JPEG, Options{Force: true, Width: 2000, Height: 2000}},
}
buf, _ := Read("fixtures/vertical.jpg")
for _, test := range tests {
image, err := Resize(buf, test.options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", test.options, err)
}
if DetermineImageType(image) != test.format {
t.Fatalf("Image format is invalid. Expected: %#v", test.format)
}
size, _ := Size(image)
if test.options.Height > 0 && size.Height != test.options.Height {
t.Fatalf("Invalid height: %d", size.Height)
}
if test.options.Width > 0 && size.Width != test.options.Width {
t.Fatalf("Invalid width: %d", size.Width)
}
Write("fixtures/test_vertical_"+strconv.Itoa(test.options.Width)+"x"+strconv.Itoa(test.options.Height)+"_out.jpg", image)
}
}
func TestResizeCustomSizes(t *testing.T) {
tests := []struct {
format ImageType
options Options
}{
{JPEG, Options{Width: 800, Height: 600}},
{JPEG, Options{Width: 1000, Height: 1000}},
{JPEG, Options{Width: 100, Height: 50}},
{JPEG, Options{Width: 2000, Height: 2000}},
{JPEG, Options{Width: 500, Height: 1000}},
{JPEG, Options{Width: 500}},
{JPEG, Options{Height: 500}},
{JPEG, Options{Crop: true, Width: 500, Height: 1000}},
{JPEG, Options{Crop: true, Enlarge: true, Width: 2000, Height: 1400}},
{JPEG, Options{Enlarge: true, Force: true, Width: 2000, Height: 2000}},
{JPEG, Options{Force: true, Width: 2000, Height: 2000}},
}
buf, _ := Read("fixtures/test.jpg")
for _, test := range tests {
image, err := Resize(buf, test.options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", test.options, err)
}
if DetermineImageType(image) != test.format {
t.Fatalf("Image format is invalid. Expected: %#v", test.format)
}
size, _ := Size(image)
if test.options.Height > 0 && size.Height != test.options.Height {
t.Fatalf("Invalid height: %d", size.Height)
}
if test.options.Width > 0 && size.Width != test.options.Width {
t.Fatalf("Invalid width: %d", size.Width)
}
}
}
func TestResizePrecision(t *testing.T) {
// see https://github.com/h2non/bimg/issues/99
img := image.NewGray16(image.Rect(0, 0, 1920, 1080))
input := &bytes.Buffer{}
jpeg.Encode(input, img, nil)
opts := Options{Width: 300}
newImg, err := Resize(input.Bytes(), opts)
if err != nil {
t.Fatalf("Resize(imgData, %#v) error: %#v", opts, err)
}
size, _ := Size(newImg)
if size.Width != opts.Width {
t.Fatalf("Invalid width: %d", size.Width)
}
}
func TestRotate(t *testing.T) {
options := Options{Width: 800, Height: 600, Rotate: 270, Crop: true}
buf, _ := Read("fixtures/test.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
if DetermineImageType(newImg) != JPEG {
t.Error("Image is not jpeg")
}
size, _ := Size(newImg)
if size.Width != options.Width || size.Height != options.Height {
t.Errorf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_rotate_out.jpg", newImg)
}
func TestInvalidRotateDegrees(t *testing.T) {
options := Options{Width: 800, Height: 600, Rotate: 111, Crop: true}
buf, _ := Read("fixtures/test.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
if DetermineImageType(newImg) != JPEG {
t.Errorf("Image is not jpeg")
}
size, _ := Size(newImg)
if size.Width != options.Width || size.Height != options.Height {
t.Errorf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_rotate_invalid_out.jpg", newImg)
}
func TestCorruptedImage(t *testing.T) {
options := Options{Width: 800, Height: 600}
buf, _ := Read("fixtures/corrupt.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
if DetermineImageType(newImg) != JPEG {
t.Fatal("Image is not jpeg")
}
size, _ := Size(newImg)
if size.Height != options.Height || size.Width != options.Width {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_corrupt_out.jpg", newImg)
}
func TestNoColorProfile(t *testing.T) {
options := Options{Width: 800, Height: 600, NoProfile: true}
buf, _ := Read("fixtures/test.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
metadata, err := Metadata(newImg)
if metadata.Profile == true {
t.Fatal("Invalid profile data")
}
size, _ := Size(newImg)
if size.Height != options.Height || size.Width != options.Width {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
}
func TestEmbedExtendColor(t *testing.T) {
options := Options{Width: 400, Height: 600, Crop: false, Embed: true, Extend: ExtendWhite, Background: Color{255, 20, 10}}
buf, _ := Read("fixtures/test_issue.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
size, _ := Size(newImg)
if size.Height != options.Height || size.Width != options.Width {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_extend_white_out.jpg", newImg)
}
func TestEmbedExtendWithCustomColor(t *testing.T) {
options := Options{Width: 400, Height: 600, Crop: false, Embed: true, Extend: 5, Background: Color{255, 20, 10}}
buf, _ := Read("fixtures/test_issue.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
size, _ := Size(newImg)
if size.Height != options.Height || size.Width != options.Width {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_extend_background_out.jpg", newImg)
}
func TestGaussianBlur(t *testing.T) {
options := Options{Width: 800, Height: 600, GaussianBlur: GaussianBlur{Sigma: 5}}
buf, _ := Read("fixtures/test.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
size, _ := Size(newImg)
if size.Height != options.Height || size.Width != options.Width {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_gaussian_out.jpg", newImg)
}
func TestSharpen(t *testing.T) {
options := Options{Width: 800, Height: 600, Sharpen: Sharpen{Radius: 1, X1: 1.5, Y2: 20, Y3: 50, M1: 1, M2: 2}}
buf, _ := Read("fixtures/test.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
size, _ := Size(newImg)
if size.Height != options.Height || size.Width != options.Width {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_sharpen_out.jpg", newImg)
}
func TestExtractWithDefaultAxis(t *testing.T) {
options := Options{AreaWidth: 200, AreaHeight: 200}
buf, _ := Read("fixtures/test.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
size, _ := Size(newImg)
if size.Height != options.AreaHeight || size.Width != options.AreaWidth {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_extract_defaults_out.jpg", newImg)
}
func TestExtractCustomAxis(t *testing.T) {
options := Options{Top: 100, Left: 100, AreaWidth: 200, AreaHeight: 200}
buf, _ := Read("fixtures/test.jpg")
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
size, _ := Size(newImg)
if size.Height != options.AreaHeight || size.Width != options.AreaWidth {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
Write("fixtures/test_extract_custom_axis_out.jpg", newImg)
}
func TestConvert(t *testing.T) {
width, height := 300, 240
formats := [3]ImageType{PNG, WEBP, JPEG}
files := []string{
"test.jpg",
"test.png",
"test.webp",
}
for _, file := range files {
img, err := os.Open("fixtures/" + file)
if err != nil {
t.Fatal(err)
}
buf, err := ioutil.ReadAll(img)
if err != nil {
t.Fatal(err)
}
img.Close()
for _, format := range formats {
options := Options{Width: width, Height: height, Crop: true, Type: format}
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
if DetermineImageType(newImg) != format {
t.Fatal("Image is not png")
}
size, _ := Size(newImg)
if size.Height != height || size.Width != width {
t.Fatalf("Invalid image size: %dx%d", size.Width, size.Height)
}
}
}
}
func TestResizePngWithTransparency(t *testing.T) {
width, height := 300, 240
options := Options{Width: width, Height: height, Crop: true}
img, err := os.Open("fixtures/transparent.png")
if err != nil {
t.Fatal(err)
}
defer img.Close()
buf, err := ioutil.ReadAll(img)
if err != nil {
t.Fatal(err)
}
newImg, err := Resize(buf, options)
if err != nil {
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
}
if DetermineImageType(newImg) != PNG {
t.Fatal("Image is not png")
}
size, _ := Size(newImg)
if size.Height != height || size.Width != width {
t.Fatal("Invalid image size")
}
Write("fixtures/transparent_out.png", newImg)
}
func TestRotationAndFlip(t *testing.T) {
files := []struct {
Name string
Angle Angle
Flip bool
}{
{"Landscape_1", 0, false},
{"Landscape_2", 0, true},
{"Landscape_3", D180, false},
{"Landscape_4", D180, true},
{"Landscape_5", D90, true},
{"Landscape_6", D90, false},
{"Landscape_7", D270, true},
{"Landscape_8", D270, false},
{"Portrait_1", 0, false},
{"Portrait_2", 0, true},
{"Portrait_3", D180, false},
{"Portrait_4", D180, true},
{"Portrait_5", D90, true},
{"Portrait_6", D90, false},
{"Portrait_7", D270, true},
{"Portrait_8", D270, false},
}
for _, file := range files {
img, err := os.Open(fmt.Sprintf("fixtures/exif/%s.jpg", file.Name))
if err != nil {
t.Fatal(err)
}
buf, err := ioutil.ReadAll(img)
if err != nil {
t.Fatal(err)
}
img.Close()
image, _, err := loadImage(buf)
if err != nil {
t.Fatal(err)
}
angle, flip := calculateRotationAndFlip(image, D0)
if angle != file.Angle {
t.Errorf("Rotation for %v expected to be %v. got %v", file.Name, file.Angle, angle)
}
if flip != file.Flip {
t.Errorf("Flip for %v expected to be %v. got %v", file.Name, file.Flip, flip)
}
// Visual debugging.
newImg, err := Resize(buf, Options{})
if err != nil {
t.Fatal(err)
}
Write(fmt.Sprintf("fixtures/exif/%s_out.jpg", file.Name), newImg)
}
}
func TestIfBothSmartCropOptionsAreIdentical(t *testing.T) {
if !(VipsMajorVersion >= 8 && VipsMinorVersion > 4) {
t.Skipf("Skipping this test, libvips doesn't meet version requirement %s > 8.4", VipsVersion)
}
benchmarkOptions := Options{Width: 100, Height: 100, Crop: true}
smartCropOptions := Options{Width: 100, Height: 100, Crop: true, SmartCrop: true}
gravityOptions := Options{Width: 100, Height: 100, Crop: true, Gravity: GravitySmart}
testImg, err := os.Open("fixtures/northern_cardinal_bird.jpg")
if err != nil {
t.Fatal(err)
}
defer testImg.Close()
testImgByte, err := ioutil.ReadAll(testImg)
if err != nil {
t.Fatal(err)
}
scImg, err := Resize(testImgByte, smartCropOptions)
if err != nil {
t.Fatal(err)
}
gImg, err := Resize(testImgByte, gravityOptions)
if err != nil {
t.Fatal(err)
}
benchmarkImg, err := Resize(testImgByte, benchmarkOptions)
if err != nil {
t.Fatal(err)
}
sch, gh, bh := md5.Sum(scImg), md5.Sum(gImg), md5.Sum(benchmarkImg)
if gh == bh || sch == bh {
t.Error("Expected both options produce a different result from a standard crop.")
}
if sch != gh {
t.Errorf("Expected both options to result in the same output, %x != %x", sch, gh)
}
}
func runBenchmarkResize(file string, o Options, b *testing.B) {
buf, _ := Read(path.Join("fixtures", file))
for n := 0; n < b.N; n++ {
Resize(buf, o)
}
}
func BenchmarkRotateJpeg(b *testing.B) {
options := Options{Rotate: 180}
runBenchmarkResize("test.jpg", options, b)
}
func BenchmarkResizeLargeJpeg(b *testing.B) {
options := Options{
Width: 800,
Height: 600,
}
runBenchmarkResize("test.jpg", options, b)
}
func BenchmarkResizePng(b *testing.B) {
options := Options{
Width: 200,
Height: 200,
}
runBenchmarkResize("test.png", options, b)
}
func BenchmarkResizeWebP(b *testing.B) {
options := Options{
Width: 200,
Height: 200,
}
runBenchmarkResize("test.webp", options, b)
}
func BenchmarkConvertToJpeg(b *testing.B) {
options := Options{Type: JPEG}
runBenchmarkResize("test.png", options, b)
}
func BenchmarkConvertToPng(b *testing.B) {
options := Options{Type: PNG}
runBenchmarkResize("test.jpg", options, b)
}
func BenchmarkConvertToWebp(b *testing.B) {
options := Options{Type: WEBP}
runBenchmarkResize("test.jpg", options, b)
}
func BenchmarkCropJpeg(b *testing.B) {
options := Options{
Width: 800,
Height: 600,
}
runBenchmarkResize("test.jpg", options, b)
}
func BenchmarkCropPng(b *testing.B) {
options := Options{
Width: 800,
Height: 600,
}
runBenchmarkResize("test.png", options, b)
}
func BenchmarkCropWebP(b *testing.B) {
options := Options{
Width: 800,
Height: 600,
}
runBenchmarkResize("test.webp", options, b)
}
func BenchmarkExtractJpeg(b *testing.B) {
options := Options{
Top: 100,
Left: 50,
AreaWidth: 600,
AreaHeight: 480,
}
runBenchmarkResize("test.jpg", options, b)
}
func BenchmarkExtractPng(b *testing.B) {
options := Options{
Top: 100,
Left: 50,
AreaWidth: 600,
AreaHeight: 480,
}
runBenchmarkResize("test.png", options, b)
}
func BenchmarkExtractWebp(b *testing.B) {
options := Options{
Top: 100,
Left: 50,
AreaWidth: 600,
AreaHeight: 480,
}
runBenchmarkResize("test.webp", options, b)
}
func BenchmarkZoomJpeg(b *testing.B) {
options := Options{Zoom: 1}
runBenchmarkResize("test.jpg", options, b)
}
func BenchmarkZoomPng(b *testing.B) {
options := Options{Zoom: 1}
runBenchmarkResize("test.png", options, b)
}
func BenchmarkZoomWebp(b *testing.B) {
options := Options{Zoom: 1}
runBenchmarkResize("test.webp", options, b)
}
func BenchmarkWatermarkJpeg(b *testing.B) {
options := Options{
Watermark: Watermark{
Text: "Chuck Norris (c) 2315",
Opacity: 0.25,
Width: 200,
DPI: 100,
Margin: 150,
Font: "sans bold 12",
Background: Color{255, 255, 255},
},
}
runBenchmarkResize("test.jpg", options, b)
}
func BenchmarkWatermarPng(b *testing.B) {
options := Options{
Watermark: Watermark{
Text: "Chuck Norris (c) 2315",
Opacity: 0.25,
Width: 200,
DPI: 100,
Margin: 150,
Font: "sans bold 12",
Background: Color{255, 255, 255},
},
}
runBenchmarkResize("test.png", options, b)
}
func BenchmarkWatermarWebp(b *testing.B) {
options := Options{
Watermark: Watermark{
Text: "Chuck Norris (c) 2315",
Opacity: 0.25,
Width: 200,
DPI: 100,
Margin: 150,
Font: "sans bold 12",
Background: Color{255, 255, 255},
},
}
runBenchmarkResize("test.webp", options, b)
}
func BenchmarkWatermarkImageJpeg(b *testing.B) {
watermark := readFile("transparent.png")
options := Options{
WatermarkImage: WatermarkImage{
Buf: watermark,
Opacity: 0.25,
Left: 100,
Top: 100,
},
}
runBenchmarkResize("test.jpg", options, b)
}
func BenchmarkWatermarImagePng(b *testing.B) {
watermark := readFile("transparent.png")
options := Options{
WatermarkImage: WatermarkImage{
Buf: watermark,
Opacity: 0.25,
Left: 100,
Top: 100,
},
}
runBenchmarkResize("test.png", options, b)
}
func BenchmarkWatermarImageWebp(b *testing.B) {
watermark := readFile("transparent.png")
options := Options{
WatermarkImage: WatermarkImage{
Buf: watermark,
Opacity: 0.25,
Left: 100,
Top: 100,
},
}
runBenchmarkResize("test.webp", options, b)
}

172
vendor/github.com/h2non/bimg/type.go generated vendored
View File

@ -1,172 +0,0 @@
package bimg
import (
"regexp"
"sync"
"unicode/utf8"
)
const (
// UNKNOWN represents an unknow image type value.
UNKNOWN ImageType = iota
// JPEG represents the JPEG image type.
JPEG
// WEBP represents the WEBP image type.
WEBP
// PNG represents the PNG image type.
PNG
// TIFF represents the TIFF image type.
TIFF
// GIF represents the GIF image type.
GIF
// PDF represents the PDF type.
PDF
// SVG represents the SVG image type.
SVG
// MAGICK represents the libmagick compatible genetic image type.
MAGICK
)
// ImageType represents an image type value.
type ImageType int
var (
htmlCommentRegex = regexp.MustCompile("(?i)<!--([\\s\\S]*?)-->")
svgRegex = regexp.MustCompile(`(?i)^\s*(?:<\?xml[^>]*>\s*)?(?:<!doctype svg[^>]*>\s*)?<svg[^>]*>[^*]*<\/svg>\s*$`)
)
// ImageTypes stores as pairs of image types supported and its alias names.
var ImageTypes = map[ImageType]string{
JPEG: "jpeg",
PNG: "png",
WEBP: "webp",
TIFF: "tiff",
GIF: "gif",
PDF: "pdf",
SVG: "svg",
MAGICK: "magick",
}
// imageMutex is used to provide thread-safe synchronization
// for SupportedImageTypes map.
var imageMutex = &sync.RWMutex{}
// SupportedImageType represents whether a type can be loaded and/or saved by
// the current libvips compilation.
type SupportedImageType struct {
Load bool
Save bool
}
// SupportedImageTypes stores the optional image type supported
// by the current libvips compilation.
// Note: lazy evaluation as demand is required due
// to bootstrap runtime limitation with C/libvips world.
var SupportedImageTypes = map[ImageType]SupportedImageType{}
// discoverSupportedImageTypes is used to fill SupportedImageTypes map.
func discoverSupportedImageTypes() {
imageMutex.Lock()
for imageType := range ImageTypes {
SupportedImageTypes[imageType] = SupportedImageType{
Load: VipsIsTypeSupported(imageType),
Save: VipsIsTypeSupportedSave(imageType),
}
}
imageMutex.Unlock()
}
// isBinary checks if the given buffer is a binary file.
func isBinary(buf []byte) bool {
if len(buf) < 24 {
return false
}
for i := 0; i < 24; i++ {
charCode, _ := utf8.DecodeRuneInString(string(buf[i]))
if charCode == 65533 || charCode <= 8 {
return true
}
}
return false
}
// IsSVGImage returns true if the given buffer is a valid SVG image.
func IsSVGImage(buf []byte) bool {
return !isBinary(buf) && svgRegex.Match(htmlCommentRegex.ReplaceAll(buf, []byte{}))
}
// DetermineImageType determines the image type format (jpeg, png, webp or tiff)
func DetermineImageType(buf []byte) ImageType {
return vipsImageType(buf)
}
// DetermineImageTypeName determines the image type format by name (jpeg, png, webp or tiff)
func DetermineImageTypeName(buf []byte) string {
return ImageTypeName(vipsImageType(buf))
}
// IsImageTypeSupportedByVips returns true if the given image type
// is supported by current libvips compilation.
func IsImageTypeSupportedByVips(t ImageType) SupportedImageType {
imageMutex.RLock()
// Discover supported image types and cache the result
itShouldDiscover := len(SupportedImageTypes) == 0
if itShouldDiscover {
imageMutex.RUnlock()
discoverSupportedImageTypes()
}
// Check if image type is actually supported
supported, ok := SupportedImageTypes[t]
if !itShouldDiscover {
imageMutex.RUnlock()
}
if ok {
return supported
}
return SupportedImageType{Load: false, Save: false}
}
// IsTypeSupported checks if a given image type is supported
func IsTypeSupported(t ImageType) bool {
_, ok := ImageTypes[t]
return ok && IsImageTypeSupportedByVips(t).Load
}
// IsTypeNameSupported checks if a given image type name is supported
func IsTypeNameSupported(t string) bool {
for imageType, name := range ImageTypes {
if name == t {
return IsImageTypeSupportedByVips(imageType).Load
}
}
return false
}
// IsTypeSupportedSave checks if a given image type is support for saving
func IsTypeSupportedSave(t ImageType) bool {
_, ok := ImageTypes[t]
return ok && IsImageTypeSupportedByVips(t).Save
}
// IsTypeNameSupportedSave checks if a given image type name is supported for
// saving
func IsTypeNameSupportedSave(t string) bool {
for imageType, name := range ImageTypes {
if name == t {
return IsImageTypeSupportedByVips(imageType).Save
}
}
return false
}
// ImageTypeName is used to get the human friendly name of an image format.
func ImageTypeName(t ImageType) string {
imageType := ImageTypes[t]
if imageType == "" {
return "unknown"
}
return imageType
}

View File

@ -1,130 +0,0 @@
package bimg
import (
"io/ioutil"
"os"
"path"
"testing"
)
func TestDeterminateImageType(t *testing.T) {
files := []struct {
name string
expected ImageType
}{
{"test.jpg", JPEG},
{"test.png", PNG},
{"test.webp", WEBP},
{"test.gif", GIF},
{"test.pdf", PDF},
{"test.svg", SVG},
{"test.jp2", MAGICK},
}
for _, file := range files {
img, _ := os.Open(path.Join("fixtures", file.name))
buf, _ := ioutil.ReadAll(img)
defer img.Close()
if VipsIsTypeSupported(file.expected) {
if DetermineImageType(buf) != file.expected {
t.Fatalf("Image type is not valid: %s != %s", file.name, ImageTypes[file.expected])
}
}
}
}
func TestDeterminateImageTypeName(t *testing.T) {
files := []struct {
name string
expected string
}{
{"test.jpg", "jpeg"},
{"test.png", "png"},
{"test.webp", "webp"},
{"test.gif", "gif"},
{"test.pdf", "pdf"},
{"test.svg", "svg"},
{"test.jp2", "magick"},
}
for _, file := range files {
img, _ := os.Open(path.Join("fixtures", file.name))
buf, _ := ioutil.ReadAll(img)
defer img.Close()
if DetermineImageTypeName(buf) != file.expected {
t.Fatalf("Image type is not valid: %s != %s", file.name, file.expected)
}
}
}
func TestIsTypeSupported(t *testing.T) {
types := []struct {
name ImageType
}{
{JPEG}, {PNG}, {WEBP}, {GIF}, {PDF},
}
for _, n := range types {
if IsTypeSupported(n.name) == false {
t.Fatalf("Image type %#v is not valid", ImageTypes[n.name])
}
}
}
func TestIsTypeNameSupported(t *testing.T) {
types := []struct {
name string
expected bool
}{
{"jpeg", true},
{"png", true},
{"webp", true},
{"gif", true},
{"pdf", true},
}
for _, n := range types {
if IsTypeNameSupported(n.name) != n.expected {
t.Fatalf("Image type %#v is not valid", n.name)
}
}
}
func TestIsTypeSupportedSave(t *testing.T) {
types := []struct {
name ImageType
}{
{JPEG}, {PNG}, {WEBP},
}
if VipsVersion >= "8.5.0" {
types = append(types, struct{ name ImageType }{TIFF})
}
for _, n := range types {
if IsTypeSupportedSave(n.name) == false {
t.Fatalf("Image type %#v is not valid", ImageTypes[n.name])
}
}
}
func TestIsTypeNameSupportedSave(t *testing.T) {
types := []struct {
name string
expected bool
}{
{"jpeg", true},
{"png", true},
{"webp", true},
{"gif", false},
{"pdf", false},
{"tiff", VipsVersion >= "8.5.0"},
}
for _, n := range types {
if IsTypeNameSupportedSave(n.name) != n.expected {
t.Fatalf("Image type %#v is not valid", n.name)
}
}
}

View File

@ -1,4 +0,0 @@
package bimg
// Version represents the current package semantic version.
const Version = "1.0.14"

692
vendor/github.com/h2non/bimg/vips.go generated vendored
View File

@ -1,692 +0,0 @@
package bimg
/*
#cgo pkg-config: vips
#include "vips.h"
*/
import "C"
import (
"errors"
"fmt"
"math"
"os"
"runtime"
"strings"
"sync"
"unsafe"
d "github.com/tj/go-debug"
)
// debug is internally used to
var debug = d.Debug("bimg")
// VipsVersion exposes the current libvips semantic version
const VipsVersion = string(C.VIPS_VERSION)
// VipsMajorVersion exposes the current libvips major version number
const VipsMajorVersion = int(C.VIPS_MAJOR_VERSION)
// VipsMinorVersion exposes the current libvips minor version number
const VipsMinorVersion = int(C.VIPS_MINOR_VERSION)
const (
maxCacheMem = 100 * 1024 * 1024
maxCacheSize = 500
)
var (
m sync.Mutex
initialized bool
)
// VipsMemoryInfo represents the memory stats provided by libvips.
type VipsMemoryInfo struct {
Memory int64
MemoryHighwater int64
Allocations int64
}
// vipsSaveOptions represents the internal option used to talk with libvips.
type vipsSaveOptions struct {
Quality int
Compression int
Type ImageType
Interlace bool
NoProfile bool
StripMetadata bool
OutputICC string // Absolute path to the output ICC profile
Interpretation Interpretation
}
type vipsWatermarkOptions struct {
Width C.int
DPI C.int
Margin C.int
NoReplicate C.int
Opacity C.float
Background [3]C.double
}
type vipsWatermarkImageOptions struct {
Left C.int
Top C.int
Opacity C.float
}
type vipsWatermarkTextOptions struct {
Text *C.char
Font *C.char
}
func init() {
Initialize()
}
// Initialize is used to explicitly start libvips in thread-safe way.
// Only call this function if you have previously turned off libvips.
func Initialize() {
if C.VIPS_MAJOR_VERSION <= 7 && C.VIPS_MINOR_VERSION < 40 {
panic("unsupported libvips version!")
}
m.Lock()
runtime.LockOSThread()
defer m.Unlock()
defer runtime.UnlockOSThread()
err := C.vips_init(C.CString("bimg"))
if err != 0 {
panic("unable to start vips!")
}
// Set libvips cache params
C.vips_cache_set_max_mem(maxCacheMem)
C.vips_cache_set_max(maxCacheSize)
// Define a custom thread concurrency limit in libvips (this may generate thread-unsafe issues)
// See: https://github.com/jcupitt/libvips/issues/261#issuecomment-92850414
if os.Getenv("VIPS_CONCURRENCY") == "" {
C.vips_concurrency_set(1)
}
// Enable libvips cache tracing
if os.Getenv("VIPS_TRACE") != "" {
C.vips_enable_cache_set_trace()
}
initialized = true
}
// Shutdown is used to shutdown libvips in a thread-safe way.
// You can call this to drop caches as well.
// If libvips was already initialized, the function is no-op
func Shutdown() {
m.Lock()
defer m.Unlock()
if initialized {
C.vips_shutdown()
initialized = false
}
}
// VipsCacheSetMaxMem Sets the maximum amount of tracked memory allowed before the vips operation cache
// begins to drop entries.
func VipsCacheSetMaxMem(maxCacheMem int) {
C.vips_cache_set_max_mem(C.size_t(maxCacheMem))
}
// VipsCacheSetMax sets the maximum number of operations to keep in the vips operation cache.
func VipsCacheSetMax(maxCacheSize int) {
C.vips_cache_set_max(C.int(maxCacheSize))
}
// VipsCacheDropAll drops the vips operation cache, freeing the allocated memory.
func VipsCacheDropAll() {
C.vips_cache_drop_all()
}
// VipsDebugInfo outputs to stdout libvips collected data. Useful for debugging.
func VipsDebugInfo() {
C.im__print_all()
}
// VipsMemory gets memory info stats from libvips (cache size, memory allocs...)
func VipsMemory() VipsMemoryInfo {
return VipsMemoryInfo{
Memory: int64(C.vips_tracked_get_mem()),
MemoryHighwater: int64(C.vips_tracked_get_mem_highwater()),
Allocations: int64(C.vips_tracked_get_allocs()),
}
}
// VipsIsTypeSupported returns true if the given image type
// is supported by the current libvips compilation.
func VipsIsTypeSupported(t ImageType) bool {
if t == JPEG {
return int(C.vips_type_find_bridge(C.JPEG)) != 0
}
if t == WEBP {
return int(C.vips_type_find_bridge(C.WEBP)) != 0
}
if t == PNG {
return int(C.vips_type_find_bridge(C.PNG)) != 0
}
if t == GIF {
return int(C.vips_type_find_bridge(C.GIF)) != 0
}
if t == PDF {
return int(C.vips_type_find_bridge(C.PDF)) != 0
}
if t == SVG {
return int(C.vips_type_find_bridge(C.SVG)) != 0
}
if t == TIFF {
return int(C.vips_type_find_bridge(C.TIFF)) != 0
}
if t == MAGICK {
return int(C.vips_type_find_bridge(C.MAGICK)) != 0
}
return false
}
// VipsIsTypeSupportedSave returns true if the given image type
// is supported by the current libvips compilation for the
// save operation.
func VipsIsTypeSupportedSave(t ImageType) bool {
if t == JPEG {
return int(C.vips_type_find_save_bridge(C.JPEG)) != 0
}
if t == WEBP {
return int(C.vips_type_find_save_bridge(C.WEBP)) != 0
}
if t == PNG {
return int(C.vips_type_find_save_bridge(C.PNG)) != 0
}
if t == TIFF {
return int(C.vips_type_find_save_bridge(C.TIFF)) != 0
}
return false
}
func vipsExifOrientation(image *C.VipsImage) int {
return int(C.vips_exif_orientation(image))
}
func vipsHasAlpha(image *C.VipsImage) bool {
return int(C.has_alpha_channel(image)) > 0
}
func vipsHasProfile(image *C.VipsImage) bool {
return int(C.has_profile_embed(image)) > 0
}
func vipsWindowSize(name string) float64 {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
return float64(C.interpolator_window_size(cname))
}
func vipsSpace(image *C.VipsImage) string {
return C.GoString(C.vips_enum_nick_bridge(image))
}
func vipsRotate(image *C.VipsImage, angle Angle) (*C.VipsImage, error) {
var out *C.VipsImage
defer C.g_object_unref(C.gpointer(image))
err := C.vips_rotate(image, &out, C.int(angle))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func vipsFlip(image *C.VipsImage, direction Direction) (*C.VipsImage, error) {
var out *C.VipsImage
defer C.g_object_unref(C.gpointer(image))
err := C.vips_flip_bridge(image, &out, C.int(direction))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func vipsZoom(image *C.VipsImage, zoom int) (*C.VipsImage, error) {
var out *C.VipsImage
defer C.g_object_unref(C.gpointer(image))
err := C.vips_zoom_bridge(image, &out, C.int(zoom), C.int(zoom))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func vipsWatermark(image *C.VipsImage, w Watermark) (*C.VipsImage, error) {
var out *C.VipsImage
// Defaults
noReplicate := 0
if w.NoReplicate {
noReplicate = 1
}
text := C.CString(w.Text)
font := C.CString(w.Font)
background := [3]C.double{C.double(w.Background.R), C.double(w.Background.G), C.double(w.Background.B)}
textOpts := vipsWatermarkTextOptions{text, font}
opts := vipsWatermarkOptions{C.int(w.Width), C.int(w.DPI), C.int(w.Margin), C.int(noReplicate), C.float(w.Opacity), background}
defer C.free(unsafe.Pointer(text))
defer C.free(unsafe.Pointer(font))
err := C.vips_watermark(image, &out, (*C.WatermarkTextOptions)(unsafe.Pointer(&textOpts)), (*C.WatermarkOptions)(unsafe.Pointer(&opts)))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func vipsRead(buf []byte) (*C.VipsImage, ImageType, error) {
var image *C.VipsImage
imageType := vipsImageType(buf)
if imageType == UNKNOWN {
return nil, UNKNOWN, errors.New("Unsupported image format")
}
length := C.size_t(len(buf))
imageBuf := unsafe.Pointer(&buf[0])
err := C.vips_init_image(imageBuf, length, C.int(imageType), &image)
if err != 0 {
return nil, UNKNOWN, catchVipsError()
}
return image, imageType, nil
}
func vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) {
image, _, err := vipsRead(buf)
if err != nil {
return false, err
}
C.g_object_unref(C.gpointer(image))
return vipsColourspaceIsSupported(image), nil
}
func vipsColourspaceIsSupported(image *C.VipsImage) bool {
return int(C.vips_colourspace_issupported_bridge(image)) == 1
}
func vipsInterpretationBuffer(buf []byte) (Interpretation, error) {
image, _, err := vipsRead(buf)
if err != nil {
return InterpretationError, err
}
C.g_object_unref(C.gpointer(image))
return vipsInterpretation(image), nil
}
func vipsInterpretation(image *C.VipsImage) Interpretation {
return Interpretation(C.vips_image_guess_interpretation_bridge(image))
}
func vipsFlattenBackground(image *C.VipsImage, background Color) (*C.VipsImage, error) {
var outImage *C.VipsImage
backgroundC := [3]C.double{
C.double(background.R),
C.double(background.G),
C.double(background.B),
}
if vipsHasAlpha(image) {
err := C.vips_flatten_background_brigde(image, &outImage,
backgroundC[0], backgroundC[1], backgroundC[2])
if int(err) != 0 {
return nil, catchVipsError()
}
C.g_object_unref(C.gpointer(image))
image = outImage
}
return image, nil
}
func vipsPreSave(image *C.VipsImage, o *vipsSaveOptions) (*C.VipsImage, error) {
var outImage *C.VipsImage
// Remove ICC profile metadata
if o.NoProfile {
C.remove_profile(image)
}
// Use a default interpretation and cast it to C type
if o.Interpretation == 0 {
o.Interpretation = InterpretationSRGB
}
interpretation := C.VipsInterpretation(o.Interpretation)
// Apply the proper colour space
if vipsColourspaceIsSupported(image) {
err := C.vips_colourspace_bridge(image, &outImage, interpretation)
if int(err) != 0 {
return nil, catchVipsError()
}
image = outImage
}
if o.OutputICC != "" && vipsHasProfile(image) {
debug("Embedded ICC profile found, trying to convert to %s", o.OutputICC)
outputIccPath := C.CString(o.OutputICC)
defer C.free(unsafe.Pointer(outputIccPath))
err := C.vips_icc_transform_bridge(image, &outImage, outputIccPath)
if int(err) != 0 {
return nil, catchVipsError()
}
image = outImage
}
return image, nil
}
func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) {
defer C.g_object_unref(C.gpointer(image))
tmpImage, err := vipsPreSave(image, &o)
if err != nil {
return nil, err
}
// When an image has an unsupported color space, vipsPreSave
// returns the pointer of the image passed to it unmodified.
// When this occurs, we must take care to not dereference the
// original image a second time; we may otherwise erroneously
// free the object twice.
if tmpImage != image {
defer C.g_object_unref(C.gpointer(tmpImage))
}
length := C.size_t(0)
saveErr := C.int(0)
interlace := C.int(boolToInt(o.Interlace))
quality := C.int(o.Quality)
strip := C.int(boolToInt(o.StripMetadata))
if o.Type != 0 && !IsTypeSupportedSave(o.Type) {
return nil, fmt.Errorf("VIPS cannot save to %#v", ImageTypes[o.Type])
}
var ptr unsafe.Pointer
switch o.Type {
case WEBP:
saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, strip, quality)
case PNG:
saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, strip, C.int(o.Compression), quality, interlace)
case TIFF:
saveErr = C.vips_tiffsave_bridge(tmpImage, &ptr, &length)
default:
saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, strip, quality, interlace)
}
if int(saveErr) != 0 {
return nil, catchVipsError()
}
buf := C.GoBytes(ptr, C.int(length))
// Clean up
C.g_free(C.gpointer(ptr))
C.vips_error_clear()
return buf, nil
}
func getImageBuffer(image *C.VipsImage) ([]byte, error) {
var ptr unsafe.Pointer
length := C.size_t(0)
interlace := C.int(0)
quality := C.int(100)
err := C.int(0)
err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, quality, interlace)
if int(err) != 0 {
return nil, catchVipsError()
}
defer C.g_free(C.gpointer(ptr))
defer C.vips_error_clear()
return C.GoBytes(ptr, C.int(length)), nil
}
func vipsExtract(image *C.VipsImage, left, top, width, height int) (*C.VipsImage, error) {
var buf *C.VipsImage
defer C.g_object_unref(C.gpointer(image))
if width > MaxSize || height > MaxSize {
return nil, errors.New("Maximum image size exceeded")
}
top, left = max(top), max(left)
err := C.vips_extract_area_bridge(image, &buf, C.int(left), C.int(top), C.int(width), C.int(height))
if err != 0 {
return nil, catchVipsError()
}
return buf, nil
}
func vipsSmartCrop(image *C.VipsImage, width, height int) (*C.VipsImage, error) {
var buf *C.VipsImage
defer C.g_object_unref(C.gpointer(image))
if width > MaxSize || height > MaxSize {
return nil, errors.New("Maximum image size exceeded")
}
err := C.vips_smartcrop_bridge(image, &buf, C.int(width), C.int(height))
if err != 0 {
return nil, catchVipsError()
}
return buf, nil
}
func vipsTrim(image *C.VipsImage) (int, int, int, int, error) {
defer C.g_object_unref(C.gpointer(image))
top := C.int(0)
left := C.int(0)
width := C.int(0)
height := C.int(0)
err := C.vips_find_trim_bridge(image, &top, &left, &width, &height)
if err != 0 {
return 0, 0, 0, 0, catchVipsError()
}
return int(top), int(left), int(width), int(height), nil
}
func vipsShrinkJpeg(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, error) {
var image *C.VipsImage
var ptr = unsafe.Pointer(&buf[0])
defer C.g_object_unref(C.gpointer(input))
err := C.vips_jpegload_buffer_shrink(ptr, C.size_t(len(buf)), &image, C.int(shrink))
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsShrink(input *C.VipsImage, shrink int) (*C.VipsImage, error) {
var image *C.VipsImage
defer C.g_object_unref(C.gpointer(input))
err := C.vips_shrink_bridge(input, &image, C.double(float64(shrink)), C.double(float64(shrink)))
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsReduce(input *C.VipsImage, xshrink float64, yshrink float64) (*C.VipsImage, error) {
var image *C.VipsImage
defer C.g_object_unref(C.gpointer(input))
err := C.vips_reduce_bridge(input, &image, C.double(xshrink), C.double(yshrink))
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsEmbed(input *C.VipsImage, left, top, width, height int, extend Extend, background Color) (*C.VipsImage, error) {
var image *C.VipsImage
// Max extend value, see: http://www.vips.ecs.soton.ac.uk/supported/8.4/doc/html/libvips/libvips-conversion.html#VipsExtend
if extend > 5 {
extend = ExtendBackground
}
defer C.g_object_unref(C.gpointer(input))
err := C.vips_embed_bridge(input, &image, C.int(left), C.int(top), C.int(width),
C.int(height), C.int(extend), C.double(background.R), C.double(background.G), C.double(background.B))
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsAffine(input *C.VipsImage, residualx, residualy float64, i Interpolator) (*C.VipsImage, error) {
var image *C.VipsImage
cstring := C.CString(i.String())
interpolator := C.vips_interpolate_new(cstring)
defer C.free(unsafe.Pointer(cstring))
defer C.g_object_unref(C.gpointer(input))
defer C.g_object_unref(C.gpointer(interpolator))
err := C.vips_affine_interpolator(input, &image, C.double(residualx), 0, 0, C.double(residualy), interpolator)
if err != 0 {
return nil, catchVipsError()
}
return image, nil
}
func vipsImageType(buf []byte) ImageType {
if len(buf) < 12 {
return UNKNOWN
}
if buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF {
return JPEG
}
if IsTypeSupported(GIF) && buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 {
return GIF
}
if buf[0] == 0x89 && buf[1] == 0x50 && buf[2] == 0x4E && buf[3] == 0x47 {
return PNG
}
if IsTypeSupported(TIFF) &&
((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) ||
(buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) {
return TIFF
}
if IsTypeSupported(PDF) && buf[0] == 0x25 && buf[1] == 0x50 && buf[2] == 0x44 && buf[3] == 0x46 {
return PDF
}
if IsTypeSupported(WEBP) && buf[8] == 0x57 && buf[9] == 0x45 && buf[10] == 0x42 && buf[11] == 0x50 {
return WEBP
}
if IsTypeSupported(SVG) && IsSVGImage(buf) {
return SVG
}
if IsTypeSupported(MAGICK) && strings.HasSuffix(readImageType(buf), "MagickBuffer") {
return MAGICK
}
return UNKNOWN
}
func readImageType(buf []byte) string {
length := C.size_t(len(buf))
imageBuf := unsafe.Pointer(&buf[0])
load := C.vips_foreign_find_load_buffer(imageBuf, length)
return C.GoString(load)
}
func catchVipsError() error {
s := C.GoString(C.vips_error_buffer())
C.vips_error_clear()
C.vips_thread_shutdown()
return errors.New(s)
}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
func vipsGaussianBlur(image *C.VipsImage, o GaussianBlur) (*C.VipsImage, error) {
var out *C.VipsImage
defer C.g_object_unref(C.gpointer(image))
err := C.vips_gaussblur_bridge(image, &out, C.double(o.Sigma), C.double(o.MinAmpl))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func vipsSharpen(image *C.VipsImage, o Sharpen) (*C.VipsImage, error) {
var out *C.VipsImage
defer C.g_object_unref(C.gpointer(image))
err := C.vips_sharpen_bridge(image, &out, C.int(o.Radius), C.double(o.X1), C.double(o.Y2), C.double(o.Y3), C.double(o.M1), C.double(o.M2))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}
func max(x int) int {
return int(math.Max(float64(x), 0))
}
func vipsDrawWatermark(image *C.VipsImage, o WatermarkImage) (*C.VipsImage, error) {
var out *C.VipsImage
watermark, _, e := vipsRead(o.Buf)
if e != nil {
return nil, e
}
opts := vipsWatermarkImageOptions{C.int(o.Left), C.int(o.Top), C.float(o.Opacity)}
err := C.vips_watermark_image(image, watermark, &out, (*C.WatermarkImageOptions)(unsafe.Pointer(&opts)))
if err != 0 {
return nil, catchVipsError()
}
return out, nil
}

549
vendor/github.com/h2non/bimg/vips.h generated vendored
View File

@ -1,549 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <vips/vips.h>
#include <vips/foreign.h>
#include <vips/vips7compat.h>
/**
* Starting libvips 7.41, VIPS_ANGLE_x has been renamed to VIPS_ANGLE_Dx
* "to help python". So we provide the macro to correctly build for versions
* before 7.41.x.
* https://github.com/jcupitt/libvips/blob/master/ChangeLog#L128
*/
#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)
#define VIPS_ANGLE_D0 VIPS_ANGLE_0
#define VIPS_ANGLE_D90 VIPS_ANGLE_90
#define VIPS_ANGLE_D180 VIPS_ANGLE_180
#define VIPS_ANGLE_D270 VIPS_ANGLE_270
#endif
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
#define INT_TO_GBOOLEAN(bool) (bool > 0 ? TRUE : FALSE)
enum types {
UNKNOWN = 0,
JPEG,
WEBP,
PNG,
TIFF,
GIF,
PDF,
SVG,
MAGICK
};
typedef struct {
const char *Text;
const char *Font;
} WatermarkTextOptions;
typedef struct {
int Width;
int DPI;
int Margin;
int NoReplicate;
float Opacity;
double Background[3];
} WatermarkOptions;
typedef struct {
int Left;
int Top;
float Opacity;
} WatermarkImageOptions;
static unsigned long
has_profile_embed(VipsImage *image) {
return vips_image_get_typeof(image, VIPS_META_ICC_NAME);
}
static void
remove_profile(VipsImage *image) {
vips_image_remove(image, VIPS_META_ICC_NAME);
}
static int
has_alpha_channel(VipsImage *image) {
return (
(image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) ||
(image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) ||
(image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK)
) ? 1 : 0;
}
/**
* This method is here to handle the weird initialization of the vips lib.
* libvips use a macro VIPS_INIT() that call vips__init() in version < 7.41,
* or calls vips_init() in version >= 7.41.
*
* Anyway, it's not possible to build bimg on Debian Jessie with libvips 7.40.x,
* as vips_init() is a macro to VIPS_INIT(), which is also a macro, hence, cgo
* is unable to determine the return type of vips_init(), making the build impossible.
* In order to correctly build bimg, for version < 7.41, we should undef vips_init and
* creates a vips_init() method that calls VIPS_INIT().
*/
#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)
#undef vips_init
int
vips_init(const char *argv0)
{
return VIPS_INIT(argv0);
}
#endif
void
vips_enable_cache_set_trace() {
vips_cache_set_trace(TRUE);
}
int
vips_affine_interpolator(VipsImage *in, VipsImage **out, double a, double b, double c, double d, VipsInterpolate *interpolator) {
return vips_affine(in, out, a, b, c, d, "interpolate", interpolator, NULL);
}
int
vips_jpegload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink) {
return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL);
}
int
vips_flip_bridge(VipsImage *in, VipsImage **out, int direction) {
return vips_flip(in, out, direction, NULL);
}
int
vips_shrink_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrink) {
return vips_shrink(in, out, xshrink, yshrink, NULL);
}
int
vips_reduce_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrink) {
return vips_reduce(in, out, xshrink, yshrink, NULL);
}
int
vips_type_find_bridge(int t) {
if (t == GIF) {
return vips_type_find("VipsOperation", "gifload");
}
if (t == PDF) {
return vips_type_find("VipsOperation", "pdfload");
}
if (t == TIFF) {
return vips_type_find("VipsOperation", "tiffload");
}
if (t == SVG) {
return vips_type_find("VipsOperation", "svgload");
}
if (t == WEBP) {
return vips_type_find("VipsOperation", "webpload");
}
if (t == PNG) {
return vips_type_find("VipsOperation", "pngload");
}
if (t == JPEG) {
return vips_type_find("VipsOperation", "jpegload");
}
if (t == MAGICK) {
return vips_type_find("VipsOperation", "magickload");
}
return 0;
}
int
vips_type_find_save_bridge(int t) {
if (t == TIFF) {
return vips_type_find("VipsOperation", "tiffsave_buffer");
}
if (t == WEBP) {
return vips_type_find("VipsOperation", "webpsave_buffer");
}
if (t == PNG) {
return vips_type_find("VipsOperation", "pngsave_buffer");
}
if (t == JPEG) {
return vips_type_find("VipsOperation", "jpegsave_buffer");
}
return 0;
}
int
vips_rotate(VipsImage *in, VipsImage **out, int angle) {
int rotate = VIPS_ANGLE_D0;
angle %= 360;
if (angle == 45) {
rotate = VIPS_ANGLE45_D45;
} else if (angle == 90) {
rotate = VIPS_ANGLE_D90;
} else if (angle == 135) {
rotate = VIPS_ANGLE45_D135;
} else if (angle == 180) {
rotate = VIPS_ANGLE_D180;
} else if (angle == 225) {
rotate = VIPS_ANGLE45_D225;
} else if (angle == 270) {
rotate = VIPS_ANGLE_D270;
} else if (angle == 315) {
rotate = VIPS_ANGLE45_D315;
} else {
angle = 0;
}
if (angle > 0 && angle % 90 != 0) {
return vips_rot45(in, out, "angle", rotate, NULL);
} else {
return vips_rot(in, out, rotate, NULL);
}
}
int
vips_exif_orientation(VipsImage *image) {
int orientation = 0;
const char *exif;
if (
vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 &&
!vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif)
) {
orientation = atoi(&exif[0]);
}
return orientation;
}
int
interpolator_window_size(char const *name) {
VipsInterpolate *interpolator = vips_interpolate_new(name);
int window_size = vips_interpolate_get_window_size(interpolator);
g_object_unref(interpolator);
return window_size;
}
const char *
vips_enum_nick_bridge(VipsImage *image) {
return vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
}
int
vips_zoom_bridge(VipsImage *in, VipsImage **out, int xfac, int yfac) {
return vips_zoom(in, out, xfac, yfac, NULL);
}
int
vips_embed_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend, double r, double g, double b) {
if (extend == VIPS_EXTEND_BACKGROUND) {
double background[3] = {r, g, b};
VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3);
return vips_embed(in, out, left, top, width, height, "extend", extend, "background", vipsBackground, NULL);
}
return vips_embed(in, out, left, top, width, height, "extend", extend, NULL);
}
int
vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int width, int height) {
return vips_extract_area(in, out, left, top, width, height, NULL);
}
int
vips_colourspace_issupported_bridge(VipsImage *in) {
return vips_colourspace_issupported(in) ? 1 : 0;
}
VipsInterpretation
vips_image_guess_interpretation_bridge(VipsImage *in) {
return vips_image_guess_interpretation(in);
}
int
vips_colourspace_bridge(VipsImage *in, VipsImage **out, VipsInterpretation space) {
return vips_colourspace(in, out, space, NULL);
}
int
vips_icc_transform_bridge (VipsImage *in, VipsImage **out, const char *output_icc_profile) {
// `output_icc_profile` represents the absolute path to the output ICC profile file
return vips_icc_transform(in, out, output_icc_profile, "embedded", TRUE, NULL);
}
int
vips_jpegsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace) {
return vips_jpegsave_buffer(in, buf, len,
"strip", INT_TO_GBOOLEAN(strip),
"Q", quality,
"optimize_coding", TRUE,
"interlace", INT_TO_GBOOLEAN(interlace),
NULL
);
}
int
vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compression, int quality, int interlace) {
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
return vips_pngsave_buffer(in, buf, len,
"strip", INT_TO_GBOOLEAN(strip),
"compression", compression,
"interlace", INT_TO_GBOOLEAN(interlace),
"filter", VIPS_FOREIGN_PNG_FILTER_NONE,
NULL
);
#else
return vips_pngsave_buffer(in, buf, len,
"strip", INT_TO_GBOOLEAN(strip),
"compression", compression,
"interlace", INT_TO_GBOOLEAN(interlace),
NULL
);
#endif
}
int
vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality) {
return vips_webpsave_buffer(in, buf, len,
"strip", INT_TO_GBOOLEAN(strip),
"Q", quality,
NULL
);
}
int
vips_tiffsave_bridge(VipsImage *in, void **buf, size_t *len) {
#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 5)
return vips_tiffsave_buffer(in, buf, len, NULL);
#else
return 0;
#endif
}
int
vips_is_16bit (VipsInterpretation interpretation) {
return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16;
}
int
vips_flatten_background_brigde(VipsImage *in, VipsImage **out, double r, double g, double b) {
if (vips_is_16bit(in->Type)) {
r = 65535 * r / 255;
g = 65535 * g / 255;
b = 65535 * b / 255;
}
double background[3] = {r, g, b};
VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3);
return vips_flatten(in, out,
"background", vipsBackground,
"max_alpha", vips_is_16bit(in->Type) ? 65535.0 : 255.0,
NULL
);
}
int
vips_init_image (void *buf, size_t len, int imageType, VipsImage **out) {
int code = 1;
if (imageType == JPEG) {
code = vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
} else if (imageType == PNG) {
code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
} else if (imageType == WEBP) {
code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
} else if (imageType == TIFF) {
code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
#if (VIPS_MAJOR_VERSION >= 8)
#if (VIPS_MINOR_VERSION >= 3)
} else if (imageType == GIF) {
code = vips_gifload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
} else if (imageType == PDF) {
code = vips_pdfload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
} else if (imageType == SVG) {
code = vips_svgload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
#endif
} else if (imageType == MAGICK) {
code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
#endif
}
return code;
}
int
vips_watermark_replicate (VipsImage *orig, VipsImage *in, VipsImage **out) {
VipsImage *cache = vips_image_new();
if (
vips_replicate(in, &cache,
1 + orig->Xsize / in->Xsize,
1 + orig->Ysize / in->Ysize, NULL) ||
vips_crop(cache, out, 0, 0, orig->Xsize, orig->Ysize, NULL)
) {
g_object_unref(cache);
return 1;
}
g_object_unref(cache);
return 0;
}
int
vips_watermark(VipsImage *in, VipsImage **out, WatermarkTextOptions *to, WatermarkOptions *o) {
double ones[3] = { 1, 1, 1 };
VipsImage *base = vips_image_new();
VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10);
t[0] = in;
// Make the mask.
if (
vips_text(&t[1], to->Text,
"width", o->Width,
"dpi", o->DPI,
"font", to->Font,
NULL) ||
vips_linear1(t[1], &t[2], o->Opacity, 0.0, NULL) ||
vips_cast(t[2], &t[3], VIPS_FORMAT_UCHAR, NULL) ||
vips_embed(t[3], &t[4], 100, 100, t[3]->Xsize + o->Margin, t[3]->Ysize + o->Margin, NULL)
) {
g_object_unref(base);
return 1;
}
// Replicate if necessary
if (o->NoReplicate != 1) {
VipsImage *cache = vips_image_new();
if (vips_watermark_replicate(t[0], t[4], &cache)) {
g_object_unref(cache);
g_object_unref(base);
return 1;
}
g_object_unref(t[4]);
t[4] = cache;
}
// Make the constant image to paint the text with.
if (
vips_black(&t[5], 1, 1, NULL) ||
vips_linear(t[5], &t[6], ones, o->Background, 3, NULL) ||
vips_cast(t[6], &t[7], VIPS_FORMAT_UCHAR, NULL) ||
vips_copy(t[7], &t[8], "interpretation", t[0]->Type, NULL) ||
vips_embed(t[8], &t[9], 0, 0, t[0]->Xsize, t[0]->Ysize, "extend", VIPS_EXTEND_COPY, NULL)
) {
g_object_unref(base);
return 1;
}
// Blend the mask and text and write to output.
if (vips_ifthenelse(t[4], t[9], t[0], out, "blend", TRUE, NULL)) {
g_object_unref(base);
return 1;
}
g_object_unref(base);
return 0;
}
int
vips_gaussblur_bridge(VipsImage *in, VipsImage **out, double sigma, double min_ampl) {
#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)
return vips_gaussblur(in, out, (int) sigma, NULL);
#else
return vips_gaussblur(in, out, sigma, NULL, "min_ampl", min_ampl, NULL);
#endif
}
int
vips_sharpen_bridge(VipsImage *in, VipsImage **out, int radius, double x1, double y2, double y3, double m1, double m2) {
#if (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 41)
return vips_sharpen(in, out, radius, x1, y2, y3, m1, m2, NULL);
#else
return vips_sharpen(in, out, "radius", radius, "x1", x1, "y2", y2, "y3", y3, "m1", m1, "m2", m2, NULL);
#endif
}
int
vips_add_band(VipsImage *in, VipsImage **out, double c) {
#if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 2))
return vips_bandjoin_const1(in, out, c, NULL);
#else
VipsImage *base = vips_image_new();
if (
vips_black(&base, in->Xsize, in->Ysize, NULL) ||
vips_linear1(base, &base, 1, c, NULL)) {
g_object_unref(base);
return 1;
}
g_object_unref(base);
return vips_bandjoin2(in, base, out, c, NULL);
#endif
}
int
vips_watermark_image(VipsImage *in, VipsImage *sub, VipsImage **out, WatermarkImageOptions *o) {
VipsImage *base = vips_image_new();
VipsImage **t = (VipsImage **) vips_object_local_array(VIPS_OBJECT(base), 10);
// add in and sub for unreffing and later use
t[0] = in;
t[1] = sub;
if (has_alpha_channel(in) == 0) {
vips_add_band(in, &t[0], 255.0);
// in is no longer in the array and won't be unreffed, so add it at the end
t[8] = in;
}
if (has_alpha_channel(sub) == 0) {
vips_add_band(sub, &t[1], 255.0);
// sub is no longer in the array and won't be unreffed, so add it at the end
t[9] = sub;
}
// Place watermark image in the right place and size it to the size of the
// image that should be watermarked
if (
vips_embed(t[1], &t[2], o->Left, o->Top, t[0]->Xsize, t[0]->Ysize, NULL)) {
g_object_unref(base);
return 1;
}
// Create a mask image based on the alpha band from the watermark image
// and place it in the right position
if (
vips_extract_band(t[1], &t[3], t[1]->Bands - 1, "n", 1, NULL) ||
vips_linear1(t[3], &t[4], o->Opacity, 0.0, NULL) ||
vips_cast(t[4], &t[5], VIPS_FORMAT_UCHAR, NULL) ||
vips_copy(t[5], &t[6], "interpretation", t[0]->Type, NULL) ||
vips_embed(t[6], &t[7], o->Left, o->Top, t[0]->Xsize, t[0]->Ysize, NULL)) {
g_object_unref(base);
return 1;
}
// Blend the mask and watermark image and write to output.
if (vips_ifthenelse(t[7], t[2], t[0], out, "blend", TRUE, NULL)) {
g_object_unref(base);
return 1;
}
g_object_unref(base);
return 0;
}
int
vips_smartcrop_bridge(VipsImage *in, VipsImage **out, int width, int height) {
#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 5)
return vips_smartcrop(in, out, width, height, NULL);
#else
return 0;
#endif
}
int vips_find_trim_bridge(VipsImage *in, int *top, int *left, int *width, int *height) {
#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 6)
return vips_find_trim(in, top, left, width, height, NULL);
#else
return 0;
#endif
}

View File

@ -1,170 +0,0 @@
package bimg
import (
"io/ioutil"
"os"
"path"
"testing"
)
func TestVipsRead(t *testing.T) {
files := []struct {
name string
expected ImageType
}{
{"test.jpg", JPEG},
{"test.png", PNG},
{"test.webp", WEBP},
}
for _, file := range files {
image, imageType, _ := vipsRead(readImage(file.name))
if image == nil {
t.Fatal("Empty image")
}
if imageType != file.expected {
t.Fatal("Invalid image type")
}
}
}
func TestVipsSave(t *testing.T) {
types := [...]ImageType{JPEG, PNG, WEBP}
for _, typ := range types {
image, _, _ := vipsRead(readImage("test.jpg"))
options := vipsSaveOptions{Quality: 95, Type: typ, StripMetadata: true}
buf, err := vipsSave(image, options)
if err != nil {
t.Fatalf("Cannot save the image as '%v'", ImageTypes[typ])
}
if len(buf) == 0 {
t.Fatalf("Empty saved '%v' image", ImageTypes[typ])
}
}
}
func TestVipsSaveTiff(t *testing.T) {
if !IsTypeSupportedSave(TIFF) {
t.Skipf("Format %#v is not supported", ImageTypes[TIFF])
}
image, _, _ := vipsRead(readImage("test.jpg"))
options := vipsSaveOptions{Quality: 95, Type: TIFF}
buf, _ := vipsSave(image, options)
if len(buf) == 0 {
t.Fatalf("Empty saved '%v' image", ImageTypes[TIFF])
}
}
func TestVipsRotate(t *testing.T) {
files := []struct {
name string
rotate Angle
}{
{"test.jpg", D90},
{"test_square.jpg", D45},
}
for _, file := range files {
image, _, _ := vipsRead(readImage(file.name))
newImg, err := vipsRotate(image, file.rotate)
if err != nil {
t.Fatal("Cannot rotate the image")
}
buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95})
if len(buf) == 0 {
t.Fatal("Empty image")
}
}
}
func TestVipsZoom(t *testing.T) {
image, _, _ := vipsRead(readImage("test.jpg"))
newImg, err := vipsZoom(image, 1)
if err != nil {
t.Fatal("Cannot save the image")
}
buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95})
if len(buf) == 0 {
t.Fatal("Empty image")
}
}
func TestVipsWatermark(t *testing.T) {
image, _, _ := vipsRead(readImage("test.jpg"))
watermark := Watermark{
Text: "Copy me if you can",
Font: "sans bold 12",
Opacity: 0.5,
Width: 200,
DPI: 100,
Margin: 100,
Background: Color{255, 255, 255},
}
newImg, err := vipsWatermark(image, watermark)
if err != nil {
t.Errorf("Cannot add watermark: %s", err)
}
buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95})
if len(buf) == 0 {
t.Fatal("Empty image")
}
}
func TestVipsWatermarkWithImage(t *testing.T) {
image, _, _ := vipsRead(readImage("test.jpg"))
watermark := readImage("transparent.png")
options := WatermarkImage{Left: 100, Top: 100, Opacity: 1.0, Buf: watermark}
newImg, err := vipsDrawWatermark(image, options)
if err != nil {
t.Errorf("Cannot add watermark: %s", err)
}
buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95})
if len(buf) == 0 {
t.Fatal("Empty image")
}
}
func TestVipsImageType(t *testing.T) {
imgType := vipsImageType(readImage("test.jpg"))
if imgType != JPEG {
t.Fatal("Invalid image type")
}
}
func TestVipsImageTypeInvalid(t *testing.T) {
imgType := vipsImageType([]byte("vip"))
if imgType != UNKNOWN {
t.Fatal("Invalid image type")
}
}
func TestVipsMemory(t *testing.T) {
mem := VipsMemory()
if mem.Memory < 1024 {
t.Fatal("Invalid memory")
}
if mem.Allocations == 0 {
t.Fatal("Invalid memory allocations")
}
}
func readImage(file string) []byte {
img, _ := os.Open(path.Join("fixtures", file))
buf, _ := ioutil.ReadAll(img)
defer img.Close()
return buf
}

View File

@ -1,21 +0,0 @@
v2.0.0 / 2014-10-22
==================
* remove live toggling feature. Closes #10
1.1.1 / 2014-07-07
==================
* fix: dispose socket. Closes #1
1.1.0 / 2014-06-29
==================
* add unix domain socket live debugging support
* add support for enabling/disabling at runtime
0.1.0 / 2014-05-24
==================
* add global and debug relative deltas

View File

@ -1,8 +0,0 @@
test:
@go test
bench:
@go test -bench=.
.PHONY: bench test

View File

@ -1,75 +0,0 @@
# go-debug
Conditional debug logging for Go libraries.
View the [docs](http://godoc.org/github.com/tj/go-debug).
## Installation
```
$ go get github.com/tj/go-debug
```
## Example
```go
package main
import . "github.com/tj/go-debug"
import "time"
var debug = Debug("single")
func main() {
for {
debug("sending mail")
debug("send email to %s", "tobi@segment.io")
debug("send email to %s", "loki@segment.io")
debug("send email to %s", "jane@segment.io")
time.Sleep(500 * time.Millisecond)
}
}
```
If you run the program with the `DEBUG=*` environment variable you will see:
```
15:58:15.115 34us 33us single - sending mail
15:58:15.116 3us 3us single - send email to tobi@segment.io
15:58:15.116 1us 1us single - send email to loki@segment.io
15:58:15.116 1us 1us single - send email to jane@segment.io
15:58:15.620 504ms 504ms single - sending mail
15:58:15.620 6us 6us single - send email to tobi@segment.io
15:58:15.620 4us 4us single - send email to loki@segment.io
15:58:15.620 4us 4us single - send email to jane@segment.io
15:58:16.123 503ms 503ms single - sending mail
15:58:16.123 7us 7us single - send email to tobi@segment.io
15:58:16.123 4us 4us single - send email to loki@segment.io
15:58:16.123 4us 4us single - send email to jane@segment.io
15:58:16.625 501ms 501ms single - sending mail
15:58:16.625 4us 4us single - send email to tobi@segment.io
15:58:16.625 4us 4us single - send email to loki@segment.io
15:58:16.625 5us 5us single - send email to jane@segment.io
```
A timestamp and two deltas are displayed. The timestamp consists of hour, minute, second and microseconds. The left-most delta is relative to the previous debug call of any name, followed by a delta specific to that debug function. These may be useful to identify timing issues and potential bottlenecks.
## The DEBUG environment variable
Executables often support `--verbose` flags for conditional logging, however
libraries typically either require altering your code to enable logging,
or simply omit logging all together. go-debug allows conditional logging
to be enabled via the __DEBUG__ environment variable, where one or more
patterns may be specified.
For example suppose your application has several models and you want
to output logs for users only, you might use `DEBUG=models:user`. In contrast
if you wanted to see what all database activity was you might use `DEBUG=models:*`,
or if you're love being swamped with logs: `DEBUG=*`. You may also specify a list of names delimited by a comma, for example `DEBUG=mongo,redis:*`.
The name given _should_ be the package name, however you can use whatever you like.
# License
MIT

View File

@ -1,128 +0,0 @@
package debug
import (
"fmt"
"io"
"math/rand"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
var (
writer io.Writer = os.Stderr
reg *regexp.Regexp
m sync.Mutex
enabled = false
)
// Debugger function.
type DebugFunction func(string, ...interface{})
// Terminal colors used at random.
var colors []string = []string{
"31",
"32",
"33",
"34",
"35",
"36",
}
// Initialize with DEBUG environment variable.
func init() {
env := os.Getenv("DEBUG")
if "" != env {
Enable(env)
}
}
// SetWriter replaces the default of os.Stderr with `w`.
func SetWriter(w io.Writer) {
m.Lock()
defer m.Unlock()
writer = w
}
// Disable all pattern matching. This function is thread-safe.
func Disable() {
m.Lock()
defer m.Unlock()
enabled = false
}
// Enable the given debug `pattern`. Patterns take a glob-like form,
// for example if you wanted to enable everything, just use "*", or
// if you had a library named mongodb you could use "mongodb:connection",
// or "mongodb:*". Multiple matches can be made with a comma, for
// example "mongo*,redis*".
//
// This function is thread-safe.
func Enable(pattern string) {
m.Lock()
defer m.Unlock()
pattern = regexp.QuoteMeta(pattern)
pattern = strings.Replace(pattern, "\\*", ".*?", -1)
pattern = strings.Replace(pattern, ",", "|", -1)
pattern = "^(" + pattern + ")$"
reg = regexp.MustCompile(pattern)
enabled = true
}
// Debug creates a debug function for `name` which you call
// with printf-style arguments in your application or library.
func Debug(name string) DebugFunction {
prevGlobal := time.Now()
color := colors[rand.Intn(len(colors))]
prev := time.Now()
return func(format string, args ...interface{}) {
if !enabled {
return
}
if !reg.MatchString(name) {
return
}
d := deltas(prevGlobal, prev, color)
fmt.Fprintf(writer, d+" \033["+color+"m"+name+"\033[0m - "+format+"\n", args...)
prevGlobal = time.Now()
prev = time.Now()
}
}
// Return formatting for deltas.
func deltas(prevGlobal, prev time.Time, color string) string {
now := time.Now()
global := now.Sub(prevGlobal).Nanoseconds()
delta := now.Sub(prev).Nanoseconds()
ts := now.UTC().Format("15:04:05.000")
deltas := fmt.Sprintf("%s %-6s \033["+color+"m%-6s", ts, humanizeNano(global), humanizeNano(delta))
return deltas
}
// Humanize nanoseconds to a string.
func humanizeNano(n int64) string {
var suffix string
switch {
case n > 1e9:
n /= 1e9
suffix = "s"
case n > 1e6:
n /= 1e6
suffix = "ms"
case n > 1e3:
n /= 1e3
suffix = "us"
default:
suffix = "ns"
}
return strconv.Itoa(int(n)) + suffix
}

View File

@ -1,152 +0,0 @@
package debug
import "testing"
import "strings"
import "bytes"
import "time"
func assertContains(t *testing.T, str, substr string) {
if !strings.Contains(str, substr) {
t.Fatalf("expected %q to contain %q", str, substr)
}
}
func assertNotContains(t *testing.T, str, substr string) {
if strings.Contains(str, substr) {
t.Fatalf("expected %q to not contain %q", str, substr)
}
}
func TestDefault(t *testing.T) {
var b []byte
buf := bytes.NewBuffer(b)
SetWriter(buf)
debug := Debug("foo")
debug("something")
debug("here")
debug("whoop")
if buf.Len() != 0 {
t.Fatalf("buffer should be empty")
}
}
func TestEnable(t *testing.T) {
var b []byte
buf := bytes.NewBuffer(b)
SetWriter(buf)
Enable("foo")
debug := Debug("foo")
debug("something")
debug("here")
debug("whoop")
if buf.Len() == 0 {
t.Fatalf("buffer should have output")
}
str := string(buf.Bytes())
assertContains(t, str, "something")
assertContains(t, str, "here")
assertContains(t, str, "whoop")
}
func TestMultipleOneEnabled(t *testing.T) {
var b []byte
buf := bytes.NewBuffer(b)
SetWriter(buf)
Enable("foo")
foo := Debug("foo")
foo("foo")
bar := Debug("bar")
bar("bar")
if buf.Len() == 0 {
t.Fatalf("buffer should have output")
}
str := string(buf.Bytes())
assertContains(t, str, "foo")
assertNotContains(t, str, "bar")
}
func TestMultipleEnabled(t *testing.T) {
var b []byte
buf := bytes.NewBuffer(b)
SetWriter(buf)
Enable("foo,bar")
foo := Debug("foo")
foo("foo")
bar := Debug("bar")
bar("bar")
if buf.Len() == 0 {
t.Fatalf("buffer should have output")
}
str := string(buf.Bytes())
assertContains(t, str, "foo")
assertContains(t, str, "bar")
}
func TestEnableDisable(t *testing.T) {
var b []byte
buf := bytes.NewBuffer(b)
SetWriter(buf)
Enable("foo,bar")
Disable()
foo := Debug("foo")
foo("foo")
bar := Debug("bar")
bar("bar")
if buf.Len() != 0 {
t.Fatalf("buffer should not have output")
}
}
func ExampleEnable() {
Enable("mongo:connection")
Enable("mongo:*")
Enable("foo,bar,baz")
Enable("*")
}
func ExampleDebug() {
var debug = Debug("single")
for {
debug("sending mail")
debug("send email to %s", "tobi@segment.io")
debug("send email to %s", "loki@segment.io")
debug("send email to %s", "jane@segment.io")
time.Sleep(500 * time.Millisecond)
}
}
func BenchmarkDisabled(b *testing.B) {
debug := Debug("something")
for i := 0; i < b.N; i++ {
debug("stuff")
}
}
func BenchmarkNonMatch(b *testing.B) {
debug := Debug("something")
Enable("nonmatch")
for i := 0; i < b.N; i++ {
debug("stuff")
}
}

View File

@ -1,25 +0,0 @@
package main
import . "github.com/visionmedia/go-debug"
import "time"
var a = Debug("multiple:a")
var b = Debug("multiple:b")
var c = Debug("multiple:c")
func work(debug DebugFunction, delay time.Duration) {
for {
debug("doing stuff")
time.Sleep(delay)
}
}
func main() {
q := make(chan bool)
go work(a, 1000*time.Millisecond)
go work(b, 250*time.Millisecond)
go work(c, 100*time.Millisecond)
<-q
}

View File

@ -1,16 +0,0 @@
package main
import . "github.com/visionmedia/go-debug"
import "time"
var debug = Debug("single")
func main() {
for {
debug("sending mail")
debug("send email to %s", "tobi@segment.io")
debug("send email to %s", "loki@segment.io")
debug("send email to %s", "jane@segment.io")
time.Sleep(500 * time.Millisecond)
}
}

3
vendor/golang.org/x/image/README generated vendored
View File

@ -1,3 +0,0 @@
This repository holds supplementary Go image libraries.
To submit changes to this repository, see http://golang.org/doc/contribute.html.

17
vendor/golang.org/x/image/README.md generated vendored Normal file
View File

@ -0,0 +1,17 @@
# Go Images
This repository holds supplementary Go image libraries.
## Download/Install
The easiest way to install is to run `go get -u golang.org/x/image`. You can
also manually git clone the repository to `$GOPATH/src/golang.org/x/image`.
## Report Issues / Send Patches
This repository uses Gerrit for code changes. To learn how to submit changes to
this repository, see https://golang.org/doc/contribute.html.
The main issue tracker for the image repository is located at
https://github.com/golang/go/issues. Prefix your issue with "x/image:" in the
subject line, so it is easy to find.

3
vendor/golang.org/x/net/README generated vendored
View File

@ -1,3 +0,0 @@
This repository holds supplementary Go networking libraries.
To submit changes to this repository, see http://golang.org/doc/contribute.html.

16
vendor/golang.org/x/net/README.md generated vendored Normal file
View File

@ -0,0 +1,16 @@
# Go Networking
This repository holds supplementary Go networking libraries.
## Download/Install
The easiest way to install is to run `go get -u golang.org/x/net`. You can
also manually git clone the repository to `$GOPATH/src/golang.org/x/net`.
## Report Issues / Send Patches
This repository uses Gerrit for code changes. To learn how to submit
changes to this repository, see https://golang.org/doc/contribute.html.
The main issue tracker for the net repository is located at
https://github.com/golang/go/issues. Prefix your issue with "x/net:" in the
subject line, so it is easy to find.

View File

@ -4,17 +4,17 @@
// +build ignore
//go:generate go run gen.go
//go:generate go run gen.go -test
package main
// This program generates table.go and table_test.go.
// Invoke as
//
// go run gen.go |gofmt >table.go
// go run gen.go -test |gofmt >table_test.go
import (
"bytes"
"flag"
"fmt"
"go/format"
"io/ioutil"
"math/rand"
"os"
"sort"
@ -42,6 +42,18 @@ func identifier(s string) string {
var test = flag.Bool("test", false, "generate table_test.go")
func genFile(name string, buf *bytes.Buffer) {
b, err := format.Source(buf.Bytes())
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := ioutil.WriteFile(name, b, 0644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func main() {
flag.Parse()
@ -52,32 +64,31 @@ func main() {
all = append(all, extra...)
sort.Strings(all)
if *test {
fmt.Printf("// generated by go run gen.go -test; DO NOT EDIT\n\n")
fmt.Printf("package atom\n\n")
fmt.Printf("var testAtomList = []string{\n")
for _, s := range all {
fmt.Printf("\t%q,\n", s)
}
fmt.Printf("}\n")
return
}
// uniq - lists have dups
// compute max len too
maxLen := 0
w := 0
for _, s := range all {
if w == 0 || all[w-1] != s {
if maxLen < len(s) {
maxLen = len(s)
}
all[w] = s
w++
}
}
all = all[:w]
if *test {
var buf bytes.Buffer
fmt.Fprintln(&buf, "// Code generated by go generate gen.go; DO NOT EDIT.\n")
fmt.Fprintln(&buf, "//go:generate go run gen.go -test\n")
fmt.Fprintln(&buf, "package atom\n")
fmt.Fprintln(&buf, "var testAtomList = []string{")
for _, s := range all {
fmt.Fprintf(&buf, "\t%q,\n", s)
}
fmt.Fprintln(&buf, "}")
genFile("table_test.go", &buf)
return
}
// Find hash that minimizes table size.
var best *table
for i := 0; i < 1000000; i++ {
@ -163,36 +174,46 @@ func main() {
atom[s] = uint32(off<<8 | len(s))
}
var buf bytes.Buffer
// Generate the Go code.
fmt.Printf("// generated by go run gen.go; DO NOT EDIT\n\n")
fmt.Printf("package atom\n\nconst (\n")
fmt.Fprintln(&buf, "// Code generated by go generate gen.go; DO NOT EDIT.\n")
fmt.Fprintln(&buf, "//go:generate go run gen.go\n")
fmt.Fprintln(&buf, "package atom\n\nconst (")
// compute max len
maxLen := 0
for _, s := range all {
fmt.Printf("\t%s Atom = %#x\n", identifier(s), atom[s])
if maxLen < len(s) {
maxLen = len(s)
}
fmt.Printf(")\n\n")
fmt.Fprintf(&buf, "\t%s Atom = %#x\n", identifier(s), atom[s])
}
fmt.Fprintln(&buf, ")\n")
fmt.Printf("const hash0 = %#x\n\n", best.h0)
fmt.Printf("const maxAtomLen = %d\n\n", maxLen)
fmt.Fprintf(&buf, "const hash0 = %#x\n\n", best.h0)
fmt.Fprintf(&buf, "const maxAtomLen = %d\n\n", maxLen)
fmt.Printf("var table = [1<<%d]Atom{\n", best.k)
fmt.Fprintf(&buf, "var table = [1<<%d]Atom{\n", best.k)
for i, s := range best.tab {
if s == "" {
continue
}
fmt.Printf("\t%#x: %#x, // %s\n", i, atom[s], s)
fmt.Fprintf(&buf, "\t%#x: %#x, // %s\n", i, atom[s], s)
}
fmt.Printf("}\n")
fmt.Fprintf(&buf, "}\n")
datasize := (1 << best.k) * 4
fmt.Printf("const atomText =\n")
fmt.Fprintln(&buf, "const atomText =")
textsize := len(text)
for len(text) > 60 {
fmt.Printf("\t%q +\n", text[:60])
fmt.Fprintf(&buf, "\t%q +\n", text[:60])
text = text[60:]
}
fmt.Printf("\t%q\n\n", text)
fmt.Fprintf(&buf, "\t%q\n\n", text)
fmt.Fprintf(os.Stderr, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize)
genFile("table.go", &buf)
fmt.Fprintf(os.Stdout, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize)
}
type byLen []string
@ -285,8 +306,10 @@ func (t *table) push(i uint32, depth int) bool {
// The lists of element names and attribute keys were taken from
// https://html.spec.whatwg.org/multipage/indices.html#index
// as of the "HTML Living Standard - Last Updated 21 February 2015" version.
// as of the "HTML Living Standard - Last Updated 18 September 2017" version.
// "command", "keygen" and "menuitem" have been removed from the spec,
// but are kept here for backwards compatibility.
var elements = []string{
"a",
"abbr",
@ -349,6 +372,7 @@ var elements = []string{
"legend",
"li",
"link",
"main",
"map",
"mark",
"menu",
@ -364,6 +388,7 @@ var elements = []string{
"output",
"p",
"param",
"picture",
"pre",
"progress",
"q",
@ -375,6 +400,7 @@ var elements = []string{
"script",
"section",
"select",
"slot",
"small",
"source",
"span",
@ -403,14 +429,21 @@ var elements = []string{
}
// https://html.spec.whatwg.org/multipage/indices.html#attributes-3
//
// "challenge", "command", "contextmenu", "dropzone", "icon", "keytype", "mediagroup",
// "radiogroup", "spellcheck", "scoped", "seamless", "sortable" and "sorted" have been removed from the spec,
// but are kept here for backwards compatibility.
var attributes = []string{
"abbr",
"accept",
"accept-charset",
"accesskey",
"action",
"allowfullscreen",
"allowpaymentrequest",
"allowusermedia",
"alt",
"as",
"async",
"autocomplete",
"autofocus",
@ -420,6 +453,7 @@ var attributes = []string{
"checked",
"cite",
"class",
"color",
"cols",
"colspan",
"command",
@ -457,6 +491,8 @@ var attributes = []string{
"icon",
"id",
"inputmode",
"integrity",
"is",
"ismap",
"itemid",
"itemprop",
@ -481,16 +517,20 @@ var attributes = []string{
"multiple",
"muted",
"name",
"nomodule",
"nonce",
"novalidate",
"open",
"optimum",
"pattern",
"ping",
"placeholder",
"playsinline",
"poster",
"preload",
"radiogroup",
"readonly",
"referrerpolicy",
"rel",
"required",
"reversed",
@ -507,10 +547,13 @@ var attributes = []string{
"sizes",
"sortable",
"sorted",
"slot",
"span",
"spellcheck",
"src",
"srcdoc",
"srclang",
"srcset",
"start",
"step",
"style",
@ -520,16 +563,22 @@ var attributes = []string{
"translate",
"type",
"typemustmatch",
"updateviacache",
"usemap",
"value",
"width",
"workertype",
"wrap",
}
// "onautocomplete", "onautocompleteerror", "onmousewheel",
// "onshow" and "onsort" have been removed from the spec,
// but are kept here for backwards compatibility.
var eventHandlers = []string{
"onabort",
"onautocomplete",
"onautocompleteerror",
"onauxclick",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
@ -541,11 +590,14 @@ var eventHandlers = []string{
"onclick",
"onclose",
"oncontextmenu",
"oncopy",
"oncuechange",
"oncut",
"ondblclick",
"ondrag",
"ondragend",
"ondragenter",
"ondragexit",
"ondragleave",
"ondragover",
"ondragstart",
@ -565,18 +617,24 @@ var eventHandlers = []string{
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadend",
"onloadstart",
"onmessage",
"onmessageerror",
"onmousedown",
"onmouseenter",
"onmouseleave",
"onmousemove",
"onmouseout",
"onmouseover",
"onmouseup",
"onmousewheel",
"onwheel",
"onoffline",
"ononline",
"onpagehide",
"onpageshow",
"onpaste",
"onpause",
"onplay",
"onplaying",
@ -585,7 +643,9 @@ var eventHandlers = []string{
"onratechange",
"onreset",
"onresize",
"onrejectionhandled",
"onscroll",
"onsecuritypolicyviolation",
"onseeked",
"onseeking",
"onselect",
@ -597,6 +657,7 @@ var eventHandlers = []string{
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunhandledrejection",
"onunload",
"onvolumechange",
"onwaiting",

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,28 @@
// generated by go run gen.go -test; DO NOT EDIT
// Code generated by go generate gen.go; DO NOT EDIT.
//go:generate go run gen.go -test
package atom
var testAtomList = []string{
"a",
"abbr",
"abbr",
"accept",
"accept-charset",
"accesskey",
"action",
"address",
"align",
"allowfullscreen",
"allowpaymentrequest",
"allowusermedia",
"alt",
"annotation",
"annotation-xml",
"applet",
"area",
"article",
"as",
"aside",
"async",
"audio",
@ -43,7 +48,6 @@ var testAtomList = []string{
"charset",
"checked",
"cite",
"cite",
"class",
"code",
"col",
@ -52,7 +56,6 @@ var testAtomList = []string{
"cols",
"colspan",
"command",
"command",
"content",
"contenteditable",
"contextmenu",
@ -60,7 +63,6 @@ var testAtomList = []string{
"coords",
"crossorigin",
"data",
"data",
"datalist",
"datetime",
"dd",
@ -93,7 +95,6 @@ var testAtomList = []string{
"foreignObject",
"foreignobject",
"form",
"form",
"formaction",
"formenctype",
"formmethod",
@ -128,6 +129,8 @@ var testAtomList = []string{
"input",
"inputmode",
"ins",
"integrity",
"is",
"isindex",
"ismap",
"itemid",
@ -140,7 +143,6 @@ var testAtomList = []string{
"keytype",
"kind",
"label",
"label",
"lang",
"legend",
"li",
@ -149,6 +151,7 @@ var testAtomList = []string{
"listing",
"loop",
"low",
"main",
"malignmark",
"manifest",
"map",
@ -179,6 +182,8 @@ var testAtomList = []string{
"nobr",
"noembed",
"noframes",
"nomodule",
"nonce",
"noscript",
"novalidate",
"object",
@ -187,6 +192,7 @@ var testAtomList = []string{
"onafterprint",
"onautocomplete",
"onautocompleteerror",
"onauxclick",
"onbeforeprint",
"onbeforeunload",
"onblur",
@ -197,11 +203,14 @@ var testAtomList = []string{
"onclick",
"onclose",
"oncontextmenu",
"oncopy",
"oncuechange",
"oncut",
"ondblclick",
"ondrag",
"ondragend",
"ondragenter",
"ondragexit",
"ondragleave",
"ondragover",
"ondragstart",
@ -221,9 +230,13 @@ var testAtomList = []string{
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadend",
"onloadstart",
"onmessage",
"onmessageerror",
"onmousedown",
"onmouseenter",
"onmouseleave",
"onmousemove",
"onmouseout",
"onmouseover",
@ -233,15 +246,18 @@ var testAtomList = []string{
"ononline",
"onpagehide",
"onpageshow",
"onpaste",
"onpause",
"onplay",
"onplaying",
"onpopstate",
"onprogress",
"onratechange",
"onrejectionhandled",
"onreset",
"onresize",
"onscroll",
"onsecuritypolicyviolation",
"onseeked",
"onseeking",
"onselect",
@ -253,9 +269,11 @@ var testAtomList = []string{
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunhandledrejection",
"onunload",
"onvolumechange",
"onwaiting",
"onwheel",
"open",
"optgroup",
"optimum",
@ -264,9 +282,11 @@ var testAtomList = []string{
"p",
"param",
"pattern",
"picture",
"ping",
"placeholder",
"plaintext",
"playsinline",
"poster",
"pre",
"preload",
@ -276,6 +296,7 @@ var testAtomList = []string{
"q",
"radiogroup",
"readonly",
"referrerpolicy",
"rel",
"required",
"reversed",
@ -297,23 +318,23 @@ var testAtomList = []string{
"shape",
"size",
"sizes",
"slot",
"small",
"sortable",
"sorted",
"source",
"spacer",
"span",
"span",
"spellcheck",
"src",
"srcdoc",
"srclang",
"srcset",
"start",
"step",
"strike",
"strong",
"style",
"style",
"sub",
"summary",
"sup",
@ -331,7 +352,6 @@ var testAtomList = []string{
"thead",
"time",
"title",
"title",
"tr",
"track",
"translate",
@ -340,12 +360,14 @@ var testAtomList = []string{
"typemustmatch",
"u",
"ul",
"updateviacache",
"usemap",
"value",
"var",
"video",
"wbr",
"width",
"workertype",
"wrap",
"xmp",
}

View File

@ -52,10 +52,12 @@ var isSpecialElementMap = map[string]bool{
"iframe": true,
"img": true,
"input": true,
"isindex": true,
"isindex": true, // The 'isindex' element has been removed, but keep it for backwards compatibility.
"keygen": true,
"li": true,
"link": true,
"listing": true,
"main": true,
"marquee": true,
"menu": true,
"meta": true,

View File

@ -167,7 +167,7 @@ type options struct {
bidirule func(s string) bool
}
// A Profile defines the configuration of a IDNA mapper.
// A Profile defines the configuration of an IDNA mapper.
type Profile struct {
options
}

View File

@ -12,7 +12,7 @@ import (
)
// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address
// with an optional username and password. See RFC 1928.
// with an optional username and password. See RFC 1928 and 1929.
func SOCKS5(network, addr string, auth *Auth, forward Dialer) (Dialer, error) {
s := &socks5{
network: network,
@ -120,6 +120,7 @@ func (s *socks5) connect(conn net.Conn, target string) error {
return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication")
}
// See RFC 1929
if buf[1] == socks5AuthPassword {
buf = buf[:0]
buf = append(buf, 1 /* password protocol version */)

View File

@ -37,7 +37,7 @@ import (
const (
// These sum of these four values must be no greater than 32.
nodesBitsChildren = 9
nodesBitsChildren = 10
nodesBitsICANN = 1
nodesBitsTextOffset = 15
nodesBitsTextLength = 6

Some files were not shown because too many files have changed in this diff Show More