ICO support
12
Gopkg.lock
generated
@ -173,6 +173,14 @@
|
||||
pruneopts = "UT"
|
||||
revision = "ae77be60afb1dcacde03767a8c37337fad28ac14"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d33e9f151ad66f61e320511ee3a2189178b78e2d1e93f098801b4ecf6154474a"
|
||||
name = "github.com/mat/besticon"
|
||||
packages = ["ico"]
|
||||
pruneopts = "UT"
|
||||
revision = "257c937cf48f690c78c900f895e9fc4027962ac2"
|
||||
version = "v3.8.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:97e35d2efabea52337e58f96e8b798fd461bf42d2519beb50f485029c6ad6aa5"
|
||||
@ -314,9 +322,10 @@
|
||||
version = "v0.18.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:72b36febaabad58e1864de2b43de05689ce27a2c9a582a61a25e71a31ba23d0b"
|
||||
digest = "1:16d8d746fb42f8318537e77dbf22745c180db4f5b61b59a89e0fb8101bb3303e"
|
||||
name = "golang.org/x/image"
|
||||
packages = [
|
||||
"bmp",
|
||||
"riff",
|
||||
"vp8",
|
||||
"vp8l",
|
||||
@ -494,6 +503,7 @@
|
||||
"github.com/aws/aws-sdk-go/service/s3",
|
||||
"github.com/bugsnag/bugsnag-go",
|
||||
"github.com/honeybadger-io/honeybadger-go",
|
||||
"github.com/mat/besticon/ico",
|
||||
"github.com/matoous/go-nanoid",
|
||||
"github.com/newrelic/go-agent",
|
||||
"github.com/prometheus/client_golang/prometheus",
|
||||
|
@ -5,7 +5,8 @@ At the moment, imgproxy supports only the most popular Web image formats:
|
||||
* PNG;
|
||||
* JPEG;
|
||||
* WebP;
|
||||
* GIF.
|
||||
* GIF;
|
||||
* ICO.
|
||||
|
||||
## GIF support
|
||||
|
||||
@ -16,3 +17,7 @@ Since processing of animated GIFs is pretty heavy, only one frame is processed b
|
||||
* `IMGPROXY_MAX_GIF_FRAMES`: the maximum of animated GIF frames to being processed. Default: `1`.
|
||||
|
||||
**Note:** imgproxy summarizes all GIF frames resolutions while checking source image resolution.
|
||||
|
||||
## ICO support
|
||||
|
||||
imgproxy supports ICO output only when using libvips 8.7.0+ compiled with ImageMagick support. Official imgproxy Docker image supports ICO out of the box.
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
_ "github.com/mat/besticon/ico"
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
|
28
ico_data.go
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
_ "github.com/mat/besticon/ico"
|
||||
)
|
||||
|
||||
func icoData(data []byte) (out []byte, width int, height int, err error) {
|
||||
var ico image.Image
|
||||
|
||||
ico, _, err = image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure that image is in RGBA format
|
||||
rgba := image.NewRGBA(ico.Bounds())
|
||||
draw.Draw(rgba, ico.Bounds(), ico, image.ZP, draw.Src)
|
||||
|
||||
width = rgba.Bounds().Dx()
|
||||
height = rgba.Bounds().Dy()
|
||||
out = rgba.Pix
|
||||
|
||||
return
|
||||
}
|
@ -3,5 +3,6 @@ enum types {
|
||||
JPEG,
|
||||
PNG,
|
||||
WEBP,
|
||||
GIF
|
||||
GIF,
|
||||
ICO
|
||||
};
|
||||
|
15
process.go
@ -74,6 +74,9 @@ func initVips() {
|
||||
vipsTypeSupportLoad[imageTypeGIF] = true
|
||||
}
|
||||
|
||||
// we load ICO with github.com/mat/besticon/ico and send decoded data to vips
|
||||
vipsTypeSupportLoad[imageTypeICO] = true
|
||||
|
||||
if int(C.vips_type_find_save_go(C.int(imageTypeJPEG))) != 0 {
|
||||
vipsTypeSupportSave[imageTypeJPEG] = true
|
||||
}
|
||||
@ -86,6 +89,9 @@ func initVips() {
|
||||
if int(C.vips_type_find_save_go(C.int(imageTypeGIF))) != 0 {
|
||||
vipsTypeSupportSave[imageTypeGIF] = true
|
||||
}
|
||||
if int(C.vips_type_find_save_go(C.int(imageTypeICO))) != 0 {
|
||||
vipsTypeSupportSave[imageTypeICO] = true
|
||||
}
|
||||
|
||||
if conf.JpegProgressive {
|
||||
cConf.JpegProgressive = C.int(1)
|
||||
@ -606,6 +612,13 @@ func vipsLoadImage(data []byte, imgtype imageType, shrink int, allPages bool) (*
|
||||
err = C.vips_webpload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(shrink), &img)
|
||||
case imageTypeGIF:
|
||||
err = C.vips_gifload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), pages, &img)
|
||||
case imageTypeICO:
|
||||
rawData, width, height, icoErr := icoData(data)
|
||||
if icoErr != nil {
|
||||
return nil, icoErr
|
||||
}
|
||||
|
||||
img = C.vips_image_new_from_memory_copy(unsafe.Pointer(&rawData[0]), C.size_t(width*height*4), C.int(width), C.int(height), 4, C.VIPS_FORMAT_UCHAR)
|
||||
}
|
||||
if err != 0 {
|
||||
return nil, vipsError()
|
||||
@ -634,6 +647,8 @@ func vipsSaveImage(img *C.struct__VipsImage, imgtype imageType, quality int) ([]
|
||||
err = C.vips_webpsave_go(img, &ptr, &imgsize, 1, C.int(quality))
|
||||
case imageTypeGIF:
|
||||
err = C.vips_gifsave_go(img, &ptr, &imgsize)
|
||||
case imageTypeICO:
|
||||
err = C.vips_icosave_go(img, &ptr, &imgsize)
|
||||
}
|
||||
if err != 0 {
|
||||
return nil, vipsError()
|
||||
|
@ -28,6 +28,7 @@ const (
|
||||
imageTypePNG = imageType(C.PNG)
|
||||
imageTypeWEBP = imageType(C.WEBP)
|
||||
imageTypeGIF = imageType(C.GIF)
|
||||
imageTypeICO = imageType(C.ICO)
|
||||
)
|
||||
|
||||
type processingHeaders struct {
|
||||
@ -43,6 +44,7 @@ var imageTypes = map[string]imageType{
|
||||
"png": imageTypePNG,
|
||||
"webp": imageTypeWEBP,
|
||||
"gif": imageTypeGIF,
|
||||
"ico": imageTypeICO,
|
||||
}
|
||||
|
||||
type gravityType int
|
||||
|
@ -25,6 +25,7 @@ var (
|
||||
imageTypePNG: "image/png",
|
||||
imageTypeWEBP: "image/webp",
|
||||
imageTypeGIF: "image/gif",
|
||||
imageTypeICO: "image/x-icon",
|
||||
}
|
||||
|
||||
contentDispositions = map[imageType]string{
|
||||
@ -32,6 +33,7 @@ var (
|
||||
imageTypePNG: "inline; filename=\"image.png\"",
|
||||
imageTypeWEBP: "inline; filename=\"image.webp\"",
|
||||
imageTypeGIF: "inline; filename=\"image.gif\"",
|
||||
imageTypeICO: "inline; filename=\"favicon.ico\"",
|
||||
}
|
||||
|
||||
authHeaderMust []byte
|
||||
|
21
vendor/github.com/mat/besticon/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2018 Matthias Lüdtke, Hamburg - https://github.com/mat
|
||||
|
||||
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.
|
BIN
vendor/github.com/mat/besticon/ico/addthis.ico
generated
vendored
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
vendor/github.com/mat/besticon/ico/besticon.ico
generated
vendored
Normal file
After Width: | Height: | Size: 97 KiB |
BIN
vendor/github.com/mat/besticon/ico/broken.ico
generated
vendored
Normal file
After Width: | Height: | Size: 32 B |
BIN
vendor/github.com/mat/besticon/ico/codeplex.ico
generated
vendored
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
vendor/github.com/mat/besticon/ico/favicon.ico
generated
vendored
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
vendor/github.com/mat/besticon/ico/github.ico
generated
vendored
Normal file
After Width: | Height: | Size: 6.4 KiB |
261
vendor/github.com/mat/besticon/ico/ico.go
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
// Package ico registers image.Decode and DecodeConfig support
|
||||
// for the icon (container) format.
|
||||
package ico
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"image"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"image/png"
|
||||
|
||||
"golang.org/x/image/bmp"
|
||||
)
|
||||
|
||||
type icondir struct {
|
||||
Reserved uint16
|
||||
Type uint16
|
||||
Count uint16
|
||||
Entries []icondirEntry
|
||||
}
|
||||
|
||||
type icondirEntry struct {
|
||||
Width byte
|
||||
Height byte
|
||||
PaletteCount byte
|
||||
Reserved byte
|
||||
ColorPlanes uint16
|
||||
BitsPerPixel uint16
|
||||
Size uint32
|
||||
Offset uint32
|
||||
}
|
||||
|
||||
func (dir *icondir) FindBestIcon() *icondirEntry {
|
||||
if len(dir.Entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
best := dir.Entries[0]
|
||||
for _, e := range dir.Entries {
|
||||
if (e.width() > best.width()) && (e.height() > best.height()) {
|
||||
best = e
|
||||
}
|
||||
}
|
||||
return &best
|
||||
}
|
||||
|
||||
// ParseIco parses the icon and returns meta information for the icons as icondir.
|
||||
func ParseIco(r io.Reader) (*icondir, error) {
|
||||
dir := icondir{}
|
||||
|
||||
var err error
|
||||
err = binary.Read(r, binary.LittleEndian, &dir.Reserved)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = binary.Read(r, binary.LittleEndian, &dir.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = binary.Read(r, binary.LittleEndian, &dir.Count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := uint16(0); i < dir.Count; i++ {
|
||||
entry := icondirEntry{}
|
||||
e := parseIcondirEntry(r, &entry)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
dir.Entries = append(dir.Entries, entry)
|
||||
}
|
||||
|
||||
return &dir, err
|
||||
}
|
||||
|
||||
func parseIcondirEntry(r io.Reader, e *icondirEntry) error {
|
||||
err := binary.Read(r, binary.LittleEndian, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type dibHeader struct {
|
||||
dibHeaderSize uint32
|
||||
width uint32
|
||||
height uint32
|
||||
}
|
||||
|
||||
func (e *icondirEntry) ColorCount() int {
|
||||
if e.PaletteCount == 0 {
|
||||
return 256
|
||||
}
|
||||
return int(e.PaletteCount)
|
||||
}
|
||||
|
||||
func (e *icondirEntry) width() int {
|
||||
if e.Width == 0 {
|
||||
return 256
|
||||
}
|
||||
return int(e.Width)
|
||||
}
|
||||
|
||||
func (e *icondirEntry) height() int {
|
||||
if e.Height == 0 {
|
||||
return 256
|
||||
}
|
||||
return int(e.Height)
|
||||
}
|
||||
|
||||
// DecodeConfig returns just the dimensions of the largest image
|
||||
// contained in the icon withou decoding the entire icon file.
|
||||
func DecodeConfig(r io.Reader) (image.Config, error) {
|
||||
dir, err := ParseIco(r)
|
||||
if err != nil {
|
||||
return image.Config{}, err
|
||||
}
|
||||
|
||||
best := dir.FindBestIcon()
|
||||
if best == nil {
|
||||
return image.Config{}, errInvalid
|
||||
}
|
||||
return image.Config{Width: best.width(), Height: best.height()}, nil
|
||||
}
|
||||
|
||||
// The bitmap header structure we read from an icondirEntry
|
||||
type bitmapHeaderRead struct {
|
||||
Size uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
Planes uint16
|
||||
BitCount uint16
|
||||
Compression uint32
|
||||
ImageSize uint32
|
||||
XPixelsPerMeter uint32
|
||||
YPixelsPerMeter uint32
|
||||
ColorsUsed uint32
|
||||
ColorsImportant uint32
|
||||
}
|
||||
|
||||
// The bitmap header structure we need to generate for bmp.Decode()
|
||||
type bitmapHeaderWrite struct {
|
||||
sigBM [2]byte
|
||||
fileSize uint32
|
||||
resverved [2]uint16
|
||||
pixOffset uint32
|
||||
Size uint32
|
||||
Width uint32
|
||||
Height uint32
|
||||
Planes uint16
|
||||
BitCount uint16
|
||||
Compression uint32
|
||||
ImageSize uint32
|
||||
XPixelsPerMeter uint32
|
||||
YPixelsPerMeter uint32
|
||||
ColorsUsed uint32
|
||||
ColorsImportant uint32
|
||||
}
|
||||
|
||||
var errInvalid = errors.New("ico: invalid ICO image")
|
||||
|
||||
// Decode returns the largest image contained in the icon
|
||||
// which might be a bmp or png
|
||||
func Decode(r io.Reader) (image.Image, error) {
|
||||
icoBytes, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r = bytes.NewReader(icoBytes)
|
||||
dir, err := ParseIco(r)
|
||||
if err != nil {
|
||||
return nil, errInvalid
|
||||
}
|
||||
|
||||
best := dir.FindBestIcon()
|
||||
if best == nil {
|
||||
return nil, errInvalid
|
||||
}
|
||||
|
||||
return parseImage(best, icoBytes)
|
||||
}
|
||||
|
||||
func parseImage(entry *icondirEntry, icoBytes []byte) (image.Image, error) {
|
||||
r := bytes.NewReader(icoBytes)
|
||||
r.Seek(int64(entry.Offset), 0)
|
||||
|
||||
// Try PNG first then BMP
|
||||
img, err := png.Decode(r)
|
||||
if err != nil {
|
||||
return parseBMP(entry, icoBytes)
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func parseBMP(entry *icondirEntry, icoBytes []byte) (image.Image, error) {
|
||||
bmpBytes, err := makeFullBMPBytes(entry, icoBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bmp.Decode(bmpBytes)
|
||||
}
|
||||
|
||||
func makeFullBMPBytes(entry *icondirEntry, icoBytes []byte) (*bytes.Buffer, error) {
|
||||
r := bytes.NewReader(icoBytes)
|
||||
r.Seek(int64(entry.Offset), 0)
|
||||
|
||||
var err error
|
||||
h := bitmapHeaderRead{}
|
||||
|
||||
err = binary.Read(r, binary.LittleEndian, &h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if h.Size != 40 || h.Planes != 1 {
|
||||
return nil, errInvalid
|
||||
}
|
||||
|
||||
var pixOffset uint32
|
||||
if h.ColorsUsed == 0 && h.BitCount <= 8 {
|
||||
pixOffset = 14 + 40 + 4*(1<<h.BitCount)
|
||||
} else {
|
||||
pixOffset = 14 + 40 + 4*h.ColorsUsed
|
||||
}
|
||||
|
||||
writeHeader := &bitmapHeaderWrite{
|
||||
sigBM: [2]byte{'B', 'M'},
|
||||
fileSize: 14 + 40 + uint32(len(icoBytes)), // correct? important?
|
||||
pixOffset: pixOffset,
|
||||
Size: 40,
|
||||
Width: uint32(h.Width),
|
||||
Height: uint32(h.Height / 2),
|
||||
Planes: h.Planes,
|
||||
BitCount: h.BitCount,
|
||||
Compression: h.Compression,
|
||||
ColorsUsed: h.ColorsUsed,
|
||||
ColorsImportant: h.ColorsImportant,
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
if err = binary.Write(buf, binary.LittleEndian, writeHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
io.CopyN(buf, r, int64(entry.Size))
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
const icoHeader = "\x00\x00\x01\x00"
|
||||
|
||||
func init() {
|
||||
image.RegisterFormat("ico", icoHeader, Decode, DecodeConfig)
|
||||
}
|
BIN
vendor/github.com/mat/besticon/ico/wowhead.ico
generated
vendored
Normal file
After Width: | Height: | Size: 2.5 KiB |
92
vendor/github.com/mat/besticon/lettericon/fonts/LICENSE_OFL.txt
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
199
vendor/golang.org/x/image/bmp/reader.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
166
vendor/golang.org/x/image/bmp/writer.go
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
// 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) error {
|
||||
buf := make([]byte, step)
|
||||
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
|
||||
}
|
||||
}
|
||||
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
|
||||
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
|
||||
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)
|
||||
}
|
||||
return encode(w, m, step)
|
||||
}
|
13
vips.h
@ -69,6 +69,9 @@ vips_type_find_save_go(int imgtype) {
|
||||
if (imgtype == GIF) {
|
||||
return vips_type_find("VipsOperation", "magicksave_buffer");
|
||||
}
|
||||
if (imgtype == ICO) {
|
||||
return vips_type_find("VipsOperation", "magicksave_buffer");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -287,6 +290,16 @@ vips_gifsave_go(VipsImage *in, void **buf, size_t *len) {
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
vips_icosave_go(VipsImage *in, void **buf, size_t *len) {
|
||||
#if VIPS_SUPPORT_MAGICK
|
||||
return vips_magicksave_buffer(in, buf, len, "format", "ico", NULL);
|
||||
#else
|
||||
vips_error("vips_icosave_go", "Saving ICO is not supported");
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
vips_cleanup() {
|
||||
vips_thread_shutdown();
|
||||
|