Browse Source

CP bootstrap fix, product images first realisation

Vova Tkach 5 years ago
parent
commit
3e8b45c795
38 changed files with 5367 additions and 2 deletions
  1. 15 0
      assets/cp.styles.css
  2. 0 0
      assets/cp.styles.css.go
  3. 1 1
      consts/consts.go
  4. 15 0
      engine/wrapper/config.go
  5. 1 0
      go.mod
  6. 4 0
      go.sum
  7. 86 1
      modules/module_api.go
  8. 94 0
      modules/module_settings.go
  9. 102 0
      modules/module_settings_act_thumbnails.go
  10. 14 0
      vendor/github.com/disintegration/imaging/.travis.yml
  11. 21 0
      vendor/github.com/disintegration/imaging/LICENSE
  12. 197 0
      vendor/github.com/disintegration/imaging/README.md
  13. 252 0
      vendor/github.com/disintegration/imaging/adjust.go
  14. 148 0
      vendor/github.com/disintegration/imaging/convolution.go
  15. 7 0
      vendor/github.com/disintegration/imaging/doc.go
  16. 169 0
      vendor/github.com/disintegration/imaging/effects.go
  17. 3 0
      vendor/github.com/disintegration/imaging/go.mod
  18. 2 0
      vendor/github.com/disintegration/imaging/go.sum
  19. 52 0
      vendor/github.com/disintegration/imaging/histogram.go
  20. 444 0
      vendor/github.com/disintegration/imaging/io.go
  21. 595 0
      vendor/github.com/disintegration/imaging/resize.go
  22. 285 0
      vendor/github.com/disintegration/imaging/scanner.go
  23. 249 0
      vendor/github.com/disintegration/imaging/tools.go
  24. 268 0
      vendor/github.com/disintegration/imaging/transform.go
  25. 167 0
      vendor/github.com/disintegration/imaging/utils.go
  26. 3 0
      vendor/golang.org/x/image/AUTHORS
  27. 3 0
      vendor/golang.org/x/image/CONTRIBUTORS
  28. 27 0
      vendor/golang.org/x/image/LICENSE
  29. 22 0
      vendor/golang.org/x/image/PATENTS
  30. 199 0
      vendor/golang.org/x/image/bmp/reader.go
  31. 262 0
      vendor/golang.org/x/image/bmp/writer.go
  32. 69 0
      vendor/golang.org/x/image/tiff/buffer.go
  33. 58 0
      vendor/golang.org/x/image/tiff/compress.go
  34. 133 0
      vendor/golang.org/x/image/tiff/consts.go
  35. 272 0
      vendor/golang.org/x/image/tiff/lzw/reader.go
  36. 684 0
      vendor/golang.org/x/image/tiff/reader.go
  37. 438 0
      vendor/golang.org/x/image/tiff/writer.go
  38. 6 0
      vendor/modules.txt

+ 15 - 0
assets/cp.styles.css

@@ -959,6 +959,21 @@ ul.pagination {
 	right: 0px;
 }
 
+/* Fix for bootstrap select */
+.dropdown.bootstrap-select {
+	position: relative;
+}
+
+.dropdown.bootstrap-select select {
+	position: static !important;
+}
+
+.dropdown.bootstrap-select button.dropdown-toggle {
+	position: absolute;
+	top: 0px;
+	left: 0px;
+}
+
 /* Bootstrap fixes */
 #sys-modal-user-settings {
 	padding-right: 0px !important;

File diff suppressed because it is too large
+ 0 - 0
assets/cp.styles.css.go


+ 1 - 1
consts/consts.go

@@ -5,7 +5,7 @@ import (
 )
 
 const AssetsPath = "assets"
-const AssetsVersion = "38"
+const AssetsVersion = "39"
 const DirIndexFile = "index.html"
 
 // Bootstrap resources

+ 15 - 0
engine/wrapper/config.go

@@ -17,6 +17,12 @@ type Config struct {
 			Index    int
 			Category int
 		}
+		Thumbnails struct {
+			ControlPanel [2]int
+			Thumbnail1   [2]int
+			Thumbnail2   [2]int
+			Thumbnail3   [2]int
+		}
 	}
 	API struct {
 		XML struct {
@@ -41,6 +47,15 @@ func (this *Config) configDefault() {
 	this.Shop.Pagination.Index = 9
 	this.Shop.Pagination.Category = 9
 
+	this.Shop.Thumbnails.ControlPanel[0] = 100
+	this.Shop.Thumbnails.ControlPanel[1] = 100
+	this.Shop.Thumbnails.Thumbnail1[0] = 200
+	this.Shop.Thumbnails.Thumbnail1[1] = 200
+	this.Shop.Thumbnails.Thumbnail2[0] = 250
+	this.Shop.Thumbnails.Thumbnail2[1] = 250
+	this.Shop.Thumbnails.Thumbnail3[0] = 450
+	this.Shop.Thumbnails.Thumbnail3[1] = 450
+
 	this.API.XML.Enabled = 0
 	this.API.XML.Name = ""
 	this.API.XML.Company = ""

+ 1 - 0
go.mod

@@ -1,6 +1,7 @@
 module golang-fave
 
 require (
+	github.com/disintegration/imaging v1.6.0
 	github.com/go-sql-driver/mysql v1.4.1
 	github.com/vladimirok5959/golang-server-bootstrap v1.0.5
 	github.com/vladimirok5959/golang-server-resources v1.0.2

+ 4 - 0
go.sum

@@ -1,3 +1,5 @@
+github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA=
+github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
 github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -17,6 +19,8 @@ github.com/vladimirok5959/golang-server-sessions v1.0.5 h1:/DgfYZ0cEfy4P9v2BEgSc
 github.com/vladimirok5959/golang-server-sessions v1.0.5/go.mod h1:W6eCEIltyTs5IUvN1DfzLk59z+M1031kr8bMFUiq8vU=
 github.com/vladimirok5959/golang-server-static v1.0.0 h1:jmVNUCVF44+Am0euUMYFN4L3gdJYwm7aS8LFUmUAHJk=
 github.com/vladimirok5959/golang-server-static v1.0.0/go.mod h1:dxZsjCCpT65Z9dLP6p7RmR2rbgtYj6E4FSSSUqkNrsw=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=

+ 86 - 1
modules/module_api.go

@@ -1,8 +1,12 @@
 package modules
 
 import (
+	"bufio"
+	"bytes"
 	"html"
 	"net/http"
+	"os"
+	"path/filepath"
 	"strings"
 	"time"
 
@@ -10,8 +14,41 @@ import (
 	"golang-fave/engine/fetdata"
 	"golang-fave/engine/wrapper"
 	"golang-fave/utils"
+
+	"github.com/disintegration/imaging"
 )
 
+func (this *Modules) api_GenerateImage(wrap *wrapper.Wrapper, width, height, color int, filename string) ([]byte, bool, string, error) {
+	file_ext := ""
+	if strings.ToLower(filepath.Ext(filename)) == ".png" {
+		file_ext = "image/png"
+	} else if strings.ToLower(filepath.Ext(filename)) == ".jpg" {
+		file_ext = "image/jpeg"
+	} else if strings.ToLower(filepath.Ext(filename)) == ".jpeg" {
+		file_ext = "image/jpeg"
+	}
+
+	src, err := imaging.Open(filename)
+	if err != nil {
+		return []byte(""), false, file_ext, err
+	}
+
+	src = imaging.Fill(src, width, height, imaging.Center, imaging.Lanczos)
+
+	var out_bytes bytes.Buffer
+	out := bufio.NewWriter(&out_bytes)
+
+	if file_ext == "image/png" {
+		imaging.Encode(out, src, imaging.PNG)
+	} else if file_ext == "image/jpeg" {
+		imaging.Encode(out, src, imaging.JPEG)
+	} else {
+		return []byte(""), false, file_ext, nil
+	}
+
+	return out_bytes.Bytes(), true, file_ext, nil
+}
+
 func (this *Modules) api_GenerateXmlCurrencies(wrap *wrapper.Wrapper) string {
 	result := ``
 	rows, err := wrap.DB.Query(
@@ -252,7 +289,55 @@ func (this *Modules) RegisterModule_Api() *Module {
 		Icon:   assets.SysSvgIconPage,
 		Sub:    &[]MISub{},
 	}, func(wrap *wrapper.Wrapper) {
-		if len(wrap.UrlArgs) == 2 && wrap.UrlArgs[0] == "api" && wrap.UrlArgs[1] == "products" {
+
+		// http://localhost:8080/api/product-image/thumb-cp/1/1565650500.png/
+		// http://localhost:8080/api/product-image/thumb-1/1/1565650500.png/
+		// http://localhost:8080/api/product-image/thumb-2/1/1565650500.png/
+		// http://localhost:8080/api/product-image/thumb-3/1/1565650500.png/
+
+		if len(wrap.UrlArgs) == 5 && wrap.UrlArgs[0] == "api" && wrap.UrlArgs[1] == "product-image" && (wrap.UrlArgs[2] == "thumb-cp" || wrap.UrlArgs[2] == "thumb-1" || wrap.UrlArgs[2] == "thumb-2" || wrap.UrlArgs[2] == "thumb-3") {
+			thumb_type := wrap.UrlArgs[2]
+			product_id := wrap.UrlArgs[3]
+			file_name := wrap.UrlArgs[4]
+
+			original_file := wrap.DHtdocs + string(os.PathSeparator) + "products" + string(os.PathSeparator) + "images" + string(os.PathSeparator) + product_id + string(os.PathSeparator) + file_name
+			if !utils.IsFileExists(original_file) {
+				// User error 404 page
+				wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
+				return
+			}
+
+			width := (*wrap.Config).Shop.Thumbnails.ControlPanel[0]
+			height := (*wrap.Config).Shop.Thumbnails.ControlPanel[1]
+
+			if thumb_type == "thumb-1" {
+				width = (*wrap.Config).Shop.Thumbnails.Thumbnail1[0]
+				height = (*wrap.Config).Shop.Thumbnails.Thumbnail1[1]
+			} else if thumb_type == "thumb-2" {
+				width = (*wrap.Config).Shop.Thumbnails.Thumbnail2[0]
+				height = (*wrap.Config).Shop.Thumbnails.Thumbnail2[1]
+			} else if thumb_type == "thumb-3" {
+				width = (*wrap.Config).Shop.Thumbnails.Thumbnail3[0]
+				height = (*wrap.Config).Shop.Thumbnails.Thumbnail3[1]
+			}
+
+			data, ok, ext, err := this.api_GenerateImage(wrap, width, height, 0, original_file)
+			if err != nil {
+				// System error 500
+				utils.SystemErrorPageEngine(wrap.W, err)
+				return
+			}
+
+			if !ok {
+				// User error 404 page
+				wrap.RenderFrontEnd("404", fetdata.New(wrap, nil, true), http.StatusNotFound)
+				return
+			}
+
+			wrap.W.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
+			wrap.W.Header().Set("Content-Type", ext)
+			wrap.W.Write(data)
+		} else if len(wrap.UrlArgs) == 2 && wrap.UrlArgs[0] == "api" && wrap.UrlArgs[1] == "products" {
 			if (*wrap.Config).API.XML.Enabled == 1 {
 				// Fix url
 				if wrap.R.URL.Path[len(wrap.R.URL.Path)-1] != '/' {

+ 94 - 0
modules/module_settings.go

@@ -23,6 +23,7 @@ func (this *Modules) RegisterModule_Settings() *Module {
 		Sub: &[]MISub{
 			{Mount: "default", Name: "Robots.txt", Show: true, Icon: assets.SysSvgIconBug},
 			{Mount: "pagination", Name: "Pagination", Show: true, Icon: assets.SysSvgIconList},
+			{Mount: "thumbnails", Name: "Thumbnails", Show: true, Icon: assets.SysSvgIconList},
 			{Mount: "api", Name: "API", Show: true, Icon: assets.SysSvgIconList},
 		},
 	}, nil, func(wrap *wrapper.Wrapper) (string, string, string) {
@@ -121,6 +122,99 @@ func (this *Modules) RegisterModule_Settings() *Module {
 				},
 			})
 
+			sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
+		} else if wrap.CurrSubModule == "thumbnails" {
+			content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{
+				{Name: "Thumbnails"},
+			})
+
+			// TODO: two fields in one line
+
+			content += builder.DataForm(wrap, []builder.DataFormField{
+				{
+					Kind:  builder.DFKHidden,
+					Name:  "action",
+					Value: "settings-thumbnails",
+				},
+				{
+					Kind:     builder.DFKNumber,
+					Caption:  "Shop thumbnail 1 width",
+					Name:     "shop-thumbnail-w-1",
+					Min:      "10",
+					Max:      "1000",
+					Required: true,
+					Value:    utils.IntToStr((*wrap.Config).Shop.Thumbnails.Thumbnail1[0]),
+				},
+				{
+					Kind:     builder.DFKNumber,
+					Caption:  "Shop thumbnail 1 height",
+					Name:     "shop-thumbnail-h-1",
+					Min:      "10",
+					Max:      "1000",
+					Required: true,
+					Value:    utils.IntToStr((*wrap.Config).Shop.Thumbnails.Thumbnail1[1]),
+				},
+				{
+					Kind:    builder.DFKText,
+					Caption: "",
+					Name:    "",
+					Value:   "",
+					CallBack: func(field *builder.DataFormField) string {
+						return `<hr>`
+					},
+				},
+				{
+					Kind:     builder.DFKNumber,
+					Caption:  "Shop thumbnail 2 width",
+					Name:     "shop-thumbnail-w-2",
+					Min:      "10",
+					Max:      "1000",
+					Required: true,
+					Value:    utils.IntToStr((*wrap.Config).Shop.Thumbnails.Thumbnail2[0]),
+				},
+				{
+					Kind:     builder.DFKNumber,
+					Caption:  "Shop thumbnail 2 height",
+					Name:     "shop-thumbnail-h-2",
+					Min:      "10",
+					Max:      "1000",
+					Required: true,
+					Value:    utils.IntToStr((*wrap.Config).Shop.Thumbnails.Thumbnail2[1]),
+				},
+				{
+					Kind:    builder.DFKText,
+					Caption: "",
+					Name:    "",
+					Value:   "",
+					CallBack: func(field *builder.DataFormField) string {
+						return `<hr>`
+					},
+				},
+				{
+					Kind:     builder.DFKNumber,
+					Caption:  "Shop thumbnail 3 width",
+					Name:     "shop-thumbnail-w-3",
+					Min:      "10",
+					Max:      "1000",
+					Required: true,
+					Value:    utils.IntToStr((*wrap.Config).Shop.Thumbnails.Thumbnail3[0]),
+				},
+				{
+					Kind:     builder.DFKNumber,
+					Caption:  "Shop thumbnail 3 height",
+					Name:     "shop-thumbnail-h-3",
+					Min:      "10",
+					Max:      "1000",
+					Required: true,
+					Value:    utils.IntToStr((*wrap.Config).Shop.Thumbnails.Thumbnail3[1]),
+				},
+				{
+					Kind:   builder.DFKSubmit,
+					Value:  "Save",
+					Target: "add-edit-button",
+				},
+			})
+
 			sidebar += `<button class="btn btn-primary btn-sidebar" id="add-edit-button">Save</button>`
 		} else if wrap.CurrSubModule == "api" {
 			content += this.getBreadCrumbs(wrap, &[]consts.BreadCrumb{

+ 102 - 0
modules/module_settings_act_thumbnails.go

@@ -0,0 +1,102 @@
+package modules
+
+import (
+	"strconv"
+
+	"golang-fave/engine/wrapper"
+	"golang-fave/utils"
+)
+
+func (this *Modules) RegisterAction_SettingsThumbnails() *Action {
+	return this.newAction(AInfo{
+		WantDB:    true,
+		Mount:     "settings-thumbnails",
+		WantAdmin: true,
+	}, func(wrap *wrapper.Wrapper) {
+		pf_shop_thumbnail_w_1 := wrap.R.FormValue("shop-thumbnail-w-1")
+		pf_shop_thumbnail_h_1 := wrap.R.FormValue("shop-thumbnail-h-1")
+
+		pf_shop_thumbnail_w_2 := wrap.R.FormValue("shop-thumbnail-w-2")
+		pf_shop_thumbnail_h_2 := wrap.R.FormValue("shop-thumbnail-h-2")
+
+		pf_shop_thumbnail_w_3 := wrap.R.FormValue("shop-thumbnail-w-3")
+		pf_shop_thumbnail_h_3 := wrap.R.FormValue("shop-thumbnail-h-3")
+
+		if _, err := strconv.Atoi(pf_shop_thumbnail_w_1); err != nil {
+			wrap.MsgError(`Must be integer number`)
+			return
+		}
+		if _, err := strconv.Atoi(pf_shop_thumbnail_h_1); err != nil {
+			wrap.MsgError(`Must be integer number`)
+			return
+		}
+
+		if _, err := strconv.Atoi(pf_shop_thumbnail_w_2); err != nil {
+			wrap.MsgError(`Must be integer number`)
+			return
+		}
+		if _, err := strconv.Atoi(pf_shop_thumbnail_h_2); err != nil {
+			wrap.MsgError(`Must be integer number`)
+			return
+		}
+
+		if _, err := strconv.Atoi(pf_shop_thumbnail_w_3); err != nil {
+			wrap.MsgError(`Must be integer number`)
+			return
+		}
+		if _, err := strconv.Atoi(pf_shop_thumbnail_h_3); err != nil {
+			wrap.MsgError(`Must be integer number`)
+			return
+		}
+
+		pfi_shop_thumbnail_w_1 := utils.StrToInt(pf_shop_thumbnail_w_1)
+		pfi_shop_thumbnail_h_1 := utils.StrToInt(pf_shop_thumbnail_h_1)
+
+		pfi_shop_thumbnail_w_2 := utils.StrToInt(pf_shop_thumbnail_w_2)
+		pfi_shop_thumbnail_h_2 := utils.StrToInt(pf_shop_thumbnail_h_2)
+
+		pfi_shop_thumbnail_w_3 := utils.StrToInt(pf_shop_thumbnail_w_3)
+		pfi_shop_thumbnail_h_3 := utils.StrToInt(pf_shop_thumbnail_h_3)
+
+		// Correct some values
+		if pfi_shop_thumbnail_w_1 < 10 {
+			pfi_shop_thumbnail_w_1 = 10
+		}
+		if pfi_shop_thumbnail_h_1 > 1000 {
+			pfi_shop_thumbnail_h_1 = 1000
+		}
+
+		if pfi_shop_thumbnail_w_2 < 10 {
+			pfi_shop_thumbnail_w_2 = 10
+		}
+		if pfi_shop_thumbnail_h_2 > 1000 {
+			pfi_shop_thumbnail_h_2 = 1000
+		}
+
+		if pfi_shop_thumbnail_w_3 < 10 {
+			pfi_shop_thumbnail_w_3 = 10
+		}
+		if pfi_shop_thumbnail_h_3 > 1000 {
+			pfi_shop_thumbnail_h_3 = 1000
+		}
+
+		(*wrap.Config).Shop.Thumbnails.Thumbnail1[0] = pfi_shop_thumbnail_w_1
+		(*wrap.Config).Shop.Thumbnails.Thumbnail1[1] = pfi_shop_thumbnail_h_1
+
+		(*wrap.Config).Shop.Thumbnails.Thumbnail2[0] = pfi_shop_thumbnail_w_2
+		(*wrap.Config).Shop.Thumbnails.Thumbnail2[1] = pfi_shop_thumbnail_h_2
+
+		(*wrap.Config).Shop.Thumbnails.Thumbnail3[0] = pfi_shop_thumbnail_w_3
+		(*wrap.Config).Shop.Thumbnails.Thumbnail3[1] = pfi_shop_thumbnail_h_3
+
+		if err := wrap.ConfigSave(); err != nil {
+			wrap.MsgError(err.Error())
+			return
+		}
+
+		// TODO: Reset images cache if changed
+
+		// Reload current page
+		wrap.Write(`window.location.reload(false);`)
+	})
+}

+ 14 - 0
vendor/github.com/disintegration/imaging/.travis.yml

@@ -0,0 +1,14 @@
+language: go
+go:
+  - "1.7.x"
+  - "1.8.x"
+  - "1.9.x"
+  - "1.10.x"
+  - "1.11.x"
+
+before_install:
+  - go get github.com/mattn/goveralls
+
+script:
+  - go test -v -race -cover
+  - $GOPATH/bin/goveralls -service=travis-ci

+ 21 - 0
vendor/github.com/disintegration/imaging/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2012 Grigory Dryapak
+
+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.

+ 197 - 0
vendor/github.com/disintegration/imaging/README.md

@@ -0,0 +1,197 @@
+# Imaging
+
+[![GoDoc](https://godoc.org/github.com/disintegration/imaging?status.svg)](https://godoc.org/github.com/disintegration/imaging)
+[![Build Status](https://travis-ci.org/disintegration/imaging.svg?branch=master)](https://travis-ci.org/disintegration/imaging)
+[![Coverage Status](https://coveralls.io/repos/github/disintegration/imaging/badge.svg?branch=master&service=github)](https://coveralls.io/github/disintegration/imaging?branch=master)
+[![Go Report Card](https://goreportcard.com/badge/github.com/disintegration/imaging)](https://goreportcard.com/report/github.com/disintegration/imaging)
+
+Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.).
+
+All the image processing functions provided by the package accept any image type that implements `image.Image` interface
+as an input, and return a new image of `*image.NRGBA` type (32bit RGBA colors, non-premultiplied alpha).
+
+## Installation
+
+    go get -u github.com/disintegration/imaging
+
+## Documentation
+
+http://godoc.org/github.com/disintegration/imaging
+
+## Usage examples
+
+A few usage examples can be found below. See the documentation for the full list of supported functions.
+
+### Image resizing
+
+```go
+// Resize srcImage to size = 128x128px using the Lanczos filter.
+dstImage128 := imaging.Resize(srcImage, 128, 128, imaging.Lanczos)
+
+// Resize srcImage to width = 800px preserving the aspect ratio.
+dstImage800 := imaging.Resize(srcImage, 800, 0, imaging.Lanczos)
+
+// Scale down srcImage to fit the 800x600px bounding box.
+dstImageFit := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
+
+// Resize and crop the srcImage to fill the 100x100px area.
+dstImageFill := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos)
+```
+
+Imaging supports image resizing using various resampling filters. The most notable ones:
+- `Lanczos` - A high-quality resampling filter for photographic images yielding sharp results.
+- `CatmullRom` - A sharp cubic filter that is faster than Lanczos filter while providing similar results.
+- `MitchellNetravali` - A cubic filter that produces smoother results with less ringing artifacts than CatmullRom.
+- `Linear` - Bilinear resampling filter, produces smooth output. Faster than cubic filters.
+- `Box` - Simple and fast averaging filter appropriate for downscaling. When upscaling it's similar to NearestNeighbor.
+- `NearestNeighbor` - Fastest resampling filter, no antialiasing.
+
+The full list of supported filters:  NearestNeighbor, Box, Linear, Hermite, MitchellNetravali, CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine. Custom filters can be created using ResampleFilter struct.
+
+**Resampling filters comparison**
+
+Original image:
+
+![srcImage](testdata/branches.png)
+
+The same image resized from 600x400px to 150x100px using different resampling filters.
+From faster (lower quality) to slower (higher quality):
+
+Filter                    | Resize result
+--------------------------|---------------------------------------------
+`imaging.NearestNeighbor` | ![dstImage](testdata/out_resize_nearest.png)
+`imaging.Linear`          | ![dstImage](testdata/out_resize_linear.png)
+`imaging.CatmullRom`      | ![dstImage](testdata/out_resize_catrom.png)
+`imaging.Lanczos`         | ![dstImage](testdata/out_resize_lanczos.png)
+
+
+### Gaussian Blur
+
+```go
+dstImage := imaging.Blur(srcImage, 0.5)
+```
+
+Sigma parameter allows to control the strength of the blurring effect.
+
+Original image                     | Sigma = 0.5                            | Sigma = 1.5
+-----------------------------------|----------------------------------------|---------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_blur_0.5.png) | ![dstImage](testdata/out_blur_1.5.png)
+
+### Sharpening
+
+```go
+dstImage := imaging.Sharpen(srcImage, 0.5)
+```
+
+`Sharpen` uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect.
+
+Original image                     | Sigma = 0.5                               | Sigma = 1.5
+-----------------------------------|-------------------------------------------|------------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_sharpen_0.5.png) | ![dstImage](testdata/out_sharpen_1.5.png)
+
+### Gamma correction
+
+```go
+dstImage := imaging.AdjustGamma(srcImage, 0.75)
+```
+
+Original image                     | Gamma = 0.75                             | Gamma = 1.25
+-----------------------------------|------------------------------------------|-----------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_gamma_0.75.png) | ![dstImage](testdata/out_gamma_1.25.png)
+
+### Contrast adjustment
+
+```go
+dstImage := imaging.AdjustContrast(srcImage, 20)
+```
+
+Original image                     | Contrast = 15                              | Contrast = -15
+-----------------------------------|--------------------------------------------|-------------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_contrast_p15.png) | ![dstImage](testdata/out_contrast_m15.png)
+
+### Brightness adjustment
+
+```go
+dstImage := imaging.AdjustBrightness(srcImage, 20)
+```
+
+Original image                     | Brightness = 10                              | Brightness = -10
+-----------------------------------|----------------------------------------------|---------------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_brightness_p10.png) | ![dstImage](testdata/out_brightness_m10.png)
+
+### Saturation adjustment
+
+```go
+dstImage := imaging.AdjustSaturation(srcImage, 20)
+```
+
+Original image                     | Saturation = 30                              | Saturation = -30
+-----------------------------------|----------------------------------------------|---------------------------------------------
+![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_saturation_p30.png) | ![dstImage](testdata/out_saturation_m30.png)
+
+## Example code
+
+```go
+package main
+
+import (
+	"image"
+	"image/color"
+	"log"
+
+	"github.com/disintegration/imaging"
+)
+
+func main() {
+	// Open a test image.
+	src, err := imaging.Open("testdata/flowers.png")
+	if err != nil {
+		log.Fatalf("failed to open image: %v", err)
+	}
+
+	// Crop the original image to 300x300px size using the center anchor.
+	src = imaging.CropAnchor(src, 300, 300, imaging.Center)
+
+	// Resize the cropped image to width = 200px preserving the aspect ratio.
+	src = imaging.Resize(src, 200, 0, imaging.Lanczos)
+
+	// Create a blurred version of the image.
+	img1 := imaging.Blur(src, 5)
+
+	// Create a grayscale version of the image with higher contrast and sharpness.
+	img2 := imaging.Grayscale(src)
+	img2 = imaging.AdjustContrast(img2, 20)
+	img2 = imaging.Sharpen(img2, 2)
+
+	// Create an inverted version of the image.
+	img3 := imaging.Invert(src)
+
+	// Create an embossed version of the image using a convolution filter.
+	img4 := imaging.Convolve3x3(
+		src,
+		[9]float64{
+			-1, -1, 0,
+			-1, 1, 1,
+			0, 1, 1,
+		},
+		nil,
+	)
+
+	// Create a new image and paste the four produced images into it.
+	dst := imaging.New(400, 400, color.NRGBA{0, 0, 0, 0})
+	dst = imaging.Paste(dst, img1, image.Pt(0, 0))
+	dst = imaging.Paste(dst, img2, image.Pt(0, 200))
+	dst = imaging.Paste(dst, img3, image.Pt(200, 0))
+	dst = imaging.Paste(dst, img4, image.Pt(200, 200))
+
+	// Save the resulting image as JPEG.
+	err = imaging.Save(dst, "testdata/out_example.jpg")
+	if err != nil {
+		log.Fatalf("failed to save image: %v", err)
+	}
+}
+```
+
+Output:
+
+![dstImage](testdata/out_example.jpg)

+ 252 - 0
vendor/github.com/disintegration/imaging/adjust.go

@@ -0,0 +1,252 @@
+package imaging
+
+import (
+	"image"
+	"image/color"
+	"math"
+)
+
+// Grayscale produces a grayscale version of the image.
+func Grayscale(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+	parallel(0, src.h, func(ys <-chan int) {
+		for y := range ys {
+			i := y * dst.Stride
+			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
+			for x := 0; x < src.w; x++ {
+				d := dst.Pix[i : i+3 : i+3]
+				r := d[0]
+				g := d[1]
+				b := d[2]
+				f := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
+				y := uint8(f + 0.5)
+				d[0] = y
+				d[1] = y
+				d[2] = y
+				i += 4
+			}
+		}
+	})
+	return dst
+}
+
+// Invert produces an inverted (negated) version of the image.
+func Invert(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+	parallel(0, src.h, func(ys <-chan int) {
+		for y := range ys {
+			i := y * dst.Stride
+			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
+			for x := 0; x < src.w; x++ {
+				d := dst.Pix[i : i+3 : i+3]
+				d[0] = 255 - d[0]
+				d[1] = 255 - d[1]
+				d[2] = 255 - d[2]
+				i += 4
+			}
+		}
+	})
+	return dst
+}
+
+// AdjustSaturation changes the saturation of the image using the percentage parameter and returns the adjusted image.
+// The percentage must be in the range (-100, 100).
+// The percentage = 0 gives the original image.
+// The percentage = 100 gives the image with the saturation value doubled for each pixel.
+// The percentage = -100 gives the image with the saturation value zeroed for each pixel (grayscale).
+//
+// Examples:
+//  dstImage = imaging.AdjustSaturation(srcImage, 25) // Increase image saturation by 25%.
+//  dstImage = imaging.AdjustSaturation(srcImage, -10) // Decrease image saturation by 10%.
+//
+func AdjustSaturation(img image.Image, percentage float64) *image.NRGBA {
+	percentage = math.Min(math.Max(percentage, -100), 100)
+	multiplier := 1 + percentage/100
+
+	return AdjustFunc(img, func(c color.NRGBA) color.NRGBA {
+		h, s, l := rgbToHSL(c.R, c.G, c.B)
+		s *= multiplier
+		if s > 1 {
+			s = 1
+		}
+		r, g, b := hslToRGB(h, s, l)
+		return color.NRGBA{r, g, b, c.A}
+	})
+}
+
+// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
+// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
+// The percentage = -100 gives solid gray image.
+//
+// Examples:
+//
+//	dstImage = imaging.AdjustContrast(srcImage, -10) // Decrease image contrast by 10%.
+//	dstImage = imaging.AdjustContrast(srcImage, 20) // Increase image contrast by 20%.
+//
+func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
+	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
+	lut := make([]uint8, 256)
+
+	v := (100.0 + percentage) / 100.0
+	for i := 0; i < 256; i++ {
+		if 0 <= v && v <= 1 {
+			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
+		} else if 1 < v && v < 2 {
+			lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
+		} else {
+			lut[i] = uint8(float64(i)/255.0+0.5) * 255
+		}
+	}
+
+	return adjustLUT(img, lut)
+}
+
+// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
+// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
+// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
+//
+// Examples:
+//
+//	dstImage = imaging.AdjustBrightness(srcImage, -15) // Decrease image brightness by 15%.
+//	dstImage = imaging.AdjustBrightness(srcImage, 10) // Increase image brightness by 10%.
+//
+func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
+	percentage = math.Min(math.Max(percentage, -100.0), 100.0)
+	lut := make([]uint8, 256)
+
+	shift := 255.0 * percentage / 100.0
+	for i := 0; i < 256; i++ {
+		lut[i] = clamp(float64(i) + shift)
+	}
+
+	return adjustLUT(img, lut)
+}
+
+// AdjustGamma performs a gamma correction on the image and returns the adjusted image.
+// Gamma parameter must be positive. Gamma = 1.0 gives the original image.
+// Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
+//
+// Example:
+//
+//	dstImage = imaging.AdjustGamma(srcImage, 0.7)
+//
+func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
+	e := 1.0 / math.Max(gamma, 0.0001)
+	lut := make([]uint8, 256)
+
+	for i := 0; i < 256; i++ {
+		lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
+	}
+
+	return adjustLUT(img, lut)
+}
+
+// AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
+// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
+// The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
+// The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
+// If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
+//
+// Examples:
+//
+//	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // Increase the contrast.
+//	dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // Decrease the contrast.
+//
+func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
+	if factor == 0 {
+		return Clone(img)
+	}
+
+	lut := make([]uint8, 256)
+	a := math.Min(math.Max(midpoint, 0.0), 1.0)
+	b := math.Abs(factor)
+	sig0 := sigmoid(a, b, 0)
+	sig1 := sigmoid(a, b, 1)
+	e := 1.0e-6
+
+	if factor > 0 {
+		for i := 0; i < 256; i++ {
+			x := float64(i) / 255.0
+			sigX := sigmoid(a, b, x)
+			f := (sigX - sig0) / (sig1 - sig0)
+			lut[i] = clamp(f * 255.0)
+		}
+	} else {
+		for i := 0; i < 256; i++ {
+			x := float64(i) / 255.0
+			arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
+			f := a - math.Log(1.0/arg-1.0)/b
+			lut[i] = clamp(f * 255.0)
+		}
+	}
+
+	return adjustLUT(img, lut)
+}
+
+func sigmoid(a, b, x float64) float64 {
+	return 1 / (1 + math.Exp(b*(a-x)))
+}
+
+// adjustLUT applies the given lookup table to the colors of the image.
+func adjustLUT(img image.Image, lut []uint8) *image.NRGBA {
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+	lut = lut[0:256]
+	parallel(0, src.h, func(ys <-chan int) {
+		for y := range ys {
+			i := y * dst.Stride
+			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
+			for x := 0; x < src.w; x++ {
+				d := dst.Pix[i : i+3 : i+3]
+				d[0] = lut[d[0]]
+				d[1] = lut[d[1]]
+				d[2] = lut[d[2]]
+				i += 4
+			}
+		}
+	})
+	return dst
+}
+
+// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
+//
+// Example:
+//
+//	dstImage = imaging.AdjustFunc(
+//		srcImage,
+//		func(c color.NRGBA) color.NRGBA {
+//			// Shift the red channel by 16.
+//			r := int(c.R) + 16
+//			if r > 255 {
+//				r = 255
+//			}
+//			return color.NRGBA{uint8(r), c.G, c.B, c.A}
+//		}
+//	)
+//
+func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+	parallel(0, src.h, func(ys <-chan int) {
+		for y := range ys {
+			i := y * dst.Stride
+			src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
+			for x := 0; x < src.w; x++ {
+				d := dst.Pix[i : i+4 : i+4]
+				r := d[0]
+				g := d[1]
+				b := d[2]
+				a := d[3]
+				c := fn(color.NRGBA{r, g, b, a})
+				d[0] = c.R
+				d[1] = c.G
+				d[2] = c.B
+				d[3] = c.A
+				i += 4
+			}
+		}
+	})
+	return dst
+}

+ 148 - 0
vendor/github.com/disintegration/imaging/convolution.go

@@ -0,0 +1,148 @@
+package imaging
+
+import (
+	"image"
+)
+
+// ConvolveOptions are convolution parameters.
+type ConvolveOptions struct {
+	// If Normalize is true the kernel is normalized before convolution.
+	Normalize bool
+
+	// If Abs is true the absolute value of each color channel is taken after convolution.
+	Abs bool
+
+	// Bias is added to each color channel value after convolution.
+	Bias int
+}
+
+// Convolve3x3 convolves the image with the specified 3x3 convolution kernel.
+// Default parameters are used if a nil *ConvolveOptions is passed.
+func Convolve3x3(img image.Image, kernel [9]float64, options *ConvolveOptions) *image.NRGBA {
+	return convolve(img, kernel[:], options)
+}
+
+// Convolve5x5 convolves the image with the specified 5x5 convolution kernel.
+// Default parameters are used if a nil *ConvolveOptions is passed.
+func Convolve5x5(img image.Image, kernel [25]float64, options *ConvolveOptions) *image.NRGBA {
+	return convolve(img, kernel[:], options)
+}
+
+func convolve(img image.Image, kernel []float64, options *ConvolveOptions) *image.NRGBA {
+	src := toNRGBA(img)
+	w := src.Bounds().Max.X
+	h := src.Bounds().Max.Y
+	dst := image.NewNRGBA(image.Rect(0, 0, w, h))
+
+	if w < 1 || h < 1 {
+		return dst
+	}
+
+	if options == nil {
+		options = &ConvolveOptions{}
+	}
+
+	if options.Normalize {
+		normalizeKernel(kernel)
+	}
+
+	type coef struct {
+		x, y int
+		k    float64
+	}
+	var coefs []coef
+	var m int
+
+	switch len(kernel) {
+	case 9:
+		m = 1
+	case 25:
+		m = 2
+	}
+
+	i := 0
+	for y := -m; y <= m; y++ {
+		for x := -m; x <= m; x++ {
+			if kernel[i] != 0 {
+				coefs = append(coefs, coef{x: x, y: y, k: kernel[i]})
+			}
+			i++
+		}
+	}
+
+	parallel(0, h, func(ys <-chan int) {
+		for y := range ys {
+			for x := 0; x < w; x++ {
+				var r, g, b float64
+				for _, c := range coefs {
+					ix := x + c.x
+					if ix < 0 {
+						ix = 0
+					} else if ix >= w {
+						ix = w - 1
+					}
+
+					iy := y + c.y
+					if iy < 0 {
+						iy = 0
+					} else if iy >= h {
+						iy = h - 1
+					}
+
+					off := iy*src.Stride + ix*4
+					s := src.Pix[off : off+3 : off+3]
+					r += float64(s[0]) * c.k
+					g += float64(s[1]) * c.k
+					b += float64(s[2]) * c.k
+				}
+
+				if options.Abs {
+					if r < 0 {
+						r = -r
+					}
+					if g < 0 {
+						g = -g
+					}
+					if b < 0 {
+						b = -b
+					}
+				}
+
+				if options.Bias != 0 {
+					r += float64(options.Bias)
+					g += float64(options.Bias)
+					b += float64(options.Bias)
+				}
+
+				srcOff := y*src.Stride + x*4
+				dstOff := y*dst.Stride + x*4
+				d := dst.Pix[dstOff : dstOff+4 : dstOff+4]
+				d[0] = clamp(r)
+				d[1] = clamp(g)
+				d[2] = clamp(b)
+				d[3] = src.Pix[srcOff+3]
+			}
+		}
+	})
+
+	return dst
+}
+
+func normalizeKernel(kernel []float64) {
+	var sum, sumpos float64
+	for i := range kernel {
+		sum += kernel[i]
+		if kernel[i] > 0 {
+			sumpos += kernel[i]
+		}
+	}
+	if sum != 0 {
+		for i := range kernel {
+			kernel[i] /= sum
+		}
+	} else if sumpos != 0 {
+		for i := range kernel {
+			kernel[i] /= sumpos
+		}
+	}
+}

+ 7 - 0
vendor/github.com/disintegration/imaging/doc.go

@@ -0,0 +1,7 @@
+/*
+Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.).
+
+All the image processing functions provided by the package accept any image type that implements image.Image interface
+as an input, and return a new image of *image.NRGBA type (32bit RGBA colors, non-premultiplied alpha).
+*/
+package imaging

+ 169 - 0
vendor/github.com/disintegration/imaging/effects.go

@@ -0,0 +1,169 @@
+package imaging
+
+import (
+	"image"
+	"math"
+)
+
+func gaussianBlurKernel(x, sigma float64) float64 {
+	return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi))
+}
+
+// Blur produces a blurred version of the image using a Gaussian function.
+// Sigma parameter must be positive and indicates how much the image will be blurred.
+//
+// Example:
+//
+//	dstImage := imaging.Blur(srcImage, 3.5)
+//
+func Blur(img image.Image, sigma float64) *image.NRGBA {
+	if sigma <= 0 {
+		return Clone(img)
+	}
+
+	radius := int(math.Ceil(sigma * 3.0))
+	kernel := make([]float64, radius+1)
+
+	for i := 0; i <= radius; i++ {
+		kernel[i] = gaussianBlurKernel(float64(i), sigma)
+	}
+
+	return blurVertical(blurHorizontal(img, kernel), kernel)
+}
+
+func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA {
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+	radius := len(kernel) - 1
+
+	parallel(0, src.h, func(ys <-chan int) {
+		scanLine := make([]uint8, src.w*4)
+		scanLineF := make([]float64, len(scanLine))
+		for y := range ys {
+			src.scan(0, y, src.w, y+1, scanLine)
+			for i, v := range scanLine {
+				scanLineF[i] = float64(v)
+			}
+			for x := 0; x < src.w; x++ {
+				min := x - radius
+				if min < 0 {
+					min = 0
+				}
+				max := x + radius
+				if max > src.w-1 {
+					max = src.w - 1
+				}
+				var r, g, b, a, wsum float64
+				for ix := min; ix <= max; ix++ {
+					i := ix * 4
+					weight := kernel[absint(x-ix)]
+					wsum += weight
+					s := scanLineF[i : i+4 : i+4]
+					wa := s[3] * weight
+					r += s[0] * wa
+					g += s[1] * wa
+					b += s[2] * wa
+					a += wa
+				}
+				if a != 0 {
+					aInv := 1 / a
+					j := y*dst.Stride + x*4
+					d := dst.Pix[j : j+4 : j+4]
+					d[0] = clamp(r * aInv)
+					d[1] = clamp(g * aInv)
+					d[2] = clamp(b * aInv)
+					d[3] = clamp(a / wsum)
+				}
+			}
+		}
+	})
+
+	return dst
+}
+
+func blurVertical(img image.Image, kernel []float64) *image.NRGBA {
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+	radius := len(kernel) - 1
+
+	parallel(0, src.w, func(xs <-chan int) {
+		scanLine := make([]uint8, src.h*4)
+		scanLineF := make([]float64, len(scanLine))
+		for x := range xs {
+			src.scan(x, 0, x+1, src.h, scanLine)
+			for i, v := range scanLine {
+				scanLineF[i] = float64(v)
+			}
+			for y := 0; y < src.h; y++ {
+				min := y - radius
+				if min < 0 {
+					min = 0
+				}
+				max := y + radius
+				if max > src.h-1 {
+					max = src.h - 1
+				}
+				var r, g, b, a, wsum float64
+				for iy := min; iy <= max; iy++ {
+					i := iy * 4
+					weight := kernel[absint(y-iy)]
+					wsum += weight
+					s := scanLineF[i : i+4 : i+4]
+					wa := s[3] * weight
+					r += s[0] * wa
+					g += s[1] * wa
+					b += s[2] * wa
+					a += wa
+				}
+				if a != 0 {
+					aInv := 1 / a
+					j := y*dst.Stride + x*4
+					d := dst.Pix[j : j+4 : j+4]
+					d[0] = clamp(r * aInv)
+					d[1] = clamp(g * aInv)
+					d[2] = clamp(b * aInv)
+					d[3] = clamp(a / wsum)
+				}
+			}
+		}
+	})
+
+	return dst
+}
+
+// Sharpen produces a sharpened version of the image.
+// Sigma parameter must be positive and indicates how much the image will be sharpened.
+//
+// Example:
+//
+//	dstImage := imaging.Sharpen(srcImage, 3.5)
+//
+func Sharpen(img image.Image, sigma float64) *image.NRGBA {
+	if sigma <= 0 {
+		return Clone(img)
+	}
+
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+	blurred := Blur(img, sigma)
+
+	parallel(0, src.h, func(ys <-chan int) {
+		scanLine := make([]uint8, src.w*4)
+		for y := range ys {
+			src.scan(0, y, src.w, y+1, scanLine)
+			j := y * dst.Stride
+			for i := 0; i < src.w*4; i++ {
+				val := int(scanLine[i])<<1 - int(blurred.Pix[j])
+				if val < 0 {
+					val = 0
+				} else if val > 0xff {
+					val = 0xff
+				}
+				dst.Pix[j] = uint8(val)
+				j++
+			}
+		}
+	})
+
+	return dst
+}

+ 3 - 0
vendor/github.com/disintegration/imaging/go.mod

@@ -0,0 +1,3 @@
+module github.com/disintegration/imaging
+
+require golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81

+ 2 - 0
vendor/github.com/disintegration/imaging/go.sum

@@ -0,0 +1,2 @@
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=

+ 52 - 0
vendor/github.com/disintegration/imaging/histogram.go

@@ -0,0 +1,52 @@
+package imaging
+
+import (
+	"image"
+	"sync"
+)
+
+// Histogram returns a normalized histogram of an image.
+//
+// Resulting histogram is represented as an array of 256 floats, where
+// histogram[i] is a probability of a pixel being of a particular luminance i.
+func Histogram(img image.Image) [256]float64 {
+	var mu sync.Mutex
+	var histogram [256]float64
+	var total float64
+
+	src := newScanner(img)
+	if src.w == 0 || src.h == 0 {
+		return histogram
+	}
+
+	parallel(0, src.h, func(ys <-chan int) {
+		var tmpHistogram [256]float64
+		var tmpTotal float64
+		scanLine := make([]uint8, src.w*4)
+		for y := range ys {
+			src.scan(0, y, src.w, y+1, scanLine)
+			i := 0
+			for x := 0; x < src.w; x++ {
+				s := scanLine[i : i+3 : i+3]
+				r := s[0]
+				g := s[1]
+				b := s[2]
+				y := 0.299*float32(r) + 0.587*float32(g) + 0.114*float32(b)
+				tmpHistogram[int(y+0.5)]++
+				tmpTotal++
+				i += 4
+			}
+		}
+		mu.Lock()
+		for i := 0; i < 256; i++ {
+			histogram[i] += tmpHistogram[i]
+		}
+		total += tmpTotal
+		mu.Unlock()
+	})
+
+	for i := 0; i < 256; i++ {
+		histogram[i] = histogram[i] / total
+	}
+	return histogram
+}

+ 444 - 0
vendor/github.com/disintegration/imaging/io.go

@@ -0,0 +1,444 @@
+package imaging
+
+import (
+	"encoding/binary"
+	"errors"
+	"image"
+	"image/draw"
+	"image/gif"
+	"image/jpeg"
+	"image/png"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"golang.org/x/image/bmp"
+	"golang.org/x/image/tiff"
+)
+
+type fileSystem interface {
+	Create(string) (io.WriteCloser, error)
+	Open(string) (io.ReadCloser, error)
+}
+
+type localFS struct{}
+
+func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) }
+func (localFS) Open(name string) (io.ReadCloser, error)    { return os.Open(name) }
+
+var fs fileSystem = localFS{}
+
+type decodeConfig struct {
+	autoOrientation bool
+}
+
+var defaultDecodeConfig = decodeConfig{
+	autoOrientation: false,
+}
+
+// DecodeOption sets an optional parameter for the Decode and Open functions.
+type DecodeOption func(*decodeConfig)
+
+// AutoOrientation returns a DecodeOption that sets the auto-orientation mode.
+// If auto-orientation is enabled, the image will be transformed after decoding
+// according to the EXIF orientation tag (if present). By default it's disabled.
+func AutoOrientation(enabled bool) DecodeOption {
+	return func(c *decodeConfig) {
+		c.autoOrientation = enabled
+	}
+}
+
+// Decode reads an image from r.
+func Decode(r io.Reader, opts ...DecodeOption) (image.Image, error) {
+	cfg := defaultDecodeConfig
+	for _, option := range opts {
+		option(&cfg)
+	}
+
+	if !cfg.autoOrientation {
+		img, _, err := image.Decode(r)
+		return img, err
+	}
+
+	var orient orientation
+	pr, pw := io.Pipe()
+	r = io.TeeReader(r, pw)
+	done := make(chan struct{})
+	go func() {
+		defer close(done)
+		orient = readOrientation(pr)
+		io.Copy(ioutil.Discard, pr)
+	}()
+
+	img, _, err := image.Decode(r)
+	pw.Close()
+	<-done
+	if err != nil {
+		return nil, err
+	}
+
+	return fixOrientation(img, orient), nil
+}
+
+// Open loads an image from file.
+//
+// Examples:
+//
+//	// Load an image from file.
+//	img, err := imaging.Open("test.jpg")
+//
+//	// Load an image and transform it depending on the EXIF orientation tag (if present).
+//	img, err := imaging.Open("test.jpg", imaging.AutoOrientation(true))
+//
+func Open(filename string, opts ...DecodeOption) (image.Image, error) {
+	file, err := fs.Open(filename)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+	return Decode(file, opts...)
+}
+
+// Format is an image file format.
+type Format int
+
+// Image file formats.
+const (
+	JPEG Format = iota
+	PNG
+	GIF
+	TIFF
+	BMP
+)
+
+var formatExts = map[string]Format{
+	"jpg":  JPEG,
+	"jpeg": JPEG,
+	"png":  PNG,
+	"gif":  GIF,
+	"tif":  TIFF,
+	"tiff": TIFF,
+	"bmp":  BMP,
+}
+
+var formatNames = map[Format]string{
+	JPEG: "JPEG",
+	PNG:  "PNG",
+	GIF:  "GIF",
+	TIFF: "TIFF",
+	BMP:  "BMP",
+}
+
+func (f Format) String() string {
+	return formatNames[f]
+}
+
+// ErrUnsupportedFormat means the given image format is not supported.
+var ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
+
+// FormatFromExtension parses image format from filename extension:
+// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
+func FormatFromExtension(ext string) (Format, error) {
+	if f, ok := formatExts[strings.ToLower(strings.TrimPrefix(ext, "."))]; ok {
+		return f, nil
+	}
+	return -1, ErrUnsupportedFormat
+}
+
+// FormatFromFilename parses image format from filename:
+// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
+func FormatFromFilename(filename string) (Format, error) {
+	ext := filepath.Ext(filename)
+	return FormatFromExtension(ext)
+}
+
+type encodeConfig struct {
+	jpegQuality         int
+	gifNumColors        int
+	gifQuantizer        draw.Quantizer
+	gifDrawer           draw.Drawer
+	pngCompressionLevel png.CompressionLevel
+}
+
+var defaultEncodeConfig = encodeConfig{
+	jpegQuality:         95,
+	gifNumColors:        256,
+	gifQuantizer:        nil,
+	gifDrawer:           nil,
+	pngCompressionLevel: png.DefaultCompression,
+}
+
+// EncodeOption sets an optional parameter for the Encode and Save functions.
+type EncodeOption func(*encodeConfig)
+
+// JPEGQuality returns an EncodeOption that sets the output JPEG quality.
+// Quality ranges from 1 to 100 inclusive, higher is better. Default is 95.
+func JPEGQuality(quality int) EncodeOption {
+	return func(c *encodeConfig) {
+		c.jpegQuality = quality
+	}
+}
+
+// GIFNumColors returns an EncodeOption that sets the maximum number of colors
+// used in the GIF-encoded image. It ranges from 1 to 256.  Default is 256.
+func GIFNumColors(numColors int) EncodeOption {
+	return func(c *encodeConfig) {
+		c.gifNumColors = numColors
+	}
+}
+
+// GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce
+// a palette of the GIF-encoded image.
+func GIFQuantizer(quantizer draw.Quantizer) EncodeOption {
+	return func(c *encodeConfig) {
+		c.gifQuantizer = quantizer
+	}
+}
+
+// GIFDrawer returns an EncodeOption that sets the drawer that is used to convert
+// the source image to the desired palette of the GIF-encoded image.
+func GIFDrawer(drawer draw.Drawer) EncodeOption {
+	return func(c *encodeConfig) {
+		c.gifDrawer = drawer
+	}
+}
+
+// PNGCompressionLevel returns an EncodeOption that sets the compression level
+// of the PNG-encoded image. Default is png.DefaultCompression.
+func PNGCompressionLevel(level png.CompressionLevel) EncodeOption {
+	return func(c *encodeConfig) {
+		c.pngCompressionLevel = level
+	}
+}
+
+// Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
+func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error {
+	cfg := defaultEncodeConfig
+	for _, option := range opts {
+		option(&cfg)
+	}
+
+	switch format {
+	case JPEG:
+		if nrgba, ok := img.(*image.NRGBA); ok && nrgba.Opaque() {
+			rgba := &image.RGBA{
+				Pix:    nrgba.Pix,
+				Stride: nrgba.Stride,
+				Rect:   nrgba.Rect,
+			}
+			return jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality})
+		}
+		return jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality})
+
+	case PNG:
+		encoder := png.Encoder{CompressionLevel: cfg.pngCompressionLevel}
+		return encoder.Encode(w, img)
+
+	case GIF:
+		return gif.Encode(w, img, &gif.Options{
+			NumColors: cfg.gifNumColors,
+			Quantizer: cfg.gifQuantizer,
+			Drawer:    cfg.gifDrawer,
+		})
+
+	case TIFF:
+		return tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
+
+	case BMP:
+		return bmp.Encode(w, img)
+	}
+
+	return ErrUnsupportedFormat
+}
+
+// Save saves the image to file with the specified filename.
+// The format is determined from the filename extension:
+// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
+//
+// Examples:
+//
+//	// Save the image as PNG.
+//	err := imaging.Save(img, "out.png")
+//
+//	// Save the image as JPEG with optional quality parameter set to 80.
+//	err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80))
+//
+func Save(img image.Image, filename string, opts ...EncodeOption) (err error) {
+	f, err := FormatFromFilename(filename)
+	if err != nil {
+		return err
+	}
+	file, err := fs.Create(filename)
+	if err != nil {
+		return err
+	}
+	err = Encode(file, img, f, opts...)
+	errc := file.Close()
+	if err == nil {
+		err = errc
+	}
+	return err
+}
+
+// orientation is an EXIF flag that specifies the transformation
+// that should be applied to image to display it correctly.
+type orientation int
+
+const (
+	orientationUnspecified = 0
+	orientationNormal      = 1
+	orientationFlipH       = 2
+	orientationRotate180   = 3
+	orientationFlipV       = 4
+	orientationTranspose   = 5
+	orientationRotate270   = 6
+	orientationTransverse  = 7
+	orientationRotate90    = 8
+)
+
+// readOrientation tries to read the orientation EXIF flag from image data in r.
+// If the EXIF data block is not found or the orientation flag is not found
+// or any other error occures while reading the data, it returns the
+// orientationUnspecified (0) value.
+func readOrientation(r io.Reader) orientation {
+	const (
+		markerSOI      = 0xffd8
+		markerAPP1     = 0xffe1
+		exifHeader     = 0x45786966
+		byteOrderBE    = 0x4d4d
+		byteOrderLE    = 0x4949
+		orientationTag = 0x0112
+	)
+
+	// Check if JPEG SOI marker is present.
+	var soi uint16
+	if err := binary.Read(r, binary.BigEndian, &soi); err != nil {
+		return orientationUnspecified
+	}
+	if soi != markerSOI {
+		return orientationUnspecified // Missing JPEG SOI marker.
+	}
+
+	// Find JPEG APP1 marker.
+	for {
+		var marker, size uint16
+		if err := binary.Read(r, binary.BigEndian, &marker); err != nil {
+			return orientationUnspecified
+		}
+		if err := binary.Read(r, binary.BigEndian, &size); err != nil {
+			return orientationUnspecified
+		}
+		if marker>>8 != 0xff {
+			return orientationUnspecified // Invalid JPEG marker.
+		}
+		if marker == markerAPP1 {
+			break
+		}
+		if size < 2 {
+			return orientationUnspecified // Invalid block size.
+		}
+		if _, err := io.CopyN(ioutil.Discard, r, int64(size-2)); err != nil {
+			return orientationUnspecified
+		}
+	}
+
+	// Check if EXIF header is present.
+	var header uint32
+	if err := binary.Read(r, binary.BigEndian, &header); err != nil {
+		return orientationUnspecified
+	}
+	if header != exifHeader {
+		return orientationUnspecified
+	}
+	if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
+		return orientationUnspecified
+	}
+
+	// Read byte order information.
+	var (
+		byteOrderTag uint16
+		byteOrder    binary.ByteOrder
+	)
+	if err := binary.Read(r, binary.BigEndian, &byteOrderTag); err != nil {
+		return orientationUnspecified
+	}
+	switch byteOrderTag {
+	case byteOrderBE:
+		byteOrder = binary.BigEndian
+	case byteOrderLE:
+		byteOrder = binary.LittleEndian
+	default:
+		return orientationUnspecified // Invalid byte order flag.
+	}
+	if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
+		return orientationUnspecified
+	}
+
+	// Skip the EXIF offset.
+	var offset uint32
+	if err := binary.Read(r, byteOrder, &offset); err != nil {
+		return orientationUnspecified
+	}
+	if offset < 8 {
+		return orientationUnspecified // Invalid offset value.
+	}
+	if _, err := io.CopyN(ioutil.Discard, r, int64(offset-8)); err != nil {
+		return orientationUnspecified
+	}
+
+	// Read the number of tags.
+	var numTags uint16
+	if err := binary.Read(r, byteOrder, &numTags); err != nil {
+		return orientationUnspecified
+	}
+
+	// Find the orientation tag.
+	for i := 0; i < int(numTags); i++ {
+		var tag uint16
+		if err := binary.Read(r, byteOrder, &tag); err != nil {
+			return orientationUnspecified
+		}
+		if tag != orientationTag {
+			if _, err := io.CopyN(ioutil.Discard, r, 10); err != nil {
+				return orientationUnspecified
+			}
+			continue
+		}
+		if _, err := io.CopyN(ioutil.Discard, r, 6); err != nil {
+			return orientationUnspecified
+		}
+		var val uint16
+		if err := binary.Read(r, byteOrder, &val); err != nil {
+			return orientationUnspecified
+		}
+		if val < 1 || val > 8 {
+			return orientationUnspecified // Invalid tag value.
+		}
+		return orientation(val)
+	}
+	return orientationUnspecified // Missing orientation tag.
+}
+
+// fixOrientation applies a transform to img corresponding to the given orientation flag.
+func fixOrientation(img image.Image, o orientation) image.Image {
+	switch o {
+	case orientationNormal:
+	case orientationFlipH:
+		img = FlipH(img)
+	case orientationFlipV:
+		img = FlipV(img)
+	case orientationRotate90:
+		img = Rotate90(img)
+	case orientationRotate180:
+		img = Rotate180(img)
+	case orientationRotate270:
+		img = Rotate270(img)
+	case orientationTranspose:
+		img = Transpose(img)
+	case orientationTransverse:
+		img = Transverse(img)
+	}
+	return img
+}

+ 595 - 0
vendor/github.com/disintegration/imaging/resize.go

@@ -0,0 +1,595 @@
+package imaging
+
+import (
+	"image"
+	"math"
+)
+
+type indexWeight struct {
+	index  int
+	weight float64
+}
+
+func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) [][]indexWeight {
+	du := float64(srcSize) / float64(dstSize)
+	scale := du
+	if scale < 1.0 {
+		scale = 1.0
+	}
+	ru := math.Ceil(scale * filter.Support)
+
+	out := make([][]indexWeight, dstSize)
+	tmp := make([]indexWeight, 0, dstSize*int(ru+2)*2)
+
+	for v := 0; v < dstSize; v++ {
+		fu := (float64(v)+0.5)*du - 0.5
+
+		begin := int(math.Ceil(fu - ru))
+		if begin < 0 {
+			begin = 0
+		}
+		end := int(math.Floor(fu + ru))
+		if end > srcSize-1 {
+			end = srcSize - 1
+		}
+
+		var sum float64
+		for u := begin; u <= end; u++ {
+			w := filter.Kernel((float64(u) - fu) / scale)
+			if w != 0 {
+				sum += w
+				tmp = append(tmp, indexWeight{index: u, weight: w})
+			}
+		}
+		if sum != 0 {
+			for i := range tmp {
+				tmp[i].weight /= sum
+			}
+		}
+
+		out[v] = tmp
+		tmp = tmp[len(tmp):]
+	}
+
+	return out
+}
+
+// Resize resizes the image to the specified width and height using the specified resampling
+// filter and returns the transformed image. If one of width or height is 0, the image aspect
+// ratio is preserved.
+//
+// Example:
+//
+//	dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos)
+//
+func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
+	dstW, dstH := width, height
+	if dstW < 0 || dstH < 0 {
+		return &image.NRGBA{}
+	}
+	if dstW == 0 && dstH == 0 {
+		return &image.NRGBA{}
+	}
+
+	srcW := img.Bounds().Dx()
+	srcH := img.Bounds().Dy()
+	if srcW <= 0 || srcH <= 0 {
+		return &image.NRGBA{}
+	}
+
+	// If new width or height is 0 then preserve aspect ratio, minimum 1px.
+	if dstW == 0 {
+		tmpW := float64(dstH) * float64(srcW) / float64(srcH)
+		dstW = int(math.Max(1.0, math.Floor(tmpW+0.5)))
+	}
+	if dstH == 0 {
+		tmpH := float64(dstW) * float64(srcH) / float64(srcW)
+		dstH = int(math.Max(1.0, math.Floor(tmpH+0.5)))
+	}
+
+	if filter.Support <= 0 {
+		// Nearest-neighbor special case.
+		return resizeNearest(img, dstW, dstH)
+	}
+
+	if srcW != dstW && srcH != dstH {
+		return resizeVertical(resizeHorizontal(img, dstW, filter), dstH, filter)
+	}
+	if srcW != dstW {
+		return resizeHorizontal(img, dstW, filter)
+	}
+	if srcH != dstH {
+		return resizeVertical(img, dstH, filter)
+	}
+	return Clone(img)
+}
+
+func resizeHorizontal(img image.Image, width int, filter ResampleFilter) *image.NRGBA {
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, width, src.h))
+	weights := precomputeWeights(width, src.w, filter)
+	parallel(0, src.h, func(ys <-chan int) {
+		scanLine := make([]uint8, src.w*4)
+		for y := range ys {
+			src.scan(0, y, src.w, y+1, scanLine)
+			j0 := y * dst.Stride
+			for x := range weights {
+				var r, g, b, a float64
+				for _, w := range weights[x] {
+					i := w.index * 4
+					s := scanLine[i : i+4 : i+4]
+					aw := float64(s[3]) * w.weight
+					r += float64(s[0]) * aw
+					g += float64(s[1]) * aw
+					b += float64(s[2]) * aw
+					a += aw
+				}
+				if a != 0 {
+					aInv := 1 / a
+					j := j0 + x*4
+					d := dst.Pix[j : j+4 : j+4]
+					d[0] = clamp(r * aInv)
+					d[1] = clamp(g * aInv)
+					d[2] = clamp(b * aInv)
+					d[3] = clamp(a)
+				}
+			}
+		}
+	})
+	return dst
+}
+
+func resizeVertical(img image.Image, height int, filter ResampleFilter) *image.NRGBA {
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, src.w, height))
+	weights := precomputeWeights(height, src.h, filter)
+	parallel(0, src.w, func(xs <-chan int) {
+		scanLine := make([]uint8, src.h*4)
+		for x := range xs {
+			src.scan(x, 0, x+1, src.h, scanLine)
+			for y := range weights {
+				var r, g, b, a float64
+				for _, w := range weights[y] {
+					i := w.index * 4
+					s := scanLine[i : i+4 : i+4]
+					aw := float64(s[3]) * w.weight
+					r += float64(s[0]) * aw
+					g += float64(s[1]) * aw
+					b += float64(s[2]) * aw
+					a += aw
+				}
+				if a != 0 {
+					aInv := 1 / a
+					j := y*dst.Stride + x*4
+					d := dst.Pix[j : j+4 : j+4]
+					d[0] = clamp(r * aInv)
+					d[1] = clamp(g * aInv)
+					d[2] = clamp(b * aInv)
+					d[3] = clamp(a)
+				}
+			}
+		}
+	})
+	return dst
+}
+
+// resizeNearest is a fast nearest-neighbor resize, no filtering.
+func resizeNearest(img image.Image, width, height int) *image.NRGBA {
+	dst := image.NewNRGBA(image.Rect(0, 0, width, height))
+	dx := float64(img.Bounds().Dx()) / float64(width)
+	dy := float64(img.Bounds().Dy()) / float64(height)
+
+	if dx > 1 && dy > 1 {
+		src := newScanner(img)
+		parallel(0, height, func(ys <-chan int) {
+			for y := range ys {
+				srcY := int((float64(y) + 0.5) * dy)
+				dstOff := y * dst.Stride
+				for x := 0; x < width; x++ {
+					srcX := int((float64(x) + 0.5) * dx)
+					src.scan(srcX, srcY, srcX+1, srcY+1, dst.Pix[dstOff:dstOff+4])
+					dstOff += 4
+				}
+			}
+		})
+	} else {
+		src := toNRGBA(img)
+		parallel(0, height, func(ys <-chan int) {
+			for y := range ys {
+				srcY := int((float64(y) + 0.5) * dy)
+				srcOff0 := srcY * src.Stride
+				dstOff := y * dst.Stride
+				for x := 0; x < width; x++ {
+					srcX := int((float64(x) + 0.5) * dx)
+					srcOff := srcOff0 + srcX*4
+					copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
+					dstOff += 4
+				}
+			}
+		})
+	}
+
+	return dst
+}
+
+// Fit scales down the image using the specified resample filter to fit the specified
+// maximum width and height and returns the transformed image.
+//
+// Example:
+//
+//	dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
+//
+func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
+	maxW, maxH := width, height
+
+	if maxW <= 0 || maxH <= 0 {
+		return &image.NRGBA{}
+	}
+
+	srcBounds := img.Bounds()
+	srcW := srcBounds.Dx()
+	srcH := srcBounds.Dy()
+
+	if srcW <= 0 || srcH <= 0 {
+		return &image.NRGBA{}
+	}
+
+	if srcW <= maxW && srcH <= maxH {
+		return Clone(img)
+	}
+
+	srcAspectRatio := float64(srcW) / float64(srcH)
+	maxAspectRatio := float64(maxW) / float64(maxH)
+
+	var newW, newH int
+	if srcAspectRatio > maxAspectRatio {
+		newW = maxW
+		newH = int(float64(newW) / srcAspectRatio)
+	} else {
+		newH = maxH
+		newW = int(float64(newH) * srcAspectRatio)
+	}
+
+	return Resize(img, newW, newH, filter)
+}
+
+// Fill creates an image with the specified dimensions and fills it with the scaled source image.
+// To achieve the correct aspect ratio without stretching, the source image will be cropped.
+//
+// Example:
+//
+//	dstImage := imaging.Fill(srcImage, 800, 600, imaging.Center, imaging.Lanczos)
+//
+func Fill(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
+	dstW, dstH := width, height
+
+	if dstW <= 0 || dstH <= 0 {
+		return &image.NRGBA{}
+	}
+
+	srcBounds := img.Bounds()
+	srcW := srcBounds.Dx()
+	srcH := srcBounds.Dy()
+
+	if srcW <= 0 || srcH <= 0 {
+		return &image.NRGBA{}
+	}
+
+	if srcW == dstW && srcH == dstH {
+		return Clone(img)
+	}
+
+	if srcW >= 100 && srcH >= 100 {
+		return cropAndResize(img, dstW, dstH, anchor, filter)
+	}
+	return resizeAndCrop(img, dstW, dstH, anchor, filter)
+}
+
+// cropAndResize crops the image to the smallest possible size that has the required aspect ratio using
+// the given anchor point, then scales it to the specified dimensions and returns the transformed image.
+//
+// This is generally faster than resizing first, but may result in inaccuracies when used on small source images.
+func cropAndResize(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
+	dstW, dstH := width, height
+
+	srcBounds := img.Bounds()
+	srcW := srcBounds.Dx()
+	srcH := srcBounds.Dy()
+	srcAspectRatio := float64(srcW) / float64(srcH)
+	dstAspectRatio := float64(dstW) / float64(dstH)
+
+	var tmp *image.NRGBA
+	if srcAspectRatio < dstAspectRatio {
+		cropH := float64(srcW) * float64(dstH) / float64(dstW)
+		tmp = CropAnchor(img, srcW, int(math.Max(1, cropH)+0.5), anchor)
+	} else {
+		cropW := float64(srcH) * float64(dstW) / float64(dstH)
+		tmp = CropAnchor(img, int(math.Max(1, cropW)+0.5), srcH, anchor)
+	}
+
+	return Resize(tmp, dstW, dstH, filter)
+}
+
+// resizeAndCrop resizes the image to the smallest possible size that will cover the specified dimensions,
+// crops the resized image to the specified dimensions using the given anchor point and returns
+// the transformed image.
+func resizeAndCrop(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
+	dstW, dstH := width, height
+
+	srcBounds := img.Bounds()
+	srcW := srcBounds.Dx()
+	srcH := srcBounds.Dy()
+	srcAspectRatio := float64(srcW) / float64(srcH)
+	dstAspectRatio := float64(dstW) / float64(dstH)
+
+	var tmp *image.NRGBA
+	if srcAspectRatio < dstAspectRatio {
+		tmp = Resize(img, dstW, 0, filter)
+	} else {
+		tmp = Resize(img, 0, dstH, filter)
+	}
+
+	return CropAnchor(tmp, dstW, dstH, anchor)
+}
+
+// Thumbnail scales the image up or down using the specified resample filter, crops it
+// to the specified width and hight and returns the transformed image.
+//
+// Example:
+//
+//	dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos)
+//
+func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
+	return Fill(img, width, height, Center, filter)
+}
+
+// ResampleFilter specifies a resampling filter to be used for image resizing.
+//
+//	General filter recommendations:
+//
+//	- Lanczos
+//		A high-quality resampling filter for photographic images yielding sharp results.
+//
+//	- CatmullRom
+//		A sharp cubic filter that is faster than Lanczos filter while providing similar results.
+//
+//	- MitchellNetravali
+//		A cubic filter that produces smoother results with less ringing artifacts than CatmullRom.
+//
+//	- Linear
+//		Bilinear resampling filter, produces a smooth output. Faster than cubic filters.
+//
+//	- Box
+//		Simple and fast averaging filter appropriate for downscaling.
+//		When upscaling it's similar to NearestNeighbor.
+//
+//	- NearestNeighbor
+//		Fastest resampling filter, no antialiasing.
+//
+type ResampleFilter struct {
+	Support float64
+	Kernel  func(float64) float64
+}
+
+// NearestNeighbor is a nearest-neighbor filter (no anti-aliasing).
+var NearestNeighbor ResampleFilter
+
+// Box filter (averaging pixels).
+var Box ResampleFilter
+
+// Linear filter.
+var Linear ResampleFilter
+
+// Hermite cubic spline filter (BC-spline; B=0; C=0).
+var Hermite ResampleFilter
+
+// MitchellNetravali is Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
+var MitchellNetravali ResampleFilter
+
+// CatmullRom is a Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
+var CatmullRom ResampleFilter
+
+// BSpline is a smooth cubic filter (BC-spline; B=1; C=0).
+var BSpline ResampleFilter
+
+// Gaussian is a Gaussian blurring filter.
+var Gaussian ResampleFilter
+
+// Bartlett is a Bartlett-windowed sinc filter (3 lobes).
+var Bartlett ResampleFilter
+
+// Lanczos filter (3 lobes).
+var Lanczos ResampleFilter
+
+// Hann is a Hann-windowed sinc filter (3 lobes).
+var Hann ResampleFilter
+
+// Hamming is a Hamming-windowed sinc filter (3 lobes).
+var Hamming ResampleFilter
+
+// Blackman is a Blackman-windowed sinc filter (3 lobes).
+var Blackman ResampleFilter
+
+// Welch is a Welch-windowed sinc filter (parabolic window, 3 lobes).
+var Welch ResampleFilter
+
+// Cosine is a Cosine-windowed sinc filter (3 lobes).
+var Cosine ResampleFilter
+
+func bcspline(x, b, c float64) float64 {
+	var y float64
+	x = math.Abs(x)
+	if x < 1.0 {
+		y = ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
+	} else if x < 2.0 {
+		y = ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
+	}
+	return y
+}
+
+func sinc(x float64) float64 {
+	if x == 0 {
+		return 1
+	}
+	return math.Sin(math.Pi*x) / (math.Pi * x)
+}
+
+func init() {
+	NearestNeighbor = ResampleFilter{
+		Support: 0.0, // special case - not applying the filter
+	}
+
+	Box = ResampleFilter{
+		Support: 0.5,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x <= 0.5 {
+				return 1.0
+			}
+			return 0
+		},
+	}
+
+	Linear = ResampleFilter{
+		Support: 1.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 1.0 {
+				return 1.0 - x
+			}
+			return 0
+		},
+	}
+
+	Hermite = ResampleFilter{
+		Support: 1.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 1.0 {
+				return bcspline(x, 0.0, 0.0)
+			}
+			return 0
+		},
+	}
+
+	MitchellNetravali = ResampleFilter{
+		Support: 2.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 2.0 {
+				return bcspline(x, 1.0/3.0, 1.0/3.0)
+			}
+			return 0
+		},
+	}
+
+	CatmullRom = ResampleFilter{
+		Support: 2.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 2.0 {
+				return bcspline(x, 0.0, 0.5)
+			}
+			return 0
+		},
+	}
+
+	BSpline = ResampleFilter{
+		Support: 2.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 2.0 {
+				return bcspline(x, 1.0, 0.0)
+			}
+			return 0
+		},
+	}
+
+	Gaussian = ResampleFilter{
+		Support: 2.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 2.0 {
+				return math.Exp(-2 * x * x)
+			}
+			return 0
+		},
+	}
+
+	Bartlett = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (3.0 - x) / 3.0
+			}
+			return 0
+		},
+	}
+
+	Lanczos = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * sinc(x/3.0)
+			}
+			return 0
+		},
+	}
+
+	Hann = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0))
+			}
+			return 0
+		},
+	}
+
+	Hamming = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0))
+			}
+			return 0
+		},
+	}
+
+	Blackman = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0))
+			}
+			return 0
+		},
+	}
+
+	Welch = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (1.0 - (x * x / 9.0))
+			}
+			return 0
+		},
+	}
+
+	Cosine = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0))
+			}
+			return 0
+		},
+	}
+}

+ 285 - 0
vendor/github.com/disintegration/imaging/scanner.go

@@ -0,0 +1,285 @@
+package imaging
+
+import (
+	"image"
+	"image/color"
+)
+
+type scanner struct {
+	image   image.Image
+	w, h    int
+	palette []color.NRGBA
+}
+
+func newScanner(img image.Image) *scanner {
+	s := &scanner{
+		image: img,
+		w:     img.Bounds().Dx(),
+		h:     img.Bounds().Dy(),
+	}
+	if img, ok := img.(*image.Paletted); ok {
+		s.palette = make([]color.NRGBA, len(img.Palette))
+		for i := 0; i < len(img.Palette); i++ {
+			s.palette[i] = color.NRGBAModel.Convert(img.Palette[i]).(color.NRGBA)
+		}
+	}
+	return s
+}
+
+// scan scans the given rectangular region of the image into dst.
+func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
+	switch img := s.image.(type) {
+	case *image.NRGBA:
+		size := (x2 - x1) * 4
+		j := 0
+		i := y1*img.Stride + x1*4
+		if size == 4 {
+			for y := y1; y < y2; y++ {
+				d := dst[j : j+4 : j+4]
+				s := img.Pix[i : i+4 : i+4]
+				d[0] = s[0]
+				d[1] = s[1]
+				d[2] = s[2]
+				d[3] = s[3]
+				j += size
+				i += img.Stride
+			}
+		} else {
+			for y := y1; y < y2; y++ {
+				copy(dst[j:j+size], img.Pix[i:i+size])
+				j += size
+				i += img.Stride
+			}
+		}
+
+	case *image.NRGBA64:
+		j := 0
+		for y := y1; y < y2; y++ {
+			i := y*img.Stride + x1*8
+			for x := x1; x < x2; x++ {
+				s := img.Pix[i : i+8 : i+8]
+				d := dst[j : j+4 : j+4]
+				d[0] = s[0]
+				d[1] = s[2]
+				d[2] = s[4]
+				d[3] = s[6]
+				j += 4
+				i += 8
+			}
+		}
+
+	case *image.RGBA:
+		j := 0
+		for y := y1; y < y2; y++ {
+			i := y*img.Stride + x1*4
+			for x := x1; x < x2; x++ {
+				d := dst[j : j+4 : j+4]
+				a := img.Pix[i+3]
+				switch a {
+				case 0:
+					d[0] = 0
+					d[1] = 0
+					d[2] = 0
+					d[3] = a
+				case 0xff:
+					s := img.Pix[i : i+4 : i+4]
+					d[0] = s[0]
+					d[1] = s[1]
+					d[2] = s[2]
+					d[3] = a
+				default:
+					s := img.Pix[i : i+4 : i+4]
+					r16 := uint16(s[0])
+					g16 := uint16(s[1])
+					b16 := uint16(s[2])
+					a16 := uint16(a)
+					d[0] = uint8(r16 * 0xff / a16)
+					d[1] = uint8(g16 * 0xff / a16)
+					d[2] = uint8(b16 * 0xff / a16)
+					d[3] = a
+				}
+				j += 4
+				i += 4
+			}
+		}
+
+	case *image.RGBA64:
+		j := 0
+		for y := y1; y < y2; y++ {
+			i := y*img.Stride + x1*8
+			for x := x1; x < x2; x++ {
+				s := img.Pix[i : i+8 : i+8]
+				d := dst[j : j+4 : j+4]
+				a := s[6]
+				switch a {
+				case 0:
+					d[0] = 0
+					d[1] = 0
+					d[2] = 0
+				case 0xff:
+					d[0] = s[0]
+					d[1] = s[2]
+					d[2] = s[4]
+				default:
+					r32 := uint32(s[0])<<8 | uint32(s[1])
+					g32 := uint32(s[2])<<8 | uint32(s[3])
+					b32 := uint32(s[4])<<8 | uint32(s[5])
+					a32 := uint32(s[6])<<8 | uint32(s[7])
+					d[0] = uint8((r32 * 0xffff / a32) >> 8)
+					d[1] = uint8((g32 * 0xffff / a32) >> 8)
+					d[2] = uint8((b32 * 0xffff / a32) >> 8)
+				}
+				d[3] = a
+				j += 4
+				i += 8
+			}
+		}
+
+	case *image.Gray:
+		j := 0
+		for y := y1; y < y2; y++ {
+			i := y*img.Stride + x1
+			for x := x1; x < x2; x++ {
+				c := img.Pix[i]
+				d := dst[j : j+4 : j+4]
+				d[0] = c
+				d[1] = c
+				d[2] = c
+				d[3] = 0xff
+				j += 4
+				i++
+			}
+		}
+
+	case *image.Gray16:
+		j := 0
+		for y := y1; y < y2; y++ {
+			i := y*img.Stride + x1*2
+			for x := x1; x < x2; x++ {
+				c := img.Pix[i]
+				d := dst[j : j+4 : j+4]
+				d[0] = c
+				d[1] = c
+				d[2] = c
+				d[3] = 0xff
+				j += 4
+				i += 2
+			}
+		}
+
+	case *image.YCbCr:
+		j := 0
+		x1 += img.Rect.Min.X
+		x2 += img.Rect.Min.X
+		y1 += img.Rect.Min.Y
+		y2 += img.Rect.Min.Y
+
+		hy := img.Rect.Min.Y / 2
+		hx := img.Rect.Min.X / 2
+		for y := y1; y < y2; y++ {
+			iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X)
+
+			var yBase int
+			switch img.SubsampleRatio {
+			case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio422:
+				yBase = (y - img.Rect.Min.Y) * img.CStride
+			case image.YCbCrSubsampleRatio420, image.YCbCrSubsampleRatio440:
+				yBase = (y/2 - hy) * img.CStride
+			}
+
+			for x := x1; x < x2; x++ {
+				var ic int
+				switch img.SubsampleRatio {
+				case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio440:
+					ic = yBase + (x - img.Rect.Min.X)
+				case image.YCbCrSubsampleRatio422, image.YCbCrSubsampleRatio420:
+					ic = yBase + (x/2 - hx)
+				default:
+					ic = img.COffset(x, y)
+				}
+
+				yy1 := int32(img.Y[iy]) * 0x10101
+				cb1 := int32(img.Cb[ic]) - 128
+				cr1 := int32(img.Cr[ic]) - 128
+
+				r := yy1 + 91881*cr1
+				if uint32(r)&0xff000000 == 0 {
+					r >>= 16
+				} else {
+					r = ^(r >> 31)
+				}
+
+				g := yy1 - 22554*cb1 - 46802*cr1
+				if uint32(g)&0xff000000 == 0 {
+					g >>= 16
+				} else {
+					g = ^(g >> 31)
+				}
+
+				b := yy1 + 116130*cb1
+				if uint32(b)&0xff000000 == 0 {
+					b >>= 16
+				} else {
+					b = ^(b >> 31)
+				}
+
+				d := dst[j : j+4 : j+4]
+				d[0] = uint8(r)
+				d[1] = uint8(g)
+				d[2] = uint8(b)
+				d[3] = 0xff
+
+				iy++
+				j += 4
+			}
+		}
+
+	case *image.Paletted:
+		j := 0
+		for y := y1; y < y2; y++ {
+			i := y*img.Stride + x1
+			for x := x1; x < x2; x++ {
+				c := s.palette[img.Pix[i]]
+				d := dst[j : j+4 : j+4]
+				d[0] = c.R
+				d[1] = c.G
+				d[2] = c.B
+				d[3] = c.A
+				j += 4
+				i++
+			}
+		}
+
+	default:
+		j := 0
+		b := s.image.Bounds()
+		x1 += b.Min.X
+		x2 += b.Min.X
+		y1 += b.Min.Y
+		y2 += b.Min.Y
+		for y := y1; y < y2; y++ {
+			for x := x1; x < x2; x++ {
+				r16, g16, b16, a16 := s.image.At(x, y).RGBA()
+				d := dst[j : j+4 : j+4]
+				switch a16 {
+				case 0xffff:
+					d[0] = uint8(r16 >> 8)
+					d[1] = uint8(g16 >> 8)
+					d[2] = uint8(b16 >> 8)
+					d[3] = 0xff
+				case 0:
+					d[0] = 0
+					d[1] = 0
+					d[2] = 0
+					d[3] = 0
+				default:
+					d[0] = uint8(((r16 * 0xffff) / a16) >> 8)
+					d[1] = uint8(((g16 * 0xffff) / a16) >> 8)
+					d[2] = uint8(((b16 * 0xffff) / a16) >> 8)
+					d[3] = uint8(a16 >> 8)
+				}
+				j += 4
+			}
+		}
+	}
+}

+ 249 - 0
vendor/github.com/disintegration/imaging/tools.go

@@ -0,0 +1,249 @@
+package imaging
+
+import (
+	"bytes"
+	"image"
+	"image/color"
+	"math"
+)
+
+// New creates a new image with the specified width and height, and fills it with the specified color.
+func New(width, height int, fillColor color.Color) *image.NRGBA {
+	if width <= 0 || height <= 0 {
+		return &image.NRGBA{}
+	}
+
+	c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
+	if (c == color.NRGBA{0, 0, 0, 0}) {
+		return image.NewNRGBA(image.Rect(0, 0, width, height))
+	}
+
+	return &image.NRGBA{
+		Pix:    bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height),
+		Stride: 4 * width,
+		Rect:   image.Rect(0, 0, width, height),
+	}
+}
+
+// Clone returns a copy of the given image.
+func Clone(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
+	size := src.w * 4
+	parallel(0, src.h, func(ys <-chan int) {
+		for y := range ys {
+			i := y * dst.Stride
+			src.scan(0, y, src.w, y+1, dst.Pix[i:i+size])
+		}
+	})
+	return dst
+}
+
+// Anchor is the anchor point for image alignment.
+type Anchor int
+
+// Anchor point positions.
+const (
+	Center Anchor = iota
+	TopLeft
+	Top
+	TopRight
+	Left
+	Right
+	BottomLeft
+	Bottom
+	BottomRight
+)
+
+func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point {
+	var x, y int
+	switch anchor {
+	case TopLeft:
+		x = b.Min.X
+		y = b.Min.Y
+	case Top:
+		x = b.Min.X + (b.Dx()-w)/2
+		y = b.Min.Y
+	case TopRight:
+		x = b.Max.X - w
+		y = b.Min.Y
+	case Left:
+		x = b.Min.X
+		y = b.Min.Y + (b.Dy()-h)/2
+	case Right:
+		x = b.Max.X - w
+		y = b.Min.Y + (b.Dy()-h)/2
+	case BottomLeft:
+		x = b.Min.X
+		y = b.Max.Y - h
+	case Bottom:
+		x = b.Min.X + (b.Dx()-w)/2
+		y = b.Max.Y - h
+	case BottomRight:
+		x = b.Max.X - w
+		y = b.Max.Y - h
+	default:
+		x = b.Min.X + (b.Dx()-w)/2
+		y = b.Min.Y + (b.Dy()-h)/2
+	}
+	return image.Pt(x, y)
+}
+
+// Crop cuts out a rectangular region with the specified bounds
+// from the image and returns the cropped image.
+func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
+	r := rect.Intersect(img.Bounds()).Sub(img.Bounds().Min)
+	if r.Empty() {
+		return &image.NRGBA{}
+	}
+	src := newScanner(img)
+	dst := image.NewNRGBA(image.Rect(0, 0, r.Dx(), r.Dy()))
+	rowSize := r.Dx() * 4
+	parallel(r.Min.Y, r.Max.Y, func(ys <-chan int) {
+		for y := range ys {
+			i := (y - r.Min.Y) * dst.Stride
+			src.scan(r.Min.X, y, r.Max.X, y+1, dst.Pix[i:i+rowSize])
+		}
+	})
+	return dst
+}
+
+// CropAnchor cuts out a rectangular region with the specified size
+// from the image using the specified anchor point and returns the cropped image.
+func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA {
+	srcBounds := img.Bounds()
+	pt := anchorPt(srcBounds, width, height, anchor)
+	r := image.Rect(0, 0, width, height).Add(pt)
+	b := srcBounds.Intersect(r)
+	return Crop(img, b)
+}
+
+// CropCenter cuts out a rectangular region with the specified size
+// from the center of the image and returns the cropped image.
+func CropCenter(img image.Image, width, height int) *image.NRGBA {
+	return CropAnchor(img, width, height, Center)
+}
+
+// Paste pastes the img image to the background image at the specified position and returns the combined image.
+func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
+	dst := Clone(background)
+	pos = pos.Sub(background.Bounds().Min)
+	pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
+	interRect := pasteRect.Intersect(dst.Bounds())
+	if interRect.Empty() {
+		return dst
+	}
+	src := newScanner(img)
+	parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
+		for y := range ys {
+			x1 := interRect.Min.X - pasteRect.Min.X
+			x2 := interRect.Max.X - pasteRect.Min.X
+			y1 := y - pasteRect.Min.Y
+			y2 := y1 + 1
+			i1 := y*dst.Stride + interRect.Min.X*4
+			i2 := i1 + interRect.Dx()*4
+			src.scan(x1, y1, x2, y2, dst.Pix[i1:i2])
+		}
+	})
+	return dst
+}
+
+// PasteCenter pastes the img image to the center of the background image and returns the combined image.
+func PasteCenter(background, img image.Image) *image.NRGBA {
+	bgBounds := background.Bounds()
+	bgW := bgBounds.Dx()
+	bgH := bgBounds.Dy()
+	bgMinX := bgBounds.Min.X
+	bgMinY := bgBounds.Min.Y
+
+	centerX := bgMinX + bgW/2
+	centerY := bgMinY + bgH/2
+
+	x0 := centerX - img.Bounds().Dx()/2
+	y0 := centerY - img.Bounds().Dy()/2
+
+	return Paste(background, img, image.Pt(x0, y0))
+}
+
+// Overlay draws the img image over the background image at given position
+// and returns the combined image. Opacity parameter is the opacity of the img
+// image layer, used to compose the images, it must be from 0.0 to 1.0.
+//
+// Examples:
+//
+//	// Draw spriteImage over backgroundImage at the given position (x=50, y=50).
+//	dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
+//
+//	// Blend two opaque images of the same size.
+//	dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
+//
+func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
+	opacity = math.Min(math.Max(opacity, 0.0), 1.0) // Ensure 0.0 <= opacity <= 1.0.
+	dst := Clone(background)
+	pos = pos.Sub(background.Bounds().Min)
+	pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
+	interRect := pasteRect.Intersect(dst.Bounds())
+	if interRect.Empty() {
+		return dst
+	}
+	src := newScanner(img)
+	parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
+		scanLine := make([]uint8, interRect.Dx()*4)
+		for y := range ys {
+			x1 := interRect.Min.X - pasteRect.Min.X
+			x2 := interRect.Max.X - pasteRect.Min.X
+			y1 := y - pasteRect.Min.Y
+			y2 := y1 + 1
+			src.scan(x1, y1, x2, y2, scanLine)
+			i := y*dst.Stride + interRect.Min.X*4
+			j := 0
+			for x := interRect.Min.X; x < interRect.Max.X; x++ {
+				d := dst.Pix[i : i+4 : i+4]
+				r1 := float64(d[0])
+				g1 := float64(d[1])
+				b1 := float64(d[2])
+				a1 := float64(d[3])
+
+				s := scanLine[j : j+4 : j+4]
+				r2 := float64(s[0])
+				g2 := float64(s[1])
+				b2 := float64(s[2])
+				a2 := float64(s[3])
+
+				coef2 := opacity * a2 / 255
+				coef1 := (1 - coef2) * a1 / 255
+				coefSum := coef1 + coef2
+				coef1 /= coefSum
+				coef2 /= coefSum
+
+				d[0] = uint8(r1*coef1 + r2*coef2)
+				d[1] = uint8(g1*coef1 + g2*coef2)
+				d[2] = uint8(b1*coef1 + b2*coef2)
+				d[3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255))
+
+				i += 4
+				j += 4
+			}
+		}
+	})
+	return dst
+}
+
+// OverlayCenter overlays the img image to the center of the background image and
+// returns the combined image. Opacity parameter is the opacity of the img
+// image layer, used to compose the images, it must be from 0.0 to 1.0.
+func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA {
+	bgBounds := background.Bounds()
+	bgW := bgBounds.Dx()
+	bgH := bgBounds.Dy()
+	bgMinX := bgBounds.Min.X
+	bgMinY := bgBounds.Min.Y
+
+	centerX := bgMinX + bgW/2
+	centerY := bgMinY + bgH/2
+
+	x0 := centerX - img.Bounds().Dx()/2
+	y0 := centerY - img.Bounds().Dy()/2
+
+	return Overlay(background, img, image.Point{x0, y0}, opacity)
+}

+ 268 - 0
vendor/github.com/disintegration/imaging/transform.go

@@ -0,0 +1,268 @@
+package imaging
+
+import (
+	"image"
+	"image/color"
+	"math"
+)
+
+// FlipH flips the image horizontally (from left to right) and returns the transformed image.
+func FlipH(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dstW := src.w
+	dstH := src.h
+	rowSize := dstW * 4
+	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+	parallel(0, dstH, func(ys <-chan int) {
+		for dstY := range ys {
+			i := dstY * dst.Stride
+			srcY := dstY
+			src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
+			reverse(dst.Pix[i : i+rowSize])
+		}
+	})
+	return dst
+}
+
+// FlipV flips the image vertically (from top to bottom) and returns the transformed image.
+func FlipV(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dstW := src.w
+	dstH := src.h
+	rowSize := dstW * 4
+	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+	parallel(0, dstH, func(ys <-chan int) {
+		for dstY := range ys {
+			i := dstY * dst.Stride
+			srcY := dstH - dstY - 1
+			src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
+		}
+	})
+	return dst
+}
+
+// Transpose flips the image horizontally and rotates 90 degrees counter-clockwise.
+func Transpose(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dstW := src.h
+	dstH := src.w
+	rowSize := dstW * 4
+	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+	parallel(0, dstH, func(ys <-chan int) {
+		for dstY := range ys {
+			i := dstY * dst.Stride
+			srcX := dstY
+			src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
+		}
+	})
+	return dst
+}
+
+// Transverse flips the image vertically and rotates 90 degrees counter-clockwise.
+func Transverse(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dstW := src.h
+	dstH := src.w
+	rowSize := dstW * 4
+	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+	parallel(0, dstH, func(ys <-chan int) {
+		for dstY := range ys {
+			i := dstY * dst.Stride
+			srcX := dstH - dstY - 1
+			src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
+			reverse(dst.Pix[i : i+rowSize])
+		}
+	})
+	return dst
+}
+
+// Rotate90 rotates the image 90 degrees counter-clockwise and returns the transformed image.
+func Rotate90(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dstW := src.h
+	dstH := src.w
+	rowSize := dstW * 4
+	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+	parallel(0, dstH, func(ys <-chan int) {
+		for dstY := range ys {
+			i := dstY * dst.Stride
+			srcX := dstH - dstY - 1
+			src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
+		}
+	})
+	return dst
+}
+
+// Rotate180 rotates the image 180 degrees counter-clockwise and returns the transformed image.
+func Rotate180(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dstW := src.w
+	dstH := src.h
+	rowSize := dstW * 4
+	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+	parallel(0, dstH, func(ys <-chan int) {
+		for dstY := range ys {
+			i := dstY * dst.Stride
+			srcY := dstH - dstY - 1
+			src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
+			reverse(dst.Pix[i : i+rowSize])
+		}
+	})
+	return dst
+}
+
+// Rotate270 rotates the image 270 degrees counter-clockwise and returns the transformed image.
+func Rotate270(img image.Image) *image.NRGBA {
+	src := newScanner(img)
+	dstW := src.h
+	dstH := src.w
+	rowSize := dstW * 4
+	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+	parallel(0, dstH, func(ys <-chan int) {
+		for dstY := range ys {
+			i := dstY * dst.Stride
+			srcX := dstY
+			src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
+			reverse(dst.Pix[i : i+rowSize])
+		}
+	})
+	return dst
+}
+
+// Rotate rotates an image by the given angle counter-clockwise .
+// The angle parameter is the rotation angle in degrees.
+// The bgColor parameter specifies the color of the uncovered zone after the rotation.
+func Rotate(img image.Image, angle float64, bgColor color.Color) *image.NRGBA {
+	angle = angle - math.Floor(angle/360)*360
+
+	switch angle {
+	case 0:
+		return Clone(img)
+	case 90:
+		return Rotate90(img)
+	case 180:
+		return Rotate180(img)
+	case 270:
+		return Rotate270(img)
+	}
+
+	src := toNRGBA(img)
+	srcW := src.Bounds().Max.X
+	srcH := src.Bounds().Max.Y
+	dstW, dstH := rotatedSize(srcW, srcH, angle)
+	dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
+
+	if dstW <= 0 || dstH <= 0 {
+		return dst
+	}
+
+	srcXOff := float64(srcW)/2 - 0.5
+	srcYOff := float64(srcH)/2 - 0.5
+	dstXOff := float64(dstW)/2 - 0.5
+	dstYOff := float64(dstH)/2 - 0.5
+
+	bgColorNRGBA := color.NRGBAModel.Convert(bgColor).(color.NRGBA)
+	sin, cos := math.Sincos(math.Pi * angle / 180)
+
+	parallel(0, dstH, func(ys <-chan int) {
+		for dstY := range ys {
+			for dstX := 0; dstX < dstW; dstX++ {
+				xf, yf := rotatePoint(float64(dstX)-dstXOff, float64(dstY)-dstYOff, sin, cos)
+				xf, yf = xf+srcXOff, yf+srcYOff
+				interpolatePoint(dst, dstX, dstY, src, xf, yf, bgColorNRGBA)
+			}
+		}
+	})
+
+	return dst
+}
+
+func rotatePoint(x, y, sin, cos float64) (float64, float64) {
+	return x*cos - y*sin, x*sin + y*cos
+}
+
+func rotatedSize(w, h int, angle float64) (int, int) {
+	if w <= 0 || h <= 0 {
+		return 0, 0
+	}
+
+	sin, cos := math.Sincos(math.Pi * angle / 180)
+	x1, y1 := rotatePoint(float64(w-1), 0, sin, cos)
+	x2, y2 := rotatePoint(float64(w-1), float64(h-1), sin, cos)
+	x3, y3 := rotatePoint(0, float64(h-1), sin, cos)
+
+	minx := math.Min(x1, math.Min(x2, math.Min(x3, 0)))
+	maxx := math.Max(x1, math.Max(x2, math.Max(x3, 0)))
+	miny := math.Min(y1, math.Min(y2, math.Min(y3, 0)))
+	maxy := math.Max(y1, math.Max(y2, math.Max(y3, 0)))
+
+	neww := maxx - minx + 1
+	if neww-math.Floor(neww) > 0.1 {
+		neww++
+	}
+	newh := maxy - miny + 1
+	if newh-math.Floor(newh) > 0.1 {
+		newh++
+	}
+
+	return int(neww), int(newh)
+}
+
+func interpolatePoint(dst *image.NRGBA, dstX, dstY int, src *image.NRGBA, xf, yf float64, bgColor color.NRGBA) {
+	j := dstY*dst.Stride + dstX*4
+	d := dst.Pix[j : j+4 : j+4]
+
+	x0 := int(math.Floor(xf))
+	y0 := int(math.Floor(yf))
+	bounds := src.Bounds()
+	if !image.Pt(x0, y0).In(image.Rect(bounds.Min.X-1, bounds.Min.Y-1, bounds.Max.X, bounds.Max.Y)) {
+		d[0] = bgColor.R
+		d[1] = bgColor.G
+		d[2] = bgColor.B
+		d[3] = bgColor.A
+		return
+	}
+
+	xq := xf - float64(x0)
+	yq := yf - float64(y0)
+	points := [4]image.Point{
+		{x0, y0},
+		{x0 + 1, y0},
+		{x0, y0 + 1},
+		{x0 + 1, y0 + 1},
+	}
+	weights := [4]float64{
+		(1 - xq) * (1 - yq),
+		xq * (1 - yq),
+		(1 - xq) * yq,
+		xq * yq,
+	}
+
+	var r, g, b, a float64
+	for i := 0; i < 4; i++ {
+		p := points[i]
+		w := weights[i]
+		if p.In(bounds) {
+			i := p.Y*src.Stride + p.X*4
+			s := src.Pix[i : i+4 : i+4]
+			wa := float64(s[3]) * w
+			r += float64(s[0]) * wa
+			g += float64(s[1]) * wa
+			b += float64(s[2]) * wa
+			a += wa
+		} else {
+			wa := float64(bgColor.A) * w
+			r += float64(bgColor.R) * wa
+			g += float64(bgColor.G) * wa
+			b += float64(bgColor.B) * wa
+			a += wa
+		}
+	}
+	if a != 0 {
+		aInv := 1 / a
+		d[0] = clamp(r * aInv)
+		d[1] = clamp(g * aInv)
+		d[2] = clamp(b * aInv)
+		d[3] = clamp(a)
+	}
+}

+ 167 - 0
vendor/github.com/disintegration/imaging/utils.go

@@ -0,0 +1,167 @@
+package imaging
+
+import (
+	"image"
+	"math"
+	"runtime"
+	"sync"
+)
+
+// parallel processes the data in separate goroutines.
+func parallel(start, stop int, fn func(<-chan int)) {
+	count := stop - start
+	if count < 1 {
+		return
+	}
+
+	procs := runtime.GOMAXPROCS(0)
+	if procs > count {
+		procs = count
+	}
+
+	c := make(chan int, count)
+	for i := start; i < stop; i++ {
+		c <- i
+	}
+	close(c)
+
+	var wg sync.WaitGroup
+	for i := 0; i < procs; i++ {
+		wg.Add(1)
+		go func() {
+			defer wg.Done()
+			fn(c)
+		}()
+	}
+	wg.Wait()
+}
+
+// absint returns the absolute value of i.
+func absint(i int) int {
+	if i < 0 {
+		return -i
+	}
+	return i
+}
+
+// clamp rounds and clamps float64 value to fit into uint8.
+func clamp(x float64) uint8 {
+	v := int64(x + 0.5)
+	if v > 255 {
+		return 255
+	}
+	if v > 0 {
+		return uint8(v)
+	}
+	return 0
+}
+
+func reverse(pix []uint8) {
+	if len(pix) <= 4 {
+		return
+	}
+	i := 0
+	j := len(pix) - 4
+	for i < j {
+		pi := pix[i : i+4 : i+4]
+		pj := pix[j : j+4 : j+4]
+		pi[0], pj[0] = pj[0], pi[0]
+		pi[1], pj[1] = pj[1], pi[1]
+		pi[2], pj[2] = pj[2], pi[2]
+		pi[3], pj[3] = pj[3], pi[3]
+		i += 4
+		j -= 4
+	}
+}
+
+func toNRGBA(img image.Image) *image.NRGBA {
+	if img, ok := img.(*image.NRGBA); ok {
+		return &image.NRGBA{
+			Pix:    img.Pix,
+			Stride: img.Stride,
+			Rect:   img.Rect.Sub(img.Rect.Min),
+		}
+	}
+	return Clone(img)
+}
+
+// rgbToHSL converts a color from RGB to HSL.
+func rgbToHSL(r, g, b uint8) (float64, float64, float64) {
+	rr := float64(r) / 255
+	gg := float64(g) / 255
+	bb := float64(b) / 255
+
+	max := math.Max(rr, math.Max(gg, bb))
+	min := math.Min(rr, math.Min(gg, bb))
+
+	l := (max + min) / 2
+
+	if max == min {
+		return 0, 0, l
+	}
+
+	var h, s float64
+	d := max - min
+	if l > 0.5 {
+		s = d / (2 - max - min)
+	} else {
+		s = d / (max + min)
+	}
+
+	switch max {
+	case rr:
+		h = (gg - bb) / d
+		if g < b {
+			h += 6
+		}
+	case gg:
+		h = (bb-rr)/d + 2
+	case bb:
+		h = (rr-gg)/d + 4
+	}
+	h /= 6
+
+	return h, s, l
+}
+
+// hslToRGB converts a color from HSL to RGB.
+func hslToRGB(h, s, l float64) (uint8, uint8, uint8) {
+	var r, g, b float64
+	if s == 0 {
+		v := clamp(l * 255)
+		return v, v, v
+	}
+
+	var q float64
+	if l < 0.5 {
+		q = l * (1 + s)
+	} else {
+		q = l + s - l*s
+	}
+	p := 2*l - q
+
+	r = hueToRGB(p, q, h+1/3.0)
+	g = hueToRGB(p, q, h)
+	b = hueToRGB(p, q, h-1/3.0)
+
+	return clamp(r * 255), clamp(g * 255), clamp(b * 255)
+}
+
+func hueToRGB(p, q, t float64) float64 {
+	if t < 0 {
+		t++
+	}
+	if t > 1 {
+		t--
+	}
+	if t < 1/6.0 {
+		return p + (q-p)*6*t
+	}
+	if t < 1/2.0 {
+		return q
+	}
+	if t < 2/3.0 {
+		return p + (q-p)*(2/3.0-t)*6
+	}
+	return p
+}

+ 3 - 0
vendor/golang.org/x/image/AUTHORS

@@ -0,0 +1,3 @@
+# This source code refers to The Go Authors for copyright purposes.
+# The master list of authors is in the main Go distribution,
+# visible at http://tip.golang.org/AUTHORS.

+ 3 - 0
vendor/golang.org/x/image/CONTRIBUTORS

@@ -0,0 +1,3 @@
+# This source code was written by the Go contributors.
+# The master list of contributors is in the main Go distribution,
+# visible at http://tip.golang.org/CONTRIBUTORS.

+ 27 - 0
vendor/golang.org/x/image/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 22 - 0
vendor/golang.org/x/image/PATENTS

@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Go project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Go, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Go.  This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation.  If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Go or any code incorporated within this
+implementation of Go constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Go
+shall terminate as of the date such litigation is filed.

+ 199 - 0
vendor/golang.org/x/image/bmp/reader.go

@@ -0,0 +1,199 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package bmp implements a BMP image decoder and encoder.
+//
+// The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html.
+package bmp // import "golang.org/x/image/bmp"
+
+import (
+	"errors"
+	"image"
+	"image/color"
+	"io"
+)
+
+// ErrUnsupported means that the input BMP image uses a valid but unsupported
+// feature.
+var ErrUnsupported = errors.New("bmp: unsupported BMP image")
+
+func readUint16(b []byte) uint16 {
+	return uint16(b[0]) | uint16(b[1])<<8
+}
+
+func readUint32(b []byte) uint32 {
+	return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
+}
+
+// decodePaletted reads an 8 bit-per-pixel BMP image from r.
+// If topDown is false, the image rows will be read bottom-up.
+func decodePaletted(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
+	paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette))
+	if c.Width == 0 || c.Height == 0 {
+		return paletted, nil
+	}
+	var tmp [4]byte
+	y0, y1, yDelta := c.Height-1, -1, -1
+	if topDown {
+		y0, y1, yDelta = 0, c.Height, +1
+	}
+	for y := y0; y != y1; y += yDelta {
+		p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width]
+		if _, err := io.ReadFull(r, p); err != nil {
+			return nil, err
+		}
+		// Each row is 4-byte aligned.
+		if c.Width%4 != 0 {
+			_, err := io.ReadFull(r, tmp[:4-c.Width%4])
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	return paletted, nil
+}
+
+// decodeRGB reads a 24 bit-per-pixel BMP image from r.
+// If topDown is false, the image rows will be read bottom-up.
+func decodeRGB(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
+	rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height))
+	if c.Width == 0 || c.Height == 0 {
+		return rgba, nil
+	}
+	// There are 3 bytes per pixel, and each row is 4-byte aligned.
+	b := make([]byte, (3*c.Width+3)&^3)
+	y0, y1, yDelta := c.Height-1, -1, -1
+	if topDown {
+		y0, y1, yDelta = 0, c.Height, +1
+	}
+	for y := y0; y != y1; y += yDelta {
+		if _, err := io.ReadFull(r, b); err != nil {
+			return nil, err
+		}
+		p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
+		for i, j := 0, 0; i < len(p); i, j = i+4, j+3 {
+			// BMP images are stored in BGR order rather than RGB order.
+			p[i+0] = b[j+2]
+			p[i+1] = b[j+1]
+			p[i+2] = b[j+0]
+			p[i+3] = 0xFF
+		}
+	}
+	return rgba, nil
+}
+
+// decodeNRGBA reads a 32 bit-per-pixel BMP image from r.
+// If topDown is false, the image rows will be read bottom-up.
+func decodeNRGBA(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
+	rgba := image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height))
+	if c.Width == 0 || c.Height == 0 {
+		return rgba, nil
+	}
+	y0, y1, yDelta := c.Height-1, -1, -1
+	if topDown {
+		y0, y1, yDelta = 0, c.Height, +1
+	}
+	for y := y0; y != y1; y += yDelta {
+		p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
+		if _, err := io.ReadFull(r, p); err != nil {
+			return nil, err
+		}
+		for i := 0; i < len(p); i += 4 {
+			// BMP images are stored in BGRA order rather than RGBA order.
+			p[i+0], p[i+2] = p[i+2], p[i+0]
+		}
+	}
+	return rgba, nil
+}
+
+// Decode reads a BMP image from r and returns it as an image.Image.
+// Limitation: The file must be 8, 24 or 32 bits per pixel.
+func Decode(r io.Reader) (image.Image, error) {
+	c, bpp, topDown, err := decodeConfig(r)
+	if err != nil {
+		return nil, err
+	}
+	switch bpp {
+	case 8:
+		return decodePaletted(r, c, topDown)
+	case 24:
+		return decodeRGB(r, c, topDown)
+	case 32:
+		return decodeNRGBA(r, c, topDown)
+	}
+	panic("unreachable")
+}
+
+// DecodeConfig returns the color model and dimensions of a BMP image without
+// decoding the entire image.
+// Limitation: The file must be 8, 24 or 32 bits per pixel.
+func DecodeConfig(r io.Reader) (image.Config, error) {
+	config, _, _, err := decodeConfig(r)
+	return config, err
+}
+
+func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown bool, err error) {
+	// We only support those BMP images that are a BITMAPFILEHEADER
+	// immediately followed by a BITMAPINFOHEADER.
+	const (
+		fileHeaderLen = 14
+		infoHeaderLen = 40
+	)
+	var b [1024]byte
+	if _, err := io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
+		return image.Config{}, 0, false, err
+	}
+	if string(b[:2]) != "BM" {
+		return image.Config{}, 0, false, errors.New("bmp: invalid format")
+	}
+	offset := readUint32(b[10:14])
+	if readUint32(b[14:18]) != infoHeaderLen {
+		return image.Config{}, 0, false, ErrUnsupported
+	}
+	width := int(int32(readUint32(b[18:22])))
+	height := int(int32(readUint32(b[22:26])))
+	if height < 0 {
+		height, topDown = -height, true
+	}
+	if width < 0 || height < 0 {
+		return image.Config{}, 0, false, ErrUnsupported
+	}
+	// We only support 1 plane, 8 or 24 bits per pixel and no compression.
+	planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
+	if planes != 1 || compression != 0 {
+		return image.Config{}, 0, false, ErrUnsupported
+	}
+	switch bpp {
+	case 8:
+		if offset != fileHeaderLen+infoHeaderLen+256*4 {
+			return image.Config{}, 0, false, ErrUnsupported
+		}
+		_, err = io.ReadFull(r, b[:256*4])
+		if err != nil {
+			return image.Config{}, 0, false, err
+		}
+		pcm := make(color.Palette, 256)
+		for i := range pcm {
+			// BMP images are stored in BGR order rather than RGB order.
+			// Every 4th byte is padding.
+			pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF}
+		}
+		return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil
+	case 24:
+		if offset != fileHeaderLen+infoHeaderLen {
+			return image.Config{}, 0, false, ErrUnsupported
+		}
+		return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil
+	case 32:
+		if offset != fileHeaderLen+infoHeaderLen {
+			return image.Config{}, 0, false, ErrUnsupported
+		}
+		return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil
+	}
+	return image.Config{}, 0, false, ErrUnsupported
+}
+
+func init() {
+	image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig)
+}

+ 262 - 0
vendor/golang.org/x/image/bmp/writer.go

@@ -0,0 +1,262 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bmp
+
+import (
+	"encoding/binary"
+	"errors"
+	"image"
+	"io"
+)
+
+type header struct {
+	sigBM           [2]byte
+	fileSize        uint32
+	resverved       [2]uint16
+	pixOffset       uint32
+	dibHeaderSize   uint32
+	width           uint32
+	height          uint32
+	colorPlane      uint16
+	bpp             uint16
+	compression     uint32
+	imageSize       uint32
+	xPixelsPerMeter uint32
+	yPixelsPerMeter uint32
+	colorUse        uint32
+	colorImportant  uint32
+}
+
+func encodePaletted(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
+	var padding []byte
+	if dx < step {
+		padding = make([]byte, step-dx)
+	}
+	for y := dy - 1; y >= 0; y-- {
+		min := y*stride + 0
+		max := y*stride + dx
+		if _, err := w.Write(pix[min:max]); err != nil {
+			return err
+		}
+		if padding != nil {
+			if _, err := w.Write(padding); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int, opaque bool) error {
+	buf := make([]byte, step)
+	if opaque {
+		for y := dy - 1; y >= 0; y-- {
+			min := y*stride + 0
+			max := y*stride + dx*4
+			off := 0
+			for i := min; i < max; i += 4 {
+				buf[off+2] = pix[i+0]
+				buf[off+1] = pix[i+1]
+				buf[off+0] = pix[i+2]
+				off += 3
+			}
+			if _, err := w.Write(buf); err != nil {
+				return err
+			}
+		}
+	} else {
+		for y := dy - 1; y >= 0; y-- {
+			min := y*stride + 0
+			max := y*stride + dx*4
+			off := 0
+			for i := min; i < max; i += 4 {
+				a := uint32(pix[i+3])
+				if a == 0 {
+					buf[off+2] = 0
+					buf[off+1] = 0
+					buf[off+0] = 0
+					buf[off+3] = 0
+					off += 4
+					continue
+				} else if a == 0xff {
+					buf[off+2] = pix[i+0]
+					buf[off+1] = pix[i+1]
+					buf[off+0] = pix[i+2]
+					buf[off+3] = 0xff
+					off += 4
+					continue
+				}
+				buf[off+2] = uint8(((uint32(pix[i+0]) * 0xffff) / a) >> 8)
+				buf[off+1] = uint8(((uint32(pix[i+1]) * 0xffff) / a) >> 8)
+				buf[off+0] = uint8(((uint32(pix[i+2]) * 0xffff) / a) >> 8)
+				buf[off+3] = uint8(a)
+				off += 4
+			}
+			if _, err := w.Write(buf); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func encodeNRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int, opaque bool) error {
+	buf := make([]byte, step)
+	if opaque {
+		for y := dy - 1; y >= 0; y-- {
+			min := y*stride + 0
+			max := y*stride + dx*4
+			off := 0
+			for i := min; i < max; i += 4 {
+				buf[off+2] = pix[i+0]
+				buf[off+1] = pix[i+1]
+				buf[off+0] = pix[i+2]
+				off += 3
+			}
+			if _, err := w.Write(buf); err != nil {
+				return err
+			}
+		}
+	} else {
+		for y := dy - 1; y >= 0; y-- {
+			min := y*stride + 0
+			max := y*stride + dx*4
+			off := 0
+			for i := min; i < max; i += 4 {
+				buf[off+2] = pix[i+0]
+				buf[off+1] = pix[i+1]
+				buf[off+0] = pix[i+2]
+				buf[off+3] = pix[i+3]
+				off += 4
+			}
+			if _, err := w.Write(buf); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func encode(w io.Writer, m image.Image, step int) error {
+	b := m.Bounds()
+	buf := make([]byte, step)
+	for y := b.Max.Y - 1; y >= b.Min.Y; y-- {
+		off := 0
+		for x := b.Min.X; x < b.Max.X; x++ {
+			r, g, b, _ := m.At(x, y).RGBA()
+			buf[off+2] = byte(r >> 8)
+			buf[off+1] = byte(g >> 8)
+			buf[off+0] = byte(b >> 8)
+			off += 3
+		}
+		if _, err := w.Write(buf); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Encode writes the image m to w in BMP format.
+func Encode(w io.Writer, m image.Image) error {
+	d := m.Bounds().Size()
+	if d.X < 0 || d.Y < 0 {
+		return errors.New("bmp: negative bounds")
+	}
+	h := &header{
+		sigBM:         [2]byte{'B', 'M'},
+		fileSize:      14 + 40,
+		pixOffset:     14 + 40,
+		dibHeaderSize: 40,
+		width:         uint32(d.X),
+		height:        uint32(d.Y),
+		colorPlane:    1,
+	}
+
+	var step int
+	var palette []byte
+	var opaque bool
+	switch m := m.(type) {
+	case *image.Gray:
+		step = (d.X + 3) &^ 3
+		palette = make([]byte, 1024)
+		for i := 0; i < 256; i++ {
+			palette[i*4+0] = uint8(i)
+			palette[i*4+1] = uint8(i)
+			palette[i*4+2] = uint8(i)
+			palette[i*4+3] = 0xFF
+		}
+		h.imageSize = uint32(d.Y * step)
+		h.fileSize += uint32(len(palette)) + h.imageSize
+		h.pixOffset += uint32(len(palette))
+		h.bpp = 8
+
+	case *image.Paletted:
+		step = (d.X + 3) &^ 3
+		palette = make([]byte, 1024)
+		for i := 0; i < len(m.Palette) && i < 256; i++ {
+			r, g, b, _ := m.Palette[i].RGBA()
+			palette[i*4+0] = uint8(b >> 8)
+			palette[i*4+1] = uint8(g >> 8)
+			palette[i*4+2] = uint8(r >> 8)
+			palette[i*4+3] = 0xFF
+		}
+		h.imageSize = uint32(d.Y * step)
+		h.fileSize += uint32(len(palette)) + h.imageSize
+		h.pixOffset += uint32(len(palette))
+		h.bpp = 8
+	case *image.RGBA:
+		opaque = m.Opaque()
+		if opaque {
+			step = (3*d.X + 3) &^ 3
+			h.bpp = 24
+		} else {
+			step = 4 * d.X
+			h.bpp = 32
+		}
+		h.imageSize = uint32(d.Y * step)
+		h.fileSize += h.imageSize
+	case *image.NRGBA:
+		opaque = m.Opaque()
+		if opaque {
+			step = (3*d.X + 3) &^ 3
+			h.bpp = 24
+		} else {
+			step = 4 * d.X
+			h.bpp = 32
+		}
+		h.imageSize = uint32(d.Y * step)
+		h.fileSize += h.imageSize
+	default:
+		step = (3*d.X + 3) &^ 3
+		h.imageSize = uint32(d.Y * step)
+		h.fileSize += h.imageSize
+		h.bpp = 24
+	}
+
+	if err := binary.Write(w, binary.LittleEndian, h); err != nil {
+		return err
+	}
+	if palette != nil {
+		if err := binary.Write(w, binary.LittleEndian, palette); err != nil {
+			return err
+		}
+	}
+
+	if d.X == 0 || d.Y == 0 {
+		return nil
+	}
+
+	switch m := m.(type) {
+	case *image.Gray:
+		return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
+	case *image.Paletted:
+		return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
+	case *image.RGBA:
+		return encodeRGBA(w, m.Pix, d.X, d.Y, m.Stride, step, opaque)
+	case *image.NRGBA:
+		return encodeNRGBA(w, m.Pix, d.X, d.Y, m.Stride, step, opaque)
+	}
+	return encode(w, m, step)
+}

+ 69 - 0
vendor/golang.org/x/image/tiff/buffer.go

@@ -0,0 +1,69 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tiff
+
+import "io"
+
+// buffer buffers an io.Reader to satisfy io.ReaderAt.
+type buffer struct {
+	r   io.Reader
+	buf []byte
+}
+
+// fill reads data from b.r until the buffer contains at least end bytes.
+func (b *buffer) fill(end int) error {
+	m := len(b.buf)
+	if end > m {
+		if end > cap(b.buf) {
+			newcap := 1024
+			for newcap < end {
+				newcap *= 2
+			}
+			newbuf := make([]byte, end, newcap)
+			copy(newbuf, b.buf)
+			b.buf = newbuf
+		} else {
+			b.buf = b.buf[:end]
+		}
+		if n, err := io.ReadFull(b.r, b.buf[m:end]); err != nil {
+			end = m + n
+			b.buf = b.buf[:end]
+			return err
+		}
+	}
+	return nil
+}
+
+func (b *buffer) ReadAt(p []byte, off int64) (int, error) {
+	o := int(off)
+	end := o + len(p)
+	if int64(end) != off+int64(len(p)) {
+		return 0, io.ErrUnexpectedEOF
+	}
+
+	err := b.fill(end)
+	return copy(p, b.buf[o:end]), err
+}
+
+// Slice returns a slice of the underlying buffer. The slice contains
+// n bytes starting at offset off.
+func (b *buffer) Slice(off, n int) ([]byte, error) {
+	end := off + n
+	if err := b.fill(end); err != nil {
+		return nil, err
+	}
+	return b.buf[off:end], nil
+}
+
+// newReaderAt converts an io.Reader into an io.ReaderAt.
+func newReaderAt(r io.Reader) io.ReaderAt {
+	if ra, ok := r.(io.ReaderAt); ok {
+		return ra
+	}
+	return &buffer{
+		r:   r,
+		buf: make([]byte, 0, 1024),
+	}
+}

+ 58 - 0
vendor/golang.org/x/image/tiff/compress.go

@@ -0,0 +1,58 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tiff
+
+import (
+	"bufio"
+	"io"
+)
+
+type byteReader interface {
+	io.Reader
+	io.ByteReader
+}
+
+// unpackBits decodes the PackBits-compressed data in src and returns the
+// uncompressed data.
+//
+// The PackBits compression format is described in section 9 (p. 42)
+// of the TIFF spec.
+func unpackBits(r io.Reader) ([]byte, error) {
+	buf := make([]byte, 128)
+	dst := make([]byte, 0, 1024)
+	br, ok := r.(byteReader)
+	if !ok {
+		br = bufio.NewReader(r)
+	}
+
+	for {
+		b, err := br.ReadByte()
+		if err != nil {
+			if err == io.EOF {
+				return dst, nil
+			}
+			return nil, err
+		}
+		code := int(int8(b))
+		switch {
+		case code >= 0:
+			n, err := io.ReadFull(br, buf[:code+1])
+			if err != nil {
+				return nil, err
+			}
+			dst = append(dst, buf[:n]...)
+		case code == -128:
+			// No-op.
+		default:
+			if b, err = br.ReadByte(); err != nil {
+				return nil, err
+			}
+			for j := 0; j < 1-code; j++ {
+				buf[j] = b
+			}
+			dst = append(dst, buf[:1-code]...)
+		}
+	}
+}

+ 133 - 0
vendor/golang.org/x/image/tiff/consts.go

@@ -0,0 +1,133 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tiff
+
+// A tiff image file contains one or more images. The metadata
+// of each image is contained in an Image File Directory (IFD),
+// which contains entries of 12 bytes each and is described
+// on page 14-16 of the specification. An IFD entry consists of
+//
+//  - a tag, which describes the signification of the entry,
+//  - the data type and length of the entry,
+//  - the data itself or a pointer to it if it is more than 4 bytes.
+//
+// The presence of a length means that each IFD is effectively an array.
+
+const (
+	leHeader = "II\x2A\x00" // Header for little-endian files.
+	beHeader = "MM\x00\x2A" // Header for big-endian files.
+
+	ifdLen = 12 // Length of an IFD entry in bytes.
+)
+
+// Data types (p. 14-16 of the spec).
+const (
+	dtByte     = 1
+	dtASCII    = 2
+	dtShort    = 3
+	dtLong     = 4
+	dtRational = 5
+)
+
+// The length of one instance of each data type in bytes.
+var lengths = [...]uint32{0, 1, 1, 2, 4, 8}
+
+// Tags (see p. 28-41 of the spec).
+const (
+	tImageWidth                = 256
+	tImageLength               = 257
+	tBitsPerSample             = 258
+	tCompression               = 259
+	tPhotometricInterpretation = 262
+
+	tStripOffsets    = 273
+	tSamplesPerPixel = 277
+	tRowsPerStrip    = 278
+	tStripByteCounts = 279
+
+	tTileWidth      = 322
+	tTileLength     = 323
+	tTileOffsets    = 324
+	tTileByteCounts = 325
+
+	tXResolution    = 282
+	tYResolution    = 283
+	tResolutionUnit = 296
+
+	tPredictor    = 317
+	tColorMap     = 320
+	tExtraSamples = 338
+	tSampleFormat = 339
+)
+
+// Compression types (defined in various places in the spec and supplements).
+const (
+	cNone       = 1
+	cCCITT      = 2
+	cG3         = 3 // Group 3 Fax.
+	cG4         = 4 // Group 4 Fax.
+	cLZW        = 5
+	cJPEGOld    = 6 // Superseded by cJPEG.
+	cJPEG       = 7
+	cDeflate    = 8 // zlib compression.
+	cPackBits   = 32773
+	cDeflateOld = 32946 // Superseded by cDeflate.
+)
+
+// Photometric interpretation values (see p. 37 of the spec).
+const (
+	pWhiteIsZero = 0
+	pBlackIsZero = 1
+	pRGB         = 2
+	pPaletted    = 3
+	pTransMask   = 4 // transparency mask
+	pCMYK        = 5
+	pYCbCr       = 6
+	pCIELab      = 8
+)
+
+// Values for the tPredictor tag (page 64-65 of the spec).
+const (
+	prNone       = 1
+	prHorizontal = 2
+)
+
+// Values for the tResolutionUnit tag (page 18).
+const (
+	resNone    = 1
+	resPerInch = 2 // Dots per inch.
+	resPerCM   = 3 // Dots per centimeter.
+)
+
+// imageMode represents the mode of the image.
+type imageMode int
+
+const (
+	mBilevel imageMode = iota
+	mPaletted
+	mGray
+	mGrayInvert
+	mRGB
+	mRGBA
+	mNRGBA
+)
+
+// CompressionType describes the type of compression used in Options.
+type CompressionType int
+
+const (
+	Uncompressed CompressionType = iota
+	Deflate
+)
+
+// specValue returns the compression type constant from the TIFF spec that
+// is equivalent to c.
+func (c CompressionType) specValue() uint32 {
+	switch c {
+	case Deflate:
+		return cDeflate
+	}
+	return cNone
+}

+ 272 - 0
vendor/golang.org/x/image/tiff/lzw/reader.go

@@ -0,0 +1,272 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package lzw implements the Lempel-Ziv-Welch compressed data format,
+// described in T. A. Welch, ``A Technique for High-Performance Data
+// Compression'', Computer, 17(6) (June 1984), pp 8-19.
+//
+// In particular, it implements LZW as used by the TIFF file format, including
+// an "off by one" algorithmic difference when compared to standard LZW.
+package lzw // import "golang.org/x/image/tiff/lzw"
+
+/*
+This file was branched from src/pkg/compress/lzw/reader.go in the
+standard library. Differences from the original are marked with "NOTE".
+
+The tif_lzw.c file in the libtiff C library has this comment:
+
+----
+The 5.0 spec describes a different algorithm than Aldus
+implements.  Specifically, Aldus does code length transitions
+one code earlier than should be done (for real LZW).
+Earlier versions of this library implemented the correct
+LZW algorithm, but emitted codes in a bit order opposite
+to the TIFF spec.  Thus, to maintain compatibility w/ Aldus
+we interpret MSB-LSB ordered codes to be images written w/
+old versions of this library, but otherwise adhere to the
+Aldus "off by one" algorithm.
+----
+
+The Go code doesn't read (invalid) TIFF files written by old versions of
+libtiff, but the LZW algorithm in this package still differs from the one in
+Go's standard package library to accomodate this "off by one" in valid TIFFs.
+*/
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"io"
+)
+
+// Order specifies the bit ordering in an LZW data stream.
+type Order int
+
+const (
+	// LSB means Least Significant Bits first, as used in the GIF file format.
+	LSB Order = iota
+	// MSB means Most Significant Bits first, as used in the TIFF and PDF
+	// file formats.
+	MSB
+)
+
+const (
+	maxWidth           = 12
+	decoderInvalidCode = 0xffff
+	flushBuffer        = 1 << maxWidth
+)
+
+// decoder is the state from which the readXxx method converts a byte
+// stream into a code stream.
+type decoder struct {
+	r        io.ByteReader
+	bits     uint32
+	nBits    uint
+	width    uint
+	read     func(*decoder) (uint16, error) // readLSB or readMSB
+	litWidth int                            // width in bits of literal codes
+	err      error
+
+	// The first 1<<litWidth codes are literal codes.
+	// The next two codes mean clear and EOF.
+	// Other valid codes are in the range [lo, hi] where lo := clear + 2,
+	// with the upper bound incrementing on each code seen.
+	// overflow is the code at which hi overflows the code width. NOTE: TIFF's LZW is "off by one".
+	// last is the most recently seen code, or decoderInvalidCode.
+	clear, eof, hi, overflow, last uint16
+
+	// Each code c in [lo, hi] expands to two or more bytes. For c != hi:
+	//   suffix[c] is the last of these bytes.
+	//   prefix[c] is the code for all but the last byte.
+	//   This code can either be a literal code or another code in [lo, c).
+	// The c == hi case is a special case.
+	suffix [1 << maxWidth]uint8
+	prefix [1 << maxWidth]uint16
+
+	// output is the temporary output buffer.
+	// Literal codes are accumulated from the start of the buffer.
+	// Non-literal codes decode to a sequence of suffixes that are first
+	// written right-to-left from the end of the buffer before being copied
+	// to the start of the buffer.
+	// It is flushed when it contains >= 1<<maxWidth bytes,
+	// so that there is always room to decode an entire code.
+	output [2 * 1 << maxWidth]byte
+	o      int    // write index into output
+	toRead []byte // bytes to return from Read
+}
+
+// readLSB returns the next code for "Least Significant Bits first" data.
+func (d *decoder) readLSB() (uint16, error) {
+	for d.nBits < d.width {
+		x, err := d.r.ReadByte()
+		if err != nil {
+			return 0, err
+		}
+		d.bits |= uint32(x) << d.nBits
+		d.nBits += 8
+	}
+	code := uint16(d.bits & (1<<d.width - 1))
+	d.bits >>= d.width
+	d.nBits -= d.width
+	return code, nil
+}
+
+// readMSB returns the next code for "Most Significant Bits first" data.
+func (d *decoder) readMSB() (uint16, error) {
+	for d.nBits < d.width {
+		x, err := d.r.ReadByte()
+		if err != nil {
+			return 0, err
+		}
+		d.bits |= uint32(x) << (24 - d.nBits)
+		d.nBits += 8
+	}
+	code := uint16(d.bits >> (32 - d.width))
+	d.bits <<= d.width
+	d.nBits -= d.width
+	return code, nil
+}
+
+func (d *decoder) Read(b []byte) (int, error) {
+	for {
+		if len(d.toRead) > 0 {
+			n := copy(b, d.toRead)
+			d.toRead = d.toRead[n:]
+			return n, nil
+		}
+		if d.err != nil {
+			return 0, d.err
+		}
+		d.decode()
+	}
+}
+
+// decode decompresses bytes from r and leaves them in d.toRead.
+// read specifies how to decode bytes into codes.
+// litWidth is the width in bits of literal codes.
+func (d *decoder) decode() {
+	// Loop over the code stream, converting codes into decompressed bytes.
+loop:
+	for {
+		code, err := d.read(d)
+		if err != nil {
+			if err == io.EOF {
+				err = io.ErrUnexpectedEOF
+			}
+			d.err = err
+			break
+		}
+		switch {
+		case code < d.clear:
+			// We have a literal code.
+			d.output[d.o] = uint8(code)
+			d.o++
+			if d.last != decoderInvalidCode {
+				// Save what the hi code expands to.
+				d.suffix[d.hi] = uint8(code)
+				d.prefix[d.hi] = d.last
+			}
+		case code == d.clear:
+			d.width = 1 + uint(d.litWidth)
+			d.hi = d.eof
+			d.overflow = 1 << d.width
+			d.last = decoderInvalidCode
+			continue
+		case code == d.eof:
+			d.err = io.EOF
+			break loop
+		case code <= d.hi:
+			c, i := code, len(d.output)-1
+			if code == d.hi {
+				// code == hi is a special case which expands to the last expansion
+				// followed by the head of the last expansion. To find the head, we walk
+				// the prefix chain until we find a literal code.
+				c = d.last
+				for c >= d.clear {
+					c = d.prefix[c]
+				}
+				d.output[i] = uint8(c)
+				i--
+				c = d.last
+			}
+			// Copy the suffix chain into output and then write that to w.
+			for c >= d.clear {
+				d.output[i] = d.suffix[c]
+				i--
+				c = d.prefix[c]
+			}
+			d.output[i] = uint8(c)
+			d.o += copy(d.output[d.o:], d.output[i:])
+			if d.last != decoderInvalidCode {
+				// Save what the hi code expands to.
+				d.suffix[d.hi] = uint8(c)
+				d.prefix[d.hi] = d.last
+			}
+		default:
+			d.err = errors.New("lzw: invalid code")
+			break loop
+		}
+		d.last, d.hi = code, d.hi+1
+		if d.hi+1 >= d.overflow { // NOTE: the "+1" is where TIFF's LZW differs from the standard algorithm.
+			if d.width == maxWidth {
+				d.last = decoderInvalidCode
+			} else {
+				d.width++
+				d.overflow <<= 1
+			}
+		}
+		if d.o >= flushBuffer {
+			break
+		}
+	}
+	// Flush pending output.
+	d.toRead = d.output[:d.o]
+	d.o = 0
+}
+
+var errClosed = errors.New("lzw: reader/writer is closed")
+
+func (d *decoder) Close() error {
+	d.err = errClosed // in case any Reads come along
+	return nil
+}
+
+// NewReader creates a new io.ReadCloser.
+// Reads from the returned io.ReadCloser read and decompress data from r.
+// If r does not also implement io.ByteReader,
+// the decompressor may read more data than necessary from r.
+// It is the caller's responsibility to call Close on the ReadCloser when
+// finished reading.
+// The number of bits to use for literal codes, litWidth, must be in the
+// range [2,8] and is typically 8. It must equal the litWidth
+// used during compression.
+func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser {
+	d := new(decoder)
+	switch order {
+	case LSB:
+		d.read = (*decoder).readLSB
+	case MSB:
+		d.read = (*decoder).readMSB
+	default:
+		d.err = errors.New("lzw: unknown order")
+		return d
+	}
+	if litWidth < 2 || 8 < litWidth {
+		d.err = fmt.Errorf("lzw: litWidth %d out of range", litWidth)
+		return d
+	}
+	if br, ok := r.(io.ByteReader); ok {
+		d.r = br
+	} else {
+		d.r = bufio.NewReader(r)
+	}
+	d.litWidth = litWidth
+	d.width = 1 + uint(litWidth)
+	d.clear = uint16(1) << uint(litWidth)
+	d.eof, d.hi = d.clear+1, d.clear+1
+	d.overflow = uint16(1) << d.width
+	d.last = decoderInvalidCode
+
+	return d
+}

+ 684 - 0
vendor/golang.org/x/image/tiff/reader.go

@@ -0,0 +1,684 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package tiff implements a TIFF image decoder and encoder.
+//
+// The TIFF specification is at http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
+package tiff // import "golang.org/x/image/tiff"
+
+import (
+	"compress/zlib"
+	"encoding/binary"
+	"fmt"
+	"image"
+	"image/color"
+	"io"
+	"io/ioutil"
+	"math"
+
+	"golang.org/x/image/tiff/lzw"
+)
+
+// A FormatError reports that the input is not a valid TIFF image.
+type FormatError string
+
+func (e FormatError) Error() string {
+	return "tiff: invalid format: " + string(e)
+}
+
+// An UnsupportedError reports that the input uses a valid but
+// unimplemented feature.
+type UnsupportedError string
+
+func (e UnsupportedError) Error() string {
+	return "tiff: unsupported feature: " + string(e)
+}
+
+var errNoPixels = FormatError("not enough pixel data")
+
+type decoder struct {
+	r         io.ReaderAt
+	byteOrder binary.ByteOrder
+	config    image.Config
+	mode      imageMode
+	bpp       uint
+	features  map[int][]uint
+	palette   []color.Color
+
+	buf   []byte
+	off   int    // Current offset in buf.
+	v     uint32 // Buffer value for reading with arbitrary bit depths.
+	nbits uint   // Remaining number of bits in v.
+}
+
+// firstVal returns the first uint of the features entry with the given tag,
+// or 0 if the tag does not exist.
+func (d *decoder) firstVal(tag int) uint {
+	f := d.features[tag]
+	if len(f) == 0 {
+		return 0
+	}
+	return f[0]
+}
+
+// ifdUint decodes the IFD entry in p, which must be of the Byte, Short
+// or Long type, and returns the decoded uint values.
+func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
+	var raw []byte
+	if len(p) < ifdLen {
+		return nil, FormatError("bad IFD entry")
+	}
+
+	datatype := d.byteOrder.Uint16(p[2:4])
+	if dt := int(datatype); dt <= 0 || dt >= len(lengths) {
+		return nil, UnsupportedError("IFD entry datatype")
+	}
+
+	count := d.byteOrder.Uint32(p[4:8])
+	if count > math.MaxInt32/lengths[datatype] {
+		return nil, FormatError("IFD data too large")
+	}
+	if datalen := lengths[datatype] * count; datalen > 4 {
+		// The IFD contains a pointer to the real value.
+		raw = make([]byte, datalen)
+		_, err = d.r.ReadAt(raw, int64(d.byteOrder.Uint32(p[8:12])))
+	} else {
+		raw = p[8 : 8+datalen]
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	u = make([]uint, count)
+	switch datatype {
+	case dtByte:
+		for i := uint32(0); i < count; i++ {
+			u[i] = uint(raw[i])
+		}
+	case dtShort:
+		for i := uint32(0); i < count; i++ {
+			u[i] = uint(d.byteOrder.Uint16(raw[2*i : 2*(i+1)]))
+		}
+	case dtLong:
+		for i := uint32(0); i < count; i++ {
+			u[i] = uint(d.byteOrder.Uint32(raw[4*i : 4*(i+1)]))
+		}
+	default:
+		return nil, UnsupportedError("data type")
+	}
+	return u, nil
+}
+
+// parseIFD decides whether the the IFD entry in p is "interesting" and
+// stows away the data in the decoder. It returns the tag number of the
+// entry and an error, if any.
+func (d *decoder) parseIFD(p []byte) (int, error) {
+	tag := d.byteOrder.Uint16(p[0:2])
+	switch tag {
+	case tBitsPerSample,
+		tExtraSamples,
+		tPhotometricInterpretation,
+		tCompression,
+		tPredictor,
+		tStripOffsets,
+		tStripByteCounts,
+		tRowsPerStrip,
+		tTileWidth,
+		tTileLength,
+		tTileOffsets,
+		tTileByteCounts,
+		tImageLength,
+		tImageWidth:
+		val, err := d.ifdUint(p)
+		if err != nil {
+			return 0, err
+		}
+		d.features[int(tag)] = val
+	case tColorMap:
+		val, err := d.ifdUint(p)
+		if err != nil {
+			return 0, err
+		}
+		numcolors := len(val) / 3
+		if len(val)%3 != 0 || numcolors <= 0 || numcolors > 256 {
+			return 0, FormatError("bad ColorMap length")
+		}
+		d.palette = make([]color.Color, numcolors)
+		for i := 0; i < numcolors; i++ {
+			d.palette[i] = color.RGBA64{
+				uint16(val[i]),
+				uint16(val[i+numcolors]),
+				uint16(val[i+2*numcolors]),
+				0xffff,
+			}
+		}
+	case tSampleFormat:
+		// Page 27 of the spec: If the SampleFormat is present and
+		// the value is not 1 [= unsigned integer data], a Baseline
+		// TIFF reader that cannot handle the SampleFormat value
+		// must terminate the import process gracefully.
+		val, err := d.ifdUint(p)
+		if err != nil {
+			return 0, err
+		}
+		for _, v := range val {
+			if v != 1 {
+				return 0, UnsupportedError("sample format")
+			}
+		}
+	}
+	return int(tag), nil
+}
+
+// readBits reads n bits from the internal buffer starting at the current offset.
+func (d *decoder) readBits(n uint) (v uint32, ok bool) {
+	for d.nbits < n {
+		d.v <<= 8
+		if d.off >= len(d.buf) {
+			return 0, false
+		}
+		d.v |= uint32(d.buf[d.off])
+		d.off++
+		d.nbits += 8
+	}
+	d.nbits -= n
+	rv := d.v >> d.nbits
+	d.v &^= rv << d.nbits
+	return rv, true
+}
+
+// flushBits discards the unread bits in the buffer used by readBits.
+// It is used at the end of a line.
+func (d *decoder) flushBits() {
+	d.v = 0
+	d.nbits = 0
+}
+
+// minInt returns the smaller of x or y.
+func minInt(a, b int) int {
+	if a <= b {
+		return a
+	}
+	return b
+}
+
+// decode decodes the raw data of an image.
+// It reads from d.buf and writes the strip or tile into dst.
+func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
+	d.off = 0
+
+	// Apply horizontal predictor if necessary.
+	// In this case, p contains the color difference to the preceding pixel.
+	// See page 64-65 of the spec.
+	if d.firstVal(tPredictor) == prHorizontal {
+		switch d.bpp {
+		case 16:
+			var off int
+			n := 2 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel
+			for y := ymin; y < ymax; y++ {
+				off += n
+				for x := 0; x < (xmax-xmin-1)*n; x += 2 {
+					if off+2 > len(d.buf) {
+						return errNoPixels
+					}
+					v0 := d.byteOrder.Uint16(d.buf[off-n : off-n+2])
+					v1 := d.byteOrder.Uint16(d.buf[off : off+2])
+					d.byteOrder.PutUint16(d.buf[off:off+2], v1+v0)
+					off += 2
+				}
+			}
+		case 8:
+			var off int
+			n := 1 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel
+			for y := ymin; y < ymax; y++ {
+				off += n
+				for x := 0; x < (xmax-xmin-1)*n; x++ {
+					if off >= len(d.buf) {
+						return errNoPixels
+					}
+					d.buf[off] += d.buf[off-n]
+					off++
+				}
+			}
+		case 1:
+			return UnsupportedError("horizontal predictor with 1 BitsPerSample")
+		}
+	}
+
+	rMaxX := minInt(xmax, dst.Bounds().Max.X)
+	rMaxY := minInt(ymax, dst.Bounds().Max.Y)
+	switch d.mode {
+	case mGray, mGrayInvert:
+		if d.bpp == 16 {
+			img := dst.(*image.Gray16)
+			for y := ymin; y < rMaxY; y++ {
+				for x := xmin; x < rMaxX; x++ {
+					if d.off+2 > len(d.buf) {
+						return errNoPixels
+					}
+					v := d.byteOrder.Uint16(d.buf[d.off : d.off+2])
+					d.off += 2
+					if d.mode == mGrayInvert {
+						v = 0xffff - v
+					}
+					img.SetGray16(x, y, color.Gray16{v})
+				}
+				if rMaxX == img.Bounds().Max.X {
+					d.off += 2 * (xmax - img.Bounds().Max.X)
+				}
+			}
+		} else {
+			img := dst.(*image.Gray)
+			max := uint32((1 << d.bpp) - 1)
+			for y := ymin; y < rMaxY; y++ {
+				for x := xmin; x < rMaxX; x++ {
+					v, ok := d.readBits(d.bpp)
+					if !ok {
+						return errNoPixels
+					}
+					v = v * 0xff / max
+					if d.mode == mGrayInvert {
+						v = 0xff - v
+					}
+					img.SetGray(x, y, color.Gray{uint8(v)})
+				}
+				d.flushBits()
+			}
+		}
+	case mPaletted:
+		img := dst.(*image.Paletted)
+		for y := ymin; y < rMaxY; y++ {
+			for x := xmin; x < rMaxX; x++ {
+				v, ok := d.readBits(d.bpp)
+				if !ok {
+					return errNoPixels
+				}
+				img.SetColorIndex(x, y, uint8(v))
+			}
+			d.flushBits()
+		}
+	case mRGB:
+		if d.bpp == 16 {
+			img := dst.(*image.RGBA64)
+			for y := ymin; y < rMaxY; y++ {
+				for x := xmin; x < rMaxX; x++ {
+					if d.off+6 > len(d.buf) {
+						return errNoPixels
+					}
+					r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
+					g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
+					b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
+					d.off += 6
+					img.SetRGBA64(x, y, color.RGBA64{r, g, b, 0xffff})
+				}
+			}
+		} else {
+			img := dst.(*image.RGBA)
+			for y := ymin; y < rMaxY; y++ {
+				min := img.PixOffset(xmin, y)
+				max := img.PixOffset(rMaxX, y)
+				off := (y - ymin) * (xmax - xmin) * 3
+				for i := min; i < max; i += 4 {
+					if off+3 > len(d.buf) {
+						return errNoPixels
+					}
+					img.Pix[i+0] = d.buf[off+0]
+					img.Pix[i+1] = d.buf[off+1]
+					img.Pix[i+2] = d.buf[off+2]
+					img.Pix[i+3] = 0xff
+					off += 3
+				}
+			}
+		}
+	case mNRGBA:
+		if d.bpp == 16 {
+			img := dst.(*image.NRGBA64)
+			for y := ymin; y < rMaxY; y++ {
+				for x := xmin; x < rMaxX; x++ {
+					if d.off+8 > len(d.buf) {
+						return errNoPixels
+					}
+					r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
+					g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
+					b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
+					a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8])
+					d.off += 8
+					img.SetNRGBA64(x, y, color.NRGBA64{r, g, b, a})
+				}
+			}
+		} else {
+			img := dst.(*image.NRGBA)
+			for y := ymin; y < rMaxY; y++ {
+				min := img.PixOffset(xmin, y)
+				max := img.PixOffset(rMaxX, y)
+				i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4
+				if i1 > len(d.buf) {
+					return errNoPixels
+				}
+				copy(img.Pix[min:max], d.buf[i0:i1])
+			}
+		}
+	case mRGBA:
+		if d.bpp == 16 {
+			img := dst.(*image.RGBA64)
+			for y := ymin; y < rMaxY; y++ {
+				for x := xmin; x < rMaxX; x++ {
+					if d.off+8 > len(d.buf) {
+						return errNoPixels
+					}
+					r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
+					g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
+					b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
+					a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8])
+					d.off += 8
+					img.SetRGBA64(x, y, color.RGBA64{r, g, b, a})
+				}
+			}
+		} else {
+			img := dst.(*image.RGBA)
+			for y := ymin; y < rMaxY; y++ {
+				min := img.PixOffset(xmin, y)
+				max := img.PixOffset(rMaxX, y)
+				i0, i1 := (y-ymin)*(xmax-xmin)*4, (y-ymin+1)*(xmax-xmin)*4
+				if i1 > len(d.buf) {
+					return errNoPixels
+				}
+				copy(img.Pix[min:max], d.buf[i0:i1])
+			}
+		}
+	}
+
+	return nil
+}
+
+func newDecoder(r io.Reader) (*decoder, error) {
+	d := &decoder{
+		r:        newReaderAt(r),
+		features: make(map[int][]uint),
+	}
+
+	p := make([]byte, 8)
+	if _, err := d.r.ReadAt(p, 0); err != nil {
+		return nil, err
+	}
+	switch string(p[0:4]) {
+	case leHeader:
+		d.byteOrder = binary.LittleEndian
+	case beHeader:
+		d.byteOrder = binary.BigEndian
+	default:
+		return nil, FormatError("malformed header")
+	}
+
+	ifdOffset := int64(d.byteOrder.Uint32(p[4:8]))
+
+	// The first two bytes contain the number of entries (12 bytes each).
+	if _, err := d.r.ReadAt(p[0:2], ifdOffset); err != nil {
+		return nil, err
+	}
+	numItems := int(d.byteOrder.Uint16(p[0:2]))
+
+	// All IFD entries are read in one chunk.
+	p = make([]byte, ifdLen*numItems)
+	if _, err := d.r.ReadAt(p, ifdOffset+2); err != nil {
+		return nil, err
+	}
+
+	prevTag := -1
+	for i := 0; i < len(p); i += ifdLen {
+		tag, err := d.parseIFD(p[i : i+ifdLen])
+		if err != nil {
+			return nil, err
+		}
+		if tag <= prevTag {
+			return nil, FormatError("tags are not sorted in ascending order")
+		}
+		prevTag = tag
+	}
+
+	d.config.Width = int(d.firstVal(tImageWidth))
+	d.config.Height = int(d.firstVal(tImageLength))
+
+	if _, ok := d.features[tBitsPerSample]; !ok {
+		return nil, FormatError("BitsPerSample tag missing")
+	}
+	d.bpp = d.firstVal(tBitsPerSample)
+	switch d.bpp {
+	case 0:
+		return nil, FormatError("BitsPerSample must not be 0")
+	case 1, 8, 16:
+		// Nothing to do, these are accepted by this implementation.
+	default:
+		return nil, UnsupportedError(fmt.Sprintf("BitsPerSample of %v", d.bpp))
+	}
+
+	// Determine the image mode.
+	switch d.firstVal(tPhotometricInterpretation) {
+	case pRGB:
+		if d.bpp == 16 {
+			for _, b := range d.features[tBitsPerSample] {
+				if b != 16 {
+					return nil, FormatError("wrong number of samples for 16bit RGB")
+				}
+			}
+		} else {
+			for _, b := range d.features[tBitsPerSample] {
+				if b != 8 {
+					return nil, FormatError("wrong number of samples for 8bit RGB")
+				}
+			}
+		}
+		// RGB images normally have 3 samples per pixel.
+		// If there are more, ExtraSamples (p. 31-32 of the spec)
+		// gives their meaning (usually an alpha channel).
+		//
+		// This implementation does not support extra samples
+		// of an unspecified type.
+		switch len(d.features[tBitsPerSample]) {
+		case 3:
+			d.mode = mRGB
+			if d.bpp == 16 {
+				d.config.ColorModel = color.RGBA64Model
+			} else {
+				d.config.ColorModel = color.RGBAModel
+			}
+		case 4:
+			switch d.firstVal(tExtraSamples) {
+			case 1:
+				d.mode = mRGBA
+				if d.bpp == 16 {
+					d.config.ColorModel = color.RGBA64Model
+				} else {
+					d.config.ColorModel = color.RGBAModel
+				}
+			case 2:
+				d.mode = mNRGBA
+				if d.bpp == 16 {
+					d.config.ColorModel = color.NRGBA64Model
+				} else {
+					d.config.ColorModel = color.NRGBAModel
+				}
+			default:
+				return nil, FormatError("wrong number of samples for RGB")
+			}
+		default:
+			return nil, FormatError("wrong number of samples for RGB")
+		}
+	case pPaletted:
+		d.mode = mPaletted
+		d.config.ColorModel = color.Palette(d.palette)
+	case pWhiteIsZero:
+		d.mode = mGrayInvert
+		if d.bpp == 16 {
+			d.config.ColorModel = color.Gray16Model
+		} else {
+			d.config.ColorModel = color.GrayModel
+		}
+	case pBlackIsZero:
+		d.mode = mGray
+		if d.bpp == 16 {
+			d.config.ColorModel = color.Gray16Model
+		} else {
+			d.config.ColorModel = color.GrayModel
+		}
+	default:
+		return nil, UnsupportedError("color model")
+	}
+
+	return d, nil
+}
+
+// DecodeConfig returns the color model and dimensions of a TIFF image without
+// decoding the entire image.
+func DecodeConfig(r io.Reader) (image.Config, error) {
+	d, err := newDecoder(r)
+	if err != nil {
+		return image.Config{}, err
+	}
+	return d.config, nil
+}
+
+// Decode reads a TIFF image from r and returns it as an image.Image.
+// The type of Image returned depends on the contents of the TIFF.
+func Decode(r io.Reader) (img image.Image, err error) {
+	d, err := newDecoder(r)
+	if err != nil {
+		return
+	}
+
+	blockPadding := false
+	blockWidth := d.config.Width
+	blockHeight := d.config.Height
+	blocksAcross := 1
+	blocksDown := 1
+
+	if d.config.Width == 0 {
+		blocksAcross = 0
+	}
+	if d.config.Height == 0 {
+		blocksDown = 0
+	}
+
+	var blockOffsets, blockCounts []uint
+
+	if int(d.firstVal(tTileWidth)) != 0 {
+		blockPadding = true
+
+		blockWidth = int(d.firstVal(tTileWidth))
+		blockHeight = int(d.firstVal(tTileLength))
+
+		if blockWidth != 0 {
+			blocksAcross = (d.config.Width + blockWidth - 1) / blockWidth
+		}
+		if blockHeight != 0 {
+			blocksDown = (d.config.Height + blockHeight - 1) / blockHeight
+		}
+
+		blockCounts = d.features[tTileByteCounts]
+		blockOffsets = d.features[tTileOffsets]
+
+	} else {
+		if int(d.firstVal(tRowsPerStrip)) != 0 {
+			blockHeight = int(d.firstVal(tRowsPerStrip))
+		}
+
+		if blockHeight != 0 {
+			blocksDown = (d.config.Height + blockHeight - 1) / blockHeight
+		}
+
+		blockOffsets = d.features[tStripOffsets]
+		blockCounts = d.features[tStripByteCounts]
+	}
+
+	// Check if we have the right number of strips/tiles, offsets and counts.
+	if n := blocksAcross * blocksDown; len(blockOffsets) < n || len(blockCounts) < n {
+		return nil, FormatError("inconsistent header")
+	}
+
+	imgRect := image.Rect(0, 0, d.config.Width, d.config.Height)
+	switch d.mode {
+	case mGray, mGrayInvert:
+		if d.bpp == 16 {
+			img = image.NewGray16(imgRect)
+		} else {
+			img = image.NewGray(imgRect)
+		}
+	case mPaletted:
+		img = image.NewPaletted(imgRect, d.palette)
+	case mNRGBA:
+		if d.bpp == 16 {
+			img = image.NewNRGBA64(imgRect)
+		} else {
+			img = image.NewNRGBA(imgRect)
+		}
+	case mRGB, mRGBA:
+		if d.bpp == 16 {
+			img = image.NewRGBA64(imgRect)
+		} else {
+			img = image.NewRGBA(imgRect)
+		}
+	}
+
+	for i := 0; i < blocksAcross; i++ {
+		blkW := blockWidth
+		if !blockPadding && i == blocksAcross-1 && d.config.Width%blockWidth != 0 {
+			blkW = d.config.Width % blockWidth
+		}
+		for j := 0; j < blocksDown; j++ {
+			blkH := blockHeight
+			if !blockPadding && j == blocksDown-1 && d.config.Height%blockHeight != 0 {
+				blkH = d.config.Height % blockHeight
+			}
+			offset := int64(blockOffsets[j*blocksAcross+i])
+			n := int64(blockCounts[j*blocksAcross+i])
+			switch d.firstVal(tCompression) {
+
+			// According to the spec, Compression does not have a default value,
+			// but some tools interpret a missing Compression value as none so we do
+			// the same.
+			case cNone, 0:
+				if b, ok := d.r.(*buffer); ok {
+					d.buf, err = b.Slice(int(offset), int(n))
+				} else {
+					d.buf = make([]byte, n)
+					_, err = d.r.ReadAt(d.buf, offset)
+				}
+			case cLZW:
+				r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8)
+				d.buf, err = ioutil.ReadAll(r)
+				r.Close()
+			case cDeflate, cDeflateOld:
+				var r io.ReadCloser
+				r, err = zlib.NewReader(io.NewSectionReader(d.r, offset, n))
+				if err != nil {
+					return nil, err
+				}
+				d.buf, err = ioutil.ReadAll(r)
+				r.Close()
+			case cPackBits:
+				d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n))
+			default:
+				err = UnsupportedError(fmt.Sprintf("compression value %d", d.firstVal(tCompression)))
+			}
+			if err != nil {
+				return nil, err
+			}
+
+			xmin := i * blockWidth
+			ymin := j * blockHeight
+			xmax := xmin + blkW
+			ymax := ymin + blkH
+			err = d.decode(img, xmin, ymin, xmax, ymax)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	return
+}
+
+func init() {
+	image.RegisterFormat("tiff", leHeader, Decode, DecodeConfig)
+	image.RegisterFormat("tiff", beHeader, Decode, DecodeConfig)
+}

+ 438 - 0
vendor/golang.org/x/image/tiff/writer.go

@@ -0,0 +1,438 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tiff
+
+import (
+	"bytes"
+	"compress/zlib"
+	"encoding/binary"
+	"image"
+	"io"
+	"sort"
+)
+
+// The TIFF format allows to choose the order of the different elements freely.
+// The basic structure of a TIFF file written by this package is:
+//
+//   1. Header (8 bytes).
+//   2. Image data.
+//   3. Image File Directory (IFD).
+//   4. "Pointer area" for larger entries in the IFD.
+
+// We only write little-endian TIFF files.
+var enc = binary.LittleEndian
+
+// An ifdEntry is a single entry in an Image File Directory.
+// A value of type dtRational is composed of two 32-bit values,
+// thus data contains two uints (numerator and denominator) for a single number.
+type ifdEntry struct {
+	tag      int
+	datatype int
+	data     []uint32
+}
+
+func (e ifdEntry) putData(p []byte) {
+	for _, d := range e.data {
+		switch e.datatype {
+		case dtByte, dtASCII:
+			p[0] = byte(d)
+			p = p[1:]
+		case dtShort:
+			enc.PutUint16(p, uint16(d))
+			p = p[2:]
+		case dtLong, dtRational:
+			enc.PutUint32(p, uint32(d))
+			p = p[4:]
+		}
+	}
+}
+
+type byTag []ifdEntry
+
+func (d byTag) Len() int           { return len(d) }
+func (d byTag) Less(i, j int) bool { return d[i].tag < d[j].tag }
+func (d byTag) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
+
+func encodeGray(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
+	if !predictor {
+		return writePix(w, pix, dy, dx, stride)
+	}
+	buf := make([]byte, dx)
+	for y := 0; y < dy; y++ {
+		min := y*stride + 0
+		max := y*stride + dx
+		off := 0
+		var v0 uint8
+		for i := min; i < max; i++ {
+			v1 := pix[i]
+			buf[off] = v1 - v0
+			v0 = v1
+			off++
+		}
+		if _, err := w.Write(buf); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func encodeGray16(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
+	buf := make([]byte, dx*2)
+	for y := 0; y < dy; y++ {
+		min := y*stride + 0
+		max := y*stride + dx*2
+		off := 0
+		var v0 uint16
+		for i := min; i < max; i += 2 {
+			// An image.Gray16's Pix is in big-endian order.
+			v1 := uint16(pix[i])<<8 | uint16(pix[i+1])
+			if predictor {
+				v0, v1 = v1, v1-v0
+			}
+			// We only write little-endian TIFF files.
+			buf[off+0] = byte(v1)
+			buf[off+1] = byte(v1 >> 8)
+			off += 2
+		}
+		if _, err := w.Write(buf); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
+	if !predictor {
+		return writePix(w, pix, dy, dx*4, stride)
+	}
+	buf := make([]byte, dx*4)
+	for y := 0; y < dy; y++ {
+		min := y*stride + 0
+		max := y*stride + dx*4
+		off := 0
+		var r0, g0, b0, a0 uint8
+		for i := min; i < max; i += 4 {
+			r1, g1, b1, a1 := pix[i+0], pix[i+1], pix[i+2], pix[i+3]
+			buf[off+0] = r1 - r0
+			buf[off+1] = g1 - g0
+			buf[off+2] = b1 - b0
+			buf[off+3] = a1 - a0
+			off += 4
+			r0, g0, b0, a0 = r1, g1, b1, a1
+		}
+		if _, err := w.Write(buf); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func encodeRGBA64(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
+	buf := make([]byte, dx*8)
+	for y := 0; y < dy; y++ {
+		min := y*stride + 0
+		max := y*stride + dx*8
+		off := 0
+		var r0, g0, b0, a0 uint16
+		for i := min; i < max; i += 8 {
+			// An image.RGBA64's Pix is in big-endian order.
+			r1 := uint16(pix[i+0])<<8 | uint16(pix[i+1])
+			g1 := uint16(pix[i+2])<<8 | uint16(pix[i+3])
+			b1 := uint16(pix[i+4])<<8 | uint16(pix[i+5])
+			a1 := uint16(pix[i+6])<<8 | uint16(pix[i+7])
+			if predictor {
+				r0, r1 = r1, r1-r0
+				g0, g1 = g1, g1-g0
+				b0, b1 = b1, b1-b0
+				a0, a1 = a1, a1-a0
+			}
+			// We only write little-endian TIFF files.
+			buf[off+0] = byte(r1)
+			buf[off+1] = byte(r1 >> 8)
+			buf[off+2] = byte(g1)
+			buf[off+3] = byte(g1 >> 8)
+			buf[off+4] = byte(b1)
+			buf[off+5] = byte(b1 >> 8)
+			buf[off+6] = byte(a1)
+			buf[off+7] = byte(a1 >> 8)
+			off += 8
+		}
+		if _, err := w.Write(buf); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func encode(w io.Writer, m image.Image, predictor bool) error {
+	bounds := m.Bounds()
+	buf := make([]byte, 4*bounds.Dx())
+	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
+		off := 0
+		if predictor {
+			var r0, g0, b0, a0 uint8
+			for x := bounds.Min.X; x < bounds.Max.X; x++ {
+				r, g, b, a := m.At(x, y).RGBA()
+				r1 := uint8(r >> 8)
+				g1 := uint8(g >> 8)
+				b1 := uint8(b >> 8)
+				a1 := uint8(a >> 8)
+				buf[off+0] = r1 - r0
+				buf[off+1] = g1 - g0
+				buf[off+2] = b1 - b0
+				buf[off+3] = a1 - a0
+				off += 4
+				r0, g0, b0, a0 = r1, g1, b1, a1
+			}
+		} else {
+			for x := bounds.Min.X; x < bounds.Max.X; x++ {
+				r, g, b, a := m.At(x, y).RGBA()
+				buf[off+0] = uint8(r >> 8)
+				buf[off+1] = uint8(g >> 8)
+				buf[off+2] = uint8(b >> 8)
+				buf[off+3] = uint8(a >> 8)
+				off += 4
+			}
+		}
+		if _, err := w.Write(buf); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// writePix writes the internal byte array of an image to w. It is less general
+// but much faster then encode. writePix is used when pix directly
+// corresponds to one of the TIFF image types.
+func writePix(w io.Writer, pix []byte, nrows, length, stride int) error {
+	if length == stride {
+		_, err := w.Write(pix[:nrows*length])
+		return err
+	}
+	for ; nrows > 0; nrows-- {
+		if _, err := w.Write(pix[:length]); err != nil {
+			return err
+		}
+		pix = pix[stride:]
+	}
+	return nil
+}
+
+func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error {
+	var buf [ifdLen]byte
+	// Make space for "pointer area" containing IFD entry data
+	// longer than 4 bytes.
+	parea := make([]byte, 1024)
+	pstart := ifdOffset + ifdLen*len(d) + 6
+	var o int // Current offset in parea.
+
+	// The IFD has to be written with the tags in ascending order.
+	sort.Sort(byTag(d))
+
+	// Write the number of entries in this IFD.
+	if err := binary.Write(w, enc, uint16(len(d))); err != nil {
+		return err
+	}
+	for _, ent := range d {
+		enc.PutUint16(buf[0:2], uint16(ent.tag))
+		enc.PutUint16(buf[2:4], uint16(ent.datatype))
+		count := uint32(len(ent.data))
+		if ent.datatype == dtRational {
+			count /= 2
+		}
+		enc.PutUint32(buf[4:8], count)
+		datalen := int(count * lengths[ent.datatype])
+		if datalen <= 4 {
+			ent.putData(buf[8:12])
+		} else {
+			if (o + datalen) > len(parea) {
+				newlen := len(parea) + 1024
+				for (o + datalen) > newlen {
+					newlen += 1024
+				}
+				newarea := make([]byte, newlen)
+				copy(newarea, parea)
+				parea = newarea
+			}
+			ent.putData(parea[o : o+datalen])
+			enc.PutUint32(buf[8:12], uint32(pstart+o))
+			o += datalen
+		}
+		if _, err := w.Write(buf[:]); err != nil {
+			return err
+		}
+	}
+	// The IFD ends with the offset of the next IFD in the file,
+	// or zero if it is the last one (page 14).
+	if err := binary.Write(w, enc, uint32(0)); err != nil {
+		return err
+	}
+	_, err := w.Write(parea[:o])
+	return err
+}
+
+// Options are the encoding parameters.
+type Options struct {
+	// Compression is the type of compression used.
+	Compression CompressionType
+	// Predictor determines whether a differencing predictor is used;
+	// if true, instead of each pixel's color, the color difference to the
+	// preceding one is saved.  This improves the compression for certain
+	// types of images and compressors. For example, it works well for
+	// photos with Deflate compression.
+	Predictor bool
+}
+
+// Encode writes the image m to w. opt determines the options used for
+// encoding, such as the compression type. If opt is nil, an uncompressed
+// image is written.
+func Encode(w io.Writer, m image.Image, opt *Options) error {
+	d := m.Bounds().Size()
+
+	compression := uint32(cNone)
+	predictor := false
+	if opt != nil {
+		compression = opt.Compression.specValue()
+		// The predictor field is only used with LZW. See page 64 of the spec.
+		predictor = opt.Predictor && compression == cLZW
+	}
+
+	_, err := io.WriteString(w, leHeader)
+	if err != nil {
+		return err
+	}
+
+	// Compressed data is written into a buffer first, so that we
+	// know the compressed size.
+	var buf bytes.Buffer
+	// dst holds the destination for the pixel data of the image --
+	// either w or a writer to buf.
+	var dst io.Writer
+	// imageLen is the length of the pixel data in bytes.
+	// The offset of the IFD is imageLen + 8 header bytes.
+	var imageLen int
+
+	switch compression {
+	case cNone:
+		dst = w
+		// Write IFD offset before outputting pixel data.
+		switch m.(type) {
+		case *image.Paletted:
+			imageLen = d.X * d.Y * 1
+		case *image.Gray:
+			imageLen = d.X * d.Y * 1
+		case *image.Gray16:
+			imageLen = d.X * d.Y * 2
+		case *image.RGBA64:
+			imageLen = d.X * d.Y * 8
+		case *image.NRGBA64:
+			imageLen = d.X * d.Y * 8
+		default:
+			imageLen = d.X * d.Y * 4
+		}
+		err = binary.Write(w, enc, uint32(imageLen+8))
+		if err != nil {
+			return err
+		}
+	case cDeflate:
+		dst = zlib.NewWriter(&buf)
+	}
+
+	pr := uint32(prNone)
+	photometricInterpretation := uint32(pRGB)
+	samplesPerPixel := uint32(4)
+	bitsPerSample := []uint32{8, 8, 8, 8}
+	extraSamples := uint32(0)
+	colorMap := []uint32{}
+
+	if predictor {
+		pr = prHorizontal
+	}
+	switch m := m.(type) {
+	case *image.Paletted:
+		photometricInterpretation = pPaletted
+		samplesPerPixel = 1
+		bitsPerSample = []uint32{8}
+		colorMap = make([]uint32, 256*3)
+		for i := 0; i < 256 && i < len(m.Palette); i++ {
+			r, g, b, _ := m.Palette[i].RGBA()
+			colorMap[i+0*256] = uint32(r)
+			colorMap[i+1*256] = uint32(g)
+			colorMap[i+2*256] = uint32(b)
+		}
+		err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
+	case *image.Gray:
+		photometricInterpretation = pBlackIsZero
+		samplesPerPixel = 1
+		bitsPerSample = []uint32{8}
+		err = encodeGray(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
+	case *image.Gray16:
+		photometricInterpretation = pBlackIsZero
+		samplesPerPixel = 1
+		bitsPerSample = []uint32{16}
+		err = encodeGray16(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
+	case *image.NRGBA:
+		extraSamples = 2 // Unassociated alpha.
+		err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
+	case *image.NRGBA64:
+		extraSamples = 2 // Unassociated alpha.
+		bitsPerSample = []uint32{16, 16, 16, 16}
+		err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
+	case *image.RGBA:
+		extraSamples = 1 // Associated alpha.
+		err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
+	case *image.RGBA64:
+		extraSamples = 1 // Associated alpha.
+		bitsPerSample = []uint32{16, 16, 16, 16}
+		err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
+	default:
+		extraSamples = 1 // Associated alpha.
+		err = encode(dst, m, predictor)
+	}
+	if err != nil {
+		return err
+	}
+
+	if compression != cNone {
+		if err = dst.(io.Closer).Close(); err != nil {
+			return err
+		}
+		imageLen = buf.Len()
+		if err = binary.Write(w, enc, uint32(imageLen+8)); err != nil {
+			return err
+		}
+		if _, err = buf.WriteTo(w); err != nil {
+			return err
+		}
+	}
+
+	ifd := []ifdEntry{
+		{tImageWidth, dtShort, []uint32{uint32(d.X)}},
+		{tImageLength, dtShort, []uint32{uint32(d.Y)}},
+		{tBitsPerSample, dtShort, bitsPerSample},
+		{tCompression, dtShort, []uint32{compression}},
+		{tPhotometricInterpretation, dtShort, []uint32{photometricInterpretation}},
+		{tStripOffsets, dtLong, []uint32{8}},
+		{tSamplesPerPixel, dtShort, []uint32{samplesPerPixel}},
+		{tRowsPerStrip, dtShort, []uint32{uint32(d.Y)}},
+		{tStripByteCounts, dtLong, []uint32{uint32(imageLen)}},
+		// There is currently no support for storing the image
+		// resolution, so give a bogus value of 72x72 dpi.
+		{tXResolution, dtRational, []uint32{72, 1}},
+		{tYResolution, dtRational, []uint32{72, 1}},
+		{tResolutionUnit, dtShort, []uint32{resPerInch}},
+	}
+	if pr != prNone {
+		ifd = append(ifd, ifdEntry{tPredictor, dtShort, []uint32{pr}})
+	}
+	if len(colorMap) != 0 {
+		ifd = append(ifd, ifdEntry{tColorMap, dtShort, colorMap})
+	}
+	if extraSamples > 0 {
+		ifd = append(ifd, ifdEntry{tExtraSamples, dtShort, []uint32{extraSamples}})
+	}
+
+	return writeIFD(w, imageLen+8, ifd)
+}

+ 6 - 0
vendor/modules.txt

@@ -1,3 +1,5 @@
+# github.com/disintegration/imaging v1.6.0
+github.com/disintegration/imaging
 # github.com/go-sql-driver/mysql v1.4.1
 github.com/go-sql-driver/mysql
 # github.com/vladimirok5959/golang-server-bootstrap v1.0.5
@@ -8,5 +10,9 @@ github.com/vladimirok5959/golang-server-resources/resource
 github.com/vladimirok5959/golang-server-sessions/session
 # github.com/vladimirok5959/golang-server-static v1.0.0
 github.com/vladimirok5959/golang-server-static/static
+# golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81
+golang.org/x/image/bmp
+golang.org/x/image/tiff
+golang.org/x/image/tiff/lzw
 # google.golang.org/appengine v1.4.0
 google.golang.org/appengine/cloudsql

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